Home > General > PCM Audio | Part 3: Basic Audio Effects – Volume Control

PCM Audio | Part 3: Basic Audio Effects – Volume Control

January 12th, 2010

So now we know what data is stored in a PCM stream, let’s look at some real waveform examples. The easiest is a simple sine wave:

sine wave

Now if we “amplify” that wave by 5, we’d get a much louder sound, represented by a wave that looked like this:

sine wave times 10

So if you want to increase the volume of your PCM stream, just multiply every PCM value by some number. If we had 2048 bytes of audio (remember… that’s 1024 samples since each sample is two bytes), we could amplify the stream with this type of code:

int16_t pcm[1024] = read in some pcm data;
for (ctr = 0; ctr < 1024; ctr++) {
    pcm[ctr] *= 2;
}

Volume control is almost that simple. There's two catches.

Clipping

Clipping occurs when your resulting value increases above the maximum value for a sample. So since we're dealing with signed 16 bit integers our maximum positive sample is 32767. If we have a PCM sample value of 5000 and we multiplied it by 10, the resulting value is -15536, not the expected 50000. When clipping occurs, you end up with noise in the audio. You should always check to see if the result of your multiplication would cause clipping, and if so, set the value to 32767 (or -32768) instead.

So our code above becomes:

int16_t pcm[1024] = read in some pcm data;
int32_t pcmval;
for (ctr = 0; ctr < 1024; ctr++) {
    pcmval = pcm[ctr] * 2;
    if (pcmval < 32767 && pcmval > -32768) {
        pcm[ctr] = pcmval
    } else if (pcmval > 32767) {
        pcm[ctr] = 32767;
    } else if (pcmval < -32768) {
        pcm[ctr] = -32768;
    }
}

Volume Is Logarithmic

The other catch is that volume as perceived by humans (measured in decibels) is logarithmic, not linear. Your first instinct would be to think "Well if I wanted to double the volume, I should just multiply the samples by 2." Unfortunately, it's not quite that easy.

Multiplying a value by 1 will obviously give you no amplification. So to decrease volume, you would multiply by a value less than 1 and greater than 0. To increase volume, multiply by a number greater than one. Unfortunately, I didn't pay enough attention to logarithms in school, so I don't have a clever answer as to how to implement a proper volume control, but I've found that this function works pretty well:

int some_level;
float multiplier = tan(some_level/100.0);

If some_level is set to a value between 0 and 148 or so, this will give you a rather linear sounding multiplier. 79 is almost a multiplier of 1 (no amplification). It is far -- really far -- from perfect, but it worked well enough for my needs of implementing a volume slider. Graphing that function from 0 to 148 gives you this:

volume multiplier

So to set an appropriate level, now we have a volume slider at 39 (roughly half volume):

int16_t pcm[1024] = read in some pcm data;
int32_t pcmval;
uint8_t level = 39; // half as loud
// uint8_t level = 118 // twice as loud (79 * 1.5)
float multiplier = tan(level/100.0);
for (ctr = 0; ctr < 1024; ctr++) {
    pcmval = pcm[ctr] * multiplier;
    if (pcmval < 32767 && pcmval > -32768) {
        pcm[ctr] = pcmval
    } else if (pcmval > 32767) {
        pcm[ctr] = 32767;
    } else if (pcmval < -32768) {
        pcm[ctr] = -32768;
    }
}

I wasn't able to find a simple logarithmic slider example, so if you have one, please post in the comments. I'd love to replace my hack.

Using some simple algorithms and that function above, you could easily implement a fade-in/out effect on PCM data by stepping through all 148 possible values over a period of time. And don't worry, we'll get to "time" later in the series.

That's pretty much all there is to know about volume, in the next part of the series, we're going to discuss mixing two streams together to create one stream.

General , ,

  1. bog
    February 28th, 2011 at 10:25 | #1

    Thanks for this simple explanation of PCM streams. It made things clearer for me.

  2. March 9th, 2011 at 04:36 | #2

    Hi. Nice article you have. But nothing is written about Channels. I have a small doubt I am working on from past few hours. Can you help me out with this?

    I am actually dealing with Stereo PCM 16 bit Signed Little Endian (everything what you’ve mentioned :)). Goal is to redirect sound only on one side of the speaker (left or right). I tried filling alternate byte pairs (16-bit pairs) with zeros as they were repeating. I thought they must be left and right speaker sound interleaving. But even after doing this, sound is pretty much same.

    Have any idea about this?

  3. Gaurav
    June 16th, 2011 at 05:34 | #3

    Very nice article. it really helped me to understand basic of audio. I was searching some term on audio on google and found your link. from the first line i could not stop me to read it complete. nice effort.
    you talked about signed audio. can you write some note on that also. that will be gr8.
    Thanks

  4. Arati
    March 22nd, 2012 at 02:39 | #4

    Very nice article on PCM. Recently I started working on testing alsa audio drivers for my sound card and I was not getting S16LE, U24BE etc in my code. Now I got a clear picture about PCM stream. Keep posting such good articles.

  5. Devika
    November 20th, 2012 at 14:35 | #5

    Great effort! Your article helped me understand the basics of PCM very clearly. I have started working on stereo recently, and it would be very helpful if you could explain about stereo PCM as well. Thanks a lot!

  6. June 24th, 2013 at 16:21 | #6

    8bit pcm is not signed. A signal that is going from +20 to -20
    will show up as going from 148 to 108. the 128 value will be
    the center line. In a 16 bit pcm it will be a signed value and zero value
    will be the center line.

  1. No trackbacks yet.