# MicroPython USB MIDI example # # This example demonstrates creating a custom MIDI device. # # To run this example: # # 1. Make sure `usb-device-midi` is installed via: mpremote mip install usb-device-midi # # 2. Run the example via: mpremote run midi_example.py # # 3. mpremote will exit with an error after the previous step, because when the # example runs the existing USB device disconnects and then re-enumerates with # the MIDI interface present. At this point, the example is running. # # 4. To see output from the example, re-connect: mpremote connect PORTNAME # # # MIT license; Copyright (c) 2023-2024 Angus Gratton import usb.device from usb.device.midi import MIDIInterface import time from machine import Pin import array, time import rp2 NUM_COL = 4 NUM_ROW = 4 NUM_LEDS = NUM_COL * NUM_ROW PIN_NUM = 16 brightness = 0.2 @rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24) def ws2812(): T1 = 2 T2 = 5 T3 = 3 wrap_target() label("bitloop") out(x, 1) .side(1) [T3 - 1] jmp(not_x, "do_zero") .side(0) [T1 - 1] jmp("bitloop") .side(0) [T2 - 1] label("do_zero") nop() .side(1) [T2 - 1] wrap() def init_leds(): # Create the StateMachine with the ws2812 program, outputting on pin sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM)) # Start the StateMachine, it will wait for data on its FIFO. sm.active(1) # Display a pattern on the LEDs via an array of LED RGB values. ar = array.array("I", [0 for _ in range(NUM_LEDS)]) ########################################################################## def show_all(): dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)]) for i, c in enumerate(ar): r = int(((c >> 8) & 0xFF) * brightness) g = int(((c >> 16) & 0xFF) * brightness) b = int((c & 0xFF) * brightness) dimmer_ar[i] = (g<<16) + (r<<8) + b sm.put(dimmer_ar, 8) def set_led(i, color): row = i // NUM_COL col = i - row * NUM_COL new_col = NUM_COL - col - 1 new_row = NUM_ROW - row - 1 if new_col % 2 == 0 else row ar[new_col * NUM_ROW + new_row] = (color[1]<<16) + (color[0]<<8) + color[2] return (set_led, show_all) class MIDIExample(MIDIInterface): # Very simple example event handler functions, showing how to receive note # and control change messages sent from the host to the device. # # If you need to send MIDI data to the host, then it's fine to instantiate # MIDIInterface class directly. def on_open(self): super().on_open() print("Device opened by host") def on_note_on(self, channel, pitch, vel): print(f"RX Note On channel {channel} pitch {pitch} velocity {vel}") def on_note_off(self, channel, pitch, vel): print(f"RX Note Off channel {channel} pitch {pitch} velocity {vel}") def on_control_change(self, channel, controller, value): print(f"RX Control channel {channel} controller {controller} value {value}") m = MIDIExample() # Remove builtin_driver=True if you don't want the MicroPython serial REPL available. usb.device.get().init(m, builtin_driver=True) print("Waiting for USB host to configure the interface...") while not m.is_open(): time.sleep_ms(100) # TX constants CHANNEL = 0 PITCH_C1 = 37 CONTROLLER = 64 control_val = 0 def init_btns(): row_pins = [ Pin(0, Pin.OUT), Pin(1, Pin.OUT), Pin(2, Pin.OUT), Pin(3, Pin.OUT), ] col_pins = [ Pin(32, Pin.IN, Pin.PULL_UP), Pin(33, Pin.IN, Pin.PULL_UP), Pin(34, Pin.IN, Pin.PULL_UP), Pin(35, Pin.IN, Pin.PULL_UP), ] pressed_keys = set() # reset cols for pin in row_pins: pin.value(1) def loop(): pressed_keys.clear() for row, row_pin in enumerate(row_pins): # set only active pin low, keep rest high row_pin.value(0) for col, col_pin in enumerate(col_pins): key = ( (NUM_ROW - row - 1) + (NUM_COL - col - 1) * NUM_ROW ) if not col_pin.value(): pressed_keys.add(key) row_pin.value(1) return set(pressed_keys) return loop btn_loop = init_btns() turned_on = set() (set_led, show_all) = init_leds() # for i in range(NUM_LEDS): # set_led(i, CYAN) # for j in range(NUM_LEDS): # if i != j: # set_led(j, BLACK) # show_all() # time.sleep(2) # CYAN = (0, 255, 255) BLACK = (0, 0, 0) print("Starting loop...") while m.is_open(): time.sleep_ms(5) # print(f"TX Note On channel {CHANNEL} pitch {PITCH_C1}") # m.note_on(CHANNEL, PITCH_C1) # Velocity is an optional third argument # time.sleep(0.5) # print(f"TX Note Off channel {CHANNEL} pitch {PITCH_C1}") # m.note_off(CHANNEL, PITCH_C1) # time.sleep(1) # print(f"TX Control channel {CHANNEL} controller {CONTROLLER} value {control_val}") # m.control_change(CHANNEL, CONTROLLER, control_val) # control_val += 1 # if control_val == 0x7F: # control_val = 0 # time.sleep(1) pressed = btn_loop() to_turn_on = pressed.difference(turned_on) to_turn_off = turned_on.difference(pressed) if len(to_turn_on): print("to_turn_on", to_turn_on) if len(to_turn_off): print("to_turn_off", to_turn_off) for key in to_turn_on: m.note_on(CHANNEL, PITCH_C1 + key - 1) set_led(key, CYAN) turned_on.add(key) for key in to_turn_off: m.note_off(CHANNEL, PITCH_C1 + key - 1) turned_on.remove(key) set_led(key, BLACK) show_all() print("USB host has reset device, example done.")