Physical Modelling Synthesis

Server.default=s=Server.internal;
s.boot;

For a sound synthesis method that truly reflects what goes on in real instruments, you need to take account of the physics of musical instruments. The mathematical equations of acoustics are the basis of physical modelling synthesis. They are tough to build, hard to control, but probably supply the most realistic sounds of the synthesis methods short of the inexpressive method of sampling. 

Because they're based on real instrument mechanics, the control parameters for them are familiar to musicians, though perhaps more from an engineer's point of view- lip tension, bore length, string cross sectional area, bow velocity... Controlling physical models in an intuitive musical way is itself a subject of open research. 

There are a number of techniques in physical modelling, including 

modal synthesis (being a study of the exact modes of vibration of acoustic systems- related to analysis + additive synthesis) 

delay line (waveguide) models (building physical models out of combinations of simple units like delays and filters, which model the propagation of sound waves in a medium) 
 
mass-spring models (based on dynamical equations; elementary masses and springs can be combined into larger models of strings, membranes, acoustic chambers, instrument bodies...) 

We won't be going too deeply into the engineering- it's a hard topic and an open research area. Good physical models can be very computationally expensive, and easy to use real time models are in many cases still out of reach. There are however an increasing number of successful designs, and certainly bound to be more to come. 

To hear a quick example, here's a physical model of a stiff string I built. Parameters such as the Young's modulus, density and radius of a string lead to calculated mode frequencies and damped decay times.

//adapted from 2.18 Vibrations of a Stiff String, p61, Thomas D. Rossing and Neville H. Fletcher (1995) Principles of Vibration and Sound. New York: Springer-Verlag 
(
var modes,modefreqs,modeamps;
var mu,t,e,s,k,f1,l,c,a,beta,beta2,density;
var decaytimefunc;
var material;

material= \nylon; // \steel

//don't know values of E and mu for a nylon/gut string
//so let's try steel

//radius 1 cm
a=0.01;

s=pi*a*a;

//radius of gyration
k=a*0.5;

if (material ==\nylon,{

e=2e+7; 

density=2000; 

},{//steel

e= 2e+11; // 2e+7; //2e+11 steel;

//density p= 7800 kg m-3 
//linear density kg m = p*S

density=7800; 
});

mu=density*s;

t=100000;

c= (t/mu).sqrt;	//speed of sound on wave

l=1.8;	//0.3

f1= c/(2*l);

beta= (a*a/l)*((pi*e/t).sqrt);

beta2=beta*beta;

modes=10;

modefreqs= Array.fill(modes,{arg i; 
var n,fr;
n=i+1;

fr=n*f1*(1+beta+beta2+(n*n*pi*pi*beta2*0.125));

if(fr>21000, {fr=21000}); //no aliasing

fr
});

decaytimefunc= {arg freq;
var t1,t2,t3;
var m,calc,e1dive2;

//VS p 50 2.13.1 air damping

m=(a*0.5)*((2*pi*freq/(1.5e-5)).sqrt);

calc= 2*m*m/((2*(2.sqrt)*m)+1);

t1= (density/(2*pi*1.2*freq))*calc;

e1dive2=0.01; //a guess!

t2= e1dive2/(pi*freq);

//leave G as 1
t3= 1.0/(8*mu*l*freq*freq*1);

1/((1/t1)+(1/t2)+(1/t3))
};

modeamps=Array.fill(modes,{arg i; decaytimefunc.value(modefreqs.at(i))});

modefreqs.postln;
modeamps.postln;

{
var output;
//EnvGen.ar(Env.new([0.001,1.0,0.9,0.001],[0.001,0.01,0.3],'exponential'),WhiteNoise.ar)
//could slightly vary amps and phases with each strike?

output=EnvGen.ar(
Env.new([0,1,1,0],[0,10,0]),doneAction:2)*
//slight initial shape favouring lower harmonics- 1.0*((modes-i)/modes)
Mix.fill(modes,{arg i; XLine.ar(1.0,modeamps.at(i),10.0)*SinOsc.ar(modefreqs.at(i),0,1.0/modes)});

Pan2.ar(output,0)
}.play;

)

