Manipulating A Sampler When It's Not Playing w/ MIDI

I’m trying to manipulate a sampler when it’s not playing with my MIDI device, using knobs, and when I have the parameters I want, trigger the sampler to play.

Right now when I push the button (ccNum=17) to trigger the synth, each time I press, it adds a synth to the server. I’d like to just have one sampler synth on the server, and just keep triggering the same sampler after the envelope time runs out.

(
	SynthDef(\sampler,{
	    arg bufnum, s, m, f, start, end, ffreq1, ffreq2, amp, gate=0;
		var env, ptr, sig, filter1, filter2;
	env=	EnvGen.kr(Env([0,1,1,0], [s, m, f]), gate);
	ptr = Phasor.ar(2, BufRateScale.kr(bufnum)*0.midiratio, start, end);
	sig = BufRd.ar(2, bufnum, ptr);
	filter1 = BHiPass.ar(sig, ffreq1);
	filter2 = BLowPass.ar(filter1, ffreq2);
	Out.ar(0, env*filter2*amp);
	}).add;
)


MIDIdef.cc(\on1, {
	arg val, num;
	case
	{num==17 && val==127} {~startsampler = Synth(\sampler, [\s, 0.1, \m, 1, \f, 0.1, \start, 200000, \end, 340000, \ffreq1, 50, \ffreq2, 700, \amp, 0.5, \gate, 1
	])}
})


And I’m not sure if this is the correct syntax for controlling the parameters for the sampler. There are more but I didn’t want to cram all of it.

MIDIdef.cc(\cc2, { arg val; { ~startsampler.set(\s, val.linexp(0, 127, 0.1, 0.9)) } }, 1, 0);

MIDIdef.cc(\cc3, { arg val; { ~startsampler.set(\start, val.linexp(0, 127, 100000, 200000)) } }, 2, 0);
1 Like

You should move the ~startsampler = Synth(\sampler…) statement out of the MIDIdef so it just happens once at initiation and not each time MIDIdef.cc receives input and then use ~startsampler.set messages like you do already. EnvGen’s done action defaults to 0, so you will be able to also set the gate to 0 and 1 without removing the synth from the server.

I appreciate the help

Would it matter if the buttons on my device only output a MIDI number of 127? When I use this:

MIDIdef.cc(\on1, { |val| ~sampler.set(\gate, val.linlin(0, 127, 0, 1))}, ccNum: 17);

I can only activate the sampler once. When I press the button again I don’t hear anything.

I’m pretty sure that’s the issue, since MIDI number 127 means making \gate = 1 and MIDI number 0 means making \gate = 0. I remember you helping me before with that, writing code so the buttons toggle between 0 and 127

Yes, use a toggle function (like explained in my older post from another thread) to keep track of the internal SC-state of the MIDI buttons.

Only got this far, and I’m not even sure if its correct. Would I need to add some kind of Boolean operator so it switches between 1 and 0.

(
~toggle =  {var ccNum=17;
	ccNum.asSymbol = 1;
};
)

Here is one way of doing which is a little more flexible than the last solution I proposed as this can step through a list of any values:

(
SynthDef(\test, {
	var sig = LFSaw.ar(\freq.kr(110)) * 0.2;
	var env = Env.asr(\atk.kr(0.1), 1, \rel.kr(1)).kr(0, \gate.kr(1));
	sig = LPF.ar(sig, \cutoff.kr(1000));
	Out.ar(0, sig * env ! 2)
}).add;
)

x = Synth(\test);

(
~toggle = (
	gate: Pseq([0, 1], inf).asStream, 
	cutoff: Pseq([200, 400, 600, 800, 500, 300], inf).asStream 
)
)

x.set(\gate, ~toggle[\gate].next) // try a few times, set the gate on and off
x.set(\cutoff, ~toggle[\cutoff].next) // sets the filter cutoff, steps through the values

MIDIdef.cc(\cc, {|val, ccNum|
	case
	{ ccNum == 17 } { x.set(\gate, ~toggle[\gate].next) }
	{ ccNum == 18 } { x.set(\cutoff, ~toggle[\cutoff].next) }	
})

