BASS.NET API 2.4.18.2

Started by radio42,

emco

The distorted sound turned out to be from a PWM signal which got activated by my .NET application. Apparently on the raspberry pi one chip (bcm2835) is used for both the audio and PWM signals to GPIO pins.
Disabling the PWM part of the application solved the issue and sound is playing fine.

zippo227

I have been working to convert my unity project to use their new IL2CPP system. I have some code that returns the volume levels using Windows with IL2CPP, and it also runs on macOS MONO in the unity editor. However when running on macOS IL2CPP, the levels are always 0. The call to BASS_ChannelGetLevel is returning true, so I'm not sure why it is not returning something > 0. I'm using Soundflower to create an aggregate device which is what I'm inputting into the client. Thanks for taking a look.

private const int levelSamplingIntervalMS = 1000;
private const float levelSamplingIntervalF = 1f;

private void MonitorLevels()
{
while (Started == true)
{
float[] data = new float[2];

if (RecordChannel != 0)
{
//Do stuff with PCM
bool success = Bass.BASS_ChannelGetLevel(RecordChannel, data, levelSamplingIntervalF, BASSLevel.BASS_LEVEL_STEREO);
if (success == false)
{
TextError = Utils.GetBassError("Could not extract level");
}
}

//TextLog = "Levels " + data[0] + " " + data[1];

iBroadcastMonitor.LevelsCallback(data);

Thread.Sleep(levelSamplingIntervalMS);
}

TextLog = "Exiting MonitorLevels";
}

Ian @ un4seen

From its name, I guess "RecordChannel" was created with BASS_RecordStart. If so, is it using a RECORDPROC callback? Without a RECORDPROC, each BASS_ChannelGetLevel call will be taking data out of the recording buffer, and so if you call it often and/or with a high "levelSamplingIntervalF" value, there may sometimes be no data for it to measure (so it returns a 0 level).

zippo227

Quote from: Ian @ un4seenFrom its name, I guess "RecordChannel" was created with BASS_RecordStart. If so, is it using a RECORDPROC callback? Without a RECORDPROC, each BASS_ChannelGetLevel call will be taking data out of the recording buffer, and so if you call it often and/or with a high "levelSamplingIntervalF" value, there may sometimes be no data for it to measure (so it returns a 0 level).

Hi Ian, that's interesting. Would you recommend I add a RECORDPROC instead of running a separate thread that tries to measure the levels? My only concern with the RECORDPROC previously was it seemed like I would need to process the data there. However, if I can simply make the call to GetLevel there instead of in my thread, that might do the trick?

I've attached the code I'm using to start the recording. You're right, I am using Bass.BASS_RecordStart with a null RECORDPROC.

/// <summary>
/// Begins recording the selected device. The device
/// will now be enabled for monitoring and encoding.
/// </summary>
/// <param name="selectedDevice"></param>
/// <returns></returns>
protected override void StartRecordingChannel(int selectedDevice)
{
Bass.BASS_RecordFree();

if (Bass.BASS_RecordInit(selectedDevice) == false)
{
TextError = Utils.GetBassError("Couldn't init recording");
return;
}

BASSInputType inputType = new BASSInputType();
Bass.BASS_RecordGetInputType(selectedDevice);

Console.WriteLine(string.Format("InputType {0}", inputType));

BASS_RECORDINFO recordInfo = new BASS_RECORDINFO();
if (Bass.BASS_RecordGetInfo(recordInfo) == false)
{
TextError = Utils.GetBassError("Couldn't get recordInfo");
return;
}

NumChannels = recordInfo.Channels;
TextLog = string.Format("Supported Channels {0}", recordInfo.Channels);

// start recording @ 44100hz 16-bit stereo (paused to setup encoder first)
// Returns a recording handle
//m_recordingCallbackProc is null to decrease latency
RecordChannel = Bass.BASS_RecordStart((int)SampleRate, 2, BASSFlag.BASS_RECORD_PAUSE, null, IntPtr.Zero);

//Try recording mono if this failed
if (RecordChannel == 0)
{
TextLog = "Could not initialize two recording channels. Attempting Mono.";
RecordChannel = Bass.BASS_RecordStart((int)SampleRate, 1, BASSFlag.BASS_RECORD_PAUSE, null, IntPtr.Zero);
}

if (RecordChannel == 0)
{
TextError = Utils.GetBassError("Couldn't start recording");
return;
}

if (Bass.BASS_ChannelPlay(RecordChannel, false) == false)
{
TextError = Utils.GetBassError("Couldn't play recording");
return;
}
}

