Search code examples
cmergepcm

Adding a 440HZ-tone on a 2ch PCM (merging PCM, noise-overlay)


I would like to add some noise on a

44100 Hz
2 channel
16 bit
interleaved PCM

I am generating 1 second of the 440Hz-noise and store it in a noise-buffer:

#define SAMPLING_RATE 44100
int *noise_buffer;

void generate_440(int *buffer) {
    int pos;  // sample number we're on
    float volume = 0.1;  // 0 to 1.0, one being the loudest

    for (pos = 0; pos < SAMPLING_RATE; pos++) {
        float a = (2 * 3.14159) * pos / (SAMPLING_RATE / 440.0);
        float v = sin(a) * volume;

        // convert from [-1.0,1.0] to [-32768,32767]:
        buffer[pos] = remap_level_to_signed_16_bit(v);
    }
}
..
noise_buffer = (int *) malloc(SAMPLING_RATE * sizeof(int));
generate_440(noise_buffer);

and add it at the moment in this way to my PCM-stream:

int count = read(file_fd, buffer, len);
int bytesPerSample = 4;
int samples = count / bytesPerSample, c, currentSample;
for (currentSample = 0; currentSample < samples; currentSample++) {
    samplesWritten++;
    memcpy(buffer+(currentSample * bytesPerSample), noise_buffer+((samplesWritten%SAMPLING_RATE)), bytesPerSample);
}

This works, but it replaces the original PCM in the buffer with the (loud) 440Hz-tone.

In have then tried to "merge" the original PCM with the noise, so one can hear the original-audio with a noise-overlay in this way (instead of the memcpy):

for (c = 0; c < bytesPerSample; c++) {
    *(buffer+(currentSample * bytesPerSample) + c)=(*(buffer+(currentSample * bytesPerSample) + c)+*(noise_buffer+((samplesWritten%SAMPLING_RATE)) + c)) / 2;
}

But it produces a strange sound. I think the problem has something to do with the 2 channels? Sadly I'm not yet familar with audio-data.

Can someone help me out or push me in the right direction?


Solution

  • Here's how I'd add two signals:

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #include <limits.h>
    
    #define SAMPLING_RATE 44100
    
    int remap_level_to_signed_16_bit(float v)
    {
      if (v >= 1)
        return 32767;
    
      if (v <= -1)
        return -32767;
    
      return v * 32767;
    }
    
    void generate_freq(int *buffer, size_t count, float volume, float freq)
    {
      size_t pos; // sample number we're on
    
      for (pos = 0; pos < count; pos++) {
        float a = 2 * 3.14159f * freq * pos / SAMPLING_RATE;
        float v = sin(a) * volume;
        // convert from [-1.0,1.0] to [-32767,32767]:
        buffer[pos] = remap_level_to_signed_16_bit(v);
      }
    }
    
    void generate_noise(int *buffer, size_t count, float volume)
    {
      size_t pos; // sample number we're on
    
      for (pos = 0; pos < count; pos++) {
        // random number [-1.0,1.0]
        float v = (rand() - RAND_MAX / 2.0f) * 2 / RAND_MAX * volume;
        // convert from [-1.0,1.0] to [-32767,32767]:
        buffer[pos] = remap_level_to_signed_16_bit(v);
      }
    }
    
    int add_two_16_bit_samples(int a, int b)
    {
      int sum;
    
      // add a and b avoiding overflow
      if (a >= 0 && b >= 0) {
        if (a > INT_MAX - b) // mathematically equivalent to if (a + b > INT_MAX)
          sum = INT_MAX; // limit sum at INT_MAX if overflow
        else
          sum = a + b;
      } else if (a < 0 && b < 0) {
        if (a < INT_MIN - b) // mathematically equivalent to if (a + b < INT_MIN)
          sum = INT_MIN; // limit sum at INT_MIN if overflow
        else
          sum = a + b;
      } else {
        sum = a + b;
      }
    
      // limit sum to [-32767,32767]
      if (sum > 32767)
        sum = 32767;
    
      if (sum < -32767)
        sum = -32767;
    
      return sum;
    }
    
    void add_16_bit_samples(int *destination, const int* source, size_t count)
    {
      for (; count--; destination++, source++) {
        *destination = add_two_16_bit_samples(*destination, *source);
      }
    }
    
    int save_16_bit_samples(const char* name, int* buf, size_t count, int stereo)
    {
      FILE* f = fopen(name, "wb");
      int res = -1; // failure by default
    
      if (f != NULL) {
    
        while (count) {
          // convert sample to 2's complement unsigned representation
          unsigned v = *buf++;
    
          // separate it into 8-bit parts
          unsigned char c[2];
          c[0] = v & 0xFF; // LSB
          c[1] = (v >> 8) & 0xFF; // MSB
    
          // save it as little endian
          if (fwrite(c, 1, 2, f) != 2)
            break;
    
          // do once more if stereo
          if (stereo)
            if (fwrite(c, 1, 2, f) != 2)
              break;
    
          count--;
        }
    
        if (count == 0)
          res = 0; // success if all samples written out
    
        fclose(f);
      }
    
      return res;
    }
    
    int main(void)
    {
      int* buf_440 = malloc(SAMPLING_RATE * sizeof(int));
      int* buf_noise = malloc(SAMPLING_RATE * sizeof(int));
    
      if (buf_440 == NULL || buf_noise == NULL) {
        printf("failed to allocate memory for samples\n");
        return EXIT_FAILURE;
      }
    
      generate_freq(buf_440, SAMPLING_RATE, 0.1f/*volume*/, 440.0f/*freq*/);
      generate_noise(buf_noise, SAMPLING_RATE, 0.01f/*volume*/);
    
      add_16_bit_samples(buf_440, buf_noise, SAMPLING_RATE);
    
      if (save_16_bit_samples("440noise.pcm", buf_440, SAMPLING_RATE, 1/*stereo*/)) {
        printf("failed to save samples to file\n");
        return EXIT_FAILURE;
      }
    
      return EXIT_SUCCESS;
    }