Devember 2022 - Better Windows audio control, MixerFixer [Completed!]

Update 8

Back at work this week, but I did make some progress.

Summary:
-Tray Icon
-Icon for when loaded in browser.
-Start with windows
-Start without main window

TrayIcon

Nothing that special here. During app startup I create a tray icon and a context menu with one item, Exit. If you left click the Tray Icon it will show the Main Window.

When closing the main Window, the user can either shutdown the whole app or just the Main Window. Although it says minimize, in reality the main window is killed off so it doesn’t eat up resources in the background for no reason.

Browser icon

Standard stuff, just the favicon for when loaded in a browser.

Start with Windows

Under the settings the user can now enable ‘Start with Windows’ which will add it to the startup items in the registry. This is also pretty standard behavior and was fairly easy to add after a quick Google search.

One thong to note here is when apps are started like this, their working directory is System32. This meant I had to get the file path of the exe at startup so I can ensure the Settings DB is created inside the app’s folder and not System32.

I did have a small problem with getting the startup location of the app though. Below are some methods to do so and they all work fine when running the app in debug using visual studio, but as soon as I publish it as a single exe then these don’t work. After messing with this for a while I believe it’s because these methods rely on the ‘MixerFixerV1.dll’ to be a thing, but since it’s a single exe there is no dll.

In the end this method worked which would return the full path and file name of the exe. So I just needed to remove the name of the exe. Not exactly eloquent, but it works.

Start without Main Window

By default the Main Window will show when you start up MixerFixer. In the settings screen the user can now have MixerFixer startup without showing the main window. This combined with starting with Windows allows MixerFixer to just do it’s thing quietly in the background while still being accessible via the browser.

I’m now running the app permanently on my main system for about a day and so far it seems to be behaving. It’s not been a full day’s worth of running the app due to pretty bad rolling blackout we are having again. Today we have 3 power outages planned of which 1 session is 2 hours with no power and the other 2 sessions are 4 hours with no power :frowning:

Will have to see how things go during the week and I’m going to ask a friend at work to give it a try.

Github updated.

4 Likes

very thorough and detailed! bit above my understanding limit but i know enough to know how much work you’ve put into creating this

:star: :cookie:

4 Likes

Small check in…

I’m working on adding the ability to store and restore Display Settings. This means MixerFixer will be able to enable/disable, set resolution, and set position of a monitor based on data stored in the DB.

Unfortunately this has proven harder than I thought. I am using WindowsDisplayAPI and I’ve actually had a bit of a hard time figuring out how it works and using it. I’ve figured out most of what I need, but I’ve ran into an issue when enabling or disable a monitor.

A name in the Device class is changing when the monitor goes from Enabled to Disabled which means that you can’t enable the monitor again. I have an idea for a workaround where I store the required names myself and then add my own override method in the WindowsDisplayAPI project which will then allow me to specify the correct name manually, but I’ve reported the issue and made a demo project to show the issue.

It’s obviously VERY possible that I’m doing something wrong, but will wait to hear from the dev of the project.

Video of issue using WPF demo project below. You will see that the ‘ScreenName’ property changes from Display2 to Display1 after it’s been disabled. No idea why though. I’ve checked an all other data points to the correct display I’m trying to Enable, but the ‘ScreenName’ property seems wrong for whatever reason.

Again though, I might just be doing it wrong…

1 Like

Almost Update 9…

FINALLY figured out how to use the WindowsDisplayAPI. First with a WPF test app and once I was happy with the basics I started moving it over to MixerFixer.

Below is a sneak peak. Don’t pay to much attention to the UI and text of the new modal as most of it will almost certainly change.

I’m working on making Update 9 the last update regarding the app it self. After that I want to post some recap posts about how all of this is working.

Update 9 Actual

Summary:

-Custom port
-Microphone volume
-QRCode
-Store and set Display settings
-Added icon for Speakers, Microphone, System Sounds, and if a app return the DLL icon
-Bad news, no Mic boost :frowning:

Custom port

My friend tried the app on his work laptop and the port conflicted with a test site he had running so I decided to change the default port to 5555 and allow the use to set a custom port. Didn’t have any good ideas on how so I settled on creating a txt file called port and typing the number in the file.

image

Microphone volume

I actually forgot about this. Only when I reviewed my original goals did I remember that I said I would allow changing of microphone volume. So I added that and it seems to work fine in my VM. I did forget that my HyperX cloud 2 does not allow you to change the microphone volume in Windows. I have to use the little wirid remote/DAC. For me this isn’t really an issue as I have the volume at 100% all the time anyway and the main issue I have is the fact that even at 100% volume the mic is too soft. O well… the volume slider is there and I’m sure it will be handy when I eventually switch microphones.

image

QRCode

For this I’m using a library I’ve used before, QRCoder. To show it I just added another modal. While the HTML for the modal is being generated, I get the local IP address, pass that to QRCoder which results in a Bitmap. I then convert that Bitmap to base64 and embed it in the HTML as the source for the HTML image control.

Below screenshot shows the HTML object being made for the modal. These objects then goes to the HTML generator to be turned into actual HTML.

