Author Topic: Record PCM data to memory stream or byte array  (Read 1904 times)

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #25 on: 11 Oct '24 - 18:12 »
Indeed, BASS_ChannelSetDSP doesn't return an HSTREAM handle (rather an HDSP handle), so it sounds like that RenderStartRecording call would fail. Perhaps you can use the "RecChannelHandle" handle there instead?

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #26 on: 11 Oct '24 - 18:35 »
Yep that works :-) and I already saw myself writing my on waveform function...

Thanks for your patience, now that I know what I needed to know I can hopefully go on working on my app.

Have a wonderful weekend :-)

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #27 on: 13 Oct '24 - 12:38 »
Edit 2:
Now there's another problem:

>> I am playing a source channel via a mixer channel.
>> When I start recording this source channel, the playback of this original source channel (not the push stream) over the mixer becomes faster for I reason that I can't see.
>> When I start recording the mixer channel, everything is alright.
>> They 're both using BASSFlag.BASS_SAMPLE_FLOAT

Code: [Select]
stream = Bass.BASS_StreamCreateFile(Handle.AddrOfPinnedObject(), 0L, Laenge, BASSFlag.BASS_STREAM_PRESCAN Or BASSFlag.BASS_SAMPLE_FLOAT Or BASSFlag.BASS_STREAM_DECODE)
Code: [Select]
MainModule.streamfx(WelchesDeck - 1) = BassFx.BASS_FX_TempoCreate(stream, BASSFlag.BASS_STREAM_DECODE)
Code: [Select]
mixer = Un4seen.Bass.AddOn.Mix.BassMix.BASS_Mixer_StreamCreate(44100, 2, BASSFlag.BASS_DEFAULT Or BASSFlag.BASS_SAMPLE_FLOAT)
What reasons can this have?

You can see the rest of my code here:

__________________________________________________________________

Edit: Before you start reading, I fixed the following issue like this  ;), so I only need an answer on Edit2 :

My microphone was set to 2 channels, 16 Bit, 48.000Hz in its settings, so I set it on 44.100 Hz and my application on "stereo".

I initialized RecordedData like this:


Code: [Select]
Dim remainder As Integer = MaxRecLength Mod 4  'divide by 4 and get remainder, because data is BASSFlag.BASS_SAMPLE_FLOAT (32 bits divided by 8 bytes = 4)
RecordedData = New Byte(MaxRecLength - 1 - remainder) {}  'initialize Byte Array for recording


>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

Okay now I got another problem:

When I hit the play button after recording, I get...:

- either a noise, when I play the recording from a microphone (the mic is mono) and set to mono
- or I can actually hear my voise, when recording from a microphone (the mic is mono) and set to stereo
- or I get error #20 - BASS_ERROR_ILLPARAM when I have recorded a source/mixer channel, which should be BASSFlag.BASS_SAMPLE_FLOAT  >> also, as soon as i start recording, the orginal channel plays quicker (during recording) and slows down again when I stop recording...

The docs say that:

Quote
BASS_ERROR_ILLPARAM: length is not valid, it must equate to a whole number of samples.

So how can I make sure that it does?

This is my code:

Code: [Select]
Private ReadOnly Property SampleFormat As BASSFlag
     Get
         Select Case ChosenChannel
             Case 0, 1, 2           'if user chose source or mixer channel
                 Return BASSFlag.BASS_SAMPLE_FLOAT
             Case 3, 4               'if user chose input recording device
                 If Not StereoOn Then    'if user chose that the signal is not stereo
                     Return BASSFlag.BASS_SAMPLE_8BITS
                 Else
                     Return BASSFlag.BASS_DEFAULT
                 End If
         End Select
     End Get
 End Property

Public ReadOnly Property NumberOfChannels As Integer Implements IZentraleKlasse.NumberOfChannels
    Get
        If LayerVM.RECViewModel.StereoOn Then
            Return 2
        Else
            Return 1
        End If
    End Get
End Property

