Applying Gain to buffer problem

Started by Chris Oakley,

Chris Oakley

I have a process where I need to apply a normalization / gain value to my PCM buffer in memory. The reason for this is I manipulate the array on the fly so you can edit the audio, add audio and even change the gain of the audio on selection of it, not just the whole file.

One of the processes is Normalization. My routine works fine, but it has a problem in that because I'm working on the buffer as 16 bit, I can't apply the gain properly.

Let me explain. If I do this:
Dim _vol As Single = Utils.GetNormalizationGain(_FilenameUsed, 10, _Start, _End, _peak)
I can get a gain back like 1.00003506 which is fine.

However, if you try to apply that to the buffer like this:
Dim _s As Short = BitConverter.ToInt16(bufferpcm, i)
_s *= _vol

you will probably see the issue right away. If _s = 62 then it will still be 62.

If I was to set up a DSP_Gain on the buffer stream and apply the same gain value of 1.00003506 then it would work.

So how can I replicate what the DSP_Gain does on the buffer data. There has to be a way to manipulate it how I wish.

Ian @ un4seen

For greater resolution, you should make the data floating-point. If you're getting the data from a BASS decoder then adding the BASS_SAMPLE_FLOAT flag to that will do it. If the data is coming from somewhere else then you can convert it (from 16-bit) to floating-point by dividing each sample by 32768.0.

Chris Oakley

Thanks Ian. I would make it Floating point, but I've already coded everything to work on 16 bit and all the functions and the buffer in memory is designed for 16 bit.

I tried:
Dim _vol As Single = Utils.GetNormalizationGain(_FilenameUsed, 10, _Start, _End, _peak)
Then in the loop through the buffer:
Dim _s As Short = BitConverter.ToInt16(bufferpcm, i)
Dim _fp As Single = _s / 32768.0
Dim _p As Single = _fp * _vol
Dim _p2 As Integer = _p * 32768.0

However _p2 still ends up as the same value as _s if _vol = 1.00003052

I reckon I'm doing something wrong somewhere.

saga

The amount of gain you want to apply is going to change a 16-bit signal by at most +/-1. 62 multiplied by 1.00003506 is 62.00217372, how do you expect that to be represented as a 16-bit integer value, if not as 62? If you need to be able to represent such a miniscule change in gain, floating-point audio is the only way to do. You are exceeding the limits of what 16-bit audio can express here.
Alternatively, you may consider a normalization factor that close to 1 to be effectively 1. It's probably the result of the highest peak already being 32767, while the negative maximum of a 16-bit signal is -32768 and the positive maximum of a 16-bit signal is 32767, meaning that there is no way to apply normalization to it. It would only result in (practically inaudible) distortion of the signal.

Chris Oakley

Whilst I understand what's being said, something doesn't make sense. If I open up the file like this:
StreamChan = Bass.BASS_StreamCreateFile(Filename, 0, 0, BASSFlag.BASS_STREAM_DECODE)and I don't specifiy the FLOAT and then I get the gain:
Vol = Un4seen.Bass.Utils.GetNormalizationGain(Filename, 10, -1, -1, Peak)
Then I do this:
ga = New Misc.DSP_Gain(stream, 0)
ga.Gain = Vol
then use a WaveWriter to output the data to a file then it manages to apply the correct gain calculation then, no matter how minute.

I do this by getting the data as a Short:
Dim data(32768 - 1) As Short
While Bass.BASS_ChannelIsActive(stream) = BASSActive.BASS_ACTIVE_PLAYING
    Dim Length As Integer = Bass.BASS_ChannelGetData(stream, data, 32768)
    WW.Write(data, Length)
End While

Ian @ un4seen

Quote from: Chris OakleyThen I do this:
ga = New Misc.DSP_Gain(stream, 0)
ga.Gain = Vol
then use a WaveWriter to output the data to a file then it manages to apply the correct gain calculation then, no matter how minute.

Even then, such a small gain will not be having any effect on low level samples like 62. The only sample values that will be changed by a gain of 1.00003506 are +/-28523 and beyond. 28522 x 1.00003506 = 28522.9999 (still 28522 when converted to integer).

If you're going to be modifying sample data (especially if multiple times) then I would recommend using floating-point data. That should also allow you to simplify your code, as there's no need for overflow checking after each operation.

Chris Oakley

Thanks Ian. I found out what was causing the issue we were seeing. It wasn't the normalization. We have a post process which uses FFPMEG to analyse the file and determine the perceived LUFS loudness gain. It turns out, and we didn't spot at first, if the file is mono you get a different value than if it's stereo.

Our other process that normlalized the file and did this post process was automatically converting to stereo but the file we were testing was mono. It took me a day to clock that. So this is why I ended up down yet another rabbit hole.