libsndfile writing chords in wave files

I'm using the C++ wrapper on the libsndfile to write wave files. Aside from a slight peculiarity concerning some crackling in front of each note (which I think is caused by the way I'm putting together samples), it's worked really well.

I want to be able to write simultaneously playing notes into these wave files, both in the form of chords, and notes from different "tracks" I've made. I was wondering if anyone has had any experience doing this, I could really use the help.

Thanks.
I love this audio stuff

Mixing waveforms is as simple as adding the samples together. Just be sure to clip overflow rather than having it wrap:

1
2
3
4
5
6
7
8
9
10
11
12
13
for( ..each sample... )
{
  outputsample = 0;  // note output sample must be larger than the sample size
    // to catch overflow.  So if working with 16-bit audio, outputsample should be 32-bits

  for( ... each track... )
    outputsample += tracksample;

  if(outputsample > 0x7FFF) outputsample = 0x7FFF;  // assuming 16-bit signed samples
  if(outputsample < -0x8000) outputsample = 0x8000;

  WriteSample(outputsample);
}


EDIT: also note that if you have to clip the output like that, it will sound bad (although it will sound MUCH worse if you let it wrap).

Alternatives would be to lower the volume of all playing tracks if you have a lot of overflow.

There are also less naive algorithms for mixing audio which can reduce the possibility of clipping, but they're a lot more complicated.
Last edited on
Well, I have the samples stored in double[] arrays. The actual code I'm using is quite complex, so I set up a simple test case and it seems to output a chord, but I don't think it sounds right.

Here's the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const int format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
const int channels = 1;
const int sampleRate = 48000;
const char* outfilename = "/home/cyle/Desktop/test.wav";

SndfileHandle outfile(outfilename, SFM_WRITE, format, channels, sampleRate);

const int size = sampleRate * 3;
double * newSample = new double[size];

double cyclesPerSample = Note::C_3 / sampleRate;

for (int k = 0; k < size; ++k)
{
	newSample[k] = 0.3 * sin(2.0 * M_PI * cyclesPerSample * k);
}

cyclesPerSample = Note::G_3 / sampleRate;
double * newSample2 = new double[size];

for (int k = 0; k < size; ++k)
{
	newSample2[k] = 0.3 * sin(2.0 * M_PI * cyclesPerSample * k);
}


cyclesPerSample = Note::D_4 / sampleRate;
double * newSample3 = new double[size];

for (int k = 0; k < size; ++k)
{
	newSample2[k] = 0.3 * sin(2.0 * M_PI * cyclesPerSample * k);
}

double * mixedSample = new double[size];

for (int k = 0; k < size; ++k)
{
	mixedSample[k] = newSample[k] + newSample2[k] + newSample3[k];
}

outfile.write(&mixedSample[0], size * 2);

return 0;


Most of it is self explanatory, but if you have any particular questions, let me know. Things like Note::C_3 is just a static constant double with the frequency value of that note at that octave. I got the frequencies from a table posted on a university site. I tested them by writing a small melody and they sound right. The chord output sounds a little weird to me, but I might have done it wrong. I didn't do any clipping or anything. I simply set the volume down by multiplying them by 0.3. Do I maybe need to drop the volume more? The library I'm using looks like it have a normalize function, should I run that?
Well, I have the samples stored in double[] arrays.


In that case you'd clip at [-1...1]. Although in your case it won't matter because you'll never exceed those bounds (.3 + .3 + .3 is still less than 1, so you're fine)

Problems I noticed are:

line 32: you're putting that tone in newSample2, leaving newSample3 uninitialized. This is probably why it's not working

line 42: why are you writing 'size*2' samples? Don't you only have 'size' samples?

The library I'm using looks like it have a normalize function, should I run that?


AFAIK normalizing is just adjusting the overall volume level. Since you're already doing that yourself with the *0.3, you don't need to do that.


EDIT: I just want to mention that if there is the possibility for overflow, you really do need to clip at [-1..1]. Failure to do so will cause wretchedly horrible crackling noises when the audio overflows.
Last edited on
Good catches. I made the changes, but the chord that was written didn't seem to change. The file is half the length, though =). I adapted this code from a testing area in my code, and I must've forgotten to change the amount be written. Anyway, here's the new snippet of code. Shouldn't look too much different, aside from the edits and a little rearranging of a couple lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const int format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
const int channels = 1;
const int sampleRate = 48000;
const char* outfilename = "/home/cyle/Desktop/test.wav";

SndfileHandle outfile(outfilename, SFM_WRITE, format, channels, sampleRate);

const int size = sampleRate * 3;

double cyclesPerSample = Note::C_3 / sampleRate;
double * newSample = new double[size];

for (int k = 0; k < size; ++k)
{
	newSample[k] = 0.3 * sin(2.0 * M_PI * cyclesPerSample * k);
}

cyclesPerSample = Note::G_3 / sampleRate;
double * newSample2 = new double[size];

for (int k = 0; k < size; ++k)
{
	newSample2[k] = 0.3 * sin(2.0 * M_PI * cyclesPerSample * k);
}


cyclesPerSample = Note::D_4 / sampleRate;
double * newSample3 = new double[size];

for (int k = 0; k < size; ++k)
{
	newSample3[k] = 0.3 * sin(2.0 * M_PI * cyclesPerSample * k);
}