Private Sub PlayPause_Execute(obj As Object)
If Not IsPlaying Then
    PlayBackChannel = Bass.BASS_StreamCreatePush(44100, MainModule.NumberOfChannels, BASSFlag.BASS_STREAM_AUTOFREE Or SampleFormat, Nothing)

    Bass.BASS_StreamPutData(PlayBackChannel, RecordedData, BytesWritten)
    Debug.WriteLine("streamputdata: " & Bass.BASS_ErrorGetCode.ToString)  'here I get error BASS_ERROR_ILLPARAM when recording source or mixer channel


    Bass.BASS_ChannelPlay(PlayBackChannel, False)

    PlayPauseIcon = PausedIcon
    PeakChannel = PlayBackChannel
    If Not PeakTimer.Enabled Then PeakTimer.Start()
Else
    Bass.BASS_ChannelPause(PlayBackChannel)
    PlayPauseIcon = PlayingIcon
    If Not IsRecording Then PeakTimer.Stop()
End If
IsPlaying = Not IsPlaying
End Sub

Private Sub StartRecording_Execute(obj As Object)
    RecordedData = New Byte(MaxRecLength + 1) {}    'set size of byte array to maximum recording length in bytes

    Dim Length As Single

    If ChosenChannel > 2 Then    'if user chose recording device:

        Dim RecDeviceIndex As Integer
        If ChosenChannel = 3 Then
            RecDeviceIndex = MainModule.LayerVM.SettingsViewModel.MikrofonIndex  'pick microphone
        ElseIf ChosenChannel = 4 Then
            RecDeviceIndex = MainModule.LayerVM.SettingsViewModel.LineInIndex  'pick line-in
        End If

        Bass.BASS_RecordInit(RecDeviceIndex)

        MyRecordingCallback = New RECORDPROC(AddressOf MyRecording)

        RecordingChannel = Bass.BASS_RecordStart(44100, MainModule.NumberOfChannels, BASSFlag.BASS_RECORD_PAUSE Or SampleFormat, 500, MeineRecordingCallback, IntPtr.Zero)

        Bass.BASS_ChannelPlay(RecordingChannel, False)
            ChannelToRender = RecordingChannel
            PeakChannel = ChannelToRender
            If Not PeakTimer.Enabled Then PeakTimer.Start()  'start timer to display peak level in progress bar
            InitializeWaveForm()   'waveform appearance
            Length = CSng(Bass.BASS_ChannelBytes2Seconds(RecordingChannel, CLng(MaxRecLength)))
            WF.RenderStartRecording(RecordingChannel, Length, 0)

        Else          'if user chose source/mixer channel to record from

            Select Case ChosenChannel
            Case 0
                RecChannelHandle = MainModule.streamfx(0) 'source channel
            Case 1
                RecChannelHandle = MainModule.streamfx(1) 'source channel
            Case 2
                RecChannelHandle = MainModule.mixer          'mixer channel
        End Select

        MyDSPCallback = New DSPPROC(AddressOf MyDSP)

        RecordingChannel = Bass.BASS_ChannelSetDSP(RecChannelHandle, MyDSPCallback, IntPtr.Zero, 0)
       
        ChannelToRender = RecChannelHandle

        PeakChannel = ChannelToRender
        If Not PeakTimer.Enabled Then PeakTimer.Start()
        InitializeWaveForm()
        Length = CSng(Bass.BASS_ChannelBytes2Seconds(RecChannelHandle, CLng(MaxRecLength)))
        WF.RenderStartRecording(RecChannelHandle, Length, 0)
       
    End If
RecordingOrStoppedIcon = StoppedIcon
    IsRecording = True
Else
    RecordingStopped()
End If
End Sub
« Last Edit: 14 Oct '24 - 07:21 by kafffee »

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #28 on: 14 Oct '24 - 16:34 »
>> I am playing a source channel via a mixer channel.
>> When I start recording this source channel, the playback of this original source channel (not the push stream) over the mixer becomes faster for I reason that I can't see.
>> When I start recording the mixer channel, everything is alright.
>> They 're both using BASSFlag.BASS_SAMPLE_FLOAT

