Sound production seems blocked - MIDI keyboard

Hi there

So I have the following code:

MIDIClient.init;

MIDIIn.connectAll;

(
SynthDef(\gera_1, {

	arg out=0, freq=300, gate=1;

	var snd, sndenv, rev, mix, revenv;

	sndenv = EnvGen.ar(Env.adsr(0.03,0.1,0.9,0.2,curve:-2),gate:gate, doneAction:2);

	snd = Saw.ar(freq,0.5)
	+Saw.ar(freq*1.02,0.3);

	snd = snd*sndenv;


	Out.ar(out,snd!2);

}).add
)


(
SynthDef(\gera_1_fx, {

	arg gate=1, out=0;

	var input, dec, decenv;

	input = In.ar(0,2);

	decenv = EnvGen.ar(Env([0,1,0],[0.2,0.5]),gate:gate, doneAction:2);

	dec = Decay.ar(input,0.2);

	dec = dec*decenv;


	Out.ar(out,dec);

}).add
)

a = Group.basicNew(s,1);


(
var notes;

notes = Array.newClear(128); 

MIDIdef.noteOn(\on_gera, { arg veloc, num, chan, src;
	notes[num] = Synth.head(a, \gera_1, [
		\freq, num.midicps,
	]); 
	Synth.tail(a, \gera_1_fx);
});

MIDIdef.noteOff(\off_gera, { arg veloc, num, chan, src;
    notes[num].release;
});

)

My basic idea would be to use two SynthDefs: one for defining things like the wave form or frequency and so on and another one for effects and to be able to use individual envelopes for each of them.

When I use my MIDI keyboard the sound production to me seems to be kind of blocked when I play a little faster.

Sorry, I am probably misunderstanding some very basic concepts on how to do what I am looking for.

This means that the buses should also be independent for each note.

The effect has an envelope.

If all of the source and effect synths use the same bus (or stereo bus pair), then one effect synth’s envelope affects all of the notes that are playing. (The effect says input = In.ar(0, 2) – definitely don’t do that. Hardcoding buses will almost always cause you problems later.)

So, to do it with separate synths, you would need to allocate a bus (or bus pair) for every note, and then when that note’s synths are finished (see Node:onFree), release the bus number(s) for reuse.

Out.ar(out,dec); – do you want to add the effect signal to the dry signal, or overwrite? Normally insert-style effects replace the dry signal.

hjh

Ok, sounds logical.

Sorry, I am somewhat confused when trying to figure out how to write that as code.

So the effect SynthDef should look like this?

(
SynthDef(\gera_1_fx, {

	arg gate=1, out=0, in=0;

	var input, dec, decenv;

	input = In.ar(in,2);

	decenv = EnvGen.ar(Env([0,1,0],[0.2,0.5]),gate:gate, doneAction:2);

	dec = Decay.ar(input,0.2);

	dec = dec*decenv;


	Out.ar(out,dec);

}).add
)

I considered the following links:
https://doc.sccode.org/Classes/Node.html
https://doc.sccode.org/Tutorials/Mark_Polishook_tutorial/17_Delays_reverbs.html
https://composerprogrammer.com/teaching/supercollider/sctutorial/tutorial.html#chapter6

I still feel kind of stuck with this whole bus thing.
I would like to add the effect to the dry signal.

Oh ok, then the Out.ar and envelope approach make sense.

For the bus management, I’d suggest to use a helper function to grab the buses and play the synths (and also register the bus release – which is slightly tricky because in theory, you may not know which synth will end first). Maybe something like this, but untested.

~playSynthPair = { |sourceDefname, sourceArgs, fxDefname, fxArgs|
    var bus = Bus.audio(s, 2);
    var synthsFreed = 0;
    var releaseFunc = {
        synthsFreed = synthsFreed + 1;
        if(synthsFreed >= 2) { bus.free };
    };
    var source = Synth(\sourceDefname, sourceArgs).onFree(releaseFunc);
    var effect = Synth.after(source, \fxDefname, fxArgs).onFree(releaseFunc);
    // hm, let's return both synths to the caller
    [source, effect]
};

