0

I have VB.NET project that I developed over 10 years ago to record audio. The original project started out using DirectSound in VB5, then I migrated it over to VB.NET and changed to XNA. Part of the program reads data from an Access database that it uses to add metadata to the recordings (mp3s), and also uses the Access data to select particular folder location to store the mp3s…so the interface to the Access database is integral. I use VB.NET because write a lot of VBA in Excel, Word, Access, and Outlook. This all worked for many years until the Target machine changed to 64-bit Office. Now the XNA libraries are no longer compatible with 64-bit Access. I first tried using the libraries in winmm. I thought that everything was fine until I ran my test program as a 64-bit object and none of the pointers returned from the devices were correct and it blew up.

After doing some research on how Audacity handles everything, I got steered towards XtAudio. I converted the Device List (PrintSimple) and Detailed List (PrintDetailed) examples into VB, and they worked perfectly.

Then I tried the Playback (RenderSimple) example. I can’t get the syntax of callback function to work correctly. I’ve worked with Delegate Functions before, so I tried modifying the code to use that syntax, but nothing seems to fit. When I finally got it to compile, it crashed on the first line of the OnBuffer function:

    Private Delegate Function delOnBuffer(ByVal stream As XtStream, ByVal buffer As XtBuffer, ByVal user As Object) As Integer
    Private Shared Function OnBuffer(ByVal stream As XtStream, ByVal buffer As XtBuffer, ByVal user As Object) As Integer

      Dim safe As XtSafeBuffer = XtSafeBuffer.Get(stream)

because stream is empty. Is there something that I am missing where stream gets initialized in the calling proceedure? Before:

Dim d As delOnBuffer = AddressOf fncOnBuffer
            Dim myBuffer As XtBuffer
            streamParams = New XtStreamParams(True, d.Invoke(Nothing, myBuffer, Nothing), Nothing, Nothing)

Any assistance/insight would be greatly appreciated. Thank you for your time in advance. Sincerely, Paul Goldstein

  • 1
    you should write a wrapper for the 32 bit, so that you can call it frm a 64 bit exe – nbk Jul 19 '23 at 18:35
  • 1
    I tried a straight forward conversion of the demo code from c# to vb.net myself, and i got stuck on the declaration of the OnBuffer delegate, too. See here: https://stackoverflow.com/questions/76744761/how-to-implement-a-c-sharp-delegate-containing-an-in-struct-parameter-in-vb-net. Furthermore @aybe's answer is spot-on, you have to pass the callback function itself into the library. – Sjoerd van Kreel Jul 22 '23 at 16:24

2 Answers2

1

In this line, there are two errors:

streamParams = New XtStreamParams(True, d.Invoke(Nothing, myBuffer, Nothing), Nothing, Nothing)

First, you pass Nothing to d.Invoke(Nothing, myBuffer, Nothing), so it's obvious why would Dim safe As XtSafeBuffer = XtSafeBuffer.Get(stream) fail, stream is Nothing.

Second, New XtStreamParams(...) expects a delegate, not its result, what you're doing with d.Invoke(...).

See the official example @ https://sjoerdvankreel.github.io/xt-audio/ :

streamParams = new XtStreamParams(true, OnBuffer, null, null);

Not sure if in VB.NET you can pass your implementation method directly, if not, just pass d.

To make it clear, it's the library that will call your callback with the right stream, not you.

aybe
  • 15,516
  • 9
  • 57
  • 105
1

Well, it appears XtAudio uses some parts of C# that are incompatible with VB.Net. Didn't even know that was possible, but alas. See How to implement a C# delegate containing an in-struct parameter in VB.NET?. The "correct" way to fix this would be for me to alter XtAudio's .NET bindings to change all "in struct" parameters to "ref struct" parameters, but I just don't have the time.

What will work, however, is to write a tiny wrapper in C# which will forward the delegate call to something that VB understands:

public delegate int VBDotNetOnBuffer(XtStream stream, XtBuffer buffer, object user);

public class DelegateWrapper
{
    readonly XtOnBuffer _wrapper;
    readonly VBDotNetOnBuffer _wrapped;

    public DelegateWrapper(VBDotNetOnBuffer wrapped)
    {
        _wrapped = wrapped;
        _wrapper = OnBuffer;
    }

    public XtOnBuffer Wrapper => _wrapper;
    public int OnBuffer(XtStream stream, in XtBuffer buffer, object user) => 
        _wrapped(stream, buffer, user);
}

From there on the conversion of the Playback example from C# to VB.Net is straightforward:

Imports Xt
Imports System.Threading
Imports ClassLibrary1
Imports System.IO

Namespace Xt

    Public Class RenderSimple

        Shared _phase As Single
        Const Frequency As Single = 440.0F
        Shared ReadOnly Mix As XtMix = New XtMix(44100, XtSample.Float32)
        Shared ReadOnly Channels As XtChannels = New XtChannels(0, 0, 1, 0)
        Shared ReadOnly Format As XtFormat = New XtFormat(Mix, Channels)

        Shared Function NextSample() As Single
            _phase += Frequency / Mix.rate
            If _phase >= 1.0F Then
                _phase = -1.0F
            End If
            Return CType(Math.Sin(2.0 * _phase * Math.PI), Single)
        End Function

        Shared Function OnBuffer(ByVal stream As XtStream, ByVal buffer As XtBuffer, ByVal user As Object) As Integer
            Dim safe As XtSafeBuffer = XtSafeBuffer.Get(stream)
            safe.Lock(buffer)
            Dim output() As Single = CType(safe.GetOutput(), Single())
            Dim f As Integer
            For f = 0 To buffer.frames - 1 Step f + 1
                output(f) = NextSample()
            Next
            safe.Unlock(buffer)
            Return 0
        End Function

        <STAThread>
        Public Shared Sub Main()
            Dim streamParams As XtStreamParams
            Dim deviceParams As XtDeviceStreamParams

            Using platform As XtPlatform = XtAudio.Init(Nothing, IntPtr.Zero)
                Dim system As XtSystem = platform.SetupToSystem(XtSetup.ConsumerAudio)
                Dim service As XtService = platform.GetService(system)
                If service Is Nothing Then
                    Return
                End If

                Dim defaultOutput As String = service.GetDefaultDeviceId(True)
                If defaultOutput Is Nothing Then
                    Return
                End If

                Using device As XtDevice = service.OpenDevice(defaultOutput)
                    If Not device.SupportsFormat(Format) Then
                        Return
                    End If

                    Dim size As XtBufferSize = device.GetBufferSize(Format)
                    Dim wrapper As New DelegateWrapper(AddressOf OnBuffer)
                    streamParams = New XtStreamParams(True, wrapper.Wrapper, Nothing, Nothing)
                    deviceParams = New XtDeviceStreamParams(streamParams, Format, size.current)
                    Using stream As XtStream = device.OpenStream(deviceParams, Nothing)
                        Using safe As XtSafeBuffer = XtSafeBuffer.Register(stream)
                            stream.Start()
                            Thread.Sleep(2000)
                            stream.Stop()
                        End Using
                    End Using
                End Using
            End Using
        End Sub
    End Class
End Namespace

This produces audio as expected.

Sjoerd van Kreel
  • 1,000
  • 6
  • 19