Code: [Select]
stream = Bass.BASS_StreamCreateFile(Handle.AddrOfPinnedObject(), 0L, Laenge, BASSFlag.BASS_STREAM_PRESCAN Or BASSFlag.BASS_SAMPLE_FLOAT Or BASSFlag.BASS_STREAM_DECODE)
Code: [Select]
MainModule.streamfx(WelchesDeck - 1) = BassFx.BASS_FX_TempoCreate(stream, BASSFlag.BASS_STREAM_DECODE)
Code: [Select]
mixer = Un4seen.Bass.AddOn.Mix.BassMix.BASS_Mixer_StreamCreate(44100, 2, BASSFlag.BASS_DEFAULT Or BASSFlag.BASS_SAMPLE_FLOAT)
What reasons can this have?

That sounds like something in the recording process is taking data from the source, which means the mixer misses that data (making it sound fast). Perhaps it's the waveform stuff?

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #29 on: 14 Oct '24 - 17:10 »
Is there a way to affect the buffer size, so that the DSP callback function is passing a bigger amount of bytes at a lower frequency? You know, so that the callback function is being called less often?

I was already running into this problem when I had a timer get the peak level via BASS_ChannelGetLevel (I happened to fix it somehow, but can't tell what it was...).

Also: The waveform is always lagging behind the actual recording position (blue line), see attachment...

Think this might be good to know...

Edit:

It's not the waveform stuff, I removed it and still it doesn't work right.

Instead I removed the peak level stuff and it works. But what's the problem? It's called every 50 miliseconds:

Code: [Select]
Private Sub UpdatePeak(sender As Object, e As ElapsedEventArgs)
    If (Not (PlayBackChannel = Nothing)) OrElse (PlayBackChannel = 0) Then
        Dim Percent As Double = Bass.BASS_ChannelGetPosition(PlayBackChannel, BASSMode.BASS_POS_BYTE) / MaxTrackLengthInBytes * 100
        PlayXPosWF = WFWidth / 100 * Prozent
        PlayBackPosition = TimeSpan.FromSeconds(GetPositionInSeconds(PlayXPosWF))
   
    End If

    If PeakChannel <> Nothing Then
     
        If StereoOn Then
            Bass.BASS_ChannelGetLevel(PeakChannel, Peak, 0.05, BASSLevel.BASS_LEVEL_STEREO)
            Debug.WriteLine("getlevel: " & Bass.BASS_ErrorGetCode.ToString)
            PeakL = Peak(0)
            PeakR = Peak(1)
        Else
            Bass.BASS_ChannelGetLevel(PeakChannel, Peak, 0.05, BASSLevel.BASS_LEVEL_MONO)
            PeakL = Peak(0)
            PeakR = Peak(1)
        End If
    End If
End Sub
« Last Edit: 15 Oct '24 - 10:43 by kafffee »

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #30 on: 15 Oct '24 - 16:53 »
If "PeakChannel" is a mixer source, those BASS_ChannelGetLevel calls will be taking data from it that the mixer won't see. You can use BASS_Mixer_ChannelGetLevel(Ex) instead to avoid that. Note you need to include the BASS_MIXER_CHAN_BUFFER flag in your BASS_Mixer_StreamAddChannel call to enable BASS_Mixer_ChannelGetLevel(Ex).

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #31 on: 16 Oct '24 - 13:24 »
Alright okay :-)

So what about this:

Quote
Is there a way to affect the buffer size, so that the DSP callback function is passing a bigger amount of bytes at a lower frequency? You know, so that the callback function is being called less often?

I guess Bass.BASS_SetConfig(BASS_CONFIG_BUFFER, xy) won't affect this in my setup, I read in the docs that the default value is already 500 ms...

