Skip to content

Instantly share code, notes, and snippets.

@SpotlightKid
Created August 6, 2024 11:26
Show Gist options
  • Save SpotlightKid/58a0ae69c94c2a2d54d363206ab82d07 to your computer and use it in GitHub Desktop.
Save SpotlightKid/58a0ae69c94c2a2d54d363206ab82d07 to your computer and use it in GitHub Desktop.
A simple physical model of a guitar-like plucked string implemented in FAUST (Work in progress!)
declare name "PM String";
declare version "0.1";
declare author "Christopher Arndt";
declare license "MIT License";
declare description "A simple physical model of a guitar-like plucked string";
// Create a JACK/Qt app with:
//
// faust2jaqt -nvoices 6 -midi pmstring.dsp
import("stdfaust.lib");
declare options "[midi:on][nvoices:6]";
max_f = 2000;
max_fc = 12000;
key = hslider("key [hidden:1]",60,36,96,1);
gain = hslider("gain [hidden:1]", 1.0, 0.0, 1.0, 0.001);
gate = button("gate [hidden:1]");
key2freq(key) = 440.0 * pow(2.0, (key - 69.0) / 12.0);
bend = hslider("bend [midi:pitchwheel][hidden:1]", 0, -2, 2, 0.01) : ba.semi2ratio : si.polySmooth(gate, 0.999, 2);
volume = hslider("[0] Volume [unit:dB]", -6, -90, 12, 0.1) : ba.db2linear : si.smoo;
// velocity_gain implements adjustable velocity sensitivity.
// At maximum amount (0..1) and maximum velocity (0..1), the value is 1.
velocity_gain(amount, velocity) = (1 - amount) + amount * velocity;
gain_vel_amount = hslider("[1] Vel. > Gain", 100, 0, 100, 1) / 100;
bridgeFilter(brightness, absorption, x) = rho * (h0 * x' + h1 * (x + x''))
with{
freq = 320;
t60 = (1 - absorption) * 20;
h0 = (1.0 + brightness) / 2;
h1 = (1.0 - brightness) / 4;
rho = pow(0.001, 1.0 / (freq * t60));
};
guitarBridge(brightness, absorption) = pm.rTermination(pm.basicBlock, reflectance)
with{
reflectance = -bridgeFilter(brightness, absorption);
};
guitarNut(brightness, absorption) = pm.lTermination(-bridgeFilter(brightness, absorption), pm.basicBlock);
openStringPickUp(length, stiffness, pluckPosition, pickupPosition, excitation) = strChain
with{
dispersionFilters = par(i, 2, si.smooth(stiffness)),_;
maxStringLength = pm.maxLength;
nti = length * pluckPosition; // top to excitation length
nto = nti * pickupPosition; // nuts to pickup length
oti = nti * (1 - pickupPosition); // pickup to excitation length
itb = length * (1 - pluckPosition); // pickup to bottom length
strChain = pm.chain(
pm.stringSegment(maxStringLength,nto)
: pm.out
: pm.stringSegment(maxStringLength, oti)
: pm.in(excitation)
: dispersionFilters :
pm.stringSegment(maxStringLength,itb)
);
};
plektrum(key, gain, gate) = no.noise * env : lowpass : highpass
with {
pluck_decay = hslider("[2] Pluck Decay (s) [unit:s]", 0.01, 0.01, 0.2, 0.001);
fc = hslider("[3] LP Filter Freq [scale:log]", 250, 40, 5000, 0.1) : si.smoo;
foffset = hslider("[4] HP Freq Offset (cents) [unit:ct]", 0, -4800, 4800, 1) : si.smoo;
//fq = hslider("[4] Resonance", 2.0, 0.5, 5.0, 0.1);
fvel = hslider("[5] Vel>Freq (cents) [unit:ct]", 1200, 0, 6000, 1);
fkey = hslider("[6] Key>Freq", 66, 0, 100, 1) / 100;
freq = key2freq(key);
pluck_attack = 0.002 * pow((1 - freq / max_f), 2);
env = en.ar(pluck_attack, pluck_decay, gate);
cutoff = min(max_fc, fc * ba.semi2ratio((key - 60) * fkey) * ba.cent2ratio(fvel * gain));
lowpass = fi.lowpass(2, cutoff);
highpass = fi.highpass(2, cutoff * ba.cent2ratio(foffset));
};
string(key, freq, gain, gate, exitation) = pm.endChain(string_chain)
with {
pickup_position = hslider("[0] Pickup position", .8, 0, 1, 0.01);
pluck_position = hslider("[1] Pluck position (modwheel) [midi:ctrl 1]", .8, 0, 1, 0.01);
//length_adjust = hslider("[2] Length adjustment", -0.152, -0.2, 0.2, 0.001);
stiffness = hslider("[3] String stiffness", .2, 0, 1, 0.01);
stiffness_adjust = hslider("[4] Key>Stiffness", -72, -100, 100, 0.1) / 100;
brightness = hslider("[5] Brightness", 0.4, 0, 1.0, 0.01);
absorption = hslider("[6] Absorption", 0.5, 0, 0.99, 0.01);
nonlinearity = hslider("[7] Vel>Nonlinearity", 0.0, 0, 0.5, 0.001);
length_adjust = -0.182; // for physmodel chain length = 4: -0.114; 5: -0.152, 6: 0.182
tuned_stiffness = max(0.0, min(1.0, stiffness + stiffness_adjust * (key - 60) / 64));
length = pm.f2l(freq);
tuned_length = length + length_adjust;
string_chain = pm.chain(
guitarNut(brightness, absorption)
: openStringPickUp(tuned_length, tuned_stiffness, pluck_position, pickup_position, exitation)
: pm.allpassNL(nonlinearity * gain)
: pm.out
: guitarBridge(brightness, absorption)
);
};
string_group(x) = hgroup("String", x);
phys_group(x) = string_group(vgroup("[1] Physical parameters", x));
pluck_group(x) = string_group(vgroup("[2] Pluck", x));
adsr_group(x) = string_group(hgroup("[3] ADSR Envelope", x));
attack = adsr_group(vslider("[1] Attack", 0.0, 0.0, 1.0, 0.001));
decay = adsr_group(vslider("[2] Decay", 0.0, 0.0, 5.0, 0.01));
sustain = adsr_group(vslider("[3] Sustain", 100, 0, 100, 1) / 100);
release = adsr_group(vslider("[4] Release", 0.1, 0.0, 5.0, 0.01));
amp_env = en.adsr(attack, decay, sustain, release, gate);
pluck = pluck_group(plektrum)(key, gain, gate) * velocity_gain(gain_vel_amount, gain);
process = phys_group(string)(key, key2freq(key) * bend, gain, gate, pluck) * amp_env * volume;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment