-
Notifications
You must be signed in to change notification settings - Fork 22
/
midi-arp.py
154 lines (121 loc) · 5.13 KB
/
midi-arp.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# SPDX-FileCopyrightText: 2021 Sandy Macdonald
#
# SPDX-License-Identifier: MIT
# A MIDI arpeggiator, with three different styles: up, down, or up-down. BPM and
# note length are both configurable, and LEDs cycle with the currently-played
# key/note to give some visual feedback.
# You'll need to connect Keybow 2040 to a computer running a DAW like Ableton,
# or other software synth, or to a hardware synth that accepts USB MIDI.
# Drop the `pmk` folder
# into your `lib` folder on your `CIRCUITPY` drive.
# NOTE! Requires the adafruit_midi CircuitPython library also!
import time
from pmk import PMK
from pmk.platform.keybow2040 import Keybow2040 as Hardware # for Keybow 2040
# from pmk.platform.rgbkeypadbase import RGBKeypadBase as Hardware # for Pico RGB Keypad Base
import usb_midi
import adafruit_midi
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn
# Set up Keybow
keybow = PMK(Hardware())
keys = keybow.keys
# Set USB MIDI up on channel 0.
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=5)
# The colour to set the keys when pressed, orange-y.
rgb = (255, 50, 0)
# MIDI velocity.
start_note = 36
velocity = 100
# Beats per minute
bpm = 80
# Play 16th notes
note_length = 1 / 16
# Assumes BPM is calculated on quarter notes
note_time = 60 / bpm * (note_length * 4)
# Arpeggio style:
# 0 = up
# 1 = down
# 2 = up-down
arp_style = 2
# Start the arp in a forwards direction (1) if the style is up or up-down, or
# or backwards (-1) if the style is down.
if arp_style == 0 or arp_style == 2:
direction = 1
elif arp_style == 1:
direction = -1
# Keep track of time of last note played and last keys pressed
last_played = None
last_pressed = []
while True:
# Always remember to call keybow.update()!
keybow.update()
# If any keys are pressed, go through shenanigans
if keybow.any_pressed():
# Fetch a list of pressed keys
pressed = keybow.get_pressed()
# If the keys pressed have changed...
if pressed != last_pressed:
# Keys that were pressed, but are no longer
missing = [k for k in last_pressed if k not in pressed]
# Any keys that were pressed, but are no longer, turn LED off
# and send MIDI note off for the respective note.
for k in missing:
note = start_note + k
midi.send(NoteOff(note, 0))
keys[k].set_led(0, 0, 0)
# Calculate MIDI note numbers
notes = [start_note + k for k in pressed]
last_pressed = pressed
# If going forward (up or starting up-down), start at 0,
# otherwise start at the end of the list of notes.
if arp_style == 0 or arp_style == 2:
this_note = 0
elif arp_style == 1:
this_note = len(notes) - 1
# Send MIDI note on message for current note and turn LED on
midi.send(NoteOn(notes[this_note], velocity))
keys[pressed[this_note]].set_led(*rgb)
# Update last_played time, set elapsed to 0, and update current and
# last note indices.
last_played = time.monotonic()
elapsed = 0
last_note = this_note
this_note += direction
# If the currently pressed notes are the same as the last loop, then...
else:
if notes != []:
# Check time elapsed since last note played
elapsed = time.monotonic() - last_played
# If the note time has elapsed, then...
if elapsed > note_time:
# Reset at the end or start of the notes list
if this_note == len(notes) and direction == 1:
this_note = 0
elif this_note < 0:
this_note = len(notes) - 1
# Send a MIDI note off for the last note, turn off LED
midi.send(NoteOff(notes[last_note], 0))
keys[pressed[last_note]].set_led(0, 0, 0)
# Send a MIDI note on for the next note, turn on LED
midi.send(NoteOn(notes[this_note], velocity))
keys[pressed[this_note]].set_led(*rgb)
# Update time last_played, make this note last note
last_played = time.monotonic()
last_note = this_note
# For the up-down style, switch direction at either end
if arp_style == 2 and this_note == len(notes) - 1:
direction = -1
elif arp_style == 2 and this_note == 0:
direction = 1
# Increment note
this_note += direction
# If nothing is now pressed, but was last time, then send MIDI note off
# for every note, and turn all the LEDs off.
elif len(last_pressed) and keybow.none_pressed():
for note in range(128):
midi.send(NoteOff(note, 0))
for key in keys:
key.set_led(0, 0, 0)
# Nothing is pressed, so reset last_pressed list
last_pressed = []