Most physical models tend to follow a paradigm of a non-linear exciter, and a linear resonantor. 

exciter- human lips in brass instruments, a reed in woodwind, the bow/plectrum/quill/hammer/fingers for strings, the beater/stick/brush/mallet/hands for percussion

resonator- the bore of wind instruments, the string of a string instrument, the membrane of a drum.  

So the exciter is the energy source of the sound, whilst the resonantor is typically an instrument body that propagates the sound. The resonator is coupled to the air which transmits sound, but in most physical models we imagine a pickup microphone on the body and miss out the voyage in air of the sound (or we add separate reverberation models and the like).  

The following is a piano sound by James McCartney that shows off how a short strike sound can be passed through filters to make a richer emulation of a real acoustic event. First you'll hear the piano hammer sound, then the rich tone. 

(
// this shows the building of the piano excitation function used below
{
	var strike, env, noise;
	strike = Impulse.ar(0.01);
	env = Decay2.ar(strike, 0.008, 0.04);
	noise = LFNoise2.ar(3000, env);
	[strike, K2A.ar(env), noise]
}.plot(0.03); //.scope
)

(
// hear the energy impulse alone without any comb resonation
{
	var strike, env, noise;
	strike = Impulse.ar(0.01);
	env = Decay2.ar(strike, 0.008, 0.04);
	noise = LFNoise2.ar(3000, env);
	10*noise
}.scope
)

//single strike with comb resonation 

(
{
	var strike, env, noise, pitch, delayTime, detune;
	strike = Impulse.ar(0.01);
	env = Decay2.ar(strike, 0.008, 0.04);
	
	pitch = (36 + 54.rand); 
		
	Pan2.ar(
			// array of 3 strings per note
			Mix.ar(Array.fill(3, { arg i;
				// detune strings, calculate delay time :
				detune = #[-0.05, 0, 0.04].at(i);
				delayTime = 1 / (pitch + detune).midicps;
				// each string gets own exciter :
				noise = LFNoise2.ar(3000, env); // 3000 Hz was chosen by ear..
				CombL.ar(noise,		// used as a string resonator
					delayTime, 		// max delay time
					delayTime,			// actual delay time
					6) 				// decay time of string
			})),
			(pitch - 36)/27 - 1 // pan position: lo notes left, hi notes right
		)
	
}.scope
)

(
// synthetic piano patch (James McCartney)
var n;
n = 6;	// number of keys playing
play({
	Mix.ar(Array.fill(n, {	// mix an array of notes
		var delayTime, pitch, detune, strike, hammerEnv, hammer;
	
		// calculate delay based on a random note
		pitch = (36 + 54.rand); 
		strike = Impulse.ar(0.1+0.4.rand, 2pi.rand, 0.1); // random period for each key
		hammerEnv = Decay2.ar(strike, 0.008, 0.04); // excitation envelope
		Pan2.ar(
			// array of 3 strings per note
			Mix.ar(Array.fill(3, { arg i;
				// detune strings, calculate delay time :
				detune = #[-0.05, 0, 0.04].at(i);
				delayTime = 1 / (pitch + detune).midicps;
				// each string gets own exciter :
				hammer = LFNoise2.ar(3000, hammerEnv); // 3000 Hz was chosen by ear..
				CombL.ar(hammer,		// used as a string resonator
					delayTime, 		// max delay time
					delayTime,			// actual delay time
					6) 				// decay time of string
			})),
			(pitch - 36)/27 - 1 // pan position: lo notes left, hi notes right
		)
	}))
})
)

