Audio mixing recommendations?
James Harkins
Jun 04 2023 | 12:52 am
Since my question about AudioMix died on the vine, let me ask a broader question -- what is the best way to handle mixing in Max?
Design ideals would be: A/ maximal reuse (minimal rebuilding of the same structures again and again), B/ easy persistence of mix settings (ideally, effortless for the end-user), C/ easy-to-use GUI.
AudioMix nails A and C but B seems slightly delicate (and not a lot of information about how to improve that).
I could live without the GUI but persistence is kind of a big deal (reopen patch --> mix is destroyed, that's not usable).
Thanks.
hjh
- Roman ThileniusJun 04 2023 | 11:47 ami have a modular mixer bpatcher collection, too, but i dont really use it, it is often faster to use vanilla objects.i mean... gain staging is [*~], an effect-send consists of one connection, and an effect-return of another one. summing is done by connecting two connections into the same place. if you like to use insert effects, make yourself a little abstraction from 2 gate objects and you are done, M/S is + and - and a shelving filter is in the biquad helpfile.there is mot much you could actually build or program when it comes to mixing, unless you have to do something strange like using 10 DSP powered VST plug-ins as 10-channel plug-in in a custom surround mixer with wacom control.
- James HarkinsJun 05 2023 | 4:59 amI guess I look at it like this: If it takes three minutes to rebuild stereo level scaling and panning, that's three minutes that I'm not spending on production.When I picked up SuperCollider, I knew I was not going to want to address mixing in terms of buses and groups. So I wrote a MixerChannel class, which time investment has more than paid off by now.Working on course demos in Pd or Max, I often don't even bother with panning because it's just that little bit more work than it's worth. When you start compromising results for that reason, it indicates a usability problem.AFAICS the smallest number of objects you need to build a quick fader from scratch is mc.pack~, mc.stereo~ (because mc.stereo~ at least gives you linear panning in one object -- not sure if you can get equal power here, though, otherwise you need to clip the pan signal, invert it for one channel, and apply the sinusoidal or square root function separately, with two *~'s... the time adds up), and mc.*~, provided that you remember to use mc.dac~ instead of dac~. Indeed, the mc way is not *bad* exactly... but I don't like to spend time rebuilding fundamental architecture. I also didn't quite care to reinvent the wheel in Max, so I wondered if anyone had a ready-made solution.hjh
- Peter OstryJun 05 2023 | 3:04 pmI don’t think you will find a Max solution you are happy with. Properly named variables for storing mixer settings and probably automation, trim function, panning and pan-law, mute, solo, solo in place, PFL, grouping, metering etc, whatever you want in your mixer. Another developer will most likely have other priorities, at least I never heard about a complete mixing solution in Max. Max is not famous for mixing, most people mix in another DAW or on hardware. Obviously you know how to program all this mixing stuff, so it might be better to make the modules yourself.
- Roman ThileniusJun 05 2023 | 7:25 pmit totally make sense what you said, and i can follow you. but if a process only consist of two objects and you wrap it into a dozen mini abstractions you will still have to make 2 connections to get it involved in your DSP.it is hard to recommend a best practice, because you have to decide yourself what you need more often and what you need less often.for example you could build a mixer abstraction (without GUI) for 16 stereo channels and with all the features you need and reuse that for the next 100 years, no problem. you can design it so that it´ll work with button, toggle and multislider right out of the box.[110.gain~] is a *~ and [110.gain-ip~] is a *~ with a line~ for the control input. then there is [110.manygain~] which does the same for 16 channels at once and [110.multigain~] with 16 indivual controls for each of the 16 channels. the latter is already a little mixer. now you can use prepend or mc to output the 16 audio channels in a single connection in order to send them to [110.multipan~] to control the panning with signals. or to [110.multipanning~] to control the pananing with data rate, or to [110.multipanner~] to use a lookuptable for the panning function. if it already stereo channels, you would already have an abstraction called multibalance...so far so good. but sooner or later you might feel the need for a 32 channel version. or for total recall. or for a mono version. then you have to continously update and expand your abstractions collection and eventually end up with more abstraction than different projects.a simple bpatcher for a "channelstrip as in a DAW" already requires scripting for changing the number of I/Os (mono/stereo/surround) and poly~ or mc~ for the different versions. then they need a s/r system to make things like "effect return" and "mute/solo" available, and at this point you have not saved a preset of this thing or involved plug-ins. i would not advised against it.but always consider to build the whole mixer in a single patcher as a possible alternative which causes less work.
- James HarkinsJun 08 2023 | 4:51 amYes, I understand your point.In SuperCollider, I handled that by having MixerChannel and MixerChannelDef -- where the latter defines number of channels in and out, parameters with expected ranges, and the signal processing to apply. So, I didn't define e.g. ambisonic mixers because the user can do that, any time ("requires scripting for changing the number of I/Os" is a non-issue over there).Graphical patching AFAICS struggles with dynamic creation of information-flow paths; it's much simpler in a text language. So "you might feel the need for a 32 channel version" is not a problem in that context. If I need five mixers, I create five. If I need 200, I create 200.``` s.options.numAudioBusChannels = 512; s.boot; ( ~mixers = Array.fill(200, { |i| MixerChannel(("chan" + (i+1)).asSymbol, s, 2, 2 ) }); ) ```In Pd, I used [throw~] / [catch~] and [send~] / [receive~] for cable-free connections between mixers, sends etc. Tidier (though with some risk of block delays, perhaps not OK for every use case).Anyway... I'm opinionated, I guess, and anyone is free to ignore me. I was wondering what people had come up with... seems that the general answer is a bespoke solution for every project.hjh