Too much microphone latency

Started by kafffee,

kafffee

#20
QuoteAnd in that case, the BASS_ASIO_ChannelEnableBASS call is failing?

Ah, okay, I added BASS_STREAM_DECODE again, now it's actually playing on the right device again. Seems like BASS_ASIO_ChannelEnableBASS was failing and then it would fall back to play it via "normal" BASS. That was confusing me a bit.
However, I tried a couple of times to log the silence respectively the "nothing" to the wav file now. The first times the error did not happen again, so I was already almost giving up on it. So I gave it a last shot: and now I had this "silence" resprectively the "nothing" come up a bunch of times...
The clicking at some points persists though... What can this be caused by?

>> I wrote a little helper program to scan the wav file for values = 0 and the result is that there are no values = 0 in the data, i.e. the mixer does not produce silence but it produces nothing when the sound drops out, doesn't it?

<edit>I also tried listening to the created wav file, and the surprising result is: You can hear neither silence nor any skips!</edit>

If needed, you can check out the code of my helper program in the attached text file.

So what does all that tell us?

__________________________________

QuoteYou could create 2 splitters (with BASS_Split_StreamCreate) from "stream A", and send one of them through the tempo/etc processing. Note that "stream A" should only be played through the splitters then, not directly, eg. don't use the "stream A" handle in BASS_Mixer_StreamAddChannel calls (or BASS_ChannelGetData/Level calls).

You were thinking about something like this?:


Private _PlayBackStreamHandle As Integer
Public Property PlayBackStreamHandle As Integer
    Get
        Return _PlayBackStreamHandle
    End Get
    Set(value As Integer)
        _PlayBackStreamHandle = value
        TempoFXStreamHandleDry = BassFx.BASS_FX_TempoCreate(value, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT)

    End Set
End Property

Private Sub Play()
PrepareMixerForASIO()
If Recording.PlayBackStreamHandle <> 0 Then Bass.BASS_ChannelFree(Recording.PlayBackStreamHandle)
Recording.PlayBackStreamHandle = Bass.BASS_StreamCreate(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_END Or BASSFlag.BASS_SAMPLE_FLOAT, StreamProcedure, CType(handleGC, IntPtr))
MessageBox.Show("streamcreate: " & Bass.BASS_ErrorGetCode.ToString)

Recording.TempoFXStreamHandleDrySplit1 = BassMix.BASS_Split_StreamCreate(Recording.TempoFXStreamHandleDry, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, Nothing)
Recording.TempoFXStreamHandleDrySplit2 = BassMix.BASS_Split_StreamCreate(Recording.TempoFXStreamHandleDry, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, Nothing)

Recording.DummyHandle = Bass.BASS_StreamCreate(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, Recording.StreamProc, IntPtr.Zero)
BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, Recording.DummyHandle, BASSFlag.BASS_DEFAULT)

Bass.BASS_ChannelPlay(MainModule.Mixer, False)
End Sub

Public Class Recording

Public StreamProc As STREAMPROC = AddressOf DummyStreamCallback

 Private Sub AddWetHandle(sender As Object, e As ParallelBusWetHandle) 'create tempo FX stream of parralel bus
    If e.ParallelBusName = SuperiorTrack.TrackName Then

        Dim NewWetHandle As New ParallelBusWetHandle With {.ParallelBusName = e.ParallelBusName, .WetDummyHandle = DummyHandle, .WetTempoFXHandle = BassFx.BASS_FX_TempoCreate(TempoFXStreamHandleDrySplit2, BASSFlag.BASS_STREAM_DECODE)}

        If NewWetHandle.WetTempoFXHandle = 0 Then MessageBox.Show(Bass.BASS_ErrorGetCode.ToString) 'returns no error
        TempoFXStreamHandleWet.Add(NewWetHandle)

    End If
 End Sub

Private Function DummyStreamCallback(handle As Integer, buffer As IntPtr, length As Integer, user As IntPtr) As Integer
    ' Mixer fragt "length" Bytes an → wir liefern GENAU diese Menge
    Dim bytesRead = Bass.BASS_ChannelGetData(TempoFXStreamHandleDrySplit1, buffer, length)
    'MessageBox.Show("ChannelGetData vom Tap Stream: " & Bass.BASS_ErrorGetCode.ToString)
    If TempoFXStreamHandleDrySplit2 <> 0 Then Bass.BASS_ChannelGetData(TempoFXStreamHandleDrySplit2, buffer, length)
    Debug.WriteLine("ChannelGetData of split stream 2: " & Bass.BASS_ErrorGetCode.ToString) 'returned error: BASS_OK
    If bytesRead <= 0 Then
        Return 0 ' Ende
    End If

    Return bytesRead
End Function

Private Sub ApplyEffects()
For Each HandleModel In rec.TempoFXStreamHandleWet
    Bass.BASS_ChannelSetAttribute(HandleModel.WetTempoFXHandle, BASSAttribute.BASS_ATTRIB_TEMPO_PITCH, argVal)
    Debug.WriteLine("Effect parallel" & "(" & HandleModel.WetTempoFXHandle & "): " & Bass.BASS_ErrorGetCode.ToString)  'returns BASS_OK