Ian @ un4seen

Yes, you should use a RECORDPROC callback in that case, to stop BASS_ChannelGetLevel removing data from the recording buffer. You don't need to move the BASS_ChannelGetLevel calls into the RECORDPROC though - it can simply "return true".

zippo227

Quote from: Ian @ un4seenYes, you should use a RECORDPROC callback in that case, to stop BASS_ChannelGetLevel removing data from the recording buffer. You don't need to move the BASS_ChannelGetLevel calls into the RECORDPROC though - it can simply "return true".

Hi Ian. That worked ;D! Attaching the relevant code for anyone who wants to see it.

//...
RecordChannel = Bass.BASS_RecordStart((int)SampleRate, 2, BASSFlag.BASS_RECORD_PAUSE, RecordProc, IntPtr.Zero);
//...

[AOT.MonoPInvokeCallback(typeof(RECORDPROC))]
private static bool RecordProc(int handle, IntPtr buffer, int length, IntPtr user)
{
    return true;
}

santanutosh

Hi,
I have been trying the Utils.GetNormalizationGain function to check the Peak and Gain factor of an Audio file. Then if required I would apply this gain factor to the same audio and save it as a new audio file. My code is like this:
_stream = Bass.BASS_StreamCreateFile(filename, 0, 0, BASSFlag.BASS_STREAM_DECODE)
gain = Utils.GetNormalizationGain(filename, 0.5, -1, -1, peak)
Bass.BASS_ChannelSetAttribute(_stream, BASSAttribute.BASS_ATTRIB_VOL, gain)

encHandle = BassEnc.BASS_Encode_Start(_stream, "Normalized.wav", BASSEncode.BASS_ENCODE_PCM Or BASSEncode.BASS_ENCODE_FP_16BIT, Nothing, 0)
If encHandle <> 0 Then
      While length > 0
        length= Bass.BASS_ChannelGetData(_stream, buffer, buffer.Length)
      End While
      BassEnc.BASS_Encode_Stop(encHandle)
End If

The new audio file is created. But there is no change in audio level. Although the gain factor is displayed mostly as 1.xxx, i.e. greater than 1.
What am I doing wrong?

radio42

This function (like the documentation describes) doesn't calculate a ReplayGain (to calculate the 'perceived' loudness value), but calculates the gain value to normalize the audio, so that the peak level is adjusted to 0dB.
So if the audio is already normalized (any peak level is already at max), there would be no change of course.
Bass.Net doesn't contain any function to calculate the ReplayGain.

santanutosh

Thanks for replying.
I tested with an Audio file which gives Peak = 0.4068 and Gain = 2.4580.
Correct me, if I'm wrong. My understanding is that the peak value is much lower than 0dB (i.e. Peak = 1.0). So, if I apply the gain factor 2.4580 to this stream I would get a stream which would have it's peak at 0dB.
Bass.BASS_ChannelSetAttribute(_stream, BASSAttribute.BASS_ATTRIB_VOL, gain)But after applying the above code, all I get is a duplicate copy of the original audio. And on testing the new file, again I have Peak = 0.4068 and Gain = 2.4580.
The waveform also hasn't changed.

radio42

Yes, that is correct.
But BASS_ChannelSetAttribute would change any WaveForm!
I am not sure, how you are testing things, but if you set via BASS_ChannelSetAttribute a volume factor above 1.0, that the volume of that stream would be amplified.
But that doesn't change the data send to the encoder, but is only applied to the output!