Or do you have another idea on how to avoid the lagging behind of my waveform (see attachment of reply #29)?

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #32 on: 16 Oct '24 - 17:52 »
If the DSP callback is on a mixer source, then the rate of the callbacks will be determined by the processing rate of the mixer. If the mixer is being played by BASS then its processing rate will be determined by the BASS_CONFIG_UPDATEPERIOD setting, or BASS_CONFIG_DEV_PERIOD if playback buffering is disabled (BASS_ATTRIB_BUFFER=0). Note BASS_CONFIG_DEV_PERIOD can't be changed on Windows.

If the aim of this is to get your waveform in sync, it seems unlikely that reducing the DSP callback rate would achieve that. But I've never used the WaveForm class myself, so I can't really advise on that. If you give it a position to display, might the problem be in that value?

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #33 on: 17 Oct '24 - 07:29 »
Edit:

Never mind the following, I decided not to implement the recording of my mixer/source channels for now. It's just too complicated and there's not that much of a surplus value...:



Quote
If the aim of this is to get your waveform in sync, it seems unlikely that reducing the DSP callback rate would achieve that.

Yes, I tried the maximum of 100ms and still the same.

Quote
If you give it a position to display, might the problem be in that value?

I think I can exclude that. I'm doing it the same way as with the recording callback and there, it works... Is there a way to draw the attention of @Radio42 to this topic though? Or anyone else does have experience?

Code: [Select]
Private Sub MyDSP(handle As Integer, channel As Integer, buffer As IntPtr, length As Integer, user As IntPtr)
     If length > 0 AndAlso buffer <> IntPtr.Zero Then

         Dim BufferSize As Integer
If BytesWritten + length > RecordedData.Length Then
    BufferSize = RecordedData.Length - BytesWritten
Else
    BufferSize = length
End If

Marshal.Copy(buffer, RecordedData, BytesWritten, BufferSize)
BytesWritten += BufferSize
Dim Prozent As Double = BytesWritten / MainModule.NumberOfChannels / MaxTrackLengthInBytes * 100
RecordingXPosWF = WFWidth / 100 * Prozent
RecordingPosition = TimeSpan.FromSeconds(GetPositionInSeconds(RecordingXPosWF))
Services.ServiceContainer.GetService(Of IMainWindowService)?.HoleDispatcher().InvokeAsync(Sub() WF.RenderRecording(buffer, BufferSize), DispatcherPriority.SystemIdle)
Services.ServiceContainer.GetService(Of IMainWindowService)?.HoleDispatcher().InvokeAsync(Sub() UpdateWaveForm(), DispatcherPriority.SystemIdle)

End Sub

Public Sub UpdateWaveForm()
    If WF IsNot Nothing Then
        If RecordingXPosWF <> 0 Then



            Dim MyWaveForm As Bitmap = WF.CreateBitmap(CInt(TransformToPixels(WFBreite)) * Zoom, CInt(TransformToPixels(WFHoehe) * MainModule.HoleSkalierung), -1, -1, False)
            Debug.WriteLine("CreateBitmap: " & Bass.BASS_ErrorGetCode.ToString)

            Dim Grafik As Graphics = Graphics.FromImage(MyWaveForm)
            Dim Stift As System.Drawing.Pen

            If (RecordingXPosWF <> Nothing) OrElse (RecordingXPosWF <> 0) Then
                Stift = New System.Drawing.Pen(System.Drawing.Color.Red)
                Grafik.DrawLine(Stift, TransformToPixels(RecordingXPosWF), 0, TransformToPixels(RecordingXPosWF), TransformToPixels(WFHoehe))
            End If

           
            WaveForm = BitmapToImageSource(MyWaveForm)
        Else
            WaveForm = BitmapToImageSource(WF.CreateBitmap(CInt(TransformToPixels(WFBreite)) * Zoom, CInt(TransformToPixels(WFHoehe) * MainModule.HoleSkalierung), -1, -1, False))

        End If
    Else
        WaveForm = Nothing
    End If
End Sub
« Last Edit: 18 Oct '24 - 12:01 by kafffee »

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #34 on: 4 Nov '24 - 12:43 »
Hey Ian, looks like most of my code is working, but I am having trouble with this:

Code: [Select]
Private _ChosenChannel As Integer
Public Property ChosenChannel As Integer
    Get
        Return _ChosenChannel
    End Get
    Set(value As Integer)
        _ChosenChannel = value


        Dim DeviceIndex As Integer

        If ChosenChannel = 1 Then     'get recording device index
            DeviceIndex = MainModule.LayerVM.SettingsViewModel.MikrofonIndex
        ElseIf ChosenChannel = 0 Then
            DeviceIndex = MainModule.LayerVM.SettingsViewModel.LineInIndex
        End If
        Bass.BASS_RecordFree()
        Bass.BASS_RecordInit(DeviceIndex)
        Debug.WriteLine("recordinit: " & Bass.BASS_ErrorGetCode.ToString)  'returns BASS_OK
        Dim info As BASS_RECORDINFO = Bass.BASS_RecordGetInfo()
        Debug.WriteLine("recordinfo: " & Bass.BASS_ErrorGetCode.ToString)   'returns BASS_OK
        Qualitaet = ParseDeviceInfo(info)

        RaisePropertyChanged()
    End Set
End Property

Private Function ParseDeviceInfo(argInfo As BASS_RECORDINFO) As List(Of String)
    Dim retList As New List(Of String)     'initialize list
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_1M08) Then retList.Add("11025 Hz, Mono, 8 Bit")  'check if flag is set
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_1S08) Then retList.Add("11025 Hz, Stereo, 8 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_1M16) Then retList.Add("11025 Hz, Mono, 16 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_1S16) Then retList.Add("11025 Hz, Stereo, 16 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_2M08) Then retList.Add("22050 Hz, Mono, 8 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_2S08) Then retList.Add("22050 Hz, Stereo, 8 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_2M16) Then retList.Add("22050 Hz, Mono, 16 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_2S16) Then retList.Add("22050 Hz, Stereo, 16 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_4M08) Then retList.Add("44100 Hz, Mono, 8 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_4S08) Then retList.Add("44100 Hz, Stereo, 8 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_4M16) Then retList.Add("44100 Hz, Mono, 16 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_4S16) Then retList.Add("44100 Hz, Stereo, 16 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_48M08) Then retList.Add("48000 Hz, Mono, 8 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_48S08) Then retList.Add("48000 Hz, Stereo, 8 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_48M16) Then retList.Add("48000 Hz, Mono, 16 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_48S16) Then retList.Add("48000 Hz, Stereo, 16 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_96M08) Then retList.Add("96000 Hz, Mono, 8 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_96S08) Then retList.Add("96000 Hz, Stereo, 8 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_96M16) Then retList.Add("96000 Hz, Mono, 16 Bit")
    If argInfo.WaveFormat.HasFlag(BASSRecordFormat.WAVE_FORMAT_96S16) Then retList.Add("96000 Hz, Stereo, 16 Bit")
    Return retList

End Function

I am trying to list the properties (sample rate, number of channels, bits per sample) of my recording devices in order to get the rest of my code right.

However, I will always get WAVE_FORMAT_UNKNOWN, no matter which device I choose, and I have six recording devices attached...

There is no error happening though...

Do you have any idea what the problem is?


Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #35 on: 4 Nov '24 - 16:56 »
Those old WAVE_FORMAT flags only apply on Windows XP and older. You won't see them set on newer Windows or the other platforms, but you will always be able to record at all of those rates (resampled if necessary). You can check the "freq" value for the device's native rate.

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #36 on: 4 Nov '24 - 18:27 »
Quote
but you will always be able to record at all of those rates

Okay, that's cool. So when I query the Channels member, I will get 1 if mono, and 2 if stereo?

The remaining issue is that I need the bytes per sample, because I need to calculate the bytes per second respectively seconds per byte manually... the ChannelBytesToSeconds / ChannelSecondstoBytes BASS functions will return Nothing (-1), if the channel handle is no more active, as far as I can see. Do you have any ideas on that?

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #37 on: 5 Nov '24 - 13:54 »
So when I query the Channels member, I will get 1 if mono, and 2 if stereo?

That's correct.

The remaining issue is that I need the bytes per sample, because I need to calculate the bytes per second respectively seconds per byte manually... the ChannelBytesToSeconds / ChannelSecondstoBytes BASS functions will return Nothing (-1), if the channel handle is no more active, as far as I can see. Do you have any ideas on that?

The bytes per sample will depend on what flags you use in your BASS_RecordStart call: BASS_SAMPLE_FLOAT is 4, BASS_SAMPLE_8BITS is 1, otherwise it's 2.

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #38 on: 5 Nov '24 - 16:24 »
Quote
The bytes per sample will depend on what flags you use in your BASS_RecordStart call: BASS_SAMPLE_FLOAT is 4, BASS_SAMPLE_8BITS is 1, otherwise it's 2.

Ah okay, so I can use whatever value I want, and it doesn't depend on the recording device's settings?

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #39 on: 5 Nov '24 - 18:04 »
Yes, like all sample rates being supported (with conversion if necessary), the same thing applies to the bitdepth/resolution. BASS doesn't currently show what the device's native bitdepth is but if you're curious, you can see it (and set it) in the Sound control panel.

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #40 on: 7 Nov '24 - 11:39 »
Okay, so when I have something like a USB turntable as recording device, it makes sense to pick BASSFlag.BASS_DEFAULT when the record is stereo, and pick BASSFlag.BASS_SAMPLE_8BITS if it's mono?

You know, to have the same sound qualitiy and at the same time, save disc space/memory?

btw: Is there an option to pause recording, because when I do with Bass.BASS_ChannelPause(RecordingChannel), my byte array seems to be filled with silence while it is paused...
« Last Edit: 7 Nov '24 - 13:52 by kafffee »

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #41 on: 7 Nov '24 - 16:29 »
Okay, so when I have something like a USB turntable as recording device, it makes sense to pick BASSFlag.BASS_DEFAULT when the record is stereo, and pick BASSFlag.BASS_SAMPLE_8BITS if it's mono?

You wouldn't ever use BASS_SAMPLE_8BITS unless you want low quality :)

The BASS_RecordStart "chans" parameter determines whether the recording is mono or stereo: 1=mono, 2=stereo.

btw: Is there an option to pause recording, because when I do with Bass.BASS_ChannelPause(RecordingChannel), my byte array seems to be filled with silence while it is paused...

BASS_ChannelPause should pause a recording. Is that call reporting success, ie. returning true? And is your RECORDPROC function still being called, and that's where the array is getting filled? If the array is being filled elsewhere, then you would need to separately pause that too.

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #42 on: 10 Nov '24 - 10:31 »
Okay that seems to work now...

Testing my playback function, I ran into these errors:

(1) Sometimes it's being played correctly, sometimes I hear just a loud noise.
(2) The playback never reaches the end.

Here's my code:

Code: [Select]
Public Sub StartPlayBack()

    Dim PlayBackPositionInBytes As Long    'declare variable
    Dim Percent As Double = PlayXPosWF / WFWidth * 100      'get percentage of playback starting position; PlayXPosWF=marked (by user) position on the waveform; WFWidth=width of waveform
    PlayBackPositionInBytes = CLng((MaxTrackLengthInBytes / 100 * Percent))     'calculate playback position in bytes

    Dim Rest As Long = PlayBackPositionInBytes Mod BytesPerSample()  'get remainder in order to get "byte-locked" playback position; BytesPerSample = 2 because SampleFormat = BASSFlag.BASS_DEFAULT
    PlayBackArray = New Byte(CInt(BytesWritten - PlayBackPositionInBytes - Rest) - 1) {}   'declare array with size of playback part; BytesWritten = total number of written bytes by recording callback

    Array.Copy(RecordedData, PlayBackPositionInBytes, PlayBackArray, 0, PlayBackArray.Length)   'copy data from RecordedData (complete recording array) to PlayBackArray; PlayBackPositionInBytes = start index of RecordedData; 0 = start index in PlayBackArray; PLayBackArray.Length = amount of data in bytes to be copied

    Bass.BASS_ChannelSetPosition(PlayBackChannel, 0)   'clear playback buffer
    Bass.BASS_StreamPutData(PlayBackChannel, PlayBackArray, PlayBackArray.Length)
    Debug.WriteLine("streamputdata: " & Bass.BASS_ErrorGetCode.ToString)   'this sometimes returns BASS_ILL_PARAM


    Bass.BASS_ChannelPlay(PlayBackChannel, False)
    Debug.WriteLine("channelplay: "Bass.BASS_ErrorGetCode.ToString)  'this sometimes returns BASS_INVALID_HANDLE
    SyncTrackEnd = New SYNCPROC(AddressOf PlayBackStopped)            'PlayBackStopped never gets called
    Bass.BASS_ChannelSetSync(PlayBackChannel, BASSSync.BASS_SYNC_END, 0, SyncTrackEnd, IntPtr.Zero)
   
    PlayPauseIcon = StoppedIcon
    PeakChannel = PlayBackChannel
    If Not PeakTimer.Enabled Then PeakTimer.Start()
    If Not PlayBackProgressTimer.Enabled Then PlayBackProgressTimer.Start()
    IsPlaying = True
End Sub

Do you have any idea of whats wrong?

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #43 on: 11 Nov '24 - 16:33 »
If BASS_StreamPutData is failing with BASS_ERROR_ILLPARAM then that means the "length" parameter isn't a whole number of sample frames. Perhaps your BytesPerSample value is incorrect? Note that needs to account for the number of channels too, eg. stereo 16-bit is 4 bytes.

To tell the stream that there's no more data coming, so it can end, you need to include the BASS_STREAMPROC_END flag in the "length" parameter too. Something like this:

Code: [Select]
    Bass.BASS_StreamPutData(PlayBackChannel, PlayBackArray, PlayBackArray.Length Or CInt(BASSStreamProc.BASS_STREAMPROC_END))

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #44 on: 12 Nov '24 - 10:02 »
Okay I got rid of the noise now, but it still doesn't reach the end...

Edit: Done some testing, it doesn't even play until the end for some reason.
« Last Edit: 12 Nov '24 - 10:12 by kafffee »

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #45 on: 13 Nov '24 - 14:13 »
Are you starting playback while the array is still being written to? If so, you would need to pass the new data to the playback stream via BASS_StreamPutData too, but note you won't be able to do that after setting BASS_STREAMPROC_END (only set that when there's no more data to come).

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #46 on: 13 Nov '24 - 16:04 »
Quote
Are you starting playback while the array is still being written to?