Next
End Sub
End Class

I cannot hear TempoFXStreamHandleDrySplit2 in the mix. What's wrong with the code? I commented the return values/errors in the relevant lines.

Ian @ un4seen

Quote from: kafffeeThe clicking at some points persists though... What can this be caused by?

Is it the recording that's clicking in the mixer output? If so, it could be that there was no data available from the recording when the mixer needed it. You can detect whether that's the case by setting a BASS_SYNC_STALL sync on the recording via BASS_Mixer_ChannelSetSync (after BASS_Mixer_ChannelAddChannel) and see if the sync gets triggered when you hear a "click". As you're setting the BASS_MIXER_NONSTOP flag on the mixer, there will also be a small gap in the written WAV file in this case.

If you're sending ASIO input to ASIO output, it's generally best to set them both to the ASIO device's native rate to avoid resampling, eg. use BASS_ASIO_GetRate in the BASS_Mixer_StreamCreate call. If you aren't currently doing that, you could try it and see if it helps.

Quote from: kafffee>> I wrote a little helper program to scan the wav file for values = 0 and the result is that there are no values = 0 in the data, i.e. the mixer does not produce silence but it produces nothing when the sound drops out, doesn't it?

<edit>I also tried listening to the created wav file, and the surprising result is: You can hear neither silence nor any skips!</edit>

If needed, you can check out the code of my helper program in the attached text file.

So what does all that tell us?

The simplest way to check for gaps in the written WAV file is to load it in sample editing software (eg. Audacity). The gaps will be visible in the waveform.

If there are no gaps in the waveform despite using the BASS_MIXER_NONSTOP flag, then that seems to suggest the mixer isn't being processed for some reason when you hear the gaps. Perhaps there were file delays? Does the problem still happen if the mixer only contains the ASIO recording and no file streams?

kafffee

#22
QuoteIs it the recording that's clicking in the mixer output?

I muted the different channels one by one:

recording channel: No StreamPutData in the ASIO input callback function
playback channel: Return 0 in the STREAMPROC callback function

... and it's the recording as well as the normal playback channels that are clicking.

The recording seems to be only clicking on the right ear, I may be wrong though.

When I completely do not create and add...

...the playback channels: I can still hear the clicking and skips/silence
...the recording channel: I cannot hear the clicking and slips/silence (I may be wrong though, it's hard to keep speaking all the time without taking any breaths in between :-) )

So my guess is, the problem is whithin the DummyProcedure (see bottom of this post).

QuoteYou can detect whether that's the case by setting a BASS_SYNC_STALL sync on the recording via BASS_Mixer_ChannelSetSync (after BASS_Mixer_ChannelAddChannel) and see if the sync gets triggered when you hear a "click"

ChannelSetSync does not fail but the callback function never gets called, only at the very beginning when it's muted it reports "stalled", I think this is due to the fact that I do not call StreamPutData in the ASIO input callback.

QuoteIf you're sending ASIO input to ASIO output, it's generally best to set them both to the ASIO device's native rate to avoid resampling, eg. use BASS_ASIO_GetRate in the BASS_Mixer_StreamCreate call. If you aren't currently doing that, you could try it and see if it helps.

I did this but it doesn't help. All the calls do not fail:


NativeASIODevSampleRate = BassAsio.BASS_ASIO_GetRate
 BassAsio.BASS_ASIO_ChannelSetRate(True, 0, NativeASIODevSampleRate)
 BassAsio.BASS_ASIO_ChannelSetRate(False, 0, NativeASIODevSampleRate)
 BassAsio.BASS_ASIO_ChannelSetRate(True, 1, NativeASIODevSampleRate)
 BassAsio.BASS_ASIO_ChannelSetRate(False, 1, NativeASIODevSampleRate)
 MessageBox.Show("SampleRates: " & CStr(NativeASIODevSampleRate))
 MainModule.Mixer = BassMix.BASS_Mixer_StreamCreate(CInt(NativeASIODevSampleRate), MainModule.Song.NumberOfChannels, BASSFlag.BASS_MIXER_NONSTOP Or BASSFlag.BASS_SAMPLE_FLOAT Or BASSFlag.BASS_STREAM_DECODE)


QuoteThe gaps will be visible in the waveform.

In my DAW, there are no visible or audible silences or skips, not even any clicking in the wav file.

QuoteDoes the problem still happen if the mixer only contains the ASIO recording and no file streams?

I am not using file streams but I am doing this:

Private StreamProcedure As STREAMPROC = AddressOf DummyProcedure

Recording.PlayBackStreamHandle = Bass.BASS_StreamCreate(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_END Or BASSFlag.BASS_SAMPLE_FLOAT, StreamProcedure, CType(handleGC, IntPtr))
                 

Private Function DummyProcedure(handle As Integer, buffer As IntPtr, length As Integer, user As IntPtr) As Integer
    Dim rec = CType(GCHandle.FromIntPtr(user).Target, Recording)
    If rec.SuperiorTrack.IsMuted Then Return 0
   
    Dim totalSamples = rec.EndOffsetInBytes \ 4
    If rec.Cursor >= totalSamples Then
        Return 0
    End If

    ' how many samples does BASS want?
    Dim samplesRequested = length \ 4

    ' how many samples are remaining?
    Dim samplesRemaining = totalSamples - rec.Cursor

    ' actual number of samples to copy
    Dim samplesToCopy = Math.Min(samplesRequested, samplesRemaining)

 
    Dim src = rec.SampleData  'rec.SampleData is a Single array with the audio data

 
    Marshal.Copy(src, CInt(Math.Min(rec.Cursor, Integer.MaxValue)), buffer, CInt(Math.Min(samplesToCopy, Integer.MaxValue)))

    rec.Cursor += samplesToCopy

    Return CInt(Math.Min(samplesToCopy * 4, Integer.MaxValue))
End Function

Ian @ un4seen

Quote from: kafffeeIn my DAW, there are no visible or audible silences or skips, not even any clicking in the wav file.

If the written WAV file sounds perfectly fine then it looks like the clicks/gaps are probably caused by delays in the processing. Perhaps that DummyProcedure is sometimes taking too long (perhaps due to .Net garbage collection) - does the problem still happen if you remove it? You could also try increasing the ASIO buffer length in your BASS_ASIO_Start call to give more time.

kafffee

Quotedoes the problem still happen if you remove it?

Yes, instead I used StreamCreatFile without a callback and it still happened.

QuoteYou could also try increasing the ASIO buffer length in your BASS_ASIO_Start call to give more time.

Okay, I tried 2048 samples instead of the default (which should be 512 with ASIO4ALL). At first glance, this seems two work, but I still need to keep testing this. There's of course an audible increase in latency...
But the weird thing is: My ASIO4ALL Control Panel still displays a buffer size of 512. Is this normal?

___________________________________________


About the other issue regarding my parallel bus:

I cannot hear the sound of my TempoFXStreamHandleDrySplit2 stream. Could you take a look again at what I wrote about this in the second part of reply #20 ?

Ian @ un4seen

Are you using any other callback functions, eg. ASIOPROC/STREAMPROC/DSPPROC/SYNCPROC? If so, I guess it's possible that any of them could be delayed by .Net garbage collection. Does the problem still happen if you remove all callback functions? If you're currently using an ASIOPROC with BASS_ASIO_ChannelEnable for the ASIO input, you could use a "push" stream with BASS_ASIO_ChannelEnableBASS instead:

inputstream = BASS_StreamCreate(BASS_ASIO_GetRate(), 1, BASS_SAMPLE_FLOAT | BASS_STREAM_DECODE, STREAMPROC_PUSH, NULL);
BASS_ASIO_ChannelEnableBASS(true, 0, inputstream, true);

I'm not a .Net user myself, so I'm not very familiar with it, but if it turns out that garbage collection is indeed the issue then you could have a look at Microsoft's docs to see if there are any solutions (perhaps latency modes?):

    https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/

Quote from: kafffeeI cannot hear the sound of my TempoFXStreamHandleDrySplit2 stream. Could you take a look again at what I wrote about this in the second part of reply #20 ?

Where is the sample data coming from? If it's the ASIO input, then I would recommend using a push stream for that instead (as above). Even if the data is coming from somewhere else, you could probably still use a push stream instead of your own buffering and STREAMPROC.

kafffee

First, I need to clarify this: I wrote:

QuoteWhen I completely do not create and add...

...the playback channels: I can still hear the clicking and skips/silence
...the recording channel: I cannot hear the clicking and slips/silence (I may be wrong though, it's hard to keep speaking all the time without taking any breaths in between :-) )

Of course I meant the other way around: When I do not create the playback channels but the recording channel I am not sure about the clicking/gaps.

__________________________________


QuoteAre you using any other callback functions, eg. ASIOPROC/STREAMPROC/DSPPROC/SYNCPROC?

I'm currently only using one ASIOPROC callback function on the input, where I do this:

Private Function AsioInputCallback(input As Boolean, channel As Integer, buffer As IntPtr, length As Integer, user As IntPtr) As Integer

    If Not SuperiorTrack.IsMuted Then
        Bass.BASS_StreamPutData(PushStreamHandleDry, buffer, length)
    End If

    RingBuf.Write(buffer, length)

    Return length
End Function

But the problem also occurs when I do not even create the input channel.

But your idea sounds good (I like ChannelEnableBASS better). So would you just do the same thing I did in my AsioInputCallback in the STREAMPROC_PUSH handler? Would that work out the same way?

__________________________________________


QuoteWhere is the sample data coming from?

It's supposed to have the same sample data as TempoFXStreamHandleDrySplit1:

As you see in my code in reply #20, I use Recording.PlayBackStreamHandle for the original sample data, then add a tempofx channel (Recording.TempoFXStreamHandleDry) on it and then do this:

Recording.TempoFXStreamHandleDrySplit1 = BassMix.BASS_Split_StreamCreate(Recording.TempoFXStreamHandleDry, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, Nothing)
Recording.TempoFXStreamHandleDrySplit2 = BassMix.BASS_Split_StreamCreate(Recording.TempoFXStreamHandleDry, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, Nothing)

Then the second "stage" of tempo fx are applied on the tempo fx stream I created in Sub AddWetHandle().

But I can't hear any "2nd-stage-fx" in the mix.

I did it this way because you suggested to create two splitter channels in reply #19. Is this the way that you were thinking of or did I do something that's obviously wrong?

If not and I can't get this to work, I would of course also try the variety w/ the push stream.

Ian @ un4seen

Quote from: kafffeeBut your idea sounds good (I like ChannelEnableBASS better). So would you just do the same thing I did in my AsioInputCallback in the STREAMPROC_PUSH handler? Would that work out the same way?

Yes, BASSASIO will call BASS_StreamPutData to feed the ASIO input channel's data to the provided push stream. You can implement your "IsMuted" switch using BASS_ASIO_ChannelPause (and BASS_ASIO_ChannelReset to resume).

Quote from: kafffeeAs you see in my code in reply #20, I use Recording.PlayBackStreamHandle for the original sample data, then add a tempofx channel (Recording.TempoFXStreamHandleDry) on it and then do this:

Recording.TempoFXStreamHandleDrySplit1 = BassMix.BASS_Split_StreamCreate(Recording.TempoFXStreamHandleDry, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, Nothing)
Recording.TempoFXStreamHandleDrySplit2 = BassMix.BASS_Split_StreamCreate(Recording.TempoFXStreamHandleDry, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, Nothing)

Then the second "stage" of tempo fx are applied on the tempo fx stream I created in Sub AddWetHandle().

But I can't hear any "2nd-stage-fx" in the mix.

Are you sure it's being played/processed, eg. does BASS_ChannelGetPosition show it advancing?

In the most recent posted code, PlayBackStreamHandle doesn't appear to be played, ie. no BASS_Mixer_StreamAddChannel call with it?

kafffee

Okay, I think I managed to realize that parallel bus thing on the playback streams with the push stream instead of the split streams. Still got to get it working on the recording channels though:

I am having trouble with this (not sure if I'm doing this right):

If Recording.IsRecording Then

     If Recording.PushStreamHandleDry <> 0 Then Bass.BASS_ChannelFree(Recording.PushStreamHandleDry)

     Recording.PushStreamHandleDry = Bass.BASS_StreamCreatePush(MainModule.Song.SampleRate, 1, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, IntPtr.Zero)


     If MainModule.Settings.AudioAPI = Enums.AudioAPIs.ASIO Then

         PrepareMixerForASIO()


         Recording.PlayBackStreamHandle = Bass.BASS_StreamCreate(MainModule.Song.SampleRate, 1, BASSFlag.BASS_STREAM_DECODE, Recording.InputStreamProc, Nothing)
         BassAsio.BASS_ASIO_ChannelEnableBASS(True, 0, Recording.PlayBackStreamHandle, True)

         BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, Recording.TempoFXStreamHandleDry, BASSFlag.BASS_DEFAULT)


         BassAsio.BASS_ASIO_ChannelSetFormat(True, 0, BASSASIOFormat.BASS_ASIO_FORMAT_32BIT)

Public Class Recording

Private _PlayBackStreamHandle As Integer
Public Property PlayBackStreamHandle As Integer
    Get
        Return _PlayBackStreamHandle
    End Get
    Set(value As Integer)
        _PlayBackStreamHandle = value
        TempoFXStreamHandleDry = BassFx.BASS_FX_TempoCreate(value, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT)

    End Set
End Property

Public InputStreamProc As STREAMPROC = AddressOf InputCallback

Private Function InputCallback(handle As Integer, buffer As IntPtr, length As Integer, user As IntPtr) As Integer
    If Not SuperiorTrack.IsMuted Then
        Bass.BASS_StreamPutData(PushStreamHandleDry, buffer, length)
    End If

    RingBuf.Write(buffer, length)

    Return length
End Function

End Class

I'm only hearing a loud noise instead of the sound from my mic. How do I do this right? You wrote to do it this way:


Quoteyou could use a "push" stream with BASS_ASIO_ChannelEnableBASS instead

inputstream = BASS_StreamCreate(BASS_ASIO_GetRate(), 1, BASS_SAMPLE_FLOAT | BASS_STREAM_DECODE, STREAMPROC_PUSH, NULL);
BASS_ASIO_ChannelEnableBASS(true, 0, inputstream, true);

I only found this way to create  a push stream in radio42's docs, and that overload does not take a STREAMPROC:

Recording.PushStreamHandleDry = Bass.BASS_StreamCreatePush(MainModule.Song.SampleRate, 1, BASSFlag.BASS_STREAM_DECODE, IntPtr.Zero)

Ian @ un4seen

Yeah, with BASS.Net you can use BASS_StreamCreatePush instead of BASS_StreamCreate(STREAMPROC_PUSH).

Regarding the noise, that could be due to the BASS_ASIO_ChannelSetFormat call setting the wrong format (BASS_ASIO_FORMAT_32BIT). You can remove that call because BASS_ASIO_ChannelEnableBASS sets the ASIO channel format.

kafffee

Okay the following code seems to be working, but there's a little delay in the TempoFXStreamHandleWet(0).WetTempoFXHandle.


Is there a way to play it sample-accurate synchronously to PushStreamHandleDry?:

Private Sub Play_Execute(obj As Object)
    If MainModule.Song Is Nothing Then Return
    BassAsio.BASS_ASIO_Init(0, BASSASIOInit.BASS_ASIO_DEFAULT)

    For Each Track In MainModule.Song.Tracks
        For Each Recording In Track.Recordings
            handleGC = GCHandle.Alloc(Recording, GCHandleType.Normal)


            If Recording.IsRecording Then


                If MainModule.Einstellungen.AudioAPI = Enums.AudioAPIs.ASIO Then

                    PrepareMixerForASIO()

                    If Recording.PushStreamHandleDry <> 0 Then Bass.BASS_ChannelFree(Recording.PushStreamHandleDry)
                    Recording.PushStreamHandleDry = Bass.BASS_StreamCreatePush(MainModule.Song.SampleRate, 1, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, IntPtr.Zero)
                    Recording.TempoFXStreamHandleDry = BassFx.BASS_FX_TempoCreate(Recording.PushStreamHandleDry, BASSFlag.BASS_STREAM_DECODE)
                    BassAsio.BASS_ASIO_ChannelEnableBASS(True, 0, Recording.PushStreamHandleDry, True)
                    Recording.EnqueueTimer.IsEnabled = True

Else

                PrepareMixerForASIO()

                If Recording.PlayBackStreamHandle <> 0 Then Bass.BASS_ChannelFree(Recording.PlayBackStreamHandle)
                ' Recording.PlayBackStreamHandle = Bass.BASS_StreamCreate(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_END Or BASSFlag.BASS_SAMPLE_FLOAT, StreamProcedure, CType(handleGC, IntPtr)) 'funktioniert
                Dim filename As String
                If Recording.SuperiorTrack.TrackName = "Spur 0" Then
                    filename = "C:\1.mp3"
                ElseIf Recording.SuperiorTrack.TrackName = "Spur 1" Then
                    filename = "C:\2.mp3"
                End If
                Recording.PlayBackStreamHandle = Bass.BASS_StreamCreateFile(filename, 0, 0, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_END Or BASSFlag.BASS_SAMPLE_FLOAT) '(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_END Or BASSFlag.BASS_SAMPLE_FLOAT, StreamProcedure, CType(handleGC, IntPtr))

                Recording.PushStreamHandleDry = Bass.BASS_StreamCreatePush(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, IntPtr.Zero)
                Recording.TempoFXStreamHandleDry = BassFx.BASS_FX_TempoCreate(Recording.PlayBackStreamHandle, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT)

                BassAsio.BASS_ASIO_ChannelEnableBASS(False, 0, MainModule.Mixer, True)

            End If
            BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, Recording.TempoFXStreamHandleDry, BASSFlag.BASS_DEFAULT)
            Bass.BASS_ChannelSetDSP(Recording.TempoFXStreamHandleDry, Recording.DSPProc, IntPtr.Zero, 0)
        Next

    Next
    BassAsio.BASS_ASIO_Start(1536)
    Bass.BASS_ChannelPlay(MainModule.Mixer, False)

End Sub

Public Class Recording

 Public DSPProc As DSPPROC = AddressOf DSPInputCallback

Private Sub DSPInputCallback(handle As Integer, channel As Integer, buffer As IntPtr, length As Integer, user As IntPtr)
    If TempoFXStreamHandleWet IsNot Nothing AndAlso TempoFXStreamHandleWet.Count > 0 Then
        Bass.BASS_StreamPutData(TempoFXStreamHandleWet(0).WetPushHandle, buffer, length)
    End If

    RingBuf.Write(buffer, length)

End Sub

 Private Sub AddWetHandle(sender As Object, e As ParallelBusWetHandle)
     If e.ParallelBusName = SuperiorTrack.TrackName Then
         Dim num As Integer
         If IsRecording Then num = 1 Else num = 2
         Dim PushHandle = Bass.BASS_StreamCreatePush(MainModule.Song.SampleRate, num, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, IntPtr.Zero)
         Dim NewWetHandle As New ParallelBusWetHandle With {.ParallelBusName = e.ParallelBusName, .WetPushHandle = PushHandle, .WetTempoFXHandle = BassFx.BASS_FX_TempoCreate(PushHandle, BASSFlag.BASS_STREAM_DECODE)}
         TempoFXStreamHandleWet.Add(NewWetHandle)
         BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, NewWetHandle.WetTempoFXHandle, BASSFlag.BASS_DEFAULT)
     End If
 End Sub

