first commit computer perfection synth code

This commit is contained in:
John Park 2023-05-31 09:52:05 -07:00
parent e5a0806a9b
commit f31a8a93a5

214
Computer_Perfection_Synth/code.py Executable file
View file

@ -0,0 +1,214 @@
# SPDX-FileCopyrightText: 2023 John Park, Jeff Epler, and Tod Kurt for Adafruit Industries
# SPDX-License-Identifier: MIT
# Computer Perfection Synth
# * 10 numbered buttons play notes
# * SET button to increase LFO rate, long press to decrease LFO rate
# * SCORE button to add lower octave
# * MODE switch changes LFO depth?
# * SKILL switch toggles sustain
# * GAME switch must stay in position 1 or it messes with the other switches
import time
import random
import board
import audiobusio
import audiomixer
import synthio
import ulab.numpy as np
import neopixel
import keypad
# NeoPixel setup
num_pixels = 34
pixels = neopixel.NeoPixel(board.D11, num_pixels, brightness=0.7, auto_write=False)
pixels.fill(0x0)
pixels.show()
time.sleep(0.25)
pix_map = [26, 23, 19, 16, 13, 10, 7, 4, 32, 29] # map the LEDs to the numbered panel sections 0-9
for p in range(len(pix_map)):
pixels[pix_map[p]] = 0xff0000
pixels.show()
time.sleep(0.1)
note_buttons = keypad.Keys(
(board.D0, board.D1, board.D2, board.D3, board.D4,
board.D5, board.D6, board.D7, board.D8, board.A5),
value_when_pressed=False,
pull=True
)
switches = keypad.Keys(
(board.A1, board.A0),
value_when_pressed=False,
pull=True
)
octave = 3 # octave multiplier
note_list = (0, 4, 6, 7, 9, 12, 16, 18, 19, 21) # Lydian scale
mod_buttons = keypad.Keys(
(board.A4, board.A3), # SET and SCORE buttons
value_when_pressed=False,
pull=True
)
SAMPLE_RATE = 48000 # clicks @ 36kHz & 48kHz on rp2040
SAMPLE_SIZE = 200
VOLUME = 12000
# Metro M7 pins for the I2S amp:
lck_pin, bck_pin, dat_pin = board.D9, board.D10, board.D12
# synth engine setup
waveform = np.zeros(SAMPLE_SIZE, dtype=np.int16) # intially all zeros (silence)
amp_env = synthio.Envelope( # default (0.1, 0.05, 0.2, 1, 0.8)
attack_time=1.0,
decay_time=0.05,
release_time=3.0,
attack_level=1.0,
sustain_level=0.8
)
synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE, waveform=waveform, envelope=amp_env)
audio = audiobusio.I2SOut(bit_clock=bck_pin, word_select=lck_pin, data=dat_pin)
mixer = audiomixer.Mixer(voice_count=1, sample_rate=SAMPLE_RATE, channel_count=1,
bits_per_sample=16, samples_signed=True, buffer_size=8192)
audio.play(mixer)
mixer.voice[0].level = 0.55
mixer.voice[0].play(synth)
led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.3) # on board neopixel
# waveforms setup
wave_sine = np.array(np.sin(np.linspace(0, 2*np.pi, SAMPLE_SIZE, endpoint=False)) * VOLUME,
dtype=np.int16)
wave_saw = np.linspace(VOLUME, -VOLUME, num=SAMPLE_SIZE, dtype=np.int16)
wave_weird1 = np.array((198,2776,5441,8031,10454,12653,14609,16333,17824,19130,20260,21227,22043,
22721,23269,23699,24019,24243,24385,24461,18630,-26956,-28048,-29175,-30249,
-31227,-32073,-32631,-32359,-31817,-30941,-29663,-27900,-25596,-22591,
-18834,-14291,-9016,-3212,2794,8624,13943,18544,22353,25408,27780,29553,
30855,31751,32315,32611,32687,32593,32351,31983,31491,30871,30097,28895,
-28240,-30489,-31343,-31975,-32431,-32697,-32767,-32615,-32217,-31525,
-30489,-29035,-27090,-24519,-21237,-17178,-12339,-6829,-902,5081,10748,
15805,20102,23615,26396,28510,30109,31245,31995,31955,31437,30729,29887,
28943,27908,26784,25560,24077,22781,-22207,-22735,-22709,-22471,-22065,
-21497,-20773,-19896,-18872,-17698,-16361,-14857,-13141,-11206,-9054,-6717,
-4259,-1796,522,2548,4167,5339,6079,6445,6503,6319,5949,5449,4847,4183,
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,
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,
-3336,-4074,-4795,-5487,-6119,-6669,-7095,-7357,-7399,-7157,-6559,-5543,
-4076,-2132,), dtype=np.int16)
wave_noise = np.array([random.randint(-VOLUME, VOLUME) for i in range(SAMPLE_SIZE)], dtype=np.int16)
# map s range a1-a2 to b1-b2
def map_range(s, a1, a2, b1, b2):
return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
# mix between values a and b, works with numpy arrays too, t ranges 0-1
def lerp(a, b, t):
return (1-t)*a + t*b
waveform[:] = wave_saw
wave_mix = 0.0
lfo_rates = (0.1, 0.5, 0.8, 1.5, 3.0, 6.0, 7.0, 8.0)
lfo_index = 0
lfo1 = synthio.LFO(rate=(lfo_rates[lfo_index]), waveform=wave_sine) # rate is in Hz
synth.lfos.append(lfo1)
hold = False # state of note hold
octaves = False
def light_button_pixels(button_number):
pixels[pix_map[button_number]+1] = 0xFF0000
pixels[pix_map[button_number]-1] = 0xFF0000
pixels.show()
def reset_button_pixels(button_number):
pixels[pix_map[button_number]+1] = 0x000000
pixels[pix_map[button_number]-1] = 0x000000
pixels.show()
def clamp(v, low, high):
return min(max(v, low), high)
print("-Computer Perfection Synth-")
note = None
mod_key = 0
last_mod_button_event_time = 0
waveset = 0
while True:
# watch for mod buttons to be pressed
mod_button_event = mod_buttons.events.get()
if mod_button_event:
mod_key = mod_button_event.key_number
if mod_button_event.pressed:
if mod_key == 0: # SET switch
last_mod_button_event_time = time.monotonic()
if mod_key == 1: # enable octaves
octaves = True
if mod_button_event.released:
if last_mod_button_event_time and mod_key == 0: # short press-release increase LFO rate
lfo_index = clamp(lfo_index+1, 0, len(lfo_rates)-1)
print(lfo_index)
lfo_rate = lfo_rates[lfo_index]
lfo1.rate = lfo_rate
last_mod_button_event_time = 0
if mod_key == 1: # disable octaves
octaves = False
# long press slows the LFO rate
if last_mod_button_event_time != 0 and time.monotonic() - last_mod_button_event_time > 1.0:
last_mod_button_event_time = 0
lfo_index = clamp(lfo_index-1, 0, len(lfo_rates)-1)
lfo_rate = lfo_rates[lfo_index]
lfo1.rate = lfo_rate
# watch for note buttons to be pressed
note_button_event = note_buttons.events.get()
if note_button_event:
i = note_button_event.key_number
if note_button_event.pressed:
if octaves:
synth.press((note_list[i]+(octave*12), note_list[i]+(octave*12)-12))
else:
synth.press((note_list[i]+(octave*12),))
light_button_pixels(i)
if note_button_event.released:
if not hold:
reset_button_pixels(i)
synth.release((note_list[i]+(octave*12), note_list[i]+(octave*12)-12))
reset_button_pixels(i)
# watch for switches to be changed
switch_event = switches.events.get()
if switch_event:
sw = switch_event.key_number
if switch_event.pressed:
if sw == 0: # MODE toggle right
mixer.voice[0].level = 0.45
# wave_mix = 0.5
waveset = 0
if sw == 1: # SKILL toggle center
hold = True
if switch_event.released:
if sw == 0: # MODE toggle center
mixer.voice[0].level = 0.95
waveset = 1
if sw == 1: # SKILL toggle right or left
hold = False
for r in range(len(note_list)): # turn off all notes
# if octaves:
synth.release((note_list[r]+(octave*12), note_list[r]+(octave*12)-12))
for h in range(len(pix_map)): # turn off held pixels
reset_button_pixels(h)
lfo_val_for_lerp = map_range(lfo1.value, -1, 1, 0, 1)
if waveset == 0:
waveform[:] = lerp(wave_sine, wave_weird1, lfo_val_for_lerp)
else:
waveform[:] = lerp(wave_saw, wave_noise, lfo_val_for_lerp)