And below is the function that gets the IP address. My friend found that my old way of getting the IP address resulted in a IP address for a VPN instead of his physical device’s IP. This was due to things like FirstOrDefault() on the array of IPs. A quick google search let me to this function and it seems to be doing what we want and getting the IP of only physical adapters. It check the registry to make sure it’s a PCI device. This might not be 100% fool proof either, but until I can think of a better idea it will have to do.

Store and set Display settings

Finally, the big one. First, just a quick recap as to why. I used Windows 8.1 until late 2021 on my old AMD FX8320. I also used, and still do, an app called ‘MultiMonitorTool’. Win8.1 had zero issues with me using this tool to quickly disable or enabling a monitor with a shortcut on the desktop and it would always place the Display back in it’s position where I placed it. This is not the case in Win10. Every time I disable my 4th monitor, Win10 would reset it’s position and I then have to go into Display Settings and manually drag it back to the position I want. Well… no more!

This turned out to be much harder than I thought it would be… well… sort of. I found a library called DisplaySettings. It can store and set things like position and resolution. Exactly what I wanted… but… I wanted to be able to turn a display on or off using MixerFixer and this library can’t do that. After a day of trying to find a way to do it and not finding anything that actually works… I should have given up… should have.

I then proceeded to spend another 4 days trying to make this work. 2 of those 4 days was spent trying to figure out a different library I found, WindowsDisplayAPI. I was easily able to find the Display I wanted and then disable it, but I couldn’t Enable the Display. Eventually talked with the dev and it turns out that I was using legacy code and that I should be using a different class in the same library. He then also explained which classes I should use to do what I want and that it’s not a 100% as the data stored by Windows will be different after a reinstall or graphics card change. The only 100% reliable way of identifying a monitor would be the serial… if all monitors had it and you could read it from it’s properties.

Anyway, we have 3 classes that we want to be using. PathDisplaySource, PathInfo, and PathDisplayTarget.
In short:

  • PathDisplaySource is the source of what you want to display. This could be your main/only desktop, or your second/extended desktop.
  • PathDisplayTarget is what the source should be displayed on. Basically, which monitor you want your source to be on.
  • PathInfo connects your source and target(s). Each Path can only have 1 source, but could have multiple targets. In the case of a desktop clone, you have one source but 2 or more targets for example. In the case of an extended desktop, you’ll have 2 sources, 2 paths, and at least 2 targets.

The PathInfo object will contain a property for the Source and an array of targets. So for the most part you only really need to work with the PathInfo object.

The dev also pointed me at another project of his, HeliosDisplayManagement, which uses his WindowsDisplayAPI library that I can use as an example and with all that I was able to make my idea a reality.

In the end, I get all the Active Paths, I then serialize the ones I want as JSON and store it in the DB. This is exactly how it’s done in HeliosDisplayManagement. I can then at a later stage pull this json and de-serialize it back into a PathInfo object which allows me to restore a display to a previous state.

To tell Windows which displays should be on (along with positions, resolution, frequency, ect) you have to pass in all the PathInfos for each display.
Let’s say I have 3 Displays and I want to turn 1 off.
I get all the Active PathInfos which will result in an array with 3 objects.
I then remove the object of the display I want off and then I’m left with 2 in the array.
I then pass only those 2 to Windows and the 3rd display will be off.

Below is an example of turning a Display on.
I first get all the current Active displays which in my case will be 2.
I then add my PathInfo object which was stored as JSON in the DB, and so now I’m passing 3 objects which results in all 3 Displays/Monitors being on.

In the background I have a timer running which will check if any monitors are turned on and then check if that monitor is marked as ‘Is Managed’ in MixerFixer. It will then restore that Display/Monitor’s position, resolution, frequency, ect as it was stored when marked as ‘Is Managed’.

You have to use the Windows Display Settings to set things up the way you want and then mark which displays you want MixerFixer to manage for you. After that you can disable or enable monitors and MixerFixer will restore them to their previous state if marked as ‘Is Managed’

image

Also, the toggle on the right will turn a display on or off. :slight_smile:

How well is this working?? For me it seems to work fine. It’s rare that other people might want this and so I don’t really have tests done on other machines. If bugs are found I will try and fix it though.

Below is a new video showing it working :slight_smile:

Added icon for Speakers, Microphone, System Sounds, and if a app return the dll icon.

Decided to add some basic icons for the Speakers, Microphone, System Sounds, and apps without an icon. When trying to get icons for these you end up with the generic icon for a dll file. I don’t know how to get the icons that Windows uses and I don’t want to risk falling down a new rabbit hole so close to the end. These will do just fine :slight_smile:

Bad news, no Mic boost :frowning:

Unfortunately this will have to be a fail. I knew it probably would not be possible on app level to boost the incoming signal for the mic before it goes off to… err… what ever app might be using it. From what I’ve seen online it seems I will have to do something much lower down instead of in a crappy little app. I’ve used EqualizerAPO to boost the mic volume for a good few years now and it’s one of those set and for get types of apps which are generally the best kind. It looks like it’s doing stuff on driver level… maybe… I don’t really know, but if you’re looking to adjust anything audio related system wide then you should check this app out as it can do a whole lot more than what I’m using it for.