A simple form of physical modelling sound synthesis (related to what we've just heard above) is Karplus-Strong synthesis. 

You start with a noise source in a delay line of length based on the pitch of note you would like. Then you successively filter the delay line until all the sound has decayed. You get a periodic sound because the loop (the delayline) is of fixed length. 

Here's a simple version:

(
 
{
var freq,time, ex, delay, filter, local;
 
freq= 100;
time= freq.reciprocal;

ex= WhiteNoise.ar(EnvGen.kr(Env([1.0,1.0,0.0,0.0], [time,0,100])));

local= LocalIn.ar(1);

filter= LPZ1.ar(ex+local); //apply filter

delay= DelayN.ar(filter, time, time);  

LocalOut.ar(delay*0.95); 

Out.ar(0, Pan2.ar(filter,0.0))
}.play

)

I can modulate the length of the delay line to make a vibrato! 

 
(
 
{
var freq,time, ex, delay, filter, local;
 
freq= 440;
time= freq.reciprocal;

ex= WhiteNoise.ar(EnvGen.kr(Env([1.0,1.0,0.0,0.0], [time,0,100])));

freq= SinOsc.ar(6, 0, 10, freq);
time= freq.reciprocal;  

local= LocalIn.ar(1);

filter= LPZ1.ar(ex+local); //apply filter

delay= DelayN.ar(filter, time, time);  

LocalOut.ar(delay*0.99); 

Out.ar(0, Pan2.ar(filter,0.0))
}.play

)

Contributions from Thor Magnusson giving an alternative viewpoint:

// we use a noise ugen to generate a burst
(
{  
 var burstEnv, att = 0, dec = 0.001; //Variable declarations 
 burstEnv = EnvGen.kr(Env.perc(att, dec), gate: Impulse.kr(1)); //envelope 
 PinkNoise.ar(burstEnv); //Noise, amp controlled by burstEnv 
}.play  
)

// but then we use Comb delay to create the delay line that creates the tone

// let's create a synthdef using Karplus-Strong
SynthDef(\ks_guitar, { arg note, pan, rand, delayTime, noiseType=1;
	var x, y, env;
	env = Env.new(#[1, 1, 0],#[2, 0.001]);
	// A simple exciter x, with some randomness.
	x = Decay.ar(Impulse.ar(0, 0, rand), 0.1+rand, WhiteNoise.ar); 
 	x = CombL.ar(x, 0.05, note.reciprocal, delayTime, EnvGen.ar(env, doneAction:2)); 
	x = Pan2.ar(x, pan);
	Out.ar(0, LeakDC.ar(x));
}).store;

// and play the synthdef
(
{
	20.do({
		Synth(\ks_guitar, [\note, 220+(400.rand), 
					\pan, 1.0.rand2, 
					\rand, 0.1+0.1.rand, 
					\delayTime, 2+1.0.rand]);
					
	   (1.0.rand + 0.5).wait;
	});
}.fork
)

// here using patterns
a = Pdef(\kspattern, 
		Pbind(\instrument, \ks_guitar, // using our sine synthdef
					\note, Pseq.new([60, 61, 63, 66], inf).midicps, // freq arg
					\dur, Pseq.new([0.25, 0.5, 0.25, 1], inf),  // dur arg
					\rand, Prand.new([0.2, 0.15, 0.15, 0.11], inf),  // dur arg
					\pan, 2.0.rand-1,
					\delayTime, 2+1.0.rand;  // envdur arg

		)
).play;

// compare using whitenoise and pinknoise as an exciter:

// whitenoise

(
{  
 var burstEnv, att = 0, dec = 0.001; 
 var burst, delayTime, delayDecay = 0.5; 
 var midiPitch = 69; // A 440 
 delayTime = midiPitch.midicps.reciprocal; 
 burstEnv = EnvGen.kr(Env.perc(att, dec), gate: Impulse.kr(1/delayDecay));  
 burst = WhiteNoise.ar(burstEnv);  
 CombL.ar(burst, delayTime, delayTime, delayDecay, add: burst);  
}.play  
) 

// pinknoise
(
{  
 var burstEnv, att = 0, dec = 0.001; 
 var burst, delayTime, delayDecay = 0.5; 
 var midiPitch = 69; // A 440 
 delayTime = midiPitch.midicps.reciprocal; 
 burstEnv = EnvGen.kr(Env.perc(att, dec), gate: Impulse.kr(1/delayDecay));  
 burst = PinkNoise.ar(burstEnv);  
 CombL.ar(burst, delayTime, delayTime, delayDecay, add: burst);  
}.play  
) 

// Note that delayTime is controlling the pitch here. The delay time is reciprocal to the pitch. // 1/100th of a sec is 100Hz, 1/400th of a sec is 400Hz.

(
SynthDef(\KSpluck, { arg midiPitch = 69, delayDecay = 1.0;
	var burstEnv, att = 0, dec = 0.001;
	var signalOut, delayTime;

	delayTime = [midiPitch, midiPitch + 12].midicps.reciprocal;
	burstEnv = EnvGen.kr(Env.perc(att, dec)); 
	signalOut = PinkNoise.ar(burstEnv); 
	signalOut = CombL.ar(signalOut, delayTime, delayTime, delayDecay, add: signalOut); 
	DetectSilence.ar(signalOut, doneAction:2);
	Out.ar(0, signalOut)
	}
).store;
)

(
//Then run this playback task
r = Task({
	{Synth(\KSpluck, 
		[
		\midiPitch, rrand(30, 90), //Choose a pitch
		\delayDecay, rrand(0.1, 3.0) //Choose duration
		]);
		//Choose a wait time before next event
		[0.125, 0.125, 0.25].choose.wait;
	}.loop;
}).play
)

Further interesting sources:

Some useful filter UGens for modelling instrument bodies and oscillators for sources:
[Klank]
[Ringz]	//single resonating component of a Klank resonator bank
[Resonz]
[Decay]
[Formant]
[Formlet]

Further examples:

[Spring]
[Ball]
[TBall]

STK Library:

http://sourceforge.net/projects/sc3-plugins/

PMSC Library: (great fun!)

http://swiki.hfbk-hamburg.de:8888/MusicTechnology/802

// Paul Lansky ported the STK physical modeling kit by Perry Cook and Gary Scavone
// for SuperCollider. It can be found on his website. 
// Here are two examples using a mandolin and a violin bow

// let's try the mandolin
{StkMandolin.ar(mul:3)}.play  

(SynthDef(\mando, {arg freq, bodysize, pickposition, stringdamping, stringdetune, aftertouch;
	var signal;
	signal = StkMandolin.ar(freq, bodysize, pickposition, stringdamping, stringdetune, aftertouch);
	Line.kr(1,1,2,doneAction:2); //force deallocation
	Out.ar(0, signal);
}).load(s)
)

(
Synth(\mando, [	\freq, rrand(300, 600), 
					\bodysize, rrand(22, 64), 
					\pickposition, rrand(22, 88),
					\stringdamping, rrand(44, 80),
					\stringdetune, rrand(1, 10),
					\aftertouch, rrand(44, 80)
					]);
)

(
Task({
	100.do({
		Synth(\mando, [	\freq, rrand(300, 600), 
					\bodysize, rrand(22, 64), 
					\pickposition, rrand(22, 88),
					\stringdamping, rrand(44, 80),
					\stringdetune, rrand(1, 10),
					\aftertouch, rrand(44, 80)
					]);
		1.wait;
	})
}).start;
)

// and the StkBowed UGen:

(
SynthDef(\bow, {arg freq, bowpressure = 64, bowposition = 64, vibfreq=64, vibgain=64, loudness=64;
	var signal;
	signal = StkBowed.ar(freq, bowpressure, bowposition, vibfreq, vibgain, loudness);
	signal = signal * EnvGen.ar(Env.linen, doneAction:2);
	Out.ar([0,1], signal);
}).load(s)
)

(
Task({
	100.do({
		Synth(\bow, [		\freq, rrand(200, 440), 
							\bowpressure, rrand(22, 64), 
							\bowposition, rrand(22, 64),
							\vibfreq, rrand(22, 44),
							\vibgain, rrand(22, 44)
					]);
		1.wait;
	})
}).start;
)