Skip to content

Commit 774c940

Browse files
authored
Merge pull request #2517 from jedgarpark/computer-perfection-synth
first commit computer perfection synth code
2 parents e5a0806 + f31a8a9 commit 774c940

File tree

1 file changed

+214
-0
lines changed

1 file changed

+214
-0
lines changed

Computer_Perfection_Synth/code.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# SPDX-FileCopyrightText: 2023 John Park, Jeff Epler, and Tod Kurt for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
# Computer Perfection Synth
4+
# * 10 numbered buttons play notes
5+
# * SET button to increase LFO rate, long press to decrease LFO rate
6+
# * SCORE button to add lower octave
7+
# * MODE switch changes LFO depth?
8+
# * SKILL switch toggles sustain
9+
# * GAME switch must stay in position 1 or it messes with the other switches
10+
11+
import time
12+
import random
13+
import board
14+
import audiobusio
15+
import audiomixer
16+
import synthio
17+
import ulab.numpy as np
18+
import neopixel
19+
import keypad
20+
21+
22+
# NeoPixel setup
23+
num_pixels = 34
24+
pixels = neopixel.NeoPixel(board.D11, num_pixels, brightness=0.7, auto_write=False)
25+
pixels.fill(0x0)
26+
pixels.show()
27+
time.sleep(0.25)
28+
pix_map = [26, 23, 19, 16, 13, 10, 7, 4, 32, 29] # map the LEDs to the numbered panel sections 0-9
29+
for p in range(len(pix_map)):
30+
pixels[pix_map[p]] = 0xff0000
31+
pixels.show()
32+
time.sleep(0.1)
33+
34+
35+
note_buttons = keypad.Keys(
36+
(board.D0, board.D1, board.D2, board.D3, board.D4,
37+
board.D5, board.D6, board.D7, board.D8, board.A5),
38+
value_when_pressed=False,
39+
pull=True
40+
)
41+
switches = keypad.Keys(
42+
(board.A1, board.A0),
43+
value_when_pressed=False,
44+
pull=True
45+
)
46+
octave = 3 # octave multiplier
47+
note_list = (0, 4, 6, 7, 9, 12, 16, 18, 19, 21) # Lydian scale
48+
49+
mod_buttons = keypad.Keys(
50+
(board.A4, board.A3), # SET and SCORE buttons
51+
value_when_pressed=False,
52+
pull=True
53+
)
54+
55+
SAMPLE_RATE = 48000 # clicks @ 36kHz & 48kHz on rp2040
56+
SAMPLE_SIZE = 200
57+
VOLUME = 12000
58+
59+
# Metro M7 pins for the I2S amp:
60+
lck_pin, bck_pin, dat_pin = board.D9, board.D10, board.D12
61+
62+
# synth engine setup
63+
waveform = np.zeros(SAMPLE_SIZE, dtype=np.int16) # intially all zeros (silence)
64+
65+
amp_env = synthio.Envelope( # default (0.1, 0.05, 0.2, 1, 0.8)
66+
attack_time=1.0,
67+
decay_time=0.05,
68+
release_time=3.0,
69+
attack_level=1.0,
70+
sustain_level=0.8
71+
)
72+
73+
synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE, waveform=waveform, envelope=amp_env)
74+
audio = audiobusio.I2SOut(bit_clock=bck_pin, word_select=lck_pin, data=dat_pin)
75+
mixer = audiomixer.Mixer(voice_count=1, sample_rate=SAMPLE_RATE, channel_count=1,
76+
bits_per_sample=16, samples_signed=True, buffer_size=8192)
77+
audio.play(mixer)
78+
mixer.voice[0].level = 0.55
79+
mixer.voice[0].play(synth)
80+
81+
led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.3) # on board neopixel
82+
83+
# waveforms setup
84+
wave_sine = np.array(np.sin(np.linspace(0, 2*np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME,
85+
dtype=np.int16)
86+
wave_saw = np.linspace(VOLUME, -VOLUME, num=SAMPLE_SIZE, dtype=np.int16)
87+
wave_weird1 = np.array((198,2776,5441,8031,10454,12653,14609,16333,17824,19130,20260,21227,22043,
88+
22721,23269,23699,24019,24243,24385,24461,18630,-26956,-28048,-29175,-30249,
89+
-31227,-32073,-32631,-32359,-31817,-30941,-29663,-27900,-25596,-22591,
90+
-18834,-14291,-9016,-3212,2794,8624,13943,18544,22353,25408,27780,29553,
91+
30855,31751,32315,32611,32687,32593,32351,31983,31491,30871,30097,28895,
92+
-28240,-30489,-31343,-31975,-32431,-32697,-32767,-32615,-32217,-31525,
93+
-30489,-29035,-27090,-24519,-21237,-17178,-12339,-6829,-902,5081,10748,
94+
15805,20102,23615,26396,28510,30109,31245,31995,31955,31437,30729,29887,
95+
28943,27908,26784,25560,24077,22781,-22207,-22735,-22709,-22471,-22065,
96+
-21497,-20773,-19896,-18872,-17698,-16361,-14857,-13141,-11206,-9054,-6717,
97+
-4259,-1796,522,2548,4167,5339,6079,6445,6503,6319,5949,5449,4847,4183,
98+
3480,2756,2028,1304,590,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
99+
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-478,-1168,-1882,-2596,
100+
-3336,-4074,-4795,-5487,-6119,-6669,-7095,-7357,-7399,-7157,-6559,-5543,
101+
-4076,-2132,), dtype=np.int16)
102+
wave_noise = np.array([random.randint(-VOLUME, VOLUME) for i in range(SAMPLE_SIZE)], dtype=np.int16)
103+
104+
# map s range a1-a2 to b1-b2
105+
def map_range(s, a1, a2, b1, b2):
106+
return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
107+
108+
# mix between values a and b, works with numpy arrays too, t ranges 0-1
109+
def lerp(a, b, t):
110+
return (1-t)*a + t*b
111+
112+
waveform[:] = wave_saw
113+
wave_mix = 0.0
114+
lfo_rates = (0.1, 0.5, 0.8, 1.5, 3.0, 6.0, 7.0, 8.0)
115+
lfo_index = 0
116+
lfo1 = synthio.LFO(rate=(lfo_rates[lfo_index]), waveform=wave_sine) # rate is in Hz
117+
synth.lfos.append(lfo1)
118+
hold = False # state of note hold
119+
octaves = False
120+
121+
def light_button_pixels(button_number):
122+
pixels[pix_map[button_number]+1] = 0xFF0000
123+
pixels[pix_map[button_number]-1] = 0xFF0000
124+
pixels.show()
125+
126+
def reset_button_pixels(button_number):
127+
pixels[pix_map[button_number]+1] = 0x000000
128+
pixels[pix_map[button_number]-1] = 0x000000
129+
pixels.show()
130+
131+
def clamp(v, low, high):
132+
return min(max(v, low), high)
133+
134+
print("-Computer Perfection Synth-")
135+
136+
note = None
137+
mod_key = 0
138+
last_mod_button_event_time = 0
139+
waveset = 0
140+
141+
142+
while True:
143+
# watch for mod buttons to be pressed
144+
mod_button_event = mod_buttons.events.get()
145+
if mod_button_event:
146+
mod_key = mod_button_event.key_number
147+
if mod_button_event.pressed:
148+
if mod_key == 0: # SET switch
149+
last_mod_button_event_time = time.monotonic()
150+
151+
if mod_key == 1: # enable octaves
152+
octaves = True
153+
154+
if mod_button_event.released:
155+
if last_mod_button_event_time and mod_key == 0: # short press-release increase LFO rate
156+
lfo_index = clamp(lfo_index+1, 0, len(lfo_rates)-1)
157+
print(lfo_index)
158+
lfo_rate = lfo_rates[lfo_index]
159+
lfo1.rate = lfo_rate
160+
last_mod_button_event_time = 0
161+
if mod_key == 1: # disable octaves
162+
octaves = False
163+
# long press slows the LFO rate
164+
if last_mod_button_event_time != 0 and time.monotonic() - last_mod_button_event_time > 1.0:
165+
last_mod_button_event_time = 0
166+
lfo_index = clamp(lfo_index-1, 0, len(lfo_rates)-1)
167+
lfo_rate = lfo_rates[lfo_index]
168+
lfo1.rate = lfo_rate
169+
170+
# watch for note buttons to be pressed
171+
note_button_event = note_buttons.events.get()
172+
if note_button_event:
173+
i = note_button_event.key_number
174+
if note_button_event.pressed:
175+
if octaves:
176+
synth.press((note_list[i]+(octave*12), note_list[i]+(octave*12)-12))
177+
else:
178+
synth.press((note_list[i]+(octave*12),))
179+
light_button_pixels(i)
180+
if note_button_event.released:
181+
if not hold:
182+
reset_button_pixels(i)
183+
synth.release((note_list[i]+(octave*12), note_list[i]+(octave*12)-12))
184+
reset_button_pixels(i)
185+
186+
# watch for switches to be changed
187+
switch_event = switches.events.get()
188+
if switch_event:
189+
sw = switch_event.key_number
190+
if switch_event.pressed:
191+
if sw == 0: # MODE toggle right
192+
mixer.voice[0].level = 0.45
193+
# wave_mix = 0.5
194+
waveset = 0
195+
if sw == 1: # SKILL toggle center
196+
hold = True
197+
198+
if switch_event.released:
199+
if sw == 0: # MODE toggle center
200+
mixer.voice[0].level = 0.95
201+
waveset = 1
202+
if sw == 1: # SKILL toggle right or left
203+
hold = False
204+
for r in range(len(note_list)): # turn off all notes
205+
# if octaves:
206+
synth.release((note_list[r]+(octave*12), note_list[r]+(octave*12)-12))
207+
for h in range(len(pix_map)): # turn off held pixels
208+
reset_button_pixels(h)
209+
210+
lfo_val_for_lerp = map_range(lfo1.value, -1, 1, 0, 1)
211+
if waveset == 0:
212+
waveform[:] = lerp(wave_sine, wave_weird1, lfo_val_for_lerp)
213+
else:
214+
waveform[:] = lerp(wave_saw, wave_noise, lfo_val_for_lerp)

0 commit comments

Comments
 (0)