Too much microphone latency

Started by kafffee,

kafffee

Hello  :)

I am trying to record from my usb microphone but the latency is too high, almost at about one second. At first I tried to use the "normal" BASS_RecordStart, then I tried WASAPI and  right now ASIO4ALL driver. There is no improvement.

There was a point though, when latency was perfect (almost instantly) with ASIO4ALL, but it sounded somewhat like Donald Duck, because I guess I set the sample rate wrong. However, right now I am unable to get back to this point, but at least I know it's "physically doable". So I tried to reduce my code to the minimum. This is what I got so far in VB.NET:

Private Sub StartRecording

For Each Track In MainModule.Song.Tracks
    For Each Recording In Track.Recordings
       
        If Recording.IsRecording Then
            If Recording.PushStreamHandle <> 0 Then Bass.BASS_ChannelFree(Recording.PushStreamHandle)
            Recording.PushStreamHandle = Bass.BASS_StreamCreatePush(MainModule.Song.SampleRate, 1, BASSFlag.BASS_STREAM_DECODE, IntPtr.Zero)
            BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, Recording.PushStreamHandle, BASSFlag.BASS_DEFAULT)

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

                BassAsio.BASS_ASIO_Init(0, BASSASIOInit.BASS_ASIO_DEFAULT)
                BassAsio.BASS_ASIO_ChannelEnable(True, 0, Recording.ASIOProc, IntPtr.Zero)
                BassAsio.BASS_ASIO_ChannelSetFormat(True, 0, BASSASIOFormat.BASS_ASIO_FORMAT_16BIT)
                BassAsio.BASS_ASIO_ChannelSetRate(True, 0, 44100)
                BassAsio.BASS_ASIO_Start(0)

        End If
        End If
  Next
Next
End Sub


Public Class Recording
Public ASIOProc As ASIOPROC = AddressOf AsioCallback

Public Function AsioCallback(input As Boolean, channel As Integer, buffer As IntPtr, length As Integer, user As IntPtr) As Integer
    Bass.BASS_StreamPutData(PushStreamHandle, buffer, length)
    Return length
End Function
End Class

In the attachments, you can see my device/ASIO4ALL settings.

Device settings:
1 channel, 16 bits, 44100Hz

ASIO4ALL settings:
Output: 2x 32-48kHz, 16 Bits
Input: 2x 11-48kHz, 16 Bits
Buffer size: 512 samples
Latency compensation: Input 16 samples; Output 16 Samples
Hardware-Buffer: deactivated
Always convert 44.1kHz<>48kHz: deactivated
Always open WDM driver with 16 bits: activated

Is there anything obviously wrong with my code/settings (or the combination of either) or what could I do to achieve what I already had?

Ian @ un4seen

One issue I see there is that the push stream (Recording.PushStreamHandle) may have the wrong sample rate. That needs to be the same as the recording, so 44100 rather than "MainModule.Song.SampleRate" in the BASS_StreamCreatePush call.

If the problem persists, try logging the BASS_StreamPutData return value to see how much data is queued and if it's rising.

kafffee

#2
MainModule.Song.SampleRate is at 44100.

I logged the BASS_StreamPutData return values and also added a stopwatch to detect the interval that the callback function is being called wit this code:

Public sw As New Stopwatch

Public Function AsioCallback(input As Boolean, channel As Integer, buffer As IntPtr, length As Integer, user As IntPtr) As Integer
    Dim now = sw.ElapsedMilliseconds
    Debug.WriteLine("Block @ " & now & " ms, size=" & length)
    Debug.WriteLine("BASS_StreamPutData return value: " & Bass.BASS_StreamPutData(PushStreamHandle, buffer, length))
    Return length
End Function

Also I did this before the actual recording, thought it would be good to know:

Private Sub GetLatencyInfo()
    Dim info As BASS_ASIO_INFO = BassAsio.BASS_ASIO_GetInfo()
    Dim chInfo As BASS_ASIO_CHANNELINFO = BassAsio.BASS_ASIO_ChannelGetInfo(True, 0)

    Dim asioBufferSize As Integer = info.bufpref
    Dim sampleRate As Double = BassAsio.BASS_ASIO_GetRate()


    Debug.WriteLine("ASIO BufferSize: " & asioBufferSize)
    Dim latencyMs As Double = (asioBufferSize / sampleRate) * 1000.0 * 2 'ASIO double-buffer

    Debug.WriteLine("Calculated Input-Latency: " & latencyMs.ToString("0.00") & " ms")

