Compare commits

..

4 Commits

41 changed files with 4440 additions and 3514 deletions
File diff suppressed because it is too large Load Diff
+68 -46
View File
@@ -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
View 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
View 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
View 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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+111
View 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
View 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
View File
@@ -0,0 +1 @@
SD cards mounted at /sd will hide this file from Python. SD cards are not visible via USB CIRCUITPY.
View File
+86
View 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
View File
@@ -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;
}
}
+175 -50
View File
@@ -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));
+5 -1
View File
@@ -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>
-7
View File
@@ -1,7 +0,0 @@
namespace HidSliders.Hid
{
public class Class1
{
}
}
+87
View 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);
}
}
}
+2 -1
View File
@@ -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
View 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
View 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));
}
}