End Class

Ian @ un4seen

If I understand correctly, you're playing the ASIO input data with (NewWetHandle.WetTempoFXHandle) and without (Recording.TempoFXStreamHandleDry) tempo processing? I think the tempo processing is likely to take them out of sync, and could be the cause of the clicks you mentioned earlier (if the tempo processing needs more data). To confirm whether that's the case, are they still out of sync if you don't enable tempo processing?

How is the MainModule.Song.SampleRate value set? I would suggest setting it to BASS_ASIO_GetRate to avoid resampling of the data between input and output. If you want to prefer a certain rate, you can try setting that in a BASS_ASIO_SetRate call first (before calling BASS_ASIO_GetRate).

kafffee

QuoteIf I understand correctly, you're playing the ASIO input data with (NewWetHandle.WetTempoFXHandle) and without (Recording.TempoFXStreamHandleDry) tempo processing?

I have enabled tempo processing on the input as well as the playback streams (both are called Recording.TempoFXStreamHandleDry), but currently have not set the BASS_ATTRIB_TEMPO_PITCH to anything other than 0.0 on them (other than the parallel bus effects, which are being applied via NewWetHandle.WetTempoFXHandle)

QuoteTo confirm whether that's the case, are they still out of sync if you don't enable tempo processing?

Yes, there's still an audible difference when I don't enable tempo processing on the parallel bus, but are still adding the push stream directly to the mixer, like this:

