{"id":165,"date":"2013-03-03T15:17:33","date_gmt":"2013-03-03T23:17:33","guid":{"rendered":"http:\/\/www.earlevel.com\/main\/?p=165"},"modified":"2020-10-01T21:29:38","modified_gmt":"2020-10-02T04:29:38","slug":"replicating-wavetables","status":"publish","type":"post","link":"https:\/\/www.earlevel.com\/main\/2013\/03\/03\/replicating-wavetables\/","title":{"rendered":"Replicating wavetables"},"content":{"rendered":"<p>My <a href=\"\/main\/2012\/05\/25\/a-wavetable-oscillator\u2014the-code\/\">WaveTableOsc<\/a> object uses a series of wavetables that are copies of single-cycle waveforms pre-rendered at a progressively smaller bandwidth. By &#8220;replicating wavetables&#8221; I mean taking a full-bandwidth single cycle waveform, and using it to make a complete set of progressively bandwidth-reduced wavetables. In this article, I&#8217;ll demonstrate principles and source code that will let you take any source waveform suitable for the lowest octave, and build the rest of the tables automatically.<\/p>\n<p>The source waveform might be any time-domain single-cycle wave\u2014something you drew, created with splines, recorded with a microphone, calculated\u2014or it can be a frequency-domain description\u2014harmonics and phases. Fundamentally, we&#8217;ll use a frequency-domain description\u2014the array pair we use in an FFT\u2014that holds amplitude and phase information for all possible harmonics. If we start with a time-domain wave instead, we simple convert it to frequency domain as the first step. The source code builds the bandwidth-reduced copies, converts them to the time domain, and adds them to the oscillator until it contains all necessary tables to cover the audio range without noticeable aliasing.<\/p>\n<p>We&#8217;ll build a new table for each octave higher, by removing the upper half of the previous table&#8217;s harmonics. If it&#8217;s not clear why, consider that all harmonics get doubled in frequency when shifting pitch. A 100 Hz sawtooth wave has harmonics spaced 100 Hz apart; shifting it up an octave to 200 Hz shifts the spacing to 200 Hz apart, so half of the harmonics that were below Nyquist move above.<\/p>\n<h3>The code<\/h3>\n<p>In principle, the code is simple:<\/p>\n<pre>repeat while the wavetable has harmonic content:\n\tadd wavetable to oscillator\n\tremove upper half of the harmonics\n<\/pre>\n<p>This is one of those places where I&#8217;d like to be more sophisticated, and use a faster &#8220;real&#8221; FFT and avoid the silliness of filling in a mirror-image array, but it&#8217;s more code and clutter\u2014I&#8217;m sticking with the generic FFT from the WaveTableOsc source code. I&#8217;ve also reused the makeWaveTable function, so the new code here is minimal. The end product is WaveUtils.h and WaveUtils.cpp, plain C utility functions. I&#8217;d probably wrap the functions into a manager class that dealt with WaveTableOsc and wavetable classes, if I were focusing on a specific implementation and not an instructive article.<\/p>\n<p>The two reused functions are used internally, and we need just one new function that we call directly\u2014fillTables:<\/p>\n<pre>void fillTables(WaveTableOsc *osc, double *freqWaveRe, double *freqWaveIm, int numSamples) {\n    int idx;\n    \n    \/\/ zero DC offset and Nyquist\n    freqWaveRe[0] = freqWaveIm[0] = 0.0;\n    freqWaveRe[numSamples &gt;&gt; 1] = freqWaveIm[numSamples &gt;&gt; 1] = 0.0;\n    \n    \/\/ determine maxHarmonic, the highest non-zero harmonic in the wave\n    int maxHarmonic = numSamples &gt;&gt; 1;\n    const double minVal = 0.000001; \/\/ -120 dB\n    while ((fabs(freqWaveRe[maxHarmonic]) + fabs(freqWaveIm[maxHarmonic]) &lt; minVal)\n        &amp;&amp; maxHarmonic) --maxHarmonic;\n\n    \/\/ calculate topFreq for the initial wavetable\n    \/\/ maximum non-aliasing playback rate is 1 \/ (2 * maxHarmonic), but we allow\n    \/\/ aliasing up to the point where the aliased harmonic would meet the next\n    \/\/ octave table, which is an additional 1\/3\n    double topFreq = 2.0 \/ 3.0 \/ maxHarmonic;\n    \n    \/\/ for subsquent tables, double topFreq and remove upper half of harmonics\n    double *ar = new double [numSamples];\n    double *ai = new double [numSamples];\n    double scale = 0.0;\n    while (maxHarmonic) {\n        \/\/ fill the table in with the needed harmonics\n        for (idx = 0; idx &lt; numSamples; idx++)\n            ar[idx] = ai[idx] = 0.0;\n        for (idx = 1; idx &lt;= maxHarmonic; idx++) {\n            ar[idx] = freqWaveRe[idx];\n            ai[idx] = freqWaveIm[idx];\n            ar[numSamples - idx] = freqWaveRe[numSamples - idx];\n            ai[numSamples - idx] = freqWaveIm[numSamples - idx];\n        }\n        \n        \/\/ make the wavetable\n        scale = makeWaveTable(osc, numSamples, ar, ai, scale, topFreq);\n\n        \/\/ prepare for next table\n        topFreq *= 2;\n        maxHarmonic &gt;&gt;= 1;\n    }\n}\n<\/pre>\n<h3>Defining an oscillator in the frequency domain<\/h3>\n<p>Here&#8217;s an example that creates and returns a sawtooth oscillator by specifying the frequency spectrum (note that we must include the mirror of the spectrum, since we&#8217;re using a complex FFT):<\/p>\n<pre>WaveTableOsc *sawOsc(void) {\n    int tableLen = 2048;    \/\/ to give full bandwidth from 20 Hz\n    int idx;\n    double *freqWaveRe = new double [tableLen];\n    double *freqWaveIm = new double [tableLen];\n    \n    \/\/ make a sawtooth\n    for (idx = 0; idx &lt; tableLen; idx++) {\n        freqWaveIm[idx] = 0.0;\n    }\n    freqWaveRe[0] = freqWaveRe[tableLen &gt;&gt; 1] = 0.0;\n    for (idx = 1; idx &lt; (tableLen &gt;&gt; 1); idx++) {\n        freqWaveRe[idx] = 1.0 \/ idx;                    \/\/ sawtooth spectrum\n        freqWaveRe[tableLen - idx] = -freqWaveRe[idx];  \/\/ mirror\n    }\n    \n    \/\/ build a wavetable oscillator\n    WaveTableOsc *osc = new WaveTableOsc();\n    fillTables(osc, freqWaveRe, freqWaveIm, tableLen);\n\n    return osc;\n}\n<\/pre>\n<h3>Defining an oscillator in the time domain<\/h3>\n<p>I you have a cycle of a waveform in the time domain, you can create and oscillator this way\u2014just do an FFT to convert to the frequency domain, and pass the result to fillTable to complete the oscillator:<\/p>\n<pre>WaveTableOsc *waveOsc(double *waveSamples, int tableLen) {\n    int idx;\n    double *freqWaveRe = new double [tableLen];\n    double *freqWaveIm = new double [tableLen];\n    \n    \/\/ convert to frequency domain\n    for (idx = 0; idx &lt; tableLen; idx++) {\n        freqWaveIm[idx] = waveSamples[idx];\n        freqWaveRe[idx] = 0.0;\n    }\n    fft(tableLen, freqWaveRe, freqWaveIm);\n\n    \/\/ build a wavetable oscillator\n    WaveTableOsc *osc = new WaveTableOsc();\n    fillTables(osc, freqWaveRe, freqWaveIm, tableLen);\n    \n    return osc;\n}\n<\/pre>\n<p>Here\u2019s a zip file containing the source code, including the two examples:<\/p>\n<p><a href=\"\/main\/wp-content\/uploads\/2013\/03\/WaveUtils.zip\">WaveUtils source code<\/a><\/p>\n<h3>Caveat<\/h3>\n<p>As with our wavetable oscillator, this source code requires wavetable arrays\u2014whether time domain or frequency domain\u2014of a length that is a power of 2. As examined in the WaveTableOsc articles, 2048 is an excellent choice. The source code does no checking to ensure powers of 2\u2014it\u2019s up to you.<\/p>\n\n\n<p><em><strong>Update:<\/strong>\u00a0Be sure to check out the improved WaveUtils, in\u00a0<a href=\"\/main\/2019\/04\/30\/waveutils-updated\/\">WaveUtils updated<\/a>.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>My WaveTableOsc object uses a series of wavetables that are copies of single-cycle waveforms pre-rendered at a progressively smaller bandwidth. By &#8220;replicating wavetables&#8221; I mean taking a full-bandwidth single cycle waveform, and using it to make a complete set of &hellip; <a href=\"https:\/\/www.earlevel.com\/main\/2013\/03\/03\/replicating-wavetables\/\">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":[20,26,24],"tags":[],"_links":{"self":[{"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/posts\/165"}],"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=165"}],"version-history":[{"count":3,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/posts\/165\/revisions"}],"predecessor-version":[{"id":988,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/posts\/165\/revisions\/988"}],"wp:attachment":[{"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/media?parent=165"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/categories?post=165"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.earlevel.com\/main\/wp-json\/wp\/v2\/tags?post=165"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}