Open Sound Control (OSC) Teensy Audio Library Implementation

@manicksan, are you OK with my adding my edits of your your DynMixer code into my Dynamic Audio library?

I've got it working here, along with a rather kludgy implementation added to /dynamic/crObject to allow the OSCAudio library to create instances of AudioMixerX of a requested size. You use /dynamic/crOb,ssi<type><name><size> or /dynamic/crOb,sssi<type><name><group><size> where <type> is "AudioMixerX" - I think this is consistent with your GUI++ as it stands at the moment. Maybe ,sis or ,siss would be slightly more logical, but I don't think it really matters.

I've also done the required edit to cores so a failed malloc() of inputQueueArray won't crash the system, so if you're OK with me adopting DynMixer, then I can push a set of changes to all three parts to unlock that area of functionality.
 
The OSC type name should be just AudioMixer as that is what it's called in the tool and therefore I don't need to create another type in the tool or have to do some special type conversions, I did a lot of checks like that in a previous version of the tool and it was hard to manage in the end.
Also that name is now free after I kind of made the c++ template mixer obsolete, and even if the template mixer would still be used it would probably not be together with the OSC dynamic one.
 
Yes you can add the dyn mixer to the lib
As that is what it was made for.

I did create that name because I did not want it to conflict with the template mixer.
And by using the same type name the design could just be exported as a static version if someone want that.
 
OK, I'll change the class name from AudioMixerX to AudioMixer, for now. I'm not 100% happy with that as the name, because it doesn't really tell the user much about the functionality, i.e. that it's a flexible number of inputs and a mono output: in the longer term it should probably be something like AudioMixerNtoMono, so we can add AudioMixerNtoStereo and so on. I expect @PaulStoffregen will have an opinion on this, when he has enough time to take an active interest!
 
But a stereo mixer can be done using two mono mixers. Also there is a function missing in the dyn mixer
That changes the gain of all channels in one go, I have it in the template mixer, and think it's s really good function to make the external coding as small as possible.

Think we don't want any long and complicated names either.
 
because it doesn't really tell the user much about the functionality
A mixer is a mixer, it don't really say how many channels there are it can be anything from 2 to infinity
 
One question
Is there any error feedback when audiomixer/(future dynamic objects) fail to allocate.
No. I'm not 100% sure what the "correct" response is in such cases, though if we're that short of memory I suspect the system would be likely to crash anyway. I did implement getChannels() which returns the actual number of channels available: this will be zero if the allocation failed, but I suspect the OSC library would not be working by then, as it does a lot of temporary allocation itself.

Speaking of future dynamically-sized objects, I have done a stereo mixer in the DynMixer source file, and support for it in OSCAudio. I know you can do it with two mono mixers, but it's a pain to pan sounds cleanly... If it's a real hassle to support in GUI++ I'll shelve it for now, but it'd be nice to have if possible.
 
