Adafruit_Learning_System_Gu.../Grand_Central_MIDI_Knobs/code.py
2021-08-17 11:46:02 -04:00

145 lines
4.5 KiB
Python

# SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries
# SPDX-License-Identifier: MIT
# Grand Central MIDI Knobs
# for USB MIDI
# Reads analog inputs, sends out MIDI CC values
# with Kattni Rembor and Jan Goolsbey for range and hysteresis code
import time
import board
import busio
from simpleio import map_range
from analogio import AnalogIn
from digitalio import DigitalInOut, Direction
import usb_midi
import adafruit_midi # MIDI protocol encoder/decoder library
from adafruit_midi.control_change import ControlChange
USB_MIDI_channel = 1 # pick your USB MIDI out channel here, 1-16
# pick your classic MIDI channel for sending over UART serial TX/RX
CLASSIC_MIDI_channel = 2
usb_midi = adafruit_midi.MIDI(
midi_out=usb_midi.ports[1], out_channel=USB_MIDI_channel - 1
)
# use DIN-5 or TRS MIDI jack on TX/RX for classic MIDI
uart = busio.UART(board.TX, board.RX, baudrate=31250, timeout=0.001) # initialize UART
classic_midi = adafruit_midi.MIDI(
midi_out=uart, midi_in=uart, out_channel=CLASSIC_MIDI_channel - 1, debug=False
)
led = DigitalInOut(board.D13) # activity indicator
led.direction = Direction.OUTPUT
knob_count = 16 # Set the total number of potentiometers used
# Create the input objects list for potentiometers
knob = []
for k in range(knob_count):
knobs = AnalogIn(
getattr(board, "A{}".format(k))
) # get pin # attribute, use string formatting
knob.append(knobs)
# assignment of knobs to cc numbers
cc_number = [
1, # knob 0, mod wheel
2, # knob 1, breath control
7, # knob 2, volume
10, # knob 3 pan
11, # knob 4, expression
53, # knob 5
54, # knob 6
74, # knob 7
74, # knob 8, Filter frequency cutoff
71, # knob 9, Filter resonance
58, # knob 10
59, # knob 11
60, # knob 12
61, # knob 13
62, # knob 14
63, # knob 15
]
# CC range list defines the characteristics of the potentiometers
# This list contains the input object, minimum value, and maximum value for each knob.
# example ranges:
# 0 min, 127 max: full range control voltage
# 36 (C2) min, 84 (B5) max: 49-note keyboard
# 21 (A0) min, 108 (C8) max: 88-note grand piano
cc_range = [
(36, 84), # knob 0: C2 to B5: 49-note keyboard
(36, 84), # knob 1
(36, 84), # knob 2
(36, 84), # knob 3
(36, 84), # knob 4
(36, 84), # knob 5
(36, 84), # knob 6
(36, 84), # knob 7
(0, 127), # knob 8: 0 to 127: full range MIDI CC/control voltage for VCV Rack
(0, 127), # knob 9
(0, 127), # knob 10
(0, 127), # knob 11
(0, 127), # knob 12
(0, 127), # knob 13
(0, 127), # knob 14
(0, 127), # knob 15
]
print("---Grand Central MIDI Knobs---")
print(" USB MIDI channel: {}".format(USB_MIDI_channel))
print(" TRS MIDI channel: {}".format(CLASSIC_MIDI_channel))
# Initialize cc_value list with current value and offset placeholders
cc_value = []
for _ in range(knob_count):
cc_value.append((0, 0))
last_cc_value = []
for _ in range(knob_count):
last_cc_value.append((0, 0))
# range_index converts an analog value (ctl) to an indexed integer
# Input is masked to 8 bits to reduce noise then a scaled hysteresis offset
# is applied. The helper returns new index value (idx) and input
# hysteresis offset (offset) based on the number of control slices (ctrl_max).
def range_index(ctl, ctrl_max, old_idx, offset):
if (ctl + offset > 65535) or (ctl + offset < 0):
offset = 0
idx = int(map_range((ctl + offset) & 0xFF00, 1200, 65500, 0, ctrl_max))
if idx != old_idx: # if index changed, adjust hysteresis offset
# offset is 25% of the control slice (65536/ctrl_max)
offset = int(
0.25 * sign(idx - old_idx) * (65535 / ctrl_max)
) # edit 0.25 to adjust slices
return idx, offset
def sign(x): # determine the sign of x
if x >= 0:
return 1
else:
return -1
while True:
# read all the knob values
for i in range(knob_count):
cc_value[i] = range_index(
knob[i].value,
(cc_range[i][1] - cc_range[i][0] + 1),
cc_value[i][0],
cc_value[i][1],
)
if cc_value[i] != last_cc_value[i]: # only send if it changed
# Form a MIDI CC message and send it:
usb_midi.send(ControlChange(cc_number[i], cc_value[i][0] + cc_range[i][0]))
classic_midi.send(
ControlChange(cc_number[i], cc_value[i][0] + cc_range[i][0])
)
last_cc_value[i] = cc_value[i]
led.value = True
time.sleep(0.01)
led.value = False