End Sub

You can find the results in the attached zipped text file, I logged a couple of seconds. The BASS_StreamPutData return values are actually rising in the first ten calls, then this pattern seems to be more or less repeating (except for the first two values) obviously. So what does that tell us? I guess that's not the way it's supposed to be?

Ian @ un4seen

That log seems OK. It suggests there's 100ms of latency due to the output processing rate, ie. the output is being processed every 100ms. Are you sure there's 1 second of latency? How is MainModule.Mixer (with Recording.PushStreamHandle plugged into it) being played? If you would like to minimize its latency, you can disable playback buffering on it via BASS_ATTRIB_BUFFER:

Bass.BASS_ChannelSetAttribute(MainModule.Mixer, BASSAttribute.BASS_ATTRIB_BUFFER, 0) ' disable playback buffering

kafffee

Okay, awesome, setting the output buffer to 0 did it :-) Thanks!

Now one more thing: For some reason, I am hearing the non-recording source channels as well as the microphone source channel only on the left ear? What's up with that? I tried to use BASSFlag.BASS_MIXER_MATRIX as well as BASSFlag.BASS_MIXER_DOWNMIX, but no success... Here goes my current code (MainModule.Song.NumberOfChannels is set to 2):

Private Sub PrepareMixerForASIO()
    If MainModule.Mixer = 0 Then
        MainModule.Mixer = BassMix.BASS_Mixer_StreamCreate(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_NONSTOP Or BASSFlag.BASS_MIXER_CHAN_DOWNMIX)
        Bass.BASS_ChannelSetAttribute(MainModule.Mixer, BASSAttribute.BASS_ATTRIB_BUFFER, 0) ' disable playback buffering
        Bass.BASS_ChannelSetSync(MainModule.Mixer, BASSSync.BASS_SYNC_MIXTIME, 0, Nothing, IntPtr.Zero)
    End If
End Sub

Private Sub Play_Execute(obj As Object)
    If MainModule.Song Is Nothing Then Return

    For Each Track In MainModule.Song.Tracks
        For Each Recording In Track.Recordings
            handleGC = GCHandle.Alloc(Recording, GCHandleType.Normal)
            If Recording.IsRecording Then   'recording channel
                If Recording.PushStreamHandle <> 0 Then Bass.BASS_ChannelFree(Recording.PushStreamHandle)

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


                If MainModule.Settings.AudioAPI = Enums.AudioAPIs.ASIO Then
                    PrepareMixerForASIO()

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

                    'Dim matrix(,) As Single = {{1.0F, 1.0F}}
                    'BassMix.BASS_Mixer_ChannelSetMatrix(Recording.PushStreamHandle, matrix)

                    BassAsio.BASS_ASIO_Init(0, BASSASIOInit.BASS_ASIO_DEFAULT)
                    BassAsio.BASS_ASIO_ChannelEnable(True, 0, Recording.ASIOInputProc, IntPtr.Zero)  'input
                    BassAsio.BASS_ASIO_ChannelEnable(False, 0, Recording.ASIOOutputProc, IntPtr.Zero)  'output

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

                    BassAsio.BASS_ASIO_ChannelSetRate(True, 0, 44100)
                    BassAsio.BASS_ASIO_ChannelSetRate(False, 0, 44100)
                    GetLatencyInfo()
                    Recording.sw.Start()
                    BassAsio.BASS_ASIO_Start(0)
                End If
           Else    'non recording channel
        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))
                BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, Recording.PlayBackStreamHandle, BASSFlag.BASS_DEFAULT)
        End If
    Next
Next
Bass.BASS_ChannelPlay(MainModule.Mixer, False)
End Sub

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

    Dim bytesRead = Bass.BASS_ChannelGetData(MainModule.Mixer, buffer, length)

    If bytesRead = -1 Then
        ' Mixer hat nichts → Buffer mit 0 füllen
        Marshal.Copy(New Byte(length - 1) {}, 0, buffer, length)
        Return length
    End If

    Return bytesRead
End Function


Ian @ un4seen

To play a stereo stream via ASIO, you will need to enable 2 ASIO output channels and join them. The simplest way to do that is to use BASS_ASIO_ChannelEnableBASS, which will automatically setup the ASIO channels appropriately. For example:

BassAsio.BASS_ASIO_ChannelEnableBASS(false, 0, MainModule.Mixer, true)

Please see the BASS_ASIO_ChannelEnableBASS documentation for details.

