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 boards
  • 01Space 0.42 OLED ESP32C3
  • 0xCB Helios
  • 42. Keebs Frood
  • 8086 USB Interposer
  • AITHinker ESP32-C3S_Kit
  • AITHinker ESP32-C3S_Kit_2M
  • ARAMCON Badge 2019
  • ARAMCON2 Badge
  • ATMegaZero ESP32-S2
  • Adafruit CLUE nRF52840 Express
  • Adafruit Circuit Playground Bluefruit
  • Adafruit EdgeBadge
  • Adafruit Feather Bluefruit Sense
  • Adafruit Feather ESP32 V2
  • Adafruit Feather ESP32-S2 Reverse TFT
  • Adafruit Feather ESP32-S2 TFT
  • Adafruit Feather ESP32-S3 Reverse TFT
  • Adafruit Feather ESP32-S3 TFT
  • Adafruit Feather ESP32S2
  • Adafruit Feather ESP32S3 4MB Flash 2MB PSRAM
  • Adafruit Feather ESP32S3 No PSRAM
  • Adafruit Feather HUZZAH32
  • Adafruit Feather MIMXRT1011
  • Adafruit Feather RP2040
  • Adafruit Feather RP2040 Adalogger
  • Adafruit Feather RP2040 CAN
  • Adafruit Feather RP2040 DVI
  • Adafruit Feather RP2040 Prop-Maker
  • Adafruit Feather RP2040 RFM
  • Adafruit Feather RP2040 Scorpio
  • Adafruit Feather RP2040 ThinkInk
  • Adafruit Feather RP2040 USB Host
  • Adafruit Feather RP2350
  • Adafruit Feather STM32F405 Express
  • Adafruit Feather nRF52840 Express
  • Adafruit Floppsy RP2040
  • Adafruit FunHouse
  • Adafruit Grand Central M4 Express
  • Adafruit HUZZAH32 Breakout
  • Adafruit ItsyBitsy ESP32
  • Adafruit ItsyBitsy M4 Express
  • Adafruit ItsyBitsy RP2040
  • Adafruit ItsyBitsy nRF52840 Express
  • Adafruit KB2040
  • Adafruit LED Glasses Driver nRF52840
  • Adafruit Macropad RP2040
  • Adafruit MagTag
  • Adafruit Matrix Portal M4
  • Adafruit MatrixPortal S3
  • Adafruit Metro ESP32S2
  • Adafruit Metro ESP32S3
  • Adafruit Metro RP2040
  • Adafruit Metro RP2350
  • Adafruit Metro nRF52840 Express
  • Adafruit Monster M4SK
  • Adafruit PyGamer
  • Adafruit PyPortal
  • Adafruit PyPortal Pynt
  • Adafruit PyPortal Titano
  • Adafruit Pybadge
  • Adafruit QT Py ESP32 PICO
  • Adafruit QT Py ESP32-S3 4MB Flash 2MB PSRAM
  • Adafruit QT Py ESP32-S3 no psram
  • Adafruit QT Py ESP32C3
  • Adafruit QT Py ESP32S2
  • Adafruit QT Py RP2040
  • Adafruit QT2040 Trinkey
  • Adafruit Trellis M4 Express
  • Adafruit Vindie S2
  • Adafruit-Qualia-S3-RGB666
  • Ai Thinker ESP32-CAM
  • Archi RP2040
  • Arduino Nano 33 BLE
  • Arduino Nano 33 BLE Rev2
  • Arduino Nano ESP32
  • Arduino Nano RP2040 Connect
  • Artisense Reference Design RD00
  • AtelierDuMaker nRF52840 Breakout
  • AutosportLabs-ESP32-CAN-X2
  • BARDUINO 4.0.2
  • BBQ20KBD
  • BDMICRO VINA-D51
  • BLE-SS dev board Multi Sensor
  • BLING!
  • BLOK
  • BPI-Bit-S2
  • BPI-Leaf-S3
  • BPI-PicoW-S3
  • BastBLE
  • BastWiFi
  • Bee-Data-Logger
  • Bee-Motion-S3
  • Bee-S3
  • BlizzardS3
  • BlueMicro840
  • Bradán Lane STUDIO Explorer Badge
  • COSMO-Pico
  • CP32-M4
  • CRCibernetica IdeaBoard
  • Challenger NB RP2040 WiFi
  • Challenger RP2040 LTE
  • Challenger RP2040 LoRa
  • Challenger RP2040 SD/RTC
  • Challenger RP2040 SubGHz
  • Challenger RP2040 WiFi
  • Challenger RP2040 WiFi/BLE
  • Challenger+ RP2350 BConnect
  • Challenger+ RP2350 WiFi6/BLE5
  • CircuitART Zero S3
  • ColumbiaDSL-Sensor-Board-V1
  • CrumpS2
  • Cytron EDU PICO W
  • Cytron IRIV IO Controller
  • Cytron MOTION 2350 Pro
  • Cytron Maker Feather AIoT S3
  • Cytron Maker Nano RP2040
  • Cytron Maker Pi RP2040
  • Cytron Maker Uno RP2040
  • DFRobot Beetle ESP32-C3
  • DFRobot FireBeetle 2 ESP32-S3
  • Datanoise PicoADK
  • Datanoise PicoADK V2
  • Deneyap Kart
  • Deneyap Kart 1A
  • Deneyap Kart 1A v2
  • Deneyap Kart G
  • Deneyap Mini
  • Deneyap Mini v2
  • DynOSSAT-EDU-OBC
  • E-Fidget
  • ELECFREAKS PICO:ED
  • ES3ink
  • ESP 12k NodeMCU
  • ESP32 Devkit V1
  • ESP32-C3-DevKitM-1
  • ESP32-H2-DevKitM-1
  • ESP32-P4-Function-EV
  • ESP32-S2-DevKitC-1-N4
  • ESP32-S2-DevKitC-1-N4R2
  • ESP32-S2-DevKitC-1-N8R2
  • ESP32-S3-Box-2.5
  • ESP32-S3-Box-Lite
  • ESP32-S3-DevKitC-1-N16
  • ESP32-S3-DevKitC-1-N32R8
  • ESP32-S3-DevKitC-1-N8
  • ESP32-S3-DevKitC-1-N8R2
  • ESP32-S3-DevKitC-1-N8R8
  • ESP32-S3-DevKitC-1-N8R8-with-HACKTABLET
  • ESP32-S3-DevKitM-1-N8
  • ESP32-S3-EYE
  • ESP32-S3-USB-OTG-N8
  • Electrolama minik
  • Electronut Labs Blip
  • Electronut Labs Papyr
  • EncoderPad RP2040
  • Espressif ESP32 DevKitc V4 WROOM-32E
  • Espressif ESP32 DevKitc V4 WROVER
  • Espressif ESP32 TTGO T8 v1.7
  • Espressif ESP32-EYE
  • Espressif ESP32-LyraT
  • Espressif-ESP32-S3-LCD-EV-Board
  • Espressif-ESP32-S3-LCD-EV-Board_v1.5
  • Feather MIMXRT1011
  • Feather MIMXRT1062
  • FeatherS2
  • FeatherS2 Neo
  • FeatherS2 PreRelease
  • FeatherS3
  • FeatherS3 Neo
  • Fig Pi
  • Flipper Zero Wi-Fi Dev
  • Franzininho WIFI w/Wroom
  • Franzininho WIFI w/Wrover
  • Freenove ESP32-WROVER-DEV-CAM
  • Gravitech Cucumber M
  • Gravitech Cucumber MS
  • Gravitech Cucumber R
  • Gravitech Cucumber RS
  • HEIA-FR Picomo V2
  • HMI-DevKit-1.1
  • Hack Club Sprig
  • Hardkernel Odroid Go
  • Heltec ESP32-S3-WIFI-LoRa-V3
  • HexKyS2
  • HiiBot BlueFi
  • IMXRT1010-EVK
  • IMXRT1015-EVK
  • IkigaiSense Vita nRF52840
  • IoTs2
  • Kaluga 1
  • LILYGO T-DECK
  • LILYGO T-DISPLAY
  • LILYGO T-DISPLAY S3 v1.2
  • LILYGO T-Display S3 Pro
  • LILYGO T-Watch-S3
  • LILYGO TEMBED ESP32S3
  • LILYGO TTGO T-01C3
  • LILYGO TTGO T-DISPLAY v1.1
  • LILYGO TTGO T-DISPLAY v1.1 4M
  • LILYGO TTGO T-OI PLUS
  • LILYGO TTGO T8 ESP32-S2
  • LILYGO TTGO T8 ESP32-S2 w/Display
  • LOLIN S3 16MB Flash 8MB PSRAM
  • LOLIN S3 MINI 4MB Flash 2MB PSRAM
  • LOLIN S3 PRO 16MB Flash 8MB PSRAM
  • Lilygo T-watch 2020 V3
  • Luatos Core-ESP32C3
  • M5STACK STAMP-C3
  • M5Stack Atom Echo
  • M5Stack Atom Lite
  • M5Stack Atom Matrix
  • M5Stack Atom U
  • M5Stack AtomS3
  • M5Stack AtomS3 Lite
  • M5Stack AtomS3U
  • M5Stack Cardputer
  • M5Stack Core Basic
  • M5Stack Core Fire
  • M5Stack Core2
  • M5Stack CoreS3
  • M5Stack Dial
  • M5Stack M5Paper
  • M5Stack Stick C
  • M5Stack Stick C Plus
  • M5Stack Timer Camera X
  • MDBT50Q-DB-40
  • MDBT50Q-RX Dongle
  • MEOWBIT
  • MORPHEANS MorphESP-240
  • MagiClick S3 N4R2
  • Maker Go ESP32C3 Supermini
  • Maker badge by Czech maker
  • MakerDiary nRF52840 MDK
  • MakerDiary nRF52840 MDK USB Dongle
  • MakerFabs-ESP32-S3-Parallel-TFT-With-Touch-7inch
  • Makerdiary M60 Keyboard
  • Makerdiary Pitaya Go
  • Makerdiary nRF52840 Connect Kit
  • Makerdiary nRF52840 M.2 Developer Kit
  • Maple Computing Elite-Pi
  • Melopero Shake RP2040
  • Metro MIMXRT1011
  • MicroDev microC3
  • MicroDev microS2
  • NanoS3
  • Neuron
  • OMGS3
  • Oak Dev Tech BREAD2040
  • Oak Dev Tech Cast-Away RP2040
  • Oak Dev Tech PixelWing ESP32S2
  • Oak Dev Tech RPGA Feather
  • Open Hardware Summit 2020 Badge
  • Oxocard Artwork
  • Oxocard Connect
  • Oxocard Galaxy
  • Oxocard Science
  • P1AM-200
  • PCA10056 nRF52840-DK
  • PCA10059 nRF52840 Dongle
  • Pajenicko PicoPad
  • Particle Argon
  • Particle Boron
  • Particle Xenon
  • PillBug
  • Pimoroni Badger 2040
  • Pimoroni Badger 2040 W
  • Pimoroni Inky Frame 5.7
  • Pimoroni Inky Frame 7.3
  • Pimoroni Interstate 75
  • Pimoroni Keybow 2040
  • Pimoroni Motor 2040
  • Pimoroni PGA2040
  • Pimoroni PGA2350
  • Pimoroni Pico DV Base W
  • Pimoroni Pico LiPo (16MB)
  • Pimoroni Pico LiPo (4MB)
  • Pimoroni Pico Plus 2
  • Pimoroni Pico dv Base
  • Pimoroni PicoSystem
  • Pimoroni Plasma 2040
  • Pimoroni Plasma 2040W
  • Pimoroni Plasma 2350
  • Pimoroni Servo 2040
  • Pimoroni Tiny 2040 (2MB)
  • Pimoroni Tiny 2040 (8MB)
  • Pimoroni Tiny 2350
  • Pimoroni Tiny FX
  • ProS3
  • PyCubedv04
  • PyCubedv04-MRAM
  • PyCubedv05
  • PyCubedv05-MRAM
  • PyKey 18 Numpad
  • PyKey 44 Ergo
  • PyKey 60
  • PyKey 87 TKL
  • PyboardV1_1
  • RF.Guru RP2040
  • RGBTouch Mini
  • RP2.65-F
  • RP2040 Stamp
  • RP2350 Stamp
  • RP2350 Stamp XL
  • Raspberry Breadstick
  • Raspberry Pi Pico
  • Raspberry Pi Pico 2
  • Raspberry Pi Pico W
  • Robo HAT MM1 M4
  • S2Mini
  • S2Pico
  • SAM E54 Xplained Pro
  • SAM32v26
  • SQFMI Watchy
  • SSCI ISP1807 Dev Board
  • SSCI ISP1807 Micro Board
  • STM32F412G_DISCO
  • STM32F4_DISCO
  • Saola 1 w/Wroom
  • Saola 1 w/Wrover
  • Seeed Studio XIAO ESP32C3
  • Seeed XIAO nRF52840 Sense
  • Seeed Xiao ESP32-S3 Sense
  • Seeeduino XIAO RP2040
  • Seeeduino XIAO RP2350
  • Silicognition LLC RP2040-Shim
  • SparkFun MicroMod RP2040 Processor
  • SparkFun MicroMod SAMD51 Processor
  • SparkFun MicroMod nRF52840 Processor
  • SparkFun Pro Micro RP2040
  • SparkFun Pro Micro RP2350
  • SparkFun Pro nRF52840 Mini
  • SparkFun STM32 MicroMod Processor
  • SparkFun Teensy MicroMod Processor
  • SparkFun Thing Plus - RP2040
  • SparkFun Thing Plus - SAMD51
  • SparkFun Thing Plus - STM32
  • Spotpear ESP32C3 LCD 1.44
  • Sunton ESP32-2424S012
  • Sunton-ESP32-8048S050
  • Sunton-ESP32-8048S070
  • SuperMini NRF52840
  • Swan R5
  • TG-Watch
  • TTGO T8 ESP32-S2-WROOM
  • Targett Module Clip w/Wroom
  • Targett Module Clip w/Wrover
  • Teensy 4.0
  • Teensy 4.1
  • Teknikio Bluebird
  • ThingPulse Pendrive S3
  • TinkeringTech ScoutMakes Azul
  • TinyPICO
  • TinyPICO Nano
  • TinyS2
  • TinyS3
  • TinyWATCH S3
  • VCC-GND Studio YD RP2040
  • VCC-GND YD-ESP32-S3 (N16R8)
  • VCC-GND YD-ESP32-S3 (N8R8)
  • VIDI X V1.1
  • W5100S-EVB-Pico
  • W5500-EVB-Pico
  • WK-50 Trackball Keyboard
  • WSC-1450
  • WarmBit BluePixel nRF52840
  • Waveshare ESP32-S2-Pico
  • Waveshare ESP32-S2-Pico-LCD
  • Waveshare ESP32-S3-GEEK
  • Waveshare ESP32-S3-Pico
  • Waveshare ESP32-S3-Tiny
  • Waveshare ESP32-S3-Zero
  • Waveshare ESP32S3 LCD 1.28
  • Waveshare RP2040-GEEK
  • Waveshare RP2040-LCD-0.96
  • Waveshare RP2040-LCD-1.28
  • Waveshare RP2040-One
  • Waveshare RP2040-PiZero
  • Waveshare RP2040-Plus (16MB)
  • Waveshare RP2040-Plus (4MB)
  • Waveshare RP2040-TOUCH-LCD-1.28
  • Waveshare RP2040-Tiny
  • Waveshare RP2040-Zero
  • WeAct Studio Pico
  • WeAct Studio Pico 16MB
  • WeMos LOLIN32 Lite
  • Wemos Lolin C3 Mini
  • Wemos Lolin C3 Pico
  • WisdPi Ardu2040M
  • WisdPi Tiny RP2040
  • iLabs Challenger 840
  • iMX RT 1020 EVK
  • iMX RT 1040 EVK
  • iMX RT 1050 EVKB
  • iMX RT 1060 EVK
  • iMX RT 1060 EVKB
  • iMX RT1011 Nano Kit
  • nanoESP32-S2 w/Wrover
  • nanoESP32-S2 w/Wroom
  • nice!nano
  • nullbits Bit-C PRO
  • senseBox MCU-S2 ESP32S2
  • splitkb.com Liatris
  • stm32f411ce-blackpill-with-flash
  • sunton_esp32_2432S028
  • sunton_esp32_2432S032C
  • takayoshiotake Octave RP2040
  • uGame22

class synthio.EnvelopeState
ATTACK: EnvelopeState

The note is in its attack phase

DECAY: EnvelopeState

The note is in its decay phase

SUSTAIN: EnvelopeState

The note is in its sustain phase

RELEASE: EnvelopeState

The note is in its release phase

synthio.BlockInput

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).

class synthio.Envelope(*, attack_time: float | None = 0.1, decay_time: float | None = 0.05, release_time: float | None = 0.2, attack_level: float | None = 1.0, sustain_level: float | None = 0.8)

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)

Parameters:
  • 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

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

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

synthio.from_file(file: BinaryIO, *, sample_rate: int = 11025, waveform: circuitpython_typing.ReadableBuffer | None = None, envelope: Envelope | None = None) MidiTrack

Create an AudioSample from an already opened MIDI file. Currently, only single-track MIDI (type 0) is supported.

Parameters:
  • 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")
synthio.midi_to_hz(midi_note: float) float

Converts the given midi note (60 = middle C, 69 = concert A) to Hz

synthio.voct_to_hz(ctrl: float) float

Converts a 1v/octave signal to Hz.

24/12 (2.0) corresponds to middle C, 33/12 (2.75) is concert A.

synthio.waveform_max_length: int

The maximum number of samples permitted in a waveform

class synthio.Biquad(b0: float, b1: float, b2: float, a1: float, a2: float)

