Hoe je elke gespeelde noot een galmstaart geeft op basis van hoe lang je die vasthield
Om elke gespeelde noot een eigen galm te geven die afhangt van de speelduur, meet je de tijd tussen note-on en note-off, koppel je die duur aan een decaytijd en pas je het resultaat toe op een galm die speciaal voor die noot is bedoeld. Laat de galm van de noot doorlopen nadat de geluidsbron is uitgeschakeld.
In deze tutorial bouwen we dat gedrag op in SuperCollider, inclusief MIDI-invoer en een test met overlappende noten. Het beoogde resultaat is eenvoudig: een kort ingedrukte noot laat een korte staart achter, terwijl een langer ingedrukte noot een langere staart achterlaat, zelfs als de galmen overlappen.
Waarom heeft elke noot een aparte galm nodig?
Neem twee overlappende noten. Je houdt de eerste twee seconden vast en tikt de tweede een kwart seconde aan. Als beide naar één galm gaan, verandert het aanpassen van de decay voor de tweede noot ook de verwerking van de resterende galm van de eerste noot. Eén gedeelde decayregeling kan niet twee onafhankelijke instellingen binnen dezelfde galmstatus behouden.
De routing moet daarom als volgt zijn: de geluidsbron van elke noot gaat naar een eigen galm, waarna de voltooide stemuitgangen worden gemixt. Dit is een architecturale gevolgtrekking: onafhankelijke staarten vereisen onafhankelijke verwerkingsstatus, tenzij het instrument of effect al een gelijkwaardige interne scheiding biedt.
Een polyfone patch biedt hiervoor een bruikbaar raamwerk. Cycling ’74 documenteert hoe Max’ poly~ steminstanties aanmaakt en thispoly~ gebruikt om elke instantie als bezet of beschikbaar te markeren. Voor deze taak moet een stem bezet blijven zolang de galmstaart klinkt, in plaats van direct bij note-off beschikbaar te worden. Cycling ’74: Polyfonie
Er is ook een timingbeperking: tijdens een liveoptreden is de uiteindelijke ingedrukte duur pas bekend wanneer de noot eindigt. De onderstaande implementatie gebruikt een voorlopige decay terwijl de toets ingedrukt is en stelt de gemeten decay in bij het loslaten. Als je de uiteindelijke decay vanaf het begin wilt toepassen, werk dan met notenduren die al in een sequentie beschikbaar zijn, of render de afzonderlijke noten achteraf.
Wat moet de ingedrukte duur aansturen?
Houd drie regelingen gescheiden: de release van het volume van het instrument, de decay van de galm en het wet-uitgangsniveau. Een langere instrumentrelease blijft geluid naar de galm sturen. Een hoger wet-niveau maakt het effect luider. Geen van beide zorgt op zichzelf voor een langere decay van de galm.
Decay betekent normaal gesproken ook geen exacte stoptijd. De documentatie van Abletons Reverb definieert de Decay-regeling als de tijd die nodig is voordat de staart is gedaald tot een duizendste van de oorspronkelijke amplitude, oftewel −60 dB. Dat onderscheid is belangrijk wanneer je een decayinstelling vergelijkt met het moment waarop een staart niet meer hoorbaar is. Ableton: Referentie voor Live-audio-effecten, Reverb
Gebruik voor een eerste implementatie deze uitdrukkelijk illustratieve koppeling, met alle waarden in seconden:
decay = min(6.0, max(0.25, 0.25 + 1.5 × heldDuration))
| Ingedrukte duur | Ingestelde decay |
|---|---|
| 0,10 seconden | 0,40 seconden |
| 0,25 seconden | 0,625 seconden |
| 1,00 seconde | 1,75 seconden |
| 2,00 seconden | 3,25 seconden |
| 4,00 seconden | 6,00 seconden, begrensd |
Deze waarden zijn ontwerpkeuzes, geen gemeten akoestische resultaten. Het minimum geeft korte tikken een hoorbare staart; de vermenigvuldigingsfactor bepaalt in welke mate het vasthouden het effect verandert; het maximum begrenst de ingestelde decay. Begin met deze koppeling en pas vervolgens die drie keuzes aan terwijl je de nootvelocity en het wet-niveau constant houdt.
Bouw een stem die na note-off blijft bestaan
In dit voorbeeld wordt SuperColliders ingebouwde GVerb gebruikt. Die accepteert een mono-ingang, produceert twee uitgangskanalen en biedt revtime in seconden. De documentatie vermeldt ook een CPU-piek bij het aanmaken en artefacten wanneer de kamergrootte verandert. Daarom houdt dit voorbeeld de kamergrootte vast en kun je het in eerste instantie het best met een paar gelijktijdige noten uitproberen. SuperCollider: GVerb
Elke stem bevat een kleine geluidsbron met twee oscillatoren, een korte volumerelease en één galm. Een aparte levensduurenvelop maakt de stem pas vrij nadat de galmstaart is uitgeklonken. SuperColliders EnvGen ondersteunt sustain-enveloppen die vrijkomen wanneer hun gate nul wordt, evenals een voltooiingsactie die de omhullende synth vrijgeeft. SuperCollider: EnvGen
Gebruik een nieuwe SuperCollider-sessie met een werkende stereo-audio-uitgang. Voer het volgende volledige setupblok uit en wacht vervolgens tot Held-note reverb ready in het Post-venster verschijnt. Er zijn geen samples of extra bibliotheken nodig. De code is aan de hand van de documentatie gecontroleerd, maar hier niet uitgevoerd omdat de SuperCollider-interpreter niet beschikbaar is.
(
s.waitForBoot {
SynthDef(\heldNoteVerb, {
|out = 0, freq = 440, amp = 0.035,
gate = 1, decay = 1, alive = 1|
var sourceEnv, lifetime, source, wet;
sourceEnv = EnvGen.kr(
Env.asr(0.01, 1, 0.02), gate
);
lifetime = EnvGen.kr(
Env.asr(0.01, 1, 0.03), alive,
doneAction: 2
);
source = (
SinOsc.ar(freq) + (0.25 * SinOsc.ar(freq * 2))
) * amp * sourceEnv;
wet = GVerb.ar(
source,
roomsize: 20,
revtime: Lag.kr(decay, 0.03),
damping: 0.5,
drylevel: 0,
earlyreflevel: 0.2,
taillevel: 0.5
);
Out.ar(out, ((source ! 2) + wet) * lifetime);
}).add;
s.sync;
~heldVoices = Dictionary.new;
~tailJobs = List.new;
~voiceGroup = Group.head(s);
~voiceKey = { |num, chan, src|
src.asString ++ ":" ++ chan.asString ++ ":" ++ num.asString
};
~decayFor = { |held|
(0.25 + (1.5 * held)).clip(0.25, 6.0)
};
~releaseHeld = { |num, chan = 0, src = 0|
var key, queue, entry, held, decay, node;
key = ~voiceKey.value(num, chan, src);
queue = ~heldVoices[key];
if(queue.notNil) {
if(queue.notEmpty) {
entry = queue.removeAt(0);
node = entry[0];
held = (Main.elapsedTime - entry[1]).max(0);
decay = ~decayFor.value(held);
node.set(\decay, decay, \gate, 0);
[\note, num, \held, held, \decay, decay].postln;
~tailJobs.add(Routine {
((2 * decay) + 0.1).wait;
node.set(\alive, 0);
}.play(SystemClock));
};
if(queue.isEmpty) { ~heldVoices.removeAt(key) };
};
};
~startHeld = { |vel, num, chan = 0, src = 0|
var key, node;
if(vel <= 0) {
~releaseHeld.value(num, chan, src);
} {
key = ~voiceKey.value(num, chan, src);
if(~heldVoices[key].isNil) {
~heldVoices[key] = List.new;
};
node = Synth(\heldNoteVerb, [
\freq, num.midicps,
\amp, (vel / 127) * 0.035
], ~voiceGroup);
~heldVoices[key].add([node, Main.elapsedTime]);
};
};
"Held-note reverb ready".postln;
};
)
De volgorde bij het loslaten is belangrijk: de stem krijgt tegelijk de decayinstelling en de wijziging van de bron-gate. De galm blijft verwerken terwijl de bron in 20 milliseconden uitklinkt. Na ongeveer tweemaal de ingestelde decay plus een kleine marge fade-out de volledige stem en maakt die zichzelf vrij.
Die laatste marge is een keuze voor resourcebeheer, geen meting van stilte. Vergroot de marge als de resterende staart bij het opruimen nog hoorbaar is. Vervang voor een groter instrument de vaste marge door monitoring van het uitgangsniveau plus een maximale levensduur. Het voorbeeld bewaart ook verwijzingen naar voltooide jobs tot het opruimen; het is dus een demonstratie en geen instrument dat onbeperkt kan blijven draaien.
Koppel gespeelde MIDI-noten aan de juiste stem
Een duurmeting hoort bij een specifieke nootinstantie. Alleen het tijdstip van de meest recente note-on bijhouden gaat al mis zodra noten overlappen. In plaats daarvan slaat de setup elke stem en de starttijd ervan op onder een sleutel met bron, kanaal en toonhoogte.
De MIDI-handleiding van SuperCollider laat zien hoe je MIDI initialiseert, ingangen verbindt en synthverwijzingen bewaart, zodat note-off het juiste geluid kan vrijgeven. De referentie voor MIDIdef specificeert callbackargumenten in de volgorde waarde, nootnummer, kanaal en bron. SuperCollider: MIDI gebruiken, SuperCollider: MIDIdef
Voer dit blok uit nadat de setup meldt dat die klaar is, om het instrument via aangesloten MIDI-ingangen te bespelen:
(
MIDIClient.init;
MIDIIn.connectAll;
MIDIdef.noteOn(\heldVerbOn, { |vel, num, chan, src|
~startHeld.value(vel, num, chan, src);
});
MIDIdef.noteOff(\heldVerbOff, { |vel, num, chan, src|
~releaseHeld.value(num, chan, src);
});
)
In dit voorbeeld wordt ‘ingedrukt’ gedefinieerd als het interval tussen ontvangen nootevents. Sustainpedaalgedrag is niet geïmplementeerd. Als je een door het pedaal verlengde duur wilt, stel dan de betreffende releasebewerkingen uit tot het pedaal wordt losgelaten en meet vervolgens dat langere interval.
Herhaalde aanslagen van dezelfde toonhoogte worden in een wachtrij opgeslagen en in volgorde van oud naar nieuw vrijgegeven. Dit is een expliciet koppelingsbeleid voor overlappende events met dezelfde toonhoogte op één bron en kanaal. Note-offs zonder overeenkomst doen niets; een note-on zonder overeenkomst blijft actief tot het opruimen. Controleer de eventkoppeling voordat je het instrument uitbreidt.
Test overlappende staarten voordat je het geluid aanpast
Test eerst het gedrag zonder keyboard. In de volgende sequentie worden dezelfde eventfuncties gebruikt als bij de MIDI-invoer: noot 60 duurt twee seconden, terwijl noot 67 halverwege begint en een kwart seconde duurt. De verwachte ingestelde decays zijn ongeveer 3,25 en 0,625 seconden.
(
~heldTest = Routine {
~startHeld.value(100, 60);
1.0.wait;
~startHeld.value(100, 67);
0.25.wait;
~releaseHeld.value(67);
0.75.wait;
~releaseHeld.value(60);
}.play(SystemClock);
)
SystemClock plant taken in seconden, waardoor deze test onafhankelijk is van de ingestelde muzikale tempo. In de praktijk kunnen kleine verschillen optreden tussen de afgedrukte en gevraagde duur. SuperCollider: SystemClock
Luister naar de korte staart van de hogere noot terwijl de lagere doorgaat, gevolgd door de langere staart van de lagere noot. Keer daarna de duren om. Deze tweede ronde helpt vaststellen dat de duur het resultaat bepaalt, en niet de toonhoogte. Vergelijk de afgedrukte paren van ingedrukte duur en decay met het geluid.
Voer het volgende opruimblok uit nadat je de MIDI-handlers hebt geïnstalleerd om dit voorbeeld te stoppen en te verwijderen. Het annuleert ook openstaande staartjobs voordat de stemmen worden verwijderd:
(
MIDIdef(\heldVerbOn).free;
MIDIdef(\heldVerbOff).free;
if(~heldTest.notNil) { ~heldTest.stop };
~tailJobs.do { |job| job.stop };
~tailJobs.clear;
~voiceGroup.free;
~heldVoices.clear;
)
Hoe spoor je staarten op die zich niet goed gedragen?
Als elke staart verandert wanneer één noot wordt losgelaten, controleer dan de audiorouting. Een tijdstempel per noot leidt nog steeds tot gedeeld gedrag als het resultaat ervan één galm aanstuurt na het mixen van de stemmen. Plaats de galm in elke stem of routeer geïsoleerde nootaudio naar afzonderlijke verwerkingsinstanties.
Als de staart precies bij note-off verdwijnt, controleer dan de levensduur van de stem. De volume-envelop moet de geluidsbron stoppen zonder de galm met die bron vrij te geven. Houd in een Max-implementatie de bezetstatus van de stem aan tot de wet-uitgang is uitgeklonken, volgens het gedocumenteerde allocatiemodel van poly~. Cycling ’74: Polyfonie
Als de decaygetallen niet kloppen, druk dan de eventkey en tijdstempels af voordat je de galm aanpast. In Max is het bijbehorende timingbouwblok timer, dat de verstreken tijd tussen events meet en standaard milliseconden gebruikt. Zet de eenheden expliciet om wanneer de bestemming seconden verwacht. Cycling ’74: timer
Als de prestaties onregelmatig worden, verminder dan het aantal gelijktijdige stemmen en controleer de galmkeuze voordat je de patch uitbreidt. Deze demonstratie hanteert geen stemlimiet en elke nieuwe noot maakt een nieuwe galm aan. Een productie-instrument heeft een expliciet allocatiebeleid nodig dat rekening houdt met zowel ingedrukte noten als nog naklinkende staarten.
Vervang de oscillator door de geluidsbron van je keuze per stem zodra de timing- en overlaptests werken. Houd dezelfde volgorde aan: wijs een stem toe, registreer de starttijd, meet bij het loslaten, stel de decay in en behoud de galm totdat de staart is uitgeklonken. Zo wordt de ingedrukte duur een herhaalbare speelregeling.
Bronnen
- Polyfonie — Cycling ’74-documentatie
- timer — Max-referentie — Cycling ’74-documentatie
- Referentie voor Live-audio-effecten — Ableton Reference Manual Version 12
- GVerb — SuperCollider Help
- EnvGen — SuperCollider Help
- MIDI gebruiken — SuperCollider Help
- MIDIdef — SuperCollider Help
- SystemClock — SuperCollider Help