Btw, BASS_MIXER_CHAN_DOWNMIX (and all other BASS_MIXER_CHAN_xxx) isn't a valid flag for BASS_Mixer_StreamCreate, so you should remove that from your call. When needed, that flag would be used in BASS_Mixer_StreamAddChannel calls instead.

kafffee

Sounds good :-) So I need to call BassAsio.BASS_ASIO_ChannelEnableBASS only once, i.e. for the first output channel?

And: If I pass MainModule.Mixer as an argument for instance, my ASIO device will always be automatically set to the sample rate and bit depth of the this mixer channel? I don't have to worry about this at all?

What if (on an input channel) I do need a callback function though, because I need to visualize and pass the data, which one of the following would you recommend?:

1. Call both BassAsio.BASS_ASIO_ChannelEnable w/ callback and BassAsio.BASS_ASIO_ChannelEnableBASS?
2. Only call BassAsio.BASS_ASIO_ChannelEnable w/ a callback?

I would like to adapt the ASIO input automatically as well, because it makes it much easier for me as a programmer as well as for the user later on...

Ian @ un4seen

Quote from: kafffeeSounds good :-) So I need to call BassAsio.BASS_ASIO_ChannelEnableBASS only once, i.e. for the first output channel?

And: If I pass MainModule.Mixer as an argument for instance, my ASIO device will always be automatically set to the sample rate and bit depth of the this mixer channel? I don't have to worry about this at all?

Yes, BASS_ASIO_ChannelEnableBASS will setup the ASIO channels to match the specified BASS channel, so you don't need to do that separately yourself.

Quote from: kafffeeWhat if (on an input channel) I do need a callback function though, because I need to visualize and pass the data, which one of the following would you recommend?:

1. Call both BassAsio.BASS_ASIO_ChannelEnable w/ callback and BassAsio.BASS_ASIO_ChannelEnableBASS?
2. Only call BassAsio.BASS_ASIO_ChannelEnable w/ a callback?

It isn't possible to use BASS_ASIO_ChannelEnable and BASS_ASIO_ChannelEnableBASS on the same ASIO channel(s) at the same time. BASS_ASIO_ChannelEnableBASS basically sets the ASIO channel(s) to use an internal callback function instead of one that you provide.

kafffee

How does BASS know which channels to join when the device has more than two channels when using BassAsio.BASS_ASIO_ChannelEnableBASS?

Why does this work:
BassAsio.BASS_ASIO_ChannelJoin(False, 1, 0)but not this:
BassAsio.BASS_ASIO_ChannelJoin(False, 0, 1)
What happens when the ASIO device does not support the given sample rate and/or bit depth of the given BASS channel when using BASS_ASIO_ChannelEnableBASS?

Need to know these three questions in order to create some stable code.

Ian @ un4seen

BASS_ASIO_ChannelEnableBASS (with join=true) handles channel joining, so you don't need to call BASS_ASIO_ChannelJoin yourself. But if you do use BASS_ASIO_ChannelJoin, "channel2" is the master channel (eg. the one used in a BASS_ASIO_ChannelEnable call) and "channel1" is the channel you want to join to it. Your working and non-working calls indicate that channel 0 is the one you used in a BASS_ASIO_ChannelEnable call.

BASS_ASIO_ChannelEnableBASS enables resampling if the ASIO device's rate is different to the BASS channel. Basically, it internally calls BASS_ASIO_ChannelSetFormat / BASS_ASIO_ChannelSetRate / BASS_ASIO_ChannelJoin to setup the ASIO channels, so you don't have to. Note it doesn't change the device's rate, so you should still call BASS_ASIO_SetRate if you want that.

kafffee

#10
Ah okay, but let's say my ASIO device has 4 output channels, how does BassAsio know which ones to join w/ BASS_ASIO_ChannelEnableBASS? Will it just join all of them? Can't really try that, I am lacking the required hardware...

___________________________________________

QuoteNote it doesn't change the device's rate, so you should still call BASS_ASIO_SetRate if you want that

Okay, so you would just call BASS_ASIO_ChannelEnableBASS, then BASS_ASIO_ChannelGetRate and then BASS_ASIO_SetRate with the result thereof, to avoid unnecessary resampling?


___________________________________________

I am using a callback function for my input now that writes the data that needs to be visualized to a ring buffer (size 1024 * 1024):

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
        Dim now = sw.ElapsedMilliseconds
        Debug.WriteLine("Block @ " & now & " ms, size=" & length)
        Debug.WriteLine("BASS_StreamPutData return value: " & Bass.BASS_StreamPutData(PushStreamHandle, buffer, length))
    End If

    RingBuf.Write(buffer, length)

    Return length