Construct a normalized biquad filter object.

This implements the “direct form 1” biquad filter, where each coefficient has been pre-divided by a0.

Biquad objects are usually constructed via one of the related methods on a Synthesizer object rather than directly from coefficients.

https://github.com/WebAudio/Audio-EQ-Cookbook/blob/main/Audio-EQ-Cookbook.txt

class synthio.LFO(waveform: circuitpython_typing.ReadableBuffer | None = None, *, rate: BlockInput = 1.0, scale: BlockInput = 1.0, offset: BlockInput = 0.0, phase_offset: BlockInput = 0.0, once: bool = False, interpolate: bool = True)

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 Synthtesizer 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.

waveform: circuitpython_typing.ReadableBuffer | None

The waveform of this lfo. (read-only, but the values in the buffer may be modified dynamically)

rate: BlockInput

The rate (in Hz) at which the LFO cycles through its waveform

offset: BlockInput

An additive value applied to the LFO’s output

phase_offset: BlockInput

An additive value applied to the LFO’s phase

scale: BlockInput

An multiplier value applied to the LFO’s output

once: bool

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

interpolate: bool

True if the waveform should perform linear interpolation between values

phase: float

The phase of the oscillator, in the range 0 to 1 (read-only)

value: float

The value of the oscillator (read-only)

