Author Topic: USB Mic inputs to mixer to soundcard output [VB.net]  (Read 148 times)

Keiji

  • Posts: 6
I've been re-learning how to program over the past few years and I've been doing ok. I've so far gotten bass to do what I needed it to do with the the help of documentation and examples. But I'm stuck on what I'm trying to do right now. I have USB mics that I have been able to detect, and get signal levels on just fine. Now what i'm trying to do is get that input from the mics and output that to a sound card. I've been coming across a few examples written in C, but I've been unsuccessful in translating that into VB, which I've already written thousands of lines in.

The following code finds the desired mics, and monitors the signal levels;

Code: [Select]
Imports Un4seen.Bass
Imports Un4seen.Bass.Misc
Imports Un4seen.Bass.AddOn.Mix

Partial Class main
    Dim _record = New RECORDPROC(AddressOf MyRecording)
    Dim recordingInputs As New ArrayList

    Dim recChannel As Integer = 0

Dim maxlevel As Integer = 32768
    Static Dim maxspectrumlevel As Single = 0
   
    Sub InitAudio()
        Un4seen.BassAsio.BassAsio.BASS_ASIO_Init(-1, BASSFlag.BASS_DEFAULT)
        Dim samplerate As Single = Un4seen.BassAsio.BassAsio.BASS_ASIO_GetRate

        Me.SetStyle(ControlStyles.UserPaint, True)
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        AudioDevicesCB.DataSource = GetAudioDevices()
        AudioDevicesCB.DisplayMember = "Name"
        AudioDevicesCB.ValueMember = "ID"


        Dim n As Integer = 0
        Dim info As New BASS_DEVICEINFO()
        While (Bass.BASS_GetDeviceInfo(n, info))
            Console.WriteLine(info.ToString())
            n += 1
        End While
        n = 0
        Dim defaultDevice As Integer = -1

        While Not (info Is Nothing)
            info = Bass.BASS_GetDeviceInfo(n)
            If Not (info Is Nothing) And info.IsDefault Then
                defaultDevice = n
                Exit While
            End If
            n += 1
        End While

        Bass.BASS_Init(1, 44100, BASSInit.BASS_DEVICE_CPSPEAKERS, IntPtr.Zero, Nothing)
        Bass.BASS_Init(2, 44100, BASSInit.BASS_DEVICE_CPSPEAKERS, IntPtr.Zero, Nothing)
        Bass.BASS_Init(3, 44100, BASSInit.BASS_DEVICE_CPSPEAKERS, IntPtr.Zero, Nothing)


        System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False

        'NotificationVolumeTrackBar.Value = My.Settings.NotificationVolume

        n = 0

        'Dim info As New BASS_DEVICEINFO()
        Dim rinfo As New BASS_RECORDINFO()

        While n < Bass.BASS_RecordGetDeviceCount()
            Bass.BASS_RecordGetDeviceInfo(n, info)
            Debug.WriteLine(n & info.name)
            If info.name.Contains("Microsoft LifeCam") Or info.name.Contains("C-Media") Then
                Debug.WriteLine("initilizing " & info.name)
                Bass.BASS_RecordInit(n)
                Bass.BASS_RecordGetInfo(rinfo)
                recChannel = Bass.BASS_RecordStart(48000, 2, BASSFlag.BASS_RECORD_PAUSE And BASSFlag.BASS_STREAM_DECODE, _record, IntPtr.Zero)
                If Bass.BASS_ChannelPlay(recChannel, False) Then
                    Debug.WriteLine("initilized " & info.name & " as " & recChannel.ToString)
                    recordingInputs.Add(New AudioDevice(recChannel, info, rinfo))
                Else
                    Debug.WriteLine("initilized " & info.name & " Failed. :: " & Bass.BASS_ErrorGetCode().ToString)
                End If
            End If
            n += 1
        End While



    End Sub
   
    Private Sub GetRecLevels() Handles AudioLevelTimer.Tick
        Dim i As Int16 = 1
        For Each mic In recordingInputs

            Dim level As Integer = mic.level()
            Dim spectrum As ArrayList = mic.spectrumdata()
            ....


        Next
    End Sub
End Class