End Function

Problem is that I hear a sporadic skipping in combination with clicking. Don't know if that has to do with it (I think it's already been like that before, just not that often).  What can I do to avoid this?

Ian @ un4seen

Quote from: kafffeeAh okay, but let's say my ASIO device has 4 output channels, how does BassAsio know which ones to join w/ BASS_ASIO_ChannelEnableBASS? Will it just join all of them? Can't really try that, I am lacking the required hardware...

You tell BASS_ASIO_ChannelEnableBASS the first channel in the "channel" parameter, and then (if join=true) it joins the next channels to that if the source is more than mono. For example, if you put a stereo source on channel 0, then it'll join channels 0 and 1.

Quote from: kafffeeOkay, so you would just call BASS_ASIO_ChannelEnableBASS, then BASS_ASIO_ChannelGetRate and then BASS_ASIO_SetRate with the result thereof, to avoid unnecessary resampling?

You could indeed do that.

Quote from: kafffeeI am using a callback function for my input now that writes the data that needs to be visualized to a ring buffer (size 1024 * 1024):
...
Problem is that I hear a sporadic skipping in combination with clicking. Don't know if that has to do with it (I think it's already been like that before, just not that often).  What can I do to avoid this?

Make sure the PushStreamHandle stream always has the same sample format as the ASIO input channel. Also check what other function calls you are using it in. Skipping sound can be caused by BASS_ChannelGetData/Level/Ex calls taking data out of the stream (that taken data won't get played).

kafffee

#12
QuoteYou tell BASS_ASIO_ChannelEnableBASS the first channel in the "channel" parameter, and then (if join=true) it joins the next channels to that if the source is more than mono. For example, if you put a stereo source on channel 0, then it'll join channels 0 and 1.

What do you mean by "putting a stereo source on channel 0"? You mean it joins as many channels as the target stream (MainModule.Mixer in this case) has, i.e. if MainModuleMixer would be quadrophonic for instance, it would join the given channel and the next 3 channels?

__________________

Okay, I removed some unnecessary code, so instead of 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
        Dim now = sw.ElapsedMilliseconds
        Debug.WriteLine("Block @ " & now & " ms, size=" & length)
        Debug.WriteLine("BASS_StreamPutData return value: " & Bass.BASS_StreamPutData(PushStreamHandle, buffer, length))
    End If

    RingBuf.Write(buffer, length)

    Return length

End Function

...I am only doing this now:

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(PushStreamHandle, buffer, length)
    End If

    RingBuf.Write(buffer, length)

    Return length


End Function

The clicking and skipping seems to have disappeared, but I still need to test this in the long term. I guess all the debug output stuff was just too time-comsuming, is that likely?

Ian @ un4seen

Quote from: kafffeeWhat do you mean by "putting a stereo source on channel 0"? You mean it joins as many channels as the target stream (MainModule.Mixer in this case) has, i.e. if MainModuleMixer would be quadrophonic for instance, it would join the given channel and the next 3 channels?

Yes. And if the device doesn't have that many channels then the call will fail with a BASS_ERROR_NOCHAN error. Please see the BASS_ASIO_ChannelEnableBASS documentation for more info.

Quote from: kafffeeThe clicking and skipping seems to have disappeared, but I still need to test this in the long term. I guess all the debug output stuff was just too time-comsuming, is that likely?

Debug logging can indeed introduce delays, which could cause audible clicks/gaps in this case if the ASIO buffer is shorter than the delay. If needed, you could try using a longer ASIO buffer (with BASS_ASIO_Start) when debugging.

kafffee

Unfortunately the clicking and skipping did not quite disappear. Any time the playback skips, there is a audible click when the sound drops out and a click when the sound sets back in. The skipping as far as I can see, always has a duration of an estimated half a second. Sometimes it also clicks without skipping.

What are your thoughts about this? What could I do? I am currently not using BASS_ChannelGetData.

QuoteMake sure the PushStreamHandle stream always has the same sample format as the ASIO input channel.

It does.

QuoteAlso check what other function calls you are using it in.

You meant the push stream handle?

Ian @ un4seen

When you say "playback skips", do you mean some data isn't being played? And each skip is as much as 500ms of data?

A skip can be caused by a BASS_ChannelGetData/Level call on a decoding channel that's feeding a mixer (or ASIOPROC). The data used in such calls won't be seen/played by the mixer (or ASIOPROC), resulting in an audible skip (when there are lots of skips it can sound like it's playing too fast). That's why I said to check what function calls you're using the PushStreamHandle handle in. Also check what calls are made on the mixer handle.

kafffee

Can't really tell if data isn't being played. What I can say is that the mixer produces silence sometimes. This happens only sporadically and at random positions in the (mixer) stream. Length of the silence is everytime no longer than a second, I found out it varies a bit, but I  do not really have proof of that, like a log, I just estimated that.

As for now, I do not use ChannelGetData/Level anywhere in my code.

I am using my PushStreamHandle in these functions:


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

Private Function Play_Execute()
[...]
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)
[...]
End Function

These calls are made on the mixer handle:

MainModule.Mixer = BassMix.BASS_Mixer_StreamCreate(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_NONSTOP Or BASSFlag.BASS_SAMPLE_FLOAT)

BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, NewWetHandle.WetTempoFXHandle, BASSFlag.BASS_DEFAULT)

