-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathcode.py
110 lines (90 loc) · 3.53 KB
/
code.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# eighties_arp_synthio.py -- playing with arpeggios, testing my Arpy class
# 20 Jun 2023 - @todbot / Tod Kurt
#
# - arp arp arp
# - two pots, two buttons, no keys!
# - no musical knowledge needed, just plug it, start twisting knobs
# - video demo: https://www.youtube.com/watch?v=noj92Ae0IQI
#
# UI is:
# knobA - adjusts root note (QTPy A0)
# knobB - adjusts BPM (QTPY A1)
# buttonA - changes arp pattern (QTPy SDA)
# buttonB - changes num iters up for pattern (QTPy SCL)
#
# Circuit:
# - See: "eighties_arp_bb.png" wiring
# - QT Py RP2040 or similar
# - QTPy RX pin is audio out, going through RC filter (1k + 100nF) to TRS jack
#
import time, random
import board, analogio, keypad
import audiopwmio, audiomixer, synthio
import ulab.numpy as np
import neopixel, rainbowio # circup install neopixel
from arpy import Arpy
num_voices = 3 # how many voices for each note
lpf_basef = 2500 # filter lowest frequency
lpf_resonance = 1.5 # filter q
knobA = analogio.AnalogIn(board.A0)
knobB = analogio.AnalogIn(board.A1)
keys = keypad.Keys( (board.SDA, board.SCL), value_when_pressed=False )
led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.1)
audio = audiopwmio.PWMAudioOut(board.RX) # RX pin on QTPY RP2040
mixer = audiomixer.Mixer(channel_count=1, sample_rate=28000, buffer_size=2048)
synth = synthio.Synthesizer(channel_count=1, sample_rate=28000)
audio.play(mixer)
mixer.voice[0].play(synth)
mixer.voice[0].level = 0.8
# our oscillator waveform, a 512 sample downward saw wave going from +/-30k
wave_saw = np.linspace(30000, -30000, num=512, dtype=np.int16) # max is +/-32k but gives us headroom
amp_env = synthio.Envelope(attack_level=1, sustain_level=1, release_time=0.5)
voices=[] # holds our currently sounding voices ('Notes' in synthio speak)
# called by arpy to turn on a note
def note_on(n):
print(" note on ", n )
led.fill(rainbowio.colorwheel( n % 12 * 20 ))
fo = synthio.midi_to_hz(n)
voices.clear() # delete any old voices
for i in range(num_voices):
f = fo * (1 + i*0.007)
lpf_f = fo * 8 # a kind of key tracking
lpf = synth.low_pass_filter( lpf_f, lpf_resonance )
voices.append( synthio.Note( frequency=f, filter=lpf, envelope=amp_env, waveform=wave_saw) )
synth.press(voices)
# called by arpy to turn off a note
def note_off(n):
print(" note off", n)
led.fill(0)
synth.release(voices)
# simple range mapper, like Arduino map()
def map_range(s, a1, a2, b1, b2): return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
arpy = Arpy()
arpy.note_on_handler = note_on
arpy.note_off_handler = note_off
arpy.on()
arpy.root_note = 37
arpy.set_arp('suspended4th')
arpy.set_bpm( bpm=110, steps_per_beat=4 ) # 110 bpm 16th notes
arpy.set_transpose(distance=12, steps=0)
knobfilter = 0.75
knobAval = knobA.value
knobBval = knobB.value
while True:
key = keys.events.get()
if key and key.pressed:
if key.key_number==0: # left button changes arp played
arpy.next_arp()
print(arpy.arp_name())
if key.key_number==1: # right button changes arp up iterations
steps = (arpy.trans_steps + 1) % 3;
print("steps",steps)
arpy.set_transpose(steps=steps)
# filter noisy adc
knobAval = knobAval * knobfilter + (1-knobfilter) * knobA.value
knobBval = knobBval * knobfilter + (1-knobfilter) * knobB.value
# map knobA to root note
arpy.root_note = int(map_range( knobAval, 0,65535, 24, 72) )
# map knobB to bpm
arpy.set_bpm( map_range(knobBval, 0,65535, 40, 180 ) )
arpy.update()