Multiple Midi Devices for Buffer Synth Trigger and Parameter Control

Hey guys still kinda new to Supercollider but I am capable of coding a few things. I didn’t think this is a problem that would even appear and I wouldnt be able to solve honestly.

Launchpad pro runs multiple buffers and I’m trying to use the bcr 20000 control gain of each of these buffers. No matter what I do, even when I see the printed data of the adjusted gain, I can’t hear the difference in intensity.

  • X** is the option to adjust the gain before I run the synth buffers. Now for some reason in the Gain Chan frame, the message - nan ID keeps appearing to the most recently affected Chan.

** Y** works with the option of connecting bcr2000 to midi chan dictionary where midi cc is refered to midi chans of Launchpad to control gain but for some reason midi between controllers doesn’t print any data or has no perceivable effect in intensity. It is possible that BCR 2000 reads midi better via MIDI Func and the problem is the Midi Trace format of both controllers but it’s hard to say to for me.
Now why am I using Midi Trace in the first place ? Well…
Another strange thing that happens is that when I do MIDI In for both Launchpad Pro and Bcr2000, only BCR2000 shows data via Midi Def or Midi Func and this doesn’t work either:

(
MIDIIn.connect(0, 2); // Connect Launchpad Pro (source 0)
MIDIIn.connect(1, 3); // Connect BCR2000 (source 3)
)

/// data per device
(
MIDIFunc.cc({ |val, num, chan, src|
("BCR2000 | CC: " + num + " | Value: " + val).postln;
}, nil, nil, 3); 
MIDIFunc.cc({ |val, num, chan, src|
("Launchpad Pro | CC: " + num + " | Value: " + val).postln;
}, nil, nil, 0); 

The only thing that will fix it is this but then I suspect the midi is in the wrong format but maybe I am mistaken.

(
MIDIClient.init;
MIDIIn.connectAll;
);

MIDIFunc.trace(true); 

Sorry for not formatting this in the proper language. I wanted to upload a file but Im a new user. Can anyone enlighten so as to how to solve this problem. I am using Windows 11 HP PC and Supercollider 3.13 if that also helps.

X

(
MIDIClient.init;
MIDIIn.connectAll;
);

MIDIFunc.trace(true);

(
s.waitForBoot {
    // Load buffers
    ~w1 = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/Worker Bee Cell 1 w1.wav", channels: [0, 1]);
    ~w2 = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/worker 2 cell w2.wav", channels: [0, 1]);
    ~f3 = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/Forager Bee Cell 1 f3.wav", channels: [0, 1]);
    ~f4 = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/cell 2 forager f4.wav", channels: [0, 1]);
    ~q5 = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/queen 5 cell q5.wav", channels: [0, 1]);
    ~d6 = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/drone 6 cell d6.wav", channels: [0, 1]);
    ~one = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/one bee stereo(double mono).wav", channels: [0, 1]);
    ~bzz = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/zen bees - buzz drone .wav", channels: [0, 1]);
    ~swarm = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/bee swarm.wav", channels: [0, 1]);
    ~toot = Buffer.readChannel(s, "C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/queen bee tooting then quacking.wav", channels: [0, 1]);

    // Create groups
    ~worker1 = Group.new; ~worker2 = Group.new; ~forager3 = Group.new;
    ~forager4 = Group.new; ~queen5 = Group.new; ~drone6 = Group.new;
    ~bee = Group.new; ~drone = Group.new; ~hive = Group.new; ~quack = Group.new;

    // Create control buses for gain
    ~gainBus = (0..7).collect { Bus.control(s, 1) } ++ (0..1).collect { Bus.control(s, 1) };

    // Define SynthDef
    SynthDef.new(\bee, {
        arg amp=1, out=0, buf, rate=1, start=0, loop=1, gate=1, da=0, cutoff=1000, gainBus;
        var sig, env, gain;

        // Envelope
        env = EnvGen.kr(Env.adsr(0.1, 0.2, 0.8, 0.5), gate, doneAction: da);

        // Read gain from control bus
        gain = max(0, In.kr(gainBus, 1) * amp);

        // Sound source
        sig = PlayBuf.ar(2, buf, BufRateScale.kr(buf) * rate, startPos: start, loop: loop, doneAction: 0) * gain;

        // Apply envelope and low-pass filter
        sig = LPF.ar(sig * env, cutoff);

        // Output
        Out.ar(out, sig);
    }).add;

    // Debugging function to print gain values
    ~printGains = {
        (0..7).do { |i| ~gainBus[i].get({ |val| ("Gain CH " + i + ": " + val).postln }); };
        (0..1).do { |i| ~gainBus[8 + i].get({ |val| ("Gain CH " + (8 + i) + ": " + val).postln }); };
    };

    // MIDI Control for Gain (CC 71 for first 8 buffers, CC 74 for last 2 buffers)
    MIDIFunc.cc({ |val, num, chan, src|
        var mappedGain = val.linlin(0, 127, 0, 1);
        if (num == 71, { ~gainBus[chan].set(mappedGain); });
        ("Updated Gain CH " + chan + " to " + mappedGain).postln;
        ~printGains.();
    }, ccNum: 71);

    MIDIFunc.cc({ |val, num, chan, src|
        var mappedGain = val.linlin(0, 127, 0, 1);
        if (num == 74, { ~gainBus[8 + chan].set(mappedGain); });
        ("Updated Gain CH " + (8 + chan) + " to " + mappedGain).postln;
        ~printGains.();
    }, ccNum: 74);

    // Function to trigger buffer playback
    ~triggerBuffer = { |buffer, index|
        Synth(\bee, [\buf, buffer, \out, 0, \gainBus, ~gainBus[index].index, \gate, 1]);
    };
}

)

(
~releaseGroup = { |group|
    group.notNil.if({
        group.set(\gate, 0);  // Send release signal instead of hard freeAll
        ("Releasing synths in " + group).postln;
    }, {
        "Group is nil".postln;
    });
};
)

/// release all groups
(
~releaseAllGroups = {
    [~worker1, ~worker2, ~forager3, ~forager4, ~queen5, ~drone6].do { |group|
        if (group.notNil) {
            group.set(\gate, 0);  // Smooth release
        };
    };
    "All groups are releasing synths smoothly.".postln;
};
)



(
~chan = IdentityDictionary.new;
~noFilterBuffers = ~noFilterBuffers ? []; // Ensure ~noFilterBuffers is initialized

~channelToSynthConfig = IdentityDictionary[
    71 -> (buf: ~w1.bufnum, group: ~worker1, out: 0),
    72 -> (buf: ~w2.bufnum, group: ~worker2, out: 0),
    73 -> (buf: ~f3.bufnum, group: ~forager3, out: 0),
    74 -> (buf: ~f4.bufnum, group: ~forager4, out: 0),
    75 -> (buf: ~q5.bufnum, group: ~queen5, out: 0),
    76 -> (buf: ~d6.bufnum, group: ~drone6, out: 0),
    66 -> (buf: ~bzz.bufnum, group: ~drone, out: 0),
    67 -> (buf: ~one.bufnum, group: ~bee, out: 0),
    68 -> (buf: ~swarm.bufnum, group: ~hive, out: 0),
    69 -> (buf: ~toot.bufnum, group: ~quack, out: 0)
];

MIDIdef.noteOn(\synth, { |nn, chan, src|
    var config, synthArgs;
    [chan, src].postln;

    if (chan == 43) {
        ~midiEnabled = true;
        MIDIdef(\synthoff).enable;
        "MIDI Input Enabled!".postln;
    };

    if (chan == 42) {
        ~midiEnabled = false;
        MIDIdef(\synthoff).disable;
        "MIDI Input Disabled!".postln;
    };

    if (chan == 77) { ~spawnSynths.(); };
    if (chan == 78) { ~freeSynths.(); };
    if (chan == 56) {
        "Releasing all synths smoothly".postln;
        ~releaseAllGroups.();
    };

    // Free groups based on MIDI channel
    if (chan == 36) { "Releasing worker1".postln; ~worker1.set(\gate, 0); };
    if (chan == 37) { "Releasing worker2".postln; ~worker2.set(\gate, 0); };
    if (chan == 38) { "Releasing forager3".postln; ~forager3.set(\gate, 0); };
    if (chan == 39) { "Releasing forager4".postln; ~forager4.set(\gate, 0); };
    if (chan == 40) { "Releasing queen5".postln; ~queen5.set(\gate, 0); };
    if (chan == 41) { "Releasing drone6".postln; ~drone6.set(\gate, 0); };
    if (chan == 46) { "Releasing bzz".postln; ~drone.set(\gate, 0); };
    if (chan == 47) { "Releasing one".postln; ~bee.set(\gate, 0); };
    if (chan == 48) { "Releasing swarm".postln; ~hive.set(\gate, 0); };
    if (chan == 49) { "Releasing toot".postln; ~quack.set(\gate, 0); };

    // Check if the channel has a synth mapping
    if (~channelToSynthConfig[chan].notNil) {
        config = ~channelToSynthConfig[chan];

        ("Playing: buf=" + config[\buf] + ", group=" + config[\group] + ", out=" + config[\out]).postln;

        if (~chan.isNil) { ~chan = IdentityDictionary.new; };

        // Ensure ~noFilterBuffers is an array before checking
        if (~noFilterBuffers.isNil.not and: { (~noFilterBuffers.asArray).includes(config[\buf]).not }) {
            config.postln; // Debugging: Show config when condition is met
        };

        // Base synth arguments
        synthArgs = [
            \buf, config[\buf],
            \loop, 1,
            \gate, 1,
            \out, config[\out]
        ];

        // If the buffer is NOT in the noFilter list, add a cutoff parameter
        if (~noFilterBuffers.includes(config[\buf]).not) {
            synthArgs = synthArgs ++ [\cutoff, 1000];
        };

        ~chan[chan] = Synth(\bee, synthArgs, config[\group]);

        ("Triggered Synth on Channel " + chan + " with buffer " + config[\buf] + " on bus " + config[\out]).postln;
    } {
        ("No synth mapped to channel " + chan).postln;
    };
});
)



// Define a mapping of CC numbers to specific target MIDI channels

(
~ccToChannelMap = IdentityDictionary[
    21 -> 71,  // CC 20 controls synth on channel 71
    22 -> 72,  // CC 21 controls synth on channel 61
    23 -> 73,  // CC 22 controls synth on channel 58
    24 -> 74,  // CC 23 controls synth on channel 53
    25 -> 75,  // CC 24 controls synth on channel 78
    26 -> 76,  // CC 25 controls synth on channel 73

];

MIDIdef.cc(\filterControl, {
    arg val, num, chan, src;
    var mappedCutoff, targetChan;

    mappedCutoff = val.linexp(0, 127, 50, 15000);  // Map CC values to filter range

    // Check if CC number exists in the mapping
    if (~ccToChannelMap.includesKey(num)) {
        targetChan = ~ccToChannelMap[num];  // Get target MIDI channel

        // Ensure the target synth exists before setting the cutoff
        if (~chan.includesKey(targetChan) and: { ~chan[targetChan].isKindOf(Synth) }) {
            ~chan[targetChan].set(\cutoff, mappedCutoff);
            ("Updated cutoff for Channel " + targetChan + " to " + mappedCutoff).postln;
        } {
            ("No active synth found for MIDI Channel " + targetChan).postln;
        };
    } {
        ("MIDI CC " + num + " is not mapped to any synth").postln;
    };
}, (21..28));  // Only respond to CC numbers 20–28
)


// Untrigger buffer synths
MIDIdef.noteOff(\synthoff, {
    arg nn, chan, src;
    [nn, chan, src].postln;

    if (~chan[chan].notNil) {
        ~chan[chan].set(\gate, 0);
        ~chan[chan] = nil;
    };
});

(
~spawnSynths = {
    ~cell1 = Group.new;
    ~cell2 = Group.new;
    ~cell3 = Group.new;
    ~cell4 = Group.new;
    ~cell5 = Group.new;
    ~cell6 = Group.new;

    ~synths = (
        worker1: Synth.new(\bee, [\buf, ~w1.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell1),
        worker2: Synth.new(\bee, [\buf, ~w2.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell2),
        forager3: Synth.new(\bee, [\buf, ~f3.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell3),
        forager4: Synth.new(\bee, [\buf, ~f4.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell4),
        queen5: Synth.new(\bee, [\buf, ~q5.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell5),
        drone6: Synth.new(\bee, [\buf, ~d6.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell6)
    );

    "Synths Spawned!".postln;
};

// Function to smoothly release synths
~freeSynths = {
    ~synths.do { |synth|
        synth.set(\gate, 0);  // Smooth release via envelope
    };

    "Synths Released with Envelope!".postln;
};
)

Y

(
MIDIClient.init;
MIDIIn.connectAll;
);

(
{
~w1 = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/Worker Bee Cell 1 w1.wav", channels: [0, 1]);
// Define the downmixing SynthDef
// Boot the server and load the buffer

//~w1.play
//~w1.numFrames; ~w1.query, ~w1.dump, ~w1.numChannels.postln;, ~w1.duration;, (~w1.bufnum.notNil).postln;

~w2 = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/worker 2 cell w2.wav", channels: [0, 1]);
//~w2.play

~f3 = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/Forager Bee Cell 1 f3.wav", channels: [0, 1]);
//~f3.play
//~f3.bufnum.postln;, ~f3.query, ~f3.dump, ~f3.numChannels.postln;, ~f3.duration;

~f4 = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/cell 2 forager f4.wav", channels: [0, 1]);
//~f4.play


~q5 = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/queen 5 cell q5.wav", channels: [0, 1]);
//~q5.play

~d6 = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/drone 6 cell d6.wav", channels: [0, 1]);
//~d6.play

~one = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/one bee stereo(double mono).wav", channels: [0, 1]);
//~one.play

~bzz = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/zen bees - buzz drone .wav", channels: [0, 1]);
//~bzz.play

~swarm = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/bee swarm.wav", channels: [0, 1]);
//~swarm.play

~toot = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/INTERACTIVE HONEYCOMB/Bee Cells For Sampler/queen bee tooting then quacking.wav", channels: [0, 1]);
//~toot.play

~flute = Buffer.readChannel(s,"C:/Users/janda/Downloads/To BEE or not to BE/flute sample.wav", channels: [0, 1]);
//~flute.play
//~flute.bufnum.postln;

}.value;
)

(
~worker1 = Group.new;
~worker2 = Group.new;
~forager3 = Group.new;
~forager4 = Group.new;
~queen5 = Group.new;
~drone6 = Group.new;
~bee = Group.new;
~drone = Group.new;
~hive = Group.new;
~quack = Group.new;
);


s.plotTree

//// release individual groups
(
~releaseGroup = { |group|
    group.notNil.if({
        group.set(\gate, 0);  // Send release signal instead of hard freeAll
        ("Releasing synths in " + group).postln;
    }, {
        "Group is nil".postln;
    });
};
)

/// release all groups
(
~releaseAllGroups = {
    [~worker1, ~worker2, ~forager3, ~forager4, ~queen5, ~drone6].do { |group|
        if (group.notNil) {
            group.set(\gate, 0);  // Smooth release
        };
    };
    "All groups are releasing synths smoothly.".postln;
};
)





// Define SynthDef for looping flute with gain control
SynthDef.new(\flute, {
    arg amp=1, out=0, buf, rate=1, loop=1, gate=1;
    var sig, env, gain;

    // PlayBuf in stereo, always looping
    sig = PlayBuf.ar(2, buf, BufRateScale.kr(buf) * rate, loop: 1, doneAction: 0);

    // Envelope for smooth start/stop
    env = EnvGen.kr(Env.asr(0.1, 1, 0.5), gate, doneAction: 2);

    // Gain control (default 0 dB)
    gain = amp.dbamp;

    // Apply gain and envelope
    sig = sig * gain * env;

    // Output to four channels (quadriphonic)
    Out.ar(out, sig.dup(2));
}).add;

// Dictionary to track the flute synth
~fluteSynths = IdentityDictionary.new;

// MIDI Note On handling
MIDIdef.noteOn(\flute_control, { |nn, chan, src|
    if (chan == 70) {  // Channel 70 starts the flute
        if (~fluteSynths[chan].isNil) {
            ~fluteSynths[chan] = Synth(\flute, [
                \buf, ~flute.bufnum,
                \out, 0,
                \amp, 0.dbamp,  // Default to 0 dB
                \gate, 1
            ]);
            "Flute Synth Started".postln;
        };
    };

    if (chan == 50) {  // Channel 71 stops the flute
        if (~fluteSynths[70].notNil) {
            ~fluteSynths[70].set(\gate, 0);
            ~fluteSynths.removeAt(70);
            "Flute Synth Released".postln;
        };
    };
});

// MIDI CC Mapping for Gain Control
MIDIdef.cc(\flute_gain, { |val, num, chan, src|
    if (num == 27) {  // Assign CC 74 for gain control
        var mappedGain = val.linlin(0, 127, -24, 24);  // Convert CC range to dB
        if (~fluteSynths[70].notNil) {
            ~fluteSynths[70].set(\amp, mappedGain.dbamp);
            ("Updated Flute Gain to " + mappedGain + " dB").postln;
        };
    };
}, 27);

)
(
SynthDef.new(\bee, {
    arg amp=1, gain=1, out=0, buf, rate=1, start=0, loop=1, gate=1, da=0, cutoff=1000;
    var sig, env;

	 // Envelope
    env = EnvGen.kr(Env.adsr(0.1, 0.2, 0.8, 0.5), gate, doneAction: da);
    // Sound source
    sig = PlayBuf.ar(2, buf, BufRateScale.kr(buf) * rate, startPos: start, loop: loop)* gain;

    // Apply envelope
    sig = sig * env; // Multiply by gain

    // Apply low-pass filter
    sig = LPF.ar(sig, cutoff);

    // Output sound
    Out.ar(out, sig);
}).add;
)

a = Synth.new(\bee, [\buf, ~w1.bufnum, \gain, 1, \loop, 1, \gate, 1, \out, 0,  \cutoff, 1000], ~worker1);
a.set(\gain, 0.1);
a.set(\gain, 2);
a.set(\gain, 0);

b = Synth.new(\bee, [\buf, ~w2.bufnum, \gain, 1, \loop, 1, \gate, 1, \out, 0,  \cutoff, 1000], ~worker2);
b.set(\gate, 0);
c = Synth.new(\bee, [\buf, ~f3.bufnum, \gain, 1, \loop, 1, \gate, 1, \out, 0,  \cutoff, 1000], ~forager3);
c.set(\gate, 0);
d = Synth.new(\bee, [\buf, ~f4.bufnum, \gain, 1, \loop, 1, \gate, 1, \out, 0,  \cutoff, 1000], ~forager4);
d.set(\gate, 0);
e = Synth.new(\bee, [\buf, ~q5.bufnum, \gain, 1, \loop, 1, \gate, 1, \out, 0,  \cutoff, 1000], ~queen5);
e.set(\gate, 0);
f = Synth.new(\bee, [\buf, ~d6.bufnum,\gain, 1, \loop, 1, \gate, 1, \out, 0,  \cutoff, 1000], ~drone6);
f.set(\gate, 0);
g = Synth.new(\bee, [\buf, ~one.bufnum, \gain, 1, \loop, 1, \gate, 1, \out, 0], ~bee);
g.set(\gate, 0);
h = Synth.new(\bee, [\buf, ~bzz.bufnum, \gain, 1, \loop, 1, \gate, 1, \out, 0], ~drone);
h.set(\gate, 0);
i = Synth.new(\bee, [\buf, ~swarm.bufnum, \gain, 1, \loop, 1, \gate, 1, \out, 0], ~hive);
i.set(\gate, 0);
j = Synth.new(\bee, [\buf, ~toot.bufnum, \gain, 1, \loop, 1, \gate, 1, \out, 0], ~quack);
j.set(\gate, 0);
k = Synth.new(\flute, [\buf, ~flute.bufnum, \loop, 1, \gate, 1, \out, 0]);
k.set(\gate, 0);



(
~chan = IdentityDictionary.new;
~noFilterBuffers = ~noFilterBuffers ? []; // Ensure ~noFilterBuffers is initialized

~channelToSynthConfig = IdentityDictionary[
    71 -> (buf: ~w1.bufnum, group: ~worker1, out: 0),
    72 -> (buf: ~w2.bufnum, group: ~worker2, out: 0),
    73 -> (buf: ~f3.bufnum, group: ~forager3, out: 0),
    74 -> (buf: ~f4.bufnum, group: ~forager4, out: 0),
    75 -> (buf: ~q5.bufnum, group: ~queen5, out: 0),
    76 -> (buf: ~d6.bufnum, group: ~drone6, out: 0),
    66 -> (buf: ~bzz.bufnum, group: ~drone, out: 0),
    67 -> (buf: ~one.bufnum, group: ~bee, out: 0),
    68 -> (buf: ~swarm.bufnum, group: ~hive, out: 0),
    69 -> (buf: ~toot.bufnum, group: ~quack, out: 0)
];

MIDIdef.noteOn(\synth, { |nn, chan, src|
    var config, synthArgs;
    [chan, src].postln;

    if (chan == 43) {
        ~midiEnabled = true;
        MIDIdef(\synthoff).enable;
        "MIDI Input Enabled!".postln;
    };

    if (chan == 42) {
        ~midiEnabled = false;
        MIDIdef(\synthoff).disable;
        "MIDI Input Disabled!".postln;
    };

    if (chan == 77) { ~spawnSynths.(); };
    if (chan == 78) { ~freeSynths.(); };
    if (chan == 56) {
        "Releasing all synths smoothly".postln;
        ~releaseAllGroups.();
    };

    // Free groups based on MIDI channel
    if (chan == 36) { "Releasing worker1".postln; ~worker1.set(\gate, 0); };
    if (chan == 37) { "Releasing worker2".postln; ~worker2.set(\gate, 0); };
    if (chan == 38) { "Releasing forager3".postln; ~forager3.set(\gate, 0); };
    if (chan == 39) { "Releasing forager4".postln; ~forager4.set(\gate, 0); };
    if (chan == 40) { "Releasing queen5".postln; ~queen5.set(\gate, 0); };
    if (chan == 41) { "Releasing drone6".postln; ~drone6.set(\gate, 0); };
    if (chan == 46) { "Releasing bzz".postln; ~drone.set(\gate, 0); };
    if (chan == 47) { "Releasing one".postln; ~bee.set(\gate, 0); };
    if (chan == 48) { "Releasing swarm".postln; ~hive.set(\gate, 0); };
    if (chan == 49) { "Releasing toot".postln; ~quack.set(\gate, 0); };

    // Check if the channel has a synth mapping
    if (~channelToSynthConfig[chan].notNil) {
        config = ~channelToSynthConfig[chan];

        ("Playing: buf=" + config[\buf] + ", group=" + config[\group] + ", out=" + config[\out]).postln;

        if (~chan.isNil) { ~chan = IdentityDictionary.new; };

        // Ensure ~noFilterBuffers is an array before checking
        if (~noFilterBuffers.isNil.not and: { (~noFilterBuffers.asArray).includes(config[\buf]).not }) {
            config.postln; // Debugging: Show config when condition is met
        };

        // Base synth arguments
        synthArgs = [
            \buf, config[\buf],
            \loop, 1,
            \gate, 1,
            \out, config[\out]
        ];

        // If the buffer is NOT in the noFilter list, add a cutoff parameter
        if (~noFilterBuffers.includes(config[\buf]).not) {
            synthArgs = synthArgs ++ [\cutoff, 1000];
        };

        ~chan[chan] = Synth(\bee, synthArgs, config[\group]);

        ("Triggered Synth on Channel " + chan + " with buffer " + config[\buf] + " on bus " + config[\out]).postln;
    } {
        ("No synth mapped to channel " + chan).postln;
    };
});
)



// Define a mapping of CC numbers to specific target MIDI channels

(
~ccToChannelMap = IdentityDictionary[
    21 -> 71,  // CC 20 controls synth on channel 71
    22 -> 72,  // CC 21 controls synth on channel 61
    23 -> 73,  // CC 22 controls synth on channel 58
    24 -> 74,  // CC 23 controls synth on channel 53
    25 -> 75,  // CC 24 controls synth on channel 78
    26 -> 76,  // CC 25 controls synth on channel 73

];

MIDIdef.cc(\filterControl, {
    arg val, num, chan, src;
    var mappedCutoff, targetChan;

    mappedCutoff = val.linexp(0, 127, 50, 15000);  // Map CC values to filter range

    // Check if CC number exists in the mapping
    if (~ccToChannelMap.includesKey(num)) {
        targetChan = ~ccToChannelMap[num];  // Get target MIDI channel

        // Ensure the target synth exists before setting the cutoff
        if (~chan.includesKey(targetChan) and: { ~chan[targetChan].isKindOf(Synth) }) {
            ~chan[targetChan].set(\cutoff, mappedCutoff);
            ("Updated cutoff for Channel " + targetChan + " to " + mappedCutoff).postln;
        } {
            ("No active synth found for MIDI Channel " + targetChan).postln;
        };
    } {
        ("MIDI CC " + num + " is not mapped to any synth").postln;
    };
}, (21..28));  // Only respond to CC numbers 20–28
)


// Untrigger buffer synths
MIDIdef.noteOff(\synthoff, {
    arg nn, chan, src;
    [nn, chan, src].postln;

    if (~chan[chan].notNil) {
        ~chan[chan].set(\gate, 0);
        ~chan[chan] = nil;
    };
});


//// TRIGGER ALL SYNTHS
// Function to create synths
(
~spawnSynths = {
    ~cell1 = Group.new;
    ~cell2 = Group.new;
    ~cell3 = Group.new;
    ~cell4 = Group.new;
    ~cell5 = Group.new;
    ~cell6 = Group.new;

    ~synths = (
        worker1: Synth.new(\bee, [\buf, ~w1.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell1),
        worker2: Synth.new(\bee, [\buf, ~w2.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell2),
        forager3: Synth.new(\bee, [\buf, ~f3.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell3),
        forager4: Synth.new(\bee, [\buf, ~f4.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell4),
        queen5: Synth.new(\bee, [\buf, ~q5.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell5),
        drone6: Synth.new(\bee, [\buf, ~d6.bufnum, \loop, 1, \gate, 1, \out, 0], ~cell6)
    );

    "Synths Spawned!".postln;
};

// Function to smoothly release synths
~freeSynths = {
    ~synths.do { |synth|
        synth.set(\gate, 0);  // Smooth release via envelope
    };

    "Synths Released with Envelope!".postln;
};
)



////MIDI
(
MIDIClient.init;
MIDIIn.connectAll;
);

MIDIClient.sources;



MIDIdef(\synth).enable; /// Enable noteon
MIDIdef(\synth).disable;/// disable noteon
MIDIdef(\synth).free;/// delete Midi Def
MIDIdef(\synth).freeAll;// delete all Midi Defs: by default ctrl. will also delete MIDI DEFs unless specified otherwise

MIDIdef(\synthoff).enable; /// Enable noteon
MIDIdef(\synthoff).disable;/// disable noteon
MIDIdef(\synthoff).free;/// delete Midi Def
MIDIdef(\synthoff).freeAll

MIDIdef.noteOn(\synth, {"buffer".postln}).permanent_(true);
MIDIdef.noteOn(\filterControl, {"buffer".postln}).permanent_(true);/// this will make sure ctrl. does not delete the Midi Def
MIDIdef.noteOn(\bcrGainControl, {"buffer".postln}).permanent_(true);




MIDIdef.cc(\bcrGainControl, { |val, num, chan, src|
    var lookupKey = [chan, num];
    var mappedGain = val.linlin(0, 127, -60, 6).dbamp;

    ("🎛 MIDI Received: Channel " + chan + " | CC=" + num + " | Value=" + val).postln;

    if (~bcrToSynthChannelMap.includesKey(lookupKey)) {
        var targetChan = ~bcrToSynthChannelMap[lookupKey];

        if (~chan.includesKey(targetChan).not or: (~chan[targetChan].isRunning.not)) {
            // 🎹 Start synth only if it doesn’t exist or isn’t running
            if (~channelToSynthConfig.includesKey(targetChan)) {
                ~chan[targetChan] = Synth(\bee, [
                    \buf, ~channelToSynthConfig[targetChan][\buf],
                    \loop, 1,
                    \gate, 1,
                    \out, ~channelToSynthConfig[targetChan][\out],
                    \gain, mappedGain  // Start with correct gain
                ], ~channelToSynthConfig[targetChan][\group]);
                ("🚀 Started Synth on Channel: " + targetChan).postln;
            } {
                ("⚠️ No config found for Channel " + targetChan).postln;
            };
        } {
            // 🎚 If the synth is already running, just update gain
            ~chan[targetChan].set(\gain, mappedGain);
            ("✅ Updated gain for Synth " + targetChan + " to " + mappedGain).postln;
        };

        // 🔎 Check the new gain value
        ~chan[targetChan].get(\gain, { |val| ("📢 Final Gain for " + targetChan + " -> " + val).postln; });
    } {
        ("❌ MIDI Mapping Failed: " + lookupKey + " is not in `~bcrToSynthChannelMap`").postln;
    };
}, (0..127));


~bcrToSynthChannelMap.keys.do { |key|
    ("🔍 Key in Map: " + key + " -> " + ~bcrToSynthChannelMap[key]).postln;
};

returns:

🔍 Key in Map:  [ 3, 71 ]  ->  74
🔍 Key in Map:  [ 6, 71 ]  ->  66
🔍 Key in Map:  [ 1, 74 ]  ->  69
🔍 Key in Map:  [ 1, 71 ]  ->  72
🔍 Key in Map:  [ 2, 71 ]  ->  73
🔍 Key in Map:  [ 5, 71 ]  ->  76
🔍 Key in Map:  [ 0, 71 ]  ->  71
🔍 Key in Map:  [ 4, 71 ]  ->  75
🔍 Key in Map:  [ 2, 74 ]  ->  70
🔍 Key in Map:  [ 7, 71 ]  ->  67
🔍 Key in Map:  [ 0, 74 ]  ->  68





~bcrToSynthChannelMap = IdentityDictionary[
    [0, 71] -> 71,
    [1, 71] -> 72,
    [2, 71] -> 73,
    [3, 71] -> 74,
    [4, 71] -> 75,
    [5, 71] -> 76,
    [6, 71] -> 66,
    [7, 71] -> 67,
    [0, 74] -> 68,
    [1, 74] -> 69,
    [2, 74] -> 70
];



-> IdentityDictionary[ ([ 1, 71 ] -> 72), ([ 0, 74 ] -> 68), ([ 1, 74 ] -> 69), ([ 2, 71 ] -> 73), ([ 6, 71 ] -> 66),
  ([ 7, 71 ] -> 67), ([ 4, 71 ] -> 75), ([ 5, 71 ] -> 76), ([ 3, 71 ] -> 74), ([ 0, 71 ] -> 71),
  ([ 2, 74 ] -> 70) ]




//when gain is updated
🎛 MIDI Received: Channel  0  | CC= 71  | Value= 74
❌ MIDI Mapping Failed:  [ 0, 71 ]  is not in `~bcrToSynthChannelMap`
🎛 MIDI Received: Channel  0  | CC= 71  | Value= 77

One problem here is that arrays aren’t valid to use as keys in an IdentityDictionary – try Dictionary here instead. (Dictionary matches based on content; IdentityDictionary needs the exact array instance – a second array with the same contents will not match.)

I’m afraid I don’t have time just at the moment to look in detail at the whole thing, but the use of arrays in this IdentityDictionary would cause elements not to be found.

hjh

Thanks for clarifying. you mean the use of arrays within the IdentityDictionarz how its used would not work in the first place ? Will try to use dictionary

a = [1, 2];
b = [1, 2];

a == b  // equality test
-> true

a === b  // identity test
-> false

a and b are equivalent but not identical. IdentityDictionary, as its name suggests, relies on identity.

hjh

Thanks this worked!! This issue is now resolved