-
Notifications
You must be signed in to change notification settings - Fork 6
/
Tambura.dsp
132 lines (109 loc) · 6.45 KB
/
Tambura.dsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
declare name "Tambura";
declare description "Pseudo physical model of an Indian Tambura/Tanpura";
declare author "Oli Larkin ([email protected])";
declare copyright "Oliver Larkin";
declare version "1.0";
declare licence "GPL";
//TODO
// - pitch env doesn't get triggered by autoplucker
// - autoplucker fixed to 4 strings
import("stdfaust.lib");
line (value, time) = state~(_,_):!,_
with {
state (t, c) = nt, ba.if (nt <= 0, value, c+(value - c) / nt)
with {
nt = ba.if( value != value', samples, t-1);
samples = time*ma.SR/1000.0;
};
};
dtmax = 4096;
//tunings of the four strings, ratios of f0
ratios(0) = 1.5;
ratios(1) = 2.;
ratios(2) = 2.01;
ratios(3) = 1.;
NStrings = 4;
sm = si.smooth(ba.tau2pole(0.05)); //50 ms smoothing
//ratios(i) = hslider("/h:main/ratio%1i [style:knob]", 1., 0.1, 2., 0.001);
pluck(i) = button("/h:trigger/pluck%1i"); // buttons for manual plucking
pluckrate = hslider("/h:trigger/auto pluck rate [style:knob][unit:hz]", 0.1, 0.0, 0.5, 0.001); // automatic plucking rate (Hz)
enableautoplucker = checkbox("/h:trigger/enable auto pluck"); // enable automatic plucking
f0 = hslider("/h:main/[1]sa [style:knob]", 36, 24, 72, 1) : sm : ba.midikey2hz; // the base pitch of the drone
t60 = hslider("/h:main/[2]decay_time [style:knob][unit:s]", 10, 0, 100, 0.1) : sm; // how long the strings decay
damp = 1. - hslider("/h:main/[3]high_freq_loss [style:knob]", 0, 0, 1., 0.01) : sm; // string brightness
fdetune = hslider("/h:main/[4]harmonic_motion [style:knob][scale:exp]", 0.001, 0., 1, 0.0001) : *(0.2) : sm; // controls the detuning of parallel waveguides that mimics harmonic motion of the tambura
coupling = hslider("/h:main/[5]sympathetic_coupling [style:knob]", 0.1, 0., 1., 0.0001) : sm; // level of sympathetic coupling between strings
jw = hslider("/h:main/[6]jawari [style:knob]", 0, 0, 1, 0.001) : *(0.1) : sm; // creates the buzzing / jawari effect
spread = hslider("/h:main/[7]string_spread [style:knob]", 1., 0., 1., 0.01) : sm; // stereo spread of strings
tscale = hslider("/h:main/[8]tune_scale [style:knob]", 1, 0.9, 1.1, 0.001); //
descale = hslider("/h:main/[9]decay_scale [style:knob]", 1, 0.1, 1., 0.001); //
//dascale = hslider("/h:main/[10]damp_scale [style:knob]", 1, 0.5, 2, 0.01); //
ptype = hslider("/h:pick/[1]material [style:knob]", 0.13, 0.0, 1., 0.01) : sm; // crossfades between pink noise and DC excitation
pattack = hslider("/h:pick/[2]attack_time [style:knob][scale:exp]", 0.07, 0, 0.5, 0.01); // attack time of pluck envelope, 0 to 0.5 times f0 wavelength
ptime = hslider("/h:pick/[3]decay_time [style:knob]", 1., 1, 100., 0.01); // decay time (1 to 10 times f0 wavelength)
ppos = hslider("/h:pick/[4]position [style:knob]", 0.25, 0.01, 0.5, 0.01); // pick position (ratio of f0 wavelength)
pbend = hslider("/h:pick/[5]bend_depth [style:knob][unit:st]", 3, 0., 12., 0.01); // pick bend depth in semitones
pbendtime = hslider("/h:pick/[6]bend_time [style:knob][unit:ms]", 10., 1, 200., 1); // pick bend time (1 to 200 ms)
vol = hslider("volume [unit:dB]", 0, -36, +4, 0.1) : ba.db2linear : sm; // master volume
// s = string index
// c = comb filter index (of 9 comb filters in risset string)
tambura(NStrings) = ( couplingmatrix(NStrings), par(s, NStrings, excitation(s)) : ro.interleave(NStrings, 2) : par(s, NStrings, string(s, pluck(s))) ) // string itself with excitation + fbk as input
~ par(s, NStrings, (!,_)) // feedback only the right waveguide
: par(s, NStrings, (+:pan(s)) // add left/right waveguides and pan
) :> _,_ //stereo output
with {
couplingmatrix(NStrings) =
par(s, NStrings, *(coupling) : couplingfilter) // coupling filters
<: par(s, NStrings, unsel(NStrings, s) :> _ ) // unsel makes sure the feedback is disconnected
with {
unsel(NStrings,s) = par(j, NStrings, U(s,j))
with {
U(s,s)=!;
U(s,j)=_;
};
//couplingfilter = component("bridgeIR.dsp");
couplingfilter = fi.highshelf(1,-100,5000) : fi.peak_eq(14, 2500, 400) : fi.peak_eq(20, 7500, 650); // EQ to simulate bridge response
};
//pan(s) = _ <: *(1-v), *(v)
pan(s) = _ <: *((1-v) : sqrt), *((v) : sqrt)
with {
spreadScale = (1/(NStrings-1));
v = 0.5 + ((spreadScale * s) - 0.5) * spread;
};
// excitation(s) = _;
excitation(s, trig) = input * ampenv : pickposfilter
with {
wl = (ma.SR/(f0 * ratios(s))); // wavelength of f0 in samples
dur = (ptime * wl) / (ma.SR/1000.); // duration of the pluck in ms
ampenv = trig * line(1. - trig, dur) : si.lag_ud(wl * pattack * (1/ma.SR), 0.005);
amprand = abs(no.noise) : ba.latch(trig) *(0.25) + (0.75);
posrand = abs(no.noise) : ba.latch(trig) *(0.2);
input = 1., no.pink_noise : si.interpolate(ptype); // crossfade between DC and pink noise excitation source
pickposfilter = fi.ffcombfilter(dtmax, ((ppos + posrand) * wl), -1); // simulation of different pluck positions
};
string(s, trig) = _, _ <: +, !,_ : rissetstring(_, s, 1., 1., 1.), rissetstring(_, s, tscale, descale, 1.) // dual risset strings for decoupled feedback
with {
rissetstring(x, s, ts, des, das) = _ <: par(c, 9, stringloop(x, s, c, ts, das)) :> _ : fi.dcblocker *(0.01); // 9 detuned delay line resonators in parallel
stringloop(x, s, c, ts, des, das) = (+ : delay) ~ ((dampingfilter : nlfm) * fbk) // waveguide string with damping filter and non linear apf for jawari effect
with {
//delay = de.fdelay1a(dtmax, dtsamples, x); // allpass interpolation has better HF response
delay = de.fdelaylti(2, dtmax, dtsamples, x); // lagrange interpolation glitches less with pitch envelope
pitchenv = trig * line(1. - trig, pbendtime) <: * : *(pbend);
thisf0 = ba.pianokey2hz( ba.hz2pianokey((f0 * ratios(s)) + ((c-4) * fdetune) + pitchenv) ) * ts;
dtsamples = (ma.SR/thisf0) - 2;
fbk = pow(0.001, 1.0/(thisf0*(t60 * descale)));
dampingfilter(x) = (h0 * x' + h1*(x+x''))
with {
d = das * damp;
h0 = (1. + d)/2;
h1 = (1. - d)/4;
};
nlfm(x) = x <: fi.allpassnn(1,(par(i,1,jw * ma.PI * x)));
};
};
};
autoplucker= phasor(pluckrate) <: <(0.25), >(0.25) & <(0.5), >(0.5) & <(0.75), >(0.75) & <(1) : par(s, NStrings, *(enableautoplucker))
with {
phasor(freq) = (freq/float(ma.SR) : (+ : ma.decimal) ~ _);
};
process = (par(s, NStrings, pluck(s)), autoplucker) :> tambura(NStrings) : *(vol), *(vol);