[{"element_type":"text","content":"\n        \u003cp\u003eA study to produce a \u003cem\u003e\u003cstrong\u003eCircuitPython\u003c/strong\u003e\u003c/em\u003e helper library to construct a \u003cem\u003esynthio.waveform\u003c/em\u003e wave table object from a list of fundamental and overtone frequencies, amplitudes, and wave shapes (sine, square, triangle, saw, noise).\u003c/p\u003e\n\u003cp\u003eThe WaveBuilder class currently resides in its \u003ca href=\"https://github.com/CedarGroveStudios/CircuitPython_WaveBuilder\" target=\"_blank\"\u003eGitHub repository\u003c/a\u003e\u0026nbsp; and can be found in the \u003ca href=\"https://circuitpython.org/libraries\" target=\"_blank\"\u003eCircuitPython Community Bundle\u003c/a\u003e. The update utility\u0026nbsp;\u003cem\u003ecircup\u003c/em\u003e can also be used to install \u003cem\u003ecedargrove_wavebuilder\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/CedarGroveStudios/CircuitPython_WaveBuilder/blob/main/media/pseudo_rtd_cedargrove_wavebuilder.pdf\" target=\"_blank\"\u003eWaveBuilder API\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n      ","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/553/original/Screenshot_2023-12-08_at_10.18.13%E2%80%AFPM.png?1702756318","metadata":{"caption":"Two-Lambda Wind Chime Waveform Generated Algorithmically from Fundamental and Overtone Oscillator Parameters"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThe objective was simple. Create a realistic approximation of a \u003ca href=\"https://github.com/CedarGroveStudios/CircuitPython_Chime\" target=\"_blank\"\u003ewind chime sound\u003c/a\u003e. Use \u003cem\u003esynthio\u003c/em\u003e to stack up a collection of sine wave \u003cem\u003eNote\u003c/em\u003e objects starting with the fundamental frequency and mix in a few unique overtones, each with its appropriate volume level. Use the same ADSR envelope for all notes and voilà, we've got a pretty good chime sound. That worked nicely. Let's try to synthesize another voice.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eThe Analog (-ish) Approach\u003c/h2\u003e\n\u003cp\u003eAn iconic drum machine, the \u003ca href=\"https://archive.org/details/synthmanual-roland-tr-808-service-notes\" target=\"_blank\"\u003eRoland TR-808\u003c/a\u003e, builds its percussion voices using analog circuits; no digital samples or models in the mix. Our first goal will be to recreate one of the more unique sounds, a high-hat cymbal. The first step in the process uses\u0026nbsp;\u003cem\u003esynthio\u003c/em\u003e to define six square wave notes of dissonant frequencies, simulating the six oscillators used to create the foundation of the metallic and slightly periodic percussion sound. After summing the six oscillators, we'll run the combined signal through some resonant filtering, add some distortion, trigger an envelope, and wrap it up with a resonant high-pass filter. Hmm, this will push the limits of my current skills with \u003cem\u003esynthio\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eUsing an approach similar to the one used to create the \u003ca href=\"https://github.com/CedarGroveStudios/CircuitPython_Chime\" target=\"_blank\"\u003ewind chime sound\u003c/a\u003e, six \u003cem\u003esynthio.Note\u003c/em\u003e objects (oscillators) were used, a high-hat envelope was linked to each, and a sine wave ring modulator was used to simulate filter resonance. The resultant sound was surprisingly close to that of the analog circuitry. However, the FeatherS2 (an ESP32-S2) bogged down and couldn't play the composite note quickly enough to be used for real-time percussion. We'll need to find a better way.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eA Digital Waveform Sample Approach\u003c/h2\u003e\n\u003cp\u003eSince waveforms within\u0026nbsp;\u003cem\u003esynthio\u003c/em\u003e are created with digital wave tables anyway, perhaps we could create a sample of the complex multi-oscillator waveform instead. The notion would be to create a series of wave table arrays that represent the oscillators then simply sum the sample values together to create the combined wave table array for \u003cem\u003esynthioNote\u003c/em\u003e to use. The length of the wave table (number of samples) would match what would be needed for a single wavelength of the fundamental (lowest) oscillator frequency.\u003c/p\u003e\n\u003cp\u003eTwo problems emerged with this approach. First, the resolution of the wave table (number of samples divided by the sample rate) may not be sufficient to accurately represent frequencies that are much higher than the fundamental. Second, when looping the wave table end-to-start with itself, care is needed to match the ending sample of the wave table with the starting sample to avoid an unwanted \"click\" (distortion) that adds unrelated high frequency harmonics. Both of these issues are detectable and solvable. Since it's possible to predict, a recommendation can be made when resolution is an issue. The loop distortion can be fixed by optionally adjusting a few sample values at the end of the wave table to gradually match the wave table's first value. We'll add these fixes into a helper class.\u003c/p\u003e\n\u003cp\u003eSuccess! The composite output wave table approach indeed created the noise similar to the analog circuitry. All that's left to do is to run the noise through a bandpass filter, adjust the resonance, add some distortion, fine-tune the envelope, and run the results through a high-pass filter. But let's make a helper class that will create the oscillators wave table before tackling the additional stuff.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"alert","content":"A wavelength is measured in seconds and can be calculated by inverting the frequency. For example, a 261.63 Hertz sine wave (Middle C on the piano) has a wavelength of approximately 4.3 milliseconds. Wavelength is sometimes referred to as period or lambda (λ).","metadata":{"class":"element row-fluid alert-element build-alert alert-info","markdown":"","alert_type":"info"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eGoing to the Head of the Class\u003c/h2\u003e\n\u003cp\u003eTo make this into a helpful helper, we'll build a class that can combine an arbitrary number of oscillators together into a single complex waveform wave table. \u0026nbsp;That will make it easy to build a digital sample model of the TR-808 high-hat square wave noise oscillators. The class will also be useful for constructing a wave table representation of the composite wind chime voice out of sine waveforms. And since the wave table will only be associated with a single instance of the \u003cem\u003esynthio.Note\u003c/em\u003e object, slow playback from having multiple \u003cem\u003eNote\u003c/em\u003e objects will no longer be an issue.\u003c/p\u003e\n\u003cp\u003eInstead of just generating a one-use wave table, the class will allow real-time adjustments to the oscillator parameters and the wave table characteristics. An updated wave table will be created as-needed when any parameter is changed.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThe class is initialized with parameters that will be used to build the \u003cem\u003esynthio\u003c/em\u003e-compatible waveform object, \u003cem\u003eWaveBuilder.wave_table\u003c/em\u003e. The parameters are:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cem\u003eoscillators\u003c/em\u003e -- a list of oscillator characteristics in tuples of wave shape, frequency, and amplitude\u003c/li\u003e\n\u003cli\u003e\n\u003cem\u003etable_length\u003c/em\u003e -- the number of samples in the resultant wave table\u003c/li\u003e\n\u003cli\u003e\n\u003cem\u003esample_max\u003c/em\u003e -- the maximum positive value of a sample; the maximum negative value is internally derived from this value\u003c/li\u003e\n\u003cli\u003e\n\u003cem\u003elambda_factor\u003c/em\u003e -- the number of fundamental wavelengths per wave table\u003c/li\u003e\n\u003cli\u003e\n\u003cem\u003eloop_smoothing\u003c/em\u003e -- smooth the transition between the end of a wave table and the start to reduce loop distortion\u003c/li\u003e\n\u003cli\u003e\n\u003cem\u003edebug\u003c/em\u003e -- enable debug print messages\u003c/li\u003e\n\u003c/ul\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThe key to building a composite waveform is the \u003cem\u003eoscillators\u003c/em\u003e parameter list. The list for the TR-808 high-hat oscillators looks like this:\u003c/p\u003e\n\u003ch4\u003eTRS-808 High-Hat\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003etr_oscillators = [\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Square, 245, 1/7),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Square, 306, 1/7),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Square, 365, 1/7),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Square, 415, 1/7),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Square, 437, 1/7),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Square, 619, 1/7),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 3400, 1/14)\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e]\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eThe first six oscillators provide square waves of equal amplitude at six dissonant frequencies. The seventh oscillator provides a sine wave metallic resonance at 3.4kHz.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/560/original/high-hat_7-osc.png?1703379450","metadata":{"caption":"The Composite Waveform for the High-Hat 7-Oscillator Noise Source (two lambda)"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch4\u003eChimes\u003c/h4\u003e\n\u003cp\u003eFor the chime sound, the \u003cem\u003eoscillators\u003c/em\u003e list is composed of:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003echime_oscillators = [\u003cbr\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 1.00, 0.60),\u003cbr\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 2.76, 0.20),\u003cbr\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 5.40, 0.10),\u003cbr\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 8.93, 0.07),\u003cbr\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 11.34, 0.02),\u003cbr\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 18.64, 0.01),\u003cbr\u003e]\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eFor the chime \u003cem\u003eoscillators\u003c/em\u003e list, the frequencies are replaced with overtone ratios. Also amplitudes decrease as the overtone ratio increases.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/561/original/chime_6-osc.png?1703380227","metadata":{"caption":"The Composite Waveform for the Chime 6-Oscillator Sound (two lambda)"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch4\u003eA Single Tone\u003c/h4\u003e\n\u003cp\u003eIf all that is needed is a single oscillator, the oscillators list would consist of a single entry:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esaw_oscillators = [(WaveShape.Saw, 440, 0.8)]\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eThe amplitude is set for 80% of full (0.8). The frequency value is arbitrary in this case since only one oscillator is defined.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/562/original/saw_1-osc.png?1703380939","metadata":{"caption":"A Single-Oscillator Saw Wave Table (two lambda)"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eMore Sound Formulas\u003c/h2\u003e\n\u003cp\u003eI'll add a few waveform lists here as the experiment progresses. Eventually, the envelope parameters will be provided as well.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch4\u003eHarmonica\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003eharmonica_oscillators = [\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 1.00, 0.10),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 2.00, 0.48),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 3.00, 0.28),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 4.00, 0.02),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 5.00, 0.12),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 6.00, 0.0),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e\u0026nbsp; \u0026nbsp; (WaveShape.Sine, 7.00, 0.0),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e]\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eThe fundamental frequency amplitude for the harmonica is significantly lower than the second and third. The sixth and seventh are shown as zero amplitude in the scientific model but are expected to change as the empirical model is developed.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/564/original/harmonica_sim_2-lambda.png?1704305131","metadata":{"caption":"An Algorithm-Generated Composite Waveform for the Harmonica Sound (two lambda)"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch4\u003ePlucked Guitar String - Center Position\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003eplucked_string_center_oscillators = [\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 1.00, 0.382),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 2.00, 0.076),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 3.00, 0.336),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 4.00, 0.084),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 5.00, 0.107),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 6.00, 0.011),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 7.00, 0.004),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e]\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eFor this test, the plucked guitar string wave shape was a sine wave. It's likely to sound more authentic with the fundamental frequency as a triangle wave that slowly morphs into a sine wave after the note is plucked.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/565/original/guitar_cntr_sim_2-lambda.png?1704305226","metadata":{"caption":"An Algorithm-Generated Composite Waveform for the Plucked String Center Sound (two lambda)"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch4\u003ePlucked Guitar String -- Bridge Position\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003eplucked_string_bridge_oscillators = [\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 1.00, 0.180),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 2.00, 0.299),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 3.00, 0.234),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 4.00, 0.180),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 5.00, 0.090),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 6.00, 0.015),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e  \u0026nbsp; (WaveShape.Sine, 7.00, 0.003),\u003c/code\u003e\u003cbr\u003e\u003ccode\u003e]\u003c/code\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/566/original/guitar_bridge_sim_2-lambda.png?1704305286","metadata":{"caption":"An Algorithm-Generated Composite Waveform for the Plucked String Bridge Sound (two lambda)"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eSimple Example Code\u003c/h2\u003e\n\u003cp\u003eThe following example plays a single tone every second but switches between a sine wave and a saw wave during the playback \u003ccode\u003ewhile True:\u003c/code\u003e loop. The code was written for a FeatherS2 (ESP32-S2) with a MAX98357A I2S amplifier.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# SPDX-FileCopyrightText: Copyright (c) 2023, 2024 JG for Cedar Grove Maker Studios\n# SPDX-License-Identifier: MIT\n\nimport time\nimport board\nimport synthio\nimport audiobusio\nimport audiomixer\nfrom cedargrove_wavebuilder import WaveBuilder, WaveShape\n\n# Define synth parameters\nSAMPLE_RATE = 22050  # The sample rate in SPS\nWAVE_TABLE_LENGTH = 512  # The wave table length in samples\nSAMPLE_MAXIMUM = 32700  # The maximum value of a sample\n\n# Define the oscillator wave shape, overtone ratio, and amplitude\ntone = [(WaveShape.Sine, 1.0, 0.6)]\n\n# Create the wave table and show the debug messages\nwave = WaveBuilder(\n    oscillators=tone,\n    table_length=WAVE_TABLE_LENGTH,\n    sample_max=SAMPLE_MAXIMUM,\n    lambda_factor=1.0,\n    loop_smoothing=True,\n    debug=True,\n)\n\n# Define the tone's ADSR envelope parameters\ntone_envelope = synthio.Envelope(\n    attack_time=0.02 + 0.01,\n    attack_level=1.0 * 1.0,\n    decay_time=0.0,\n    release_time=2.0,\n    sustain_level=1.0,\n)\n\n# Configure synthesizer for I2S output on a Feather S2\naudio_output = audiobusio.I2SOut(\n    bit_clock=board.D12, word_select=board.D9, data=board.D6, left_justified=False\n)\nmixer = audiomixer.Mixer(\n    sample_rate=SAMPLE_RATE, buffer_size=4096, voice_count=1, channel_count=1\n)\naudio_output.play(mixer)\nmixer.voice[0].level = 0.50\n\nsynth = synthio.Synthesizer(sample_rate=SAMPLE_RATE)\nmixer.play(synth)\n\nnote_1 = synthio.Note(880, envelope=tone_envelope, waveform=wave.wave_table)\n\nwhile True:\n    # Set the note waveform to sine and play the note\n    wave.oscillators = [(WaveShape.Sine, 1.0, 0.6)]\n    note_1.waveform = wave.wave_table\n    synth.press(note_1)\n    synth.release(note_1)\n    time.sleep(1)\n\n    # Set the note waveform to square and play the note\n    wave.oscillators = [(WaveShape.Saw, 1.0, 0.6)]\n    note_1.waveform = wave.wave_table\n    synth.press(note_1)\n    synth.release(note_1)\n    time.sleep(1)","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThe next example shows how a wind chime sound is simulated using \u003cem\u003eWaveBuilder\u003c/em\u003e and \u003cem\u003esynthio\u003c/em\u003e.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# SPDX-FileCopyrightText: Copyright (c) 2023, 2024 JG for Cedar Grove Maker Studios\n# SPDX-License-Identifier: MIT\n\n\"\"\"\n===============================================================================\nAn example of using the WaveBuilder class to simulate wind chimes. The\noscillator values are from the CedarGrove Chime class.\n\"\"\"\n\nimport time\nimport board\nimport synthio\nimport audiobusio\nimport audiomixer\nfrom cedargrove_wavebuilder import WaveBuilder, WaveShape\n\nprint(\"=== WaveBuilder Simpletest===\")\n\n# Define synth parameters\nSAMPLE_RATE = 22050  # The sample rate in SPS\nWAVE_TABLE_LENGTH = 512  # The wave table length in samples\nPLOT = False  # Plot the wave table array via the REPL\n\n# Define the wave type, overtone ratio, and amplitude (0.0 to 1.0)\nchimes = [\n    (WaveShape.Sine, 1.0, 0.6),\n    (WaveShape.Sine, 2.76, 0.2),\n    (WaveShape.Sine, 5.40, 0.1),\n    (WaveShape.Sine, 8.93, 0.1),\n]\n\nwave = WaveBuilder(\n    oscillators=chimes,\n    table_length=WAVE_TABLE_LENGTH,\n    sample_max=32700,\n    lambda_factor=1,\n    loop_smoothing=True,\n)\n\nif PLOT:\n    # Plot the wave_table array contents\n    for point in wave.wave_table:\n        print(f\"({point / 1000}, )\")\n\n# Define Chime ADSR envelope parameters\nchime_envelope = synthio.Envelope(\n    attack_time=0.02 + 0.01,\n    attack_level=1.0 * 1.0,\n    decay_time=0.0,\n    release_time=2.0,\n    sustain_level=1.0,\n)\n\n# Configure synthesizer for I2S output on a Feather S2\naudio_output = audiobusio.I2SOut(\n    bit_clock=board.D12, word_select=board.D9, data=board.D6, left_justified=False\n)\nmixer = audiomixer.Mixer(\n    sample_rate=SAMPLE_RATE, buffer_size=4096, voice_count=1, channel_count=1\n)\naudio_output.play(mixer)\nmixer.voice[0].level = 0.50\n\nsynth = synthio.Synthesizer(sample_rate=SAMPLE_RATE, waveform=wave.wave_table)\nmixer.play(synth)\n\nnote_1 = synthio.Note(880, envelope=chime_envelope)\n\nprint(\"===\")\n\nwhile True:\n    synth.press(note_1)\n    synth.release(note_1)\n    time.sleep(0.5)","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch4\u003eAdding It Up\u003c/h4\u003e\n\u003cp\u003eThe technique of adding the outputs of several oscillators together to create complex sounds is sometimes referred to as \u003ca href=\"https://en.wikipedia.org/wiki/Additive_synthesis\" target=\"_blank\"\u003eadditive synthesis.\u003c/a\u003e Traditionally, only sine wave oscillators are used since almost any desired waveform can be created from \u003ca href=\"https://en.wikipedia.org/wiki/Fourier_series\" target=\"_blank\"\u003ea collection of sine waves\u003c/a\u003e. The WaveBuilder class extends that technique by allowing any or all oscillator wave shapes to be sine, saw, square, or triangle so that fewer oscillators are needed for highly complex sounds such as percussion or distorted electric guitar.\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"alert","content":"Plans for a future version of WaveBuilder include accepting a previously compiled wave table of a complex waveform as a root oscillator substitute. Imagine using additive synthesis to alter a trumpet wave table sample to create a never-heard-before alien animal call. Yeah.","metadata":{"class":"element row-fluid alert-element build-alert alert-info","markdown":"","alert_type":"info"}},{"element_type":"text","content":"\n  \n  \n  \n        \u003ch4\u003eDigital Waveform Sampling Observations\u003c/h4\u003e\n\u003cp\u003eI've always been hesitant to use digital sampling to recreate musical voices. It's not a stretch to imagine that an important bit of the sampled voice could be lost when only a fraction of the sound waveform is captured for looped playback, removing most of the musician's expression or instrument variations. The character of some instruments, like the guitar, is contained in the slight frequency change that happens when a string is plucked (it stretches a little) followed by a change in the waveform shape from triangle to sine as the sound energy is gradually altered by the resonance of the guitar body during decay. The change at the start of the note can easily be captured by digital sampling, but it'll be repeated continuously as the wave table loops. And the gradual change of the waveform shape and amplitude would require a much larger wave table and a method for compressing or expanding the table to accomodate notes of varying durations.\u003c/p\u003e\n\u003cp\u003eMany synthesizers, including \u003cem\u003esynthio,\u003c/em\u003e only utilize a single wavelength of a waveform to recreate the sound because it's prohibitive to capture the entire start-to-finish of a note as it transitions from attack to release. A single wavelength table for Middle C on a piano needs only 190 bytes of storage for the 4.3msec 16-bit recording at a resolution of 22.050k samples per second. However, capturing a wave table of a played note in its entirety, which in some cases may take up to a couple of seconds to completely decay, could easily exceed 60kbytes.\u003c/p\u003e\n\u003cp\u003eIn order to take full advantage of the digital sampling technique for reproducing the voice of a musical instrument, a process to compensate for the missing elements is needed if realism is a requirement (or is an obsessive preference). To pursue that elusive high-quality rendering, there's a lot more on my list to discover about how to link together \u003cem\u003esynthio\u003c/em\u003e functions and features to create viable renditions of musical instrument and analog circuitry sounds. I'm still learning.\u003c/p\u003e\n      \n\n\n","metadata":{}}]