A while back, I ditched my AVR for a miniDSP Flex HT. I really liked having more direct control over room correction (I use a combination of MSO and REW), but the one thing I missed was dynamic loudness compensation (or Dynamic EQ in Denon-speak).
I've recently completed a DIY project to replicate this feature in my system. I thought I'd share it here to help anyone who'd like to do the same thing, and also to get feedback if I've made any glaring errors.
One other note before diving in: I am not a programmer and have had no training. I've included the python script below, but I'm certain the same thing could be accomplished more elegantly and efficiently. The script comes with absolutely no warranties, as you'd expect with something posted to a forum, but maybe even more so since I don't actually know what I'm doing.
Ok, enough preamble.
Repository: https://github.com/mrene/minidsp-rs
Documentation: https://minidsp-rs.pages.dev
I installed the package on a Raspberry Pi Zero 2W using the available .deb package files.
When I purchased my miniDSP, I also got a WI-DG wireless bridge. This allowed me to connect the Device Console configuration software to the unit over my local network. The wireless bridge also provides an API over TCP that can be addressed by minidsp-rs. So, for example, a status request can be made like this:
Unfortunately, my Flex HD is not yet supported by minidsp-rs, and the above status request returned information on volume, preset, and mute, but the input and output levels were blank. The Flex HTx is partially supported, however, and I was able to get input and output levels using:
Additionally, minidsp-rs can be told to output to JSON for easier manipulation:
There's no push functionality in minidsp-rs, so my first script just polled the unit every few seconds and printed the results. This worked great when the unit was on. When the unit was turned off, the command returned an error which was easy to trap, but often took 10-15 seconds to timeout, which was not ideal.
So I connected the pi to the minidsp directly via USB and confirmed that the status request still threw an error when the unit was off, but only took a fraction of a second to do so. But now I needed a way to connect the Device Console software to the pi.
Fortunately, minidsp-rs comes bundled with a daemon and system service file for just this purpose. Follow the instructions to configure the daemon and set up the system service.
But now I had a new problem. If you make a status request to the WI-DG when Device Console is connected, the request is ignored and eventually times out. But if you make a status request to the unit over USB when Device Console is connected to the minidsp-rs daemon, it interrupts the service and throws javascript errors in Device Console. So I needed to stop polling the unit when Device Console was connected. I ended up using the
Where 5333 is the standard port for Device Console.
At this point I had a working script that polled for the current miniDSP volume level, and paused polling when Device Console was connected. Time to actually apply some loudness compensation.
This essentially amounts to a low shelf filter at 70 Hz and a high shelf filter at 3500 Hz, with a Q of 0.707 and a (default) gain of 0.5 times the difference between the current volume and some defined reference volume.
Reference volume is a bit of a vague target. I've often seen it stated that movies are mixed at 85 dB RMS, and it's been mentioned that music is usually mixed lower. Since this is all adjustable after the fact, I decided a reasonable starting place would be 82.5 dB, which corresponds on my system to about -5 on the miniDSP.
Once a reference level is chosen, it's easy to calculate the gain of the shelf filters (and the ratio can also be adjusted later). So now I just needed to get the filters dynamically loaded on the miniDSP.
The minidsp-rs documentation explains how to load filters from biquads in a file, but digging through the GitHub discussions, I found that biquads can also be loaded directly. The form of the command is:
N and M are the zero-indexed channel and filter numbers, and b0, b1, b2, a1, and a2 are biquad coefficients. Setting input filters appears to be supported on some units, but didn't work on the Flex HT(x).
To calculate the biquad coefficients, I used the formulae found here:
Once I coded the calculations, I did a reality check by comparing the output to equivalent filters generated in REW. However the calculated values for a1 and a2 were always the opposite sign as those in REW, which disturbed me at first. Eventually, I found that this is a documented (almost in passing) feature on miniDSP:
www.minidsp.com
With all the pieces now in place, I updated the script and prepared to enjoy automated dynamic compensation on my miniDSP!
Well, almost. The script worked great, and it sounded much better than no compensation, but it wasn't quite right. I played around with different reference levels and filter gain ratios, but was never entirely satisfied. So I figured I could do better.
The calculator only produces 29 data points, but this is still enough to copy into a spreadsheet, export as a csv file, and import into REW. REW extrapolates values between the points.
Since I'd already selected 82.5 dB as my reference level, I created curves in REW for 82.5, 77.5, 72.5, 67.5, and 62.5 phon. Here they are:
I'm actually not interested the curves so much as the difference between the curves, so first I normalized the SPL:
And then used trace division to plot the -5, -10, -15 and -20 curves relative to the reference level. These became my target curves for the compensation filters (note the zoomed-in scale):
The next step was mostly trial and error. I needed to apply the shelf filters linearly (meaning the PEQ for -20 would be the same as -10, except with twice the gain). So I hand-crafted filters for -10, applied scaled versions to the other levels, and then iterated until I was satisfied. In the end, this is what I settled on:
Low Shelf:
Fc: 180 Hz
Q: 0.4
Gain: 0.5 * (reference_volume - current_volume)
High Shelf:
Fc: 12,000 Hz
Q: 0.66
Gain: 0.35 * (reference_volume - current_volume)
And here's how these filters compare to the targets:
As you can see, the low compensation starts diverging from the target at around 50 Hz, but this was preferable to deviations at higher frequencies, which I expected to be more audible.
The only changes I needed to make to my script were the values used to generate the biquads, so it was trivial to get the new compensation filters working. Sounded great. After a week or so, I decided that the compensation effect was just a little strong for my taste, so I adjusted my reference level down 2.5 dB and have been happy ever since.
I've also attached the .mdat file with the ISO 226 curves, targets, and filters:
I hope this is useful to someone.
Edit: minor changes for clarity and grammar
I've recently completed a DIY project to replicate this feature in my system. I thought I'd share it here to help anyone who'd like to do the same thing, and also to get feedback if I've made any glaring errors.
One other note before diving in: I am not a programmer and have had no training. I've included the python script below, but I'm certain the same thing could be accomplished more elegantly and efficiently. The script comes with absolutely no warranties, as you'd expect with something posted to a forum, but maybe even more so since I don't actually know what I'm doing.
Ok, enough preamble.
1. Controlling the miniDSP
Controlling the miniDSP unit is accomplished with the open source minidsp-rs package:Repository: https://github.com/mrene/minidsp-rs
Documentation: https://minidsp-rs.pages.dev
I installed the package on a Raspberry Pi Zero 2W using the available .deb package files.
When I purchased my miniDSP, I also got a WI-DG wireless bridge. This allowed me to connect the Device Console configuration software to the unit over my local network. The wireless bridge also provides an API over TCP that can be addressed by minidsp-rs. So, for example, a status request can be made like this:
$ minidsp --tcp 192.168.0.10 statusUnfortunately, my Flex HD is not yet supported by minidsp-rs, and the above status request returned information on volume, preset, and mute, but the input and output levels were blank. The Flex HTx is partially supported, however, and I was able to get input and output levels using:
$ minidsp --force-kind flexhtx --tcp 192.168.0.10 statusAdditionally, minidsp-rs can be told to output to JSON for easier manipulation:
$ minidsp --output json --force-kind flexhtx --tcp 192.168.0.10 statusThere's no push functionality in minidsp-rs, so my first script just polled the unit every few seconds and printed the results. This worked great when the unit was on. When the unit was turned off, the command returned an error which was easy to trap, but often took 10-15 seconds to timeout, which was not ideal.
So I connected the pi to the minidsp directly via USB and confirmed that the status request still threw an error when the unit was off, but only took a fraction of a second to do so. But now I needed a way to connect the Device Console software to the pi.
Fortunately, minidsp-rs comes bundled with a daemon and system service file for just this purpose. Follow the instructions to configure the daemon and set up the system service.
But now I had a new problem. If you make a status request to the WI-DG when Device Console is connected, the request is ignored and eventually times out. But if you make a status request to the unit over USB when Device Console is connected to the minidsp-rs daemon, it interrupts the service and throws javascript errors in Device Console. So I needed to stop polling the unit when Device Console was connected. I ended up using the
ss command to check for an active connection:$ ss -l state established | grep -w 5333Where 5333 is the standard port for Device Console.
At this point I had a working script that polled for the current miniDSP volume level, and paused polling when Device Console was connected. Time to actually apply some loudness compensation.
2. CamillaDSP-style filters
CamillaDSP has a well-documented loudness compensation feature, which seemed like a reasonable place to start:This essentially amounts to a low shelf filter at 70 Hz and a high shelf filter at 3500 Hz, with a Q of 0.707 and a (default) gain of 0.5 times the difference between the current volume and some defined reference volume.
Reference volume is a bit of a vague target. I've often seen it stated that movies are mixed at 85 dB RMS, and it's been mentioned that music is usually mixed lower. Since this is all adjustable after the fact, I decided a reasonable starting place would be 82.5 dB, which corresponds on my system to about -5 on the miniDSP.
Once a reference level is chosen, it's easy to calculate the gain of the shelf filters (and the ratio can also be adjusted later). So now I just needed to get the filters dynamically loaded on the miniDSP.
The minidsp-rs documentation explains how to load filters from biquads in a file, but digging through the GitHub discussions, I found that biquads can also be loaded directly. The form of the command is:
$ minidsp output N peq M set -- b0 b1 b2 a1 a2N and M are the zero-indexed channel and filter numbers, and b0, b1, b2, a1, and a2 are biquad coefficients. Setting input filters appears to be supported on some units, but didn't work on the Flex HT(x).
To calculate the biquad coefficients, I used the formulae found here:
Once I coded the calculations, I did a reality check by comparing the output to equivalent filters generated in REW. However the calculated values for a1 and a2 were always the opposite sign as those in REW, which disturbed me at first. Eventually, I found that this is a documented (almost in passing) feature on miniDSP:
Advanced Biquad Programming
Flexibility really matters when it comes to innovative speaker design. As DIYers and speaker designers ourselves, we came to realize that miniDSP platforms could be used for both basic and advanced digital signal processing applications. While textbook filter implementation like Butterworth...
With all the pieces now in place, I updated the script and prepared to enjoy automated dynamic compensation on my miniDSP!
Well, almost. The script worked great, and it sounded much better than no compensation, but it wasn't quite right. I played around with different reference levels and filter gain ratios, but was never entirely satisfied. So I figured I could do better.
3. ISO 226 based filters
I decided to try to base my loudness compensation on the ISO 226 equal-loudness curves we have probably all seen. Graphs of the curves are everywhere, but it took me a little searching to find tabular data for them. There are probably other calculators out there, but this is the only one I found:The calculator only produces 29 data points, but this is still enough to copy into a spreadsheet, export as a csv file, and import into REW. REW extrapolates values between the points.
Since I'd already selected 82.5 dB as my reference level, I created curves in REW for 82.5, 77.5, 72.5, 67.5, and 62.5 phon. Here they are:
I'm actually not interested the curves so much as the difference between the curves, so first I normalized the SPL:
And then used trace division to plot the -5, -10, -15 and -20 curves relative to the reference level. These became my target curves for the compensation filters (note the zoomed-in scale):
The next step was mostly trial and error. I needed to apply the shelf filters linearly (meaning the PEQ for -20 would be the same as -10, except with twice the gain). So I hand-crafted filters for -10, applied scaled versions to the other levels, and then iterated until I was satisfied. In the end, this is what I settled on:
Low Shelf:
Fc: 180 Hz
Q: 0.4
Gain: 0.5 * (reference_volume - current_volume)
High Shelf:
Fc: 12,000 Hz
Q: 0.66
Gain: 0.35 * (reference_volume - current_volume)
And here's how these filters compare to the targets:
As you can see, the low compensation starts diverging from the target at around 50 Hz, but this was preferable to deviations at higher frequencies, which I expected to be more audible.
The only changes I needed to make to my script were the values used to generate the biquads, so it was trivial to get the new compensation filters working. Sounded great. After a week or so, I decided that the compensation effect was just a little strong for my taste, so I adjusted my reference level down 2.5 dB and have been happy ever since.
4. Files
I've attached the python script I wrote. Again: use at your own risk, this could break things! You'll want to set it up as a user service (should be easy to find instructions online).I've also attached the .mdat file with the ISO 226 curves, targets, and filters:
I hope this is useful to someone.
Edit: minor changes for clarity and grammar
Attachments
Last edited: