libopenmpt playback have problem

Started by EthanF4D, February 07, 2018, 07:30:10

Previous topic - Next topic

EthanF4D

TLDR:
openmpt_module_read_interleaved_float_stereo(mod, samplerate, count, interleaved_stereo) only sounds perfect when count = 1024.
When count < 1024, the playback is choppy. When count > 1024, has kicks and becomes muddy, also individual note tempos are not correct.
Am I using it correctly?

A little bit background on what I am doing:
I am a game developer, attempting to write a music game like beatmania.
I am developing in Unity (C#). I wrote a wrapper to libopenmpt.dll .
I successfully load a module file, use openmpt_module_read_interleaved_float_stereo() to read into a buffer and then output the data directly.
The sample rate is 48000, and the buffer size is 2048 (1024 * 2 channels).
Under these conditions, the music playback is perfect.

At this point, I have to mute some notes from the music so that the user can hit the notes and playback separately.
I will mute the notes using the interface openmpt_module_ext_interface_interactive.
As most notes are note on/off at the start/end of a row, ideally I would read the audio samples in each rows.

So I have tests on reading the samples using different buffer sizes, and got the results I stated at the beginning.
I read the example, it uses 480 (because of 1/10 of sample rate 48000), but sounds choppy.
256, 512 (1/4 and 1/2 of 1024), sounds choppy
2048, 4096 (2 and 4 times of 1024), 4472 (my test song's row worth of samples), have some kicks and muddy sound.
48000 (same as sample rate), have kicks, overall song tempo is correct, but individual note duration is incorrect.

manx

I'm not quite sure I understand correctly what you are doing, I'll try to answer anyway.

Quote from: EthanF4D on February 07, 2018, 07:30:10
TLDR:
openmpt_module_read_interleaved_float_stereo(mod, samplerate, count, interleaved_stereo) only sounds perfect when count = 1024.
When count < 1024, the playback is choppy. When count > 1024, has kicks and becomes muddy, also individual note tempos are not correct.
Am I using it correctly?
[...]
The sample rate is 48000, and the buffer size is 2048 (1024 * 2 channels).

I do not know how Unity sound output works, but if this buffer size implies that Unity hands you a buffer or callback that it wants you to fill exactly 1024 frames into, the following applies:
I think this sounds like you are not using it correctly. If your output buffer size is always 1024, and you are only varying the libopenmpt read count, you inevitably would have to use an additional buffer layer in between in order to store superfluous samples of a read call when the output buffer is full.

If you want to render the output directly from libopenmpt to the sound output buffer (there are reasons to do it that way as well as reasons not to), the canonical way is to just call libopenmpt with a read count the same size as the buffer has free space at the current point in time (in case the output API works without fixed size buffer chunks). The count passed to the read function can vary with each consecutive call. Thus, if you connect sound output and libopenmpt directly and your output API uses a fixe3d buffer chunk size, the easiest way is to just adapt the libopenmpt read count to that buffer size (which in your case is 1024).

The problem you are experiencing sounds like a problem regarding correctly filling the output buffer as demanded by your sound output API, and nothing specific to libopenmpt.

It would probably help if you could show the relevant code fragments (i.e. the part from Unity handing you a buffer up to your call to libopenmpt filling it (if Unity's API is a pull-style API), or from you filling a buffer with libopenmpt up to sending it to Unity (if Unity's API is push-style API).

EthanF4D

Thank you for the reply.

Unity is pull-style as you described. The data array is interleaved. Number of samples is data.Length / channels.
The simple direct output code sounds perfect:

        protected virtual void OnAudioFilterRead(float[] data, int channels)
        {
            int samplesToRead= data.Length / channels; // <--- debug revealed this is 1024
            ReadAudioDataFromModule(data, channels, (ulong)samplesToRead);
        }

        private void ReadAudioDataFromModule(float[] buffer, int channels, ulong samplesToRead)
        {
            ulong read;
            switch (channels)
            {
                case 1:
                    read = OpenMPTWrapper.openmpt_module_read_float_mono(Module.ModulePtr, SampleRate, samplesToRead, buffer);
                    break;
                case 2:
                    read = OpenMPTWrapper.openmpt_module_read_interleaved_float_stereo(Module.ModulePtr, SampleRate, samplesToRead, buffer);
                    break;
                case 4:
                    read = OpenMPTWrapper.openmpt_module_read_interleaved_float_quad(Module.ModulePtr, SampleRate, samplesToRead, buffer);
                    break;
                default:
                    throw new NotImplementedException();
            }
        }


for my usage of obtaining a different buffer size, a buffer layer is used:

        public int RequestFixedSamples = 4472; // <----- adjust sample count, 4472 is num samples of a row in my test song

        private int PreReadOffset = 0;
        private float[] Buffer = null;
        private int RowSamplesLeftOver = 0;
       
        protected override void OnAudioFilterRead(float[] data, int channels)
        {
            int numberOfDataSamples = data.Length / channels; // <--- debug revealed this is 1024
            int numberOfSampleRequested = RequestFixedSamples;
            if (Buffer == null || Buffer.Length < numberOfSampleRequested * channels)
            {
                Buffer = new float[numberOfSampleRequested * channels];
            }

            int dataOffset = 0;
            while (numberOfDataSamples > 0)
            {
                bool newRow = RowSamplesLeftOver == 0;
                if (newRow)
                {
                    ++CurrentRow;
                    // MuteAndUnmuteSomeChannels();
                    ReadAudioDataFromModule(Buffer, channels, (ulong)numberOfSampleRequested);
                    PreReadOffset = 0;
                }
                int samplesToRead = newRow ? numberOfSampleRequested : RowSamplesLeftOver;
                if (numberOfDataSamples >= samplesToRead)
                {
                    RowSamplesLeftOver = 0;
                }
                else
                {
                    RowSamplesLeftOver = samplesToRead - numberOfDataSamples;
                    samplesToRead = numberOfDataSamples;
                }
                Array.Copy(Buffer, PreReadOffset, data, dataOffset, samplesToRead * channels);
                PreReadOffset += samplesToRead;
                dataOffset += samplesToRead;
                numberOfDataSamples -= samplesToRead;
            }
        }

EthanF4D

Thanks manx for your time.

I have found the bug and like you said, it is the copy to buffer error.
The PreReadOffset and dataOffset was incremented incorrectly.
After the correction, the audio sounds perfect again.

Please remove this post if you see fit.
--------------------------
Array.Copy(Buffer, PreReadOffset, data, dataOffset, samplesToRead * channels);
PreReadOffset += samplesToRead * channels;
dataOffset += samplesToRead * channels;
numberOfDataSamples -= samplesToRead;