No. I stopped recording before:

This is my recording callback:

Code: [Select]
Private Function MyRecording(handle As Integer, buffer As IntPtr, length As Integer, user As IntPtr) As Boolean
    Dim cont As Boolean = True

    If length > 0 AndAlso buffer <> IntPtr.Zero Then
     

        Dim BufferSize As Integer

       
        If BytesWritten + length > RecordedData.Length Then
            BufferSize = RecordedData.Length - BytesWritten
        Else
            BufferSize = length
        End If

        Marshal.Copy(buffer, RecordedData, BytesWritten, BufferSize)
        BytesWritten += PufferGroesse

       

        Dim Percent As Double = BytesWritten / MaxTrackLengthInBytes * 100    'calculate and display recording progress
        RecordingXPosWF = WFWidth / 100 * Percent
        RecordingPosition = TimeSpan.FromSeconds(GetPositionInSeconds(RecordingXPosWF))

        Services.ServiceContainer.GetService(Of IMainWindowService)?.HoleDispatcher().InvokeAsync(Sub() WF.RenderRecording(buffer, BufferSize), DispatcherPriority.SystemIdle)
        Services.ServiceContainer.GetService(Of IMainWindowService)?.HoleDispatcher().InvokeAsync(Sub() UpdateWaveForm(Nothing, Nothing), DispatcherPriority.SystemIdle)

        If BytesWritten >= MaxTrackLengthInBytes Then
           
            cont = False
            Services.ServiceContainer.GetService(Of IMainWindowService)?.HoleDispatcher().InvokeAsync(Sub() RecordingStopped(), DispatcherPriority.SystemIdle)  'call RecordingStopped() from main UI thread
            Dim OKVM = New OKDialogViewModel
            OKVM.Message = "Recording stopped."         'MessageBox telling the uset that it stopped
            Services.ServiceContainer.GetService(Of IMainWindowService)?.HoleDispatcher().InvokeAsync(Sub() dialogService.ShowModalDialog("", OKVM, Me, True, False, Services.WindowStyle.None, Services.ResizeMode.NoResize, 500, Services.SizeToContent.Height, Services.WindowStartupLocation.CenterOwner, ""), DispatcherPriority.SystemIdle)

        End If
    End If
    Return cont
End Function

Code: [Select]
Public Sub RecordingStopped()


 
    Bass.BASS_ChannelStop(RecordingChannel)
 
    Dim Percent As Double = BytesWritten / MaxTrackLengthInBytes * 100
    RecordingXPosWF = WFWidth / 100 * Percent
    RecordingPosition = TimeSpan.FromSeconds(GetPositionInSeconds(RecordingXPosWF))

    WF.RenderRecording(RenderBuffer, RenderLength)
    UpdateWaveForm(Nothing, Nothing)

    If Not IsPlaying Then PeakTimer.Stop()
 
    PeakL = 0
    PeakR = 0
    RecordingOrStoppedIcon = RecordingIcon
    HasRecorded = True  'recording states
    IsRecording = False
    IsRecordingPaused = False
End Sub

And this is how I start/stop playback:


Code: [Select]
Private Sub PlayPause_Execute(obj As Object)

 If Not IsPlaying Then

PlayBackChannel = Bass.BASS_StreamCreatePush(SamplingRate, MainModule.NumberOfChannels, BASSFlag.BASS_STREAM_AUTOFREE Or SampleFormat, Nothing)
Bass.BASS_ChannelFlags(PlayBackChannel, BASSFlag.BASS_MUSIC_STOPBACK, BASSFlag.BASS_MUSIC_STOPBACK)
Debug.WriteLine("stopback: " & Bass.BASS_ErrorGetCode.ToString)

    StartPlayBack()
