Discontinuous Multichannel Out

Options would be

  • a PseudoUGen - this would need to deal with any annoying order issues (don’t write to anything before reading), and could have an option to silence channels
  • Via Server - downside is we end up with possible complications for node order. The after default group approach is a bit crude with this, as it’s hard to determine what the correct order is for arbitrary sequences of nodes doing analysis or post-processing.
  • As an option to scsynth / supernova - This is probably the best option IMV, though maybe the most work, as it’s simple and clear. Reordering happens at the last moment, before writing to the hardware buffers.

Maybe something like a dictionary or an association could be helping here which remaps the channel at the bottom of the graph - something like the SafetyQuark?

~myMap = (0->0, 1->1, 2->3, 3->4);
 
// should this be static class instead of a object?
RemapOutChannels.applyMap(~myMap);

Some additional options could be

// apply identity except on the provided values
RemapOutChannels.identity((2->3, 3->2));

or also add a way to silent all undefined values

// apply identity except on the provided keys
RemapOutChannels.applyMap((0->0, 2->2), silentUndefined: true);

which could also be made explicitly via an association which maps to nil

RemapOutChannels.applyMap((0->nil, 1->0));

The question is if mappings such as 0->0, 1->0, 2->0, 3->0 should be “meaned”? Maybe there is a way to define the folding operation here, such as sum or mean? As scsynth is operating in 32 bit floats, this could also be considered not a problem since the scaling can happen via volume.

TotalMix offers a mixing matrix - maybe RemapOutChannels (or whatever it is called) could work internally via such a routing matrix as well which could also be set manually for more granular control.

~mixMatrix = Array2D.fromArray(4, 4, 16.collect(0.0));
// apply identity
4.do({|i| ~mixMatrix[i, i] = 1.0});
// also add 0.5 of chanel 0 to chanel 1
~mixMatrix[0, 1] = 0.5;

// apply re-routing
RemapOutChannels.applyMatrix(~mixArray);

I think such a feature would be a great addition when working with multi-channel, either as a Quark or within the standard library. Maybe simply re-implement TotalMix without TotalMix?

It could be part of the Volume class, maybe?

I would suggest the following two interfaces:

  1. Combination of existing buses (the type of buses should be identical):

    ~outbus_a = Bus(\audio, 0, 4)
    ~outbus_b = Bus(\audio, 8, 4)
    ~outbus_c = Bus(\audio, 16, 4)
    
    Bus.concatenate([~outbus_a, ~outbus_b, ~outbus_c])
    
    Bus.combine([~outbus_a, ~outbus_b, ~outbus_c])
    
  2. Make a subsequent bus connection

    Bus.config(\audio, (0..3) ++ (8..11) ++ (16..19))
    
    Bus.makeSeq(\audio, (0..3) ++ (8..11) ++ (16..19))
    

A general solution would take some design.

A solution for a specific case is actually not that complicated.

~remap = {
    ~remapSynth = {
        var sig = In.ar(0, 16);
        var silent = Silent.ar(1);
        ReplaceOut.ar(0,
            [silent]  // 0
            ++ sig[0..8]  // 1-9
            ++ (silent!2)  // 10-11
            ++ sig[9..15]  // 12-18
        )
    }.play(target: s.defaultGroup, addAction: \addAfter)
};

ServerTree.add(~remap);

… then boot.

An interesting question for future documentation is, what is the mental block that makes it hard to visualize? That is, it’s not a code-complexity problem – it’s a problem of getting confused about the requirements while writing the code.

hjh

3 Likes

Yes, I think that’s right.

Maybe easier to update the remapping without modifying the SynthDef code. Also, generating the SD code according to num channels.

(

~remapConfig = IdentityDictionary[
    0  ->  1,
    1  ->  2,
    2  ->  3,
    3  ->  4,
    4  ->  5,
    5  ->  6,
    6  ->  7,
    7  ->  8,
    8  ->  9,
    9  -> 12,
    10 -> 13,
    11 -> 14,
    12 -> 15,
    13 -> 16,
    14 -> 17
];


~generateRemapSynthDef = { |numInputChannels, numOutputChannels|
    SynthDef(\remapOutputs, {
        var sig = In.ar(0, numInputChannels);  
        var remapped = Array.fill(numOutputChannels, { Silent.ar(1) });  

        ~remapConfig.keysValuesDo { |src, dest|
            if (src < numInputChannels and: { dest < numOutputChannels }) {
                remapped[dest] = sig[src];
            }
        };

        ReplaceOut.ar(0, remapped);
    }).add;
};

~generateRemapSynthDef.(16, 18);  
)

(
~createRemapSynth = {
    ~remapSynth = Synth(\remapOutputs, target: s.defaultGroup, addAction: \addAfter);
};

ServerTree.add( ~createRemapSynth);

)

1 Like

Yet another way would be to use MainFX, in JITLibExtensions quark, for remapping the outs:

// make a proxy filter function that remaps
ProxyChain.add(\remapOuts, \filter -> { |in|
	// first 8, 2 silent, next 8
	in.keep(8) ++ Silent.ar(2) ++ in.drop(8)
});
// make a MainFX for default server with that func
MainFX(s, 18, [\remapOuts]);
// activate the func, survives Cmd-Period
MainFX(s).add(\remapOuts, 1);
// show on gui
MainFX(s).gui(\MainFX_s, 4); 

If this topic were to be included in the help document, where should it be documented?

Probably Server Guide and/or Audio device selection, or a link from there?

2 Likes