A teljes mozgásegyenlet külső erővel: \(F=m\,a+c\,v+k\,x\), vagyis \(m\ddot x+c\dot x+k x=F\). Mértékegységek (SI): \(m\) [kg], \(k\) [N/m], \(c\) [N·s/m], \(F\) [N], \(x\) [m]. Szabad lecsengésnél (\(F=0\)): \(\omega_0=\sqrt{k/m}\), \(\sigma=\dfrac{c}{2m}\), \(\zeta=\dfrac{c}{2\sqrt{km}}\), \(\omega_d=\omega_0\sqrt{1-\zeta^2}\), \(T_{60}=\dfrac{\ln 1000}{\sigma}\approx 13{,}8\,\dfrac{m}{c}\). A válasz egy velocitásimpulzusra (\(v_0=J/m\)): \(x(t)=\dfrac{J}{m\,\omega_d}\,e^{-\sigma t}\sin\omega_d t\). Példa: \(m=10\) g, \(k=76\) kN/m, \(c=0{,}05\) N·s/m: \(f_0=439\) Hz, \(T_{60}\approx2{,}8\) s.
Digitálisan ez egy két mintás rezgő: \(y_n=a_1y_{n-1}+a_2y_{n-2}\), \(a_1=2r\cos\theta\), \(a_2=-r^2\), \(r=e^{-\sigma/f_s}\), \(\theta=\omega_d/f_s\). Pontos és mindig stabil (\(r<1\)). A módusos szintézis építőeleme ez az egy rezgő.
Fal – \(k\) – \(m_1\) – \(k\) – \(m_2\) (a jobb vég szabad). A két egyenlet: \(m\ddot x_1=-k x_1+k(x_2-x_1)\), \(m\ddot x_2=-k(x_2-x_1)\). Mátrixosan (csillapítás és erő nélkül): \(\mathbf M\ddot{\mathbf x}+\mathbf K\mathbf x=\mathbf 0\), ahol \(\mathbf M=m\mathbf I\), \(\mathbf K=k\begin{pmatrix}2&-1\\-1&1\end{pmatrix}\). Csillapítással és erővel általában:
\(\mathbf M\ddot{\mathbf x}+\mathbf C\dot{\mathbf x}+\mathbf K\mathbf x=\mathbf f(t)\)
Közös ütemű mozgás. Keressünk olyan megoldást, ahol minden tömeg ugyanazon az \(\omega\)-n mozog, rögzített arányban: \(\mathbf x=\mathbf u\cos\omega t\). Behelyettesítve \((\mathbf K-\omega^2\mathbf M)\mathbf u=\mathbf 0\), amelynek nemtriviális megoldásához \(\det(\mathbf K-\omega^2\mathbf M)=0\). \(N\) tömegnél \(N\) megoldás van: ezek a módusok (\(\omega_n\), \(\mathbf u_n\)).
\(N=2\) kézzel. \(\omega^2=\dfrac{3\mp\sqrt5}{2}\dfrac km\), azaz \(\omega_1=0{,}618\,\omega_0\), \(\omega_2=1{,}618\,\omega_0\). Alakok: \(\mathbf u_1=(1;\,1{,}618)\) (a két tömeg egy irányban), \(\mathbf u_2=(1;\,-0{,}618)\) (ellentétesen). A 439 Hz-es példánál \(f_1=271\) Hz és \(f_2=710\) Hz.
A módusalakok ortogonálisak: \(\mathbf u_m^T\mathbf M\mathbf u_n=m_n\delta_{mn}\) és \(\mathbf u_m^T\mathbf K\mathbf u_n=m_n\omega_n^2\delta_{mn}\). Ha \(\mathbf x=\sum_n q_n\mathbf u_n\), akkor arányos csillapításnál (\(\mathbf C=\alpha\mathbf M+\beta\mathbf K\)) minden módusra külön egyenlet adódik:
\(\ddot q_n+2\sigma_n\dot q_n+\omega_n^2q_n=\dfrac{\mathbf u_n^T\mathbf f}{m_n},\qquad \sigma_n=\dfrac{\alpha+\beta\omega_n^2}{2}\)
Minden módus tehát ugyanaz az egyszerű tömeg–rugó–fék rezgő, mint az 1. pontban, saját \(\omega_n\)-nel és \(\sigma_n\)-nel (\(m_n\) a módus-tömege). Az \(\alpha\)-tag minden módust egyformán fékez, a \(\beta\)-tag a magasakat jobban: ezért tompul a hang a lecsengés közben. (A formula egyezik a teljes, csatolt rendszer közvetlen numerikus megoldásával.)
Egy \(J\) impulzus a \(p\). tömegen (\(\mathbf f=J\delta(t)\mathbf e_p\)) minden módusra egy kezdősebességet ad: \(\dot q_n(0)=u_n(p)J/m_n\). Az \(n\). módus válasza az 1. pont megoldása, a kitérés a \(q\). tömegnél pedig a módusok összege:
\(x_q(t)=\sum_{n=1}^{N}\dfrac{u_n(p)\,u_n(q)}{m_n\,\omega_{d,n}}\,J\;e^{-\sigma_n t}\sin(\omega_{d,n}t)\)
Három dolog kell módusonként: frekvencia (\(\omega_n\)), lecsengés (\(\sigma_n\)) és erősség (\(u_n(p)u_n(q)/(m_n\omega_{d,n})\)). A hangszínt ez a három lista adja. Az erősségben szereplő \(u_n(p)u_n(q)\) miatt számít az ütés és a hallgatás helye; az \(1/\omega_n\) miatt a magas módusok eleve halkabbak.
Algoritmus (modális bank): minden módus egy két mintás rezgő (1. pont), a kimenet az összegük. Költség: módusonként mintánként 2 szorzás, vagyis 100 módus 44,1 kHz-en kb. 8,8 millió szorzás/s. Az \(f_s/2\) fölötti és a túlcsillapított (\(\sigma_n\ge\omega_n\)) módusokat kihagyjuk.
| Végek | Frekvencia | Alak | \(m_n/m\) |
|---|---|---|---|
| fal – szabad | \(\omega_n=2\omega_0\sin\dfrac{(2n-1)\pi}{4N+2}\) | \(u_n(j)=\sin\dfrac{(2n-1)j\pi}{2N+1}\) | \(\dfrac{2N+1}{4}\) |
| fal – fal | \(\omega_n=2\omega_0\sin\dfrac{n\pi}{2N+2}\) | \(u_n(j)=\sin\dfrac{n j\pi}{N+1}\) | \(\dfrac{N+1}{2}\) |
(\(\omega_0=\sqrt{k/m}\), \(n,j=1\ldots N\).) \(N=1\)-re fal–szabad végnél \(\omega_1=\omega_0\): visszakapjuk az egyetlen rezgőt. A fal–szabad lánc frekvenciái közel \(1:3:5\ldots\), a fal–fal láncé közel \(1:2:3\ldots\) (húr), de a magas módusok a szinusz miatt összesűrűsödnek (\(N=8\), fal–fal: \(1:1{,}97:2{,}88:3{,}70\)). Az ütés helye: a falhoz közel ütve a magasabb módusok erősebbek (fényesebb hang), a szabad végén az alaphang dominál; ugyanígy a húrnál a híd közelében pengetve fényesebb a hang.
| Rendszer | Módusok aránya \(f_n/f_1\) | Megjegyzés |
|---|---|---|
| Ideális húr | 1 : 2 : 3 : 4 … | harmonikus; ütés/pengetés helye: \(|\sin(n\pi p)|\) |
| Merev húr (zongora) | \(n\sqrt{\dfrac{1+Bn^2}{1+B}}\) | \(B\approx10^{-4}\)–\(10^{-3}\); kissé inharmonikus |
| Zárt cső | 1 : 3 : 5 : 7 … | páratlan harmonikusok |
| Szabad–szabad rúd | 1 : 2,76 : 5,40 : 8,93 : 13,34 … | xilofon, csengő; \(f\propto1/L^2\); (4,73; 7,85; 11,0…)² arányai |
| Marimba-rúd | 1 : 4 : 10 (hangolt) | a rúd alját faragják, hogy a 2. és 3. módus harmonikus legyen |
| Kör alakú membrán | 1 : 1,59 : 2,14 : 2,30 : 2,65 : 2,92 | Bessel-függvények zérushelyei; ideális, levegő nélkül |
| Harang (közelítő) | 0,5 : 1 : 1,19 : 1,5 : 2 … (a prime = 1) | hum, prime, tierce, quint, nominal; hangolt, közelítő értékek |
Anyag: a fém kevésbé csillapít, ezért hosszabban cseng; a fa, műanyag vagy bőr rövidebben. A frekvenciafüggő lecsengést a demóban \(\sigma_n=\sigma_1\,[1+g(r_n^2-1)]\) adja (\(r_n=f_n/f_1\), \(g\) a „magas módusok féke”). A kalapács hatását egy egyszerű aluláteresztő közelíti (\(1/\sqrt{1+(f/f_c)^2}\)): lágy kalapács, kis \(f_c\), tompább hang.
| Módszer | Erőssége | Gyengesége | Tipikus példa |
|---|---|---|---|
| Modális szintézis | pontos, olcsó, módusonként állítható hang és lecsengés | csak lineáris, időben állandó rendszerre; nemlineáris érintkezést nem ad | rúd, harang, hangszertest, ütőhangszer |
| Karplus–Strong / hullámvezető | nagyon olcsó, húr és cső | nehezebb a testet és az inharmonicitást adni | pengetett húr, síp |
| Euler / FDTD / tömeg-rugó hálózat | nemlineáris, közben változó paraméter, bármilyen erő | kis lépés kell, a frekvencia kissé pontatlan, sok számítás | gong, tam-tam, dobbőr érintkezése |
Hangszeres gyakorlat: a zongorában a hangszertestet modálisan, a húrt hullámvezetővel szokás modellezni; a gongot nemlineáris szimulációval.
Belső tömegre \(m\ddot x_j=k(x_{j-1}-2x_j+x_{j+1})\). Keressük \(x_j=u_j\cos\omega t\) alakban, \(u_j=\sin(j\theta)\) próbafüggvénnyel (\(u_0=0\), a fal miatt). A \(\sin((j-1)\theta)+\sin((j+1)\theta)=2\sin(j\theta)\cos\theta\) azonosságból
\(\omega^2=2\omega_0^2(1-\cos\theta)=4\omega_0^2\sin^2\dfrac\theta2\)
A \(\theta\)-t a másik vég feltétele szabja meg. Fal–fal: \(u_{N+1}=0\), azaz \((N+1)\theta=n\pi\), ebből \(\omega_n=2\omega_0\sin\frac{n\pi}{2N+2}\). Fal–szabad: az utolsó tömeg egyenlete \(m\ddot x_N=k(x_{N-1}-x_N)\) olyan, mintha \(u_{N+1}=u_N\) lenne. Ez \(\sin((N+1)\theta)-\sin(N\theta)=2\cos\!\big((N+\tfrac12)\theta\big)\sin\tfrac\theta2=0\)-t jelent, tehát \(\theta=\frac{(2n-1)\pi}{2N+1}\) és \(\omega_n=2\omega_0\sin\frac{(2n-1)\pi}{4N+2}\). A módus-tömegeket (\(m_n/m=\sum_j\sin^2(j\theta)\)) a trigonometrikus összeg kiértékelése adja: \((N+1)/2\), illetve \((2N+1)/4\). Ez az 5. pont táblázatának levezetése; saját számolás, nem szó szerinti idézet.
Az 1. pont rezgőjének átviteli függvénye \(H(s)=\dfrac{1}{m\,(s^2+2\sigma s+\omega_0^2)}\), pólusai \(s=-\sigma\pm j\omega_d\). Az \(z=e^{sT}\) leképezés (impulzus-invariancia) a pólusokat \(re^{\pm j\theta}\)-ra viszi, \(r=e^{-\sigma T}\), \(\theta=\omega_dT\). A nevező \(1-2r\cos\theta\,z^{-1}+r^2z^{-2}\), vagyis a 2 mintás rezgő \(a_1=2r\cos\theta\), \(a_2=-r^2\) együtthatóival. Következmények: (i) a frekvencia és a lecsengés pontos (nincs frekvenciatorzulás, szemben a bilineáris transzformációval); (ii) stabil, ha \(\sigma>0\), azaz \(r<1\); (iii) a \(\omega>\pi f_s\) módusok visszatükröződnének, ezért kihagyjuk (a demó \(0{,}9\pi f_s\)-nél vág); (iv) a demó rezgője az ideális válasz egy mintával eltolt másolatát adja, ami nem hallható.
Az 1. és 4. pont összefüggése frekvenciatartományban: az \(f(t)\) bemenet és az \(x_q\) kimenet közötti átviteli függvény \(H_{pq}(s)=\sum_n\dfrac{u_n(p)\,u_n(q)}{m_n\,(s^2+2\sigma_ns+\omega_n^2)}\). A pólusok (\(\omega_n,\sigma_n\)) a rendszer sajátjai, a számlálók (maradékok) az ütés és a hallgatás helyétől függnek. A parciális törtekre bontás és az állapottér diagonalizálása ugyanazt adja: a pólusok minden bemenetre és kimenetre közösek, a diagonalizált rendszer pedig fizikailag helyes párhuzamos szűrőbank [17]. Feltétel: az arányos csillapítás. Általános csillapításnál a módusok komplexek, és az állapottér-sajátérték feladatot kell megoldani.
Python (NumPy), SuperCollider és Max/MSP gen~ (genexpr). Mindhárom ugyanazt a képletet valósítja meg, mint a böngészős demók: módusonként a/ωd · e−σt · sin(ωdt). A paraméterek nevei és mértékegységei megegyeznek a demók csúszkáival.
| Demó | Opció | Python / SC / gen~ neve |
|---|---|---|
| 1 | Tömegek száma, végek | N (1–8; gen~ ≤ 16), ends: "ff" / \ff / 0 = fal–szabad, "fx" / \fx / 1 = fal–fal |
| Tömeg, rugó, csillapítás, magas módusok féke | m [g], k [kN/m], c [N·s/m], beta [µs] | |
| Ütés és hallgatás helye | p, q (1…N) | |
| Módusok be/ki | mask: lista [1,0,1] (Python, SC); egész szám, n. bit = n. módus (gen~) | |
| Módusfrekvenciák, arányok, alakok | Python: mode_info | |
| Lassított felvétel | Python: slowmo; SC: slow:true + ~at; gen~: a hang a q. tömeg kitérése, lassítva a lenti Python/SC képlet adja | |
| Hang az idő függvényében | a bank / ~modal kimenete | |
| 🔔 Ütés | a program futtatása; gen~: in1 0→1 | |
| N = 1 | az 1. pont egyetlen rezgője (oscillator) | |
| 2 | Anyag (7 előbeállítás) | mat/material: húr, merev húr, zárt cső, rúd, marimba, membrán, harang (gen~: 0…6) |
| Alapfrekvencia, módusok száma, T60 | f1, M, T60 | |
| Magas módusok féke | g | |
| Ütés helye (húrnál) | pos | |
| Kalapács vágófrekvenciája | fc | |
| Sávdiagram (erősség dB, T60) | a/w és LN1000/s a visszaadott listákból |
import numpy as np, wave
LN1000 = np.log(1000)
def oscillator(m, k, c, J=1.0, fs=44100, T=3.0):
"""1. pont: egy tömeg–rugó–fék (SI), kitérés egy J impulzusra."""
w0, s = np.sqrt(k/m), c/(2*m); wd = np.sqrt(w0**2 - s**2)
t = np.arange(int(fs*T))/fs
return J/(m*wd) * np.exp(-s*t) * np.sin(wd*t)
def resonator(m, k, c, J=1.0, fs=44100, T=3.0):
"""Ugyanez két mintás rezgőként: y[n] = a1 y[n-1] + a2 y[n-2]."""
w0, s = np.sqrt(k/m), c/(2*m); wd = np.sqrt(w0**2 - s**2)
r, th = np.exp(-s/fs), wd/fs; a1, a2 = 2*r*np.cos(th), -r*r
y = np.zeros(int(fs*T)); y1 = y2 = 0.0
for i in range(len(y)):
y[i] = (J/(m*wd)*np.sin(th) if i == 0 else 0) + a1*y1 + a2*y2
y2, y1 = y1, y[i]
return y
def bank(w, s, a, fs=44100, T=3.0):
"""Modális bank. w: szögfrekvencia [rad/s], s: sigma [1/s], a: erősség (1/w_d-t a függvény adja)."""
w, s, a = map(np.asarray, (w, s, a))
t = np.arange(int(fs*T))/fs
ok = (w < 0.9*np.pi*fs) & (s < w) # Nyquist fölött / túlcsillapított: kihagyjuk
wd = np.sqrt(w[ok]**2 - s[ok]**2)
y = (a[ok]/wd * np.exp(-np.outer(t, s[ok])) * np.sin(np.outer(t, wd))).sum(1)
return 0.5*y/np.max(np.abs(y))
def write_wav(name, y, fs=44100):
with wave.open(name, "wb") as f:
f.setnchannels(1); f.setsampwidth(2); f.setframerate(fs)
f.writeframes((y*32767).astype("<i2").tobytes())(
// Közös hangszóró-függvény: spec = [w, s, a] listák (w: rad/s, s: sigma, a: erősség)
~modal = { |spec, dur=4, level=0.5|
var w = spec[0], s = spec[1], a = spec[2];
var nyq = 0.9*pi*Server.default.sampleRate;
var ok = (0..w.size-1).select { |i| (w[i] < nyq) and: { s[i] < w[i] } };
var wd = ok.collect { |i| (w[i].squared - s[i].squared).sqrt };
var amp = ok.collect { |i, j| a[i] / wd[j] };
var sg = ok.collect { |i| s[i] };
var t = Sweep.ar(Impulse.ar(0)); // idő az ütés óta
var y = Mix(SinOsc.ar(wd/(2*pi), 0, amp) * (sg.neg * t).exp);
y * level / amp.abs.sum.max(1e-12) * EnvGen.kr(Env([1,1,0], [dur, 0.05]), doneAction: 2)
};
// 1. pont (egyetlen rezgő): m=0.01, k=76000, c=0.049
{ var m=0.01, k=76000, c=0.049, w0=(k/m).sqrt, s=c/(2*m), wd=(w0.squared-s.squared).sqrt;
SinOsc.ar(wd/(2*pi)) * (s.neg * Sweep.ar(Impulse.ar(0))).exp * 0.3 * EnvGen.kr(Env([1,1,0],[3,0.05]), doneAction: 2) ! 2 }.play;
)gen~ megjegyzés. A genexpr-ben az y1, y2 (és hasonló, betű+szám) nevek ütközhetnek a beépített nevekkel, ezért a kódokban ya, yb, zya, cfa stb. szerepel. Az if/else ágban vagy ciklusban először létrehozott változó kívül nem látszik, ezért minden ideiglenes változó a kód elején kap kezdőértéket.
Ugyanaz a két mintás rezgő, mint a Python resonator. Beállítás és használat: mint a Demó 1-nél ([gen~], benne codebox, ütéshez [click~]). Újabb ütés a futó hanghoz hozzáadódik, ami a lineáris rendszerre helyes.
// 1. pont – egyetlen tömeg–rugó–fék, gen~ (codebox). in1: ütés (0→1), out1: kitérés [m]
Param m(10, min=1, max=100); // tömeg [g]
Param k(76, min=5, max=500); // rugómerevség [kN/m]
Param c(0.049, min=0, max=200); // csillapítás [N·s/m]
Param J(1, min=0, max=10); // impulzus [N·s]
History ya(0), yb(0), prev(0); // (az y1, y2 nevek ütköznek a gen~ beépített neveivel)
mm = m/1000;
w0 = sqrt(k*1000/mm); // omega_0 [rad/s]
s = c/(2*mm); // sigma [1/s]
wd = sqrt(max(w0*w0 - s*s, 1e-9)); // omega_d (túlcsillapítva kb. 0 kimenet)
r = exp(-s/samplerate); th = wd/samplerate;
trig = (in1 > 0 && prev <= 0);
v = trig*J/(mm*wd)*sin(th) + 2*r*cos(th)*ya - r*r*yb; // y[n] = a1 y[n-1] + a2 y[n-2]
yb = ya; ya = v; prev = in1;
out1 = v;A teljes modális bank gen~ változata (több módus, 16-ig) a Demó 1 és a Demó 2 kártyán van.
def chain(N, ends, m, k, c, beta, p, q, mask=None):
"""Demó 1. N: tömegek száma (1–8) · ends: "ff" fal–szabad / "fx" fal–fal
m [g] · k [kN/m] · c [N·s/m, tömegenként] · beta [µs] · p: ütés, q: hallgatás helye (1…N)
mask: módusok be/ki, pl. [1,0,1] (a „Módusok” jelölőnégyzetek)"""
m, k, beta = m/1e3, k*1e3, beta*1e-6
w0, n = np.sqrt(k/m), np.arange(1, N+1)
if ends == "ff":
w = 2*w0*np.sin((2*n-1)*np.pi/(4*N+2)); S = (2*N+1)/4
u = lambda j: np.sin((2*n-1)*j*np.pi/(2*N+1))
else:
w = 2*w0*np.sin(n*np.pi/(2*N+2)); S = (N+1)/2
u = lambda j: np.sin(n*j*np.pi/(N+1))
s = c/m/2 + beta*w**2/2 # Rayleigh: sigma_n
a = u(p)*u(q)/S * (1 if mask is None else np.resize(mask, N))
return w, s, a, u, S
def mode_info(N, ends, m, k, c, beta): # „f = … Hz · arány …” és módusalakok
w, s, a, u, S = chain(N, ends, m, k, c, beta, 1, 1)
f = w/2/np.pi
return f, f/f[0], np.array([u(j) for j in range(1, N+1)]).T # alakok: [módus, tömeg]
def slowmo(N, ends, m, k, c, beta, p, tau, mask=None):
"""Lassított felvétel: az 1. módus 0,5 Hz, T60 = 8 s. Visszaad: a j=1…N tömeg kitérése tau-nál."""
w, s, a, u, S = chain(N, ends, m, k, c, beta, p, 1)
ws, ss = 2*np.pi*0.5*w/w[0], s/s[0]*LN1000/8
wd, mk = np.sqrt(ws**2 - ss**2), np.ones(N) if mask is None else np.resize(mask, N)
return [np.sum(mk*u(j)*u(p)/(S*wd)*np.exp(-ss*tau)*np.sin(wd*tau)) for j in range(1, N+1)]
# 🔔 Ütés (alapértelmezett demóbeállítás: N=3, fal–szabad, m=10 g, k=76 kN/m, c=0,049)
w, s, a, _, _ = chain(3, "ff", 10, 76, 0.049, 0, 3, 3)
write_wav("lanc.wav", bank(w, s, a, T=min(6, max(.4, 1.2*LN1000/s.min()))))
# tompuló hang: beta = 20 µs, csak az 1. és 3. módus: mask=[1,0,1]
w, s, a, _, _ = chain(5, "fx", 10, 76, 0.049, 20, 2, 4, mask=[1,0,1,1,1])
write_wav("lanc_tompul.wav", bank(w, s, a))Futtasd előbb a „Közös alap” blokkot (~modal), a szervert indítsd el (s.boot).
(
// Demó 1. P: (N:1–8, ends:\ff|\fx, m:g, k:kN/m, c:N·s/m, beta:µs, p:, q:, mask:[1,0,1…], slow:true|false)
~chain = { |P|
var N = P[\N] ? 3, ends = P[\ends] ? \ff, m = P[\m] ? 10, k = P[\k] ? 76, c = P[\c] ? 0.049;
var beta = P[\beta] ? 0, p = P[\p] ? 3, q = P[\q] ? 3, mask = P[\mask] ? 1, slow = P[\slow] ? false;
var mm = m/1000, w0 = (k*1000/mm).sqrt, n = (1..N);
var w, u, S, s, a;
if(ends == \ff) {
w = n.collect { |i| 2*w0*(((2*i-1)*pi)/(4*N+2)).sin };
u = { |i, j| (((2*i-1)*j*pi)/(2*N+1)).sin }; S = (2*N+1)/4;
} {
w = n.collect { |i| 2*w0*((i*pi)/(2*N+2)).sin };
u = { |i, j| ((i*j*pi)/(N+1)).sin }; S = (N+1)/2;
};
s = w.collect { |x| c/mm/2 + (beta*1e-6*x*x/2) };
a = n.collect { |i| u.(i, p) * u.(i, q) / S * mask.asArray.wrapAt(i-1) };
if(slow) { s = s * (1000.log/8/s[0]); w = w * (pi/w[0]) }; // lassított felvétel: f1 = 0,5 Hz, T60 = 8 s
[w, s, a]
};
// kitérés a q. tömegnél tau időpontban (lassított rajzhoz: slow:true, q: j)
~at = { |spec, tau| spec[0].size.collect { |i| var wd = (spec[0][i].squared - spec[1][i].squared).sqrt;
spec[2][i]/wd * exp(spec[1][i].neg*tau) * sin(wd*tau) }.sum };
{ ~modal.(~chain.((N:3, ends:\ff, p:3, q:3)), 4) ! 2 }.play; // 🔔 Ütés
// { ~modal.(~chain.((N:5, ends:\fx, beta:20, p:2, q:4, mask:[1,0,1,1,1])), 3) ! 2 }.play;
// ~at.(~chain.((N:3, p:3, q:2, slow:true)), 1.5).postln;
)Hozz létre egy [gen~] objektumot, nyisd meg, tegyél bele codebox-ot, és másold be a kódot; az in1-et és out1-et kösd a codebox be- és kimenetére. Kívül [click~] adja az ütést a gen~ bemenetére, a paramétereket üzenettel állítod (pl. [N 5(). A p és q ne legyen nagyobb N-nél.
// Demó 1 – gen~ (codebox). in1: ütés (0→1 impulzus, pl. [click~]), out1: hang. Legfeljebb 16 módus.
Param N(3, min=1, max=16); // tömegek száma
Param ends(0, min=0, max=1); // 0 = fal–szabad, 1 = fal–fal
Param m(10, min=1, max=100); // tömeg [g]
Param k(76, min=5, max=500); // rugómerevség [kN/m]
Param c(0.049, min=0, max=200); // csillapítás [N·s/m, tömegenként]
Param beta(0, min=0, max=100); // magas módusok féke [µs]
Param p(3, min=1, max=16); // ütés helye
Param q(3, min=1, max=16); // hallgatás helye
Param mask(65535, min=0, max=65535);// módusok be/ki: n. bit = n. módus
Param level(0.5, min=0, max=1);
Data zya(16), zyb(16), cfa(16), cfb(16), gin(16);
History prev(0), gnorm(1);
// Minden változó itt kap kezdőértéket: a genexpr nem látja kívül azt, ami először
// egy if/else ágban vagy ciklusban keletkezett.
first = 0; w0 = 0; nrm = 0; nn = 0; w = 0; sm = 1; up = 0; uq = 0;
sg = 0; ok = 0; wd = 1; rr = 0; th = 0; v = 0; acc = 0; i = 0;
if (in1 > 0 && prev <= 0) {
w0 = sqrt(k*1000/(m/1000)); nrm = 0;
for (i = 0; i < 16; i += 1) {
nn = i + 1;
if (ends < 0.5) {
w = 2*w0*sin((2*nn-1)*pi/(4*N+2)); sm = (2*N+1)/4;
up = sin((2*nn-1)*p*pi/(2*N+1)); uq = sin((2*nn-1)*q*pi/(2*N+1));
} else {
w = 2*w0*sin(nn*pi/(2*N+2)); sm = (N+1)/2;
up = sin(nn*p*pi/(N+1)); uq = sin(nn*q*pi/(N+1));
}
sg = c/(m/1000)/2 + beta*1e-6*w*w/2;
ok = (nn <= N) * (w < 0.9*pi*samplerate) * (sg < w) * (floor(mask/pow(2, i)) % 2);
wd = sqrt(max(w*w - sg*sg, 1e-9)); rr = exp(-sg/samplerate); th = wd/samplerate;
cfa.poke(2*rr*cos(th), i); cfb.poke(-rr*rr, i);
gin.poke(ok*up*uq/sm/wd*sin(th), i); nrm += ok*abs(up*uq/sm/wd);
zya.poke(0, i); zyb.poke(0, i);
}
gnorm = max(nrm, 1e-12); first = 1;
}
acc = 0;
for (i = 0; i < 16; i += 1) {
v = first*gin.peek(i) + cfa.peek(i)*zya.peek(i) + cfb.peek(i)*zyb.peek(i);
zyb.poke(zya.peek(i), i); zya.poke(v, i); acc += v;
}
prev = in1;
out1 = acc * level / gnorm;A bank, write_wav és LN1000 a „Közös alap” blokkból való.
n = np.arange(1, 17)
PRESETS = { # arányok, f1 [Hz], T60 [s], g, húr? (az ütés helye számít)
"Húr (ideális)": (n, 196, 3.0, .5, True),
"Merev húr (zongora)": (n*np.sqrt((1+5e-4*n*n)/(1+5e-4)), 110, 4.0, .6, True),
"Zárt cső": (2*n[:8]-1, 220, 1.2, .3, False),
"Rúd (szabad–szabad)": ([1,2.757,5.404,8.933,13.344,18.638,24.814,31.872], 440, 2.5, .3, False),
"Marimba-rúd (1:4:10)": ([1,4,10], 262, 0.8, .2, False),
"Membrán (dob)": ([1,1.593,2.136,2.295,2.653,2.917,3.155,3.5,3.599,3.647], 120, 0.6, .6, False),
"Harang (közelítő)": ([.5,1,1.19,1.5,2,2.5,3,4], 220, 6.0, .4, False)}
def designer(mat, f1=None, M=8, T60=None, g=None, pos=0.2, fc=20000):
"""Demó 2. mat: anyag · f1 [Hz] · M: módusok száma · T60 [s] az 1. módusé · g: magas módusok féke (0–1)
pos: ütés helye a húr hosszának törtrésze (csak húrnál) · fc: kalapács vágófrekvenciája [Hz]"""
r, F, T, G, string = PRESETS[mat]
r = np.asarray(r, float)[:M]; n = np.arange(1, len(r)+1)
f1, T60, g = f1 or F, T60 or T, G if g is None else g
f = f1*r
s = LN1000/T60 * (1 + g*(r**2 - 1)) # sigma_n = sigma_1 (1 + g (r^2 - 1))
a = (np.abs(np.sin(n*np.pi*pos)) if string else 1) / np.sqrt(1 + (f/fc)**2)
return 2*np.pi*f, s, a
for name in PRESETS: # 🔔 mind a 7 előbeállítás
w, s, a = designer(name)
write_wav(name.split()[0] + ".wav", bank(w, s, a, T=min(8, max(.5, 1.2*LN1000/s.min()))))
w, s, a = designer("Húr (ideális)", pos=0.1, fc=2000) # híd közelében, lágy kalapácsA ~modal a „Közös alap” blokkból való.
(
~presets = ( // [arányok, f1, T60, g, húr?]
hur: [(1..16), 196, 3, 0.5, true],
merev: [(1..16).collect { |n| n*((1+(5e-4*n*n))/(1+5e-4)).sqrt }, 110, 4, 0.6, true],
cso: [(1..8).collect { |n| 2*n-1 }, 220, 1.2, 0.3, false],
rud: [[1,2.757,5.404,8.933,13.344,18.638,24.814,31.872], 440, 2.5, 0.3, false],
marimba: [[1,4,10], 262, 0.8, 0.2, false],
membran: [[1,1.593,2.136,2.295,2.653,2.917,3.155,3.5,3.599,3.647], 120, 0.6, 0.6, false],
harang: [[0.5,1,1.19,1.5,2,2.5,3,4], 220, 6, 0.4, false]
);
// P: (mat:\hur|\merev|\cso|\rud|\marimba|\membran|\harang, f1:Hz, M:módusok, T60:s, g:0–1, pos:0.05–0.5, fc:Hz)
~designer = { |P|
var pr = ~presets[P[\mat] ? \rud], r = pr[0], f1 = P[\f1] ? pr[1], M = P[\M] ? 8;
var T60 = P[\T60] ? pr[2], g = P[\g] ? pr[3], pos = P[\pos] ? 0.2, fc = P[\fc] ? 20000;
var fr, w, s, a;
r = r.keep(M); fr = f1*r; w = fr*(2*pi);
s = r.collect { |x| 1000.log/T60 * (1 + (g*(x*x - 1))) };
a = r.collect { |x, i| (if(pr[4]) { ((i+1)*pi*pos).sin.abs } { 1 }) / (1 + ((fr[i]/fc).squared)).sqrt };
[w, s, a]
};
{ ~modal.(~designer.((mat:\rud, f1:440, M:6)), 3) ! 2 }.play; // 🔔 Ütés
// { ~modal.(~designer.((mat:\hur, pos:0.1, fc:2000, T60:3)), 4) ! 2 }.play; // húr, híd közelében, lágy kalapács
)Felépítése és használata ugyanaz, mint a Demó 1-é.
// Demó 2 – gen~ (codebox). in1: ütés (0→1), out1: hang. Legfeljebb 16 módus.
Param material(3, min=0, max=6); // 0 húr, 1 merev húr, 2 zárt cső, 3 rúd, 4 marimba, 5 membrán, 6 harang
Param f1(440, min=50, max=1000); // alapfrekvencia [Hz]
Param M(8, min=1, max=16); // módusok száma
Param T60(2.5, min=0.2, max=8); // az 1. módus T60-a [s]
Param g(0.3, min=0, max=1); // magas módusok féke
Param pos(0.2, min=0.05, max=0.5);// ütés helye (csak húrnál)
Param fc(20000, min=300, max=20000); // kalapács vágófrekvenciája [Hz]
Param level(0.5, min=0, max=1);
Data ratio(16), zya(16), zyb(16), cfa(16), cfb(16), gin(16);
History prev(0), gnorm(1);
// előbeállítások (f1, T60, g): húr 196/3/.5 · merev 110/4/.6 · cső 220/1.2/.3 · rúd 440/2.5/.3 · marimba 262/.8/.2 · membrán 120/.6/.6 · harang 220/6/.4
// Minden változó itt kap kezdőértéket (lásd a Demó 1 megjegyzését).
first = 0; nr = 0; nrm = 0; nn = 0; rt = 1; fr = 0; w = 0; sg = 0; stp = 1; amp = 0;
ok = 0; wd = 1; rr = 0; th = 0; v = 0; acc = 0; i = 0;
if (in1 > 0 && prev <= 0) {
nr = (material < 2)*16 + (material == 2)*8 + (material == 3)*8 + (material == 4)*3 + (material == 5)*10 + (material == 6)*8;
for (i = 0; i < 16; i += 1) {
nn = i + 1;
if (material == 0) { ratio.poke(nn, i); }
if (material == 1) { ratio.poke(nn*sqrt((1 + 5e-4*nn*nn)/(1 + 5e-4)), i); }
if (material == 2) { ratio.poke(2*nn - 1, i); }
}
if (material == 3) { ratio.poke(1,0); ratio.poke(2.757,1); ratio.poke(5.404,2); ratio.poke(8.933,3); ratio.poke(13.344,4); ratio.poke(18.638,5); ratio.poke(24.814,6); ratio.poke(31.872,7); }
if (material == 4) { ratio.poke(1,0); ratio.poke(4,1); ratio.poke(10,2); }
if (material == 5) { ratio.poke(1,0); ratio.poke(1.593,1); ratio.poke(2.136,2); ratio.poke(2.295,3); ratio.poke(2.653,4); ratio.poke(2.917,5); ratio.poke(3.155,6); ratio.poke(3.5,7); ratio.poke(3.599,8); ratio.poke(3.647,9); }
if (material == 6) { ratio.poke(0.5,0); ratio.poke(1,1); ratio.poke(1.19,2); ratio.poke(1.5,3); ratio.poke(2,4); ratio.poke(2.5,5); ratio.poke(3,6); ratio.poke(4,7); }
nrm = 0;
for (i = 0; i < 16; i += 1) {
nn = i + 1; rt = ratio.peek(i); fr = f1*rt; w = twopi*fr;
sg = log(1000)/T60*(1 + g*(rt*rt - 1));
stp = (material < 2) ? abs(sin(nn*pi*pos)) : 1;
amp = stp/sqrt(1 + (fr/fc)*(fr/fc));
ok = (nn <= min(M, nr)) * (w < 0.9*pi*samplerate) * (sg < w);
wd = sqrt(max(w*w - sg*sg, 1e-9)); rr = exp(-sg/samplerate); th = wd/samplerate;
cfa.poke(2*rr*cos(th), i); cfb.poke(-rr*rr, i);
gin.poke(ok*amp/wd*sin(th), i); nrm += ok*abs(amp/wd);
zya.poke(0, i); zyb.poke(0, i);
}
gnorm = max(nrm, 1e-12); first = 1;
}
acc = 0;
for (i = 0; i < 16; i += 1) {
v = first*gin.peek(i) + cfa.peek(i)*zya.peek(i) + cfb.peek(i)*zyb.peek(i);
zyb.poke(zya.peek(i), i); zya.poke(v, i); acc += v;
}
prev = in1;
out1 = acc * level / gnorm;chain(5,"ff",10,76,0.049,0,5,5)). Cseréld az ütés helyét 5-ről 1-re (p=1). Mit hallasz? Utána add meg a beta értékét (Rayleigh-csillapítás), és hasonlítsd a hangot a demóéhoz.| Fogalom | Angolul | Jelentés |
|---|---|---|
| Módus | mode | Mozgásforma, ahol minden pont ugyanazon a frekvencián, rögzített arányban mozog. |
| Sajátfrekvencia, sajátalak | natural frequency, mode shape | A módus ωn és un: a det(K − ω²M) = 0 megoldásai. |
| Tömeg-, merevség-, csillapításmátrix | mass, stiffness, damping matrix | M, K, C: a mozgásegyenlet Mẍ + Cẋ + Kx = f mátrixai. |
| Ortogonalitás | orthogonality | umTM un = 0, ha m ≠ n; ezért esik szét a rendszer független rezgőkre. |
| Módus-tömeg | modal mass | mn = unTM un. |
| Módus-koordináta | modal coordinate | qn: az n. módus kitérése, x = Σ qnun. |
| Csillapítási tényező, σ, ζ | damping ratio, decay rate | σ = c/2m [1/s]; ζ = c/(2√(km)) [–]; ζ = 1 a kritikus csillapítás. |
| Rayleigh-csillapítás | Rayleigh damping | C = αM + βK; σn = (α + βωn²)/2. |
| T60 | T60, decay time | A lecsengés ideje 60 dB-lel (az amplitúdó ezredrészére): T60 = ln 1000 / σ. |
| Impulzusválasz | impulse response | A rendszer válasza egy rövid ütésre; a modális szintézis ennek a módusonkénti összege. |
| Két pólusú rezgő | two-pole resonator | yn = a₁yn−1 + a₂yn−2; a modális bank építőeleme. |
| Modális bank | modal filter bank | Párhuzamosan futó rezgők összege. |
| Nyquist-frekvencia | Nyquist frequency | fs/2; efölötti módust nem szintetizálunk (a demó 0,45·fs-nál vág). |
| Inharmonicitás | inharmonicity | A résztónusok nem egész számú többszörösei az alaphangnak. |
| Inharmonicitási együttható | inharmonicity coefficient B | Merev húrnál fn = n f₀ √(1 + Bn²). |
| Szabad–szabad rúd | free–free bar | Euler–Bernoulli-rúd; módusarányai (4,73; 7,85; 11,0 …)² arányai. |
| Bessel-függvény | Bessel function | A kör alakú membrán módusai a Bessel-függvények zérushelyeiből adódnak. |
| Harang résztónusai | hum, prime, tierce, quint, nominal | Az 1 : 2 : 2,4 : 3 : 4 körüli hangolt résztónusok (a demó: 0,5 : 1 : 1,19 : 1,5 : 2). |
| Ütés és hallgatás helye | excitation / pickup point | Az erősség módusonként un(p)·un(q). |
| Kalapács-szűrő | hammer lowpass | A kalapács lágyságát egyszerű aluláteresztővel közelítjük. |
| Hullámvezető, Karplus–Strong | digital waveguide, Karplus–Strong | Késleltetővonalas húr- és csőmodell; a modális szintézis alternatívája. |
| FDTD, tömeg-rugó hálózat | finite-difference time-domain, mass–spring network | Időlépéses szimuláció; nemlineáris érintkezésre is jó. |
| Modalys | Modalys (IRCAM) | Modális szintézisen alapuló fizikai modellező program. |
| Klank / DynKlank | Klank, DynKlank (SuperCollider) | Rezonátorbank: frekvencia-, amplitúdó- és 60 dB-es lecsengési lista. |
| gen~, genexpr | gen~, genexpr (Max) | Mintaszintű jelfeldolgozás Max-ban, szöveges kóddal. |
| Jel | Egység | Jelentés |
|---|---|---|
| m, k, c | kg, N/m, N·s/m | tömeg, rugómerevség, csillapítás |
| ω0, f0 | rad/s, Hz | √(k/m), illetve ω0/2π |
| σ, ζ | 1/s, – | lecsengési ráta, csillapítási tényező |
| ωd | rad/s | csillapított szögfrekvencia √(ω² − σ²) |
| un(j) | – | az n. módusalak a j. tömegnél |
| mn | kg | módus-tömeg (a demóban m·S) |
| p, q | – | ütés és hallgatás helye (tömeg sorszáma) |
| α, β | 1/s, s | Rayleigh-együtthatók (a demóban β µs-ban) |
| g, fc | –, Hz | magas módusok féke, kalapács vágófrekvenciája |
| fs | Hz | mintavételi frekvencia |
| Jel | Hivatkozás | Mire használható |
|---|---|---|
| [1] | Adrien, J.-M. (1991). The Missing Link: Modal Synthesis. In: De Poli, Piccialli, Roads (szerk.), Representations of Musical Signals, MIT Press, 269. oldaltól. | a modális szintézis alapgondolata, 3–4. pont |
| [2] | Morrison, J. D., Adrien, J.-M. (1993). MOSAIC: A Framework for Modal Synthesis. Computer Music Journal 17(1), 45–56. | modális keretrendszer, Modalys előzménye |
| [3] | van den Doel, K., Pai, D. K. (1998). The Sounds of Physical Shapes. Presence 7(4), 382–395. | módusok, ütés helye, bank algoritmus (4. pont) |
| [4] | van den Doel, K., Kry, P. G., Pai, D. K. (2001). FoleyAutomatic: Physically-based Sound Effects for Interactive Simulation and Animation. SIGGRAPH 2001, 537–544. | interaktív modális szintézis, anyagparaméterek |
| [5] | O'Brien, J. F., Cook, P. R., Essl, G. (2001). Synthesizing Sounds from Physically Based Motion. SIGGRAPH 2001, 529–536. | módusok számítása, csillapítás |
| [6] | Fletcher, N. H., Rossing, T. D. (1998). The Physics of Musical Instruments, 2. kiadás, Springer. | húr, rúd, membrán, harang módusarányai (6. pont) |
| [7] | Chaigne, A., Kergomard, J. (2016). Acoustics of Musical Instruments, Springer. | hangszerek rezgéstana, csillapítás, kalapács |
| [8] | Fletcher, H. (1964). Normal Vibration Frequencies of a Stiff Piano String. J. Acoust. Soc. Am. 36(1), 203–209. | merev húr inharmonicitása (B) |
| [9] | Bilbao, S. (2009). Numerical Sound Synthesis, Wiley. | módusok, FDTD, stabilitás (7. pont) |
| [10] | Smith, J. O. Physical Audio Signal Processing, W3K Publishing (online: ccrma.stanford.edu/~jos/pasp/). | hullámvezetők, két pólusú rezgő, módusok |
| [11] | Karplus, K., Strong, A. (1983). Digital Synthesis of Plucked-String and Drum Timbres. Computer Music Journal 7(2), 43–55. | Karplus–Strong (7. pont) |
| [12] | Cook, P. R. (2002). Real Sound Synthesis for Interactive Applications, A K Peters. | modális szintézis játékokban, gyakorlat |
| [13] | Inman, D. J. Engineering Vibration, Pearson. | mozgásegyenlet, sajátérték-feladat, modális csillapítás (1–3. pont) |
| [14] | Rayleigh, J. W. S. (1894). The Theory of Sound, Macmillan. | lineáris rezgések, a módusszétválasztás klasszikus forrása |
| [15] | IRCAM: Modalys dokumentáció, support.ircam.fr/docs/Modalys | modális szintézis hangszeres gyakorlata |
| [16] | SuperCollider Help: Klank, DynKlank, Ringz. · Cycling '74: Max dokumentáció, gen~ és genexpr. | a Kódok fül SuperCollider és gen~ részei |
| [17] | Smith, J. O. Review of Physical Signal Models (Music 420), CCRMA, Stanford University. ccrma.stanford.edu/~jos/ReviewPM/ (State Space to Modal Synthesis) | pólusok megosztása, diagonalizálás, párhuzamos szűrőbank (10. pont) |