Although I consider the app it self now complete, I did say in a previous post that I was going to try and improve on how I’ve explained how this app works. It won’t be crazy details, but I think some visual aids are in order. I also consider this thread as part of the project as a whole and so I’m not done.

Today I was on the roof almost all day installing new wiring and I got some bad sun burn, but I will try and get these Recap Posts done before end of the month.

For today’s video, I decided that something local is in order.

1 Like

The Recap Part 1

The why:

I actually explained quite well why I wanted to build what I built in the original post and so I don’t want to just copy paste everything here. Link to original post which has a link back to this point:
To 1st post

But, here’s a quick summary anyway.
-Windows Mixer hasn’t really changed since Vista.
-No way to specify default volume level for applications when they start up.
-Will reset audio levels when you plug your USB headset into a new USB port… or because reasons…
-Will change default output or capture device when plugging in or enabling. In my case it will sometimes switch my audio to my 4th monitor which is connected using HDMI and has its own sound, but I don’t want it to.
-No way to store and restore audio levels when doing a reinstall.
-No dark mode…. The white glare burns us!

-Building it in HTML because extra challenge and I prefer making UIs using HTML and CSS.
-Chance to use SignalR (Web sockets) and WebView2 (browser control) which I’ve never used before.
-Because I think it’s cool and Linux has had apps with HTML frontends you can access over the network using a browser since forever. Windows historically not so much and I think it’s neat even though I know it’s extra work that’s not needed for this use case.

The Recap Part 2

App overview:

We use a WPF windows desktop app to contain everything we’re going to be doing. It will host our web server in which 99% of functionality will exist. Below image is a very simple overview of what is in this app.

WPF App:
The WPF app has 2 windows. A main window and a small window that will ask if the user wants to shutdown or minimise the app when clicking the close button. It also has a TrayIcon so MF can run quietly in the background without a GUI open.

Confirmation window:

The main window holds the WebView2 control which is just a browser control. It’s based on Chromium and so works basically the same and supports everything chromium does. This control will host 99% of our UI which is done using HTML and CSS.

Main window with WebView2 control with no HTML content:

The WPF app is also responsible for starting the Kestrel web server. The web server will in turn initialise the AudioCore and DisplaySettings classes.

During startup we also init the Tray Icon, load theme info so the the WPF part matches the HTML part, and we register our events with our MessageBus class so we can easily talk between WPF and web server when things happen. For example, the message bus is used to update the colours of the WPF part on the fly as we change it in the HTML part.

The change event to update WPF to match HTML colours:

The WPF part is basically just our host… a container for our web site in a way. It has almost no UI or functionality tied to what we actually want to do. All the main functionality lives inside our website part.

1 Like

The Recap Part 3

AudioCore:

I’m using a library called NAudio.

This makes things a lot easier when trying to deal with Windows Audio. The library can do a lot, but I’m only really interested in the MMDevice and AudioSessionControl classes.

MMDevice represents the device you want to interact with. This can be your onboard sound or USB headset. MMDevice can be sound output, like speakers, or input like a Microphone.

AudioSessionControl represents an application you may have running that is producing sound. This can be your browser playing a youtube video, a game, or something like discord.

I created a wrapper class called ‘AudioObject’ that can hold either a MMDevice or an AudioSession. This wrapper class contains some functions like:
-ChangeVolume
-ChangeMute
-ChangeIsManaged
-GetMute
-GetVolume

When one of these functions are called, let’s say ChangeVolume, it will internally figure out if it needs to change the volume of a device or audio session and then I don’t have to worry about it. Below is an overview of the functions of this class. It also handles events (not show here) like when the volume changes in Windows so we can react to it.

I then created a class called AudioCore and this class will load your current default device along with any apps you may have running using the AudioObject class to store and keep track of everything. It will also keep track of apps starting up or shutting down and when an App starts up it will set the app to a default volume which is configurable in MF. An Update event will also be triggered so the UI will know to update as well.

Here we set our device, load all our audio sessions, and assign our event handlers:

AudioCore and the AudioObject will also update the DB with any changes made making sure it stays in sync with what is going on. If the volume of an App is changed, it gets updated in the DB right away. If an App is marked to be Managed by MixerFixer, then MF will try and push the volume levels stored in the DB to Windows effectively disabling changing of volume level in Windows.

In short we can catch when things change in Windows and we can also push changes to Windows. We can then also do things like detect when Windows changes something and then override it and put things back as they are stored in the DB if configured to do so.

This allows us to have volume levels persist across device changes or even system changes.

2 Likes

The Recap Part 4

Display Settings:

Library used:
WindowsDisplayAPI

Recap for my recap:
I’ve been using an app called ‘MultiMonitorTool’ with a shortcut on the desktop to quickly turn on or off my 4th monitor. In windows 8 this worked great as Win8 would remember the position of the 4th monitor no matter how many times I enabled or disabled it meaning it would always end up back where I initially placed it in Windows Display Settings.

