1
0
mirror of synced 2024-11-05 01:49:15 +00:00

Implemented Arduino firmware

Implemented serial connection in Windows application
This commit is contained in:
Mark van Renswoude 2021-02-20 12:16:18 +01:00
parent 072e73df88
commit f15491c541
11 changed files with 553 additions and 79 deletions

View File

@ -0,0 +1,230 @@
/*
*
* Configuration
* Modify these settings according to your hardware design
*
*/
// Set this to the number of potentiometers you have connected
const byte KnobCount = 1;
// For each potentiometer, specify the port
const byte KnobPin[KnobCount] = {
// A0,
A1
};
// Minimum time between reporting changing values, reduces serial traffic
const unsigned long MinimumInterval = 50;
// Alpha value of the Exponential Moving Average (EMA) to reduce noise
const float EMAAlpha = 0.6;
// How many measurements to take at boot time to seed the EMA
const byte EMASeedCount = 5;
/*
*
* Le code
* Here be dragons.
*
*/
bool active = false;
enum OutputMode {
Binary, // Communication with the desktop application
PlainText, // Plain text, useful for the Arduino Serial Monitor
Plotter // Graph values, for the Arduino Serial Plotter
};
OutputMode outputMode = Binary;
byte volume[KnobCount];
unsigned long lastChange[KnobCount];
int analogReadValue[KnobCount];
float emaValue[KnobCount];
unsigned long currentTime;
unsigned long lastPlot;
void setup()
{
Serial.begin(115200);
// Wait for the Serial port hardware to initialise
while (!Serial) {}
// Seed the moving average
for (byte knobIndex = 0; knobIndex < KnobCount; knobIndex++)
emaValue[knobIndex] = analogRead(KnobPin[knobIndex]);
for (byte seed = 1; seed < EMASeedCount - 1; seed++)
for (byte knobIndex = 0; knobIndex < KnobCount; knobIndex++)
getVolume(knobIndex);
// Read the initial values
currentTime = millis();
for (byte knobIndex = 0; knobIndex < KnobCount; knobIndex++)
{
volume[knobIndex] = getVolume(knobIndex);
lastChange[knobIndex] = currentTime;
}
}
void loop()
{
if (Serial.available())
processMessage(Serial.read());
// Not that due to ADC checking and Serial communication, currentTime will not be
// accurate throughout the loop. But since we don't need exact timing for the interval this
// is acceptable and saves a few calls to millis.
currentTime = millis();
// Check volume knobs
byte newVolume;
for (byte knobIndex = 0; knobIndex < KnobCount; knobIndex++)
{
newVolume = getVolume(knobIndex);
if (newVolume != volume[knobIndex] && (currentTime - lastChange[knobIndex] >= MinimumInterval))
{
if (active)
// Send out new value
outputVolume(knobIndex, newVolume);
volume[knobIndex] = newVolume;
lastChange[knobIndex] = currentTime;
}
}
if (outputMode == Plotter && (currentTime - lastPlot) >= 50)
{
outputPlotter();
lastPlot = currentTime;
}
}
void processMessage(byte message)
{
switch (message)
{
case 'H': // Handshake
processHandshakeMessage();
break;
case 'Q': // Quit
processQuitMessage();
break;
}
}
void processHandshakeMessage()
{
byte buffer[2];
if (Serial.readBytes(buffer, 3) < 3)
return;
if (buffer[0] != 'M' || buffer[1] != 'K')
return;
switch (buffer[2])
{
case 'B':
outputMode = Binary;
break;
case 'P':
outputMode = PlainText;
break;
case 'G':
outputMode = Plotter;
break;
default:
return;
}
switch (outputMode)
{
case Binary:
Serial.write('H');
Serial.write(KnobCount);
break;
case PlainText:
Serial.print("Hello! I have ");
Serial.print(KnobCount);
Serial.println(" knobs.");
break;
}
active = true;
}
void processQuitMessage()
{
switch (outputMode)
{
case Binary:
case PlainText:
Serial.write('Q');
break;
}
active = false;
}
byte getVolume(byte knobIndex)
{
analogReadValue[knobIndex] = analogRead(KnobPin[knobIndex]);
emaValue[knobIndex] = (EMAAlpha * analogReadValue[knobIndex]) + ((1 - EMAAlpha) * emaValue[knobIndex]);
return map(emaValue[knobIndex], 0, 1023, 0, 100);
}
void outputVolume(byte knobIndex, byte newVolume)
{
switch (outputMode)
{
case Binary:
Serial.write('V');
Serial.write(knobIndex);
Serial.write(newVolume);
break;
case PlainText:
Serial.print("Volume #");
Serial.print(knobIndex);
Serial.print(" = ");
Serial.println(newVolume);
break;
}
}
void outputPlotter()
{
for (byte i = 0; i < KnobCount; i++)
{
if (i > 0)
Serial.print('\t');
Serial.print(analogReadValue[i]);
Serial.print('\t');
Serial.print(emaValue[i]);
Serial.print('\t');
Serial.print(volume[i]);
}
Serial.println();
}

View File

@ -6,8 +6,8 @@ Control audio devices using physical knobs.
Inspired by [an article on Prusa's blog](https://blog.prusaprinters.org/3d-print-an-oversized-media-control-volume-knob-arduino-basics_30184/), this project has a slightly different set of goals: Inspired by [an article on Prusa's blog](https://blog.prusaprinters.org/3d-print-an-oversized-media-control-volume-knob-arduino-basics_30184/), this project has a slightly different set of goals:
**Must have** **Must have**
1. Control multiple audio devices, one set of physical controls per device 1. Control multiple audio devices, one set of physical controls per device
2. Volume is set to an absolute value (potentiometer instead of a rotary encoder) 2. Volume is set to an absolute value (potentiometer instead of a rotary encoder)
Because of the second requirement, a simple media keys HID device does not suffice and extra software is required on the desktop. Because of the second requirement, a simple media keys HID device does not suffice and extra software is required on the desktop.
@ -16,6 +16,8 @@ Because of the second requirement, a simple media keys HID device does not suffi
a. by changing the Windows default output device a. by changing the Windows default output device
b. by running a VoiceMeeter macro b. by running a VoiceMeeter macro
2. Corresponding LEDs to indicate the currently active device 2. Corresponding LEDs to indicate the currently active device
3. OSD
4. API / plugins to use extra knobs and buttons for other purposes
## Developing ## Developing
The hardware side uses an Arduino sketch to communicate the hardware state over the serial port. The hardware side uses an Arduino sketch to communicate the hardware state over the serial port.

View File

@ -185,7 +185,6 @@ namespace MassiveKnob.Forms
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Massive Knob - Settings"; this.Text = "Massive Knob - Settings";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.SettingsForm_FormClosing); this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.SettingsForm_FormClosing);
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.SettingsForm_FormClosed);
this.NotifyIconMenu.ResumeLayout(false); this.NotifyIconMenu.ResumeLayout(false);
this.CommunicationGroupbox.ResumeLayout(false); this.CommunicationGroupbox.ResumeLayout(false);
this.CommunicationGroupbox.PerformLayout(); this.CommunicationGroupbox.PerformLayout();

View File