Public Class AudioDevice
    Private deviceinfo As New BASS_DEVICEINFO()
    Private recordinfo As New BASS_RECORDINFO

    Private mID As Integer
    Private mName As String

    Dim _fadelevel As Integer = 0
    Dim _peaklevel As Integer = Integer.MaxValue
    Dim _peak As Integer
    Dim maxlevel As Integer = Integer.MaxValue


    Public Sub New(ByVal id As Integer, ByVal info As BASS_DEVICEINFO, rinfo As BASS_RECORDINFO)
        mID = id
        deviceinfo = info
        recordinfo = rinfo
        mName = deviceinfo.name
        ...
    End Sub

    Public ReadOnly Property level As Integer
        Get
...Return level
        End Get
    End Property

    Public Function spectrumdata() As ArrayList
        ...
    End Function
End Class   

The above code works great for what I want it to do. It's not perfect, I know, and I omitted a huge chunk of irrelevant code, but it works. But now what I want to do is plug those mic inputs into a mixer, and I can't figure out how to do a push stream, or whatever it is I need to do to monitor the mics via sound card. Any assistance or sample code would be appreciated!

Ian @ un4seen

  • Administrator
  • Posts: 20210
When using standard BASS recording (eg. not ASIO or WASAPI), you won't usually need a "push" stream as you can plug the recording channel directly into the mixer (after removing the RECORDPROC from the BASS_RecordStart call). But you will need to change the level monitoring code to use BASS_Mixer_ChannelGetLevel/Data instead of BASS_ChannelGetLevel/Data, otherwise the level monitoring would be taking data from the recording and making it unavailable to the mixer (resulting in skipping). Please see the BASS_Mixer_ChannelGetLevel/Data documentation for details.

Keiji

  • Posts: 6
Thanks for the reply!

The part about having to get my levels from BASS_Mixer_ChannelGetLevel I totally understand and knew I would have to. The part I'm not understanding is what you mean by removing the RECORDPROC from the BASS_RecordStart call.   Does BASS_RecordStart not require a RECORDPROC  ???

Keiji

  • Posts: 6
After playing with the code for a few hours, I figured this out;
Code: [Select]
        n = 0

        Dim rinfo As New BASS_RECORDINFO()
        Dim mixer As Integer = BassMix.BASS_Mixer_StreamCreate(44100, 2, BASSFlag.BASS_SAMPLE_FLOAT)
        While n < Bass.BASS_RecordGetDeviceCount()
            Bass.BASS_RecordGetDeviceInfo(n, info)
            Debug.WriteLine(n & info.name)
            If info.name.Contains("C-Media") Then
                Debug.WriteLine("initilizing " & info.name)
                Bass.BASS_RecordInit(n)
                Bass.BASS_RecordGetInfo(rinfo)
                Dim recinfo = Bass.BASS_RecordGetInfo
                mixerRecChannel = Bass.BASS_RecordStart(recinfo.freq, recinfo.Channels, BASSFlag.BASS_SAMPLE_FLOAT Or BASSFlag.BASS_STREAM_DECODE, Nothing, Nothing)
                Dim streamA As Integer = Bass.BASS_StreamCreate(mixerRecChannel, 0, 0, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT)
                If BassMix.BASS_Mixer_StreamAddChannel(mixer, mixerRecChannel, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_MIXER_BUFFER) Then
                    Debug.WriteLine("initilized " & info.name & " as " & mixerRecChannel.ToString)
                    recordingInputs.Add(New AudioDevice(mixerRecChannel, info, rinfo))
                Else
                    Debug.WriteLine("initilized " & info.name & " Failed. :: " & Bass.BASS_ErrorGetCode().ToString)
                End If
            End If
            n += 1
        End While

        Debug.WriteLine(Bass.BASS_ErrorGetCode)
        Bass.BASS_SetDevice(1)
        Bass.BASS_ChannelPlay(mixer, False)

This allows me to listen to my mics thru my soundcard as I wanted.

Code: [Select]
Dim level As Integer = recordingInputs(0).mixerlevel()
Code: [Select]
        Public ReadOnly Property mixerlevel As Integer
        Get
            mixerlevel = Utils.LowWord32(BassMix.BASS_Mixer_ChannelGetLevel(ID))
            Return level
        End Get

But I'm getting a considerable delay. Is there any way to reduce or eliminate this?

Ian @ un4seen

  • Administrator
  • Posts: 20210
The part about having to get my levels from BASS_Mixer_ChannelGetLevel I totally understand and knew I would have to. The part I'm not understanding is what you mean by removing the RECORDPROC from the BASS_RecordStart call.   Does BASS_RecordStart not require a RECORDPROC  ???

