Hello everyone,
I am submitting here a problem that I am unable to solve related to the protevent concept.
It all starts with the sampler project which, for a few years now, I have been implementing piece by piece and using in various jobs.
The post is quite long, but I hope I can be concise and clear enough for you to understand everything.
Premise
The goal is to place as much reusable code as possible within a protoevent to then use it as a starting point for multiple Pbindefs, within which only the key-value matches you want to change are defined.
The context is to use a sample bank (in this example we will use the SSO) and a sample loading function that basically prepares a dictionary of buffers and integers to represent their corresponding midi notes.
The task of the Pbindef is, given a scale, octave and degree, to retrieve the most ‘suitable’ sample to play the desired note and calculate the correct rate at which to play it.
in use
I typically use the following SynthDef:
(
SynthDef(\hybrid_player, {
|
out=0, gate=1, amp=0.9, buf, rate=1, pan=0.0,
atk=5, dcy=0.1, sus=0.7, rel=5
|
var sig, sig1, sig2, env;
var density = LFNoise0.kr(25).range(1, 5);
var trigger = Impulse.kr( density );
var pos = 0.5 + TRand.kr(trigger, -0.35, 0.35);
var length = 1 + TRand.kr(trigger, 0.25, 0.35);
env = EnvGen.kr(Env.adsr(atk, dcy, sus, rel), gate, doneAction:2);
sig1 = PlayBuf.ar(1, buf, BufRateScale.ir(buf)*rate, 1, doneAction:0);
sig2 = Mix.ar(GrainBuf.ar(
1,
trigger,
length,
buf,
rate,
pos,
2,
pan: 0)
);
// TODO: sometimes the Line time create some glitch: why?
sig = SelectX.ar(Line.kr(0, 1.0, 0.3), [sig1, sig2]);
sig = LeakDC.ar(sig);
sig = Normalizer.ar(sig, 0.4);
sig = sig * env * amp;
Out.ar(out, Pan2.ar(sig, pan));
}).add;
);
and, after defining a dictionary and the load function (see this post ), I load the samples by evaluating the row:
~func_load_samplebank.(\sso_flute, "Flute", $-, $#, [\flute], ~my_samples, basepath:~path_ssoBasepath);
I can now play a musical phrase by evaluating this Pbindef
(
Pbindef(\flutes_pbindef,
\instrument, \hybrid_player,
\samples_dict, ~my_samples[\sso_flute],
\scale, Scale.major,
\root, 0,
\octave, 5,
\degree, Pseq([\rest, [0,4], [0,5]], 1),
\dur, 4,
\amp, 0.4,
\atk, 1,
\dcy, 0.5,
\sus, 0.7,
\rel, 5,
\pan, 0.0,
\out, 0,
\index, Pfunc({
|e|
e.use({
if( ~degree.() != \rest, {
~midinote.().asArray.collect { |note| ~samples_dict[\midinotes].indexIn(note); }
}, {
0;
});
});
}),
\buf, Pfunc({
|e|
e.use({
~samples_dict[\buffers][ ~index.() ];
});
}),
\rate, Pfunc({
|e|
e.use({
(~midinote.() - ~samples_dict[\midinotes][ ~index.() ]).midiratio;
});
}),
\callback, {
|e|
"\nCallback".postln;
e.asSortedArray.do({
|item|
postf("%\t%\n",item[0], item[1]);
});
}
).quant_([4]).play;
)
I associated a debugging function with the callback key in order to analyse the characteristics of the events generated by the pattern and to better compare them with the result generated by the following examples.
You can see how:
- first I create the association between the custom key \samples_dict with the dictionary of samples/midinotes created earlier;
- search, on the basis of the midinote produced by the pattern (and already present in the current environment) for the nearest index for the midi note and store that value in the custom key \index;
- use this index to retrieve the buffer from the samples dictionary;
- derive the rate at which to play the sample by means of a quick calculation associated with the \rate key;
The problem
At this point, I would like to move all the index, buf and rate associations to an eventPrototype so that I can write other Pbindefs more quickly and legibly. These will have the same basic behaviour but will perhaps use other samples to reproduce the sound of other musical instruments.
To do this, I proceeded as described below. I create the protoevent:
(
~myProtoEvent = (
\index: Pfunc({
|e|
e.use({
if( ~degree.() != \rest, {
~midinote.().asArray.collect { |note| ~samples_dict[\midinotes].indexIn(note); }
}, {
0;
});
});
}),
\buf: Pfunc({
|e|
e.use({
~samples_dict[\buffers][ ~index.() ];
});
}),
\rate: Pfunc({
|e|
e.use({
(~midinote.() - ~samples_dict[\midinotes][ ~index.() ]).midiratio;
});
})
);
)
and then I try to use it inside the pattern:
(
Pbindef(\flutes_pbindef_w_protoevent,
\instrument, \hybrid_player,
\samples_dict, ~my_samples[\sso_flute],
\scale, Scale.major,
\root, 0,
\octave, 5,
\degree, Pseq([\rest, [0,4], [0,5]], 1),
\dur, 4,
\amp, 0.4,
\atk, 1,
\dcy, 0.5,
\sus, 0.7,
\rel, 5,
\pan, 0.0,
\out, 0,
\callback, {
|e|
"\nCallback".postln;
e.asSortedArray.do({
|item|
postf("%\t%\n",item[0], item[1]);
});
}
).quant_([4]).play(TempoClock.default, protoEvent:~myProtoEvent);
)
The interesting thing is that, although this pattern is sounding, it has a completely different behaviour to the starting pattern, and if we go to examine the output generated by the callback function, I realise that there are substantial differences between corresponding associations between the events of the first and second patterns.
Listing only the associations that seem to change between the first and second patterns
I see how:
- in the pattern without the protoevent, for buffer, index and rate (the keys we propose to calculate) the correct corresponding data is returned, while for the pattern that uses the protoevent a Pfunc is returned instead in all three cases. why?
I mean, it would also seem logical to me given that in the protoevent it is a function (which will probably be wrapped inside a Pfunc, when it is used later in a pattern), but why is it not evaluated to return what I need?
- I notice how, for both patterns, the \samples_dict key is associated with a dictionary that differs from the one I initially associated with it.
To be more precise, the starting sample dictionary seems to have been enriched with other key-value associations that come from the event environment instead. Why?
Shown here are the values associated with the \samples_dict key for the initial pattern
( 'instrument': hybrid_player, 'midinotes': [ 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81 ], 'buffers': [ Buffer(5, 265447, 1, 44100.0, /home/nicola/Musica/samples/Sonatina Symphonic Orchestra/Samples/Flute/flute-d#3.wav), Buffer(8, 272961, 1, 44100.0, /home/nicola/Musica/samples/Sonatina Symphonic Orchestra/Samples/Flute/flute-f#3.wav), Buffer(0, 273052, 1, 44100.0, /home/nicola/Musica/samples/Sonatina Symphonic Orchestra/Samples/Flute/flute-a3.wav), Buffer(3, 272896, 1, 44100.0, /home/nicola/Musica/samples...etc...
and for the second one:
( 'instrument': hybrid_player, 'buf': a Pfunc, 'rate': a Pfunc, 'midinotes': [ 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81 ], 'index': a Pfunc, 'buffers': [ Buffer(5, 265447, 1, 44100.0, /home/nicola/Musica/samples/Sonatina Symphonic Orchestra/Samples/Flute/flute-d#3.wav), Buffer(8, 272961, 1, 44100.0, /home/nicola/Musica/samples/Sonatina Symphonic Orchestra/Samples/Flute/flute-f#3.wav), Buffer(0, 273052, 1, 44100.0, /home/nicola/Musica/samples/Sonatina Symphonic Orchestra/Samples/Flute/flute-a3.wav), Buf...etc...