I can’t test the mididef here on the train, but I think it should work.

1 Like

The issue here is variable scope.

Any arg or var declared in a function exists only for the lifetime of the function call. When the function exits, these variables are forgotten – until the next function call, when they start over with all fresh values, no memory of the last time.

For a MIDIFunc to toggle a value, the function needs to know what value the toggle held last time the function was called.

But any state declared inside the MIDIFunc function is forgotten.

Therefore the toggle’s state must be declared outside MIDIFunc. The MIDIFunc will manipulate the externally-declared state, but not define it.

~togValue = 0;

MIDIdef.cc(\toggle, { |val|
    if(val > 0) {
        ~togValue = 1 - ~togValue;
        ~synth.set(\gate, ~togValue);
    };
}, ccNum: yourCCNum);

All of Thor’s suggestions (here, and in the other thread) are built around this idea. (The stream idea, previous post, is a good one! Very flexible.)

hjh

1 Like

Thanks James and Thor, I got it to work. Running in to another issue though.

I can’t figure out the reason why this control bus isn’t working. I’m having the start time on the envelope in my sampler read from a control bus but when I turn the knob (ccNum=1), I don’t hear any change. I’d like to change the start time of the envelope from the knob even when the sampler is not playing.

~cc2 = Bus.control(s, 1).set(0.1);

MIDIdef.cc(\cc2, { arg val; { ~cc2.set(val.linexp(0, 127, 0.1, 1)) } }, 1, 0);

(
	SynthDef(\sampler,{
	    arg bufnum, s, m, f, start, end, ffreq1, ffreq2, amp, gate=0;
		var env, ptr, sig, filter1, filter2, filter3, mainsig, noise;
	env=	EnvGen.kr(Env([0,1,1,0], [s, m, f]), gate);
	ptr = Phasor.ar(2, BufRateScale.kr(bufnum)*0.midiratio, start, end);
	sig = BufRd.ar(2, bufnum, ptr);
	filter1 = BHiPass.ar(sig, ffreq1);
	filter2 = BLowPass.ar(filter1, ffreq2);
	Out.ar(0, env*filter2*amp);
	}).add;
)

~sampler = Synth(\sampler, [\s, ~cc2.asMap, \m, 2, \f, 0.1, \start, 200000, \end, 340000, \ffreq1, 50, \ffreq2, 700, \amp, 0.5]);

EDIT: I got some stuff wrong as you can see from James’ post below, I deleted that part.

I changed the env of the synthdef to what I think you want - gate 1: samples loops around, gate 0: sample fades out. Remember to supply the bufnum below.

(
SynthDef(\sampler,{
	arg bufnum, rel, start, end, ffreq1, ffreq2, amp, gate = 0, atkBus; // added atkBus to args
	var env, ptr, sig, filter1, filter2, filter3, mainsig, noise;
	env = Env.asr(In.kr(atkBus), 1, rel).kr(0, gate);
	ptr = Phasor.ar(2, BufRateScale.kr(bufnum), start, end);
	sig = BufRd.ar(2, bufnum, ptr);
	filter1 = BHiPass.ar(sig, ffreq1);
	filter2 = BLowPass.ar(filter1, ffreq2);
	Out.ar(0, env * filter2 * amp);
}).add;
)

~sampler = Synth(\sampler, [atkBus: ~cc2, sus: 1, rel: 0.1, start: 0, end: 34000, ffreq1: 50, ffreq2: 700, amp: 0.5]);
// NB Remember to supply the bufnum
// ~sampler.set(\bufnum, x) 
~cc2.set(1);
~sampler.set(\gate, 1)
~sampler.set(\gate, 0)

For some stuff you might need, like a one-shot sample, it is better to use Named Control style arguments because you can specify a trigger argument, like \gate.tr(0) - this is what you need for one-shot samples. Here is how I would write it. You can comment out the sustaining env and uncomment the perc env to test one-shot samples. It is highly advisable to use default values for most arguments especially filter frequencies.

(
SynthDef(\sampler,{
	var env, ptr, sig, filter1, filter2;
	var buf = \bufnum.kr;
	env = Env.asr(In.kr(\atkBus.kr), 1, \rel.kr(0.1)).kr(0, \gate.kr(0));
	// env = Env.perc(In.kr(\atkBus.kr), \rel.kr(0.5)).kr(0, \gate.tr(0));
	ptr = Phasor.ar(2, BufRateScale.kr(\bufnum.kr), \start.kr(0), BufFrames.kr(buf));
	sig = BufRd.ar(2, buf, ptr);
	filter1 = BHiPass.ar(sig, \ffreq1.kr(50));
	filter2 = BLowPass.ar(filter1, \ffreq2.kr(700));
	Out.ar(0, env * filter2 * \amp.kr(1));
}).add;
)

There are at least two other ways of achieving the same result, which one to pick is mostly a stylistic choice.

Use a bus outside the synthdef and asMap to map the bus to the arg:

(
SynthDef(\sampler,{
	var env, ptr, sig, filter1, filter2;
	var buf = \bufnum.kr;
	env = Env.asr(\atk.kr(0.1), 1, \rel.kr(0.1)).kr(0, \gate.kr(0));
	// env = Env.perc(\atk.kr(0.1), \rel.kr(0.5)).kr(0, \gate.tr(0));
	ptr = Phasor.ar(2, BufRateScale.kr(\bufnum.kr), \start.kr(0), BufFrames.kr(buf));
	sig = BufRd.ar(2, buf, ptr);
	filter1 = BHiPass.ar(sig, \ffreq1.kr(50));
	filter2 = BLowPass.ar(filter1, \ffreq2.kr(700));
	Out.ar(0, env * filter2 * \amp.kr(1));
}).add;
)

~sampler = Synth(\sampler, [atk: ~cc2.asMap, sus: 1, rel: 0.1, start: 0, end: 34000, ffreq1: 50, ffreq2: 700, amp: 0.5]);
// NB Remember to supply the bufnum
// ~sampler.set(\bufnum, x) 
~cc2.set(0.1);
~sampler.set(\gate, 1)
~sampler.set(\gate, 0)

Or what I would probably do, not use a bus at all, use the same synthDef as above and set the value directly in the mididef:

MIDIdef.cc(\cc2, { arg val; ~sampler.set(\atk, val.linexp(0, 127, 0.1, 1)) }, 1, 0);

The last two examples has the advantage that you don’t have to decide wether you are using a bus or not when you write the synthdef - some instances of the synthdef can use busses, others not.

1 Like

The problem is that you have an extra pair of function braces. So your MIDI action is to create a function that sets the control bus, but not call the function, so nothing gets set.

MIDIdef.cc(\cc2, { arg val; ~cc2.set(val.linexp(0, 127, 0.1, 1)) }, 1, 0);

AFAICS your usage of ~cc2.kr is fine – this method creates the In.kr unit that Thor recommended. However, my favorite way to handle this case is the asMap approach (which Thor just showed you).

I’m curious why the variable name is cc2 when it’s for ccnum 1? That seems likely to cause confusion when you come back to the code six months later.

hjh

1 Like

@James - yes missed the MIDIdef part and good to learn you can use ~busName.kr directly, did not know that. I got confused using s.newBusAllocators now, it does not delete old control busses or erase the values and busses can still be set using the old names, so what does it do? There does not seem to be any documentation of what it does in the helpfiles. I looked up the implementation, but it didn’t help me understanding what is going on.

It’s acceptable as a hack, but I generally avoid it except for one-off situations, because…

Hardcoding bus numbers into SynthDefs is usually a bad idea (as you already stated). aBus.kr hardcodes a bus number, so it’s generally worse than either passing the bus number in, or using asMap. (I have one exception, which is throwaway live coding situations, where “correctness” isn’t as important. I never, ever write a reusable SynthDef with a hardcoded bus number.)

I prefer asMap because this way, you can be consistent about defining synth inputs in terms of values, rather than the above situation where the attack time is bus related and hold/release times are values. Consistency in the synth’s public interface avoids later confusion.