Yes, in order to plug a recording channel into a mixer, it needs to not have a RECORDPROC. The BASS_Mixer_StreamAddChannel call will otherwise fail. A recording channel not using a RECORDPROC is equivalent to a stream using the BASS_STREAM_DECODE flag. It means that BASS_ChannelGetData can be used to get the recorded data (instead of the data being sent to a RECORDPROC), which is what the mixer will do.

I see a few issues in the posted cade:

Code: [Select]
                mixerRecChannel = Bass.BASS_RecordStart(recinfo.freq, recinfo.Channels, BASSFlag.BASS_SAMPLE_FLOAT Or BASSFlag.BASS_STREAM_DECODE, Nothing, Nothing)
                Dim streamA As Integer = Bass.BASS_StreamCreate(mixerRecChannel, 0, 0, BASSFlag.BASS_STREAM_DECODE Or BASSFlag.BASS_SAMPLE_FLOAT)
...
        Bass.BASS_SetDevice(1)

The BASS_STREAM_DECODE flag shouldn't be used in the BASS_RecordStart call, and the BASS_StreamCreate call is unnecessary. The BASS_SetDevice call isn't really needed either unless there are multiple output devices initialized (via BASS_Init), in which case it should be called before BASS_Mixer_StreamCreate rather than BASS_ChannelPlay, as a stream's device is set at creation (can later be changed via BASS_ChannelSetDevice).

But I'm getting a considerable delay. Is there any way to reduce or eliminate this?

The mixer will unfortunately add some latency due to buffering. The latency will be determined by the mixer's playback buffer length, which is determined by the BASS_CONFIG_BUFFER setting (default 500ms) at its creation. When using a small buffer, will you will also need to lower the update period via the BASS_CONFIG_UPDATEPERIOD setting (default 100ms). For example, for mimimal buffering, you could do this:

Code: [Select]
Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_UPDATEPERIOD, 10) ' 10ms update period
Dim info As BASS_INFO = Bass.BASS_GetInfo()
Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_BUFFER, 10 + info.minbuf + 1) ' playback buffer = update period + minbuf + 1ms margin

Please see the documentation for details on the mentioned functions/options. Note you will also need to use the BASS_DEVICE_LATENCY flag in your BASS_Init call(s) to make the "minbuf" value available.

As I mentioned earlier, if you don't need to support Windows XP, it may be better to use WASAPI (via the BASSWASAPI add-on) rather than standard BASS/DirectSound for this. That would remove the playback buffer from the mixer; the mixer would become a "decoding channel" (use the BASS_STREAM_DECODE flag), which your output WASAPIPROC would get its data from via BASS_ChannelGetData. In this case you would need the "push" stream that you mentioned earlier, to receive the data from the input WASAPIPROC and feed it into the mixer, ie. the push stream is plugged into the mixer.

Keiji

  • Posts: 6
I was going to post asking how to initialize a device via BassWasapi and plug it in, but I'm going to try and figure it out.
« Last Edit: 9 Aug '17 - 17:23 by Keiji »

Ian @ un4seen

  • Administrator
  • Posts: 20210
To minimize the changes, you could actually keep the existing recording channel code as it won't really affect latency very much, and just use WASAPI for output. The WASAPI output code could look something like this (in C/C++):

Code: [Select]
BASS_WASAPI_Init(-1, 0, 0, BASS_WASAPI_EVENT, 0, 0, WasapiProc, NULL); // initialize default output device in event-driven shared mode
BASS_WASAPI_INFO wi;
BASS_WASAPI_GetInfo(&wi); // get output info
mixer = BASS_Mixer_StreamCreate(wi.freq, wi.chans, BASS_SAMPLE_FLOAT|BASS_STREAM_DECODE); // create a mixer with the same sample format
// start the recording and add it to the mixer here
BASS_WASAPI_Start(); // start the output

...

DWORD CALLBACK WasapiProc(void *buffer, DWORD length, void *user)
{
DWORD c = BASS_ChannelGetData(mixer, buffer, length); // get data from the mixer
if (c == -1) c = 0; // an error, no data
return c;
}

Please see the documentation for details on the mentioned functions.

Keiji

  • Posts: 6
Re: USB Mic inputs to mixer to soundcard output [VB.net]
« Reply #7 on: 11 Aug '17 - 03:38 »
First, thanks for the continued help.