hjh

So should it look like this?

(
~playSynthPair = { |sourceDefname, sourceArgs, fxDefname, fxArgs|
    var bus = Bus.audio(s, 2);
    var synthsFreed = 0;
    var releaseFunc = {
        synthsFreed = synthsFreed + 1;
        if(synthsFreed >= 2) { bus.free };
    };

	var source = Synth(\gera_1).onFree(releaseFunc);
    var effect = Synth.after(source, \gera_1_fx).onFree(releaseFunc);
    // hm, let's return both synths to the caller
    [source, effect]
};
)

(
var notes;

notes = Array.newClear(128);

MIDIdef.noteOn(\on_gera, { arg veloc, num, chan, src;
	notes[num] = ~playSynthPair;
});

MIDIdef.noteOff(\off_gera, { arg veloc, num, chan, src;
    notes[num].release;
});

)

I tried some things but couldn’t quite figure out how to use your snippet of code to produce sound (couldn’t so far), sorry.

Have you gotten one source and one synth to play together? That is, take the problems one at a time: First get \gera_1 and \gera_1_fx to produce sound at all (1). Then add bus logic (2). Then automate the process (3, the function I sketched out, but which I didn’t complete at that time). Then trigger that by MIDI (4).

I recognize this state of mind when coding – we all get into this space at times – not understanding what is going on, and trying random code changes. The probability of accidentally stumbling onto the right solution is low. So then you’re left with two alternatives: a/ somebody on the forum writes your code for you, or b/ solve one problem first – really solve it, get it to be solid, before adding the next piece.

I’ll admit here that in my function sketch, I forgot to include the bus routing – or rather, I assumed you would fill in the synth arguments. That is, I had assumed you had worked out (2) and would plug that solution into the function… but it seems you’re not clear on (2).

So forget the function and take a step back.

First allocate a bus for the source-effect pair.

b = Bus.audio(s, 2);

You want to output \gera_1 to this bus. The SynthDef, correctly, has an out argument, so you need to give this argument the value of b.

x = Synth(\gera_1, [out: b, freq: something you should fill in, this part isn't my job]);

Your newer effect SynthDef has in and out arguments. If you want the effect to read from the same bus that x is writing to, what value should in take? If you want the effect to write to the hardware, what value should out take?

We can talk about automatic bus management and MIDI after you’ve got this. I apologize if that seems basic and not getting to the “interesting” questions – but higher levels of abstraction need to be built on a foundation that is working.

hjh

I see.

So I could make (2) work:

(
SynthDef(\gera_1, {

	arg out=0, freq=300, gate=1;

	var snd, sndenv, rev, mix, revenv;

//	sndenv = EnvGen.ar(Env.adsr(0.03,0.1,0.9,0.2,curve:-2),gate:gate, doneAction:2);

	sndenv = EnvGen.ar(Env([0,1,0],[1,3]),gate:gate, doneAction:2);

	snd = Saw.ar(freq,0.2)
	+Saw.ar(freq*1.02,0.05);

	snd = snd*sndenv;


	Out.ar(out,snd!2);

}).add;



SynthDef(\gera_1_fx, {

	arg gate=1, out=0, in;

	var input, dec, rev, decenv;

	input = In.ar(in,2);

	decenv = EnvGen.ar(Env([0,1,0],[1,7]),gate:gate, doneAction:2);

	dec = Decay.ar(input,0.2);

	dec = dec*decenv;


	Out.ar(out,dec);

}).add;

b = Bus.audio(s, 2);

)

x = Synth(\gera_1, [out: b, freq: 200]);
y = Synth.after(x, \gera_1_fx, [in: b]);

Found this, which was helpful, too:
https://doc.sccode.org/Tutorials/Getting-Started/11-Busses.html
The adsr envelope (gera_1) might be more useful for MIDI.

I think (3) works too. I can produce sound by:

(
~playSynthPair = { |sourceDefname, sourceArgs, fxDefname, fxArgs|
    var bus = Bus.audio(s, 2);
    var synthsFreed = 0;
    var releaseFunc = {
        synthsFreed = synthsFreed + 1;
        if(synthsFreed >= 2) { bus.free };
    };

	var source = Synth(\gera_1, [out: b]).onFree(releaseFunc);
	var effect = Synth.after(source, \gera_1_fx, [in: b]).onFree(releaseFunc);
    // hm, let's return both synths to the caller
    [source, effect]
};
)

~playSynthPair.fork

So now I am stuck with (4):

I can’t quite figure out how to make that function usable for MIDI.

(
var notes;

notes = Array.newClear(128);

MIDIdef.noteOn(\on_gera, { arg veloc, num, chan, src;
	notes[num] = ~playSynthPair;
});

MIDIdef.noteOff(\off_gera, { arg veloc, num, chan, src;
    notes[num].release;
});

)

The tutorial series does explain about using functions as functions.

http://doc.sccode.org/Tutorials/Getting-Started/04-Functions-and-Other-Functionality.html

To call a function, use the .value message (not fork).

Also make sure you understand the purpose of arguments in that help file. The function is not very useful if it isn’t parameterized.

~playSynthPair = { |sourceDefname, sourceArgs, fxDefname, fxArgs|
    var bus = Bus.audio(s, 2);
    var synthsFreed = 0;
    var releaseFunc = {
        synthsFreed = synthsFreed + 1;
        if(synthsFreed >= 2) { bus.free };
    };

	var source = Synth(sourceDefname, sourceArgs ++ [out: b]).onFree(releaseFunc);
	var effect = Synth.after(source, effectDefname, effectArgs ++ [in: b]).onFree(releaseFunc);
    // hm, let's return both synths to the caller
    [source, effect]
};

Then you should pass the source and effect parameters as arguments in the value message (the function tutorial explains the connection between the function argument list and the value argument list).

hjh

I have one as I hope final question:

When I use this envelope for gera_1 all synths are released as I wish consulting the Node Tree:

sndenv = EnvGen.ar(Env([0,1,0],[1,3]),gate:gate, doneAction:2);

But when I use this one:

sndenv = EnvGen.ar(Env.adsr(0.03,0.1,0.9,0.2,curve:-2),gate:gate, doneAction:2);

It remains in the Node Tree.

But apart from this it works and looks like this:

(
var notes;

notes = Array.newClear(128);

MIDIdef.noteOn(\on_gera, { arg veloc, num, chan, src;
	notes[num] = ~playSynthPair.value(\gera_1, [\freq, num.midicps], \gera_1_fx)
});

MIDIdef.noteOff(\off_gera, { arg veloc, num, chan, src;
    notes[num].release;
});

)

Thanks a lot for your help! I really learned a lot through this topic!

So you do have an instruction to release the note:

But… what is a “note” here?

It’s two synths.

Now check the implementations of release.

release (5 matches)

  • Event - an environment that represents an action [Classes]

  • Node - Abstract superclass of Synth and Group [Classes]

  • NodeProxy - a reference on a server [Classes]

  • Object - abstract superclass of all objects [Classes]

  • ProxySpace - an environment of references on a server

The MIDI example you copied puts single Synth instances into a notes slot. So when you do notes[num].release, it calls the Node implementation.

But now notes[num] is an array of two synths. The only applicable version of release, then, comes from Object: “Remove all dependants of the receiver.” Well… that doesn’t have anything to do with stopping synths. So it’s unsurprising that your synths aren’t getting released.

SC can’t magically guess your data structure. You have to know what your data structure is, and write the code to manage it.

What you want to do is release both synths, by looping over the array.

notes[num].do { |oneSynth| oneSynth.release }

You’ll have a problem with fixed duration envelopes here, but it should be ok if the source and effect envelopes are both ADSR style.

hjh

Ah, could just check it now.
Great, thanks a lot!