1
1
# SPDX-FileCopyrightText: 2023 John Park for Adafruit
2
2
#
3
3
# SPDX-License-Identifier: MIT
4
- # Meowsic Toy Piano MIDI Keyboard
4
+ # Cyber Cat MIDI Keyboard conversion for Meowsic Cat Piano
5
+
6
+ # Functions:
7
+ # --28 keys
8
+ # --left five toe buttons: patches
9
+ # --right five toe buttons: picking CC number for ice cream cone control
10
+ # --volume arrows: octave up/down
11
+ # --tempo arrows: pitchbend up/down
12
+ # --on switch: reset
13
+ # --nose button: midi panic
14
+ # --record button: ice cream cone CC enable/disable (led indicator)
15
+ # --play button: start stop arp or sequence in soft synth via cc 16 0/127
16
+ # --treble clef button: hold notes (use nose to turn off all notes)
17
+ # --face button: momentary CC 0/127 on CC number 17
5
18
6
19
import keypad
7
20
import board
8
21
import busio
9
22
import supervisor
23
+ import digitalio
10
24
from adafruit_simplemath import map_range
11
25
from adafruit_msa3xx import MSA311
12
26
import usb_midi
15
29
from adafruit_midi .note_off import NoteOff
16
30
from adafruit_midi .control_change import ControlChange
17
31
from adafruit_midi .program_change import ProgramChange
18
- # from adafruit_midi.start import Start
19
- # from adafruit_midi.stop import Stop
20
32
from adafruit_midi .pitch_bend import PitchBend
21
33
22
- supervisor .runtime .autoreload = False # prevent unwanted restarts due to OS weirdness
34
+ supervisor .runtime .autoreload = True # set False to prevent unwanted restarts due to OS weirdness
35
+
36
+ ledpin = digitalio .DigitalInOut (board .A3 )
37
+ ledpin .direction = digitalio .Direction .OUTPUT
38
+ ledpin .value = True
23
39
24
40
i2c = board .STEMMA_I2C ()
25
41
msa = MSA311 (i2c )
33
49
34
50
midi_usb_channel = 1
35
51
midi_usb = adafruit_midi .MIDI (midi_out = usb_midi .ports [1 ], out_channel = midi_usb_channel - 1 )
36
- midi_serial_channel = 4
52
+ midi_serial_channel = 1
37
53
midi_serial = adafruit_midi .MIDI (midi_out = midi_uart , out_channel = midi_serial_channel - 1 )
38
54
39
55
octave = 4
@@ -59,104 +75,158 @@ def send_pc(bank, folder, patch):
59
75
midi_usb .send (ProgramChange (patch ))
60
76
midi_serial .send (ProgramChange (patch ))
61
77
62
- def send_bend (bend_val ):
63
- midi_usb .send (PitchBend (bend_val ))
64
- midi_serial .send (PitchBend (bend_val ))
78
+ def send_bend (bend_start , bend_val , rate , dir ):
79
+ b = bend_start
80
+ if dir is 0 :
81
+ while b > bend_val + rate :
82
+ print (b )
83
+ b = b - rate
84
+ midi_usb .send (PitchBend (b ))
85
+ midi_serial .send (PitchBend (b ))
86
+ if dir is 1 :
87
+ while b < bend_val - rate :
88
+ print (b )
89
+ b = b + rate
90
+ midi_usb .send (PitchBend (b ))
91
+ midi_serial .send (PitchBend (b ))
65
92
66
93
def send_midi_panic ():
67
94
for x in range (128 ):
68
95
midi_usb .send (NoteOff (x , 0 ))
69
96
midi_serial .send (NoteOff (x , 0 ))
70
97
71
98
# key ranges
72
- piano_keys = range (0 , 28 ) # note 'range()' excludes last value, so add one
73
- toes = ( list (range (28 , 33 )) + list (range (35 , 40 )) ) # L/R toe series is interruped by 33, 34
99
+ piano_keys = range (0 , 28 ) # 'range()' excludes last value, so add one
100
+ patch_toes = list (range (28 , 33 ))
101
+ cc_toes = list (range (35 , 40 ))
74
102
clef_button = 33
75
103
nose_button = 47
76
104
face_button = 34
77
105
record_button = 44
78
106
play_button = 45
107
+ vol_down_button = 43
108
+ vol_up_button = 42
109
+ tempo_down_button = 41
110
+ tempo_up_button = 40
79
111
80
112
# patch assigments
81
113
patch_list = (
82
- (0 ,0 ,0 ), # piano
83
- (1 ,0 ,0 ), # bells
84
- (1 ,0 ,1 ), # meow
85
- (2 ,0 ,0 ), # organ
86
- (3 ,0 ,0 ), # banjo
87
- (4 ,0 ,1 ), # rock
88
- (5 ,0 ,0 ), # blues
89
- (6 ,0 ,0 ), # samba
90
- (7 ,0 ,1 ), # tencho
91
- (8 ,0 ,4 ) # disco
114
+ (0 , 0 , 0 ), # bank 0, folder 0, patch 0
115
+ (1 , 0 , 0 ),
116
+ (1 , 0 , 1 ),
117
+ (2 , 0 , 0 ),
118
+ (3 , 0 , 0 ),
92
119
)
93
120
121
+ pb_max = 16383 # bend up value
122
+ pb_default = 8192 # bend center value
123
+ pb_min = 0 # bend down value
124
+ pb_change_rate = 100 # interval for pitch bend, lower number is slower
125
+ pb_return_rate = 100 # interval for pitch bend release
126
+
94
127
# accelerometer filtering variables
95
128
slop = 0.2 # threshold for accelerometer send
96
129
filter_percent = 0.5 # ranges from 0.0 to 1.0
97
130
accel_data_y = msa .acceleration [1 ]
98
131
last_accel_data_y = msa .acceleration [1 ]
99
132
100
- cone_mode = False # mod wheel vs pitch bend
133
+ # midi cc variables
134
+ cc_enable = True
135
+ cc_numbers = (1 , 43 , 44 , 14 , 15 ) # mod wheel, filter cutoff, resonance, user, user
136
+ cc_current = 0
137
+ cc_play = 16
138
+ cc_face_number = 17
139
+
101
140
started = False # state of arp/seq play
141
+ note_hold = False
142
+
143
+ print ("Cyber Cat MIDI Keyboard" )
102
144
103
145
104
146
while True :
105
- new_data_y = msa .acceleration [1 ]
106
- accel_data_y = ((new_data_y * filter_percent ) + (1 - filter_percent ) * accel_data_y ) # smoothed
107
- if abs (accel_data_y - last_accel_data_y ) > slop :
108
- if cone_mode is True : # pitch bend mode
109
- pitch = int (map_range (accel_data_y , 9 , - 9 , 0 , 16383 ))
110
- send_bend (pitch )
111
-
112
- else :
147
+ if cc_enable :
148
+ new_data_y = msa .acceleration [1 ]
149
+ accel_data_y = ((new_data_y * filter_percent ) + (1 - filter_percent ) * accel_data_y ) # smooth
150
+ if abs (accel_data_y - last_accel_data_y ) > slop :
113
151
modulation = int (map_range (accel_data_y , 9 , - 9 , 0 , 127 ))
114
- send_cc (1 , modulation )
115
-
116
- last_accel_data_y = accel_data_y
152
+ send_cc (cc_numbers [cc_current ], modulation )
153
+ last_accel_data_y = accel_data_y
117
154
118
155
event = key_matrix .events .get ()
119
156
if event :
120
157
if event .pressed :
121
158
key = event .key_number
159
+
122
160
# Note keys
123
- if key in piano_keys : # its one of the piano keys
161
+ if key in piano_keys :
124
162
send_note_on (key , octave )
125
- # Patch buttons (cat toes)
126
- if key in toes :
127
- pc_key = toes .index (key ) # remove offset for patch list indexing
163
+
164
+ # Volume buttons
165
+ if key is vol_down_button :
166
+ octave = min (max ((octave - 1 ), 0 ), 7 )
167
+ if key is vol_up_button :
168
+ octave = min (max ((octave + 1 ), 0 ), 7 )
169
+
170
+ # Tempo buttons
171
+ if key is tempo_down_button :
172
+ send_bend (pb_default , pb_min , pb_change_rate , 0 )
173
+ if key is tempo_up_button :
174
+ send_bend (pb_default , pb_max , pb_change_rate , 1 )
175
+
176
+ # Patch buttons (left cat toes)
177
+ if key in patch_toes :
178
+ pc_key = patch_toes .index (key ) # remove offset for patch list indexing
128
179
send_pc (patch_list [pc_key ][0 ], patch_list [pc_key ][1 ], patch_list [pc_key ][2 ])
129
- # Play key
180
+
181
+ # cc buttons (right cat toes)
182
+ if key in cc_toes :
183
+ cc_current = cc_toes .index (key ) # remove offset for cc list indexing
184
+
185
+ # Play key -- use MIDI learn to have arp/seq start or stop with this
130
186
if key is play_button :
131
187
if not started :
132
- # use midi learning and a CC
133
- send_cc (16 , 127 ) # map to seq/arp on/off Synth One
134
- # midi_usb.send(Start())
188
+ send_cc (cc_play , 127 ) # map to seq/arp on/off Synth One, e.g.
135
189
started = True
136
190
else :
137
- send_cc (16 , 0 )
138
- # midi_usb.send(Stop())
191
+ send_cc (cc_play , 0 )
139
192
started = False
140
- # Record key
193
+
194
+ # Record key -- enable icecream cone
141
195
if key is record_button :
142
- if cone_mode :
143
- send_bend (8191 ) # 'zero' out pitch bend to center position
144
- cone_mode = False
145
- else :
146
- send_cc (1 , 64 ) # 'zero' out the mod wheel to center position
147
- cone_mode = True
196
+ if cc_enable is True :
197
+ cc_enable = False
198
+ ledpin .value = False
199
+
200
+ elif cc_enable is False :
201
+ send_cc (cc_numbers [cc_current ], 0 ) # zero it
202
+ cc_enable = True
203
+ ledpin .value = True
148
204
149
205
# Clef
150
- if key is clef_button : # octave down
151
- octave = min (max ((octave - 1 ), 0 ), 7 )
206
+ if key is clef_button : # hold
207
+ note_hold = not note_hold
208
+
152
209
# Face
153
- if key is face_button : # octave up
154
- octave = min (max ((octave + 1 ), 0 ), 7 )
155
- # STOP
210
+ if key is face_button : # momentary cc
211
+ send_cc (cc_face_number , 127 )
212
+
213
+ # Nose
156
214
if key is nose_button :
157
215
send_midi_panic () # all notes off
158
216
159
217
if event .released :
160
218
key = event .key_number
161
219
if key in piano_keys :
162
- send_note_off (key , octave )
220
+ if not note_hold :
221
+ send_note_off (key , octave )
222
+ if note_hold :
223
+ pass
224
+
225
+ if key is face_button : # momentary cc release
226
+ send_cc (cc_face_number , 0 )
227
+
228
+ if key is tempo_down_button :
229
+ send_bend (pb_min , pb_default , pb_return_rate , 1 )
230
+
231
+ if key is tempo_up_button :
232
+ send_bend (pb_max , pb_default , pb_return_rate , 0 )
0 commit comments