Skip to content

Commit 18bab8e

Browse files
committed
Simple percussion program
1 parent de6342a commit 18bab8e

20 files changed

+343
-0
lines changed

circuitpython/perc/code.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Cooper Dalrymple
2+
# SPDX-License-Identifier: MIT
3+
"""
4+
`code.py`
5+
================================================================================
6+
7+
This is the main code.py for a percussive sampler
8+
9+
29 July 2025 - @relic_se / Cooper Dalrymple
10+
"""
11+
12+
import hardware
13+
import asyncio
14+
import audiocore
15+
import os
16+
import audiomixer
17+
18+
# Settings
19+
20+
## Velocity is 0->127
21+
HARD = 100
22+
SOFT = 0 # Adjust to act as gate
23+
24+
SAMPLES = {
25+
36: {
26+
"samples": {
27+
HARD: "kick_hard",
28+
SOFT: "kick_soft"
29+
},
30+
"level": 0.25
31+
},
32+
38: {
33+
"samples": {
34+
HARD: "snare_hard",
35+
SOFT: "snare_soft"
36+
},
37+
"level": 0.25
38+
},
39+
41: {
40+
"samples": {
41+
HARD: "tom_lo_hard",
42+
SOFT: "tom_lo_soft"
43+
},
44+
"level": 0.25
45+
},
46+
42: {
47+
"samples": {
48+
SOFT: "hihat_closed"
49+
},
50+
"level": 0.25
51+
},
52+
43: {
53+
"samples": {
54+
HARD: "tom_mid_hard",
55+
SOFT: "tom_mid_soft"
56+
},
57+
"level": 0.25
58+
},
59+
44: {
60+
"samples": {
61+
SOFT: "hihat_pedal"
62+
},
63+
"level": 0.25
64+
},
65+
45: {
66+
"samples": {
67+
HARD: "tom_hi_hard",
68+
SOFT: "tom_hi_soft"
69+
},
70+
"level": 0.25
71+
},
72+
46: {
73+
"samples": {
74+
SOFT: "hihat_open"
75+
},
76+
"level": 0.25
77+
},
78+
49: {
79+
"samples": {
80+
HARD: "crash_hard",
81+
SOFT: "crash_soft"
82+
},
83+
"level": 0.25
84+
},
85+
51: {
86+
"samples": {
87+
HARD: "ride_hard",
88+
SOFT: "ride_soft"
89+
},
90+
"level": 0.25
91+
}
92+
}
93+
94+
# Load Samples
95+
96+
samples = dict()
97+
for note in SAMPLES.values():
98+
for name in note["samples"].values():
99+
if name not in samples and os.stat(path := "/samples/{:s}.wav".format(name)):
100+
samples[name] = audiocore.WaveFile(path)
101+
102+
mixer = audiomixer.Mixer(
103+
sample_rate=hardware.SAMPLE_RATE,
104+
channel_count=hardware.CHANNEL_COUNT,
105+
buffer_size=hardware.BUFFER_SIZE,
106+
voice_count=len(samples),
107+
)
108+
109+
hardware.audio.play(mixer)
110+
for i, wav in enumerate(samples.values()):
111+
mixer.voice[i].play(wav)
112+
mixer.voice[i].level = 0.0
113+
114+
def get_sample(notenum:int, velocity:int = 127) -> tuple[str, float]:
115+
if notenum in SAMPLES:
116+
for vel, name in SAMPLES[notenum]["samples"].items():
117+
if velocity >= vel:
118+
return (name, SAMPLES[notenum]["level"])
119+
return None
120+
121+
def play_sample(name:str, level:float = 0.25) -> None:
122+
global samples
123+
if name in samples:
124+
i = 0
125+
for val, wav in samples.items():
126+
if val == name:
127+
mixer.voice[i].level = level
128+
mixer.voice[i].play(wav)
129+
break
130+
i += 1
131+
132+
def stop_sample(name:str) -> None:
133+
global samples
134+
if name in samples:
135+
for i, val in enumerate(samples.keys()):
136+
if val == name:
137+
mixer.voice[i].stop()
138+
break
139+
140+
# Keyboard
141+
142+
async def touch_handler():
143+
while True:
144+
for event in hardware.check_touch():
145+
if (notenum := event.key_number + 36) in SAMPLES:
146+
name, level = get_sample(notenum)
147+
if event.pressed:
148+
play_sample(name, level)
149+
elif event.released:
150+
pass
151+
# stop_sample(name)
152+
await asyncio.sleep(0.005)
153+
154+
# MIDI
155+
156+
from adafruit_midi.note_off import NoteOff
157+
from adafruit_midi.note_on import NoteOn
158+
159+
async def midi_handler():
160+
while True:
161+
for midi in (hardware.midi_uart, hardware.midi_usb):
162+
msg = midi.receive()
163+
if isinstance(msg, (NoteOn, NoteOff)) and msg.note in SAMPLES:
164+
name, level = get_sample(msg.note, msg.velocity)
165+
if isinstance(msg, NoteOn) and msg.velocity != 0:
166+
play_sample(name, level)
167+
elif isinstance(msg, NoteOff) or (isinstance(msg, NoteOn) and msg.velocity == 0):
168+
pass
169+
# stop_sample(name)
170+
await asyncio.sleep(0)
171+
172+
# Controls
173+
174+
async def controls_handler():
175+
while True:
176+
for event in hardware.check_buttons():
177+
pass
178+
knobA, knobB = hardware.knobA.value, hardware.knobB.value
179+
await asyncio.sleep(0.005)
180+
181+
async def main():
182+
await asyncio.gather(
183+
asyncio.create_task(touch_handler()),
184+
asyncio.create_task(midi_handler()),
185+
asyncio.create_task(controls_handler())
186+
)
187+
188+
asyncio.run(main())

