Az eredeti Karplus-Strong hurok a késleltetett minta és az azt megelőző minta átlagát csatolja vissza:
\( y[n] = x[n] + g\cdot\frac{y[n-N] + y[n-N-1]}{2} \)
Az átviteli függvény: \( H(z)=\dfrac{1}{1-g\,z^{-N}\,\frac{1+z^{-1}}{2}} \). Az átlagoló szűrő egy egyszerű aluláteresztő: DC-n erősítése 1, a Nyquist-frekvencián 0 – ezért halnak el gyorsabban a magas felharmonikusok.
Az átlagoló szűrő amplitúdó- és fázismenete (\(\omega=2\pi f/f_s\)): \( |H_{lp}(e^{j\omega})|=\cos\tfrac{\omega}{2},\; \angle H_{lp}=-\tfrac{\omega}{2} \). A fázis lineáris, ezért a késleltetés minden frekvencián ugyanannyi.
Az átlagoló szűrő minden frekvencián pontosan fél mintányi késleltetést ad, ezáltal a hurok tényleges hossza \(N+0{,}5\) minta (és a felharmonikusok pontosan egész számú többszörösei az alapnak). A pontos hangolás:
\( N + \tfrac12 = \dfrac{f_s}{f}\;\Rightarrow\; N=\dfrac{f_s}{f}-\tfrac12 \)
432 Hz-nél: 44100/432 − 0,5 = 101,58 minta (44100/432 = 102,083).
Súlyozott szűrő, \((1-S)+S\,z^{-1}\), esetén a késleltetés kis frekvencián \(S\) minta, ezért \(N=f_s/f-S\). A kódokban ez a damp paraméter (\(S=\tfrac12\) az eredeti átlagolás).
A késleltetés ritkán egész szám. Két bevett megoldás:
A T60 az az idő, amely alatt a hang amplitúdója 60 dB-lel, azaz az ezredrészére csökken. Minden periódus után a jel \(g\)-szeresére csökken. Egy másodpercben \(f\) periódus van, tehát a T60 idejére:
\( g^{\,f\cdot T_{60}}=0{,}001\;\Rightarrow\; g=10^{-3/(f\cdot T_{60})} \)
A hurokszűrő miatt a \(k\)-adik felharmonikus (\(kf\)) minden körben \(g\cos(\pi k f/f_s)\)-szeresére csökken (\(S=\tfrac12\) esetén), így lecsengése:
\( T_{60,k}=\dfrac{-3}{f\,\log_{10}\!\big(g\cos(\pi k f/f_s)\big)} \)
Példa: 432 Hz, \(T_{60}=3\) s, \(f_s=44100\) Hz esetén a 10. felharmonikus (4320 Hz) kb. 0,3 s alatt elhal – tízszer gyorsabban az alaphangnál. A beállított \(T_{60}\) tehát az alaphangra (közelítőleg) érvényes.
A zaj a húr kezdeti alakját jelöli ki. A klasszikus algoritmusban hossza pontosan egy periódus (\(N\) minta): ekkor a hurok egyszer, teljesen betelik. Rövidebb impulzusnál a hurok csak részben töltődik fel, hosszabbnál a visszacsatolt példányok egymásra rakódnak. Minden pengetés más zajmintát használ, ezért minden hang kicsit más. A zaj átlaga (egyenáramú komponens) a hurokban csak \(g\)-vel csillapodik, ezért érdemes átlagmentesíteni.
A húr kis kitéréseit a hullámegyenlet írja le: \( \dfrac{\partial^2 y}{\partial t^2}=c^2\dfrac{\partial^2 y}{\partial x^2} \), ahol a hullámsebesség \( c=\sqrt{T/\mu} \) (\(T\): húrfeszítés, \(\mu\): hosszegységre jutó tömeg). A megoldás két ellentétes irányba haladó hullám összege (d'Alembert); ezeket egy-egy késleltetővonal modellezi – ez a digitális hullámvezető (Smith). A rögzített végeken a hullám előjelet vált és visszaverődik; a veszteségek a hurokszűrőbe kerülnek. A hullám oda-vissza útja \(2L/c\) ideig tart, ez a periódus:
\( f=\dfrac{c}{2L}=\dfrac{1}{2L}\sqrt{\dfrac{T}{\mu}},\qquad N+\tfrac12=\dfrac{f_s}{f}=\dfrac{2L f_s}{c} \)
A Karplus–Strong hurok a két irányt egyetlen körben egyesíti (a két visszaverődés előjelváltása kiejti egymást), így a hurokhossz a hullám teljes körülfordulási ideje. Nagyobb feszítés vagy rövidebb húr magasabb, nehezebb húr mélyebb hangot ad.
Állítsd be a paramétereket, majd pengesd meg a húrt.
DSP környezetben történő megvalósításhoz szükséges kódstruktúrák.
// Puffer létrehozása a hurok késleltetéséhez (48kHz-es mintavételezéshez igazítva)
Delay dl(48000);
// Második puffer a pengetési pozíció (fésűszűrő) késleltetéséhez
Delay bd(24000);
// Memóriaváltozó az előző mintához (hurokszűrőhöz)
History y1(0);
// Memóriaváltozó a dinamika szűréshez
History dyn_mem(0);
// Paraméterek definiálása határokkal és alapértelmezett értékekkel
Param freq(432, min=20, max=20000); // Hz (alapértelmezett 432 Hz)
Param damp(0.5, min=0, max=0.5); // 0.0 - 0.5 (0 = fényes, 0.5 = az eredeti átlagoló szűrő)
Param t60(3, min=0.001); // Lecsengési idő másodpercben
Param beta(0.1, min=0, max=0.99); // Pengetési pozíció a húrhossz törtrészeként (0 = ki)
Param dyn(0.5, min=0, max=1); // Dinamika (0.0 - 1.0)
// Rövid zajimpulzus (bemeneti exciter) fogadása
burst = in1;
// Dinamika szűrő: aluláteresztő szűrő alkalmazása a gerjesztésen a leütési erő alapján
R_L = 0.95 * (1 - dyn);
exc_filtered = (1 - R_L) * burst + R_L * dyn_mem;
dyn_mem = exc_filtered;
// Feedback (visszacsatolási együttható) kiszámítása a T60 képlet alapján (frekvenciafüggő)
g = pow(0.001, 1 / (freq * t60));
// A késleltetési idő kiszámítása a hangmagasság és a szűrő fáziskésésének kompenzációjával
samps = max(1, samplerate / freq - damp);
d = dl.read(samps, interp="cubic"); // Olvasás köbös interpolációval a törtrészes késleltetéshez
// Kétpontos FIR aluláteresztő szűrő kiszámítása a hurokban
filtered = (1 - damp) * d + damp * y1;
y1 = d;
// Pengetési pozíció (fésűszűrő) megvalósítása a gerjesztésen: H = 1 - z^-M
M = max(1, floor(beta * samplerate / freq + 0.5));
pm = bd.read(M);
bd.write(exc_filtered);
exc = beta > 0 ? exc_filtered - pm : exc_filtered;
// A hurok frissítése: a gerjesztés és a visszacsatolt, szűrt jel összege beíródik a delaybe
dl.write(exc + g * filtered);
out1 = d; // Hang kiadása a kimenetre
in1) kb. egy periódus hosszú zajimpulzus kell (1000/freq ms), pl. [noise~] × [line~], a [line~]-nek 1, 0 2.3 üzenetet küldve 432 Hz-nél. A paramétereket üzenetként küldhetjük a gen~-nek (pl. freq 220, t60 2).SynthDef(\ksManual, { |out=0, freq=432, t60=3, damp=0.5, amp=0.4, beta=0.1, dyn=0.5, t_trig=1|
// Visszacsatolási tényező (g) kiszámítása a T60 lecsengési időből
var g = 0.001.pow(1 / (freq * t60));
// Alap gerjesztő jel: fehérzaj impulzus, hossza pontosan egy periódus (betölti a hurkot)
var rawExc = WhiteNoise.ar * Trig1.ar(t_trig, freq.reciprocal);
// Dinamika szűrő: a zaj sávszélességének szűrése a dinamika paraméter alapján
var rL = 0.95 * (1 - dyn);
var exc0 = OnePole.ar(rawExc, rL);
// Pengetési pozíció fésűszűrője (kivonjuk a késleltetett zajt, ha beta > 0)
var exc = exc0 - (DelayN.ar(exc0, 0.05, (beta * freq.reciprocal).max(ControlDur.ir)) * (beta > 0));
// Helyi visszacsatolás olvasása a hurokból
var fb = LocalIn.ar(1);
// Késleltetővonal a hurokban, kompenzálva a vezérlőblokk és a hurokszűrő (damp minta) késését
var del = DelayC.ar(exc + fb, 0.1, (freq.reciprocal - ControlDur.ir - (damp * SampleDur.ir)).max(0.0001));
// Kétpontos átlagoló (FIR) aluláteresztő a hurokban: (1-damp)*x[n] + damp*x[n-1]
var lp = (del * (1 - damp)) + (Delay1.ar(del) * damp);
// Visszacsatolás írása a hurok végén a csillapított és skálázott jellel
LocalOut.ar(lp * g);
// Automatikus kilépés, ha a hang lecsengett és elhalkult
DetectSilence.ar(del, 0.0001, 0.5, doneAction: 2);
// Sztereó kimenetre küldés a megadott amplitúdóval
Out.ar(out, (del * amp) ! 2);
}).add;
// Előbb indítsd el a szervert: s.boot;
// A SynthDef.add aszinkron: az alábbi sort a SynthDef hozzáadása után, külön futtasd.
Synth(\ksManual, [\freq, 432, \t60, 3, \damp, 0.5, \beta, 0.1, \dyn, 0.5]);
s.options.blockSize = 16; s.reboot;Az első \(N\) kimeneti minta véletlenszám, utána \( y[n]=\tfrac12\big(y[n-N]+y[n-(N+1)]\big) \). A zajkezelés és a szűrés kombinációja határozza meg a hangszín gazdagságát. (A hurok tényleges hossza \(N+\tfrac12\).)
Dob-változat: ha az átlag előjelét véletlenszerűen megfordítjuk (a + előjel valószínűsége \(b\)), \(b=1\)-nél húrhangot, \(b=\tfrac12\)-nél dobszerű, zajos hangot kapunk; \(b=0\)-nál a hang egy oktávval mélyebb lesz, csak páratlan felharmonikusokkal.
Az Extended Karplus–Strong (EKS) (Jaffe és Smith, 1983) a hurkot fizikailag motivált szűrőkkel egészíti ki:
A \(H_\beta(z)=1-z^{-\beta N}\) fésűszűrő a \(h\)-adik felharmonikuson \(2\,|\sin(\pi h\beta)|\) erősítést ad. Ha \(h\beta\) egész, a felharmonikus kiesik. Elég \(0<\beta\le\tfrac12\)-t vizsgálni, mert \(\beta\) és \(1-\beta\) ugyanazt a spektrumot adja.
Az oszlopok a \(h\)-adik felharmonikus relatív amplitúdóját mutatják (\(|\sin(\pi h\beta)|\)); a rózsaszín oszlopok kieső felharmonikusok.
| Feladat | Leírás |
|---|---|
| Hangolásmérés | Játssz 432 Hz-et egész \(N=102\)-vel (fél minta korrekció nélkül) és a pontos képlettel (\(N=101{,}58\), törtrészes késleltetés); mérd a frekvenciát. Egész késleltetésnél a várt érték 430,2 Hz, kb. −7 cent. |
| Spektrogram | Rögzítsd a kimenetet, és nézd meg spektrogramon a felharmonikusok viselkedését: a magasak gyorsabban halnak el. Vesd össze a 10. felharmonikus lecsengését a \(T_{60,k}\) képlettel. |
| T60-mérés | Állíts be 3 s-ot, és mérd meg dB-skálán, mikor esik az alaphang 60 dB-lel. Ellenőrizd a \(g\) képletet. |
| Pengetési pozíció | Próbáld ki \(\beta=\tfrac12,\tfrac13,\tfrac14\) értéket, és keresd meg a spektrumban a kieső felharmonikusokat. |
| Zajimpulzus hossza | Változtasd az impulzus hosszát (fél, egy, két periódus), és hallgasd meg a különbséget. |
| Dob-változat | Valósítsd meg az előjelcserés változatot SuperCollider-ben vagy gen~-ben, \(b=1;\,0{,}5;\,0\) értékekkel. |
| Stabilitás | Emeld \(g\)-t rövid időre 1 fölé (nagyon kis hangerővel!), és figyeld a jel növekedését. |