Author Topic: (BASS.NET) Is it possible to optimize this code to reduce latency?  (Read 521 times)

trojannemo

  • Posts: 97
Hi everyone. Got another help post. One of my free software is a little piano player that uses MIDI notes as well as real piano samples in .ogg format. The user can click on each key to play the sample or use their keyboard or connect a midi keyboard to the pc and use it with the software. The point is there's multiple ways to activate the PlaySample code below. I don't notice any latency. But I have a reasonably powerful computer. I have a user say that he's experiencing 100ms or so of latency which is killing the enjoyment. I looked at my code and it all seems to be right and I can't figure out how to improve it. Can you take a look and let me know if there's an "aha!" moment of something i'm missing that could improve playback, possibly on lower end computers?
The piano samples are loaded into memory at runtime or if they change the base octave they're playing on. But they are NOT loaded every time a key is pressed.

Here's the relevant C# code:

Code: [Select]
using Un4seen.Bass;
using Un4seen.Bass.AddOn.Mix;

private readonly List<int> STREAMS;
private int BassMixer;
private int BassStream;
private const int BassBuffer = 100;

if (!Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, Handle))
{
     MessageBox.Show("Error initializing BASS.NET:\n" + Bass.BASS_ErrorGetCode() + "\nWon't be able to play any audio!", AppName, MessageBoxButtons.OK, MessageBoxIcon.Error);
     return;
}
Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_BUFFER, BassBuffer);
Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_UPDATEPERIOD, 5);