Windows 10 does not and I have to manually place it back in the correct position every time I enable the display again. I just wanted a simple way to store a monitor’s current settings like position and then have it automatically restored whenever I enable the monitor. Having this be part of an Audio Control app is a little odd, I know, but I wanted everything in one place.

The over complicated and probably not needed solution:

I set out first using a different library, DisplaySettings. It supported reading and restoring things like the position and resolution which is really all I wanted, but it does not support turning a monitor on or off. This functionality wasn’t really needed, but something in me just had to have it. I tried a lot of methods I found online, but none actually worked. So after 2 days I gave up and I should have left it there…

Enter a different library… WindowsDisplayAPI. This supports everything I wanted, but I had a real hard time figuring out how to use it. After 2 more days and chatting to the dev I finally had some idea of what I needed to do.

The WindowsDisplayAPI contains some legacy code which doesn’t really work which is what I was stuck on for 2 days. What we are interested in are the following 3 classes: PathDisplaySource, PathInfo, and PathDisplayTarget

-PathDisplaySource is the source of what you want to display. This could be your main/only desktop, or your second/extended desktop.

-PathDisplayTarget is what the source should be displayed on. Basically, which monitor you want your source to be on.

-PathInfo connects your source and target(s). Each Path can only have 1 source, but could have multiple targets. In the case of a desktop clone, you have one source but 2 or more targets for example. In the case of an extended desktop, you’ll have 2 sources, 2 paths, and at least 2 targets.

The PathInfo object will contain a property for the Source and an array of targets. So for the most part you only really need to work with the PathInfo object.

The dev of the project also pointed me at another project of his that uses his WindowsDisplayAPI that I could use as an example of how to do what I want to do:
HeliosDisplayManagement

In the end, I get all the Active Paths, I then serialize the ones I want as JSON and store it in the DB. Serializing the object as JSON is exactly how it’s done in HeliosDisplayManagement. I can then at a later stage pull this json and de-serialize it back into a PathInfo object which allows me to restore a display to a previous state.

Below is how you get all Active Paths. I placed it in a function so I can reuse it.

In MixerFixer I update and store Display info every time the user marks a display to be managed or when the user turns a display off using MF so that MF knows how to restore the display if the user then turns the display back on using the Display Settings Modal in MF.

In the background a timer will check for any Display Info in the DB that is marked as Is Managed and if it detects that a Display was enabled it will try to restore that Display to a previous state automatically.

Below is what the Display Settings Modal looks like and the 2 options to mark a Display as managed or to turn it on or off.

To inform Windows which displays should be on (along with positions, resolution, frequency, ect) you have to pass in all the PathInfos for each display using the ‘ApplyPathInfos’ function in the library.

Let’s say I have 3 Displays and I want to turn 1 off.
-I get all the Active PathInfos which will result in an array with 3 objects.
-I then remove the object of the display I want off and then I’m left with 2 in the array.
-I then pass only those 2 objects/PathInfos to Windows and the 3rd display will be off.

Below I get all Active Paths except the one I want off:

Below is an example of turning a Display on.
-I first get all the current Active displays which in my case will be 2.
-I then add my PathInfo object which was stored as JSON in the DB, and so now I’m passing 3 objects which results in all 3 Displays/Monitors being on.

Was it worth spending 4 days to have the option to disable or enable a display using MF??? Probably not, but it’s in and I’m quite happy that it is.

1 Like

The Recap Part 5

The web parts:

The basics here consist of Kestrel, SignalR, and our webpage/HTML. When it comes to Kestrel and SignalR it will be better to read the official documentation rather than me trying to explain it all here. I’m far from an expert and will probably just get a lot wrong anyway.

Kestrel:

In short, Kestrel is our web server which will start our SignalR hub and host our web page. It allows us to connect to our webpage over the local network using a browser. This is not needed though. The WebView2 control supports comms between the page and the C# backend, but I wanted the page accessible over the network and for that we need a web server.

To enable kestrel in our WPF app, we have to edit our project file to include support for it. We also need to include any folders or files we want to embed in our web app so it all ends up as part of the final exe. These can be JS libraries, images, css files, ect.

Best would be to look at work done by someone far more qualified than I. Below link goes to a blog post that goes through step by step on how to host a HTML page inside of a WPF app:

SignalR:

I wanted to use websockets for this project and SignalR was an obvious choice here. It’s been around for some time now and it’s constantly updated. It will also automatically fall back to long polling if websockets is not supported on a given environment.

Websockets allows us to have full 2 way communication between our web page and the C# backend. It means we can push data to the page live at any time from the C# backend as things happen while the user might be interacting with the GUI at the same time. We don’t have to wait for user requests and this, along with a timer in the backend, is how I’m able to constantly push audio levels to the front end so you can see audio level changes live.

Webpage/HTML:

So with hosting and comms figured out, we need to look at our actual UI and how we are going to build it. I could easily just have stayed with WPF, but I really prefer doing UI things with HTML and CSS. So to make things harder that’s what I wanted to use.

Normally you will write some HTML and some CSS classes to create the UI you want, but we need something more dynamic than that. Something where we can create templates and have our HTML be generated as and when we need it.