I'm just experimenting with a slightly simpler grouped design:
Code:
{"version":1,"settings":{"arduino":{"ProjectName":"GroupExampleStereo2","Board":{"Platform":"","Board":"","Options":""}},"BiDirDataWebSocketBridge":{},"workspaces":{},"sidebar":{"autoSwitchTabToInfoTab":false},"palette":{},"editor":{},"devTest":{},"IndexedDBfiles":{"testFileNames":"testFile.txt"},"NodeDefGenerator":{},"NodeDefManager":{},"NodeHelpManager":{},"OSC":{"LiveUpdate":false,"UseDebugLinkName":true}},"workspaces":[{"type":"tab","id":"3629fcd9.ccc604","label":"Main","inputs":0,"outputs":0,"export":true,"isMain":true,"mainNameType":"tabName","mainNameExt":".ino","generateCppDestructor":false,"extraClassDeclarations":"","settings":{"workspaceBgColor":"#EDFFDF","scaleFactor":0.9,"showGridHminor":false,"showGridHmajor":false,"showGridVminor":false,"showGridVmajor":false,"useCenterBasedPositions":false},"nodes":[{"id":"Main_waveform1","type":"AudioSynthWaveform","name":"LFO","comment":"","x":455,"y":455,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_MonoSynth1:0"]]},{"id":"Main_MonoSynth1","type":"MonoSynth","name":"MonoSynth[6]","x":570,"y":455,"z":"3629fcd9.ccc604","bgColor":"#CCFFCC","wires":[["Main_mixer1:0","Main_mixer2:0"]]},{"id":"Main_mixer1","type":"AudioMixer","name":"mixerL","inputs":1,"comment":"","x":755,"y":425,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_i2s1:0"]]},{"id":"Main_mixer2","type":"AudioMixer","name":"mixerR","inputs":1,"comment":"","x":755,"y":470,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[["Main_i2s1:1"]]},{"id":"Main_i2s1","type":"AudioOutputI2S","name":"i2s","comment":"","x":890,"y":455,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[]},{"id":"Main_sgtl5000_1","type":"AudioControlSGTL5000","name":"sgtl5000","comment":"","x":860,"y":505,"z":"3629fcd9.ccc604","bgColor":"#E6E0F8","wires":[]},{"id":"Main_Button1","type":"UI_Button","name":"use connection to connect wafo to mixer1 input 1","comment":"","w":360,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/audio/wafo2mixer1/connect*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"sisi\", \"wafo\", 0, \"mixer1\", 1);\nOSC.SendAsSlipToSerial(data);","x":2200,"y":170,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button2","type":"UI_Button","name":"create new AudioSynthWaveForm wafo","comment":"","w":275,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/dynamic/createObject*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"ss\", \"AudioSynthWaveform\", \"wafo\");\nOSC.SendAsSlipToSerial(data);","x":2260,"y":90,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button3","type":"UI_Button","name":"create connection wafo2mixer1","comment":"","w":223,"h":30,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/dynamic/createConn*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"s\", \"wafo2mixer1\");\nOSC.SendAsSlipToSerial(data);","x":2260,"y":130,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button4","type":"UI_Button","name":"do all above in one go","comment":"","w":157,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var data = osc.writeBundle({\r\n        timeTag: osc.timeTag(0),\r\n        packets: [\r\n            {\r\n                address: \"/teensy1/dynamic/createObject*\",\r\n                args: [\r\n\t\t\t\t\t{type: \"s\", value: \"AudioSynthWaveform\"},\r\n\t\t\t\t\t{type: \"s\", value: \"wafo\"}\r\n                ]\r\n            },\r\n            {\r\n                address: \"/teensy1/dynamic/createConn*\",\r\n                args: [\r\n                    {type: \"s\", value: \"wafo2mixer1\"}\r\n                ]\r\n            },\r\n            {\r\n                address: \"/teensy1/audio/wafo2mixer1/connect*\",\r\n                args: [\r\n                    {type: \"s\", value: \"wafo\"},\r\n\t\t\t\t\t{type: \"i\", value: 0},\r\n\t\t\t\t\t{type: \"s\", value: \"mixer1\"},\r\n\t\t\t\t\t{type: \"i\", value: 1}\r\n                ]\r\n            }\r\n\t\t\t\r\n        ]\r\n    });\r\nOSC.SendAsSlipToSerial(data);","x":2280,"y":210,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button7","type":"UI_Button","name":"Button","comment":"","w":100,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/dynamic/createObject*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"ss\", \"AudioSynthWaveform\", \"   \");\nOSC.SendAsSlipToSerial(data);","x":2340,"y":280,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]},{"id":"Main_Button8","type":"UI_Button","name":"rename waveform2 to wafo1","comment":"","w":275,"h":34,"textSize":14,"midiCh":"0","midiId":"0","pressAction":"","repeatPressAction":false,"releaseAction":"","repeatReleaseAction":false,"local":"true","sendCommand":"var addr = \"/teensy1/dynamic/ren*\"\nvar data = OSC.GetSimpleOSCdata(addr,\"ss\", \"waveform2\", \"wafo\");\nOSC.SendAsSlipToSerial(data);","x":2295,"y":370,"z":"3629fcd9.ccc604","bgColor":"#F6F8BC","wires":[]}]},{"type":"tab","id":"de8d0e25.32ebb","label":"MonoSynth","inputs":0,"outputs":0,"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","generateCppDestructor":false,"extraClassDeclarations":"","settings":{"workspaceBgColor":"#EDFFDF","showGridHminor":false,"showGridHmajor":false,"showGridVminor":false,"showGridVmajor":false,"useCenterBasedPositions":false},"nodes":[{"id":"MonoSynth_In1","type":"TabInput","name":"Modulation","comment":"","x":440,"y":280,"z":"de8d0e25.32ebb","bgColor":"#CCE6FF","wires":[["MonoSynth_waveformMod1:0","MonoSynth_waveformMod1:1","MonoSynth_waveformMod2:0","MonoSynth_waveformMod2:1"]]},{"id":"MonoSynth_waveformMod1","type":"AudioSynthWaveformModulated","name":"wav1","comment":"","x":620,"y":255,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_mixer4_1:0"]]},{"id":"MonoSynth_waveformMod2","type":"AudioSynthWaveformModulated","name":"wav2","comment":"","x":620,"y":310,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_mixer4_1:1"]]},{"id":"MonoSynth_mixer4_1","type":"AudioMixer4","name":"mixer","comment":"","inputs":"4","x":770,"y":280,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_envelope1:0"]]},{"id":"MonoSynth_envelope1","type":"AudioEffectEnvelope","name":"env","comment":"","x":905,"y":295,"z":"de8d0e25.32ebb","bgColor":"#E6E0F8","wires":[["MonoSynth_Out1:0"]]},{"id":"MonoSynth_Out1","type":"TabOutput","name":"Out","comment":"","x":1015,"y":295,"z":"de8d0e25.32ebb","bgColor":"#cce6ff","wires":[]}]}],"nodeAddons":{"h4yn0nnyNodes":{"isAddon":true,"label":"h4yn0nnyNodes","description":"","credits":"h4yn0nnym0u5e","homepage":"","url":"https://api.github.com/repos/h4yn0nnym0u5e","types":{"AudioEffectExpEnvelope":{"defaults":{"name":{"type":"c_cpp_name"},"comment":{},"color":{"value":"#c6c0d8"}},"shortName":"expEnv","inputs":1,"outputs":1,"inputTypes":{"0":"i16"},"outputTypes":{"0":"i16"},"category":"effect","help":"","color":"#c6c0d8","icon":"arrow-in.png"},"AudioMixerStereo":{"defaults":{"name":{"type":"c_cpp_name"},"id":{},"inputs":{"value":1,"maxval":255,"minval":1,"type":"int"},"comment":{},"color":{"value":"#E6E0F8"}},"dynInputs":"","shortName":"mixerS","inputs":1,"outputs":2,"category":"mixer","color":"#E6E0F8","icon":"arrow-in.png"}}}}
}
It's not exporting to the Teensy, and I think part of the problem is that you're trying to make connections to the group objects themselves, which isn't possible (they're not actually audio objects, just OSCAudio "placeholders"). For example, in OSC / Export Simple I can see:
Code:
{"address":"/teensy*/dynamic/createConnection","args":[{"type":"s","value":"LFO_0_MonoSynth_0"}]}
{"address":"/teensy*/audio/LFO_0_MonoSynth_0/co","args":[{"type":"s","value":"LFO"},{"type":"i","value":0},[COLOR="#FF0000"]{"type":"s","value":"MonoSynth"}[/COLOR],{"type":"i","value":"0"}]}
{"address":"/teensy*/dynamic/createConnection","args":[{"type":"s","value":"MonoSynth_0_mixerL_0"}]}
{"address":"/teensy*/audio/MonoSynth_0_mixerL_0/co","args":[[COLOR="#FF0000"]{"type":"s","value":"MonoSynth"}[/COLOR],{"type":"i","value":0},{"type":"s","value":"mixerL"},{"type":"i","value":0}]}
LFO.0 should have 24 connections, two to each of the two AudioSynthWaveformModulated objects in the 6 instances of MonoSynth. Similarly the 6 AudioEffectEnvelope objects' outputs need two connections each, to mixerL.N and mixerR.N.

My previous JSON with two levels of grouping seems to give another type of problem, where the / group separators get lost, or doubled up, or something. Hard to tell, and not critical right now.
 
How is the input structure of the stereo mixer
Is it
L0 R0 L1 R1
or
L0 L1 R0 R1

No the OSC export of groups is not complete yet
But you can do arrays of standard audio objects which utilizes the group object
 
How is the input structure of the stereo mixer
Is it
L0 R0 L1 R1
or
L0 L1 R0 R1
Currently it's M0 M1 ... Mn, with the mono inputs being panned across the two outputs using pan(channel, position): position is -1.0 (fully left) through 0.0 (centred) to +1.0 (fully right). At centre each output is 71% amplitude or 50% power, which should behave better than a simple linear law. However, you've pointed out another way of using it, which is to use the inputs in pairs and add a balance() control: in that case I think L0 R0 L1 R1 etc would be best, the GUI wiring will be neater. I'll have to look up the maths usually used for a balance control... I don't think it needs a new object, though.
No the OSC export of groups is not complete yet. But you can do arrays of standard audio objects which utilizes the group object
Understood - I'll have a go at the latter soon.
 
Your variant would be the easiest to implement in the GUI
What is the type (class name) of your mixer.
Also if "my way of doing it" would to be implemented then that should also need to support array sources (In the stereo variant would mean only two inputs L & R is required to be connected to the array source output objects, the tool would then make a Stereo mixer supporting the array size.)
Then I believe there also need to be static variants of the same mixers but that would be taken care of by the tool. Have you seen my post about the tool now generating static mixer objects based on how many inputs you need?

So that concludes to three mixer variants:
1. Mono mixer
2. Panned stereo output mixer
3. Fully Stereo mixer with balance
 
I've called it AudioMixerStereo.

Not sure if you'll be able to import this, as I made a couple of custom nodes to allow me to put something like an AudioMixerStereo into the design:
Code:
{"version":1,"settings":{"arduino":{"Board":{"Platform":"","Board":"","Options":""}},"BiDirDataWebSocketBridge":{},"workspaces":{},"sidebar":{},"palette":{},"editor":{},"devTest":{},"IndexedDBfiles":{"testFileNames":"testFile.txt"},"NodeDefGenerator":{},"NodeDefManager":{},"NodeHelpManager":{},"OSC":{}},"workspaces":[{"type":"tab","id":"Main","label":"Main","inputs":0,"outputs":0,"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","generateCppDestructor":false,"extraClassDeclarations":"","settings":{},"nodes":[{"id":"Main_StereoVoice1","type":"StereoVoice","name":"stereovoice[3]","x":510,"y":270,"z":"Main","bgColor":"#CCFFCC","wires":[["Main_mixerS1:0"],["Main_mixerS1:1"]]},{"id":"Main_MonoVoice1","type":"MonoVoice","name":"monovoice[4]","x":510,"y":350,"z":"Main","bgColor":"#CCFFCC","wires":[["Main_mixer1:0"]]},{"id":"Main_waveform1","type":"AudioSynthWaveform","name":"waveform","comment":"","x":525,"y":405,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_mixer1:4"]]},{"id":"Main_mixerS1","type":"AudioMixerStereo","name":"mixerS","inputs":2,"comment":"","x":689,"y":270,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_mixerS3:0"],["Main_mixerS3:1"]]},{"id":"Main_mixer1","type":"AudioMixer","name":"mixer","inputs":5,"comment":"","x":696,"y":380,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_mixerS3:2"]]},{"id":"Main_mixerS3","type":"AudioMixerStereo2","name":"mixerS2","inputs":3,"comment":"","x":870,"y":330,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_i2s1:0"],["Main_i2s1:1"]]},{"id":"Main_i2s1","type":"AudioOutputI2S","name":"i2s","comment":"","x":995,"y":330,"z":"Main","bgColor":"#E6E0F8","wires":[]}]},{"type":"tab","id":"20724301.77542c","label":"MonoVoice","inputs":0,"outputs":0,"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","generateCppDestructor":false,"extraClassDeclarations":"","settings":{},"nodes":[{"id":"MonoVoice_Out1","type":"TabOutput","name":"Out","comment":"","x":359,"y":102,"z":"20724301.77542c","bgColor":"#cce6ff","wires":[]}]},{"type":"tab","id":"39f2a0d0.854c4","label":"StereoVoice","inputs":0,"outputs":0,"export":true,"isMain":false,"mainNameType":"tabName","mainNameExt":".ino","generateCppDestructor":false,"extraClassDeclarations":"","settings":{},"nodes":[{"id":"StereoVoice_Out1","type":"TabOutput","name":"outL","comment":"","x":258,"y":149,"z":"39f2a0d0.854c4","bgColor":"#CCE6FF","wires":[]},{"id":"StereoVoice_Out2","type":"TabOutput","name":"outR","comment":"","x":260,"y":200,"z":"39f2a0d0.854c4","bgColor":"#CCE6FF","wires":[]}]}],"nodeAddons":{"mine":{"isAddon":true,"label":"mine","description":"","credits":"","homepage":"","url":"https://api.github.com/repos/[user]/[repository]/contents/[subpath if any]","types":{"AudioMixerStereo":{"defaults":{"name":{"value":"Main_mixerS1"},"id":{},"inputs":{"value":2,"maxval":255,"minval":1,"type":"int"},"comment":{},"color":{"value":"#E6E0F8"}},"dynInputs":"","shortName":"mixerS","inputs":2,"outputs":2,"category":"mixer","color":"#E6E0F8","icon":"arrow-in.png"},"AudioMixerStereo2":{"defaults":{"name":{"value":"Main_mixerS3"},"id":{},"inputs":{"value":3,"maxval":255,"minval":1,"type":"int"},"comment":{},"color":{"value":"#E6E0F8"}},"dynInputs":"","shortName":"mixerS","inputs":2,"outputs":2,"category":"mixer","color":"#E6E0F8","icon":"arrow-in.png"}}}}
}
Here's an image, just in case:
2022-01-06 13_00_13-Audio System Design Tool++ for Teensy Audio Library.png
  • mixerS is an AudioMixerStereo automatically expanded to 6 inputs because it's connected to an array[3] source
  • mixer is an AudioMixer manually expanded to 5 inputs: the first 4 come from monovoice[4], the last one from waveform (I think from reading elsewhere this is valid, but it's probably not really needed)
  • mixerS2 is an AudioMixerStereo manually expanded to 3 inputs: the first two are a stereo pair, so you would use gain() and balance() to control them: the last one is mono in / stereo out, so you would use gain() and pan()
In my head, the to-be-implemented balance(channel) would actually affect channel and channel+1, and results in gain(channel) doing the same. This means we only need two mixer variants to implement your variants 2 and 3, but might be confusing for users? We'd need another name with 3 variants: perhaps AudioMixer, AudioMixerMtoS and AudioMixerStoS (AudioMixerStoM is just a 2-input AudioMixer...).
 
The custom nodes are saved in the JSON that means anyone can import it (you will need to use my tool++)
 
The custom nodes are saved in the JSON that means anyone can import it (you will need to use my tool++)
Good, that means you should be able to import my JSON! One thing I couldn't find was a way (if there is one) to set the number of inputs shown for a specific instance of a variable-input-width object, as you've done for AudioMixer. I copied the source JSON into my custom node, but the magic didn't happen:
Code:
{
    "defaults": {
        "name": {
            "value": "Main_mixerS1"
        },
        "id": {},
        [COLOR="#0000FF"]"inputs": {
            "value": 4,
            "maxval": 255,
            "minval": 1,
            "type": "int"
        }[/COLOR],
        "comment": {},
        "color": {
            "value": "#E6E0F8"
        }
    },
    [COLOR="#0000FF"]"dynInputs": "",[/COLOR]
    "shortName": "mixerS",
    "inputs": 2,
    "outputs": 2,
    "category": "mixer",
    "color": "#E6E0F8",
    "icon": "arrow-in.png"
}
I would have guessed one or other of the blue sections would signal the tool to give me the option, but I only get the usual dialogue when I double-click the object:
2022-01-06 16_34_13-Audio System Design Tool++ for Teensy Audio Library.png
 
This means we only need two mixer variants to implement your variants 2 and 3, but might be confusing for users? We'd need another name with 3 variants: perhaps AudioMixer, AudioMixerMtoS and AudioMixerStoS (AudioMixerStoM is just a 2-input AudioMixer...).
I've just re-read this, and it's already confusing me ... and I wrote it! I think we could do MtoM, MtoS and StoS (manicksan's three variants) with just two classes, by making AudioMixerStereo do both MtoS and StoS. But that might be confusing. So we could do the 3 variants with 3 classes, if people prefer, and come up with a name for each one.

That's closer to what I way trying to convey...
 
AudioMixerSIDO (Single Input Dual Output)
AudioMixerDIDO (Dual Input Dual Output)
or
AudioMixerMISO (Mono In Stereo Out)
AudioMixerSISO (Stereo In Stereo Out)

I have a Global Edit Dialog that is used for non special types.
Think I should also have a option that defines the edit dialog structure it in the node def. or when I think about this further there should not be needing any such structure at all, because it's kind of already defined in the def. Structure.

The dynInputs "parameter" is just a way to define that a object have a dynamic input count, so that I don't have to hardcode all types that is having that option.

I also have a similar parameter to define non audio objects and UI objects.

I got this crazy idea to make it possible to embed a export script in the node def. That script would then make it possible to do special things like #189 & #190 without modding the tool further.
 
After further thinking
We only need two mixers as you say
Single output mixer
Dual (stereo) Output mixer

The dual variant need two extra functions:
pan(ch, amount)
balance(chA, chB, amount)
The pan function adjust how much of a specific input that goes to either output.
The balance function adjust how much the gain differ from two inputs that goes to the two outputs (yes this is hard to describe but it should work as a stereo signal balance adjustment)
Basically the (stereo) mixer only need two (one for each output) arrays of multipliers(gain adjusters)
That is then manipulated by pan and balance to get the desired output.

Then we also need some fade functionality built into the mixer to avoid pops/clicks it will need additional two arrays of multipliers so that both the current and desired value can be stored, the fade time could be a global value, and if set to zero disables the fade functionality.
 
Last edited:
After further thinking
We only need two mixers as you say
Single output mixer
Dual (stereo) Output mixer

The dual variant need two extra functions:
pan(ch, amount)
balance(chA, chB, amount)
The pan function adjust how much of a specific input that goes to either output.
The balance function adjust how much the gain differ from two inputs that goes to the two outputs (yes this is hard to describe but it should work as a stereo signal balance adjustment)
Basically the (stereo) mixer only need two (one for each output) arrays of multipliers(gain adjusters)
That is then manipulated by pan and balance to get the desired output.
Sounds good, means we can stick with AudioMixer and AudioMixerStereo. I've started updating my AudioMixerStereo (as yet not tested), with the following scheme (very similar to yours):
  • pan(ch, position)
  • balance(chL, chR, amount)
  • balance(chL, amount)
position/amount is as before, -1.0 to +1.0. The 3-parameter balance() assigns and "pairs" the two channels so the mixer remembers the pairing and which one is the left channel: subsequent calls just use the chL value and retrieve the chR one from the pairing info; but if sent the chR channel, it swaps the channels and sets amount = -amount, so in theory everything works correctly. The 2-parameter balance() assigns chR = chL+1. And if you pan() a channel from a stereo pair then they're unpaired back to mono-to-stereo function. An attempt to pair is ignored if either channel is already paired.
Then we also need some fade functionality built into the mixer to avoid pops/clicks it will need additional two arrays of multipliers so that both the current and desired value can be stored, the fade time could be a global value, and if set to zero disables the fade functionality.
I know what you mean, not 100% convinced we ought to roll this into the mixer. Had a brief discussion some time back on this* where the clicks were a problem when following an analogue input (? could have been MIDI...). A simple fade gives "corners" at the change, rather than "steps", so there's still some break-through, and of course it makes the mixer more complex still! It may be worth considering different fade modes depending on the expected source of the gain/pan/balance values, e.g. if they're manual knobs or programmatically generated. Definitely worth putting on the "to think about" list...

(*edit: this thread)
 
Last edited:
One thing that would be better than
balance(chL, chR, amount)
Is to have a stereo pairing function
SetPair(chL,chR,pairNr)
Then you just change the balance by
bal(pairNr, amount)
It would in a user perspective be much easier to grasp, also then the pair functionality can then also be used like this
gainP(pairNr, amount)
 
Probably the pairing would not be changed so much, also I can include it in the OSC export.
if we want to do the mixer complete there could also be a group functionality similar to cs3318
https://statics.cirrus.com/pubs/proDatasheet/CS3318_F1.pdf
Side 17 5.4.1

I will also try to do a simple fade variant mixer, could surely be useful at the OSC lib as then the OSC messages don't need every step to be sent, and then the mixer will behave closer to a real one.
 
One thing that would be better than
balance(chL, chR, amount)
Is to have a stereo pairing function
SetPair(chL,chR,pairNr)
Then you just change the balance by
bal(pairNr, amount)
It would in a user perspective be much easier to grasp, also then the pair functionality can then also be used like this
gainP(pairNr, amount)
Not at all sure that's better / easier to grasp, and certainly harder to implement. It means two extra function names (SetPair and gainP) and an extra array to store the pairNr->chL+chR mapping. The array doesn't take any extra memory, granted. pairNr only has to go up to N/2 (where N is the number of channels), but then the user has to remember that stereo pair 6+7 is mapped to pairNr 3, rather than just remembering that the mono channels are 0-3 and the stereo ones are 4, 6, 8 and 10. This fits well with hardware mixers, apart from numbering starting from 0 in software and 1 in hardware. gainP is only needed because of the additional pairNr - as I currently have it, gain(ch,level) works equally well for paired or unpaired channels. Admittedly gain(level) was broken until just now ... need to test it all!
 
Probably the pairing would not be changed so much, also I can include it in the OSC export.
if we want to do the mixer complete there could also be a group functionality similar to cs3318
https://statics.cirrus.com/pubs/proDatasheet/CS3318_F1.pdf
Side 17 5.4.1

I will also try to do a simple fade variant mixer, could surely be useful at the OSC lib as then the OSC messages don't need every step to be sent, and then the mixer will behave closer to a real one.
I think the grouping is possibly best left to the application writer ... though maybe could do it with gain(level,bitmap) for up to 32 or 64 channels, similarly pan() and balance()? Not quite the same as the CS3318, which sums the channel and master gains ... the more I consider it, the more I think it'll just add overhead to the update loop which most users won't use anyway. I could be wrong, though!

The fade mixer mentioned in the "Mixer and Amp gain really noisy" thread isn't a bad place to start, though it appears to do a float multiply and divide for every pair of samples, even if it's at the target level, which probably isn't great on a Teensy 3.x. It could easily be re-factored to integer maths. It implements an exponential fade, so technically never reaches its target. As shown with a smoothness of 1000 it reaches 90% of target after about 26ms @ 44.1kHz. Definitely needs some "that's close enough, set to target and stop fading" termination logic. I'm toying with a sinusoidal fade algorithm, which is one extra multiply / add step per sample [pair] but smooths gain slope vs time as well as gain value. It's a bit harder to do the termination logic for that...
 
Back
Top