double * mixedSample = new double[size];

for (int k = 0; k < size; ++k)
{
	mixedSample[k] = newSample[k] + newSample2[k] + newSample3[k];
}

outfile.write(&mixedSample[0], size);

return 0;


I'm making this chord based on a score my friend wrote. I'm no good at reading music, but I'm 90% sure this is what the chord is supposed to be, but it doesn't sound like the chord.
I don't see any other problems. You're sure the tones are correct when you play them one at a time?

Also now that you mention it, I think the chord might be wrong. Is it a major chord? Major chords are 1,3,5 ... so a C major chord would be C, E, G, not C, G, D (or is this G major / D major? I forget what those scales are .. I'd need to see a keyboard =x)
Last edited on
Making the change to C_4, E_4, G_4 makes a nice chord. I'll have to talk to my friend and ask him what the notes are. I'm a novice at music theory and reading. I play drum xD

I think I'll be good for now. I'll play with my new knowledge and probably be back again at some point. Thanks for all of your help.

Edit: Changing all the 4's to 3's in that major chord sounds kind of awkward. I'm wondering if it's just the type of wave I'm using not making nice low chords. Or maybe it is OK and my speakers just suck. I'll play with it some more.
Last edited on
Reading notes is pretty simple =P Can you show a picture or something? I could tell you what the notes are.

Changing all the 4's to 3's in that major chord sounds kind of awkward


The octave shouldn't matter. That only only make the chord higher or lower, but it should sound more or less the same.

Or maybe it is OK and my speakers just suck.


Not likely. If your speakers couldn't play simple tones you would have noticed long before this.

My money is on either your Note::C_3 etc constants being wrong, or your chord being wrong, or both.


EDIT:

OK... here's how you calc tones. Check this against what you have to make sure your constants are right.

Concert A is 440 Hz (almost exactly, it's kind of freaky)

Moving up one octave is 2x Hz (so one octave up from concert A is 880, another octave up is 1760, etc)
Moving down one octave is 0.5 Hz (so 220, 110, etc)

Calculating other tones would be Tone * 2^(X/12) where 'X' is 1-11 depending on how many notes you want to go up. So:

A# = 1
B = 2
C = 3
C# = 4
... etc

So for example, to get middle C, we take A 220 and multiply it by 2^(3/12) = 261.626 Hz


Once you have the frequency in Hz... when generating the tone you do this:

1
2
3
4
for(i = 0; i < whatever; ++i)
{
  sample = volume * sin( 2 * PI * Freq_In_Hz * i / samplerate );
}



ANOTHER EDIT:

Your C_3 etc constants are floating point, right? They'll be all wrong if they're integers.

Seems like an obvious thing, but I figured I might as well check.
Last edited on
Yes, my constants are all doubles.

My constants look to have the correct values relative to one another. My 3rd octave frequencies are all really close to half of the 4th octave frequencies. I was told that the other chord I was talking about is supposed to have some dissonance to it, so that's probably why it sounded awkward.

The formula you have is the same one that I'm using, so I know that's right. What I'm wondering now, though, is if there are things we can do to the samples that affect EQ and add effects (mainly distortion at this point). I was also wondering about sampling percussion noises.

I know these are more complex concepts, and this is sort of getting further from a programming question and closer to a digital music question. We could move to PM or email, if that would be more appropriate.

Thank you for all your help so far, and I hope you can continue to help me.
There are lots of things you can do to manipulate the audio. Although it's a very complicated and diverse topic. I only briefly touched on it myself (most of what I know comes from writing retro system emulators, and you don't need to do a whole lot of DSP for those).

A starting point would be to get an FFT lib and play around with it.

Basically the way FFT works is you give it a chunk of audio samples, and with some mathematical magic it transforms those samples into a group of harmonics (tones).

Once you have the have the harmonics, you can raise the amplitude of some harmonics, and lower others (like an EQ control thing -- to give a bass boost or something).... add additional harmonics, or remove existing harmonics (to change the timbre, or add distortion)... change the phase of some harmonics so they mix with lower peaks reducing the risk of clipping... etc... etc.

After you make your changes you run it through a reverse FFT and change the harmonics back into a stream of samples.
Do you have a favorite FFT? I found one (http://www.fftw.org/) with a quick Google search of "C++ fft." It looks solid to me, but since I'm a beginner, I want to make sure. Also, would this FFT stuff have more effective ways of doing chords and tracks than the way you originally showed me?

Edit: I have also found KissFFT (http://sourceforge.net/projects/kissfft/), which also looks pretty good. I'm leaning more towards it that FFTW.
Last edited on
Yeah FFTW is the one I tried way back when. It's pretty good, but I haven't used any others so I don't have anything to compare it with.

Also, would this FFT stuff have more effective ways of doing chords and tracks than the way you originally showed me?


Not really.

All the FFT does it take a sound wave and convert it to a series of frequencies. You can mess with those frequencies however you want, the convert them back to a sound wave.

You could use that to generate tones and chords instead of how you're doing it now, but it wouldn't really be practical.
I'm gonna take both FFTW and KissFFT for a test drive and see what I come up with. I'll probably return with more questions after some time with it.

Thanks again for all of your help. You will definitely be credited once I get this program working.
Topic archived. No new replies allowed.