Dim PushHandle = Bass.BASS_StreamCreatePush(MainModule.Song.SampleRate, num, flags, IntPtr.Zero)
Dim NewWetHandle As New ParallelBusWetHandle With {.ParallelBusName = e.ParallelBusName, .WetPushHandle = PushHandle, .WetTempoFXHandle = 0} 'BassFx.BASS_FX_TempoCreate(PushHandle, BASSFlag.BASS_STREAM_DECODE)}
TempoFXStreamHandleWet.Add(NewWetHandle)
'BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, NewWetHandle.WetTempoFXHandle, BASSFlag.BASS_DEFAULT)
BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, NewWetHandle.WetPushHandle, BASSFlag.BASS_DEFAULT)

What I am trying to do is something like a light-weight DAW. It's supposed to take the signal from a track repsectively recording, then add a pitch effect to it and change the volume thereof, then redirect that signal to the mixer, in sync with the original recording. Something like a parallel bus, or also called AUX-bus.

QuoteHow is the MainModule.Song.SampleRate value set?

It's been set to 44100. Just to make sure, I changed it to a readonly property which always returns  BASS_ASIO_GetRate.

Ian @ un4seen

Quote from: kafffeeI have enabled tempo processing on the input as well as the playback streams (both are called Recording.TempoFXStreamHandleDry), but currently have not set the BASS_ATTRIB_TEMPO_PITCH to anything other than 0.0 on them (other than the parallel bus effects, which are being applied via NewWetHandle.WetTempoFXHandle)