Bass.BASS_ChannelSetAttribute(MainModule.Mixer, BASSAttribute.BASS_ATTRIB_BUFFER, 0) ' disable playback buffering

Bass.BASS_ChannelSetSync(MainModule.Mixer, BASSSync.BASS_SYNC_MIXTIME, 0, Nothing, IntPtr.Zero)

BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, WetHandle.WetTempoFXHandle, BASSFlag.BASS_DEFAULT)

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

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

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

Bass.BASS_ChannelPlay(MainModule.Mixer, False)


_____________________________________________________


In the meantime, I kept working on, now I am actually using ChannelGetData here:

Private Function DummyStreamCallback(handle As Integer, buffer As IntPtr, length As Integer, user As IntPtr) As Integer
    ' Mixer asking for "length" bytes → provides exactly this amount
    Dim bytesRead = Bass.BASS_ChannelGetData(TempoFXStreamHandleDry, buffer, length)

    If bytesRead <= 0 Then
        Return 0 ' end
    End If

    Return bytesRead
End Function

But this sounds different than the "first problem", where it produces only a very sporadic silence. Here, it's audible all the time, and like you said, it's playing too fast.

______________________________________


What could I do to fix or narrow down the "first problem" and this second one?

Ian @ un4seen

Quote from: kafffeeCan't really tell if data isn't being played. What I can say is that the mixer produces silence sometimes.

Are you sure it's producing silence, or might it be producing nothing? You can confirm which it is by setting a WAV writer on the mixer and then see if there are silent gaps in the written file. You can start the WAV writer something like this:

wavwriter = BASS_Encode_StartPCMFile(mixer, "mix.wav", BASS_ENCODE_QUEUE | BASS_ENCODE_AUTOFREE);

If there are silent gaps in the written file, were there multiple sources in the mixer at that time (your code shows multiple sources) and they all went silent at the same time?

Quote from: kafffeeThese calls are made on the mixer handle:

Bass.BASS_ChannelSetAttribute(MainModule.Mixer, BASSAttribute.BASS_ATTRIB_BUFFER, 0) ' disable playback buffering
Bass.BASS_ChannelSetSync(MainModule.Mixer, BASSSync.BASS_SYNC_MIXTIME, 0, Nothing, IntPtr.Zero)
Bass.BASS_ChannelPlay(MainModule.Mixer, False)

You can remove these calls, as they'll be failing anyway. BASS_ChannelSetAttribute(BASS_ATTRIB_BUFFER) and BASS_ChannelPlay because the mixer is a decoding channel (BASS_STREAM_DECODE), and BASS_ChannelSetSync because no callback function was provided.

You're also adding Recording.TempoFXStreamHandleDry to the mixer twice? The 2nd call will fail (BASS_ERROR_ALREADY) if so, unless you're also calling BASS_Mixer_ChannelRemove in between? If you are using BASS_Mixer_ChannelRemove then might the silent gaps be related to that, ie. the silence is because there are sometimes no sources?

Quote from: kafffeeIn the meantime, I kept working on, now I am actually using ChannelGetData here:

Private Function DummyStreamCallback(handle As Integer, buffer As IntPtr, length As Integer, user As IntPtr) As Integer
    ' Mixer asking for "length" bytes → provides exactly this amount
    Dim bytesRead = Bass.BASS_ChannelGetData(TempoFXStreamHandleDry, buffer, length)
...

But this sounds different than the "first problem", where it produces only a very sporadic silence. Here, it's audible all the time, and like you said, it's playing too fast.

