Projects

DIY PC Audio Mixer

Liam Davies

Issue 56, March 2022

This article includes additional downloadable resources.
Please log in to access.

Log in

Manage audio on your computer quickly with this mini mixer board!

BUILD TIME: A weekend
DIFFICULTY RATING: Intermediate

Desktop computers have always been known for their productivity. Whatever the platform you’re working on, whether that be Windows, Mac or Linux, all have powerful multitasking tools and shortcuts to maintain a speedy workflow.

One of the most frustrating things I’ve found personally - working on Windows at least - is managing audio levels of different windows. The master volume control is frequently available directly as physical buttons on headsets, keyboards and speakers, but this isn’t enough granular control - it just changes all application volumes at once. If you’re a content producer, gamer, or a media manager, you may be able to relate.

When playing games, I’m often listening to music on Spotify, playing games and talking to online friends all at the same time. PC games frequently decide they’re going to start at full volume for no reason. Or maybe a specific song playing on Spotify is a little too loud.

The way this is managed on Windows is using the Volume mixer in the bottom right taskbar manager. This requires multiple clicks to get to, so it can’t be instantly used to change the volume for any particular window.

We could just make a piece of software to add yet more shortcuts to the keyboard, but we're a bit handier than that here at DIYODE, so we’re going to be using some old-fashioned electronics to fix the problem.

How It Works

At the heart of our mini mixer project is a set of linear potentiometers, which work exactly the same way as the ‘knob’ style potentiometer; just in a line. We feed in an upper and a lower voltage on two pins, and the remaining third pin acts as the ‘wiper’. For example, feeding in 0V and 5V would permit output values between 0V and 5V, depending on the position of the slider knob.

XC3734 10k Slider Potentiometer from jaycar.com.au

However, we can’t make any measurements with an analog voltage directly. We need to convert it to a digital representation. By connecting the wiper of the potentiometer to an analog pin of the Arduino Nano, we can use the inbuilt Analog-To-Digital converter to convert it into a 10-bit integer (i.e. between 0 and 1023). At the bottom of the range, the value reads 0, and at the top, it reads 1023. So, we can just divide it by 10.23 to get a volume range of 0-100%, right?

Well, not quite.

Additional Processing

Like most other analog electronics, potentiometers are never 100% accurate. We want our audio mixer to be as consistent as possible, so we’re trying to fix as many problems as we see pop up.

The first issue is that of the full range of the linear potentiometers. All conductors with current flowing through them experience some form of voltage drop, and while it’s this very effect we exploit to create a variable voltage using our sliders, it also causes the sliders to not reach the full range between 0V and 5V.

The reason this occurs is because we have power wires on the ends of the potentiometers. These will necessarily have their own - small, but not negligible - voltage drop. When the slider is moved to the bottom position, it might only reach 0.05V, or when it’s moved to the top, it might only reach 4.95V. We can make our power wires shorter or thicker, but ultimately this problem will persist to some degree.

We can compensate for this in software, though! By setting the top and bottom ‘deadzone’ of the 0V-5V range, the Arduino only cares what the potentiometers are reading if they are in their operating voltage. If it’s below 0.05V, for example, we just read the potentiometer as zero.

Non-linearity

The next quirk is the fact that the potentiometer sliders we chose are non-linear. That is, moving the knob the same distance will produce different changes in voltage depending on where the knob actually is.

This may seem like an inaccuracy with the potentiometers, but this is actually an important requirement for many applications. Specifically, since these potentiometers are often used for audio control applications, they are designed to have a logarithmic response. Looking at the chart shown here, notice how the output gets steeper the further we turn the knob. This is designed to compensate for how human hearing also perceives audio logarithmically. The response you see here is sometimes known as an A-taper potentiometer. By using one of these potentiometers for audio control, it sounds more ‘natural’ when changing volume.

The potentiometer we sourced isn’t actually a logarithmic potentiometer though, it looks more like an ‘S’-curve, which is sometimes called a B taper in the audio world. It provides more control at the lower and upper ends of the potentiometer range, so values around 0-10% and 90%-100% can more accurately be selected.

We didn’t like this behaviour, as we found it’s difficult to regulate some applications in Windows during testing. If you’re planning to use this mixer board in media applications like Adobe Premiere, having a non-linear volume slider would be very frustrating. Imagine having an audio source go from barely audible to deafening with an accidental finger twitch - okay, that’s an exaggeration, but you get the idea.

