Skip to content

Commit

Permalink
Merge pull request #2686 from jedgarpark/faderwave
Browse files Browse the repository at this point in the history
first commit Faderwave synth code
  • Loading branch information
TheKitty authored Dec 6, 2023
2 parents eaf7ea2 + 82d99d2 commit d041b3a
Showing 1 changed file with 296 additions and 0 deletions.
296 changes: 296 additions & 0 deletions Faderwave_Synth/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
# SPDX-FileCopyrightText: Copyright (c) 2023 john park for Adafruit Industries
#
# SPDX-License-Identifier: MIT
''' Faderwave Synthesizer
use 16 faders to create the single cycle waveform
rotary encoder adjusts other synth parameters
audio output: line level over 3.5mm TRS
CV output via DAC '''

import board
import busio
import ulab.numpy as np
import rotaryio
import neopixel
from digitalio import DigitalInOut, Pull
import displayio
from adafruit_display_text import label
import terminalio
import synthio
import audiomixer
from adafruit_debouncer import Debouncer
import adafruit_ads7830.ads7830 as ADC
from adafruit_ads7830.analog_in import AnalogIn
import adafruit_displayio_ssd1306
import adafruit_ad569x
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff

displayio.release_displays()

ITSY_TYPE = 0 # Pick your ItsyBitsy: 0=M4, 1=RP2040

# neopixel setup for RP2040 only
if ITSY_TYPE == 1:
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.3)
pixel.fill(0x004444)

i2c = busio.I2C(board.SCL, board.SDA, frequency=1_000_000)

midi = adafruit_midi.MIDI(midi_in=usb_midi.ports[0], in_channel=0)

NUM_FADERS = 16
num_oscs = 2 # how many oscillators for each note
detune = 0.003 # how much to detune the oscillators
volume = 0.6 # mixer volume
lpf_freq = 12000 # user Low Pass Filter frequency setting
lpf_basef = 500 # filter lowest frequency
lpf_resonance = 0.1 # filter q

faders_pos = [0] * NUM_FADERS
last_faders_pos = [0] * NUM_FADERS

# Initialize ADS7830
adc_a = ADC.ADS7830(i2c, address=0x48) # default address 0x48
adc_b = ADC.ADS7830(i2c, address=0x4A) # A0 jumper 0x49, A1 0x4A

faders = [] # list for fader objects on first ADC
for fdr in range(8): # add first group to list
faders.append(AnalogIn(adc_a, fdr))
for fdr in range(8): # add second group
faders.append(AnalogIn(adc_b, fdr))

# Initialize AD5693R for CV out
dac = adafruit_ad569x.Adafruit_AD569x(i2c)
dac.gain = True
dac.value = faders[0].value # set dac out to the slider level

# Rotary encoder setup
ENC_A = board.D9
ENC_B = board.D10
ENC_SW = board.D7

button_in = DigitalInOut(ENC_SW) # defaults to input
button_in.pull = Pull.UP # turn on internal pull-up resistor
button = Debouncer(button_in)

encoder = rotaryio.IncrementalEncoder(ENC_A, ENC_B)
encoder_pos = encoder.position
last_encoder_pos = encoder.position

# display setup
OLED_RST = board.D13
OLED_DC = board.D12
OLED_CS = board.D11

spi = board.SPI()
display_bus = displayio.FourWire(spi, command=OLED_DC, chip_select=OLED_CS,
reset=OLED_RST, baudrate=30_000_000)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=64)

# Create display group
group = displayio.Group()
# Create background rectangle
# bg_rect = Rect(0, 0, display.width, display.height, fill=0x0)
# group.append(bg_rect)
# Set the font for the text label
font = terminalio.FONT

# Create text label
title = label.Label(font, x=2, y=4, text=("Faderwave Synthesizer"), color=0xffffff)
group.append(title)

title2 = label.Label(font, x=2, y=10, text=("---------------------"), color=0xffffff)
group.append(title2)

column_x = (20, 90)
row_y = (22, 34, 48, 60)

# Create menu selector
menu_sel = 0
menu_sel_txt = label.Label(font, text=("->"), color=0xffffff)
menu_sel_txt.x = column_x[0]-16
menu_sel_txt.y = row_y[menu_sel]
group.append(menu_sel_txt)

# Create detune text
det_txt_a = label.Label(font, text=("Detune....."), color=0xffffff)
det_txt_a.x = column_x[0]
det_txt_a.y = row_y[0]
group.append(det_txt_a)

det_txt_b = label.Label(font, text=(str(0.003)), color=0xffffff)
det_txt_b.x = column_x[1]
det_txt_b.y = row_y[0]
group.append(det_txt_b)

# Create number of oscs text
num_oscs_txt_a = label.Label(font, text=("Num Oscs..."), color=0xffffff)
num_oscs_txt_a.x = column_x[0]
num_oscs_txt_a.y = row_y[1]
group.append(num_oscs_txt_a)

num_oscs_txt_b = label.Label(font, text=(str(num_oscs)), color=0xffffff)
num_oscs_txt_b.x = column_x[1]
num_oscs_txt_b.y = row_y[1]
group.append(num_oscs_txt_b)

# Create volume text
vol_txt_a = label.Label(font, text=("Volume....."), color=0xffffff)
vol_txt_a.x = column_x[0]
vol_txt_a.y = row_y[2]
group.append(vol_txt_a)

