Skip to content

Instantly share code, notes, and snippets.

@jedgarpark
Created December 5, 2023 21:52
Show Gist options
  • Save jedgarpark/20442b0c367665884d9df0f634caab02 to your computer and use it in GitHub Desktop.
Save jedgarpark/20442b0c367665884d9df0f634caab02 to your computer and use it in GitHub Desktop.
# Faderwave v0.2 PCB
# john park 2023
import time
import board
import busio
import ulab.numpy as np
import rotaryio
import neopixel
from digitalio import DigitalInOut, Pull
import displayio
from adafruit_display_shapes.rect import Rect
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()
midi = adafruit_midi.MIDI(midi_in=usb_midi.ports[0], in_channel=0)
debug_notes = False
ITSY_TYPE = 0 # 0=M4, 1=RP2040
# neopixel setup
if ITSY_TYPE is 1:
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.3)
pixel.fill(0x004444)
i2c = busio.I2C(board.SCL, board.SDA, frequency=1_000_000)
NUM_FADERS = 16
num_voices = 2 # how many voices for each note
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 f in range(8): # add first group to list
faders.append(AnalogIn(adc_a, f))
for f in range(8): # add second group
faders.append(AnalogIn(adc_b, f))
# 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=1000000)
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 to display the number
text = label.Label(font, text=str(faders[0].value//256), color=0xffffff)
text.x = display.width // 12
text.y = display.height // 2
text.anchor_point = (0.5, 0.5)
text.scale = 2
group.append(text)
# Show the display group
display.show(group)
# Synthio setup
if ITSY_TYPE is 0:
import audioio
audio = audioio.AudioOut(left_channel=board.A0, right_channel=board.A1) # M4 built-in DAC
if ITSY_TYPE is 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 i in range(NUM_FADERS):
wave_user[i] = int(map_range(faders_pos[i], 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_f = fo * 8 # a kind of key tracking
lpf = synth.low_pass_filter(lpf_f, lpf_resonance)
voices.clear() # delete any old voices
for i in range(num_voices):
f = fo * (1 + i*0.007)
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):
# print(" 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:
msg = midi.receive()
if isinstance(msg, NoteOn) and msg.velocity != 0:
# print("noteOn: ", msg.note)
# text.text=(str(msg.note))
note_on(msg.note)
elif isinstance(msg, NoteOff) or isinstance(msg, NoteOn) and msg.velocity == 0:
# print("noteOff:", msg.note)
note_off(msg.note)
button.update()
if button.fell:
note_on(45)
if button.rose:
note_off(45)
encoder_pos = encoder.position
if encoder_pos is not last_encoder_pos:
# print("encoder:", encoder.position) # starts at zero, goes neg or pos
last_encoder_pos = encoder.position
#
for i in range(len(faders)):
# print(faders[0].value)
faders_pos[i] = faders[i].value//256
if faders_pos[i] is not last_faders_pos[i]:
faders_to_wave()
# text.text=(str(faders[i].value))
last_faders_pos[i] = faders_pos[i]
# if you want to send out a DAC value as a test:
# dac.value = faders[0].value
time.sleep(0.001)
@todbot
Copy link

todbot commented Dec 8, 2023

I forget precedence on and vs or in Python, so to be explicit instead of doing

    elif isinstance(msg, NoteOff) or isinstance(msg, NoteOn) and msg.velocity == 0:

do this:

    elif isinstance(msg, NoteOff) or (isinstance(msg, NoteOn) and msg.velocity == 0):

I think I've made this mistake in my own code.

@todbot
Copy link

todbot commented Dec 8, 2023

To guard against the same note being played twice without a noteoff, you could do something like this in your note_on():

def note_on(n):
    # ...
    note_off(n)
    notes_pressed[n] = voices

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment