Creating a MIDI Bank

I have two basic SynthDef’s. One playing a saw wave, and the other playing a pulse wave. I have assigned the pulse wave to play when I hit a button that has a CCnum of 17. I have assigned the saw wave to play when I hit the button that has a CCnum of 20, then hit the button that has a CCnum of 17. So I’m pressing two buttons to activate the second synth. Basically overriding the CCnum 17 button to accommodate the second synth. But when I activate the second synth, the first synth is still playing. I’d like to ‘free’ the synths as I’m activating them. I have tried a few things but to no avail. I’ll post the code below. And I’m making this post right before I go to sleep for the night so if I don’t respond right away, that’s why. I appreciate any help.

(
SynthDef.new(\pulse, {
arg  freq, width=0.1;
var env, sig;
env = EnvGen.kr(Env([0,1,1,0], [0.1, 120, 1]), doneAction:2);
sig = Pulse.ar(freq, width, 0.1);
Out.ar(0, sig*env.dup);
}).add;
)

(
SynthDef.new(\saw, {
arg  freq=220;
var env, sig;
env = EnvGen.kr(Env([0,1,1,0], [0.1, 120, 1]), doneAction:2);
sig = Pulse.ar(freq, 0.1);
Out.ar(0, sig*env.dup);
}).add;
)

MIDIdef.cc(\on1, {
	arg val, num;
	case
	{num==17 && val==127} {a = Synth(\pulse, [\freq, 220])}
})

MIDIdef.cc(\switch1, {
	arg val, num;
	case
	{num==20 && val==127} {MIDIdef.cc(\on2, {
	arg val, num;
	case
		{num==17 && val==127} {b = Synth(\saw, [\freq, 220])}
	})}
	{num==20  && val==127} {a.stop}
})

The ‘a.stop’ doesn’t work. I’ve tried a few derivatives of this, putting it in different spots but it still doesn’t work.

Here I refactored and changed your code a bit and converted the envs to sustaining envelopes with a gate argument. In your example the SynthDefs are virtually identical, so everything could be done with just one SynthDef but I suspect you want different SynthDefs. Is this the behaviour you are looking for?:

(
SynthDef.new(\pulse, {
	arg  freq, width=0.1, gate = 1;
	var env, sig;
	env = Env.asr([0.1, 1, 1]).kr(2, gate);
	sig = Pulse.ar(freq, width, 0.1);
	Out.ar(0, sig*env.dup);
}).add;
)

(
SynthDef.new(\saw, {
	arg  freq=220, gate = 1;
	var env, sig;
	env = Env.asr([0.1, 1, 1]).kr(2, gate);
	sig = Saw.ar(freq, 0.1); 
	Out.ar(0, sig*env.dup);
}).add;
)

(
// start first synth
x = Synth(\pulse, [freq: 110]);
)

(
// stop first synth, start second synth
x.set(\gate, 0);
x = Synth(\saw, [freq: 220]);
)
1 Like

Yes, I’d just need

(
// start first synth
x = Synth(\pulse, [freq: 110]);
)

(
// stop first synth, start second synth
x.set(\gate, 0);
x = Synth(\saw, [freq: 220]);
)

in two separate MIDIdef’s.

I’m trying to figure out how to make a MIDI Bank. I posted a basic example, but i guess it’s better to assume that all of my knobs and buttons are taken, and so I want to assign one button to override all of the knobs and buttons to make room for other MIDIdef’s.

I am not sure I exactly follow, but if you want to change banks, you can do this:

(
~bank = 0;
MIDIdef.program(\pg, {|n| ~bank = n })
)

Then you can create an array of cc values and index into it with ~bank.

(
// Create 10 banks of 8 controllers each
~numBanks = 10;
~numControllers = 8;
~ccValues = ~numBanks.collect{|n| (n * ~numControllers..n * ~numControllers + ~numControllers - 1) }
)

If you device does not send program change, you can use a button to send a noteOn or ccValue and interpret this as a bank change. You can use one button to step through banks, wrapping around to 0, when the end of the list is reached.

Now your knobs can be assigned to different cc values. For this example, let’s assume that you have a midi device with 8 knobs which transmit cc controllers 40-47, ie. first knob cc = 40, 2nd knob cc = 41 etc.

(
~offset = 40;
MIDIdef.cc(\cc, {|value, ccNum|
	var controller = ~ccValues[~bank][value - offset];
	case
	{ controller == 0 } { /* map 'value' here to whatever you need */ }
	{ controller == 1 } {  /* map 'value' here to whatever you need */}
	{ controller == 2 } {  /* map 'value' here to whatever you need */}
	{ controller == 3 } { /* map 'value' here to whatever you need */ }
	///etc...
})
)

I hope this is helpful, or maybe I am misunderstanding what you are looking for.

Oh this is great! Thank you!

Could we apply your code to my example, just to see if I understand it correctly?

I have a MIDI device that just has 16 knobs and 4 buttons. The 16 knobs has ccNum’s of 1-16, and the buttons have ccNum’s of 17-20.

I’d like one button, so I guess ccNum 17, to be able to play two SynthDef’s, but at different times that I choose. So ‘numControllers’ would be equal to 2. If I press 17 to play the pulse wave SynthDef, but I want to switch to the saw wave SynthDef by pressing ccNum 17 again. How would I go about doing that? And once I switch, I’d like the Pulse wave synth to stop, so you only hear the saw wave synth.

And the buttons only trigger a value of ‘127’. They don’t switch back and forth between ‘0’ and ‘127’ like a lot of other MIDI devices. One of the drawbacks to my device.

And I’m thinking that having each individual knob and button to have its own bank would be too chaotic, I think.

Cuz usually I have multiple knobs applied to one SynthDef, so would it be possible to create ‘groupings’ of knobs and buttons to a bank. So for example, have knobs with ccNum 1-4, and button 17 have its own bank, then knobs ccNum 5-8 and button 18 have its own bank, etc So when I change banks its only changing for those groupings?

Ok, this is going to be a rather long post. When dealing with many controllers, especially if you want to use GUI elements in conjunction with midi controllers, the data structure is key. The structure below may be a tad bit more involved than needed if you don’t use additional SC created GUI(s) but as soon as you want to create a GUI and have both the gui and the cc controllers update values, this structure makes it very easy. For now I will focus on the cc controllers and save the GUI part for another day.

// I will use the single letter variable 'p' to keep track of all events, first bank = index of 0;
p = ();
~bank = 0;

/// 2 SynthDefs for demonstration, here I keep doneaction = 0, so synths can be retriggered
(
SynthDef.new(\pulse, {
	var env, sig;
	env = Env.asr([\atk.kr(0.3), 1, \rel.kr(1)]).kr(0, \gate.kr(0));
	sig = Pulse.ar(\freq.kr(110), \width.kr(0.1), 0.2);
	Out.ar(0, sig!2 * env * \amp.kr(1));
}).add;

SynthDef.new(\saw, {
	var env, sig;
	env = Env.asr([\atk.kr(0.3), 1, \rel.kr(1)]).kr(0, \gate.kr(0));
	sig = Saw.ar(\freq.kr(110), 0.1); 
	sig = LPF.ar(sig, \cutOff.kr(440).lag(0.1));
	Out.ar(0, sig!2 * env  * \amp.kr(1));
}).add;
)

/// Each Synth has a corresponding event keeping track of parameters, values and mappings

p.add(\pulse -> (
	sequence: [\amp, \atk, \rel, \width], // in case you want to create a GUI with controls in a specific order
	vals: (amp: 1, atk: 0.3, rel: 1, width: 0.1, gate: 0), // initial values, these will be dynamically changed by the cc controllers
	maps: (amp: [0, 1], atk: [0.005, 3], rel: [0.05, 6], width: [0, 1], gate: [0, 1, \lin, 1])
));

p.add(\saw -> (
	sequence: [\amp, \atk, \rel, \width], // in case you want to create a GUI with controls in a specific order
	vals: (amp: 1, atk: 0.3, rel: 1, cutOff: 440, gate: 0), // initial values, these will be dynamically changed by the cc controllers
	maps: (amp: [0, 1], atk: [0.005, 3], rel: [0.05, 6], cutOff: [50, 2000, \exp], gate: [0, 1, \lin, 1])
))

// instantiate both synths and add them to the synth-events:

p.pulse.add(\syn -> Synth(\pulse)); // initial gate = 0, so no sound yet
p.pulse.syn.set(\gate, 1) // trigger the env to hear sound
p.pulse.syn.set(\gate, 0) // no sound, but synth is still on the server

p.saw.add(\syn -> Synth(\saw));
p.saw.syn.set(\gate, 1) // trigger the env to hear sound
p.saw.syn.set(\gate, 0) // no sound, but synth is still on the server

// create mappings for you buttons, since they only send value = 127, we need to toggle it:

~buttonStates = 10.collect{|i| (\17: 0, \18: 0, \19: 0, 20: \0) }; // 4 buttons per bank, initially set to 0;

// simulation af button press from button with ccNum = 17

( // evaluate several times to see the button toggle for \17
var ccNum = 17; // incoming via MIDIdef;
~buttonStates[~bank][ccNum.asSymbol] = 1 - ~buttonStates[~bank][ccNum.asSymbol]; // toggle button
)

// create master event for mappings for cc controllers

(
~ccValues = ()!10; // 10 empty events
)

// create assignments for the controls or bank 0
(
~ccValues[0] = (
	\1: [\pulse, \amp], \2: [\pulse, \atk], \3: [\pulse, \rel], \4: [\pulse, \width],
	\5: [\saw, \amp], \6: [\saw, \atk], \7: [\saw, \rel], \8: [\saw, \cutOff],
)
)

// create master event for button presses

(
~buttonFuncs = ()!10;
)

// create botton funcs for bank 0
(
~buttonFuncs[0] = (
	\17: {|val| 
		p.saw.vals.gate = val; 
		p.saw.syn.set(\gate, p.saw.vals.gate);
		p.pulse.vals.gate = 1 - val;
		p.pulse.syn.set(\gate, p.pulse.vals.gate);
	},// toggles the 2 synths
	\18: {|val|
	// put something here
	}
	// etc.
)
)

( // emulate the pressing of button, to toggle the 2 synths, evaluate several times
~buttonStates[~bank][\17] = 1 - ~buttonStates[~bank][\17];
~buttonFuncs[0][\17].(~buttonStates[~bank][\17]);
)

// Function for settings parameters
(
~setParameter = {| val = 0, target = \pulse, parameter = \amp| // val is normalized in the range [0, 1]
	var mappedVal = p[target][\maps][parameter].asSpec.map(val);
	p[target][\vals][parameter] = mappedVal;
	p[target][\vals][parameter].debug(target++" "++parameter);
	p[target][\syn].set(parameter, mappedVal);
};
)

// test it ///

~setParameter.(1, \saw, \gate);
~setParameter.(0.3, \saw, \cutOff);
~setParameter.(1, \pulse, \gate);
~setParameter.(0.3, \pulse, \width);

~setParameter.(0, \saw, \gate);
~setParameter.(0, \pulse, \gate);

// MIDIdef
(
MIDIdef.cc(\cc, {|value, ccNum|
	[value, ccNum].postln;
	case 
	{ ccNum < 17 } 
	{ 
		var normVal = value.linlin(0, 127, 0, 1);
		var assign = ~ccValues[~bank][ccNum.asSymbol];
		if (assign.notNil) { ~setParameter.(normVal, assign[0], assign[1]) } 
	}
	
	{ (ccNum >= 17) && (ccNum <= 20) } // button,
	{
		// ignore the incoming value, the but value toggles bewteen 0 and 1
		~buttonStates[~bank][ccNum.asSymbol] = 1 - ~buttonStates[~bank][ccNum.asSymbol]; 
		if (~buttonFuncs[~bank][ccNum.asSymbol].notNil)
		{ ~buttonFuncs[~bank][ccNum.asSymbol].(~buttonStates[~bank][ccNum.asSymbol]) }
	}
});
)

/// Testing, I don't have a controller hooked up, so sending values with midiout

m = MIDIOut(1); // 1 = IAC bus on my mac
m.control(0, 17) // simulate pressing button with ccNum = 17
m.control(0, 18) // nothing happens, \18 is not defined

m.control(0, 1, rrand(20, 90)) // sending amp messages to the pulse synth
m.control(0, 8, rrand(20, 127)) // sending cuttoff messages to the saw synth, make sure gate is = 1

Wow, thank you so much man. I really appreciate this. And I actually understand the logic and syntax. behind all of it. Just a lot to sink-in and learn.

Check also this post in case you want to add a GUI to your setup: How to build a fully decoupled GUI

2 Likes