There exist many ways to do this already and in C# there is a library for this exact use case:
Handlebars.Net

However, I don’t like the idea of having to write HTML by hand… even as templates. I wanted to see if I can come up with a way of generating HTML without needing to manually write any HTML by hand.

I came up with the idea of breaking it into 2 parts, the objects that represent my HTML elements and the Generator.

NOTE: This is by no means some kind of industry ready thing I’ve come up with. It was an idea I’ve had for a while and this was a good excuse to try it.

HTML Objects:
We define what we want using a C# class. This will be our ‘HTML object’. Inside of it will be what type of object it should be like Div or Img, what attributes it should have like id or class along with the value of that attribute, what children it should have, and what data it should have. In the case of a Label, we need to add what text it will be showing. So this is also where we do our data binding. This means our generator does not have to do any kind of find and replace work to bind our data to our HTML. Our generator can have a clean straight loop through our objects and generate our HTML in one simple swing.

Below you can see what this HTML object looks like. The Children property is just a list of HTML objects again and so you nest objects just like you would HTML elements when writing html by hand. It also has a function to add attributes which will also check if an attribute already exists in the list. If it does, it will just append the data to the existing attribute item so we don’t have to worry about accidentally creating multiple attribute items with the same name. The AddChild function is just to help make the code a little neater when using this object.

The Generator:
For the generator I had to write some HTML so it knows what and how to generate my HTML when I pass my ‘HTML object’ to it. This was quite simple as HTML elements are actually very simple. To generate an element I need to create the open and closing tags along with any attributes like id or class. Below is a piece of the generator that generates a Div.

The generator class has a single StringBuilder instance and as the generator loops through all the objects (using recursion) it will append the HTML string to this StringBuilder instance in the correct order. Meaning, open tag first, then attributes, then any child objects/elements, then closing tag.

And here’s a snippet of GetTag:

The way I use this is by creating some functions that will return my HTML objects. These functions act as my templates. Let’s say I want a Div with a Button inside it. I will then have a function that builds my Div Object and another function that builds my Button object. These functions will have some parameters so I can pass data to them like what Text I might want inside my button. In the case of MixerFixer, I can pass the whole AudioObject class so I can build the volume control along with the slider, text, toggle switches, and icon for the Speakers or application and bind any data I need as part of my HTML at the same time.

The Div function will call the Button function and add the Button object as a child of the Div. In the case of the button text, we add a child HTML object of Type ‘Raw’ to the button object with the text we want in our button. Raw means to take the data and add it to our HTML as plain text. A good example of where to use this would be a Label, but here I’m using it for my button. Naturally, the raw text you might want to add can be huge, but that’s up to you.

As for styling with CSS… that’s still done the good old fashion way… by hand. You can however specify the CSS class as an attribute when building the HTML object.

I then send my Div object to the generator and I get the HTML for a Button inside of a Div back as HTML string. Here’s our example in code.

And the resulting HTML string:

You can create template functions that are reusable. I for example created a function that will generate my toggle switches and I can now reuse this function over and over. I can also have my template just straight up return the HTML I want by building my template and then sending it straight to the generator to be turned into HTML. See example:

These templates can be as big and as complex as you want, but they are all built using the same basic ‘HTML object’ class just stacked as high as you want. It’s like having a piece of lego… you stack the same piece of lego as much as you want and can potentially build something quite large…


… James May’s lego house…

Once I created my HTML objects and then turned them into HTML string using the generator, I need to send my new HTML to the screen which we’ll look at in the next part as that is part of the comms between the front end and back end.

The Recap Part 6

Comms between frontend and backend…. The next part:

We need to be able to take user input on the frontend, send it to the backend to be processed, and then send updates back to the frontend.

To transport our data we are using SignalR/Websockets covered earlier, but the data we send can potentially be in all sorts of formats. I wanted to try and have a kind of ‘standardised’ way of passing data between the frontend and backend.

To do this I first created my CommObject, a C# class. This object contains things like what type of message it is, what data we might be passing, and then also things like our generated HTML and the state of any Modals we might have (show or hide).

Then on the backend we have a single function that will process all incoming messages. This might be text the user entered or a button click. This function will take the CommObject and essentially mutate it by potentially changing its CommType, adding Data, or even adding new HTML to it and then sending this mutated message back to the frontend.

Snippet of Backend ProcessMessage:

In the case of an exception, the CommObject will be mutated into a ShowMessage object along with the error message which will then be shown on the screen. Below I’m also adding the stack trace of the exception which will be logged to the console for debugging. Normally you won’t and should not send the stack trace to the frontend, but it’s a good example of how the CommObject can be mutated :stuck_out_tongue:

The ‘_Modal_Message_Show’ function is mutating the CommObject to have info about the state of the Modal, generating the HTML for the modal, and also changing the CommType so the javascript side knows how to deal with this CommObject.

On the frontend, javascript side, we again have a single function that will deal with any incoming CommObjects. It will always check for HTML and then add that HTML to the page. It will also check for any Modal info and then either show or hide a modal depending on the state of the model in the CommObject.

