{"id":121,"date":"2012-05-25T21:44:13","date_gmt":"2012-05-26T05:44:13","guid":{"rendered":"http:\/\/www.earlevel.com\/main\/?p=121"},"modified":"2020-10-01T21:25:39","modified_gmt":"2020-10-02T04:25:39","slug":"a-wavetable-oscillator-the-code","status":"publish","type":"post","link":"https:\/\/www.earlevel.com\/main\/2012\/05\/25\/a-wavetable-oscillator-the-code\/","title":{"rendered":"A wavetable oscillator\u2014the code"},"content":{"rendered":"<p>The wavetable oscillator code follows the basic structure of most audio processing blocks, with three types of functions:<\/p>\n<ul>\n<li> <strong>at the start:<\/strong> initialization\u2014create the oscillator and its wavetables<\/li>\n<li> <strong>as needed:<\/strong> control\u2014change the oscillator frequency, and pulse width for PWM<\/li>\n<li> <strong>every sample:<\/strong> processing\u2014get next output sample, update phase<\/li>\n<\/ul>\n<h3>At the start<\/h3>\n<p><i>Initialization<\/i>\u2014Most of our work for the oscillator is here. Fortunately, this is the part that&#8217;s not time-critical. We start by initializing the oscillator&#8217;s phase to zero, and creating all of the wavetables we need\u2014starting with the lowest frequency wavetable, followed by its progressively bandwidth-reduced copies for successively higher frequency ranges. In this implementation, each wavetable is accompanied by its table length, and the highest pitch that the table is built to handle without noticeable aliasing.<\/p>\n<h3>As needed<\/h3>\n<p><i>Control<\/i>\u2014You may change the oscillator frequency only for new notes, or it might be as often as every sample if you want modulation with updates at the sample rate (also true of pulse-width if you want to pulse-width modulation). And fortunately this task is the simplest and fastest of all for the oscillator, so it&#8217;s no problem supporting it for every sample.<\/p>\n<h3>Every sample<\/h3>\n<p><i>Processing<\/i>\u2014In general you need to update each oscillator&#8217;s phase for every sample, though you can choose to suspend updating of any oscillator that&#8217;s part of a voice that&#8217;s silent. And of course the oscillator won&#8217;t do you much good if you don&#8217;t get its output for every sample and use it.<\/p>\n<p>Updating the phase is trivial\u2014just add the phase increment, and wrap it back into the range of 0-1 if necessary.<\/p>\n<p>Retrieving the current output is a bit more work. First, we need to determine which table to use, by comparing the current frequency control (phase increment) with the highest-frequency table that can accommodate it. Then we use the current phase value, from the phase accumulator, to determine the wavetable positions and weighting for our linear interpolation to yield the oscillator output.<\/p>\n<p>For instance, if the phase accumulator is 0.51, and the length of the appropriate wavetable is 2048, then the offset into the table is 0.51 x 2048, or 1044.48. So, we use table samples at indices 1044 and 1045, and use the fractional part\u20140.48\u2014in the interpolation.<\/p>\n<h3>Key oscillator components<\/h3>\n<h4>Variables<\/h4>\n<p>The oscillator is implemented in C++. We&#8217;ll start with the oscillator&#8217;s private member variables, but keep in mind that you won&#8217;t access these directly\u2014you&#8217;ll interact with the oscillator through the member functions.<\/p>\n<p>First, each oscillator needs a phase accumulator and a phase increment (frequency control):<\/p>\n<pre>double phasor;      \/\/ phase accumulator\ndouble phaseInc;    \/\/ phase increment\n<\/pre>\n<p>And since we&#8217;re supporting variable pulse width via the difference between two saws, we need a phase offset control:<\/p>\n<pre>double phaseOfs;    \/\/ phase offset for PWM\n<\/pre>\n<p>And each oscillator needs its list of wavetables and how many there are. We pick a maximum number of wavetables to support\u2014this information doesn&#8217;t take up much memory, and it&#8217;s more efficient than allocating the list dynamically; we can cover 20 Hz to 20 kHz with ten tables\u2014I picked 32 as the maximum in case you want to try half- or third-octave tables, but you can set numWaveTableSlots to whatever value you want:<\/p>\n<pre>\/\/ list of wavetables\nint numWaveTables;\nwaveTable waveTables[numWaveTableSlots];\n<\/pre>\n<p>Here&#8217;s the wavetable structure\u2014the length of the wavetable, the top frequency it&#8217;s designed for, and the wavetable itself (which is allocated dynamically):<\/p>\n<pre>typedef struct {\n    double topFreq;\n    int waveTableLen;\n    float *waveTable;\n} waveTable;\n<\/pre>\n<p>Why does each wavetable have its own length variable? Because this oscillator doesn&#8217;t require a constant size. For example, for a memory-sensitive environment, you could use longer tables for low frequencies in order to accommodate more harmonics, and shorter ones for higher frequency tables. For constant oversampling, you could cut the table size in half (along with the number of harmonics) for each higher octave.<\/p>\n<h4>Functions<\/h4>\n<p>Now for the oscillator functions. Besides the constructor\u2014which simply initializes the phase accumulator, phase increment, phase offset, and the empty wavetable list\u2014and the destructor, we have this function to initialize the oscillator:<\/p>\n<pre>void addWaveTable(int len, float *waveTableIn, double topFreq);\n<\/pre>\n<p>The addWaveTable function specifies the length of a wavetable to add to the oscillator, its contents, and the top frequency (phase increment) it&#8217;s designed for. Call it once for each wavetable you need to add\u2014from lowest frequency tables to highest.<\/p>\n<p>We have these functions for setting frequency (normalized to the sample rate\u2014it&#8217;s the phase increment) and the phase offset (for setting pulse width, and perhaps other uses in the future):<\/p>\n<pre>void setFrequency(double inc);\nvoid setPhaseOffset(double offset);\n<\/pre>\n<p>Here is the function to update the oscillator phase by adding adding the phase increment to the phase accumulator\u2014call this once per sample:<\/p>\n<pre>void updatePhase(void);\n<\/pre>\n<p>Finally, the routines that retrieve the current oscillator output. Use getOutput, normally. But for variable pulse width, initialize the oscillator for a sawtooth oscillator, and get the oscillator output via getOutputMinusOffset, where the pulse width is set with setPhaseOffset, with a value between 0.0 and 1.0 corresponding to the pulse width:<\/p>\n<pre>float getOutput(void);\nfloat getOutputMinusOffset(void);\n<\/pre>\n<h3>The code<\/h3>\n<p>Here&#8217;s the wavetable oscillator source code, in a  .zip file\u2014WaveTableOsc.cpp and WaveTableOsc.h, along with a file, main.c, that gives a few oscillator examples and generates .wav file output:<\/p>\n<p><a href=\"\/main\/wp-content\/uploads\/2012\/05\/WaveTableOsc.zip\">WaveTableOsc source code<\/a><\/p>\n<p><i>More comments and output examples coming soon.<\/i><\/p>\n\n\n<p><em><strong>Update:<\/strong> Be sure to check out the updated WaveTableOsc, in <a href=\"\/main\/2019\/04\/28\/wavetableosc-optimized\/\">WaveTableOsc optimized<\/a>.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The wavetable oscillator code follows the basic structure of most audio processing blocks, with three types of functions: at the start: initialization\u2014create the oscillator and its wavetables as needed: control\u2014change the oscillator frequency, and pulse width for PWM every sample: &hellip; <a href=\"https:\/\/www.earlevel.com\/main\/2012\/05\/25\/a-wavetable-oscillator-the-code\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,20,26,24],"tags":[],"_links":{"self":[{"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/posts\/121"}],"collection":[{"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/comments?post=121"}],"version-history":[{"count":2,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/posts\/121\/revisions"}],"predecessor-version":[{"id":987,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/posts\/121\/revisions\/987"}],"wp:attachment":[{"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/media?parent=121"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/categories?post=121"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/tags?post=121"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}