Does that mean you have 2 tempo streams with the same source data, and one of them (TempoFXStreamHandleDry) has BASS_ATTRIB_TEMPO_PITCH=0 and the other (WetTempoFXHandle) has BASS_ATTRIB_TEMPO_PITCH!=0? If so, I think the differing BASS_ATTRIB_TEMPO_PITCH settings is quite likely to take them a bit out of sync, and that may also vary because the tempo processing's rate isn't constant (it processes varying sized blocks of data).

If you aren't already using it, you could try the latest BASS_FX build (which has the latest SoundTouch tempo processing):

    www.un4seen.com/stuff/bass_fx.zip

Quote from: kafffeeWhat I am trying to do is something like a light-weight DAW. It's supposed to take the signal from a track repsectively recording, then add a pitch effect to it and change the volume thereof, then redirect that signal to the mixer, in sync with the original recording. Something like a parallel bus, or also called AUX-bus.

Are you sure you need mutiple tempo streams in this case? Wouldn't you only need one? You could create 2 splitters from the original recording, and plug one directly into the mixer, and the other going through tempo processing. For example:

directsplit = BASS_Split_StreamCreate(original, BASS_STREAM_DECODE, 0); // create splitter from original
BASS_Mixer_StreamAddChannel(mixer, directsplit, 0); // add it to the mix
temposplit = BASS_Split_StreamCreate(original, BASS_STREAM_DECODE, 0); // create tempo processing splitter from original
temposplit = BASS_FX_TempoCreate(temposplit, BASS_STREAM_DECODE | BASS_FX_FREESOURCE); // enable tempo processing
BASS_Mixer_StreamAddChannel(mixer, temposplit, 0); // add it to the mix

Note that you don't add the "original" handle directly to the mixer when splitting it (due to the data stealing issue mentioned earlier).

kafffee

QuoteDoes that mean you have 2 tempo streams with the same source data, and one of them (TempoFXStreamHandleDry) has BASS_ATTRIB_TEMPO_PITCH=0 and the other (WetTempoFXHandle) has BASS_ATTRIB_TEMPO_PITCH!=0?

No, WetTempoFXHandle gets its data from TempoFXStreamHandleDry, but not directly:

I have this:

Bass.BASS_ChannelSetDSP(Recording.TempoFXStreamHandleDry, Recording.DSPProc, IntPtr.Zero, 0)

[...]

Public DSPProc As DSPPROC = AddressOf DSPInputCallback