The ‘_SetAllHTMLs’ function is below and all we’re really doing is placing the HTML string inside of the specified container. This can be a div, button, or even style tags.

Below is how data/CommObjects move around. All user interactions ( frontend in green) follow this pattern. User does a thing, CommObject goes to backend for processing, mutated ComObject is sent back, JS handles the result/updates UI.

As for pushing from the backend side, the same type of CommObject is used but it flows in one direction.

The Data in the CommObject can be things like the Volume Level the slider should be at, the text of a label or input, or the current level of the audio that might be playing which in this case is just 2 divs working like a progress bar we constantly update. This gives us the bouncing bars as Audio is playing.

You need to include the id of the element you want to update, what type of element it is, and obviously the data you wish to update it with. Let’s say we want to change the text on a Label… the data needs to contain that’s it’s for a label with the id and the text we want to place inside the label.

The reason we need to know what type of element it is comes down to the fact that you access certain aspects of an element differently depending on the type of element it is. In the case of my Toggle switches, I need to access the ‘IsChecked’ attribute of the div that makes up the switch. For a text input I need to access the value property.

I’m sure there are many ways to improve and even simplify this and I do actually have some ideas, but for this project I didn’t want to dive down that rabbit hole just yet.

The Recap Part 7

Theming:

As part of our webpage/frontend, we obviously have CSS. Using CSS variables we can easily define our colours to be used and then if we want to have customizable theming all we have to do is override some of these CSS variables to change the colours.

Below are the variables for MixerFixer. Some of them are derived from the 3 base colours. I’m using an offset to lighten or darken some of the colours and that is how we get things like our ‘app header background’ and ‘background highlight’. This offset method is terrible and I won’t recommend it as it has limits. Fortunately, css handles those limits gracefully and for what I’m doing this will do. I’ve not done theming quite like this before and so will need to do more research on how I can achieve something similar without using an offset like this.

In the case of MixerFixer, we end up with 3 main colours we can override to change the whole look of the app.
MF_Theme_Background
MF_Theme_Accent
MF_Theme_Text

In reality though, we’ll be overriding the RGB variables for these 3 main colours.

Originally my idea was to have a colour picker, but that’s how most apps do it. Since I needed to override the RGB values I decided to use sliders instead. So for each of the 3 main colours, we have 3 sliders for red, green and blue with a range of 0-255.

Then it’s a fairly simple case of creating the variable with the same name for each of the RGB values and placing it on the page to override the defaults. And since we’re using websockets we can do this live.

Earlier I said that we can place our HTML string inside basically anything with an Id. This is how we override our CSS variables. On the page we have a Style tag with an Id. When we change any of the RGB values we generate new variables in C# as a string and add it as “HTML” to our CommObject and our _SetAllHTMLs JS function will take care of the rest.

Yes, we can do this just in JS and pass only the value to be stored in the DB to the backend. But although it’s not optimal, it does reduce the complexity and amount of JS I have.

Below is the C# function that will handle the change event when dragging one of the RGB sliders. It will update the DB with the new custom value, add a new HTML string (which is just our CSS overrides), some data to update the text on the screen, and send the CommObject back. Then our JS function will dump our “HTML” into our style tag and CSS takes it from there.

And here’s our HTML page with the Style tag where our CSS overrides end up…

We also need to inform the WPF part that the colours have changed. So we trigger the ThemChanged event we registered during startup and then WPF will read the new colour values from the DB and apply them to it’s own theme.

And then the LoadTheme function in WPF. C# does not handle colour values lower than 0 gracefully so we have to check it when applying our offsets. WPF only cares about the Background colour and Accent colour.

As already stated, things can be done very differently here and certainly a lot better. For what I’m doing I think it’s fine and I know there is a lot of room for improvement.

The Recap Part 8

The Modal:

I’ve used and made many HTML modals over the years, but they all had similar limitations. I would often need 2 sets of code that’s basically the same apart from one set that does not have the mask to make it a popup instead of a modal. Often the modals I used in the past would have a separate div as the background mask and JS would be needed to show both the mask and the content. Sure, normally, you didn’t really need to worry about this little detail as the library would take care of it for you and you just need to call a Show or Hide function. Still, I felt it could be better and it’s something we can really use at work.

I wanted to make a modal that could do both Modal with a mask and a popup with no mask with just a small property change. I also wanted to make the Modal quite simple to use and have it automatically restrict its max height and width depending on the amount of space available. Normally I would do this using JS, but things have moved on and I wanted to try and do as much as possible using just CSS.

Turns out, I could do basically everything with just CSS. Below is a link to a JSFiddle showing it in action:

I have my main Modal div which has width: 90vw; and height: 90vh;. This is what automatically gives us our maximum height and width for our modal. You can also use calc in CSS to fine tune what the max height and width should be.

-The main div then contains my background mask Div and my Content Div.

-The main div is set to have overflow: visible; This is so we can have the mask be visible when we make our main Div visible.

-The mask is position: absolute; along with a z-index: -1; The -1 is important to make sure the Mask does not interfere with the content.

