Last active
December 27, 2024 00:08
-
-
Save todbot/532e069845c2cc4c1bc39c9162a34bfe to your computer and use it in GitHub Desktop.
Drone synthing using two knobs to control three voices in synthio with optional echo effect in CircuitPython
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# two_pot_drone_synth.py -- Use two knobs to control three voices in synthio | |
# with optional echo effect | |
# 25 Nov 2024 - @todbot / Tod Kurt | |
# video demo: https://www.youtube.com/watch?v=xEmhk-dVXqQ | |
# original simpler version: | |
# https://gist.github.com/todbot/53bfa7333ac7c1fcff8eaf0f547556ff | |
import time, random | |
import board, keypad, analogio | |
import audiopwmio, audiomixer, synthio | |
import ulab.numpy as np | |
sample_rate=36000 # higher sampling rate means faster knob response | |
# set up input devices | |
keys = keypad.Keys( (board.GP4, board.GP8), value_when_pressed=False, pull=True) | |
knob1 = analogio.AnalogIn(board.GP26) | |
knob2 = analogio.AnalogIn(board.GP27) | |
# set up audio out. Using mixer reduces glitches | |
audio = audiopwmio.PWMAudioOut(board.GP15) | |
mixer = audiomixer.Mixer(voice_count=1, sample_rate=sample_rate, channel_count=1, | |
bits_per_sample=16, samples_signed=True, buffer_size=2048) | |
synth = synthio.Synthesizer(sample_rate=sample_rate) | |
audio.play(mixer) | |
# try to load up echo effect, if not available no worries | |
try: | |
import audiodelays | |
effect = audiodelays.Echo( max_delay_ms=600, delay_ms=600, | |
decay=0.7, mix=0.4, freq_shift=False, | |
buffer_size=2048, sample_rate=sample_rate ) | |
mixer.voice[0].play(effect) | |
effect.play(synth) | |
except: | |
mixer.voice[0].play(synth) # if we don't have delay | |
# configure our synthesizer and set it going | |
lfo = synthio.LFO(rate=5) | |
amp_env = synthio.Envelope(attack_time=0.5, release_time=0.5) | |
wave_saw = np.linspace(30000, -30000, num=512, dtype=np.int16) # max is +/-32k but gives us headroom | |
# need to lower amplitude or get clipping when delays are active | |
note1 = synthio.Note(frequency=100, waveform=wave_saw, amplitude=0.5, envelope=amp_env) | |
note2 = synthio.Note(frequency=100, waveform=wave_saw, amplitude=0.5, envelope=amp_env) | |
note3 = synthio.Note(frequency=100, waveform=wave_saw, amplitude=0.5, envelope=amp_env) | |
synth.press((note1,note2, note3)) | |
f = 0.7 # for simple knob filtering | |
k1, k2 = 0, 0 | |
last_print_time = 0 | |
while True: | |
# handle button presses | |
if key := keys.events.get(): | |
print("key:",key) | |
if key.key_number == 0: | |
if key.pressed: | |
note1.bend = lfo | |
else: | |
note1.bend = 0 | |
if key.key_number == 1: | |
if key.pressed: | |
synth.release_all() | |
else: | |
synth.press((note1,note2)) | |
# simple knob filtering because noisy ADCs | |
k1 += int((knob1.value - k1) * f) | |
k2 += int((knob2.value - k2) * f) | |
# update the drone voices' pitches | |
note1.frequency = synthio.midi_to_hz( k1 / 512 ) | |
note2.frequency = synthio.midi_to_hz( k2 / 512 ) | |
note3.frequency = note2.frequency*1.005 # detune for phatness | |
time.sleep(0.001) | |
if time.monotonic() - last_print_time > 0.2: | |
last_print_time = time.monotonic() | |
print("notes %4.1f %4.1f" % (k1/512, k2/512)) # a little debug info |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment