♪ Modális szintézis: a tömeg–rugó–fék rendszertől a hangszerig

1. Az építőkő: egy tömeg, egy rugó, egy fék

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ő.

2. Két tömeg és a mátrixos alak

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.

3. Módus-koordináták: a rendszer szétesik

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.)

4. A modális szintézis egyenlete

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.

5. A lánc módusai

VégekFrekvenciaAlak\(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.

6. Valódi hangszerek: módusarányok

RendszerMódusok aránya \(f_n/f_1\)Megjegyzés
Ideális húr1 : 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úd1 : 2,76 : 5,40 : 8,93 : 13,34 …xilofon, csengő; \(f\propto1/L^2\); (4,73; 7,85; 11,0…)² arányai
Marimba-rúd1 : 4 : 10 (hangolt)a rúd alját faragják, hogy a 2. és 3. módus harmonikus legyen
Kör alakú membrán1 : 1,59 : 2,14 : 2,30 : 2,65 : 2,92Bessel-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.

7. Módszerek összevetése

MódszerErősségeGyengeségeTipikus példa
Modális szintézispontos, olcsó, módusonként állítható hang és lecsengéscsak lineáris, időben állandó rendszerre; nemlineáris érintkezést nem adrúd, harang, hangszertest, ütőhangszer
Karplus–Strong / hullámvezetőnagyon olcsó, húr és csőnehezebb a testet és az inharmonicitást adnipengetett húr, síp
Euler / FDTD / tömeg-rugó hálózatnemlineáris, közben változó paraméter, bármilyen erőkis lépés kell, a frekvencia kissé pontatlan, sok számításgong, 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.

8. Levezetés: a lánc sajátértékei és alakjai

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.

9. A diszkrét rezgő pontossága és stabilitása

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ó.

10. Átviteli függvény és a módusok mint pólusok

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.

11. Érvényességi határok

  • Csak lineáris, időben állandó rendszerre igaz (a 7. pont is ezt mondja).
  • A Rayleigh-modell két paraméterrel írja le a \(\sigma_n(\omega_n)\) függvényt, ezért nem illeszthető tetszőleges lecsengésekhez; a Hangszer-tervező \(g\)-paramétere ugyanilyen egyszerűsítés.
  • A módusok számának csonkolása a magas részeket elhagyja; \(1/\omega_n\) miatt a hiba kicsi, de nem nulla.
  • A kalapács aluláteresztős modellje szemléltető egyszerűsítés, nem mért kontaktmodell.
  • A demó előbeállításainak számértékei szemléltető értékek.

Demó 1: tömegek lánca (módusok és lassított felvétel)

🎯 Mit csinálj?
N = 1-nél egyetlen rezgőt kapsz. Növeld N-et, és nézd, hogyan szól a lánc. A rajz lassított felvétel: kapcsold ki-be az egyes módusokat, így látod, hogy a mozgás a módusok összege. Az ütés után az animáció újraindul.

Hang az idő függvényében

Módusalakok (bal oldalon a fal)

Demó 2: Hangszer-tervező (modális bank)

🎯 Mit csinálj?
Válassz anyagot, és hallgasd meg. Állítsd a módusok számát, a lecsengést, a magas módusok fékét, a kalapács lágyságát és (húrnál) az ütés helyét. A sávdiagram mutatja a módusokat: magasság = erősség (dB), szín = lecsengés (rózsaszín rövid, kék hosszú).

Hang az idő függvényében

Kódok: minden demóbeállítás három nyelven

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
1Tömegek száma, végekN (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ékem [g], k [kN/m], c [N·s/m], beta [µs]
Ütés és hallgatás helyep, q (1…N)
Módusok be/kimask: lista [1,0,1] (Python, SC); egész szám, n. bit = n. módus (gen~)
Módusfrekvenciák, arányok, alakokPython: mode_info
Lassított felvételPython: 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ébena bank / ~modal kimenete
🔔 Ütésa program futtatása; gen~: in1 0→1
N = 1az 1. pont egyetlen rezgője (oscillator)
2Anyag (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, T60f1, M, T60
Magas módusok fékeg
Ütés helye (húrnál)pos
Kalapács vágófrekvenciájafc
Sávdiagram (erősség dB, T60)a/w és LN1000/s a visszaadott listákból

Közös alap (Python, SuperCollider, Max/MSP gen~)

Python

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())

SuperCollider

(
// 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;
)

Max/MSP gen~ (genexpr)

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.

Demó 1: tömegek lánca

Python

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))

SuperCollider

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;
)

Max/MSP gen~ (genexpr)

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;

Demó 2: Hangszer-tervező

Python

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ács

SuperCollider

A ~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
)

Max/MSP gen~ (genexpr)

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;

Feladatok

  1. Három beállítás (az 1. pont és az 1. demó után). Számold ki \(f_0\), \(\zeta\), \(T_{60}\) és \(c_{krit}\) értékét: (a) \(m=10\) g, \(k=76\) kN/m, \(c=0{,}049\) N·s/m; (b) \(m=5\) g, \(k=300\) kN/m, \(c=7{,}6\) N·s/m; (c) \(m=10\) g, \(k=76\) kN/m, \(c=55{,}1\) N·s/m. Ellenőrizd a láncdemóval (N = 1).
  2. N = 2 kézzel. Számold ki \(f_1\), \(f_2\) és a módusalakokat \(f_0=439\) Hz-re, majd az amplitúdóarányt \(a_2/a_1\) (ütés és hallgatás a 2. tömegnél).
  3. Ütés helye. N = 5, fal–szabad, hallgatás az 5. tömegnél. Állítsd az ütés helyét 1-től 5-ig. Minden helyen írd le, melyik módus gyengül, és hol a leg„fényesebb” a hang.
  4. Anyagok táblázata. Hallgasd meg mind a 7 előbeállítást a Hangszer-tervezőben. Töltsd ki: anyag, módusarány, lecsengés (rövid/közepes/hosszú), egy szó a hangszínre.
  5. Python. Futtasd a Kódok fül Demó 1 programját N = 5-re (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.
  6. Projekt. Tervezz saját hangszert (anyag, 6–10 módus, \(f_1\), \(T_{60}\), bomlás). Dokumentáld a paramétereket, és készíts 10 másodperces hangmintát.
  7. Házi. Tervezz marimba-rudat 1 : 4 : 10 aránnyal (3 módus), vagy saját harangot; magyarázd el, melyik paramétert miért választottad.

Önellenőrző kérdések

  1. Mi a módus?
  2. Hány módusa van egy \(N\) tömegű láncnak?
  3. Miért tompul a hang a lecsengés alatt?
  4. Fal–szabad láncon hol fényesebb a hang: a falnál vagy a szabad végén ütve?
  5. Mikor jobb az Euler-módszer a modális szintézisnél?

Szótár (magyar – angol)

FogalomAngolulJelentés
MódusmodeMozgásforma, ahol minden pont ugyanazon a frekvencián, rögzített arányban mozog.
Sajátfrekvencia, sajátalaknatural frequency, mode shapeA módus ωn és un: a det(K − ω²M) = 0 megoldásai.
Tömeg-, merevség-, csillapításmátrixmass, stiffness, damping matrixM, K, C: a mozgásegyenlet Mẍ + Cẋ + Kx = f mátrixai.
OrtogonalitásorthogonalityumTM un = 0, ha m ≠ n; ezért esik szét a rendszer független rezgőkre.
Módus-tömegmodal massmn = unTM un.
Módus-koordinátamodal coordinateqn: 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ásRayleigh dampingC = αM + βK; σn = (α + βωn²)/2.
T60T60, decay timeA lecsengés ideje 60 dB-lel (az amplitúdó ezredrészére): T60 = ln 1000 / σ.
Impulzusválaszimpulse responseA 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 resonatoryn = a₁yn−1 + a₂yn−2; a modális bank építőeleme.
Modális bankmodal filter bankPárhuzamosan futó rezgők összege.
Nyquist-frekvenciaNyquist frequencyfs/2; efölötti módust nem szintetizálunk (a demó 0,45·fs-nál vág).
InharmonicitásinharmonicityA résztónusok nem egész számú többszörösei az alaphangnak.
Inharmonicitási együtthatóinharmonicity coefficient BMerev húrnál fn = n f₀ √(1 + Bn²).
Szabad–szabad rúdfree–free barEuler–Bernoulli-rúd; módusarányai (4,73; 7,85; 11,0 …)² arányai.
Bessel-függvényBessel functionA kör alakú membrán módusai a Bessel-függvények zérushelyeiből adódnak.
Harang résztónusaihum, prime, tierce, quint, nominalAz 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 helyeexcitation / pickup pointAz erősség módusonként un(p)·un(q).
Kalapács-szűrőhammer lowpassA kalapács lágyságát egyszerű aluláteresztővel közelítjük.
Hullámvezető, Karplus–Strongdigital waveguide, Karplus–StrongKésleltetővonalas húr- és csőmodell; a modális szintézis alternatívája.
FDTD, tömeg-rugó hálózatfinite-difference time-domain, mass–spring networkIdőlépéses szimuláció; nemlineáris érintkezésre is jó.
ModalysModalys (IRCAM)Modális szintézisen alapuló fizikai modellező program.
Klank / DynKlankKlank, DynKlank (SuperCollider)Rezonátorbank: frekvencia-, amplitúdó- és 60 dB-es lecsengési lista.
gen~, genexprgen~, genexpr (Max)Mintaszintű jelfeldolgozás Max-ban, szöveges kóddal.

Jelölések és mértékegységek

JelEgységJelentés
m, k, ckg, N/m, N·s/mtömeg, rugómerevség, csillapítás
ω0, f0rad/s, Hz√(k/m), illetve ω0/2π
σ, ζ1/s, –lecsengési ráta, csillapítási tényező
ωdrad/scsillapított szögfrekvencia √(ω² − σ²)
un(j)–az n. módusalak a j. tömegnél
mnkgmódus-tömeg (a demóban m·S)
p, q–ütés és hallgatás helye (tömeg sorszáma)
α, β1/s, sRayleigh-együtthatók (a demóban β µs-ban)
g, fc–, Hzmagas módusok féke, kalapács vágófrekvenciája
fsHzmintavételi frekvencia

Források

JelHivatkozásMire 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/Modalysmodá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)