santanutosh

I create 2 streams from 2 different audio files of same freq, channels and bitrate.
stream1=Bass.BASS_StreamCreateFile(file1, 0, 0, BASSFlag.BASS_STREAM_DECODE)
stream2=Bass.BASS_StreamCreateFile(file2, 0, 0, BASSFlag.BASS_STREAM_DECODE)
mixer=BassMix.BASS_Mixer_StreamCreate(FileInfo.freq, FileInfo.chans, BASSFlag.BASS_STREAM_DECODE)
BassMix.BASS_Mixer_StreamAddChannel(mixer, stream1, BASSFlag.BASS_MIXER_BUFFER)
BassMix.BASS_Mixer_StreamAddChannel(mixer, stream2, BASSFlag.BASS_MIXER_BUFFER)
Then I create a mixer and add the stream channels in the mixer. Then I set an encoder on the mixer as I need the mixed audio in another file.
encHandle=BassEnc.BASS_Encode_Start(mixer, fileName, BASSEncode.BASS_ENCODE_PCM Or BASSEncode.BASS_ENCODE_FP_16BIT, Nothing, 0)Then I use the following to get the mixed data.
While len > 0
    len= BassMix.BASS_Mixer_ChannelGetData(stream1, buffer, buffer.Length)
    len= BassMix.BASS_Mixer_ChannelGetData(stream2, buffer, buffer.Length)
End While
BassEnc.BASS_Encode_Stop(encHandle)
I do not know if I'm doing right or wrong. But I'm only getting a 0Kb audio file as an output. Please help me.

santanutosh

QuoteI have been trying the Utils.GetNormalizationGain function to check the Peak and Gain factor of an Audio file. Then if required I would apply this gain factor to the same audio and save it as a new audio file.
Okay. I solved it.
fxHandle = Bass.BASS_ChannelSetFX(_stream, BASSFXType.BASS_FX_VOLUME, 0)
Dim par As New BASS_FX_VOLUME_PARAM
par.fTarget = par.fCurrent * vol
Bass.BASS_FXSetParameters(fxHandle, par)
vol = Gain % delivered by GetNormalizationGain function. After this, Bass.BASS_ChannelGetData(_stream, buffer, buffer.Length) returns the modified data.  :)

santanutosh

I have a question regarding the Marker used in WaveForm Class. As given in documentation, we can have Markers of different Color and Alignment by using "{...}" in the Marker Name.
Is it also possible to have Markers of different lengths? I tried by putting "{Length=0.5}", but didn't work. Also, if the vertcal position of an individual Marker could be altered, it would be very nice.
Thank you.

radio42

All Markers are drawn with the same length using the global MarkerLength property.

toob

My goal is to show audio db levels via VU meters from Windows speaker output. It's my understanding I should use BassWasapi to capture the speaker output into a stream. Then use a timer event (or a loop) and use BASS_ChannelGetLevel on the stream to get left and right values for the VU meters.
The code below does this but the values output by BASS_ChannelGetLevel are approx 3 seconds behind the speaker output. For example if I stop the music playing BASS_ChannelGetLevel keeps outputting values greater than say 0.1 for 3 seconds. Likewise if I restart the track BASS_ChannelGetLevel does not output values above 0.1 for 3 seconds.
I have tried setting _wasapi.SetFullDuplex buffer to true and false without any noticeable difference.
3 is my Windows speaker output loopback channel device number.

What is causing the 3 second delay and how can I fix it?