@ -4,6 +4,8 @@ using System.IO.Ports;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Dapplo.Windows.Devices;
using Dapplo.Windows.Devices.Enums;
using MassiveKnob.Hardware; using MassiveKnob.Hardware;
using MassiveKnob.Settings; using MassiveKnob.Settings;
using MassiveKnob.UserControls; using MassiveKnob.UserControls;
@ -11,13 +13,15 @@ using Nito.AsyncEx;
namespace MassiveKnob.Forms namespace MassiveKnob.Forms
{ {
public partial class SettingsForm : Form, IMassiveKnobHardwareObserver public partial class SettingsForm : Form, IMassiveKnobHardwareObserver, IObserver<DeviceNotificationEvent>
{ {
private readonly IAudioDeviceManager audioDeviceManager; private readonly IAudioDeviceManager audioDeviceManager;
private readonly IMassiveKnobHardwareFactory massiveKnobHardwareFactory; private readonly IMassiveKnobHardwareFactory massiveKnobHardwareFactory;
private readonly List<KnobDeviceControl> knobDeviceControls = new List<KnobDeviceControl>(); private readonly List<KnobDeviceControl> knobDeviceControls = new List<KnobDeviceControl>();
private bool loading = true; private bool loading = true;
private string lastConnectedPort = null;
private IDisposable deviceSubscription;
private IMassiveKnobHardware hardware; private IMassiveKnobHardware hardware;
private IAudioDevice[] devices; private IAudioDevice[] devices;
private Settings.Settings settings; private Settings.Settings settings;
@ -35,42 +39,43 @@ namespace MassiveKnob.Forms
this.massiveKnobHardwareFactory = massiveKnobHardwareFactory; this.massiveKnobHardwareFactory = massiveKnobHardwareFactory;
InitializeComponent(); InitializeComponent();
SerialPortStatusLabel.Text = Strings.StatusNotConnected; SerialPortStatusLabel.Text = Strings.StatusNotConnected;
Task.Run(async () => // Due to the form not being visible initially (see SetVisibleCore), we can't use the Load event
{ AsyncLoad();
await LoadSettings();
await Task.WhenAll(
LoadSerialPorts(),
LoadAudioDevices()
);
loading = false;
await Connect();
}).ContinueWith(t =>
{
if (t.IsFaulted && t.Exception != null)
SafeCall(() => throw t.Exception);
});
} }
private void SafeCall(Action action) private async void AsyncLoad()
{
await LoadSettings();
await Task.WhenAll(
LoadSerialPorts(),
LoadAudioDevices()
);
deviceSubscription = DeviceNotification.OnNotification.Subscribe(this);
loading = false;
await Connect();
}
private void RunInUIContext(Action action)
{ {
if (InvokeRequired) if (InvokeRequired)
Invoke(action); Invoke(action);
else else
action(); action();
} }
private Task LoadSerialPorts() private Task LoadSerialPorts()
{ {
var portNames = SerialPort.GetPortNames(); var portNames = SerialPort.GetPortNames();
SafeCall(() => RunInUIContext(() =>
{ {
SerialPortCombobox.BeginUpdate(); SerialPortCombobox.BeginUpdate();
try try
@ -97,22 +102,19 @@ namespace MassiveKnob.Forms
private async Task LoadSettings() private async Task LoadSettings()
{ {
var newSettings = await SettingsJsonSerializer.Deserialize(); var newSettings = await SettingsJsonSerializer.Deserialize();
SafeCall(() => SetSettings(newSettings)); RunInUIContext(() => SetSettings(newSettings));
} }
private void SaveSettings() private async Task SaveSettings()
{ {
if (settings == null) if (settings == null)
return; return;
Task.Run(async () => using (await saveSettingsLock.LockAsync())
{ {
using (await saveSettingsLock.LockAsync()) await SettingsJsonSerializer.Serialize(settings);
{ }
await SettingsJsonSerializer.Serialize(settings);
}
});
} }
@ -120,9 +122,8 @@ namespace MassiveKnob.Forms
{ {
string serialPort = null; string serialPort = null;
SafeCall(() => RunInUIContext(() =>
{ {
SerialPortStatusLabel.Text = Strings.StatusConnecting;
serialPort = (string)SerialPortCombobox.SelectedItem; serialPort = (string)SerialPortCombobox.SelectedItem;
}); });
@ -145,7 +146,7 @@ namespace MassiveKnob.Forms
private async Task LoadAudioDevices() private async Task LoadAudioDevices()
{ {
var newDevices = await audioDeviceManager.GetDevices(); var newDevices = await audioDeviceManager.GetDevices();
SafeCall(() => SetDevices(newDevices)); RunInUIContext(() => SetDevices(newDevices));
} }
@ -158,10 +159,12 @@ namespace MassiveKnob.Forms
SerialPortCombobox.SelectedItem = value.SerialPort; SerialPortCombobox.SelectedItem = value.SerialPort;
// No need to update the knob device user controls, as they are not loaded yet // No need to update the knob device user controls, as they are not loaded yet
// (guaranteed by the order in AsyncLoad)
settings = value; settings = value;
} }
private void SetDevices(IEnumerable<IAudioDevice> value) private void SetDevices(IEnumerable<IAudioDevice> value)
{ {
devices = value.ToArray(); devices = value.ToArray();
@ -206,13 +209,13 @@ namespace MassiveKnob.Forms
if (i < settings.Knobs.Count) if (i < settings.Knobs.Count)
knobDeviceControl.SetDeviceId(settings.Knobs[i].DeviceId); knobDeviceControl.SetDeviceId(settings.Knobs[i].DeviceId);
knobDeviceControl.OnDeviceChanged += (sender, args) => knobDeviceControl.OnDeviceChanged += async (sender, args) =>
{ {
while (settings.Knobs.Count - 1 < args.KnobIndex) while (settings.Knobs.Count - 1 < args.KnobIndex)
settings.Knobs.Add(new Settings.Settings.KnobSettings()); settings.Knobs.Add(new Settings.Settings.KnobSettings());
settings.Knobs[args.KnobIndex].DeviceId = args.DeviceId; settings.Knobs[args.KnobIndex].DeviceId = args.DeviceId;
SaveSettings(); await SaveSettings();
}; };
knobDeviceControls.Add(knobDeviceControl); knobDeviceControls.Add(knobDeviceControl);
@ -241,7 +244,13 @@ namespace MassiveKnob.Forms
{ {
// Prevent the form from showing at startup // Prevent the form from showing at startup
if (!startupVisibleCalled) if (!startupVisibleCalled)
{
startupVisibleCalled = true; startupVisibleCalled = true;
// Make sure the underlying window is still created, otherwise Close won't work
if (!IsHandleCreated)
CreateHandle();
}
else else
base.SetVisibleCore(value); base.SetVisibleCore(value);
} }
@ -253,16 +262,43 @@ namespace MassiveKnob.Forms
} }
private void Quit() private async Task Quit()
{ {
Hide();
deviceSubscription?.Dispose();
foreach (var knobDeviceControl in knobDeviceControls)
knobDeviceControl.Dispose();
knobDeviceControls.Clear();
if (hardware != null)
{
hardware.DetachObserver(this);
await hardware.Disconnect();
}
audioDeviceManager?.Dispose();
closing = true; closing = true;
Close(); Close();
} }
public void Connecting()
{
RunInUIContext(() =>
{
SerialPortStatusLabel.Text = Strings.StatusConnecting;
});
}
public void Connected(int knobCount) public void Connected(int knobCount)
{ {
SafeCall(() => RunInUIContext(() =>
{ {
SerialPortStatusLabel.Text = Strings.StatusConnected; SerialPortStatusLabel.Text = Strings.StatusConnected;
SetKnobCount(knobCount); SetKnobCount(knobCount);
@ -272,14 +308,15 @@ namespace MassiveKnob.Forms
public void Disconnected() public void Disconnected()
{ {
SafeCall(() => RunInUIContext(() =>
{ {
SerialPortStatusLabel.Text = Strings.StatusNotConnected; SerialPortStatusLabel.Text = Strings.StatusNotConnected;
lastConnectedPort = null;
}); });
} }
public void VolumeChanged(int knob, int volume) public async void VolumeChanged(int knob, int volume)
{ {
if (knob >= settings.Knobs.Count) if (knob >= settings.Knobs.Count)
return; return;
@ -289,15 +326,12 @@ namespace MassiveKnob.Forms
var deviceId = settings.Knobs[knob].DeviceId.Value; var deviceId = settings.Knobs[knob].DeviceId.Value;
Task.Run(async () => using (await setVolumeLock.LockAsync())
{ {
using (await setVolumeLock.LockAsync()) var device = await audioDeviceManager.GetDeviceById(deviceId);
{ if (device != null)
var device = await audioDeviceManager.GetDeviceById(deviceId); await device.SetVolume(volume);
if (device != null) }
await device.SetVolume(volume);
}
});
} }
@ -310,20 +344,6 @@ namespace MassiveKnob.Forms
e.Cancel = true; e.Cancel = true;
} }
private void SettingsForm_FormClosed(object sender, FormClosedEventArgs e)
{
foreach (var knobDeviceControl in knobDeviceControls)
knobDeviceControl.Dispose();
knobDeviceControls.Clear();
hardware?.DetachObserver(this);
hardware?.Disconnect().GetAwaiter().GetResult();
audioDeviceManager?.Dispose();
}
private void NotifyIcon_DoubleClick(object sender, EventArgs e) private void NotifyIcon_DoubleClick(object sender, EventArgs e)
{ {
@ -331,9 +351,9 @@ namespace MassiveKnob.Forms
} }
private void QuitToolStripMenuItem_Click(object sender, EventArgs e) private async void QuitToolStripMenuItem_Click(object sender, EventArgs e)
{ {
Quit(); await Quit();
} }
@ -349,15 +369,39 @@ namespace MassiveKnob.Forms
} }
private void SerialPortCombobox_SelectedIndexChanged(object sender, EventArgs e) private async void SerialPortCombobox_SelectedIndexChanged(object sender, EventArgs e)
{ {
if (loading || (string)SerialPortCombobox.SelectedItem == settings.SerialPort) var newPort = (string) SerialPortCombobox.SelectedItem;
if (loading || newPort == lastConnectedPort)
return; return;
settings.SerialPort = (string) SerialPortCombobox.SelectedItem;
SaveSettings();
Task.Run(Connect); lastConnectedPort = newPort;
if (settings.SerialPort != newPort)
{
settings.SerialPort = (string) SerialPortCombobox.SelectedItem;
await SaveSettings();
}
await Connect();
}
public async void OnNext(DeviceNotificationEvent value)
{
if ((value.EventType == DeviceChangeEvent.DeviceArrival ||
value.EventType == DeviceChangeEvent.DeviceRemoveComplete) &&
value.Is(DeviceBroadcastDeviceType.DeviceInterface))
{
await LoadSerialPorts();
}
}
public void OnError(Exception error)
{
}
public void OnCompleted()
{
} }
} }
} }

View File

@ -36,8 +36,15 @@ namespace MassiveKnob.Hardware
{ {
observers.Remove(observer); observers.Remove(observer);
} }
public void Connecting()
{
foreach (var observer in observers)
observer.Connecting();
}
public void Connected(int knobCount) public void Connected(int knobCount)
{ {
foreach (var observer in observers) foreach (var observer in observers)

View File

@ -4,6 +4,7 @@ namespace MassiveKnob.Hardware
{ {
public interface IMassiveKnobHardwareObserver public interface IMassiveKnobHardwareObserver
{ {
void Connecting();
void Connected(int knobCount); void Connected(int knobCount);
void Disconnected(); void Disconnected();
@ -25,6 +26,6 @@ namespace MassiveKnob.Hardware
public interface IMassiveKnobHardwareFactory public interface IMassiveKnobHardwareFactory
{ {
IMassiveKnobHardware Create(string serialPort); IMassiveKnobHardware Create(string portName);
} }
} }

View File

@ -48,6 +48,7 @@ namespace MassiveKnob.Hardware
} }
// ReSharper disable once UnusedMember.Global - for testing purposes only
public class MockMassiveKnobHardwareFactory : IMassiveKnobHardwareFactory public class MockMassiveKnobHardwareFactory : IMassiveKnobHardwareFactory
{ {
private readonly int knobCount; private readonly int knobCount;
@ -62,7 +63,7 @@ namespace MassiveKnob.Hardware
} }
public IMassiveKnobHardware Create(string serialPort) public IMassiveKnobHardware Create(string portName)
{ {
return new MockMassiveKnobHardware(knobCount, volumeChangeInterval, maxVolume); return new MockMassiveKnobHardware(knobCount, volumeChangeInterval, maxVolume);
} }

View File

@ -0,0 +1,180 @@
using System.Diagnostics;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
namespace MassiveKnob.Hardware
{
public class SerialMassiveKnobHardware : AbstractMassiveKnobHardware
{
private readonly string portName;
private Thread workerThread;
private readonly CancellationTokenSource workerThreadCancellation = new CancellationTokenSource();
private readonly TaskCompletionSource<bool> workerThreadCompleted = new TaskCompletionSource<bool>();
public SerialMassiveKnobHardware(string portName)
{
this.portName = portName;
}
public override async Task TryConnect()
{
if (workerThread != null)
await Disconnect();
workerThread = new Thread(RunWorker)
{
Name = "SerialMassiveKnobHardware Worker"
};
workerThread.Start();
}
public override async Task Disconnect()
{
workerThreadCancellation.Cancel();
await workerThreadCompleted.Task;
workerThread = null;
}
private void RunWorker()
{
Observers.Connecting();
while (!workerThreadCancellation.IsCancellationRequested)
{
SerialPort serialPort = null;
void SafeCloseSerialPort()
{
try
{
serialPort?.Dispose();
}
catch
{
// ignroed
}
serialPort = null;
Observers.Disconnected();
Observers.Connecting();
}
var knobCount = 0;
while (serialPort == null && !workerThreadCancellation.IsCancellationRequested)
{
try
{
serialPort = new SerialPort(portName, 115200);
serialPort.Open();
// Send handshake
serialPort.Write(new[] { 'H', 'M', 'K', 'B' }, 0, 4);
// Wait for reply
serialPort.ReadTimeout = 1000;
var response = serialPort.ReadByte();
if ((char) response == 'H')
{
knobCount = serialPort.ReadByte();
if (knobCount > -1)
break;
}
SafeCloseSerialPort();
Thread.Sleep(500);
}
catch
{
SafeCloseSerialPort();
Thread.Sleep(500);
}
}
if (workerThreadCancellation.IsCancellationRequested)
{
SafeCloseSerialPort();
break;
}
var processingMessage = false;
Debug.Assert(serialPort != null, nameof(serialPort) + " != null");
serialPort.DataReceived += (sender, args) =>
{
if (args.EventType != SerialData.Chars || processingMessage)
return;
var senderPort = (SerialPort) sender;
processingMessage = true;
try
{
var message = (char) senderPort.ReadByte();
ProcessMessage(senderPort, message);
}
finally
{
processingMessage = false;
}
};
Observers.Connected(knobCount);
try
{
// This is where sending data to the hardware would be implemented
while (serialPort.IsOpen && !workerThreadCancellation.IsCancellationRequested)
{
Thread.Sleep(10);
}
}
catch
{
// ignored
}
Observers.Disconnected();
SafeCloseSerialPort();
if (!workerThreadCancellation.IsCancellationRequested)
Thread.Sleep(500);
}
workerThreadCompleted.TrySetResult(true);
}
private void ProcessMessage(SerialPort serialPort, char message)
{
switch (message)
{
case 'V':
var knobIndex = (byte)serialPort.ReadByte();
var volume = (byte)serialPort.ReadByte();
if (knobIndex < 255 && volume <= 100)
Observers.VolumeChanged(knobIndex, volume);
break;
}
}
}
public class SerialMassiveKnobHardwareFactory : IMassiveKnobHardwareFactory
{
public IMassiveKnobHardware Create(string portName)
{
return new SerialMassiveKnobHardware(portName);
}
}
}

View File

@ -65,6 +65,7 @@
<Compile Include="Hardware\IAudioDeviceManager.cs" /> <Compile Include="Hardware\IAudioDeviceManager.cs" />
<Compile Include="Hardware\IMassiveKnobHardware.cs" /> <Compile Include="Hardware\IMassiveKnobHardware.cs" />
<Compile Include="Hardware\MockMassiveKnobHardware.cs" /> <Compile Include="Hardware\MockMassiveKnobHardware.cs" />
<Compile Include="Hardware\SerialMassiveKnobHardware.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Settings\Settings.cs" /> <Compile Include="Settings\Settings.cs" />
@ -116,6 +117,12 @@
<PackageReference Include="AudioSwitcher.AudioApi.CoreAudio"> <PackageReference Include="AudioSwitcher.AudioApi.CoreAudio">
<Version>4.0.0-alpha5</Version> <Version>4.0.0-alpha5</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Dapplo.Windows.Devices">
<Version>0.11.24</Version>
</PackageReference>
<PackageReference Include="Dapplo.Windows.Messages">
<Version>0.11.24</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json"> <PackageReference Include="Newtonsoft.Json">
<Version>12.0.3</Version> <Version>12.0.3</Version>
</PackageReference> </PackageReference>

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String></wpf:ResourceDictionary>

View File

@ -35,7 +35,8 @@ namespace MassiveKnob
container.Register<IAudioDeviceManagerFactory, CoreAudioDeviceManagerFactory>(); container.Register<IAudioDeviceManagerFactory, CoreAudioDeviceManagerFactory>();
// For testing without the hardware: // For testing without the hardware:
container.Register<IMassiveKnobHardwareFactory>(() => new MockMassiveKnobHardwareFactory(3, TimeSpan.FromSeconds(1), 25)); container.Register<IMassiveKnobHardwareFactory, SerialMassiveKnobHardwareFactory>();
//container.Register<IMassiveKnobHardwareFactory>(() => new MockMassiveKnobHardwareFactory(3, TimeSpan.FromSeconds(1), 25));
return container; return container;
} }