Private Sub DSPInputCallback(handle As Integer, channel As Integer, buffer As IntPtr, length As Integer, user As IntPtr)
    If TempoFXStreamHandleWet IsNot Nothing AndAlso TempoFXStreamHandleWet.Count > 0 Then
        If Not SuperiorTrack.IsMuted Then
            Bass.BASS_StreamPutData(TempoFXStreamHandleWet(0).WetPushHandle, buffer, length)

        End If
    End If

    If IsRecording Then RingBuf.Write(buffer, length)

End Sub

When I add the parallel bus fx, this happens:

Dim PushHandle = Bass.BASS_StreamCreatePush(MainModule.Song.SampleRate, num, flags, IntPtr.Zero)
 Dim NewWetHandle As New ParallelBusWetHandle With {.ParallelBusName = e.ParallelBusName, .WetPushHandle = PushHandle, .WetTempoFXHandle = BassFx.BASS_FX_TempoCreate(PushHandle, BASSFlag.BASS_STREAM_DECODE)}
 TempoFXStreamHandleWet.Add(NewWetHandle)
 BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, NewWetHandle.WetTempoFXHandle, BASSFlag.BASS_DEFAULT)

Note that even if I set both TempoFXStreamHandledry and WetTempoFXHandle and to BASS_ATTRIB_TEMPO_PITCH = 0.0 there is an audible difference after I added WetTempoFXHandle to the mix.

__________________

I am currently using bassfx version 2.4.12.6, don't know if that's the latest version?

__________________

QuoteAre you sure you need mutiple tempo streams in this case? Wouldn't you only need one? You could create 2 splitters from the original recording, and plug one directly into the mixer, and the other going through tempo processing.

