Generate loopable sine wave cycle for given frequencies

I am somewhat new to DSP so bare with me. I am working on a simple wavetable oscillator. What I am trying to do is generate one cycle of a wave form for each note (frequency) and store it in an array for later playback. I know I could generate one frequency, store it and resample to the other frequencies in real time but I'm looking for performance and I'm still learning so I'm trying to keep it simple for now.

In my first attempts I was able to generate one cycle of a sine wave at each frequency but when I went to play back the stored wave forms the frequencies were off by 0.5 - 0.10 hz or more and had a noticeable clicking noise when the cycle repeated. I know this is because the start and end of the wave form are not lining up on 0. I've been struggling to solve this problem.

Below is my most recent C# code I use to generate and store the sine wave. This will produce more cycles but seems to fix Hz variance I was getting with just generating one cycle. The only issue that remains is the clicking when it loops back. I may be going about this completely wrong so any suggestions would be greatly appreciated.

double frequency = 440.0; //A4
double sampleRate = 44100;
double numSamples = sampleRate / frequency;
double phase = frequency / sampleRate;

int j = 0;
for (double i = 0.0; i < numSamples; i += phase)
{
double sample = Math.Sin(i * 2 * Math.PI);

sineWaveTable[j] = sample;

j++;
}


I'm not sure if it's even possible to generate a single cycle of a sine wave for each note and have it start and stop on 0. If not, am I stuck with having to use resampleing to get the other desired notes?

• Only frequencies that divide the sampling rate will have a single cycle that fits a repeated buffer of an integer number of samples. So what you want to do, play back all possible frequencies from a single cycle buffer without interpolation or resampling does not work. Oct 22, 2016 at 22:39
• Also you mention performance as a design goal: On many architectures, small computations are much faster than memory lookup. And memory lookup gets a lot faster if you focus on a small table that fits the cache size. So you might find that a single table with a good interpolation method is significantly faster than a full table of samples for all possible notes. Oct 22, 2016 at 22:41
• Jazzmaniac, thank you for your reply. I was afraid that would be the anwser. Can you recommend a decent interpolation method to use? I have tried a few I found on various websites but they actually used more CPU than if I were to directly calculate the wave form.
– djex
Oct 22, 2016 at 23:31
• You may be interested in reading about the CORDIC algorithm: en.wikipedia.org/wiki/Cordic
– MBaz
Oct 23, 2016 at 0:55
• if you have enough points in your wavetable, linear interpolation is just fine. Oct 23, 2016 at 3:45

Your clicks are coming from two sources. The wavetable and the hardware.

If you are looking to create a simple little instrument that produces tones by playing back a wavetable, then there is no way to fix the size of the "playback window" to reproduce tones without clicks.

The frequencies that compose the chromatic scale follow a geometric progression that achieves doubling of the frequency in 12 steps. The frequency for each tone is produced by multiplying the previous tone by something like 1.05. That factor is worked out by: Let's take $A_F = 440Hz$ and its octave at $A_O = 880Hz$. Each one of the tones is $A_{n+1} = A_n * r$, so $A_O = A_F \cdot r^{12}$. But we know that $A_O = A_F \cdot 2$. Therefore, $r^{12} = 2$ and $r \approx 1.059463094359$

So, you might fit $A_F,A_O$ neatly in a wavetable, but $A_F \sharp$ now needs $\approx 1.0594$ more samples to fit in neatly. Not only there is no integer there but even if you could reproduce the tones in neat windows you would get problems with the tempo, the duration of each note in a musical piece, because $A_F$ is shorter than $A_F \sharp$.

The "classic" way around this is to setup a high resolution wavetable of a single sinusoid which is then read back at "different speeds", with interpolation. So, $A_F$ is read back at $r^0 = 1$ speed (and practically no interpolation) but $A_F \sharp$ is read back at $r^{1} \approx 1.0594$ speed and a bit of interpolation when the "needle" has to land between known samples.

BUT!, this has nothing to do with your output stream. In other words, you have to set-up (or adopt) a stream system where samples are continuously being pushed to the sound card and you have a "global" sense of time.

Once upon a time, you did this by setting up three pointers (that's in C, C++) to three buffers. Let's call them $H,D,L$. You set up the sound card to record in $H$ and playback $L$ and while that was going on, your main code was processing buffer $D$. When the sound card called its callback to signify that the recording ended, you would "circularly switch" the buffers around so that they now pointed to $L \rightarrow D, D \rightarrow H, H \rightarrow silence$ and re-set up the sound card to do the same job while you were now processing the $D$ which is now your freshly recorded $H$. Those buffers had a length, say for instance 1000 samples and the task then was becoming to find a sweet spot that balanced length of buffer with responsiveness.

But as you can see, in this system, samples are being pushed out to the sound card continuously in blocks of $bufferLength$ samples and you don't really care if what is being played back fits the buffer. All that you do is iterate your models (e.g. the wavetable), produced a buffer of samples, freeze the models, playback that buffer and move again from the top.

Now, obviously, the above includes real time input from audio sources too. If all you are doing is a tiny little synth, you could have two buffers and switch around between them, working on one while the other is being played back.

But even if you did this with some off the shelf cheap sound card in the market, you still got clicks although, you have worked so hard to perfectly align the zeroes between blocks. This is because of something called latency.

In other words, just because your program called some Operating System Application Programming Interface function that said "Record NOW!" (or "Play NOW!), it doesn't mean that this command will propagate through the driver and eventually the audio hardware and recording will start NOW. Recording (and playback) will commence some $NOW! + latency$ units of time later and in off the shelf sound cards, this used to be an awful time later. So awful that you could hear the silence between calls.

So, the solution there was to move over to something like ASIO which eventually packaged this whole workflow into a complete solution all the way from the software calls to the hardware.

As an example, an 8-track external sound card can do 48kHz Stereo playback / rec over buffers of 256 samples (with absolutely no clicks). This means, that sound is processed in blocks that last approximately 5ms. The human ear begins resolving sounds as distinct when they are apart for more than 40ms, so this 5ms is really safe. It means that we can plug an electric instrument in the sound card and process it, in real time like it went through an effects box and the performer will not be noticing any difference between what they play and what they hear.

If you tried to make $H,D,L$ the size of 256 and try to do the same with an off the shelf (relatively popular) sound card, first of all you would g/et/ /an/awf/ul/am//ount/of/cli/k/s// (one every 5ms) and secondly, the driver and operating system would be so much overwhelmed by the amount of function calls that soon you would be greeted with Seg Faults, blue screens, system freeze, reprepreprepreprepreprepreprepreprereprepreprepreprepreprepeated waves out of synsynsynsynsynsynsynsynsynsynreprepresync and other amusing side effects.

So, bottom line, keep working on the DSP side of your synth but make sure that your sound hardware is up to the task as well if you really want to get rid of the clicks.

Hope this helps.