retrigger() None

Reset the LFO’s internal index to the start of the waveform. Most useful when it its once property is True.

class synthio.MathOperation

Operation for a Math block

__call__(a: BlockInput, b: BlockInput = 0.0, c: BlockInput = 1.0) Math

A MathOperation enumeration value can be called to construct a Math block that performs that operation

SUM: MathOperation

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.

ADD_SUB: MathOperation

Computes a+b-c. For 2-input subtraction, set b to 0.0.

PRODUCT: MathOperation

Computes a*b*c. For 2-input product, set one argument to 1.0.

MUL_DIV: MathOperation

Computes a*b/c. If c is zero, the output is 1.0.

SCALE_OFFSET: MathOperation

Computes (a*b)+c.

OFFSET_SCALE: MathOperation

Computes (a+b)*c. For 2-input multiplication, set b to 0.

LERP: MathOperation

Computes a * (1-c) + b * c.

CONSTRAINED_LERP: MathOperation

Computes a * (1-c') + b * c', where c' is constrained to be between 0.0 and 1.0.

DIV_ADD: MathOperation

Computes a/b+c. If b is zero, the output is c.

ADD_DIV: MathOperation

Computes (a+b)/c. For 2-input product, set b to 0.0.

MID: MathOperation

Returns the middle of the 3 input values.

MAX: MathOperation

Returns the biggest of the 3 input values.

MIN: MathOperation

Returns the smallest of the 3 input values.

ABS: MathOperation

Returns the absolute value of a.

class synthio.Math(operation: MathOperation, a: BlockInput, b: BlockInput = 0.0, c: BlockInput = 1.0)

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 Synthtesizer objects is undefined.

In the current implementation, Maths are updated every 256 samples. This should be considered an implementation detail.

a: BlockInput

The first input to the operation

b: BlockInput

The second input to the operation

c: BlockInput

The third input to the operation

operation: MathOperation

The function to compute

value: float

The value of the oscillator (read-only)

class synthio.MidiTrack(buffer: circuitpython_typing.ReadableBuffer, tempo: int, *, sample_rate: int = 11025, waveform: circuitpython_typing.ReadableBuffer | None = None, envelope: Envelope | None = None)

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.

Parameters:
  • 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")
deinit() None

Deinitialises the MidiTrack and releases any hardware resources for reuse.

__enter__() MidiTrack

No-op used by Context Managers.

__exit__() None

Automatically deinitializes the hardware when exiting a context. See Lifetime and ContextManagers for more info.

sample_rate: int

32 bit value that tells how quickly samples are played in Hertz (cycles per second).

error_location: int | None

Offset, in bytes within the midi data, of a decoding error

class synthio.Note(*, frequency: float, panning: BlockInput = 0.0, waveform: circuitpython_typing.ReadableBuffer | None = None, waveform_loop_start: int = 0, waveform_loop_end: int = waveform_max_length, envelope: Envelope | None = None, amplitude: BlockInput = 0.0, bend: BlockInput = 0.0, filter: Biquad | None = None, ring_frequency: float = 0.0, ring_bend: float = 0.0, ring_waveform: circuitpython_typing.ReadableBuffer | None = None, ring_waveform_loop_start: int = 0, ring_waveform_loop_end: int = waveform_max_length)

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.

frequency: float

The base frequency of the note, in Hz.

filter: Biquad | None

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

panning: BlockInput

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.

amplitude: BlockInput

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.

bend: BlockInput

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.

waveform: circuitpython_typing.ReadableBuffer | None

The waveform of this note. Setting the waveform to a buffer of a different size resets the note’s phase.

waveform_loop_start: int

The sample index of where to begin looping waveform data.

Values outside the range 0 to waveform_max_length-1 (inclusive) are rejected with a ValueError.

Values greater than or equal to the actual waveform length are treated as 0.

waveform_loop_end: int

The sample index of where to end looping waveform data.

Values outside the range 1 to waveform_max_length (inclusive) are rejected with a ValueError.

If the value is greater than the actual waveform length, or less than or equal to the loop start, the loop will occur at the end of the waveform.

Use the synthio.waveform_max_length constant to set the loop point at the end of the wave form, no matter its length.

envelope: Envelope

The envelope of this note

ring_frequency: float

The ring frequency of the note, in Hz. Zero disables.

For ring to take effect, both ring_frequency and ring_waveform must be set.

ring_bend: float

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.

ring_waveform: circuitpython_typing.ReadableBuffer | None

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.

ring_waveform_loop_start: int

The sample index of where to begin looping waveform data.

Values outside the range 0 to waveform_max_length-1 (inclusive) are rejected with a ValueError.

Values greater than or equal to the actual waveform length are treated as 0.

ring_waveform_loop_end: int

The sample index of where to end looping waveform data.

Values outside the range 1 to waveform_max_length (inclusive) are rejected with a ValueError.

If the value is greater than the actual waveform length, or less than or equal to the loop start, the loop will occur at the end of the waveform.

Use the synthio.waveform_max_length constant to set the loop point at the end of the wave form, no matter its length.

synthio.NoteSequence

A sequence of notes, which can each be integer MIDI note numbers or Note objects

synthio.NoteOrNoteSequence

A note or sequence of notes

synthio.LFOOrLFOSequence

An LFO or a sequence of LFOs

class synthio.Synthesizer(*, sample_rate: int = 11025, channel_count: int = 1, waveform: circuitpython_typing.ReadableBuffer | None = None, envelope: Envelope | None = None)

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.

Parameters:
  • 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.

press(/, press=()) None

Turn some notes on.

Pressing a note that was already pressed has no effect.

Parameters:

press (NoteOrNoteSequence) – Any sequence of notes.

release(/, release=()) None

Turn some notes off.

Releasing a note that was already released has no effect.

Parameters:

release (NoteOrNoteSequence) – Any sequence of notes.

change(release: NoteOrNoteSequence = (), press: NoteOrNoteSequence = (), retrigger: LFOOrLFOSequence = ()) None

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.

Parameters:

Note: for compatibility, release_then_press may be used as an alias for this function. This compatibility name will be removed in 9.0.

release_all_then_press(/, press) None

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.

Parameters:

press (NoteOrNoteSequence) – Any sequence of notes.

release_all() None

Turn any currently-playing notes off

deinit() None

Deinitialises the object and releases any memory resources for reuse.

__enter__() Synthesizer

No-op used by Context Managers.

__exit__() None

Automatically deinitializes the hardware when exiting a context. See Lifetime and ContextManagers for more info.

envelope: Envelope | None

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)

sample_rate: int

32 bit value that tells how quickly samples are played in Hertz (cycles per second).

pressed: NoteSequence

A sequence of the currently pressed notes (read-only property).

This does not include notes in the release phase of the envelope.

note_info(note: Note) Tuple[EnvelopeState | None, float]

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).

blocks: List[BlockInput]

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.

max_polyphony: int

Maximum polyphony of the synthesizer (read-only class property)

low_pass_filter(frequency: float, q_factor: float = 0.7071067811865475) Biquad

Construct a low-pass filter with the given parameters.

frequency, called f0 in the cookbook, is the corner frequency in Hz of the filter.

q_factor, called Q in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.

high_pass_filter(frequency: float, q_factor: float = 0.7071067811865475) Biquad

Construct a high-pass filter with the given parameters.

frequency, called f0 in the cookbook, is the corner frequency in Hz of the filter.

q_factor, called Q in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.

band_pass_filter(frequency: float, q_factor: float = 0.7071067811865475) Biquad

Construct a band-pass filter with the given parameters.

frequency, called f0 in the cookbook, is the center frequency in Hz of the filter.

q_factor, called Q in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.

The coefficients are scaled such that the filter has a 0dB peak gain.