Second, I translated your code so that everything seems to check out;
Code: [Select]
Imports Un4seen.BassWasapi.BassWasapi
Imports Un4seen.Bass.AddOn.Mix
Public Class Main
    Dim _WASAPIPROC = New WASAPIPROC(AddressOf WASAPIPROC)
    Dim mixer As Integer = 0

        Sub InitMixerAudio()
        Dim info As New BASS_WASAPI_INFO()
        Debug.WriteLine(BASS_WASAPI_Init(-1, 0, 0, BASSWASAPIInit.BASS_WASAPI_EVENT, 0, 0, _WASAPIPROC, Nothing)) '; // initialize Default output device In Event-driven Shared mode
        Debug.WriteLine(BASS_WASAPI_GetInfo(info))
        Debug.WriteLine(Bass.BASS_ErrorGetCode())
        Debug.WriteLine(info.ToString)

        Bass.BASS_Init(-1, info.freq, info.chans, IntPtr.Zero, Nothing)
        mixer = BassMix.BASS_Mixer_StreamCreate(info.freq, info.chans, BASSFlag.BASS_SAMPLE_FLOAT)

        Debug.WriteLine(Bass.BASS_ErrorGetCode())



        Dim n As Integer = 0
        Dim Dinfo As New BASS_WASAPI_DEVICEINFO()
        While (BassWasapi.BASS_WASAPI_GetDeviceInfo(n, Dinfo))
            If Dinfo.IsEnabled And Dinfo.SupportsRecording Then
                Debug.WriteLine("---" & Dinfo.name)
                Debug.WriteLine(Dinfo.ToString())
                BASS_WASAPI_Init(n, 0, 0, BASSWASAPIInit.BASS_WASAPI_EVENT, 0, 0, _WASAPIPROC, Nothing) '; // initialize Default output device In Event-driven Shared mode
                Debug.WriteLine(Bass.BASS_ErrorGetCode) '; // create a mixer With the same sample format
            End If
            n += 1
        End While

        Debug.WriteLine(BassWasapi.BASS_WASAPI_Start()) ' ; // start the output
    End Sub

    Public Function WASAPIPROC(buffer As IntPtr, length As Integer, user As IntPtr) As Integer
        Dim c As Integer = Bass.BASS_ChannelGetData(mixer, buffer, length) '; // Get data from the mixer
        If (c = -1) Then c = 0 '; // an Error, no data
        ' Debug.WriteLine(c)
        Return c
    End Function


And it so far works ok. What I can't figure out is the push stream and how to plug my WASAPI devices to my mixer. I've spent about four hours looking for instructions on how to do it, it's just not coming to me very easy.

Ian @ un4seen

  • Administrator
  • Posts: 20210
Re: USB Mic inputs to mixer to soundcard output [VB.net]
« Reply #8 on: 11 Aug '17 - 17:47 »
Shared mode WASAPI will have about the same latency as DirectSound for recording, so switching won't really affect latency. If you would like to switch anyway, the "push" stream needs to have the same sample format as the WASAPI input device, which you can get from BASS_WASAPI_GetInfo (as in the WASAPI output case). It could look something like this:

Code: [Select]
BASS_WASAPI_Init(indevice, 0, 0, 0, 0.1, 0, InputWasapiProc, NULL); // initialize input device in shared mode (100ms buffer, default period)
BASS_WASAPI_GetInfo(&wi); // get input info
instream = BASS_StreamCreate(wi.freq, wi.chans, BASS_SAMPLE_FLOAT|BASS_STREAM_DECODE, STREAMPROC_PUSH, NULL); // create a push stream with the same sample format
BASS_Mixer_StreamAddChannel(mixer, instream, 0); // add it to the mixer

...

DWORD CALLBACK InputWasapiProc(void *buffer, DWORD length, void *user)
{
BASS_StreamPutData(instream, buffer, length); // pass the captured data to the push stream
return 1;
}

Keiji

  • Posts: 6
Re: USB Mic inputs to mixer to soundcard output [VB.net]
« Reply #9 on: 12 Aug '17 - 03:26 »
Last question, probably. What would STREAMPROC_PUSH reference?

Ian @ un4seen

  • Administrator
  • Posts: 20210
Re: USB Mic inputs to mixer to soundcard output [VB.net]
« Reply #10 on: 14 Aug '17 - 17:57 »
STREAMPROC_PUSH is a constant (defined as -1). With BASS.Net, I believe you would use "BASSStreamProc.STREAMPROC_PUSH", or use BASS_StreamCreatePush instead of BASS_StreamCreate.