Harpsichord synth - individual detune array for each frequency


I am trying to create a harpsichord synth in which the strings for each note would be hit at small onset different times (50-90ms) also with a small detuning between each string freq * {rrand(1-percentDetune, 1+percentDetune)}.dup(4) .

SynthDef(\harpsi, { |outbus = 0, freq = 440, amp = 1.0, percentDetune = 0.003, detuneRand = 0.002, atk=0.001, rel=0.5 |
	var out, env, sig;
	env = EnvGen.ar(Env.perc(atk, rel), doneAction: Done.freeSelf) * amp;
	sig = DelayN.ar(
		Pulse.ar(freq * {rrand(1-percentDetune, 1+percentDetune)}.dup(4), 0.125, 0.75),
		0.090, {rrand(0.050, 0.090)}.dup(4)).sum;
	out =  sig * env;
	Out.ar(outbus, LeakDC.ar(out ! 2));

Pbind(\instrument, \harpsi, \amp, 0.25, \degree, Pseries(0, 1, 8), \dur, 0.25).play;

I manage to have a global detune for each harpsichord key as well as a global difference in the attack onset. How would it be possible to have a unique detuning for each harpsichord key (freq) as well as unique onset difference for each key?

Any other considerations about how to improve the synthesis model of a harpsichord?

Hi John,
you can do everything you have described directly inside a pattern!

s.waitForBoot {
	SynthDef(\harpsi, { 
		var env = Env.perc(\atk.kr(0.01), \rel.kr(1.0)) .ar(2, \gate.kr(1));
		var sig = Pulse.ar(\freq.kr(220));
		var out = sig * env * \amp.kr(0.2);
		OffsetOut.ar(\out.kr(0), out!2)
		\instrument, \harpsi, 
		\amp, 0.25, 
		\degree, Pn(Pseries(0, 1, 8), inf),
		\dur, 1,
		\mtranspose, [0, 0.013, -0.011, 0.023, -0.032], // detune in cents
		\strum, 0.005 // time between each detuned freq - make it big to see the effect

If you want to change the \mtranspose or \strum for each note, you could use a Pfunc or Pwhite, like this:

\mtranspose, Pfunc({   {0.01.rand2}!4   }).trace, 
\strum, Pwhite(0.001, 0.1)

This, isn’t a ‘a global detune for each harpsichord key’, but a global detune for all harpsichords as the randomness is only evaluated when the synthdef is defined.

1 Like

If the values in question are randomized, a fun way to do this entirely within a SynthDef is to use Hasher. Hasher.kr(freq) will give you a value between -1 and 1 that is “random” but produces the same result for the same frequency. I do this a lot to add consistent imperfections to mallet and keyboard instruments.


Thanks a lot!

From my little knowledge on patterns, I think that you can’t pass an array for the key \strum because it won’t multichannel expand, right? Or is there another built-in key to treat onsets like initial phase and not as a global different time between strummed notes?

Regarding the Hasher, is it possible to multichannel expand twice? For instance, here are four different detuned versions of a Pulse.

Pulse.ar(freq * {Hasher.kr(freq).range(1-(percentDetune+Rand(0, detuneRand)), 1+percentDetune+Rand(0, detuneRand))}.dup(4), 0.125, 0.75)

If I use [freq,freq*2] I am going to get this?

Pulse.ar(freq * {Hasher.kr(freq).range(1-(percentDetune+Rand(0, detuneRand)), 1+percentDetune+Rand(0, detuneRand))}.dup(4), 0.125, 0.75),
Pulse.ar(freq*2 * {Hasher.kr(freq).range(1-(percentDetune+Rand(0, detuneRand)), 1+percentDetune+Rand(0, detuneRand))}.dup(4), 0.125, 0.75),

and each octave will have their own singular 4 detuned oscillators?

Hasher will always produce the same result for the same input. To turn it into a full-on seeded random number generator, I iterate Hasher by defining a little function like this:

var state, random;
state = \seed.kr(0);
random = { state = Hasher.kr(state); state };

Now every call to random.() will produce a different random value from -1 to 1, and the sequence will be reproducible but re-randomized for each distinct seed (to which you may supply freq or anything else).

When I’m lazy I do stuff like Hasher.kr(freq + [0, 1, 2, 3]) to get four channels of random values, but be aware that freq and freq + 1 will have values in common, so it’s not completely decorrelated from the input.

There’s also RandSeed but I’ve never gotten it to work. I think all UGens that use randomness draw from a common RGen and RandSeed affects the state of all random ugens, so it will stop reproducing the same results in polyphony or something like that. If I were rewriting the random UGens I would give each one an isolated prng with an optional pseudorandom seed.

1 Like