What this does: controlling volume for deck 1 or deck 2 from any interface (midi controllers, GUI, or TouchOSC) results in midi output sent back to TouchOSC to update their positions
Note: I did this without reading https://github.com/mixxxdj/mixxx/wiki/midi%20scripting and I think the midi scripting interface probably resolves the issues I had and simplifies the approach.
A google of mixxx osc
turned my afternoon into this...
https://www.reddit.com/r/DJs/comments/rwq59j/djing_on_a_budget_homemade_touchosc_controller/ https://github.com/grufkork/touchosc-dj
This TouchOSC controller is awesome, but I also wondered if it could work in reverse: use controls in the Mixxx GUI or other midi controllers, and have the updated mixer state be reflected in TouchOSC. For example, use the touchscreen for super fast fader cutting in/out, but use a midi controller for tactile faders most of the time.
The TouchOSC config is already good for this, just need to create output mappings...
The "Output Mappings" section for Mixxx controllers has the following columns:
- Channel
- Opcode (e.g. CC)
- Control (default 0x00)
- On Value (default 0x00)
- Off Value (default 0x00)
- Action (e.g. "[Channel2],volume" = Deck 2 volume fader)
- On Range Min (default 0x00)
- On Range Max (default 0x00)
The mixxx documentation doens't exactly explain these columns, and it took a lot of trial and error before it clicked to me what they did.
My original thinking was it was going to be trivial to assign a fader to a CC (fader value = 0% = CC=0, fader value = 100% = CC=127), and that would be one line in the Output Mapping table. It's more complicated.
Eventually figured out:
- On Range (Min, Max) is the range of the "Value" where the CC will have "On Value"
- The volume faders are non-linear, so while the "Parameter" goes from 0 to 1, the "Value" that is needed for midi mapping scales logarithimcally (this can be seen if running
mixxx ---developer
, opening Developer > Developer Tools menu, and finding Group [Channel 1], Item "volume", and observing the Value and Parameter columns.
For example
- fader at 0%: Value = 0, Parameter = 0
- fader at ~50%: Value = 0.231179, Parameter = 0.488636
- fader at 100%: Value = 1, Parameter = 1
Found the math for the scaling in the source code here: src/engine/enginemaster.cpp
pChannelInfo->m_pVolumeControl = new ControlAudioTaperPot(ConfigKey(group, "volume"), -20, 0, 1);
with some words in class ControlAudioTaperPot : public ControlPotmeter
:
// The AudioTaperPot has a log scale, starting at -Infinity
// minDB is the Start value of the pure db scale it cranked to -Infinity by the linear part of the AudioTaperPot
// maxDB is the Upper gain Value
// neutralParameter is a knob position between 0 and 1 where the gain is 1 (0dB)
Doing a test with converting the C++ to python, comparing to obserbed params/values:
data = [(0, 0),
(0.0764014, 0.22723),
(0.222339, 0.477273),
(0.744194, 0.88634),
(1, 1)]
minDB = -20
maxDB = 0
neutralParameter = 1
for expected_value, parameter in data:
value = parameter_to_value(parameter, minDB, maxDB, neutralParameter)
print(f"Parameter: {parameter:.6f} --> Expected Value: {expected_value:.6f} / Computed Value: {value:.6f}")
# Parameter: 0.000000 --> Expected Value: 0.000000 / Computed Value: 0.000000
# Parameter: 0.227230 --> Expected Value: 0.076401 / Computed Value: 0.076383
# Parameter: 0.477273 --> Expected Value: 0.222339 / Computed Value: 0.222339
# Parameter: 0.886340 --> Expected Value: 0.744194 / Computed Value: 0.744148
# Parameter: 1.000000 --> Expected Value: 1.000000 / Computed Value: 1.000000
So that's perfect.
Another thing is the "On Range Min" and "On Range Max" should be centered around the actual value, so in the script I did this with
range_min = (previous_value + center_value) * 0.5
range_max = (center_value + next_value) * 0.5
Other issues I've encountered:
- I'm using https://github.com/velolala/touchosc2midi to get the midi from my Microsoft Surface Pro 5 over to my linux box (notes on getting it working on Arch Linux)
- related to previous point, I think Mixxx will not interpret output mappings under my "RtMidiIn Client:TouchOSC Bridge 129:0" Controller, since for some reason it doesn't output any midi to the related "TouchOSC Bridge" output... possibly because different name or it thinks they aren't the same device?
- I tried messing with
modprobe snd-virmidi
for virtual midi devices, but those don't work as expected - I found out you can use "Midi Through" loopbacks in Mixxx only if you run
mixxx --developer
(issue: mixxxdj/mixxx#8356, PR: mixxxdj/mixxx#4148) - not sure if there is a solution in RtMidi and/or alsa/pipewire to avoid this
- I tried messing with
- so I'm using a seperate "controller" for the outputs going to "Midi Through" and then routing that to TouchOSC Bridge, so have an independent mixxx xml file for this, but otherwise you could combine this xml with the touchosc-dj one
This solution only covers volume faders, and I think the Parameter vs Value problem exists for a lot of other controls we might want to map like this. E.g. tempo is complicated by rate ranges, cross-fader is non-linear, etc.
I have yet to look at the documentation for javascript controller stuff, so maybe this is already all solved there and this whole gist is obsolete. Ideally there would be an interface to directly map to "Parameter" and not have to work backwards from ParameterToValue functions