Compare commits
4 Commits
efdbb11f92
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e78421829 | |||
| 0fb69b0fbc | |||
| 0b8893d433 | |||
| 700e87f807 |
File diff suppressed because it is too large
Load Diff
@@ -4147,6 +4147,28 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "MCP_CS"
|
||||
(shape input)
|
||||
(at 245.11 138.43 0)
|
||||
(fields_autoplaced yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify left)
|
||||
)
|
||||
(uuid "110d57d2-9a85-4aeb-9b6a-69c907ecb067")
|
||||
(property "Intersheetrefs" "${INTERSHEET_REFS}"
|
||||
(at 255.5337 138.43 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify left)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "Pot3"
|
||||
(shape input)
|
||||
(at 245.11 120.65 0)
|
||||
@@ -4213,6 +4235,28 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "MCP_CS"
|
||||
(shape input)
|
||||
(at 127 124.46 180)
|
||||
(fields_autoplaced yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify right)
|
||||
)
|
||||
(uuid "18abe3ae-619d-4ea9-a26d-baf9ec2f3ec9")
|
||||
(property "Intersheetrefs" "${INTERSHEET_REFS}"
|
||||
(at 116.5763 124.46 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify right)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "Pot4"
|
||||
(shape input)
|
||||
(at 245.11 123.19 0)
|
||||
@@ -4675,28 +4719,6 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "Gnd"
|
||||
(shape input)
|
||||
(at 127 124.46 180)
|
||||
(fields_autoplaced yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify right)
|
||||
)
|
||||
(uuid "47bd9f65-0e2a-4fc6-bd4e-d5f3d0725d71")
|
||||
(property "Intersheetrefs" "${INTERSHEET_REFS}"
|
||||
(at 120.4468 124.46 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify right)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "USB_5v"
|
||||
(shape input)
|
||||
(at 214.63 138.43 180)
|
||||
@@ -4895,6 +4917,28 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "MCP_CS"
|
||||
(shape input)
|
||||
(at 127 167.64 180)
|
||||
(fields_autoplaced yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify right)
|
||||
)
|
||||
(uuid "60a88f2d-666a-4237-80f4-f94f107c2781")
|
||||
(property "Intersheetrefs" "${INTERSHEET_REFS}"
|
||||
(at 116.5763 167.64 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify right)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "Ch1Zero"
|
||||
(shape input)
|
||||
(at 214.63 133.35 180)
|
||||
@@ -5665,28 +5709,6 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "Gnd"
|
||||
(shape input)
|
||||
(at 127 167.64 180)
|
||||
(fields_autoplaced yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify right)
|
||||
)
|
||||
(uuid "c18809a2-710c-49cd-8166-9b76e2516110")
|
||||
(property "Intersheetrefs" "${INTERSHEET_REFS}"
|
||||
(at 120.4468 167.64 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(justify right)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
)
|
||||
(global_label "3v3"
|
||||
(shape input)
|
||||
(at 135.89 22.86 180)
|
||||
@@ -8261,10 +8283,10 @@
|
||||
)
|
||||
)
|
||||
(pin "2"
|
||||
(uuid "266096b0-0722-468b-b667-02afdd8ba748")
|
||||
(uuid "266096b0-0722-468b-b667-02afdd8ba749")
|
||||
)
|
||||
(pin "1"
|
||||
(uuid "a9d6408d-fc4c-4df3-b784-212408267add")
|
||||
(uuid "a9d6408d-fc4c-4df3-b784-212408267ade")
|
||||
)
|
||||
(instances
|
||||
(project "ControlMixer"
|
||||
|
||||
12
ControlMixerPython/boot.py
Normal file
12
ControlMixerPython/boot.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import board
|
||||
import digitalio
|
||||
import usb_mixer
|
||||
recovery_pin = digitalio.DigitalInOut(board.D3)
|
||||
recovery_pin.direction = digitalio.Direction.INPUT
|
||||
recovery_pin.pull = digitalio.Pull.UP
|
||||
|
||||
if recovery_pin.value == True:
|
||||
print("Booting normal mode")
|
||||
usb_mixer.initUsb()
|
||||
else:
|
||||
print("Booting recovery mode")
|
||||
63
ControlMixerPython/code.py
Normal file
63
ControlMixerPython/code.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import usb_mixer
|
||||
import board
|
||||
import adafruit_dotstar
|
||||
import analogio
|
||||
import time
|
||||
import math
|
||||
import ulab.numpy
|
||||
import supervisor
|
||||
import key_interface
|
||||
import storage
|
||||
|
||||
ANALOG_SAMPLE_SIZE=150
|
||||
|
||||
pixel = adafruit_dotstar.DotStar(board.DOTSTAR_CLOCK, board.DOTSTAR_DATA, 1)
|
||||
slider_in = [
|
||||
analogio.AnalogIn(board.A0),
|
||||
analogio.AnalogIn(board.A1),
|
||||
analogio.AnalogIn(board.A2),
|
||||
analogio.AnalogIn(board.A3),
|
||||
analogio.AnalogIn(board.A4)
|
||||
]
|
||||
|
||||
i2c = board.I2C()
|
||||
keys = key_interface.KeyInterface(i2c)
|
||||
|
||||
slider_vals = [0,0,0,0,0]
|
||||
buttons = [0,0,0,0,0]
|
||||
|
||||
def readSlider(num):
|
||||
vals = []
|
||||
for i in range(0, ANALOG_SAMPLE_SIZE):
|
||||
vals.append((slider_in[num].value / 0xFFFF) * 0xFFF) # Scale analogio back to ADC resolution
|
||||
val_median = ulab.numpy.mean(ulab.numpy.ndarray(vals))
|
||||
return math.floor((val_median / 0xFFF) * usb_mixer.RESOLUTION)
|
||||
|
||||
try:
|
||||
pixel.fill((0,0,32))
|
||||
while not supervisor.runtime.usb_connected:
|
||||
time.sleep(0.5)
|
||||
|
||||
mixer = usb_mixer.UsbMixer()
|
||||
pixel.fill((0,32,0))
|
||||
while True:
|
||||
new_values = [
|
||||
readSlider(0),
|
||||
readSlider(1),
|
||||
readSlider(2),
|
||||
readSlider(3),
|
||||
readSlider(4)
|
||||
]
|
||||
new_buttons = keys.scan()
|
||||
if new_values != slider_vals or new_buttons != buttons:
|
||||
mixer.send_values(new_values[0], new_values[1], new_values[2], new_values[3], new_values[4], new_buttons)
|
||||
pixel.fill((32,32,32))
|
||||
slider_vals = new_values
|
||||
buttons = new_buttons
|
||||
pixel.fill((0,32,0))
|
||||
time.sleep(0.001)
|
||||
except Exception as ex:
|
||||
pixel.fill((255,0,0))
|
||||
with open("/runtime_ex.txt", "w") as fp:
|
||||
fp.write(ex)
|
||||
print(ex)
|
||||
40
ControlMixerPython/key_interface.py
Normal file
40
ControlMixerPython/key_interface.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import board
|
||||
import busio
|
||||
import digitalio
|
||||
from adafruit_mcp230xx.mcp23017 import MCP23017
|
||||
|
||||
class KeyInterface:
|
||||
_MCP23017_OLATA = 0x14
|
||||
_MCP23017_OLATB = 0x15
|
||||
|
||||
def set_olata(self, val: int):
|
||||
self.mcp._write_u8(self._MCP23017_OLATA, val)
|
||||
|
||||
def set_olatb(self, val: int):
|
||||
self.mcp._write_u8(self._MCP23017_OLATB, val)
|
||||
|
||||
def __init__(self, i2c):
|
||||
self._i2c = i2c
|
||||
|
||||
# Turn on MCP
|
||||
self._mcp_reset_pin = digitalio.DigitalInOut(board.D4)
|
||||
self._mcp_reset_pin.direction = digitalio.Direction.OUTPUT
|
||||
self._mcp_reset_pin.value = True
|
||||
|
||||
# Initialize MCP
|
||||
self.mcp = MCP23017(self._i2c)
|
||||
|
||||
# Set up MCP pins
|
||||
self.mcp.iodirb = 255 # GPIO B (rows) all input
|
||||
self.mcp.ipolb = 255 # GPIO B inverted polarity
|
||||
self.mcp.gppub = 255 # GPIO B all pulled up
|
||||
self.set_olata(0) # GPIO A all output low
|
||||
|
||||
def scan(self):
|
||||
result = []
|
||||
for i in range(0, 6):
|
||||
self.mcp.iodira = 0xFF ^ (0x01 << i)
|
||||
result.append(self.mcp.gpiob)
|
||||
self.mcp.iodira = 0xFF
|
||||
return result
|
||||
|
||||
BIN
ControlMixerPython/lib/adafruit_bus_device/i2c_device.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_bus_device/i2c_device.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_bus_device/spi_device.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_bus_device/spi_device.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_dotstar.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_dotstar.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_husb238.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_husb238.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/__init__.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/__init__.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/digital_inout.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/digital_inout.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23008.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23008.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23016.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23016.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23017.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23017.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp230xx.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp230xx.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23s08.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23s08.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23s17.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23s17.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23sxx.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23sxx.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23xxx.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_mcp230xx/mcp23xxx.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_register/i2c_bcd_alarm.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_register/i2c_bcd_alarm.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_register/i2c_bcd_datetime.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_register/i2c_bcd_datetime.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_register/i2c_bit.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_register/i2c_bit.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_register/i2c_bits.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_register/i2c_bits.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_register/i2c_struct.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_register/i2c_struct.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/adafruit_register/i2c_struct_array.mpy
Normal file
BIN
ControlMixerPython/lib/adafruit_register/i2c_struct_array.mpy
Normal file
Binary file not shown.
BIN
ControlMixerPython/lib/neopixel.mpy
Normal file
BIN
ControlMixerPython/lib/neopixel.mpy
Normal file
Binary file not shown.
111
ControlMixerPython/mixer_leds.py
Normal file
111
ControlMixerPython/mixer_leds.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from power import HUSB238
|
||||
import board
|
||||
import neopixel
|
||||
|
||||
VM_GREEN = (32,255,32)
|
||||
VM_RED = (255,16,16)
|
||||
|
||||
LED1 = 1 << 0 # 1
|
||||
LED2 = 1 << 1 # 2
|
||||
LED3 = 1 << 2 # 4
|
||||
LED4 = 1 << 3 # 8
|
||||
LED5 = 1 << 4 # 16
|
||||
LED6 = 1 << 5 # 32
|
||||
LED7 = 1 << 6 # 64
|
||||
LED8 = 1 << 7 # 128
|
||||
LED9 = 1 << 8 # 256
|
||||
LED10 = 1 << 9 # 512
|
||||
LED11 = 1 << 10 # 1024
|
||||
LED12 = 1 << 11 # 2048
|
||||
LED13 = 1 << 12 # 4096
|
||||
LED14 = 1 << 13 # 8192
|
||||
LED15 = 1 << 14 # 16384
|
||||
LED16 = 1 << 15 # 32768
|
||||
LED17 = 1 << 16 # 65536
|
||||
LED18 = 1 << 17 # 131072
|
||||
LED19 = 1 << 18 # 262144
|
||||
LED20 = 1 << 19 # 524288
|
||||
LED21 = 1 << 20 # 1048576
|
||||
LED22 = 1 << 21 # 2097152
|
||||
LED23 = 1 << 22 # 4194304
|
||||
LED24 = 1 << 23 # 8388608
|
||||
LED25 = 1 << 24 # 16777216
|
||||
LED26 = 1 << 25 # 33554432
|
||||
LED27 = 1 << 26 # 67108864
|
||||
LED28 = 1 << 27 # 134217728
|
||||
LED29 = 1 << 28 # 268435456
|
||||
LED30 = 1 << 29 # 536870912
|
||||
LED31 = 1 << 30 # 1073741824
|
||||
LED32 = 1 << 31 # 2147483648
|
||||
LED33 = 1 << 32 # 4294967296
|
||||
LED34 = 1 << 33 # 8589934592
|
||||
LED35 = 1 << 34 # 17179869184
|
||||
LED36 = 1 << 35 # 34359738368
|
||||
LED37 = 1 << 36 # 68719476736
|
||||
LED38 = 1 << 37 # 137438953472
|
||||
LED39 = 1 << 38 # 274877906944
|
||||
LED40 = 1 << 39 # 549755813888
|
||||
LED41 = 1 << 40 # 1099511627776
|
||||
LED42 = 1 << 41 # 2199023255552
|
||||
LED43 = 1 << 42 # 4398046511104
|
||||
LED44 = 1 << 43 # 8796093022208
|
||||
LED45 = 1 << 44 # 17592186044416
|
||||
|
||||
leds = [
|
||||
LED1, LED2, LED3, LED4, LED5,
|
||||
LED6, LED7, LED8, LED9, LED10,
|
||||
LED11, LED12, LED13, LED14, LED15,
|
||||
LED16, LED17, LED18, LED19, LED20,
|
||||
LED21, LED22, LED23, LED24, LED25,
|
||||
LED26, LED27, LED28, LED29, LED30,
|
||||
LED31, LED32, LED33, LED34, LED35,
|
||||
LED36, LED37, LED38, LED39, LED40,
|
||||
LED41, LED42, LED43, LED44, LED45
|
||||
]
|
||||
|
||||
def get_led_states(value):
|
||||
return [(value & led) != 0 for led in leds]
|
||||
|
||||
|
||||
_COLORS = [
|
||||
VM_RED, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN,
|
||||
VM_RED, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN,
|
||||
VM_RED, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN,
|
||||
VM_RED, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN,
|
||||
VM_RED, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN, VM_GREEN
|
||||
]
|
||||
|
||||
class MixerLeds:
|
||||
def __init__(self, i2c, max_brightness = 1):
|
||||
self._i2c = i2c
|
||||
self._pd = HUSB238(i2c)
|
||||
self._pixels = neopixel.NeoPixel(board.D5, 45, auto_write=False, brightness=max_brightness)
|
||||
self._known_wattage = 0
|
||||
self._max_brightness = max_brightness
|
||||
|
||||
def _init_pd(self):
|
||||
if not self._pd.connect():
|
||||
print('Init PD failed to start connection')
|
||||
return False
|
||||
if self._known_wattage == self._pd.wattage:
|
||||
return True
|
||||
available_wattage = self._pd.attempt_wattage(30)
|
||||
self._known_wattage = available_wattage[1]
|
||||
if not available_wattage[0]:
|
||||
self._pixels.deinit()
|
||||
available_brightness = max(available_wattage[1]/30.0, self._max_brightness)
|
||||
self._pixels = neopixel.NeoPixel(board.D5, 45, brightness=available_brightness, auto_write=False)
|
||||
|
||||
return True
|
||||
|
||||
def set_state(self, state):
|
||||
if not self._init_pd():
|
||||
return False
|
||||
|
||||
for idx, led_state in enumerate(get_led_states(state)):
|
||||
if (led_state):
|
||||
self._pixels[idx] = _COLORS[idx]
|
||||
else:
|
||||
self._pixels[idx] = (0,0,0)
|
||||
self._pixels.show()
|
||||
return True
|
||||
88
ControlMixerPython/power.py
Normal file
88
ControlMixerPython/power.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import adafruit_husb238
|
||||
import time
|
||||
|
||||
class HUSB238:
|
||||
_pd = None
|
||||
|
||||
def __init__(self, i2c):
|
||||
self._i2c = i2c
|
||||
|
||||
def connect(self):
|
||||
if (self.is_connected()):
|
||||
try:
|
||||
return self._pd.attached
|
||||
except Exception as e:
|
||||
return False
|
||||
try:
|
||||
self._pd = adafruit_husb238.Adafruit_HUSB238(self._i2c)
|
||||
return True
|
||||
except Exception as e:
|
||||
self._pd = None
|
||||
return False
|
||||
|
||||
def is_connected(self):
|
||||
if self._pd is not None:
|
||||
try:
|
||||
return self._pd.attached
|
||||
except Exception as e:
|
||||
self._pd = None
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def attempt_wattage(self, watts):
|
||||
if not self.is_connected():
|
||||
return False, 0
|
||||
if self.wattage >= watts:
|
||||
return True, self.wattage
|
||||
voltages = self.available_voltages
|
||||
next_voltage_index = voltages.index(self.voltage) + 1
|
||||
while self.wattage < watts and next_voltage_index < len(voltages):
|
||||
if not self.is_connected():
|
||||
print('Connection lost')
|
||||
return False, 0
|
||||
target = voltages[next_voltage_index]
|
||||
self.voltage = target
|
||||
time.sleep(1)
|
||||
if (self._pd.voltage != target):
|
||||
print(f'Voltage did not switch to {target}v')
|
||||
return False, self.wattage
|
||||
next_voltage_index += 1
|
||||
resulting_wattage = self.wattage
|
||||
return resulting_wattage >= watts, resulting_wattage
|
||||
|
||||
@property
|
||||
def wattage(self):
|
||||
return self.voltage * self.current
|
||||
|
||||
@property
|
||||
def available_voltages(self):
|
||||
return self._safe_pd_call(lambda: self._pd.available_voltages, [])
|
||||
|
||||
@property
|
||||
def voltage(self):
|
||||
return self._safe_pd_call(lambda: self._pd.voltage, 0);
|
||||
|
||||
@voltage.setter
|
||||
def voltage(self, value):
|
||||
self._pd.voltage = value
|
||||
|
||||
@property
|
||||
def current(self):
|
||||
return self._safe_pd_call(lambda: self._pd.current, 0)
|
||||
|
||||
def _unsafe_pd_call(self, func, default_val, attempts=20):
|
||||
attempt_count = 0
|
||||
while attempt_count < attempts:
|
||||
try:
|
||||
return func()
|
||||
except Exception as e:
|
||||
print('unsafe waiting')
|
||||
time.sleep(0.5)
|
||||
return default_val
|
||||
|
||||
def _safe_pd_call(self, func, default_val):
|
||||
if self.is_connected():
|
||||
return func()
|
||||
else:
|
||||
return default_val
|
||||
1
ControlMixerPython/sd/placeholder.txt
Normal file
1
ControlMixerPython/sd/placeholder.txt
Normal file
@@ -0,0 +1 @@
|
||||
SD cards mounted at /sd will hide this file from Python. SD cards are not visible via USB CIRCUITPY.
|
||||
0
ControlMixerPython/settings.toml
Normal file
0
ControlMixerPython/settings.toml
Normal file
86
ControlMixerPython/usb_mixer.py
Normal file
86
ControlMixerPython/usb_mixer.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import usb_hid
|
||||
import supervisor
|
||||
import storage
|
||||
import usb_midi
|
||||
|
||||
DEFAULT_REPORT_ID = 1
|
||||
HANDSHAKE_REPORT_ID = 2
|
||||
VENDOR_ID = 0x0359
|
||||
PRODUCT_ID = 0x6497 # T9 MIXR
|
||||
|
||||
RESOLUTION = 100 # Voicemeeter sliders go from 12.0 to -60.0 in 0.1 increments
|
||||
|
||||
REPORT_DESCRIPTOR = bytes([
|
||||
0x05, 0x01, # Usage Page (Generic Desktop Controls)
|
||||
0x09, 0x08, # Usage (Multi-Axis Controller)
|
||||
#0x09, 0x04, # Joystick usage (for windows to pick up as usb controller)
|
||||
0xA1, 0x01, # Collection (Application)
|
||||
# BUTTONS
|
||||
0x05, 0x09, # Usage Page (Button)
|
||||
0x19, 0x01, # Usage Minimum (Button 1)
|
||||
0x29, 0x30, # Usage Maximum (Button 48)
|
||||
0x15, 0x00, # Logical Minimum (0)
|
||||
0x25, 0x01, # Logical Maximum (1)
|
||||
0x75, 0x01, # Report Size (1 bit)
|
||||
0x95, 0x30, # Report Count (48)
|
||||
0x81, 0x02, # Input (Data, Variable, Absolute)
|
||||
# SLIDERS
|
||||
0x15, 0x00, # Logical Minimum (0)
|
||||
0x26, 0xFF, 0xFF, # Logical Maximum (RESOLUTION (720))
|
||||
0x75, 0x10, # Report Size (16-bit)
|
||||
#0x85, DEFAULT_REPORT_ID, # Report ID (DEFAULT_REPORT_ID)
|
||||
0x95, 0x05, # Report Count (5)
|
||||
0x09, 0x30, # Usage (X)
|
||||
0x09, 0x31, # Usage (Y)
|
||||
0x09, 0x32, # Usage (Z)
|
||||
0x09, 0x33, # Usage (Rx)
|
||||
0x09, 0x34, # Usage (Ry)
|
||||
0x81, 0x00, # Input (Data, Array, Absolute)
|
||||
0xC0, # End Collection
|
||||
#0x05, 0x06, # Usage Page (Generic Device Controls)
|
||||
#0x09, 0x01, # Usage (Background/Nonuser controls)
|
||||
#0xA1, 0x01, # Collection (Application)
|
||||
#0x75, 0x20, # Report Size (32-bit)
|
||||
#0x95, 0x01, # Report Count (1)
|
||||
#0x85, HANDSHAKE_REPORT_ID,# Report ID (HANDSHAKE_REPORT_ID)
|
||||
#0x81, 0x01, # Input (Constant, Array, Absolute)
|
||||
#0xC0, # End Collection
|
||||
])
|
||||
|
||||
def initUsb():
|
||||
if supervisor.runtime.usb_connected:
|
||||
raise RuntimeError("USB cannot be initialized post-boot")
|
||||
supervisor.set_usb_identification(manufacturer="TechAbsol", product="USB Mixer", vid=VENDOR_ID, pid=PRODUCT_ID)
|
||||
|
||||
usb_midi.disable()
|
||||
usb_hid.set_interface_name("CircuitPython Mixer")
|
||||
sliders = usb_hid.Device(
|
||||
report_descriptor = REPORT_DESCRIPTOR,
|
||||
usage_page=0x01,
|
||||
usage=0x08,
|
||||
report_ids=(0,),
|
||||
in_report_lengths=(16,),
|
||||
out_report_lengths=(0,),
|
||||
)
|
||||
|
||||
#storage.disable_usb_drive()
|
||||
#storage.remount("/", False)
|
||||
usb_hid.enable((sliders,))
|
||||
|
||||
class UsbMixer():
|
||||
def __init__(self):
|
||||
if not supervisor.runtime.usb_connected:
|
||||
raise RuntimeError("USB is not connected")
|
||||
self._device = next((dev for dev in usb_hid.devices if dev.usage_page == 0x01 and dev.usage == 0x08))
|
||||
if self._device is None:
|
||||
raise RuntimeError("USB mixer doesn't appear to be initialized")
|
||||
|
||||
def _valToBytes(self, value):
|
||||
return bytes([value & 0xFF, (value >> 8) & 0xFF])
|
||||
|
||||
def _keysToBytes(self, keys):
|
||||
return bytes([keys[0], keys[1], keys[2], keys[3], keys[4], keys[5]])
|
||||
|
||||
def send_values(self, slider1, slider2, slider3, slider4, slider5, keys):
|
||||
report = self._keysToBytes(keys) + self._valToBytes(slider1) + self._valToBytes(slider2) + self._valToBytes(slider3) + self._valToBytes(slider4) + self._valToBytes(slider5)
|
||||
self._device.send_report(report)
|
||||
44
HidSliders.Gui/Form1.Designer.cs
generated
44
HidSliders.Gui/Form1.Designer.cs
generated
@@ -108,6 +108,7 @@
|
||||
mixerAutoConnectCheckbox = new CheckBox();
|
||||
mixerLabel = new Label();
|
||||
notifyIcon1 = new NotifyIcon(components);
|
||||
listBox1 = new ListBox();
|
||||
groupBox1.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)trackBar1).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)trackBar2).BeginInit();
|
||||
@@ -480,6 +481,7 @@
|
||||
checkBox5.TabIndex = 38;
|
||||
checkBox5.Text = "B3";
|
||||
checkBox5.UseVisualStyleBackColor = true;
|
||||
checkBox5.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox6
|
||||
//
|
||||
@@ -491,6 +493,7 @@
|
||||
checkBox6.TabIndex = 37;
|
||||
checkBox6.Text = "B2";
|
||||
checkBox6.UseVisualStyleBackColor = true;
|
||||
checkBox6.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox7
|
||||
//
|
||||
@@ -502,6 +505,7 @@
|
||||
checkBox7.TabIndex = 36;
|
||||
checkBox7.Text = "B1";
|
||||
checkBox7.UseVisualStyleBackColor = true;
|
||||
checkBox7.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox8
|
||||
//
|
||||
@@ -513,6 +517,7 @@
|
||||
checkBox8.TabIndex = 35;
|
||||
checkBox8.Text = "A5";
|
||||
checkBox8.UseVisualStyleBackColor = true;
|
||||
checkBox8.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox9
|
||||
//
|
||||
@@ -524,6 +529,7 @@
|
||||
checkBox9.TabIndex = 34;
|
||||
checkBox9.Text = "A4";
|
||||
checkBox9.UseVisualStyleBackColor = true;
|
||||
checkBox9.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox10
|
||||
//
|
||||
@@ -535,6 +541,7 @@
|
||||
checkBox10.TabIndex = 33;
|
||||
checkBox10.Text = "A3";
|
||||
checkBox10.UseVisualStyleBackColor = true;
|
||||
checkBox10.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox11
|
||||
//
|
||||
@@ -547,6 +554,7 @@
|
||||
checkBox11.Tag = "A2";
|
||||
checkBox11.Text = "A2";
|
||||
checkBox11.UseVisualStyleBackColor = true;
|
||||
checkBox11.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox12
|
||||
//
|
||||
@@ -559,6 +567,7 @@
|
||||
checkBox12.Tag = "";
|
||||
checkBox12.Text = "A1";
|
||||
checkBox12.UseVisualStyleBackColor = true;
|
||||
checkBox12.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox13
|
||||
//
|
||||
@@ -594,6 +603,7 @@
|
||||
checkBox15.TabIndex = 48;
|
||||
checkBox15.Text = "B3";
|
||||
checkBox15.UseVisualStyleBackColor = true;
|
||||
checkBox15.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox16
|
||||
//
|
||||
@@ -605,6 +615,7 @@
|
||||
checkBox16.TabIndex = 47;
|
||||
checkBox16.Text = "B2";
|
||||
checkBox16.UseVisualStyleBackColor = true;
|
||||
checkBox16.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox17
|
||||
//
|
||||
@@ -616,6 +627,7 @@
|
||||
checkBox17.TabIndex = 46;
|
||||
checkBox17.Text = "B1";
|
||||
checkBox17.UseVisualStyleBackColor = true;
|
||||
checkBox17.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox18
|
||||
//
|
||||
@@ -627,6 +639,7 @@
|
||||
checkBox18.TabIndex = 45;
|
||||
checkBox18.Text = "A5";
|
||||
checkBox18.UseVisualStyleBackColor = true;
|
||||
checkBox18.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox19
|
||||
//
|
||||
@@ -638,6 +651,7 @@
|
||||
checkBox19.TabIndex = 44;
|
||||
checkBox19.Text = "A4";
|
||||
checkBox19.UseVisualStyleBackColor = true;
|
||||
checkBox19.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox20
|
||||
//
|
||||
@@ -649,6 +663,7 @@
|
||||
checkBox20.TabIndex = 43;
|
||||
checkBox20.Text = "A3";
|
||||
checkBox20.UseVisualStyleBackColor = true;
|
||||
checkBox20.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox21
|
||||
//
|
||||
@@ -661,6 +676,7 @@
|
||||
checkBox21.Tag = "A2";
|
||||
checkBox21.Text = "A2";
|
||||
checkBox21.UseVisualStyleBackColor = true;
|
||||
checkBox21.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox22
|
||||
//
|
||||
@@ -673,6 +689,7 @@
|
||||
checkBox22.Tag = "";
|
||||
checkBox22.Text = "A1";
|
||||
checkBox22.UseVisualStyleBackColor = true;
|
||||
checkBox22.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox23
|
||||
//
|
||||
@@ -708,6 +725,7 @@
|
||||
checkBox25.TabIndex = 58;
|
||||
checkBox25.Text = "B3";
|
||||
checkBox25.UseVisualStyleBackColor = true;
|
||||
checkBox25.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox26
|
||||
//
|
||||
@@ -719,6 +737,7 @@
|
||||
checkBox26.TabIndex = 57;
|
||||
checkBox26.Text = "B2";
|
||||
checkBox26.UseVisualStyleBackColor = true;
|
||||
checkBox26.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox27
|
||||
//
|
||||
@@ -730,6 +749,7 @@
|
||||
checkBox27.TabIndex = 56;
|
||||
checkBox27.Text = "B1";
|
||||
checkBox27.UseVisualStyleBackColor = true;
|
||||
checkBox27.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox28
|
||||
//
|
||||
@@ -741,6 +761,7 @@
|
||||
checkBox28.TabIndex = 55;
|
||||
checkBox28.Text = "A5";
|
||||
checkBox28.UseVisualStyleBackColor = true;
|
||||
checkBox28.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox29
|
||||
//
|
||||
@@ -752,6 +773,7 @@
|
||||
checkBox29.TabIndex = 54;
|
||||
checkBox29.Text = "A4";
|
||||
checkBox29.UseVisualStyleBackColor = true;
|
||||
checkBox29.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox30
|
||||
//
|
||||
@@ -763,6 +785,7 @@
|
||||
checkBox30.TabIndex = 53;
|
||||
checkBox30.Text = "A3";
|
||||
checkBox30.UseVisualStyleBackColor = true;
|
||||
checkBox30.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox31
|
||||
//
|
||||
@@ -775,6 +798,7 @@
|
||||
checkBox31.Tag = "A2";
|
||||
checkBox31.Text = "A2";
|
||||
checkBox31.UseVisualStyleBackColor = true;
|
||||
checkBox31.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox32
|
||||
//
|
||||
@@ -787,6 +811,7 @@
|
||||
checkBox32.Tag = "";
|
||||
checkBox32.Text = "A1";
|
||||
checkBox32.UseVisualStyleBackColor = true;
|
||||
checkBox32.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox33
|
||||
//
|
||||
@@ -822,6 +847,7 @@
|
||||
checkBox35.TabIndex = 68;
|
||||
checkBox35.Text = "B3";
|
||||
checkBox35.UseVisualStyleBackColor = true;
|
||||
checkBox35.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox36
|
||||
//
|
||||
@@ -833,6 +859,7 @@
|
||||
checkBox36.TabIndex = 67;
|
||||
checkBox36.Text = "B2";
|
||||
checkBox36.UseVisualStyleBackColor = true;
|
||||
checkBox36.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox37
|
||||
//
|
||||
@@ -844,6 +871,7 @@
|
||||
checkBox37.TabIndex = 66;
|
||||
checkBox37.Text = "B1";
|
||||
checkBox37.UseVisualStyleBackColor = true;
|
||||
checkBox37.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox38
|
||||
//
|
||||
@@ -855,6 +883,7 @@
|
||||
checkBox38.TabIndex = 65;
|
||||
checkBox38.Text = "A5";
|
||||
checkBox38.UseVisualStyleBackColor = true;
|
||||
checkBox38.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox39
|
||||
//
|
||||
@@ -866,6 +895,7 @@
|
||||
checkBox39.TabIndex = 64;
|
||||
checkBox39.Text = "A4";
|
||||
checkBox39.UseVisualStyleBackColor = true;
|
||||
checkBox39.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox40
|
||||
//
|
||||
@@ -877,6 +907,7 @@
|
||||
checkBox40.TabIndex = 63;
|
||||
checkBox40.Text = "A3";
|
||||
checkBox40.UseVisualStyleBackColor = true;
|
||||
checkBox40.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox41
|
||||
//
|
||||
@@ -889,6 +920,7 @@
|
||||
checkBox41.Tag = "A2";
|
||||
checkBox41.Text = "A2";
|
||||
checkBox41.UseVisualStyleBackColor = true;
|
||||
checkBox41.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// checkBox42
|
||||
//
|
||||
@@ -901,6 +933,7 @@
|
||||
checkBox42.Tag = "";
|
||||
checkBox42.Text = "A1";
|
||||
checkBox42.UseVisualStyleBackColor = true;
|
||||
checkBox42.CheckedChanged += busCheckbox_CheckedChanged;
|
||||
//
|
||||
// slider1Label
|
||||
//
|
||||
@@ -986,11 +1019,21 @@
|
||||
notifyIcon1.Visible = true;
|
||||
notifyIcon1.Click += notifyIcon1_Click;
|
||||
//
|
||||
// listBox1
|
||||
//
|
||||
listBox1.FormattingEnabled = true;
|
||||
listBox1.ItemHeight = 15;
|
||||
listBox1.Location = new Point(12, 278);
|
||||
listBox1.Name = "listBox1";
|
||||
listBox1.Size = new Size(200, 64);
|
||||
listBox1.TabIndex = 77;
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(780, 363);
|
||||
Controls.Add(listBox1);
|
||||
Controls.Add(groupBox2);
|
||||
Controls.Add(slider5Label);
|
||||
Controls.Add(slider4Label);
|
||||
@@ -1160,5 +1203,6 @@
|
||||
private CheckBox mixerAutoConnectCheckbox;
|
||||
private Label mixerLabel;
|
||||
private NotifyIcon notifyIcon1;
|
||||
private ListBox listBox1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using HidLibrary;
|
||||
using HidSliders.Hid;
|
||||
using Voicemeeter;
|
||||
using VoiceMeeter;
|
||||
using Timer = System.Windows.Forms.Timer;
|
||||
|
||||
namespace HidSliders.Gui
|
||||
{
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
private IDisposable? _vmClient;
|
||||
private IHidDevice? _hidDevice;
|
||||
private const float Resolution = 72.0f;
|
||||
private bool _mixerConnected;
|
||||
private List<MixerButton> _previousButtons = [];
|
||||
private readonly HidInterface _hidInterface;
|
||||
private const int ZeroPercent = 90;
|
||||
private readonly List<ChannelControlCollection> _channels;
|
||||
private bool _suppressCheckboxUpdate = false;
|
||||
private Timer _syncTimer = new();
|
||||
|
||||
public Form1()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -44,24 +48,134 @@ namespace HidSliders.Gui
|
||||
_channels[3].ChannelSelector.Value = Properties.Settings.Default.Channel4Strip;
|
||||
_channels[4].ChannelSelector.Value = Properties.Settings.Default.Channel5Strip;
|
||||
|
||||
_syncTimer.Tick += SyncVM;
|
||||
_syncTimer.Interval = 100;
|
||||
_syncTimer.Enabled = true;
|
||||
if (Properties.Settings.Default.AutoConnect)
|
||||
{
|
||||
autoConnectCheckBox.Checked = true;
|
||||
Task.Run(ConnectVoicemeeter).Wait();
|
||||
}
|
||||
|
||||
var enumerator = new HidEnumerator();
|
||||
var hidDevices = enumerator.Enumerate(0x0359, [0x6497]);
|
||||
_hidDevice = hidDevices.FirstOrDefault();
|
||||
voicemeeterStatusLabel.Text = $"Mixer {(_hidDevice?.IsConnected == true ? "" : "not ")}connected";
|
||||
_hidInterface = new HidInterface();
|
||||
mixerLabel.Text = "Mixer not connected";
|
||||
|
||||
if (_hidDevice?.IsConnected == true && Properties.Settings.Default.MixerAutoConnect)
|
||||
if (Properties.Settings.Default.MixerAutoConnect)
|
||||
{
|
||||
UsbListenerLoop();
|
||||
ConnectHidMixer();
|
||||
mixerAutoConnectCheckbox.Checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSliderValues(int[] values)
|
||||
{
|
||||
this.BeginInvoke(() =>
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var gain = HidToGain(values[i]);
|
||||
SetStripGain(_channels[i].CurrentStrip, gain);
|
||||
_channels[i].TrackBar.Value = (int)gain;
|
||||
_channels[i].IndicatorLabel.Text = $"{gain} dB";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleButtonPress(MixerButton button)
|
||||
{
|
||||
var buttonGroup = MixerButtonHelper.GetButtonGroup(button);
|
||||
var channelButton = MixerButtonHelper.GetChannelButton(button, buttonGroup);
|
||||
|
||||
ChannelControlCollection channel;
|
||||
switch (buttonGroup)
|
||||
{
|
||||
case ButtonGroups.Channel1:
|
||||
channel = _channels[0];
|
||||
break;
|
||||
case ButtonGroups.Channel2:
|
||||
channel = _channels[1];
|
||||
break;
|
||||
case ButtonGroups.Channel3:
|
||||
channel = _channels[2];
|
||||
break;
|
||||
case ButtonGroups.Channel4:
|
||||
channel = _channels[3];
|
||||
break;
|
||||
case ButtonGroups.Channel5:
|
||||
channel = _channels[4];
|
||||
break;
|
||||
default:
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
switch (channelButton)
|
||||
{
|
||||
case ChannelButton.ChannelA1:
|
||||
channel.BusA1Checkbox.Checked = !channel.BusA1Checkbox.Checked;
|
||||
break;
|
||||
case ChannelButton.ChannelA2:
|
||||
channel.BusA2Checkbox.Checked = !channel.BusA2Checkbox.Checked;
|
||||
break;
|
||||
case ChannelButton.ChannelA3:
|
||||
channel.BusA3Checkbox.Checked = !channel.BusA3Checkbox.Checked;
|
||||
break;
|
||||
case ChannelButton.ChannelA4:
|
||||
channel.BusA4Checkbox.Checked = !channel.BusA4Checkbox.Checked;
|
||||
break;
|
||||
case ChannelButton.ChannelA5:
|
||||
channel.BusA5Checkbox.Checked = !channel.BusA5Checkbox.Checked;
|
||||
break;
|
||||
case ChannelButton.ChannelB1:
|
||||
channel.BusB1Checkbox.Checked = !channel.BusB1Checkbox.Checked;
|
||||
break;
|
||||
case ChannelButton.ChannelB2:
|
||||
channel.BusB2Checkbox.Checked = !channel.BusB2Checkbox.Checked;
|
||||
break;
|
||||
case ChannelButton.ChannelB3:
|
||||
channel.BusB3Checkbox.Checked = !channel.BusB3Checkbox.Checked;
|
||||
break;
|
||||
case ChannelButton.ChannelMute:
|
||||
channel.MuteCheckBox.Checked = !channel.MuteCheckBox.Checked;
|
||||
break;
|
||||
default:
|
||||
throw new Exception();
|
||||
}
|
||||
listBox1.Items.Add($"{buttonGroup} {channelButton} pressed");
|
||||
}
|
||||
|
||||
private void OnButtons(List<MixerButton> buttons)
|
||||
{
|
||||
BeginInvoke(() =>
|
||||
{
|
||||
var newButtons = buttons.Except(_previousButtons).ToList();
|
||||
_previousButtons = buttons;
|
||||
|
||||
newButtons.ForEach(HandleButtonPress);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnSliderComplete()
|
||||
{
|
||||
BeginInvoke(() =>
|
||||
{
|
||||
_mixerConnected = false;
|
||||
button1.Enabled = true;
|
||||
mixerLabel.Text = "Mixer disconnected";
|
||||
});
|
||||
}
|
||||
|
||||
private void ConnectHidMixer()
|
||||
{
|
||||
_mixerConnected = _hidInterface.ConnectDevice(0x0359, 0x6497);
|
||||
if (_mixerConnected)
|
||||
{
|
||||
mixerLabel.Text = "Mixer connected";
|
||||
button1.Enabled = false;
|
||||
_hidInterface.SliderObservable.Subscribe(OnSliderValues, OnSliderComplete);
|
||||
_hidInterface.ButtonsObservable.Subscribe(OnButtons);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectVoicemeeter()
|
||||
{
|
||||
RunVoicemeeterParam version = RunVoicemeeterParam.None;
|
||||
@@ -84,6 +198,7 @@ namespace HidSliders.Gui
|
||||
{
|
||||
EnableVMControls(version);
|
||||
voicemeeterStatusLabel.Text = "Connected";
|
||||
_syncTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +207,16 @@ namespace HidSliders.Gui
|
||||
await ConnectVoicemeeter();
|
||||
}
|
||||
|
||||
private void SyncVM(object? sender, EventArgs eventArgs)
|
||||
{
|
||||
if (_vmClient == null || Remote.IsParametersDirty() != 1) return;
|
||||
|
||||
foreach (var channelControlCollection in _channels)
|
||||
{
|
||||
SyncChannelControls(channelControlCollection);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableVMControls(RunVoicemeeterParam version)
|
||||
{
|
||||
foreach (var channel in _channels)
|
||||
@@ -106,6 +231,7 @@ namespace HidSliders.Gui
|
||||
|
||||
private void SyncChannelControls(ChannelControlCollection channel)
|
||||
{
|
||||
_suppressCheckboxUpdate = true;
|
||||
channel.BusA1Checkbox.Checked = GetBusEnabled(channel.CurrentStrip, VMBus.A1);
|
||||
channel.BusB1Checkbox.Checked = GetBusEnabled(channel.CurrentStrip, VMBus.B1);
|
||||
if (vmBananaRadio.Checked || vmPotatoRadio.Checked)
|
||||
@@ -125,6 +251,7 @@ namespace HidSliders.Gui
|
||||
channel.TrackBar.Value = (int)GetStripGain(channel.CurrentStrip);
|
||||
channel.SoloCheckBox.Checked = GetChannelSolo(channel.CurrentStrip);
|
||||
channel.MuteCheckBox.Checked = GetChannelMute(channel.CurrentStrip);
|
||||
_suppressCheckboxUpdate = false;
|
||||
}
|
||||
|
||||
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
|
||||
@@ -133,44 +260,9 @@ namespace HidSliders.Gui
|
||||
Properties.Settings.Default.Save();
|
||||
}
|
||||
|
||||
private async void UsbListenerLoop()
|
||||
{
|
||||
if (_hidDevice == null) return;
|
||||
mixerLabel.Text = "Mixer online";
|
||||
while (true)
|
||||
{
|
||||
if (_hidDevice.IsConnected)
|
||||
{
|
||||
button1.Enabled = false;
|
||||
//label1.Text = "waiting for report";
|
||||
var progress = new Progress<string>();
|
||||
progress.ProgressChanged += (o, s) => voicemeeterStatusLabel.Text = s;
|
||||
var report = await Task.Run(() => _hidDevice.ReadReport());
|
||||
if (report.ReadStatus == HidDeviceData.ReadStatus.Success)
|
||||
{
|
||||
var reportChunk = new byte[2];
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Array.Copy(report.Data, i * 2, reportChunk, 0, 2);
|
||||
var sliderVal = BitConverter.ToInt16(reportChunk);
|
||||
var gain = HidToGain(sliderVal);
|
||||
SetStripGain(_channels[i].CurrentStrip, gain);
|
||||
_channels[i].TrackBar.Value = (int)gain;
|
||||
_channels[i].IndicatorLabel.Text = $"{gain} dB";
|
||||
}
|
||||
}
|
||||
//button1.Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
button1.Enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void button1_Click(object sender, EventArgs e)
|
||||
{
|
||||
UsbListenerLoop();
|
||||
ConnectHidMixer();
|
||||
}
|
||||
|
||||
private float HidToGain(int hidValue)
|
||||
@@ -179,11 +271,43 @@ namespace HidSliders.Gui
|
||||
// Truncate to first decimal (move decimal, int truncate, move decimal)
|
||||
//var realResult = 5.8456f + 17.0486f * (float)Math.Log(Math.Max(hidValue / Resolution, 0.02));
|
||||
//return ((int)(realResult * 10)) / 10.0f;
|
||||
return hidValue - 60;
|
||||
return (int)(CalculateGain(hidValue, ZeroPercent) * 10) / 10.0f;
|
||||
}
|
||||
|
||||
public static float CalculateGain(float slider, int zeroPct)
|
||||
{
|
||||
float minSlider;
|
||||
float maxSlider;
|
||||
float minGain;
|
||||
float maxGain;
|
||||
|
||||
if (slider >= zeroPct)
|
||||
{
|
||||
minSlider = zeroPct;
|
||||
maxSlider = 100;
|
||||
minGain = 0;
|
||||
maxGain = 12;
|
||||
}
|
||||
else
|
||||
{
|
||||
minSlider = 0;
|
||||
maxSlider = zeroPct;
|
||||
minGain = -60;
|
||||
maxGain = 0;
|
||||
}
|
||||
|
||||
// Calculate the percentage of slider position within the range
|
||||
var percentage = (slider - minSlider) / (maxSlider - minSlider);
|
||||
|
||||
// Interpolate gain within the range based on the percentage
|
||||
var calculatedGain = minGain + (percentage * (maxGain - minGain));
|
||||
|
||||
return calculatedGain;
|
||||
}
|
||||
|
||||
private void trackBar_Scroll(object sender, EventArgs e)
|
||||
{
|
||||
if (_suppressCheckboxUpdate) return;
|
||||
if (sender is TrackBar trackBar && _vmClient != null)
|
||||
{
|
||||
var channel = _channels.First(c => c.OwnsControl(trackBar));
|
||||
@@ -193,6 +317,7 @@ namespace HidSliders.Gui
|
||||
|
||||
private void busCheckbox_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_suppressCheckboxUpdate) return;
|
||||
if (sender is CheckBox { Tag: VMBus bus } checkBox && _vmClient != null)
|
||||
{
|
||||
var channel = _channels.First(c => c.OwnsControl(checkBox));
|
||||
@@ -243,8 +368,6 @@ namespace HidSliders.Gui
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void SetBusEnabled(int strip, VMBus bus, bool enabled)
|
||||
{
|
||||
Remote.SetParameter($"Strip[{strip}].{bus}", enabled ? 1 : 0);
|
||||
@@ -292,6 +415,7 @@ namespace HidSliders.Gui
|
||||
|
||||
private void soloCheckBox_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_suppressCheckboxUpdate) return;
|
||||
if (sender is CheckBox checkBox && _vmClient != null)
|
||||
{
|
||||
var channel = _channels.First(c => c.OwnsControl(checkBox));
|
||||
@@ -301,6 +425,7 @@ namespace HidSliders.Gui
|
||||
|
||||
private void muteCheckBox_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_suppressCheckboxUpdate) return;
|
||||
if (sender is CheckBox checkBox && _vmClient != null)
|
||||
{
|
||||
var channel = _channels.First(c => c.OwnsControl(checkBox));
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="hidlibrary" Version="3.3.40" />
|
||||
<PackageReference Include="hidlibrary" Version="3.2.49" />
|
||||
<PackageReference Include="VoicemeeterRemote" Version="1.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HidSliders.Hid\HidSliders.Hid.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace HidSliders.Hid
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
87
HidSliders.Hid/HidInterface.cs
Normal file
87
HidSliders.Hid/HidInterface.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Reactive.Subjects;
|
||||
using HidLibrary;
|
||||
|
||||
namespace HidSliders.Hid
|
||||
{
|
||||
public class HidInterface
|
||||
{
|
||||
private IHidDevice? _device;
|
||||
private Subject<int[]> _sliderUpdate = new();
|
||||
private Subject<List<MixerButton>> _buttonsUpdate = new();
|
||||
|
||||
public IObservable<int[]> SliderObservable => _sliderUpdate;
|
||||
public IObservable<List<MixerButton>> ButtonsObservable => _buttonsUpdate;
|
||||
|
||||
public bool ConnectDevice(int vendorId, int devId)
|
||||
{
|
||||
var enumerator = new HidEnumerator();
|
||||
IEnumerable<IHidDevice> hidDevices = enumerator.Enumerate(vendorId, [devId]);
|
||||
|
||||
_device = hidDevices.FirstOrDefault();
|
||||
|
||||
if (_device is { IsConnected: true })
|
||||
{
|
||||
_device.OpenDevice();
|
||||
_device.MonitorDeviceEvents = true;
|
||||
|
||||
_device.ReadReport(OnReport);
|
||||
_device.Removed += DeviceOnRemoved;
|
||||
_device.Inserted += DeviceOnInserted;
|
||||
}
|
||||
|
||||
return _device?.IsConnected ?? false;
|
||||
}
|
||||
|
||||
private void DeviceOnInserted()
|
||||
{
|
||||
_sliderUpdate = new Subject<int[]>();
|
||||
_buttonsUpdate = new Subject<List<MixerButton>>();
|
||||
}
|
||||
|
||||
private void DeviceOnRemoved()
|
||||
{
|
||||
_sliderUpdate.OnCompleted();
|
||||
_buttonsUpdate.OnCompleted();
|
||||
}
|
||||
|
||||
private void ReadSliders(byte[] reportSliderBytes)
|
||||
{
|
||||
var reportChunk = new byte[4];
|
||||
var sliderValues = new int[5];
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Array.Copy(reportSliderBytes, i * 2, reportChunk, 0, 2);
|
||||
sliderValues[i] = BitConverter.ToInt32(reportChunk);
|
||||
}
|
||||
_sliderUpdate.OnNext(sliderValues);
|
||||
}
|
||||
|
||||
private void ReadButtons(byte[] reportButtonBytes)
|
||||
{
|
||||
if (reportButtonBytes.Length != 8)
|
||||
{
|
||||
throw new ArgumentException("incoming report data was not padded to 8 bytes");
|
||||
}
|
||||
var buttonLong = BitConverter.ToInt64(reportButtonBytes);
|
||||
_buttonsUpdate.OnNext(MixerButtonHelper.GetSetFlags(buttonLong));
|
||||
}
|
||||
|
||||
private void ReadReport(byte[] reportBytes)
|
||||
{
|
||||
var buttons = new byte[8];
|
||||
var sliders = new byte[10];
|
||||
Array.Copy(reportBytes, 0, buttons, 0, 6);
|
||||
Array.Copy(reportBytes, 6, sliders, 0, 10);
|
||||
ReadSliders(sliders);
|
||||
ReadButtons(buttons);
|
||||
}
|
||||
|
||||
private void OnReport(HidReport report)
|
||||
{
|
||||
if (!_device!.IsConnected) return;
|
||||
|
||||
ReadReport(report.Data);
|
||||
_device!.ReadReport(OnReport);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="hidlibrary" Version="3.3.40" />
|
||||
<PackageReference Include="hidlibrary" Version="3.2.49" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
75
HidSliders.Hid/MixerButton.cs
Normal file
75
HidSliders.Hid/MixerButton.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace HidSliders.Hid;
|
||||
|
||||
[Flags]
|
||||
public enum MixerButton : long
|
||||
{
|
||||
Button1 = 1L << 0, // 1
|
||||
Button2 = 1L << 1, // 2
|
||||
Button3 = 1L << 2, // 4
|
||||
Button4 = 1L << 3, // 8
|
||||
Button5 = 1L << 4, // 16
|
||||
Button6 = 1L << 5, // 32
|
||||
Button7 = 1L << 6, // 64
|
||||
Button8 = 1L << 7, // 128
|
||||
Button9 = 1L << 8, // 256
|
||||
Button10 = 1L << 9, // 512
|
||||
Button11 = 1L << 10, // 1024
|
||||
Button12 = 1L << 11, // 2048
|
||||
Button13 = 1L << 12, // 4096
|
||||
Button14 = 1L << 13, // 8192
|
||||
Button15 = 1L << 14, // 16384
|
||||
Button16 = 1L << 15, // 32768
|
||||
Button17 = 1L << 16, // 65536
|
||||
Button18 = 1L << 17, // 131072
|
||||
Button19 = 1L << 18, // 262144
|
||||
Button20 = 1L << 19, // 524288
|
||||
Button21 = 1L << 20, // 1048576
|
||||
Button22 = 1L << 21, // 2097152
|
||||
Button23 = 1L << 22, // 4194304
|
||||
Button24 = 1L << 23, // 8388608
|
||||
Button25 = 1L << 24, // 16777216
|
||||
Button26 = 1L << 25, // 33554432
|
||||
Button27 = 1L << 26, // 67108864
|
||||
Button28 = 1L << 27, // 134217728
|
||||
Button29 = 1L << 28, // 268435456
|
||||
Button30 = 1L << 29, // 536870912
|
||||
Button31 = 1L << 30, // 1073741824
|
||||
Button32 = 1L << 31, // 2147483648
|
||||
Button33 = 1L << 32, // 4294967296
|
||||
Button34 = 1L << 33, // 8589934592
|
||||
Button35 = 1L << 34, // 17179869184
|
||||
Button36 = 1L << 35, // 34359738368
|
||||
Button37 = 1L << 36, // 68719476736
|
||||
Button38 = 1L << 37, // 137438953472
|
||||
Button39 = 1L << 38, // 274877906944
|
||||
Button40 = 1L << 39, // 549755813888
|
||||
Button41 = 1L << 40, // 1099511627776
|
||||
Button42 = 1L << 41, // 2199023255552
|
||||
Button43 = 1L << 42, // 4398046511104
|
||||
Button44 = 1L << 43, // 8796093022208
|
||||
Button45 = 1L << 44 // 17592186044416
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ButtonGroups : long
|
||||
{
|
||||
Channel1 = 0b00001_00000000_00000000_00000000_00000000_11111111,
|
||||
Channel2 = 0b00010_00000000_00000000_00000000_11111111_00000000,
|
||||
Channel3 = 0b00100_00000000_00000000_11111111_00000000_00000000,
|
||||
Channel4 = 0b01000_00000000_11111111_00000000_00000000_00000000,
|
||||
Channel5 = 0b10000_11111111_00000000_00000000_00000000_00000000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ChannelButton
|
||||
{
|
||||
ChannelA1 = 1 << 0,
|
||||
ChannelA2 = 1 << 1,
|
||||
ChannelA3 = 1 << 2,
|
||||
ChannelA4 = 1 << 3,
|
||||
ChannelA5 = 1 << 4,
|
||||
ChannelB1 = 1 << 5,
|
||||
ChannelB2 = 1 << 6,
|
||||
ChannelB3 = 1 << 7,
|
||||
ChannelMute = 1 << 8,
|
||||
}
|
||||
75
HidSliders.Hid/MixerButtonHelper.cs
Normal file
75
HidSliders.Hid/MixerButtonHelper.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
namespace HidSliders.Hid;
|
||||
|
||||
public class MixerButtonHelper
|
||||
{
|
||||
public static List<MixerButton> GetSetFlags(long value)
|
||||
{
|
||||
var setFlags = new List<MixerButton>();
|
||||
foreach (MixerButton flag in Enum.GetValues(typeof(MixerButton)))
|
||||
{
|
||||
if ((value & (long)flag) == (long)flag)
|
||||
{
|
||||
setFlags.Add(flag);
|
||||
}
|
||||
}
|
||||
return setFlags;
|
||||
}
|
||||
|
||||
public static ButtonGroups GetButtonGroup(MixerButton button)
|
||||
{
|
||||
var buttonValue = (long)button;
|
||||
|
||||
if (((long)ButtonGroups.Channel1 & buttonValue) != 0) return ButtonGroups.Channel1;
|
||||
if (((long)ButtonGroups.Channel2 & buttonValue) != 0) return ButtonGroups.Channel2;
|
||||
if (((long)ButtonGroups.Channel3 & buttonValue) != 0) return ButtonGroups.Channel3;
|
||||
if (((long)ButtonGroups.Channel4 & buttonValue) != 0) return ButtonGroups.Channel4;
|
||||
if (((long)ButtonGroups.Channel5 & buttonValue) != 0) return ButtonGroups.Channel5;
|
||||
|
||||
throw new ArgumentException("The provided button does not match any button group.", nameof(button));
|
||||
}
|
||||
|
||||
public static ChannelButton GetChannelButton(MixerButton button)
|
||||
{
|
||||
return GetChannelButton(button, GetButtonGroup(button));
|
||||
}
|
||||
|
||||
public static ChannelButton GetChannelButton(MixerButton button, ButtonGroups buttonGroup)
|
||||
{
|
||||
var longButton = (long)button;
|
||||
if (longButton >= 0x10000000000)
|
||||
{
|
||||
return ChannelButton.ChannelMute;
|
||||
}
|
||||
|
||||
int intButton = 0;
|
||||
switch (buttonGroup)
|
||||
{
|
||||
case ButtonGroups.Channel1:
|
||||
intButton = (int)(longButton >> 0);
|
||||
break;
|
||||
case ButtonGroups.Channel2:
|
||||
intButton = (int)(longButton >> 8);
|
||||
break;
|
||||
case ButtonGroups.Channel3:
|
||||
intButton = (int)(longButton >> 16);
|
||||
break;
|
||||
case ButtonGroups.Channel4:
|
||||
intButton = (int)(longButton >> 24);
|
||||
break;
|
||||
case ButtonGroups.Channel5:
|
||||
intButton = (int)(longButton >> 32);
|
||||
break;
|
||||
}
|
||||
|
||||
if (((int)ChannelButton.ChannelA1 & intButton) != 0) return ChannelButton.ChannelA1;
|
||||
if (((int)ChannelButton.ChannelA2 & intButton) != 0) return ChannelButton.ChannelA2;
|
||||
if (((int)ChannelButton.ChannelA3 & intButton) != 0) return ChannelButton.ChannelA3;
|
||||
if (((int)ChannelButton.ChannelA4 & intButton) != 0) return ChannelButton.ChannelA4;
|
||||
if (((int)ChannelButton.ChannelA5 & intButton) != 0) return ChannelButton.ChannelA5;
|
||||
if (((int)ChannelButton.ChannelB1 & intButton) != 0) return ChannelButton.ChannelB1;
|
||||
if (((int)ChannelButton.ChannelB2 & intButton) != 0) return ChannelButton.ChannelB2;
|
||||
if (((int)ChannelButton.ChannelB3 & intButton) != 0) return ChannelButton.ChannelB3;
|
||||
|
||||
throw new ArgumentException("The provided button does not match any channel button in the specified button group.", nameof(button));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user