vol_txt_b = label.Label(font, text=(str(volume)), color=0xffffff)
vol_txt_b.x = column_x[1]
vol_txt_b.y = row_y[2]
group.append(vol_txt_b)


# Create lpf frequency text
lpf_txt_a = label.Label(font, text=("LPF........"), color=0xffffff)
lpf_txt_a.x = column_x[0]
lpf_txt_a.y = row_y[3]
group.append(lpf_txt_a)

lpf_txt_b = label.Label(font, text=(str(lpf_freq)), color=0xffffff)
lpf_txt_b.x = column_x[1]
lpf_txt_b.y = row_y[3]
group.append(lpf_txt_b)


# Show the display group
display.root_group = group

# Synthio setup
if ITSY_TYPE == 0:
import audioio
audio = audioio.AudioOut(left_channel=board.A0, right_channel=board.A1) # M4 built-in DAC
if ITSY_TYPE == 1:
import audiopwmio
audio = audiopwmio.PWMAudioOut(board.A1)
# if using I2S amp:
# audio = audiobusio.I2SOut(bit_clock=board.MOSI, word_select=board.MISO, data=board.SCK)

mixer = audiomixer.Mixer(channel_count=2, sample_rate=44100, buffer_size=4096)
synth = synthio.Synthesizer(channel_count=2, sample_rate=44100)
audio.play(mixer)
mixer.voice[0].play(synth)
mixer.voice[0].level = 0.75

wave_user = np.array([0]*NUM_FADERS, dtype=np.int16)
amp_env = synthio.Envelope(attack_time=0.3, attack_level=1, sustain_level=0.65, release_time=0.3)

def faders_to_wave():
for j in range(NUM_FADERS):
wave_user[j] = int(map_range(faders_pos[j], 0, 255, -32768, 32767))

notes_pressed = {} # which notes being pressed. key=midi note, val=note object

def note_on(n):
voices = [] # holds our currently sounding voices ('Notes' in synthio speak)
fo = synthio.midi_to_hz(n)
lpf = synth.low_pass_filter(lpf_freq, lpf_resonance)

for k in range(num_oscs):
f = fo * (1 + k*detune)
voices.append(synthio.Note(frequency=f, filter=lpf, envelope=amp_env, waveform=wave_user))
synth.press(voices)
notes_pressed[n] = voices

def note_off(n):
note = notes_pressed.get(n, None)
if note:
synth.release(note)

# simple range mapper, like Arduino map()
def map_range(s, a1, a2, b1, b2):
return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))


while True:
# get midi messages
msg = midi.receive()
if isinstance(msg, NoteOn) and msg.velocity != 0:
note_on(msg.note)
elif isinstance(msg, NoteOff) or isinstance(msg, NoteOn) and msg.velocity == 0:
note_off(msg.note)

# check faders
for i in range(len(faders)):
faders_pos[i] = faders[i].value//256
if faders_pos[i] is not last_faders_pos[i]:
faders_to_wave()
last_faders_pos[i] = faders_pos[i]

# send out a DAC value based on fader 0
if i == 0:
dac.value = faders[0].value

# check encoder button
button.update()
if button.fell:
menu_sel = (menu_sel+1) % 4
menu_sel_txt.y = row_y[menu_sel]

# check encoder
encoder_pos = encoder.position
if encoder_pos > last_encoder_pos:
delta = encoder_pos - last_encoder_pos
if menu_sel == 0:
detune = detune + (delta * 0.001)
detune = min(max(detune, -0.030), 0.030)
formatted_detune = str("{:.3f}".format(detune))
det_txt_b.text = formatted_detune

elif menu_sel == 1:
num_oscs = num_oscs + delta
num_oscs = min(max(num_oscs, 1), 8)
formatted_num_oscs = str(num_oscs)
num_oscs_txt_b.text = formatted_num_oscs

elif menu_sel == 2:
volume = volume + (delta * 0.01)
volume = min(max(volume, 0.00), 1.00)
mixer.voice[0].level = volume
formatted_volume = str("{:.2f}".format(volume))
vol_txt_b.text = formatted_volume

elif menu_sel == 3:
lpf_freq = lpf_freq + (delta * 1000)
lpf_freq = min(max(lpf_freq, 1000), 20_000)
formatted_lpf = str(lpf_freq)
lpf_txt_b.text = formatted_lpf

last_encoder_pos = encoder.position

if encoder_pos < last_encoder_pos:
delta = last_encoder_pos - encoder_pos
if menu_sel == 0:
detune = detune - (delta * 0.001)
detune = min(max(detune, -0.030), 0.030)
formatted_detune = str("{:.3f}".format(detune))
det_txt_b.text = formatted_detune

elif menu_sel == 1:
num_oscs = num_oscs - delta
num_oscs = min(max(num_oscs, 1), 8)
formatted_num_oscs = str(num_oscs)
num_oscs_txt_b.text = formatted_num_oscs

elif menu_sel == 2:
volume = volume - (delta * 0.01)
volume = min(max(volume, 0.00), 1.00)
mixer.voice[0].level = volume
formatted_volume = str("{:.2f}".format(volume))
vol_txt_b.text = formatted_volume

elif menu_sel == 3:
lpf_freq = lpf_freq - (delta * 1000)
lpf_freq = min(max(lpf_freq, 1000), 20_000)
formatted_lpf = str(lpf_freq)
lpf_txt_b.text = formatted_lpf

last_encoder_pos = encoder.position

0 comments on commit d041b3a

Please sign in to comment.