/ Grand_Central_MIDI_Knobs / code.py
code.py
1 # SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries 2 # SPDX-License-Identifier: MIT 3 4 # Grand Central MIDI Knobs 5 # for USB MIDI 6 # Reads analog inputs, sends out MIDI CC values 7 # with Kattni Rembor and Jan Goolsbey for range and hysteresis code 8 9 import time 10 import board 11 import busio 12 from simpleio import map_range 13 from analogio import AnalogIn 14 from digitalio import DigitalInOut, Direction 15 import usb_midi 16 import adafruit_midi # MIDI protocol encoder/decoder library 17 from adafruit_midi.control_change import ControlChange 18 19 20 USB_MIDI_channel = 1 # pick your USB MIDI out channel here, 1-16 21 # pick your classic MIDI channel for sending over UART serial TX/RX 22 CLASSIC_MIDI_channel = 2 23 24 usb_midi = adafruit_midi.MIDI( 25 midi_out=usb_midi.ports[1], out_channel=USB_MIDI_channel - 1 26 ) 27 # use DIN-5 or TRS MIDI jack on TX/RX for classic MIDI 28 uart = busio.UART(board.TX, board.RX, baudrate=31250, timeout=0.001) # initialize UART 29 classic_midi = adafruit_midi.MIDI( 30 midi_out=uart, midi_in=uart, out_channel=CLASSIC_MIDI_channel - 1, debug=False 31 ) 32 33 led = DigitalInOut(board.D13) # activity indicator 34 led.direction = Direction.OUTPUT 35 36 knob_count = 16 # Set the total number of potentiometers used 37 38 # Create the input objects list for potentiometers 39 knob = [] 40 for k in range(knob_count): 41 knobs = AnalogIn( 42 getattr(board, "A{}".format(k)) 43 ) # get pin # attribute, use string formatting 44 knob.append(knobs) 45 46 # assignment of knobs to cc numbers 47 cc_number = [ 48 1, # knob 0, mod wheel 49 2, # knob 1, breath control 50 7, # knob 2, volume 51 10, # knob 3 pan 52 11, # knob 4, expression 53 53, # knob 5 54 54, # knob 6 55 74, # knob 7 56 74, # knob 8, Filter frequency cutoff 57 71, # knob 9, Filter resonance 58 58, # knob 10 59 59, # knob 11 60 60, # knob 12 61 61, # knob 13 62 62, # knob 14 63 63, # knob 15 64 ] 65 66 # CC range list defines the characteristics of the potentiometers 67 # This list contains the input object, minimum value, and maximum value for each knob. 68 # example ranges: 69 # 0 min, 127 max: full range control voltage 70 # 36 (C2) min, 84 (B5) max: 49-note keyboard 71 # 21 (A0) min, 108 (C8) max: 88-note grand piano 72 cc_range = [ 73 (36, 84), # knob 0: C2 to B5: 49-note keyboard 74 (36, 84), # knob 1 75 (36, 84), # knob 2 76 (36, 84), # knob 3 77 (36, 84), # knob 4 78 (36, 84), # knob 5 79 (36, 84), # knob 6 80 (36, 84), # knob 7 81 (0, 127), # knob 8: 0 to 127: full range MIDI CC/control voltage for VCV Rack 82 (0, 127), # knob 9 83 (0, 127), # knob 10 84 (0, 127), # knob 11 85 (0, 127), # knob 12 86 (0, 127), # knob 13 87 (0, 127), # knob 14 88 (0, 127), # knob 15 89 ] 90 91 print("---Grand Central MIDI Knobs---") 92 print(" USB MIDI channel: {}".format(USB_MIDI_channel)) 93 print(" TRS MIDI channel: {}".format(CLASSIC_MIDI_channel)) 94 95 # Initialize cc_value list with current value and offset placeholders 96 cc_value = [] 97 for _ in range(knob_count): 98 cc_value.append((0, 0)) 99 last_cc_value = [] 100 for _ in range(knob_count): 101 last_cc_value.append((0, 0)) 102 103 # range_index converts an analog value (ctl) to an indexed integer 104 # Input is masked to 8 bits to reduce noise then a scaled hysteresis offset 105 # is applied. The helper returns new index value (idx) and input 106 # hysteresis offset (offset) based on the number of control slices (ctrl_max). 107 def range_index(ctl, ctrl_max, old_idx, offset): 108 if (ctl + offset > 65535) or (ctl + offset < 0): 109 offset = 0 110 idx = int(map_range((ctl + offset) & 0xFF00, 1200, 65500, 0, ctrl_max)) 111 if idx != old_idx: # if index changed, adjust hysteresis offset 112 # offset is 25% of the control slice (65536/ctrl_max) 113 offset = int( 114 0.25 * sign(idx - old_idx) * (65535 / ctrl_max) 115 ) # edit 0.25 to adjust slices 116 return idx, offset 117 118 119 def sign(x): # determine the sign of x 120 if x >= 0: 121 return 1 122 else: 123 return -1 124 125 126 while True: 127 # read all the knob values 128 for i in range(knob_count): 129 cc_value[i] = range_index( 130 knob[i].value, 131 (cc_range[i][1] - cc_range[i][0] + 1), 132 cc_value[i][0], 133 cc_value[i][1], 134 ) 135 if cc_value[i] != last_cc_value[i]: # only send if it changed 136 # Form a MIDI CC message and send it: 137 usb_midi.send(ControlChange(cc_number[i], cc_value[i][0] + cc_range[i][0])) 138 classic_midi.send( 139 ControlChange(cc_number[i], cc_value[i][0] + cc_range[i][0]) 140 ) 141 last_cc_value[i] = cc_value[i] 142 led.value = True 143 144 time.sleep(0.01) 145 led.value = False