Skip to content

Commit d041b3a

Browse files
authored
Merge pull request #2686 from jedgarpark/faderwave
first commit Faderwave synth code
2 parents eaf7ea2 + 82d99d2 commit d041b3a

File tree

1 file changed

+296
-0
lines changed

1 file changed

+296
-0
lines changed

Faderwave_Synth/code.py

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2023 john park for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
''' Faderwave Synthesizer
5+
use 16 faders to create the single cycle waveform
6+
rotary encoder adjusts other synth parameters
7+
audio output: line level over 3.5mm TRS
8+
CV output via DAC '''
9+
10+
import board
11+
import busio
12+
import ulab.numpy as np
13+
import rotaryio
14+
import neopixel
15+
from digitalio import DigitalInOut, Pull
16+
import displayio
17+
from adafruit_display_text import label
18+
import terminalio
19+
import synthio
20+
import audiomixer
21+
from adafruit_debouncer import Debouncer
22+
import adafruit_ads7830.ads7830 as ADC
23+
from adafruit_ads7830.analog_in import AnalogIn
24+
import adafruit_displayio_ssd1306
25+
import adafruit_ad569x
26+
import usb_midi
27+
import adafruit_midi
28+
from adafruit_midi.note_on import NoteOn
29+
from adafruit_midi.note_off import NoteOff
30+
31+
displayio.release_displays()
32+
33+
ITSY_TYPE = 0 # Pick your ItsyBitsy: 0=M4, 1=RP2040
34+
35+
# neopixel setup for RP2040 only
36+
if ITSY_TYPE == 1:
37+
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.3)
38+
pixel.fill(0x004444)
39+
40+
i2c = busio.I2C(board.SCL, board.SDA, frequency=1_000_000)
41+
42+
midi = adafruit_midi.MIDI(midi_in=usb_midi.ports[0], in_channel=0)
43+
44+
NUM_FADERS = 16
45+
num_oscs = 2 # how many oscillators for each note
46+
detune = 0.003 # how much to detune the oscillators
47+
volume = 0.6 # mixer volume
48+
lpf_freq = 12000 # user Low Pass Filter frequency setting
49+
lpf_basef = 500 # filter lowest frequency
50+
lpf_resonance = 0.1 # filter q
51+
52+
faders_pos = [0] * NUM_FADERS
53+
last_faders_pos = [0] * NUM_FADERS
54+
55+
# Initialize ADS7830
56+
adc_a = ADC.ADS7830(i2c, address=0x48) # default address 0x48
57+
adc_b = ADC.ADS7830(i2c, address=0x4A) # A0 jumper 0x49, A1 0x4A
58+
59+
faders = [] # list for fader objects on first ADC
60+
for fdr in range(8): # add first group to list
61+
faders.append(AnalogIn(adc_a, fdr))
62+
for fdr in range(8): # add second group
63+
faders.append(AnalogIn(adc_b, fdr))
64+
65+
# Initialize AD5693R for CV out
66+
dac = adafruit_ad569x.Adafruit_AD569x(i2c)
67+
dac.gain = True
68+
dac.value = faders[0].value # set dac out to the slider level
69+
70+
# Rotary encoder setup
71+
ENC_A = board.D9
72+
ENC_B = board.D10
73+
ENC_SW = board.D7
74+
75+
button_in = DigitalInOut(ENC_SW) # defaults to input
76+
button_in.pull = Pull.UP # turn on internal pull-up resistor
77+
button = Debouncer(button_in)
78+
79+
encoder = rotaryio.IncrementalEncoder(ENC_A, ENC_B)
80+
encoder_pos = encoder.position
81+
last_encoder_pos = encoder.position
82+
83+
# display setup
84+
OLED_RST = board.D13
85+
OLED_DC = board.D12
86+
OLED_CS = board.D11
87+
88+
spi = board.SPI()
89+
display_bus = displayio.FourWire(spi, command=OLED_DC, chip_select=OLED_CS,
90+
reset=OLED_RST, baudrate=30_000_000)
91+
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=64)
92+
93+
# Create display group
94+
group = displayio.Group()
95+
# Create background rectangle
96+
# bg_rect = Rect(0, 0, display.width, display.height, fill=0x0)
97+
# group.append(bg_rect)
98+
# Set the font for the text label
99+
font = terminalio.FONT
100+
101+
# Create text label
102+
title = label.Label(font, x=2, y=4, text=("Faderwave Synthesizer"), color=0xffffff)
103+
group.append(title)
104+
105+
title2 = label.Label(font, x=2, y=10, text=("---------------------"), color=0xffffff)
106+
group.append(title2)
107+
108+
column_x = (20, 90)
109+
row_y = (22, 34, 48, 60)
110+
111+
# Create menu selector
112+
menu_sel = 0
113+
menu_sel_txt = label.Label(font, text=("->"), color=0xffffff)
114+
menu_sel_txt.x = column_x[0]-16
115+
menu_sel_txt.y = row_y[menu_sel]
116+
group.append(menu_sel_txt)
117+
118+
# Create detune text
119+
det_txt_a = label.Label(font, text=("Detune....."), color=0xffffff)
120+
det_txt_a.x = column_x[0]
121+
det_txt_a.y = row_y[0]
122+
group.append(det_txt_a)
123+
124+
det_txt_b = label.Label(font, text=(str(0.003)), color=0xffffff)
125+
det_txt_b.x = column_x[1]
126+
det_txt_b.y = row_y[0]
127+
group.append(det_txt_b)
128+
129+
# Create number of oscs text
130+
num_oscs_txt_a = label.Label(font, text=("Num Oscs..."), color=0xffffff)
131+
num_oscs_txt_a.x = column_x[0]
132+
num_oscs_txt_a.y = row_y[1]
133+
group.append(num_oscs_txt_a)
134+
135+
num_oscs_txt_b = label.Label(font, text=(str(num_oscs)), color=0xffffff)
136+
num_oscs_txt_b.x = column_x[1]
137+
num_oscs_txt_b.y = row_y[1]
138+
group.append(num_oscs_txt_b)
139+
140+
# Create volume text
141+
vol_txt_a = label.Label(font, text=("Volume....."), color=0xffffff)
142+
vol_txt_a.x = column_x[0]
143+
vol_txt_a.y = row_y[2]
144+
group.append(vol_txt_a)
145+
146+
vol_txt_b = label.Label(font, text=(str(volume)), color=0xffffff)
147+
vol_txt_b.x = column_x[1]
148+
vol_txt_b.y = row_y[2]
149+
group.append(vol_txt_b)
150+
151+
152+
# Create lpf frequency text
153+
lpf_txt_a = label.Label(font, text=("LPF........"), color=0xffffff)
154+
lpf_txt_a.x = column_x[0]
155+
lpf_txt_a.y = row_y[3]
156+
group.append(lpf_txt_a)
157+
158+
lpf_txt_b = label.Label(font, text=(str(lpf_freq)), color=0xffffff)
159+
lpf_txt_b.x = column_x[1]
160+
lpf_txt_b.y = row_y[3]
161+
group.append(lpf_txt_b)
162+
163+
164+
# Show the display group
165+
display.root_group = group
166+
167+
# Synthio setup
168+
if ITSY_TYPE == 0:
169+
import audioio
170+
audio = audioio.AudioOut(left_channel=board.A0, right_channel=board.A1) # M4 built-in DAC
171+
if ITSY_TYPE == 1:
172+
import audiopwmio
173+
audio = audiopwmio.PWMAudioOut(board.A1)
174+
# if using I2S amp:
175+
# audio = audiobusio.I2SOut(bit_clock=board.MOSI, word_select=board.MISO, data=board.SCK)
176+
177+
mixer = audiomixer.Mixer(channel_count=2, sample_rate=44100, buffer_size=4096)
178+
synth = synthio.Synthesizer(channel_count=2, sample_rate=44100)
179+
audio.play(mixer)
180+
mixer.voice[0].play(synth)
181+
mixer.voice[0].level = 0.75
182+
183+
wave_user = np.array([0]*NUM_FADERS, dtype=np.int16)
184+
amp_env = synthio.Envelope(attack_time=0.3, attack_level=1, sustain_level=0.65, release_time=0.3)
185+
186+
def faders_to_wave():
187+
for j in range(NUM_FADERS):
188+
wave_user[j] = int(map_range(faders_pos[j], 0, 255, -32768, 32767))
189+
190+
notes_pressed = {} # which notes being pressed. key=midi note, val=note object
191+
192+
def note_on(n):
193+
voices = [] # holds our currently sounding voices ('Notes' in synthio speak)
194+
fo = synthio.midi_to_hz(n)
195+
lpf = synth.low_pass_filter(lpf_freq, lpf_resonance)
196+
197+
for k in range(num_oscs):
198+
f = fo * (1 + k*detune)
199+
voices.append(synthio.Note(frequency=f, filter=lpf, envelope=amp_env, waveform=wave_user))
200+
synth.press(voices)
201+
notes_pressed[n] = voices
202+
203+
def note_off(n):
204+
note = notes_pressed.get(n, None)
205+
if note:
206+
synth.release(note)
207+
208+
# simple range mapper, like Arduino map()
209+
def map_range(s, a1, a2, b1, b2):
210+
return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
211+
212+
213+
while True:
214+
# get midi messages
215+
msg = midi.receive()
216+
if isinstance(msg, NoteOn) and msg.velocity != 0:
217+
note_on(msg.note)
218+
elif isinstance(msg, NoteOff) or isinstance(msg, NoteOn) and msg.velocity == 0:
219+
note_off(msg.note)
220+
221+
# check faders
222+
for i in range(len(faders)):
223+
faders_pos[i] = faders[i].value//256
224+
if faders_pos[i] is not last_faders_pos[i]:
225+
faders_to_wave()
226+
last_faders_pos[i] = faders_pos[i]
227+
228+
# send out a DAC value based on fader 0
229+
if i == 0:
230+
dac.value = faders[0].value
231+
232+
# check encoder button
233+
button.update()
234+
if button.fell:
235+
menu_sel = (menu_sel+1) % 4
236+
menu_sel_txt.y = row_y[menu_sel]
237+
238+
# check encoder
239+
encoder_pos = encoder.position
240+
if encoder_pos > last_encoder_pos:
241+
delta = encoder_pos - last_encoder_pos
242+
if menu_sel == 0:
243+
detune = detune + (delta * 0.001)
244+
detune = min(max(detune, -0.030), 0.030)
245+
formatted_detune = str("{:.3f}".format(detune))
246+
det_txt_b.text = formatted_detune
247+
248+
elif menu_sel == 1:
249+
num_oscs = num_oscs + delta
250+
num_oscs = min(max(num_oscs, 1), 8)
251+
formatted_num_oscs = str(num_oscs)
252+
num_oscs_txt_b.text = formatted_num_oscs
253+
254+
elif menu_sel == 2:
255+
volume = volume + (delta * 0.01)
256+
volume = min(max(volume, 0.00), 1.00)
257+
mixer.voice[0].level = volume
258+
formatted_volume = str("{:.2f}".format(volume))
259+
vol_txt_b.text = formatted_volume
260+
261+
elif menu_sel == 3:
262+
lpf_freq = lpf_freq + (delta * 1000)
263+
lpf_freq = min(max(lpf_freq, 1000), 20_000)
264+
formatted_lpf = str(lpf_freq)
265+
lpf_txt_b.text = formatted_lpf
266+
267+
last_encoder_pos = encoder.position
268+
269+
if encoder_pos < last_encoder_pos:
270+
delta = last_encoder_pos - encoder_pos
271+
if menu_sel == 0:
272+
detune = detune - (delta * 0.001)
273+
detune = min(max(detune, -0.030), 0.030)
274+
formatted_detune = str("{:.3f}".format(detune))
275+
det_txt_b.text = formatted_detune
276+
277+
elif menu_sel == 1:
278+
num_oscs = num_oscs - delta
279+
num_oscs = min(max(num_oscs, 1), 8)
280+
formatted_num_oscs = str(num_oscs)
281+
num_oscs_txt_b.text = formatted_num_oscs
282+
283+
elif menu_sel == 2:
284+
volume = volume - (delta * 0.01)
285+
volume = min(max(volume, 0.00), 1.00)
286+
mixer.voice[0].level = volume
287+
formatted_volume = str("{:.2f}".format(volume))
288+
vol_txt_b.text = formatted_volume
289+
290+
elif menu_sel == 3:
291+
lpf_freq = lpf_freq - (delta * 1000)
292+
lpf_freq = min(max(lpf_freq, 1000), 20_000)
293+
formatted_lpf = str(lpf_freq)
294+
lpf_txt_b.text = formatted_lpf
295+
296+
last_encoder_pos = encoder.position

0 commit comments

Comments
 (0)