Else
    Bass.BASS_ChannelStop(PlayBackChannel)
    PlayBackStopped(Nothing, Nothing, Nothing, Nothing)

End If
End Sub

Code: [Select]
Public Sub StartPlayBack()
   
    Dim PlayBackPositionInBytes As Long
    Dim Percent As Double = PlayXPosWF / WFBreite * 100
    PlayBackPositionInBytes = CLng((MaxTrackLengthInBytes / 100 * Percent))

    Dim Rest As Long = PlayBackPositionInBytes Mod BytesPerSample()
 
    PlayBackArray = New Byte(CInt(BytesWritten - PlayBackPositionInBytes - Rest) - 1) {}

    Array.Copy(RecordedData, PlayBackPositionInBytes, PlayBackArray, 0, PlayBackArray.Length)

    Bass.BASS_ChannelSetPosition(PlayBackChannel, 0)
    Bass.BASS_StreamPutData(PlayBackChannel, PlayBackArray, PlayBackArray.Length Or CInt(BASSStreamProc.BASS_STREAMPROC_END))
    Debug.WriteLine("streamputdata: " & Bass.BASS_ErrorGetCode.ToString)


    Bass.BASS_ChannelPlay(PlayBackChannel, False)
    Debug.WriteLine("play: " & Bass.BASS_ErrorGetCode.ToString)
    SyncTrackEnd = New SYNCPROC(AddressOf PlayBackStopped)
    Bass.BASS_ChannelSetSync(PlayBackChannel, BASSSync.BASS_SYNC_END, 0, SyncTrackEnd, IntPtr.Zero)
    Dim fehler As String = Bass.BASS_ErrorGetCode.ToString


    PlayPauseIcon = StoppedIcon
    PeakChannel = PlayBackChannel
    If Not PeakTimer.Enabled Then PeakTimer.Start()
    If Not PlayBackProgressTimer.Enabled Then PlayBackProgressTimer.Start()
    IsPlaying = True