Actually not for my own personal use. But I am trying to implement this some kind of "generic" (don't know if that's the right word for this): I have a source track with effects (whether it's "normal" ones or tempo fx) added to it, then I can create a parallel bus, that has this track as a source and adds other fx to it. In the ideal case, it will play in sync with the source channel, only with the additional fx to it.

___________________

If I decide to do this w/ the splitters, will this allow me to playback the 2 streams totally in sync (sample-accurate) or will the adding of fx always delay the signal a bit?

Ian @ un4seen

Quote from: kafffeeI have this:

Bass.BASS_ChannelSetDSP(Recording.TempoFXStreamHandleDry, Recording.DSPProc, IntPtr.Zero, 0)

[...]

Public DSPProc As DSPPROC = AddressOf DSPInputCallback

Private Sub DSPInputCallback(handle As Integer, channel As Integer, buffer As IntPtr, length As Integer, user As IntPtr)
    If TempoFXStreamHandleWet IsNot Nothing AndAlso TempoFXStreamHandleWet.Count > 0 Then
        If Not SuperiorTrack.IsMuted Then
            Bass.BASS_StreamPutData(TempoFXStreamHandleWet(0).WetPushHandle, buffer, length)

        End If
    End If

    If IsRecording Then RingBuf.Write(buffer, length)

End Sub

That looks like WetPushHandle does ultimately get the same data as TempoFXStreamHandleDry? Instead of using a DSP function on TempoFXStreamHandleDry to forward the data to WetPushHandle, you could use splitters on the source (like in the snippet in my previous post). Using a DSP function on one stream to feed another stream depends on the 1st stream being processed before the 2nd stream, otherwise there will be a delay in the 2nd stream (it won't see the data until the next update cycle).

Quote from: kafffeeI am currently using bassfx version 2.4.12.6, don't know if that's the latest version?

That is the current release version, but not the latest build. You could try the latter (posted above) and see if it helps. I did a quick comparison of them with BASS_ATTRIB_TEMPO_PITCH=1, and both actually made the sound a bit earlier than in the original (a negative delay), but the difference was smaller with the latest build.

kafffee

#36
QuoteUsing a DSP function on one stream to feed another stream depends on the 1st stream being processed before the 2nd stream, otherwise there will be a delay in the 2nd stream (it won't see the data until the next update cycle).

Ah okay, that's just what I thought.

Quoteyou could use splitters on the source

Okay, I will try that. By the way, is it possible to create a split stream from another split stream, just in case I still want to have two tempo effects in a row, i.e. use the "wet signal" from the source channel to create another tempo stream from it?

______________________________

Edit:

Tried that option w/ the split streams. Even when I'm using the "dry" source signal, there is a well audible delay. Which makes sense, the computer takes a bit time to calculate the pitch. But is there a way, to eiher compensate that or decrease it? Maybe one of the following?:

1. Measure the delay of the tempo stream then playback the source with this delay?
2. Use a VST
3. Use another function than the one that comes with bass_fx?
4. anything other than that?

PS: I am using the latest bass_fx build now.

Ian @ un4seen

Quote from: kafffeeOkay, I will try that. By the way, is it possible to create a split stream from another split stream, just in case I still want to have two tempo effects in a row, i.e. use the "wet signal" from the source channel to create another tempo stream from it?

Yes, you can create splitter streams from another splitter stream, ie. use a splitter stream handle in BASS_Split_StreamCreate calls.

Quote from: kafffeeTried that option w/ the split streams. Even when I'm using the "dry" source signal, there is a well audible delay. Which makes sense, the computer takes a bit time to calculate the pitch.

Please post that code, to show how you're creating and starting the streams that you're seeing a delay between. Also, how long is the delay?

kafffee

QuotePlease post that code

The creation of the recording channels:

 PrepareMixerForASIO()

 If Recording.PushStreamHandleDry <> 0 Then Bass.BASS_ChannelFree(Recording.PushStreamHandleDry)

 Recording.PushStreamHandleDry = Bass.BASS_StreamCreatePush(MainModule.Song.SampleRate, 1, BASSFlag.BASS_STREAM_DECODE, IntPtr.Zero)
 MessageBox.Show("streamcreatepush: " & Bass.BASS_ErrorGetCode.ToString & "; " & Recording.SuperiorTrack.TrackName)
 'Recording.TempoFXStreamHandleDry = BassFx.BASS_FX_TempoCreate(Recording.PushStreamHandleDry, BASSFlag.BASS_STREAM_DECODE)
 Recording.SplitOriginal = BassMix.BASS_Split_StreamCreate(Recording.PushStreamHandleDry, BASSFlag.BASS_STREAM_DECODE, Nothing)

 BassAsio.BASS_ASIO_ChannelEnableBASS(True, 0, Recording.PushStreamHandleDry, True)
 BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, Recording.SplitOriginal, BASSFlag.BASS_DEFAULT)

respectively the normal playback channels:

PrepareMixerForASIO()

If Recording.PlayBackStreamHandle <> 0 Then Bass.BASS_ChannelFree(Recording.PlayBackStreamHandle)
' Recording.PlayBackStreamHandle = Bass.BASS_StreamCreate(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_END Or BASSFlag.BASS_SAMPLE_FLOAT, StreamProcedure, CType(handleGC, IntPtr)) 'funktioniert

Recording.PlayBackStreamHandle = Bass.BASS_StreamCreateFile(filename, 0, 0, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_END Or BASSFlag.BASS_SAMPLE_FLOAT) '(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_END Or BASSFlag.BASS_SAMPLE_FLOAT, StreamProcedure, CType(handleGC, IntPtr))

'Recording.TempoFXStreamHandleDry = BassFx.BASS_FX_TempoCreate(Recording.PlayBackStreamHandle, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT)
Recording.SplitOriginal = BassMix.BASS_Split_StreamCreate(Recording.PlayBackStreamHandle, BASSFlag.BASS_STREAM_DECODE, Nothing)
MessageBox.Show("err...:" & Bass.BASS_ErrorGetCode.ToString)


BassAsio.BASS_ASIO_ChannelEnableBASS(False, 0, MainModule.Mixer, True)
BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, Recording.SplitOriginal, BASSFlag.BASS_DEFAULT)

Then adding the fx of the parallel bus:

Private Sub AddWetHandle(sender As Object, e As ParallelBusWetHandle)
    If e.ParallelBusName = SuperiorTrack.TrackName Then
        Dim num As Integer
        Dim flags As BASSFlag = BASSFlag.BASS_STREAM_DECODE
        If IsRecording Then
            num = 1
        Else
            num = 2
            flags = flags Or BASSFlag.BASS_SAMPLE_FLOAT
        End If
     
        Dim SplitForParBus As Integer = BassMix.BASS_Split_StreamCreate(PushStreamHandleDry, BASSFlag.BASS_STREAM_DECODE, Nothing)
        Dim NewWetHandle As New ParallelBusWetHandle With {.ParallelBusName = e.ParallelBusName, .WetPushHandle = SplitForParBus, .WetTempoFXHandle = BassFx.BASS_FX_TempoCreate(SplitForParBus, BASSFlag.BASS_STREAM_DECODE)}
        TempoFXStreamHandleWet.Add(NewWetHandle)
        BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, NewWetHandle.WetTempoFXHandle, BASSFlag.BASS_DEFAULT)
        'BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, NewWetHandle.WetPushHandle, BASSFlag.BASS_DEFAULT)
        'MessageBox.Show("streamaddchannel push: " & Bass.BASS_ErrorGetCode.ToString & "; " & SuperiorTrack.TrackName)
    End If
End Sub

_______________


QuoteAlso, how long is the delay?

Is there a way to measure it?

I can definitely say that for instance in case of the playback channels, it sounds "richer"/fuller (even if pitch is set to 0.0), and in case of the recording channels, it's more than that: You can actually hear what I'm speaking in the mic twice, i.e. it's more than a doubling effect, you can actually tell the original signal from the parallel bus signal by ear, like it's been spoken twice.

Ian @ un4seen

You can measure a delay between the mixer sources by setting a WAV writer (BASS_Encode_StartPCMFile) on the mixer (MainModule.Mixer), and playing a file containing a very short pulse of sound (eg. sinewave) through it, and then loading the written WAV file in a sample editor to see if there are now multiple pulses and what the gap is between them. If there's no delay, then there should just be one louder pulse (due to mixing multiple copies of the same sound).