circuitpython/perc/hardware.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 Cooper Dalrymple
2+
# SPDX-License-Identifier: MIT
3+
"""
4+
`hardware.py`
5+
================================================================================
6+
7+
Configure all of the hardware components of the pico_touch_synth
8+
9+
29 July 2025 - @relic_se / Cooper Dalrymple
10+
"""
11+
12+
import board
13+
import busio
14+
import digitalio
15+
import os
16+
17+
# Settings
18+
19+
SAMPLE_RATE = 44100
20+
CHANNEL_COUNT = 1
21+
BUFFER_SIZE = 2048
22+
23+
# MCU
24+
25+
#import microcontroller
26+
#microcontroller.cpu.frequency = 200_000_000
27+
28+
is_rp2350 = 'rp2350' in os.uname()[0]
29+
30+
led = digitalio.DigitalInOut(board.LED)
31+
led.switch_to_output()
32+
33+
# Pins
34+
35+
buttonA_pin = board.USER_SW
36+
buttonB_pin = board.GP28
37+
knobA_pin = board.A0 # GP26
38+
knobB_pin = board.A1 # GP27
39+
i2s_bck_pin = board.GP20
40+
i2s_lck_pin = board.GP21
41+
i2s_dat_pin = board.GP22
42+
i2c_scl_pin = board.GP19
43+
i2c_sda_pin = board.GP18
44+
uart_rx_pin = board.GP17
45+
uart_tx_pin = board.GP16
46+
47+
touch_pins = (
48+
board.GP0, board.GP1, board.GP2, board.GP3, board.GP4, board.GP5,
49+
board.GP6, board.GP7 ,board.GP8, board.GP9, board.GP10, board.GP11,
50+
board.GP12, board.GP13, board.GP14, board.GP15 )
51+
52+
# Audio
53+
54+
import audiobusio
55+
56+
audio = audiobusio.I2SOut(
57+
bit_clock=i2s_bck_pin,
58+
word_select=i2s_lck_pin,
59+
data=i2s_dat_pin,
60+
)
61+
62+
# Controls
63+
64+
import keypad
65+
import analogio
66+
67+
buttonA = keypad.Keys((buttonA_pin,), value_when_pressed=False, pull=False)
68+
buttonB = keypad.Keys((buttonB_pin,), value_when_pressed=False, pull=True)
69+
70+
def check_buttons():
71+
"""Check the touch inputs, return keypad-like Events"""
72+
events = []
73+
eventA = buttonA.events.get()
74+
if eventA:
75+
events.append(keypad.Event(0, eventA.pressed))
76+
eventB = buttonB.events.get()
77+
if eventB:
78+
events.append(keypad.Event(1, eventB.pressed))
79+
return events
80+
81+
knobA = analogio.AnalogIn(knobA_pin)
82+
knobB = analogio.AnalogIn(knobB_pin)
83+
84+
# Display
85+
86+
import i2cdisplaybus
87+
import displayio
88+
import adafruit_displayio_ssd1306
89+
90+
DW,DH = 128,64
91+
displayio.release_displays()
92+
93+
i2c = busio.I2C(
94+
scl=i2c_scl_pin,
95+
sda=i2c_sda_pin,
96+
frequency=1_000_000,
97+
)
98+
display_bus = i2cdisplaybus.I2CDisplayBus(i2c, device_address=0x3c)
99+
display = adafruit_displayio_ssd1306.SSD1306(display_bus,
100+
rotation=180,
101+
width=DW, height=DH,
102+
auto_refresh=False,
103+
)
104+
display.refresh()
105+
106+
# Touch Keys
107+
108+
import touchio
109+
import adafruit_debouncer
110+
111+
pull_type = None if not is_rp2350 else digitalio.Pull.UP
112+
113+
touchins = [touchio.TouchIn(pin, pull_type) for pin in touch_pins]
114+
for touchin in touchins:
115+
touchin.threshold = int(touchin.threshold * 1.1)
116+
117+
touches = [adafruit_debouncer.Debouncer(touchin) for touchin in touchins]
118+
119+
def check_touch():
120+
"""Check the touch inputs, return keypad-like Events"""
121+
events = []
122+
for i,touch in enumerate(touches):
123+
touch.update()
124+
if touch.rose:
125+
events.append(keypad.Event(i,True))
126+
elif touch.fell:
127+
events.append(keypad.Event(i,False))
128+
return events
129+
130+
# MIDI
131+
132+
import usb_midi
133+
import adafruit_midi
134+
135+
midi_settings = {
136+
"in_channel": 0,
137+
"out_channel": 0,
138+
"debug": False,
139+
}
140+
141+
uart = busio.UART(uart_tx_pin, uart_rx_pin, baudrate=31250, timeout=0.001)
142+
143+
midi_uart = adafruit_midi.MIDI(
144+
midi_in=uart,
145+
midi_out=uart,
146+
**midi_settings,
147+
)
148+
149+
midi_usb = adafruit_midi.MIDI(
150+
midi_in=usb_midi.ports[0],
151+
midi_out=usb_midi.ports[1],
152+
**midi_settings,
153+
)

circuitpython/perc/samples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
https://github.com/alex-esc/sample-pi
2+
`for i in *.flac; do ffmpeg -i "$i" -sample_fmt s16 -ar 44100 "${i%.*}.wav"; done`
224 KB
Binary file not shown.
160 KB
Binary file not shown.
17.9 KB
Binary file not shown.
155 KB
Binary file not shown.
23.5 KB
Binary file not shown.
60 KB
Binary file not shown.
48.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)