TL;DR is: don’t use newBusAllocators. IMO this should be treated as a private method of Server.

If you’re reaching for newBusAllocators, there’s probably a mistake in the code or in the code’s design.

The server bus allocators only manage bus numbers. There is no action on the server for allocating or freeing buses. In the server, all buses exist all the time (unlike Buffers, where memory needs to be alloc’ed and freed).

hjh

Ah thanks for clarifying. In the past I have used it when running larger code structures at initiation time thinking it would free previously defined busses. It never caused any problems but in the future I will not use it.

Setting the value in the MIDIdef uses the same synthdef, just without the bus and asMap. In reality I would do bookkeeping of the value set by the MIDIdef to decouple the mididef from the code, so in that sense using busses is easier. I did not like the inconsistency of the first synthdef either…

Thanks guys, I really appreciate the help. Learning a lot.

If you’re completely sure you won’t be using old Bus objects at the same time as new ones created after newBusAllocators, then it’s probably ok. My gut feelings are 1/ if you’re not absolutely sure of this, then there could be a little risk of bus index collision, and 2/ in my own use, if I have buses lying around, I probably also have buffers lying around too. In that case, “reset” is just “recompile the class library and reload” and then I do have a hard guarantee of no collisions.

Yes – it depends on the case. If it’s a shared value, or a value I want to persist in the server for a future synth, then (in my own use) I use GenericGlobalControl (ddwCommon quark) as a language-side value mirrored on a control bus. Then I get easy language-side access, easy automation (with language side monitoring, optionally), typical interfaces (asMap, kr, asPattern and it acts like a number in math ops IIRC), and it serves as a Model in MVC (so GUI use then becomes much simpler, although the notifications are quirky).

If it’s a single synth, I’d probably just set the control directly.

hjh

1 Like

Actually, now that I think back, some of the stranger errors I have gotten which then later magically disappeared could stem from that.

I’ve updated my sampler so I can choose what rate the samples playback from. I have 4 items within an array, but when I turn the knob (ccNum=2), I’m only getting the first and last items of the array. Why would that be?

~cc2 = Bus.control(s, 1).set(0.1);
~cc3 = Bus.control(s, 1).set(0);


MIDIdef.cc(\cc2, { arg val; ~cc2.set(val.linexp(0, 127, 0.1, 3)) }, 1, 0);
MIDIdef.cc(\cc3, { arg val; ~cc3.set(val.linexp(0, 127, 0, 3)) }, 2, 0);

(
	SynthDef(\sampler,{
	    arg bufnum, s, m, f, start, end, ffreq1, ffreq2, amp, gate=0, which;
		var env, ptr, sig, filter1, filter2, filter3, mainsig, noise;
	env=	EnvGen.kr(Env([0,1,1,0], [s, m, f]), gate);
	ptr = Phasor.ar(2, BufRateScale.kr(bufnum)*Select.kr(which, [0.midiratio, 2.midiratio, 4.midiratio, 7.midiratio]), start, end);
	sig = BufRd.ar(2, bufnum, ptr);
	filter1 = BHiPass.ar(sig, ffreq1);
	filter2 = BLowPass.ar(filter1, ffreq2);
	Out.ar(0, env*filter2*amp);
	}).add;
)

~sampler = Synth(\sampler, [\bufnum, j.bufnum, \s, ~cc2.asMap, \which, 3, \m, 2, \f, 0.1, \start, 1300000, \end, 1310000, \ffreq1, 50, \ffreq2, 2700, \amp, 0.5]);

~togValue = 0;

MIDIdef.cc(\toggle, { |val|
    if(val > 0) {
        ~togValue = 1 - ~togValue;
        ~sampler.set(\gate, ~togValue);
    };
}, ccNum: 17);

Shouldn’t this be \which, ~cc3.asMap?

Also try val.linlin(0, 127, 0, 3.99999) – linexp that touches or crosses zero does not work (and is a weird idea for an array index anyway, which should be linear).

hjh

1 Like