-The mask also has width: 100vw; and height: 100vh; So now the mask will always be the full size of the viewport. More on viewport units here: CSS Viewport Units: A Quick Start — SitePoint

-The content has display:flex; This is important if we want it to automatically add vertical or horizontal scroll bars if the content is too big for the available space.

-The content body then has overflow: auto; which will give our scrollbars if the content is bigger than available space.

-If we want to show a popup with no background mask, we have to set a attribute on the main div like this: NoMask=“1”. In the css the mask will then be set to display:none; and with that we have a Modal and a Popup in one.

-The only JS needed is to set another attribute on the main div to either show or hide the modal, IsVisible=“1”

Using JQuery it looks like this:
$("#MyModalId").attr("IsVisible", "1");

One thing you’ll notice is that the HTML for all the Modals in MF is being generated and added to the page on the fly. For this we have a ModalHolder div on the page where we APPEND our Modal HTML.

When we close the modal we actually remove the html for that particular modal instead of just making it not visible. So every time you open a modal, a CommObject is sent to the backend, the backend builds our modal and sends it back, and finally our JS will add the HTML to the ModalHolder and ensure it’s visible.

I’m trying to remember all the important bits here, but I think I have it all covered. In hindsight I should have done more documentation on this as I was working on it. The fiddle is a working example and should be easy to copy if anyone is interested.

The Recap part 9

The Codes of QR

To make it easier to use something like your phone to open MF in the browser, I’ve added a HTML modal that will show a QRCode with the web address to MF. QRCodes have been around for a while and so I don’t think too much needs to be said here.

I’m using the QRCoder C# library:

You pass a string to the library and it creates a QRCode Bitmap with it. Then it’s up to you what you want to do with that Bitmap. In my case I wanted it shown in a Modal in my HTML page. So I convert the bitmap to a base64 string and add it as the value for my ‘src’ attribute in my HTML object. Once the generator runs through my HTML object it will embed that base64 string in the ‘src’ property of the html image element.

The Recap Part 10… and tiny update…

Tiny update.

Added a button so you can easily open MF in your default browser.

Conclusions:

I’m mostly happy with the outcome… mostly because it’s working… at least for me. I am very well aware that a lot of what I’ve done can be improved or done a lot better if I were to do it again.

In terms of the code and project structure I think it’s maybe a 5 out of 10. Not horrible, but not decent either. This usually happens though when you do a lot of stuff for the first time and you don’t know how things will turn out when you start. This can make it hard to plan properly ahead. More experience will also help a lot with planning in general even when working on new things.

In terms of functionality it’s going to have to be an 8 out 10. This is down to the fact that I could not do the Microphone boost like I wanted and nothing is perfect.

In terms of performance I would give it a 8 out of 10. Start up is a little slow, but performance seems pretty good while it’s running as it’s not using a lot of system resources. Resource usage goes up when you have the UI open, but that’s to be expected. Even with the UI open I think performance is quite acceptable.

Or I am completely out with my scoring and Ryan will tear me a new one… have at it :stuck_out_tongue:

I think I’ve covered everything that needs it. I obviously didn’t go into too much detail as I mostly wanted to bring across how things work rather than what each line of code does. Even so I probably included more code than what was needed.

And I’m sure there will be more bugs to fix :stuck_out_tongue:

GitHub is up to date and I also updated the ReadMe file there to include a video and some basic info on what it is, what it can do, and how to use it.

MixerFixer Github

I consider this project DONE!

Now let’s see how the little 5600x likes this…

Big Fixes

Found some bugs while using the app… shocking, I know :stuck_out_tongue:

Stupid infinite loops…

Had another case of an infinite loop when changing the volume of an app while it’s marked as Is Managed. Honestly, I’m not 100% how to deal with this properly and I think I need to change quite a bit to try and handle the volume change events triggered by Windows in a better way.

When I change the volume using MF, Windows will trigger a volume change event and then on rare occasions the DB somehow gets out of sync and then MF and Windows jumps between 2 volume levels forever until I kill MF.

To try and prevent this until I can come up with something better, I added a timeout to the OnChange event of the HTML slider control. Now the change will only be committed to the backend after a 15ms delay. If another change happens within that 15ms delay, it gets reset for another 15ms. This won’t solve the problem, but I hoping that it will result in the issue being even more rare. I’ve spammed click and dragged and scrolled the slider as much as I can and I can not reproduce the issue.

Change to JS code handling the Slider change event. First we clear the timeout, then we set the timeout. If the function is called before the 15ms passed, it will be cleared and then set again. Yes, a bit of a hack, but I need to rethink this and the events.
image

Device Priority issue

On my friend’s work laptop MF would just throw and error every time he tried to run it. Wasn’t really able to figure out why, but it seemed that a device on his system did not have the DeviceFriendlyName property in it’s property list. So when I tried to access it using the NAudio library it will result in an exception. To get around it I changed the function to loop through all devices and then skip a device if an exception is generated while trying to access the DeviceFriendlyName property. This solved the issue on the laptop and my friend was able to run MF just fine after that.

