synthio
– Support for multi-channel audio synthesis
At least 2 simultaneous notes are supported. samd5x, mimxrt10xx and rp2040 platforms support up to 12 notes.
Available on these boardsThe note is in its attack phase
The note is in its decay phase
The note is in its sustain phase
The note is in its release phase
Blocks and Notes can take any of these types as inputs on certain attributes
A BlockInput can be any of the following types: Math
, LFO
, builtins.float
, None
(treated same as 0).
Construct an Envelope object
The Envelope defines an ADSR (Attack, Decay, Sustain, Release) envelope with linear amplitude ramping. A note starts at 0 volume, then increases to attack_level
over attack_time
seconds; then it decays to sustain_level
over decay_time
seconds. Finally, when the note is released, it decreases to 0
volume over release_time
.
If the sustain_level
of an envelope is 0, then the decay and sustain phases of the note are always omitted. The note is considered to be released as soon as the envelope reaches the end of the attack phase. The decay_time
is ignored. This is similar to how a plucked or struck instrument behaves.
If a note is released before it reaches its sustain phase, it decays with the same slope indicated by sustain_level/release_time
(or attack_level/release_time
for plucked envelopes)
attack_time (float) – The time in seconds it takes to ramp from 0 volume to attack_volume
decay_time (float) – The time in seconds it takes to ramp from attack_volume to sustain_volume
release_time (float) – The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by release_time
and sustain_level
. If the sustain_level
is 0.0
then the release slope calculations use the attack_level
instead.
attack_level (float) – The level, in the range 0.0
to 1.0
of the peak volume of the attack phase
sustain_level (float) – The level, in the range 0.0
to 1.0
of the volume of the sustain phase relative to the attack level
The time in seconds it takes to ramp from 0 volume to attack_volume
The time in seconds it takes to ramp from attack_volume to sustain_volume
The time in seconds it takes to ramp from sustain_volume to release_volume. When a note is released before it has reached the sustain phase, the release is done with the same slope indicated by release_time
and sustain_level
The level, in the range 0.0
to 1.0
of the peak volume of the attack phase
The level, in the range 0.0
to 1.0
of the volume of the sustain phase relative to the attack level
Create an AudioSample from an already opened MIDI file. Currently, only single-track MIDI (type 0) is supported.
file (BinaryIO) – Already opened MIDI file
sample_rate (int) – The desired playback sample rate; higher sample rate requires more memory
waveform (ReadableBuffer) – A single-cycle waveform. Default is a 50% duty cycle square wave. If specified, must be a ReadableBuffer of type ‘h’ (signed 16 bit)
envelope (Envelope) – An object that defines the loudness of a note over time. The default envelope provides no ramping, voices turn instantly on and off.
Playing a MIDI file from flash:
import audioio import board import synthio data = open("single-track.midi", "rb") midi = synthio.from_file(data) a = audioio.AudioOut(board.A0) print("playing") a.play(midi) while a.playing: pass print("stopped")
Converts the given midi note (60 = middle C, 69 = concert A) to Hz
Converts a 1v/octave signal to Hz.
24/12 (2.0) corresponds to middle C, 33/12 (2.75) is concert A.
The maximum number of samples permitted in a waveform
The type of filter
A low-pass filter
A high-pass filter
A band-pass filter
A notch filter
A low shelf filter
A high shelf filter
A peaking equalizer filter
Construct a biquad filter object with given settings.
frequency
gives the center frequency or corner frequency of the filter, depending on the mode.
Q
gives the gain or sharpness of the filter.
A
controls the gain of peaking and shelving filters according to the formula A = 10^(dBgain/40)
. For other filter types it is ignored.
Since frequency
and Q
are BlockInput
objects, they can be varied dynamically. Internally, this is evaluated as “direct form 1” biquad filter.
The internal filter state x[] and y[] is not updated when the filter coefficients change, and there is no theoretical justification for why this should result in a stable filter output. However, in practice, slowly varying the filter’s characteristic frequency and sharpness appears to work as you’d expect.
The mode of filter (read-only)
The central frequency (in Hz) of the filter
The sharpness (Q) of the filter
The gain (A) of the filter
This setting only has an effect for peaking and shelving EQ filters. It is related to the filter gain according to the formula A = 10^(dBgain/40)
.
A low-frequency oscillator block
Every rate
seconds, the output of the LFO cycles through its waveform
. The output at any particular moment is waveform[idx] * scale + offset
.
If waveform
is None, a triangle waveform is used.
rate
, phase_offset
, offset
, scale
, and once
can be changed at run-time. waveform
may be mutated.
waveform
must be a ReadableBuffer
with elements of type 'h'
(16-bit signed integer). Internally, the elements of waveform
are scaled so that the input range [-32768,32767]
maps to [-1.0, 0.99996]
.
An LFO only updates if it is actually associated with a playing Synthesizer
, including indirectly via a Note
or another intermediate LFO.
Using the same LFO as an input to multiple other LFOs or Notes is OK, but the result if an LFO is tied to multiple Synthesizer
objects is undefined.
In the current implementation, LFOs are updated every 256 samples. This should be considered an implementation detail, though it affects how LFOs behave for instance when used to implement an integrator (l.offset = l
).
An LFO’s value
property is computed once when it is constructed, and then when its associated synthesizer updates it.
This means that for instance an LFO created with offset=1
has value==1
immediately, but updating the offset
property alone does not change value
; it only updates through an association with an active synthesizer.
The interpolation of the waveform is necessarily different depending on the once
property. Consider a LFO with waveform=np.array([0, 100], dtype=np.int16), interpolate=True, once=True, rate=1
. Over 1 second this LFO’s output will change from 0
to 100
, and will remain at 100
thereafter, creating a “bend out” over a duration of 1 second.
However, when once=False
, this creates a triangle waveform with a period of 1 second. Over about the first half second the input will increase from 0
to 100
, then during the second half of the second it will decrease back to 0
.
The time of the peak output is different depending on the value of once
: At 1.0s for once=True
and at 0.5s for once=False
.
Because of this difference in interpolation, dynamically updating the once
flag except when the LFO is at a phase of 0 will cause a step in the LFO’s output.
The waveform of this lfo. (read-only, but the values in the buffer may be modified dynamically)
The rate (in Hz) at which the LFO cycles through its waveform
An additive value applied to the LFO’s output
An additive value applied to the LFO’s phase
An multiplier value applied to the LFO’s output
True if the waveform should stop when it reaches its last output value, false if it should re-start at the beginning of its waveform
This applies to the phase
before the addition of any phase_offset
True if the waveform should perform linear interpolation between values
The phase of the oscillator, in the range 0 to 1 (read-only)
The value of the oscillator (read-only)
Operation for a Math block
A MathOperation enumeration value can be called to construct a Math block that performs that operation
Computes a+b+c
. For 2-input sum, set one argument to 0.0
. To hold a control value for multiple subscribers, set two arguments to 0.0
.
Computes a+b-c
. For 2-input subtraction, set b
to 0.0
.
Computes a*b*c
. For 2-input product, set one argument to 1.0
.
Computes a*b/c
. If c
is zero, the output is 1.0
.
Computes (a*b)+c
.
Computes (a+b)*c
. For 2-input multiplication, set b
to 0.
Computes a * (1-c) + b * c
.
Computes a * (1-c') + b * c'
, where c'
is constrained to be between 0.0
and 1.0
.
Computes a/b+c
. If b
is zero, the output is c
.
Computes (a+b)/c
. For 2-input product, set b
to 0.0
.
Returns the middle of the 3 input values.
Returns the biggest of the 3 input values.
Returns the smallest of the 3 input values.
Returns the absolute value of a
.
An arithmetic block
Performs an arithmetic operation on up to 3 inputs. See the documentation of MathOperation
for the specific functions available.
The properties can all be changed at run-time.
An Math only updates if it is actually associated with a playing Synthesizer
, including indirectly via a Note
or another intermediate Math.
Using the same Math as an input to multiple other Maths or Notes is OK, but the result if an Math is tied to multiple Synthesizer
objects is undefined.
In the current implementation, Maths are updated every 256 samples. This should be considered an implementation detail.
The first input to the operation
The second input to the operation
The third input to the operation
The function to compute
The value of the oscillator (read-only)
Simple MIDI synth
Create a MidiTrack from the given stream of MIDI events. Only “Note On” and “Note Off” events are supported; channel numbers and key velocities are ignored. Up to two notes may be on at the same time.
buffer (ReadableBuffer) – Stream of MIDI events, as stored in a MIDI file track chunk
tempo (int) – Tempo of the streamed events, in MIDI ticks per second
sample_rate (int) – The desired playback sample rate; higher sample rate requires more memory
waveform (ReadableBuffer) – A single-cycle waveform. Default is a 50% duty cycle square wave. If specified, must be a ReadableBuffer of type ‘h’ (signed 16 bit)
envelope (Envelope) – An object that defines the loudness of a note over time. The default envelope provides no ramping, voices turn instantly on and off.
Simple melody:
import audioio import board import synthio dac = audioio.AudioOut(board.SPEAKER) melody = synthio.MidiTrack(b"\0\x90H\0*\x80H\0\6\x90J\0*\x80J\0\6\x90L\0*\x80L\0\6\x90J\0" + b"*\x80J\0\6\x90H\0*\x80H\0\6\x90J\0*\x80J\0\6\x90L\0T\x80L\0" + b"\x0c\x90H\0T\x80H\0\x0c\x90H\0T\x80H\0", tempo=640) dac.play(melody) print("playing") while dac.playing: pass print("stopped")
Deinitialises the MidiTrack and releases any hardware resources for reuse.
No-op used by Context Managers.
Automatically deinitializes the hardware when exiting a context. See Lifetime and ContextManagers for more info.
32 bit value that tells how quickly samples are played in Hertz (cycles per second).
Tempo of the streamed events, in MIDI ticks per second.
Offset, in bytes within the midi data, of a decoding error
Construct a Note object, with a frequency in Hz, and optional panning, waveform, envelope, tremolo (volume change) and bend (frequency change).
If waveform or envelope are None
the synthesizer object’s default waveform or envelope are used.
If the same Note object is played on multiple Synthesizer objects, the result is undefined.
The base frequency of the note, in Hz.
If not None, the output of this Note is filtered according to the provided coefficients.
Construct an appropriate filter by calling a filter-making method on the Synthesizer
object where you plan to play the note, as filter coefficients depend on the sample rate
Defines the channel(s) in which the note appears.
-1 is left channel only, 0 is both channels, and 1 is right channel. For fractional values, the note plays at full amplitude in one channel and partial amplitude in the other channel. For instance -.5 plays at full amplitude in the left channel and 1/2 amplitude in the right channel.
The relative amplitude of the note, from 0 to 1
An amplitude of 0 makes the note inaudible. It is combined multiplicatively with the value from the note’s envelope.
To achieve a tremolo effect, attach an LFO here.
The pitch bend depth of the note, from -12 to +12
A depth of 0 plays the programmed frequency. A depth of 1 corresponds to a bend of 1 octave. A depth of (1/12) = 0.0833 corresponds to a bend of 1 semitone, and a depth of .00833 corresponds to one musical cent.
To achieve a vibrato or sweep effect, attach an LFO here.
The waveform of this note. Setting the waveform to a buffer of a different size resets the note’s phase.
The sample index of where to begin looping waveform data.
The value is limited to the range 0
to len(waveform)-1
(inclusive).
The sample index of where to end looping waveform data.
The value is limited to the range waveform_loop_start+1
to len(waveform)
(inclusive).
Use the synthio.waveform_max_length
constant to set the loop point at the end of the wave form, no matter its length.
The envelope of this note
The ring frequency of the note, in Hz. Zero disables.
For ring to take effect, both ring_frequency
and ring_waveform
must be set.
The pitch bend depth of the note’s ring waveform, from -12 to +12
A depth of 0 plays the programmed frequency. A depth of 1 corresponds to a bend of 1 octave. A depth of (1/12) = 0.0833 corresponds to a bend of 1 semitone, and a depth of .00833 corresponds to one musical cent.
To achieve a vibrato or sweep effect on the ring waveform, attach an LFO here.
The ring waveform of this note. Setting the ring_waveform to a buffer of a different size resets the note’s phase.
For ring to take effect, both ring_frequency
and ring_waveform
must be set.
The sample index of where to begin looping waveform data.
The value is limited to the range 0
to len(ring_waveform)-1
(inclusive).
The sample index of where to end looping waveform data.
The value is limited to the range ring_waveform_loop_start+1
to len(ring_waveform)
(inclusive).
Use the synthio.waveform_max_length
constant to set the loop point at the end of the wave form, no matter its length.
A sequence of notes, which can each be integer MIDI note numbers or Note
objects
A note or sequence of notes
An LFO or a sequence of LFOs
Create a synthesizer object.
This API is experimental.
Integer notes use MIDI note numbering, with 60 being C4 or Middle C, approximately 262Hz. Integer notes use the given waveform & envelope, and do not support advanced features like tremolo or vibrato.
sample_rate (int) – The desired playback sample rate; higher sample rate requires more memory
channel_count (int) – The number of output channels (1=mono, 2=stereo)
waveform (ReadableBuffer) – A single-cycle waveform. Default is a 50% duty cycle square wave. If specified, must be a ReadableBuffer of type ‘h’ (signed 16 bit)
envelope (Optional[Envelope]) – An object that defines the loudness of a note over time. The default envelope, None
provides no ramping, voices turn instantly on and off.
Turn some notes on.
Pressing a note that was already pressed has no effect.
press (NoteOrNoteSequence) – Any sequence of notes.
Turn some notes off.
Releasing a note that was already released has no effect.
release (NoteOrNoteSequence) – Any sequence of notes.
Start notes, stop them, and/or re-trigger some LFOs.
The changes all happen atomically with respect to output generation.
It is OK to release note that was not actually turned on.
Pressing a note that was already pressed returns it to the attack phase but without resetting its amplitude. Releasing a note and immediately pressing it again returns it to the attack phase with an initial amplitude of 0.
At the same time, the passed LFOs (if any) are retriggered.
release (NoteOrNoteSequence) – Any sequence of notes.
press (NoteOrNoteSequence) – Any sequence of notes.
retrigger (LFOOrLFOSequence) – Any sequence of LFOs.
Note: for compatibility, release_then_press
may be used as an alias for this function. This compatibility name will be removed in 9.0.
Turn any currently-playing notes off, then turn on the given notes
Releasing a note and immediately pressing it again returns it to the attack phase with an initial amplitude of 0.
press (NoteOrNoteSequence) – Any sequence of notes.
Turn any currently-playing notes off
Deinitialises the object and releases any memory resources for reuse.
No-op used by Context Managers.
Automatically deinitializes the hardware when exiting a context. See Lifetime and ContextManagers for more info.
The envelope to apply to all notes. None
, the default envelope, instantly turns notes on and off. The envelope may be changed dynamically, but it affects all notes (even currently playing notes)
32 bit value that tells how quickly samples are played in Hertz (cycles per second).
A sequence of the currently pressed notes (read-only property).
This does not include notes in the release phase of the envelope.
Get info about a note’s current envelope state
If the note is currently playing (including in the release phase), the returned value gives the current envelope state and the current envelope value.
If the note is not playing on this synthesizer, returns the tuple (None, 0.0)
.
A list of blocks to advance whether or not they are associated with a playing note.
This can be used to implement ‘free-running’ LFOs. LFOs associated with playing notes are advanced whether or not they are in this list.
This property is read-only but its contents may be modified by e.g., calling synth.blocks.append()
or synth.blocks.remove()
. It is initially an empty list.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4