You can choose from a number of different boards when using synthio. The simplest setup is to use the PropMaker Feather RP2040 with a speaker's negative and positive wires screwed into the speaker terminal blocks as shown here.
Alternately, you can use a QT Py ESP32-S2 with either the I2S Amplifier BFF or the Audio BFF (which has the same amplifier but also adds an SD card reader).
Solder header pins and sockets onto the QT Py and BFF to connect them belly-to-belly as shown, then plug in your speaker (no need for an SD card).
Envelope
The Envelope controls how a note's amplitude changes over time. This is divided into 4 sections and controlled by 5 numbers:
- In the first phase, the attack, the note's amplitude rises to the
attack_amplitude
overattack_time
seconds. - In the second phase, the decay, the note's amplitude falls by a factor of
sustain_amplitude
overdecay_time
seconds. - This amplitude is maintained until the note is released, unless the
sustain_amplitude
is zero, in which case the note is released automatically when the amplitude reaches 0. - When the note is released, it falls to zero amplitude at a rate given by
release_time
.
Different values evoke different kinds of instruments. For instance, a plucked instrument has a very small attack_time
and fades away in a defined length of time, meaning it has a sustain_amplitude
of zero.
envelope = synthio.Envelope(attack_time=0.1, decay_time = 0.1, release_time=0.1, attack_level=1, sustain_level=0.05)
Waveform
A "waveform" is an abstract kind of object. It can be an array.array('h', ...)
or a np.array(..., dtype=np.int16)
, a memoryview
or any other in memory buffer that contains signed 16-bit values.
This waveform is usually a small file, with just a few hundred samples, because it represents just a single "cycle" of the sound.
Waveforms are used by Notes
and by LFOs
.
import synthio import ulab.numpy as np wave_sine = np.array(np.sin(np.linspace(0, 2*np.pi, SAMPLE_SIZE, endpoint=False)) * SAMPLE_VOLUME, dtype=np.int16) wave_saw = np.linspace(SAMPLE_VOLUME, -SAMPLE_VOLUME, num=SAMPLE_SIZE, dtype=np.int16) wave_tri = np.concatenate((np.linspace(-SAMPLE_VOLUME, SAMPLE_VOLUME, num=half_period, dtype=np.int16), np.linspace(SAMPLE_VOLUME, -SAMPLE_VOLUME, num=half_period, dtype=np.int16))) wave_square = np.concatenate((np.full(half_period, SAMPLE_VOLUME, dtype=np.int16), np.full(half_period, -SAMPLE_VOLUME, dtype=np.int16)))
Note
Each individual sound is a Note
. A note has a bunch of properties:
-
waveform
: a small memory buffer that sets the overall tone of an instrument. -
envelope
: an object to define how the overall note amplitude changes over time -
frequency
: the basic frequency of the note in Hz (which can be modified bybend
) -
bend
: a modification to the basic frequency of the note, in units of 1/octave. This can be a number, or aBlock
in case the bend value should vary over time (e.g., a vibrato effect) -
amplitude
: a modification to the note amplitude. This can be a number, or aBlock
in case the amplitude value should vary over time (e.g., a tremolo effect) -
ring_waveform
,ring_frequency
,ring_bend
: A second, optional waveform that modulates the main waveform to create richer sounds
note1 = synthio.Note(frequency, panning, waveform, envelope, amplitude, bend, filter, ring_frequency, ring_bend, ring_waveform)
Blocks: LFOs and Math
Another way to create rich sounds is to vary parameters over time. The Note parameters that can be varied in this way are bend
, amplitude
, and ring_amplitude
.
Furthermore, multiple Math and LFO blocks can be connected together.
An LFO has these settable properties:
waveform
rate
gain
offset
once
It also has a phase
accumulator and an output value
. Here's how an LFO works:
- the
phase
advances over time, at the givenrate
. Ifrate
is negative, then it "advances" towards smaller numbers. - when the
phase
passes 1, it wraps around to 0. When going in reverse, values wrap from 0 back to 1. - When
once
isTrue
, the phase will stick at 0 or 1 rather than wrapping around. - The
waveform
is indexed based on thephase
. This value is multiplied bygain
and thenoffset
is added to create the final output value
The waveform
lets you create different ramps, the same as for notes.
lfo = synthio.LFO(rate=0.6, scale=0.05, phase_offset=0.0, once=False)
For more details, check out the full synthio
documentation here.
Next, we'll get into the code examples.
Text editor powered by tinymce.