Weirdly, we see the same devices in MF vs what we see in Windows. I don’t know what MF is finding, but it seems even Windows is like…
image

Code change below. You can see at the bottom I tried to ignore all devices where the property is NULL, but it will still generate an exception.

Device change not detected

MF can enforce which device should be the current default depending on which devices are Active. If a device becomes active, MF will make it the default IF the user has set it up in MF and then update the UI.

But let’s say no devices are set in MF to be managed and your default device changes in Windows. Although I did have the DeviceChanged event it didn’t seem to do what I thought it would and the UI did not end up being updated. The device would change, but MF would continue to show the UI for the previous device.

To fix this I added a ELSE to the timer used to enforce the default device. If no devices are set to be Managed by MF, then it will check if the current device in MF matches the current default device in Windows. If it does not match, it will reload the AudioCore class and trigger an update for the UI.

So even though I have zero devices set as Is Managed in MF, the UI will update when I turn on my bluetooth headphones as Windows will make that the Default on it’s own.

Git readme fixes…

When I did the read me file on git, I was in quite a rush and missed some issues. It’s also fixed now.

That’s it for now.

I have the app running all the time and starting up with Windows. Apart from the 1 time infinite loop, it actually seems quite stable and doing what I wanted… at least on my machine :stuck_out_tongue:

Git update:
MixerFixer - Git

2 Likes

More Bug fixes…

App with same name… let’s just ignore it :stuck_out_tongue:

Some apps will show up twice with the same name like Discord. Looks like one instance is the sound of Discord app it self while the other is the audio of whatever channel you may be connected to.

image

I believe this is what actually caused the infinite loop mentioned in the post above. I did try and see if there is another way to Uniquely identify these instances, but Process ID is all that seems to be unique which I can’t use as it changes each time you start up an application.

I did have the idea of marking certain applications to be ignored by MixerFixer and decided to add that feature as a way for me to get around the duplicate name issue for now.

I have MF setup to default the volume of any application that starts up to 10%, but in the case of Discord I don’t want that. Problem is that I have 2 instances with the same name which is what I think caused my infinite loop issue. I still need to find a good couple of hours to try and reproduce this issue as right now I’ve not been able to.

For now I have MF set to ignore Discord so it won’t mess with the volume in any way regardless of what I set it to.

Git updated:
MixerFixer - Git

1 Like

very very awesome, i’ve read it all and truth be told most of it is way over my head but bits like the above one make perfect sense. if you already wrote bout this i’m sorry but have you thought about an overamplification option? like for some things (lookin’ at u plex :stuck_out_tongue:) which need a bump in amplification for source material that has a way low output?

i love the little switch toggle thingies, they’re great!

Thanks. Unfortunately boosting system audio at app level doesn’t seem like the way to go. I wanted to boost microphone input, but from what I can see online this is something you need to do on a lower level in the OS.

You can boost audio on app level if that app is producing the audio, like VLC for example, but if you want to boost system wide or boost app from outside the app (Like boost VLC with another app), you need to intercept the audio, boost it, and then send it along to it’s destination like your headphones. There’s probably some hacky way to do it, but I’ve not found it.

It sounds like you need to boost certain source files and not everything? That will need to be done either on the fly by plex or the media player that you use. VLC can go up to 200% and Media Player classic has a boost option in the settings somewhere.

In your case, finding a way for your media player to boost on the fly will probably be the easiest as some plugin might already exist… although I’m assuming you’ve already looked.

You could also look into using something that can open your source material and boost the volume in the files directly, but that will require you to go and manually do it or write an app that can do it for you. It looks like FFmpeg can do just that. It can also detect the current volume level in a file which can be useful to then boost it by the correct amount to get it inline with other content.

FFmpeg Audio Volume Manipulation

I use FFmpeg to merge the audio and video files I get from the youtubes by passing arguments to the FFmpeg exe from an app I wrote I think in 2014. FFmpeg can be super useful… worth a look for anyone playing around with video and audio.

However, if by chance you are watching plex on a specific machine (and it’s windows), then you can boost and/or change all kinds of audio stuff using:

https://equalizerapo.com/

I use it to boost my microphone, but it can do all sorts of cool and interesting stuff to your audio. When you run it for the first time, it will ask which audio devices you want to be affected and it will require a restart.

Unfortunately it does not look like you can make changes to audio for a specific app. However, if you are watching plex through a specific audio device then this might still be useful as you can boost that audio device. Everything that uses that audio device will be affected, but I bring this up in the small chance that only plex might be affected when boosting your audio device.

Not sure how helpful any of this is, but feel free to ask if you think I can help :smiley:

1 Like

yeah its a lovely feature & my distro does overamplification though you’re absolutely spot on, system wide would have to be handled at a lower level.

yup and my roomie who runs our plex server also has as well but its likely such a common problem that someone, somewhere has a fix…just needle + haystack kinda dealio!

very, and its appreciated thank you. I’m not saying we’re lazy & don’t want to ffmpeg (or handbrake increase levels) every single file) was just hoping there was an OTF method is all :slight_smile:

Anyways great work on your devember project!

1 Like