Private Sub Button3_Click(sender As System.Object, e As System.EventArgs) Handles Button3.Click

        Bass.BASS_Init(0, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero)
        err = Bass.BASS_ErrorGetCode()
        If Not err = 14 Then
            If err > 0 Then
                MsgBox("bass error " & err.ToString)
                Exit Sub
            End If
        End If

        '3 = loopback speaker output
        _wasapi = New BassWasapiHandler(3, False, 44100, 2, 0, 0.0F)
        _wasapi.Init()
        _wasapi.SetFullDuplex(0, BASSFlag.BASS_STREAM_DECODE, False)
        vu_stream = _wasapi.OutputChannel
        If _wasapi.DeviceMute Then
            _wasapi.DeviceMute = False
        End If
        _wasapi.Start()


        vu_timer.Interval = 50
        vu_timer.Start()

    End Sub

    Private Sub vu_timer_Tick(sender As System.Object, e As System.EventArgs) Handles vu_timer.Tick

        Dim left_val As Double = 0
        Dim right_val As Double = 0

        Dim Levels(2) As Single
        Bass.BASS_ChannelGetLevel(vu_stream, Levels, 0.02, BASSLevel.BASS_LEVEL_STEREO)
        left_val = Levels(0) * 100
        right_val = Levels(1) * 100

        Me.Text = left_val

        If left_val > 0.1 Then
            final_delay_left = False
            last_left_val = left_val
            Me.AGauge1.Value = left_val
        Else
            If final_delay_left = False Then
                final_delay_left = True
                Dim t = New Thread(Sub() Me.NeddleDecayLeft(last_left_val))
                t.Start()
            End If
        End If

    End Sub

Thanks

Ian @ un4seen

The latency will likely be caused by buffering in the intermediate stream. I don't think you need that stream, ie. you can get the level directly from the input/recording. I'm not very familiar with the BassWasapiHandler class so I'm not sure how to best use that, but you could call BASS_WASAPI_Init to initialize and then use BASS_WASAPI_GetLevel(Ex) to get the levels instead. Alternatively, you could use the regular BASS recording functions instead of BASSWASAPI, ie. BASS_RecordInit/Start and BASS_ChannelGetLevel(Ex), as BASS itself supports WASAPI loopback recording too.

If you just want to display the level (no other processing of the data) then another option is to simply use BASS_WASAPI_GetDeviceLevel instead. There's no need to even initialize the device for that. For example:

float left = BassWasapi.BASS_WASAPI_GetDeviceLevel(device, 0);
float right = BassWasapi.BASS_WASAPI_GetDeviceLevel(device, 1);

Please see the documentation for details on the mentioned functions.

toob

Quote from: Ian @ un4seenfloat left = BassWasapi.BASS_WASAPI_GetDeviceLevel(device, 0);
float right = BassWasapi.BASS_WASAPI_GetDeviceLevel(device, 1);

Thanks Ian that last option worked a treat and more concise for my VU meters.

I use the code below to feed a spectrum visuals stream and also to breakdown FFT frequencies into low/mid/hi ranges to drive DMX lighting on another stream, using two separate timers. The code works fine but your simplified answer above got me thinking if BassWasapiHandler is the best / most effective method to get the live windows speaker output for either the spectrum visuals or FFT data for the low/mid/hi freq triggering?

        _wasapi1 = New BassWasapiHandler(3, False, 44100, 2, 0.0F, 0.0F)
        _wasapi1.Init()
        _wasapi1.SetFullDuplex(0, BASSFlag.BASS_DEFAULT Or BASSFlag.BASS_STREAM_DECODE, False)
        _wasapi1.DeviceMute = False
        _wasapi1.DeviceVolume = 1.0F
        spectrum_stream = _wasapi1.OutputChannel
        bassmidhi_stream = _wasapi1.OutputChannel
        _wasapi1.Start()

       start_timers...

teq

Hi there! Recently i've decided to port my app(Unity) to macOS, but i face with issue.
There is no Waveform class under Un4seen.Bass.Misc. It's strange because under Windows it's totally fine.
And of course i use macOS version of bass.h and Bass.Net.OSX.

Ian @ un4seen

If I recall correctly, BASS.Net's Waveform class requires features that are only available on Windows, so it unfortunately isn't supported on other platforms.