To compensate for the non-linear response of the potentiometer, we commenced an experiment to first plot the response profile. Since we couldn’t find a datasheet available, we had to do it ourselves. Our method was simple: Move the slider, measure the distance, measure the output voltage, and pop it into a Google Sheets spreadsheet.

After we had around 30 datapoints, we imported the data into Desmos (an online graphing application) and used a Sine function to model the response of the potentiometer. The math nerds reading will know that an Inverse function is required here, since we want to reverse the effects of the non-linearity, not apply it again!

The upshot is that we now have essentially a function that takes in a value (the voltage we read from the potentiometer, or ‘x’) and spits out another value - 'c(x)' - that models a more consistent, linear response of the slider.

Syncing

This problem rears its ugly head in pretty much every situation where it’s possible to control a variable both through software and hardware.

When changing the volume on the physical slider, the Windows software simply updates its position on the virtual slider, no worries there - that’s the whole point of the project. However, when changing volume on the virtual slider, we, of course, can’t change the position of the physical slider automatically.

There are a number of ways around this problem, the first being just completely ignoring the inbuilt software slider. In other words, we just keep writing the physical position of the slider to the virtual one. It wouldn’t be possible to move it through software at all.

This problem appears in many other places in engineering. It’s the reason why most smart devices are designed with rotary encoders rather than potentiometers - they provide a relative position rather than an absolute one, making software control easier. Imagine your house thermostat using a potentiometer, where the position of the temperature knob is known as an absolute position. As soon as you’d go to change the temperature with the buttons on the remote, it would instantly fall out of sync.

There isn’t an easy way to get around this problem in our case, unless we were to use something like a motorised slider. These are available but are substantially more complicated and expensive. Our solution is to simply not change application-specific volume within Windows.

Fundamental Build

Parts Required:Jaycar
1 x Arduino Nano or compatible boardXC4414
1 x Slide Potentiometer (10kΩ)XC3734
1 x OLED Monochrome Screen 128x64XC3728
1 x Header StripHM3212

The fundamental build of this project will be fairly simple, and will mainly focus on making sure the code works as expected.

First, we slotted in an Arduino-compatible Nano with power wires connecting the breadboard rails to 5V and Ground on the red and blue lines respectively.

To display the current channel and volume level, we opted to use a monochrome 128x64 OLED display. OLEDs are a favourite of ours because of their simplicity and adaptability in displaying almost any form of graphical or text data. In this case, a monochrome display was selected to keep things simple and cheap. There are also many Arduino-based libraries that are set up to make interfacing with these simple and convenient.

The module we chose is 5V and 3.3V compatible, so it’ll work with all sorts of Arduino-compatible boards. All it needs is a 5V and Ground connection for power, and two I2C lines for data - SDA to A4 and SCL to A5.

Note that some forms of these OLED screens are SPI-based instead, which are much faster, but as a trade-off use more data pins. If you would like a faster refresh rate or have a need to send more data (e.g. if you’re displaying bitmaps or GIFs on the display), consider the SPI alternative.

Finally, we can pop in our slide potentiometer. We desoldered the right-angled headers and added underside headers instead - as we’ll be doing with all of the sliders in the main build. This makes it convenient to use with a breadboard.

The slide potentiometers we’re using are dual-gang, so there are two resistance tracks within the potentiometer that move identically. This is designed so that two isolated circuits can use the same analog output when necessary, but in our case, we’re using both on the same Arduino to get better sampling results from the potentiometers. This is discussed more in the upcoming code section.

Each gang needs 5V and Ground connected, and a wire connecting the wiper to an Arduino Nano pin - we used A1 and A2.

Code

Before we get the slightly more scary C# program for the main build, let’s focus on getting consistent readings from the slider and talking to the OLED screen in our code.

#include
#include
#include
#include
#include
#define i2c_Address 0x3c
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

There are a few libraries to import here, all of which are used for interfacing with the OLED screen. You’ll need to install all of the above from the Library Manager within the Arduino IDE.

void loop() {
float total = 0;
for(int i = 0; i < avgSamples; i++) {
int p1 = analogRead(POT_PIN_1);
int p2 = analogRead(POT_PIN_2);
total += (p1 + p2) / (float)2;
}
total /= (float)avgSamples;
total = correctedSlider(total);
total = map(total, BOTTOM_DEADZONE, TOP_DEADZONE, 0, 100);
total = constrain(total, 0, 100);

Here is how we’re processing the analog data from the potentiometer. Since we have a dual gang potentiometer, we can read the voltage on two pins simultaneously, then average their resulting values. This will reduce the noise, especially after taking a number of consecutive readings and then averaging that figure.

This technique of averaging is a very widely-used technique in Digital Signal Processing (DSP), in order to obtain reliable digital readings from analog instruments. There are various types of averaging methods, such as running and exponential averaging - but we’re simply taking multiple readings and then calculating the average for each iteration of the loop.

After we’ve averaged the reading, we can then use the correction function we determined earlier to make the potentiometer output more consistent with the input movements. Finally, we remap the 0-1023 input range to the 0-100% output that we send to the connected computer.

display.clearDisplay();
display.setTextSize(0);
display.setTextColor(SH110X_WHITE);
display.setCursor(0, 15);
display.setFont(&FreeSans9pt7b);
display.println("Test Slider");
display.setTextSize(1);
display.setFont(&FreeSans24pt7b);
display.setCursor(0, 60);
display.print((int)total);
display.display();
Serial.println(total);

Before we do that, though, we need to display the volume level on the OLED screen. Unfortunately, the Arduino IDE doesn’t include a graphical editor for making UI elements, so we had to go through some trial-and-error before we came up with a pleasing combination.

Our UI is simple and communicates all of what we need to know with a quick glance. The top subtitle will print the application name - in our case, “Test Slider”. The main build will replace these with “Spotify”, “Chrome” and so forth. The line below is a much larger 24pt that will print the volume level, actively updating as we drag the slider.

Finally, we can print the current volume level of the slider to the Serial port, ready for the PC on the other end to receive. We only wanted to check that the OLED screen and the signal processing was reliable and effective.

Main Build:

Now that we know our fundamental build is working well, we can commit to building a fancy enclosure and soldering together a permanent circuit.

3D Printed Case

Nobody wants a mess of wires hanging around on their desk, so let’s build a 3D printed case before we assemble the electronics. The 3D printed case comes in two parts, the main enclosure and a faceplate to hide the majority of the bolts.

We printed ours in an IKEA-esque bright yellow and blue, but feel free to use whatever colours suit your setup. You may also wish to pick domed buttons that complement the colours. Any buttons with a low profile and a 12mm mounting diameter will work.

Anyhow, once everything is printed we can begin assembly. It’s generally a pretty simple process of screwing in everything to its designated grooves. We’ve used M3 screws and nuts across the whole build to keep it simple.

For the linear potentiometers, you’ll need to desolder the 90 degree angled headers and remove the slider knobs before inserting them into the main enclosure. The slider bodies should rise up through the main enclosure, and the PCBs should sit flush with the 3D printed lip. Once the nuts and bolts are screwed in, they should fit snug.

The OLED screen is a similar story, just screw it in with M3 fasteners and check the screen is not tilted (and in the right orientation!). If it’s tilted, you may need to shorten the header pins to make it fit.

Main Build Electronics

Parts Required:Jaycar
1 x Arduino Nano or compatible boardXC4414
3 x Slide Potentiometers (10kΩ)XC3734
3 x Dome Pushbutton SwitchesSP0656
1 x OLED Monochrome Screen 128x64XC3728
1 x Header StripHM3212
20 x M3x10mm BoltsHP0404
32 x M3 NutsHP0426
Solid Core WireWH3032

Despite what the photos may look like, the wiring for this project is actually very simple! This is very much a beginner-friendly project when it comes to building - no external capacitors, resistors or prototyping boards are required.

We’re also adding an additional ‘Action’ button beneath each of the three sliders, which can be programmed to mute a window, switch focus to it, or virtually any other action that can be accomplished within our C# program.

We first added power wires to the potentiometer sliders. Note that in our case, we’re not soldering to the headers - we’re soldering to the potentiometer outputs on the breakout board directly. (We inadvertently ruined the solder pads trying to get header pins off it!)

Across the top end of the potentiometers, we added a red wire across all three - six connections total - for the 5V power. The ground wires need to be connected to at least one of the two ground pads on the potentiometers.

The action buttons at the bottom of each slider also need a ground wire. Since we’re using the Arduino Nano’s internal pullup resistors, we don’t need any additional resistors here. We connected all of the grounds together with soft single-core black wire.

Next up, we soldered the two signal wires to the potentiometers, using the same colour for each set of slider wires. It’s important to make these long enough to reach the Arduino Nano up the top later.

The action buttons also need their own signal wires, which we used brown wires for. We found it’s important to go easy with the soldering heat on these cheaper dome buttons, as the plastic they’re moulded out of will melt in seconds and ruin the internal switching mechanism.

The OLED screen requires two power wires: 5V and Ground. We soldered the 5V wire to the same rail the potentiometer uses, and the Ground to its own connection with the Arduino Nano. Note that we purposely didn’t use header sockets here since there isn’t enough vertical clearance - we just soldered directly to the header.

The SDA and SCL wires also need to be added, for which we used white and yellow wires respectively. These will connect to A4 and A5 on the Arduino Nano. Speaking of which, let’s get that hooked up.

We’re using an Arduino compatible Nano with no headers attached, as we want to keep the build as compact as possible. You can find more detailed connection information in the schematics.

The Arduino Nano we used was double-side taped onto the underside of the enclosure in its appropriately shaped groove and a USB-C cable (our models are USB-C, but any version will work) was attached for programming, communication and power.

Once everything is mounted into the main enclosure, we can screw on the faceplate. The faceplate is optional - it doesn’t make a functional difference - but it covers up the exposed sliders and bolts, while adding a second colour accent to the build.

Arduino Code

We need to make a number of changes to our fundamental build code to make it work with the additional potentiometers and buttons.

Since we're dealing with six total analog inputs, we need to collect data from each individually. We first made an array of all six pins so that we can refer to each by an index, rather than directly by a name. If you want to extend the audio mixer with additional sliders, you can simply define more pin assignments and go from there!

const byte sliderAssignments[] = {

const byte sliderAssignments[] = {
CHAN_1_POT_1,
CHAN_1_POT_2,
CHAN_2_POT_1,
CHAN_2_POT_2,
CHAN_3_POT_1,
CHAN_3_POT_2
};

Now, we can write a function that will automatically read each slider value, and write the resulting value to a data array.

float sliderValues[] = {0,0,0};
float deltaSliderValues[] = {0,0,0};
int averageSamples = 20;
void readSliderValues() {
for(int i = 0; i < sizeof(sliderAssignments) / sizeof(byte); i += 2) {
float total = 0;
for(int a = 0; a < averageSamples; a++) {
total += analogRead(sliderAssignments[i]) / (float)(2.0 * averageSamples);
total += analogRead(sliderAssignments[i+1]) / (float)(2.0 * averageSamples);
}
//Assigns rounded total to the
//resulting slider value.
deltaSliderValues[i/2] = abs(total - sliderValues[i/2]);
sliderValues[i/2] = total;
}
}

In the case of our three sliders, we loop three times, and average both potentiometers on each. You'll notice that before we write to the "sliderValues" array, we also write to the "deltaSliderValues" array. When we use 'delta' in a variable, it's typically signifying a value that represents change. The changing quantity in our case is how much the potentiometer values change per read. We found that if we left the sliders in some spots, the volume readings would flicker rapidly between one value and the next (30.999% and 31.000% for example), and every time it changes, it would send a message to our PC program.

The upshot is that we defined a threshold value at which rate sliders must change in order to trigger messages sending. We found that a value around 5-10 (out of a range 0-1023) is suitable, however, sometimes when the user drags the sliders slowly it may not trigger. This may need to be tuned for your application.

void loop() {
readSliderValues();
for(int i = 0; i < 3; i++) {
if(deltaSliderValues[i] > DELTA_MINIMUM) {
int sliderVal = round(correctedSlider(sliderValues[i]));
sliderVal = map(sliderVal, BOTTOM_DEADZONE, TOP_DEADZONE, 0, 100);
sliderVal = constrain(sliderVal, 0, 100);
display.clearDisplay();
display.setTextSize(0);
---omitted display code---
display.print(sliderVal);
display.display();
Serial.print("CH#");
byte channel = i;
Serial.print(channel);
Serial.print(':');
Serial.println(sliderVal);
}
}

This is where the bulk of our code functionality occurs. After reading each slider, we determine whether its value has changed enough to trigger a new message sending.

We then apply the correction function we talked about at the beginning of this project, and map the 0-1023 value to 0-100. After we've updated the OLED with the value, we also write the changed potentiometer to the Serial port so our PC program can read it.

for(int i = 0; i < 3; i++) {
bool button = digitalRead(actionButtonAssignments[i]);
if(lastActionButtonStates[i] == 1 && !button) {
Serial.print("B#");
Serial.println(i);
}
lastActionButtonStates[i] = button;

Finally, we check the 'action' buttons at the bottom of the audio mixer. Since we're only looking for a falling edge (i.e. our button has just been pressed), we need to do some state handling to ensure the button hasn't already been pressed last time the loop executed. If we have pressed it, we send a "B#x" message to the PC, where 'x' is the button index.

PC Application

While our 3D printed case and Arduino code is cool, we’ve still got a bit of extra work to do before we can use it with our computer. We need to write some custom software that will provide an interface between the mixer board and Windows. Note that we haven’t investigated how much work it would be to port it to other operating systems, but given that a Serial port interface can be used on virtually any platform, you shouldn’t have any massive issues with doing so.

We’re writing an application in C# with Windows Forms to accomplish this functionality, which is a fairly straightforward way of building high-performance code while still making it easy to use.

To build a C# Form application, you’ll need to have Microsoft Visual Studio installed on your computer. As long as the .NET Framework and C# support is installed, there isn’t a lot else you will need to do. Since we need access to the audio control in Windows, we also installed the CSCore library from the NuGet package manager, which can be found under the ‘Project’ tab at the top of the screen.

The form editor within Microsoft Visual Studio is intuitive, offering various components that can be dragged-and-dropped into a window. These components are all frequently seen in almost every other standard Windows application, so their functionality should be familiar to most.

In the Properties subwindow, different attributes can be changed about each component, such as the display name, size, anchoring (i.e. how the component reacts to changing parent window sizes), and event handling. For example, we may want to call a function when the user changes the selection of a dropdown box containing the available COM ports. This can be done by adding a function name to the SelectedIndexChanged event handler.

The ‘changeCOMPort’ function can be seen below, where we’re simply changing the name of the connected Serial port.

private void changeCOMPort(object sender, EventArgs e)
{
ComboBox comboBox = (ComboBox)sender;
String comPort = (string)comboBox.Items[comboBox.SelectedIndex];
Program.portName = comPort;
}

Developing a working application using C# Forms is more than achievable for a beginner, but learning the best strategies for making an efficient program is much harder. For example, running subtasks on the main thread of the program could potentially freeze the program should it take some time. When we scan for audio sources using the CSCore API, we need to ensure that the API runs on a new thread to avoid CSCore complaining. So, we need to use this code to wait for the CSCore library to finish finding the system audio sources:

bool scanningDone = false;
Task.Run(() => ScanForAudio(out scanningDone));
while (!scanningDone) ;

When the ScanForAudio function is finished finding the available system processes that have accessible audio levels, we can continue executing. By the way, this isn’t perfect code by any means! It will suffice for our purposes, but what if we don’t finish the scan at all? The while loop will continue looping indefinitely and eventually cause Windows to kill the program. When working with a computer processor that is constantly moving between various multi-threaded programs, we need to ensure our program plays nice with Windows.

Anyhow, once we have found the audio devices we wish to assign to channels on our audio mixer, we then need to add it to the windows form.

int index = 0;
foreach (String process in audioProcesses)
{
var sourceCombobox = new System.Windows.Forms.ComboBox();
sourceCombobox.DropDownHeight = 150;
--— omitted code —--
sourceCombobox.Items.AddRange(sliderAssignments.ToArray());
//Additional audio sources are initialized so
//that they are disabled.
if(index >= 3)
{
sourceCombobox.SelectedIndex = 3;
} else
{
sourceCombobox.SelectedIndex = index;
}
sources.Append(new MixerSource(process, MixerSource.intToChannel(index)));
var applicationName =
new System.Windows.Forms.Label();
applicationName.AutoSize = true;
--— omitted code —--
var sourcePanel = new System.Windows.Forms.Panel();
sourcePanel.Controls.Add(sourceCombobox);
sourcePanel.Controls.Add(applicationName);
sourcePanel.Location = new System.Drawing.Point(3, 3);
sourcePanel.Name = "sourcePanel" + index.ToString();
sourcePanel.Size = new System.Drawing.Size(273, 48);
form.audioSourcePanel.Controls.Add(sourcePanel);
index++;
}

This code programmatically adds elements to our form, depending on what applications are currently running on the system.

And that is the result. Cool, isn’t it?! This front-end of the program is only half of the work. We also have to manage opening the COM ports, receiving data, parsing and sending the data to the CSCore API.

Double-clicking on any control in the Windows Forms API allows you to link any 'click' action to code instantly. This will open up a new method, as above.

This is linked to the "Connect" button on the form and can run other functions based on whether or not the COM port is currently connected. The Program.Close/OpenMixerCOM(); functions accesses the physical COM port and attempts to open/close it, while SetCOMDis/connected() sets the appropriate UI elements to display the current status.

public void SetCOMDisconnected()
      {
         statusText.Text = "Status: Disconnected";
         statusText.ForeColor = Color.Red;
         connectButton.Text = "Connect";
         comPortBox.Enabled = true;
       }

Of course, we can't just open the COM port and expect all of the audio functionality to work. We need to interface with a Windows-side program that will change volume for a specific audio application. This is where the CSCore library comes in!

CSCore allows us to instantly query the available audio outputs, running processes that have audio sources, and their current audio levels.

private void toggleConnectButton_Click(object sender, EventArgs e)
{
if (Program._serialPort.IsOpen)
{
//Serial port is open, time to close it!
Program.CloseMixerCOM();
SetCOMDisconnected();
}
else
{
//Serial port isn't open, time to open it!
Program.OpenMixerCOM();
SetCOMConnected();
}
}

To tell CSCore what audio process to change, we need to listen to the Serial port and see what information the Arduino has sent over in our "CH#x" format. By carefully formatting and extracting certain parts of the received string, we can pass the newly found number over to the SetAudioLevel function. Since we also know which channel it is on, we can refer to the process ID we stored earlier in the program.

There is unfortunately quite a lot of code in the Windows C# program, so we’re not going to go into the full nitty-gritty of everything here. What we wanted to show is how different the programming methods are, when compared to Arduino.

As usual, you can download the finished program in the project files. If you understandably don’t want to run random executables from the internet, we’ve also provided the source code if you want to compile it yourself, modify it or just have a poke around.

Once it’s running, just select the COM port your mixer board is on, hit connect and boom, we’re now talking to our mixer board!

Testing

Despite the relatively low cost of all the electronic components in this build, we were pleasantly surprised with the performance of this project. Since we have implemented various methods of noise reduction and signal processing, we found by playing with the code parameters, we could create a reasonably responsive mixer board.

Because we are polling six signal lines of analog data, and taking many samples of each channel, the Arduino clearly hits its limit in terms of processing speed. There is significantly more latency on the OLED screen than there is with fewer averaging samples. However, we didn’t find that this impacted usability as there were still 4-5 screen updates per second.

We’re also aware that this code isn’t optimised. More speed could be had by using register-level operations instead of slow loops, array accesses and float data types.

We did find that sometimes, noise would cause the volume sliders to inadvertently trigger a message, changing the volume by one or two units up or down. This was usually unnoticeable while using Windows programs, unless a very loud application has a low volume set. For example, going from 2% to 3% would create a relative 50% increase in volume.

But enough about the technical stuff! How good is it to use? It works pretty much as intended, and when placed to the side of a desktop keyboard forms a convenient location for quickly changing volume levels.

The sliders are smooth, and the OLED screen works really well in the dark due to a lack of backlight - there is an impressive amount of contrast present.

Using it while multitasking is super handy, especially while using multiple sources of media. Gaming, listening to music and watching YouTube can all be independently controlled, for example. It also is very useful in Zoom calls for reducing the volume of loud presenters actively, while doing other tasks on your computer.

Where To From Here?

Besides optimising the Arduino code, there isn’t a lot more to be done on the functionality side of the mixer board. Nonetheless, a number of aesthetic and quality of life improvements could be made here.

More visual cues for actions could be displayed on the OLED screen, such as when a window is switched or an application is added to the audio source list. A “screensaver” with fancy geometric shapes or patterns could be programmed to display whenever the mixer board is idle - i.e. a slider isn’t being moved. Remember, the OLED screen isn’t a limited graphics device like a 7-segment display, we can write any form of graphics we please onto it, provided we have the processing power, of course.

On the Windows program side of things, there are countless integrations this mixer board could work with. The most obvious change we would make to this project given more development time would make this work with the Adobe Premiere Pro and After Effects SDK - for some seriously slick video editing! Changing audio track volume with a real-time slider is typically a luxury only expensive PC accessories can flaunt.

However, this is only a niche application of this mixer board. More general computer control could be used with this, such as changing screen brightness, microphone level, scrubbing through YouTube videos, or quickly scrolling through webpages.

Since we’ve provided the source code for the Windows program, if you’re handy with C# you can expand this to just about any program API you can think of.

Earlier in the article, we mentioned that it would be possible to provide better control syncing with motorised sliders. These are typically used with expensive audio stations in recording studios, where keyframes can be played back in realtime to automatically change the physical slider position. We’d be interested to see this in a DIY electronics project, so if you do get your hands on some of these motorised sliders, let us know what you’re making with them! ■