App sync code, handles button presses from mixer and syncs changes from VM. Included python code from the mixer. Some other PCB changes, but still needs improvements

This commit is contained in:
2024-05-16 23:42:27 -05:00
parent efdbb11f92
commit 700e87f807
39 changed files with 4246 additions and 3514 deletions

File diff suppressed because it is too large Load Diff

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"

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")

View File

@@ -0,0 +1,5 @@
Adafruit CircuitPython 9.0.4 on 2024-04-16; Adafruit ItsyBitsy M4 Express with samd51g19
Board ID:itsybitsy_m4_express
UID:A9C26FCF53324853202020472A3908FF
boot.py output:
Booting normal mode

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)

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

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)

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;
}
}

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));

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>

View File

@@ -1,7 +0,0 @@
namespace HidSliders.Hid
{
public class Class1
{
}
}

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);
}
}
}

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>

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,
}

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));
}
}