private void LoadPianoSamples()
        {
            var SamplePath = Application.StartupPath + "\\samples\\";
            var missing = new List<int>();
            for (var i = 0; i < 25; i++)
            {
                var actualSample = (BaseOctave * 12) + i + 3; //we're always going from C1, first three samples can't be used
                try
                {
                    if (File.Exists(SamplePath + actualSample + ".ogg"))
                    {
                        STREAMS[i] = Bass.BASS_StreamCreateFile(SamplePath + actualSample + ".ogg", 0L, 0L, BASSFlag.BASS_SAMPLE_FLOAT);
                    }
                    else if (File.Exists(SamplePath + actualSample + ".wav"))
                    {
                        STREAMS[i] = Bass.BASS_StreamCreateFile(SamplePath + actualSample + ".wav", 0L, 0L, BASSFlag.BASS_SAMPLE_FLOAT);
                    }
                    else
                    {
                        missing.Add(actualSample);
                        STREAMS[i] = 0;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error loading sample: '" + actualSample + "':\n" + ex.Message + "\nSample will NOT play.", AppName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            if (!missing.Any()) return;
            var samples = missing.Aggregate("", (current, t) => current + ("'" + t + "'\n"));
            MessageBox.Show("The following " + (missing.Count > 1 ? "samples are" : "sample is") + " missing:\n" + samples + "You can use .ogg and .wav files only\nMissing " + (missing.Count > 1 ? "samples" : "sample") + " will NOT play.", AppName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

private void PlaySample(int key, int velocity)
        {
            const int PADDING = 12; //seems this player is an octave lower than our piano samples, let's compensate
            var MIDI_NOTE = PADDING + key + (BaseOctave*12);
            if (CurrentAudioType > 0)
            {
                MidiPlayer.Play(new NoteOn(0, 1, (byte)MIDI_NOTE, 127));
            }
            else
            {
                Bass.BASS_ChannelSetAttribute(STREAMS[key], BASSAttribute.BASS_ATTRIB_VOL, (float)(velocity / 127.0));
                Bass.BASS_ChannelPlay(STREAMS[key], true);
            }
            STATE_INPUT[key] = true;
            UpdatePressedKeys(key);
        }

Thank you!

Ian @ un4seen

  • Administrator
  • Posts: 25882
Rather than reducing the playback buffer size (BASS_CONFIG_BUFFER) and update period (BASS_CONFIG_UPDATEPERIOD), I would suggest you try disabling playback buffering via BASS_ATTRIB_BUFFER. For example:

Code: [Select]
STREAMS[i] = Bass.BASS_StreamCreateFile(...);
Bass.BASS_ChannelSetAttribute(STREAMS[i], BASSAttribute.BASS_ATTRIB_BUFFER, 0); // disable playback buffering

Another option is to use samples instead of streams, ie. BASS_SampleLoad instead of BASS_StreamCreateFile. That will mean the file is pre-decoded to memory, so it doesn't need to be decoded during each playback. If you need some stream features (eg. DSP/FX/syncs) then note that you can create a stream from a sample via BASS_SampleGetChannel. Please see the documentation for details.

trojannemo

  • Posts: 97
Getting this error. I must be using an old version?

'Un4seen.Bass.BASSAttribute' does not contain a definition for 'BASS_ATTRIB_BUFFER'

radio42

  • Posts: 4838
Maybe you do something wrong or are missing a reference.
    BASSAttribute.BASS_ATTRIB_BUFFER
is contained since many versions within Bass.Net!

trojannemo

  • Posts: 97
I don't think i've updated it my Bass or Bass.NET copies in YEARS. It's worked fine as is and I don't tend to update for the sake of updating. It looks like my current version is 2.4.11.0. Let me get the latest version and try again.

trojannemo

  • Posts: 97
Oh boy. Was going to post that it's not working with the new versions at all but i figured out the cause - you're up to .NET 4.8 and i'm still on .NET 4.0. Let me see if I can change the target framework without causing any issues.

trojannemo

  • Posts: 97
Ok nothing broke.

Code: [Select]
var ogg = File.ReadAllBytes(SamplePath + actualSample + ".ogg");
STREAMS[i] = Bass.BASS_SampleLoad(ogg, 0L, ogg.Length, ogg.Length, BASSFlag.BASS_SAMPLE_FLOAT);

This doesn't work. Silent audio. Any idea why?

Code: [Select]
STREAMS[i] = Bass.BASS_StreamCreateFile(SamplePath + actualSample + ".ogg", 0L, 0L, BASSFlag.BASS_SAMPLE_FLOAT);
Bass.BASS_ChannelSetAttribute(STREAMS[i], BASSAttribute.BASS_ATTRIB_BUFFER, 0); // disable playback buffering

This works, but I'm not sure if it's "fixing the issue" since I don't notice a difference before and after disabling the buffer.

Ian @ un4seen

  • Administrator
  • Posts: 25882
Code: [Select]
var ogg = File.ReadAllBytes(SamplePath + actualSample + ".ogg");
STREAMS[i] = Bass.BASS_SampleLoad(ogg, 0L, ogg.Length, ogg.Length, BASSFlag.BASS_SAMPLE_FLOAT);

This doesn't work. Silent audio. Any idea why?

When using samples, you first need to get a sample channel from BASS_SampleGetChannel before playing it. For example:

Code: [Select]
chan = BASS_SampleGetChannel(sample, 0);
BASS_ChannelStart(chan);

Note that if the BASS_SampleLoad "max" parameter is set to 1 then the channel handle returned by BASS_SampleGetChannel will be the same as the sample handle, which means you could call BASS_SampleGetChannel straight after BASS_SampleLoad and then use the sample handle in BASS_ChannelPlay/Start calls. Please see the BASS_SampleLoad and BASS_SampleGetChannel documentation for details.

Code: [Select]
STREAMS[i] = Bass.BASS_StreamCreateFile(SamplePath + actualSample + ".ogg", 0L, 0L, BASSFlag.BASS_SAMPLE_FLOAT);
Bass.BASS_ChannelSetAttribute(STREAMS[i], BASSAttribute.BASS_ATTRIB_BUFFER, 0); // disable playback buffering

This works, but I'm not sure if it's "fixing the issue" since I don't notice a difference before and after disabling the buffer.

What is the BASS_ChannelSetAttribute return value? If it's false, make sure you also updated BASS.DLL (as well as BASS.Net). The BASS_ATTRIB_BUFFER option was introduced in BASS 2.4.13.

Looking at your code again now, I don't think there is actually any need to either set a small playback and update period (as in your code) or to disable playback buffering. The only reason to do those things would be for realtime generated data/DSP, but you're just playing OGG/WAV files? You could still try using samples instead though, to have the file data pre-decoded to memory.

trojannemo

  • Posts: 97
I was using very old versions of bass and bass.net. Now all my BASS files and add-ons are up to date.
I'll have to play around with the samples - i've never used them so I was a little stuck there. But this is only to play ogg or wav samples of piano keys. No fancy effects or mixing or anything else.