End Sub

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #47 on: 13 Nov '24 - 17:22 »
When the stream finishes playing, does your SyncTrackEnd function get called, and what does BASS_ChannelIsActive and BASS_ChannelGetPosition say about the stream?

kafffee

  • Posts: 291
Re: Record PCM data to memory stream or byte array
« Reply #48 on: 13 Nov '24 - 17:58 »
Okay, the values (and some more that might be helpful) are as follows:

ChannelIsActive: BASS_ACTIVE_STOPPED
ChannelGetPosition (BASS_POS_BYTE): -1
PlayBackArray Length: 278244
RecordedData Length: 10584000
BytesWritten: 728532
StartPosition in Bytes (sample-locked): 450288


Looks alright to me....
« Last Edit: 13 Nov '24 - 18:04 by kafffee »

Ian @ un4seen

  • Administrator
  • Posts: 26172
Re: Record PCM data to memory stream or byte array
« Reply #49 on: 14 Nov '24 - 16:47 »
That ChannelGetPosition return value (-1=fail) looks like the stream has been freed (or the handle is invalid for some other reason). Is your SyncTrackEnd function is getting called? If not, are you calling BASS_ChannelStop on the stream or BASS_Stop? They will free a stream that has BASS_STREAM_AUTOFREE set. If you want to be able to resume it then you should use BASS_ChannelPause or BASS_Pause instead.