|
| 1 | +# SPDX-FileCopyrightText: 2023 John Park for Adafruit |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | +# Meowsic Toy Piano MIDI Keyboard |
| 5 | + |
| 6 | +import keypad |
| 7 | +import board |
| 8 | +import busio |
| 9 | +import supervisor |
| 10 | +from adafruit_simplemath import map_range |
| 11 | +from adafruit_msa3xx import MSA311 |
| 12 | +import usb_midi |
| 13 | +import adafruit_midi |
| 14 | +from adafruit_midi.note_on import NoteOn |
| 15 | +from adafruit_midi.note_off import NoteOff |
| 16 | +from adafruit_midi.control_change import ControlChange |
| 17 | +from adafruit_midi.program_change import ProgramChange |
| 18 | +# from adafruit_midi.start import Start |
| 19 | +# from adafruit_midi.stop import Stop |
| 20 | +from adafruit_midi.pitch_bend import PitchBend |
| 21 | + |
| 22 | +supervisor.runtime.autoreload = False # prevent unwanted restarts due to OS weirdness |
| 23 | + |
| 24 | +i2c = board.STEMMA_I2C() |
| 25 | +msa = MSA311(i2c) |
| 26 | + |
| 27 | +key_matrix = keypad.KeyMatrix( |
| 28 | + column_pins=(board.D2, board.D3, board.D4, board.D5, board.D6, board.D7, board.D8, board.D9), |
| 29 | + row_pins=(board.D10, board.MOSI, board.MISO, board.CLK, board.A0, board.A1) |
| 30 | +) |
| 31 | + |
| 32 | +midi_uart = busio.UART(board.TX, None, baudrate=31250) |
| 33 | + |
| 34 | +midi_usb_channel = 1 |
| 35 | +midi_usb = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=midi_usb_channel-1) |
| 36 | +midi_serial_channel = 4 |
| 37 | +midi_serial = adafruit_midi.MIDI(midi_out=midi_uart, out_channel=midi_serial_channel-1) |
| 38 | + |
| 39 | +octave = 4 |
| 40 | +note_offset = 9 # first note on keyboard is an A, first key in keypad matrix is 0 |
| 41 | + |
| 42 | +def send_note_on(note, octv): |
| 43 | + note = ((note+note_offset)+(12*octv)) |
| 44 | + midi_usb.send(NoteOn(note, 120)) |
| 45 | + midi_serial.send(NoteOn(note, 120)) |
| 46 | + |
| 47 | +def send_note_off(note, octv): |
| 48 | + note = ((note+note_offset)+(12*octv)) |
| 49 | + midi_usb.send(NoteOff(note, 0)) |
| 50 | + midi_serial.send(NoteOff(note, 0)) |
| 51 | + |
| 52 | +def send_cc(number, val): |
| 53 | + midi_usb.send(ControlChange(number, val)) |
| 54 | + midi_serial.send(ControlChange(number, val)) |
| 55 | + |
| 56 | +def send_pc(bank, folder, patch): |
| 57 | + send_cc(0, bank) |
| 58 | + send_cc(32, folder) |
| 59 | + midi_usb.send(ProgramChange(patch)) |
| 60 | + midi_serial.send(ProgramChange(patch)) |
| 61 | + |
| 62 | +def send_bend(bend_val): |
| 63 | + midi_usb.send(PitchBend(bend_val)) |
| 64 | + midi_serial.send(PitchBend(bend_val)) |
| 65 | + |
| 66 | +def send_midi_panic(): |
| 67 | + for x in range(128): |
| 68 | + midi_usb.send(NoteOff(x, 0)) |
| 69 | + midi_serial.send(NoteOff(x, 0)) |
| 70 | + |
| 71 | +# 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 |
| 74 | +clef_button = 33 |
| 75 | +nose_button = 47 |
| 76 | +face_button = 34 |
| 77 | +record_button = 44 |
| 78 | +play_button = 45 |
| 79 | + |
| 80 | +# patch assigments |
| 81 | +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 |
| 92 | +) |
| 93 | + |
| 94 | +# accelerometer filtering variables |
| 95 | +slop = 0.2 # threshold for accelerometer send |
| 96 | +filter_percent = 0.5 # ranges from 0.0 to 1.0 |
| 97 | +accel_data_y = msa.acceleration[1] |
| 98 | +last_accel_data_y = msa.acceleration[1] |
| 99 | + |
| 100 | +cone_mode = False # mod wheel vs pitch bend |
| 101 | +started = False # state of arp/seq play |
| 102 | + |
| 103 | + |
| 104 | +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: |
| 113 | + 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 |
| 117 | + |
| 118 | + event = key_matrix.events.get() |
| 119 | + if event: |
| 120 | + if event.pressed: |
| 121 | + key = event.key_number |
| 122 | + # Note keys |
| 123 | + if key in piano_keys: # its one of the piano keys |
| 124 | + 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 |
| 128 | + send_pc(patch_list[pc_key][0], patch_list[pc_key][1], patch_list[pc_key][2]) |
| 129 | + # Play key |
| 130 | + if key is play_button: |
| 131 | + 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()) |
| 135 | + started = True |
| 136 | + else: |
| 137 | + send_cc(16, 0) |
| 138 | + # midi_usb.send(Stop()) |
| 139 | + started = False |
| 140 | + # Record key |
| 141 | + 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 |
| 148 | + |
| 149 | + # Clef |
| 150 | + if key is clef_button: # octave down |
| 151 | + octave = min(max((octave - 1), 0), 7 ) |
| 152 | + # Face |
| 153 | + if key is face_button: # octave up |
| 154 | + octave = min(max((octave + 1), 0), 7 ) |
| 155 | + # STOP |
| 156 | + if key is nose_button: |
| 157 | + send_midi_panic() # all notes off |
| 158 | + |
| 159 | + if event.released: |
| 160 | + key = event.key_number |
| 161 | + if key in piano_keys: |
| 162 | + send_note_off(key, octave) |
0 commit comments