You can't do this if TempoFXStreamHandleDry is also plugged in the mixer, because the BASS_ChannelGetData call's data won't be seen/played by the mixer. What's the intended purpose of this DummyStreamCallback function?

kafffee

Okay, I tried to log when the PCM data of the mixer channel output is "silence", respectively the float data = 0.

Weird thing happened: In the control panel of ASIO4ALL I selected the same sound device as input and as output (which is my USB Mic, that also has a headphone jack). But still, the sound came from my screen speakers. And: The problem seems not to occur here, i.e. only when the sound actually comes from the headphone jack of my mic.

The clicking though still occurs, mostly whithin the first three seconds of the recording.

However I did not manage to set the playback output device to my USB mic, so I can't really tell right now whether the mixer actually produces silence or nothing at all.

Does the fact that the issue only happens when in- and output device are the same already help you narrow it down or will I have to manage to set the output device to my USB mic in order to create that wav file?

___________________________________________________________________


QuoteIf there are silent gaps in the written file, were there multiple sources in the mixer at that time (your code shows multiple sources) and they all went silent at the same time?

Still I can say that there are multiple channels plugged into the mixer and they all go silent at the same time when the problem occurs.

____________________________________________________________________


QuoteYou can remove these calls, as they'll be failing anyway.

Oh, sorry for that, I was just trying something with that BASS_STREAM_DECODE flag, I am actually not using it. Just forgot to remove it in my post.

____________________________________________________________________

QuoteYou're also adding Recording.TempoFXStreamHandleDry to the mixer twice?

No, these are called from different code paths, in a way that they never get added twice.

____________________________________________________________________

QuoteWhat's the intended purpose of this DummyStreamCallback function?

What I am trying to do is to create a parallel bus to an existing stream, i.e. take the signal from tempo stream A (TempoFXStreamHandleDry), then add (tempo) effects to it, reduce its volume and then play it in the mixer at the same time so it's completely synchronous to stream A, just with the additional effects.

Take a look:

Private StreamProc As STREAMPROC = AddressOf DummyStreamCallback

Private Sub AddWetHandle(sender As Object, e As ParallelBusWetHandle)
    If e.ParallelBusName = SuperiorTrack.TrackName Then
        Dim NewWetDummyHandle As Integer = Bass.BASS_StreamCreate(MainModule.Song.SampleRate, MainModule.Song.NumberOfChannels, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT, StreamProc, IntPtr.Zero)
        Dim NewWetHandle As New ParallelBusWetHandle With {.ParallelBusName = e.ParallelBusName, .WetDummyHandle = NewWetDummyHandle, .WetTempoFXHandle = BassFx.BASS_FX_TempoCreate(NewWetDummyHandle, BASSFlag.BASS_STREAM_DECODE)}

        If NewWetHandle.WetTempoFXHandle = 0 Then MessageBox.Show(Bass.BASS_ErrorGetCode.ToString)
        TempoFXStreamHandleWet.Add(NewWetHandle)
        BassMix.BASS_Mixer_StreamAddChannel(MainModule.Mixer, NewWetHandle.WetTempoFXHandle, BASSFlag.BASS_DEFAULT)
        MessageBox.Show("StreamAddChannel: " & Bass.BASS_ErrorGetCode.ToString)
    End If
End Sub

So I am trying to let the mixer pull its data from the dummy stream. Is this doable when I put the BASS_STREAM_DECODE flag to the mixer and then push the mixer data to a push stream, and then playback the push stream, will this avoid the data not getting "stolen" from the mixer or which way would you do this?

Ian @ un4seen

Quote from: kafffeeOh, sorry for that, I was just trying something with that BASS_STREAM_DECODE flag, I am actually not using it. Just forgot to remove it in my post.

Does that mean you're playing the mixer through BASS, not BASSASIO? And in that case, the BASS_ASIO_ChannelEnableBASS call is failing? Please check all return values to confirm whether each call is successful.

Quote from: kafffeeWhat I am trying to do is to create a parallel bus to an existing stream, i.e. take the signal from tempo stream A (TempoFXStreamHandleDry), then add (tempo) effects to it, reduce its volume and then play it in the mixer at the same time so it's completely synchronous to stream A, just with the additional effects.

You could create 2 splitters 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).

Another option is to create a "push" stream with the same sample format for the tempo/etc path and feed that via a DSP function on "stream A", ie. the DSPPROC calls BASS_StreamPutData.

If the problem persists, I would suggest you try to find what's causing it by simplifying/disabling things until it stops. And check all return values :)