Algoritmiske trommebeats
Beatproduktion spiller en central rolle i megen nyere populærmusik. Vi kigger her primært på trommebeats, idet vi udforsker forskellige teknikker inden for algoritmisk beatproduktion. De fleste musikproducere, der selv laver musik på computere, kender nok den situation, hvor laver et beat ved at komponere et MIDI-klip, som så afspiller samples via et trommesampler-plugin i en DAW. Når man har lavet et fedt beat, kan man så kopiere det, så vi hører det samme beat i alle tre vers. Dette risikerer dog hurtigt at blive noget monotont og maskinelt, og derfor findes der i nogle DAW-programmer forskellige teknikker til at indarbejde forskellige små afvigelser i timing og dynamik.
Men i SuperCollider har vi adgang til et righoldigt bibliotek af patterns, som vi kan bruge til at skabe både fremtrædende og mere subtile variationer i beats, fra rytmik til klang, mikrotiming og dynamik. Når vi afspiller samples med en specialdesignet SynthDef på SuperColliders lydserver, kan vi samtidig bruge vores patterns til at skabe detaljeret, klanglig variation, som kun vanskeligt kan lade sig gøre med den relativt begrænsede MIDI-protokol i en traditionel DAW. Dette betyder selvfølgelig ikke, at man ikke må bruge en DAW - lydeksemplerne i dette afsnit er faktisk efterbehandlet med rumklang, da det giver en bedre fornemmelse af de ellers noget tørre trommelyde.
Forberedelse til beatfremstilling
Inden vi kan gå i gang med at bruge patterns til at fremstille trommebeats, skal vi forbered nogle redskaber og materialer. Først og fremmest skal vi bruge en SynthDef, der kan afspille vores samples med de klanglige og afspilningsmæssige justeringer, vi ønsker. Derudover skal vi selvfølgelig vælge og indlæse nogle passende samples til brug i vores beat.
SynthDef til oneshot-samples
Når vi skaber vores egne med trommebeats ud af eksisterende samples, er det nyttigt at bruge såkaldte oneshot-samples. Der er med dette udtryk blot tale om korte samples, som indeholder lyden af ét anslag af fx en tromme, en klavertangent, eller andet instrument. Derfor indretter vi en SynthDef på følgende måde:
- Vi afspiller den valgte buffer fra start til slut og afslutter derefter Synth'en med
PlayBuf
sdoneAction
-argument. Brug af SynthDef'en beror således på, at vores samples er velorganiserede og egnede til en sådan oneshot-anvendelse. - For at kunne styre afspilningshastigheden og fx afspille nogle samples baglæns indfører vi et SynthDef-argument kaldet
rate
. - På nogle akustiske trommer vil der opstår små variationer i de dominerende partialtoners frekvenser, alt efter hvor på trommeskindet, man slår, og hvorvidt skindet er udsat for varierende spændthed. For at kunne simulere disse små variationer indfører vi et SynthDef-argument kaldet
cent
, hvor vi kan "stemme" trommen et antal cent op eller ned. - For yderligere at kunne variere de valgte samples klangligt anvender vi to teknikker til ændring af klang:
- Lav- og højpasfiltre med tilhørende argumenter
lpf_cutoff
oghpf_cutoff
. - En simpel form for klanglig manipulation kaldet waveshaping1 med method'en
.htan
som kan styres med argumentetdrive
i intervallet 0-1. Dette kan skabe en mere forvrænget klang, hvis vi ønsker det.
- Lav- og højpasfiltre med tilhørende argumenter
SynthDef(\oneshot, {
arg amp = 0.1, out = 0, pan = 0,
buf, cent = 0, rate = 1,
drive = 0, lpf_cutoff = 20000, hpf_cutoff = 20;
var freqScale = (cent*0.01).midiratio;
var dur = BufDur.kr(buf) / rate.abs;
var sig = PlayBuf.ar(
numChannels: 2,
bufnum: buf,
rate: rate * BufRateScale.kr(buf) * freqScale,
loop: 1
);
sig = (sig* drive.linexp(0, 1, 1, 25)).tanh; // drive/distortion
sig = sig * (1 - (drive * 0.5)).pow(2.5);
sig = sig * Env.linen(0.001, dur - 0.002, 0.001).kr(doneAction: 2);
sig = LPF.ar(sig, lpf_cutoff);
sig = HPF.ar(sig, hpf_cutoff);
sig = Balance2.ar(sig[0], sig[1], pan, amp);
Out.ar(out, sig);
}).add;
SynthDef'en ovenfor baseres på stereo-samples. Hvis du i stedet ønsker at anvende mono-samples, kan du justere linje 7, så PlayBuf
læser én kanal i stedet for to fra den angivne buffer, samt ændre Balance2.ar(sig[0], sig[1], pan, amp)
til Pan2.ar(sig, pan, amp)
, så vi panorerer korrekt.
Valg af oneshot-samples
Som eksempelmateriale bruger jeg herunder udvalgte samples fra en samling af samples kaldet electro-drums2 af freesound.org-brugeren 'soneproject' (2013)3.
~kick = Buffer.read(s, "C:/lydfiler/252821__soneproject__kik2.wav");
~snare = Buffer.read(s, "C:/lydfiler/244354__soneproject__snr001.wav");
~clap = Buffer.read(s, "C:/lydfiler/252823__soneproject__clp2.wav");
// Vi kan teste vores SynthDef med disse tre samples
Synth(\oneshot, [\buf, ~kick, \pan, -0.6]);
Synth(\oneshot, [\buf, ~snare, \pan, 0]);
Synth(\oneshot, [\buf, ~clap, \pan, 0.6]);
Synth(\oneshot, [\buf, ~clap, \rate, -1.5]);
Valg af beattype
En simpel indgangsvinkel til at danne et trommebeat er som følger:
- Vi starter med at dele takten op i mindre segmenter.
- Derefter fremstiller vi en række patterns, som kan udfylde disse segmenter.
- Til sidst kombinerer vi disse patterns på forskellig vis.
Som eksempel kan vi tage et traditionelt, populærmusikalsk trommebeat i 4/4. Her kunne en helt enkel tilgang være at veksle mellem stortromme på de ulige taktslag, 1 og 3, og lilletromme på de lige taktslag, 2 og 4 ("backbeatet").
// Tilbagelænet tempo: 85 BPM
TempoClock.tempo = 85 / 60;
// ~kick og ~snare er defineret ovenfor
Pbind(\instrument, \oneshot, \buf, Pseq([~kick, ~snare], 4)).play;
Dette simple beat udfylder måske nok de mest grundlæggende funktioner, men det bliver hurtigt monotont at lytte til. Lad os derfor skabe nogle variationsmuligheder, hvor forskellige segmenter varer præcis ét taktslag, så de kan indgå på henholdsvis de ulige og lige taktslag og erstatte det simple beat her. Her viser jeg nogle eksempler, men du kan selvsagt med stor fordel skrive dine egne patterns i stedet.
Her er det oplagt at navngive de forskellige variationsmuligheder, så vi kan anvende dem efterfølgende ved at angive deres navn. Dertil kunne vi bruge en række variabler med unikke navne, men vi bruger i stedet en datastruktur, som minder om en liste, nemlig en Dictionary
. Som navnet antyder er en dictionary en samling af indhold, der kan tilgås ved hjælp af bestemte nøgler (små tekstbidder). Dette står i modsætning til lister, hvor vi tilgår elementerne ved hjælp af deres indeks (tal). Vi opretter en dictionary med Dictionary.new
og gemmer den under den globale variabel b
, så vi let kan henvise til den senere. Vi kan derefter tilføje en Pbind med b.put(\minPbind, Pbind())
og tilgå denne igen med b[\minPbind]
.
Da vi arbejder med variationsmuligheder, som skal vare præcist ét taktslag, vil jeg herunder notere nodeværdierne på SuperColliders måde, dvs. \dur, 1
svarer til ét taktslag. \dur, 0.5
svarer dermed til et halvt taktslag (hvad vi typisk omtaler som ottendedele), og så fremdeles.
Ulige taktslag
Som det første kan vi definere nogle grundlæggende nøgler og værdier/patterns, som segmenterne til de ulige taktslag kan have som udgangspunkt:
- Vi bruger SynthDef'en
\oneshot
, som vi definerede ovenfor. - Vi vælger bufferen
~kick
, som indeholder vores trommesample - Vi vælger ved hjælp af
Pgauss
at variere lydstyrken en lille smule, med middelværdi omkring -18 dB. - Parameteren
\drive
sættes i udgangspunktet til 0,2.
Vi gemmer disse med en Pbind under den globale variabel ~uligeBasis
. Bemærk, at denne Pbind, afspillet med ~uligeBasis.play;
kan generere et ubegrænset antal events og derfor ikke overholder vores strategi om at begrænse sig til ét taktslag. Dette løser vi imidlertid herunder, når vi skaber nye Pbinds med afsæt i denne.
~uligeBasis = Pbind(
\instrument, \oneshot,
\buf, ~kick,
\db, Pgauss(-18, 0.5),
\drive, 0.2,
\lag, Pgauss(0, 0.002),
);
Herefter kan vi bruge sammensætning af patterns til at skabe en række variationsmuligheder. Her skal vi holde tungen lige i munden, når vi arbejder med \dur
-nøglen og de patterns, som definerer hvor mange events, variationsmuligheden skal bestå af, da vores sekvenser skal vare præcist ét taktslag.
Første mulighed: Enkelt stortrommeslag
Her har vi bare et enkelt anslag ved kraftig lydstyrke og dermed den simpleste variationsmulighed, som vi kan kalde for \downbeat
.
b.put(\downbeat,
Pbindf(~uligeBasis, \db, Pgauss(-15, 0.5, 1))
);
b[\downbeat].play;
Anden mulighed: Ottendedele
Her har vi ganske enkelt to halve taktslag (dvs. ottendedele), som vi kalder \dobbelt
.
b.put(\dobbelt,
Pbindf(~uligeBasis, \dur, Pseq([0.5, 0.5]))
);
b[\dobbelt].play;
Tredje mulighed: Trioliserede sekstendedele
Her har vi et mønster af trioliserede sekstendedele, distortion-præget klang og faldende afspilningshastighed/tonehøjde, som lyder lidt \badass
.
b.put(\badass,
Pbindf(~uligeBasis,
\dur, Pseq([1/3, 1/6, 1/3, 1/6]),
\lpf_cutoff, 2500,
\drive, 0.6,
\rate, Pseries(1, -0.2),
)
);
b[\badass].play;
Lige taktslag
Til de lige taktslag definerer vi også først en Pbind med de grundlæggende indstillinger under den globale variabel ~ligeBasis
. Her lægger vi os med \lag
-nøglen en lille smule lidt frem på beatet, dvs. med mulighed for, at anslaget ligger nogle få millisekunder før den maskinelt definerede, ideelle timing. Vi varierer afspilningshastigheden en lille smule med \cent
-nøglen (jævnfør SynthDef'en ovenfor).
~ligeBasis = Pbind(
\instrument, \oneshot,
\buf, ~snare,
\lag, Pgauss(-0.003, 0.001),
\db, Pgauss(-20, 0.5),
\lpf_cutoff, 3000,
\cent, Pgauss(0, 1),
\drive, 0.2,
);
Første mulighed: En langstrakt lilletrommelyd
Et enkelt anslag, men med nedsat afspilningshastighed, hvilket strækker samplet til at vare mere end dobbelt så lang tid som oprindeligt. Den distortion-prægede klang giver anledning til navnet \cyberpunk
.
b.put(\cyberpunk,
Pbindf(~ligeBasis,
\dur, 1, \lpf_cutoff, 1800,
\drive, Pwhite(0.6, 0.8),
\rate, Pwhite(0.35, 0.48, 1),
)
);
b[\cyberpunk].play;
Anden mulighed: Sekstendedelstrioler
Her afspilles seks fordoblede lilletrommelyde med faldende lydstyrke, tilfældig transponering og skiftende stereo-placering. Vi kalder segmentet for \stagger
på grund af den systematiske forskydning.
b.put(\stagger,
Pbindf(~ligeBasis,
\dur, 1/6, \db, Pseries(-25, -2, 6),
\rate, Pseries([1.5, 3.7], Pwhite(-0.4, 0.4)).stutter(2),
\pan, Pxrand([-0.75, 0.75], inf).stutter(2).clump(2)
),
);
b[\stagger].play;
Tredje mulighed: Varierende underdeling
Den tredje mulighed er mest rytmisk interessant, da vi her deler vores ene taktslag op i en tilfældig underdeling valgt med Pwhite
. Hertil bruger vi Pkey
, som henviser til den værdi, der er knyttet til en anden (forudgående) nøgle. Afspilningshastigheden og dermed tonehøjden varieres betydeligt, og mikrotimingen justeres en smule, så vi ikke i udgangspunktet ligger så langt fremme på beatet som ellers. Panoreringen skifter, så overraskelsesmomentet tydeliggøres, og vi kalder derfor segmentet for \surprise
.
b.put(\surprise,
Pbindf(~lige,
\num, Pwhite(3, 9).stutter(inf),
\rate, Pexprand(0.5, 4.0, Pkey(\num).asStream),
\dur, 1 / Pkey(\num),
\drive, Pexprand(0.4, 0.5),
\pan, Pwhite(-0.8, 0.8),
\lag, Pgauss(0, 0.004)
)
);
b[\surprise].play;
Fjerde mulighed: Tynd ud
Toogtredivtedele, skiftende mellem ~snare
og ~clap
med "fortyndet" klang og det passende navn \tynd
, grundet højpasfilter med cutoff ved 2 kHz og øget afspilningshastighed. Panoreringen starter fra midt og bevæger sig gradvist ud mod skiftevis højre og venstre.
b.put(\tynd,
Pbindf(~ligeBasis,
\dur, 1/8, \hpf_cutoff, 2000,
\rate, Pseries(1.5, 0.2).stutter(2),
\buf, Pxrand([~snare, ~clap], 4).stutter(2),
\pan, Pseries(0, 0.1) * Pseq([1, -1], inf),
\drive, Pwhite(0.4, 0.7)
)
);
b[\tynd].play;
Sammensætning af variationsmuligheder i generative beat-opskrifter
Nu når vi endelig frem til sammensætning af vores variationsmuligheder i egentlige beats. Vi har en række valgmuligheder gemt under de to lister ~ulige
og ~lige
, som beskrevet ovenfor.
Enkel sekvensering
Som det første kan vi ganske enkelt vælge nogle af de mest enkle valgmuligheder og afspille dem i en valgt rækkefølge med Pseq
. Her bruger vi en direkte notation for at tilgå de enkelte elementer i b
.
Men det bliver hurtigt trivielt at skrive b[\navn]
frem for blot \navn
. For at finde de forskellige Pbinds from fra en Dictionary
inden for en pattern-kontekst, kan vi i stedet bruge pattern'et Psym
, som bruger symboler (dvs. tekststrenge i formen \tekst
) til at finde de ønskede patterns eller værdier fra dictonary'en.
Med andre ord: Hvis vi bruger Pseq
til at sekvensere symboler som \downbeat
og \surprise
, kan Psym
finde de relevante Pbinds frem fra vores dictionary b
. Det virker måske lidt overflødigt, men bliver hurtigt nyttigt, hvis vi skal notere navnene mange gange for at beskrive forskellige kombinationsmuligheder.
Psym(
Pseq([
\downbeat,
\cyberpunk,
\dobbelt,
\stagger
], 2),
b
).play;
Bemærk, at hvis vi eksekverer ovenstående kildekode flere gange, vil den overordnede sekvens være ens, men der vil også være små variationer, da de valgte Pbinds indeholder patterns, som genererer tilfældige værdier inden for de angivne parametre.
Hvis vi vil spille to variationsmuligheder på samme tid, kan vi notere en liste med de ønskede variationer. Her lader vi fx stortrommen spille i ottendedele også fra 2- og 4-slaget i takten.
Psym(
Pseq([
\downbeat,
[\dobbelt, \cyberpunk],
\downbeat,
[\dobbelt, \tynd],
], 2),
b
).play;
Varierede sekvenser med aleatorik
Vi kan selvfølgelig også i stedet for faste elementer i vores sekvens bruge patterns, der vælger mellem en række valgmuligheder, såsom Prand
.
Psym(
Pseq([
Prand([\downbeat, \dobbelt]),
\cyberpunk,
Prand([\dobbelt, \badass]),
Prand([\stagger, \surprise, \tynd])
], 4),
b
).play;
Er vi ligeglade med, om stortrommen rammer downbeatet og lilletrommen backbeatet, kan vi bede om en tilfældig rækkefølge med Pshuf
. Interessant nok kan man på grund af den gentagne rækkefølge godt fornemme pulsen, selvom underdeling og klang skifter for hvert taktslag.
Aleatoriske beats med konstant forandring
Vi kan få en automatisk liste med nøglerne til en dictionary med .keys.asArray
, og kombineret med Pxrand
og .stutter
kan vi få få et lidt kaotisk men metrisk velfungerende beat, hvor de tilfældigt valgte segmenter gentages for at skabe genkendelighed i strømmen af skiftende segmenter.
Psym(Pxrand(b.keys.asArray, 4).stutter(2), b).play;
Dette bliver dog hurtigt lidt for tilfældigt til at kunne udgøre et stabilt beat. Vi kan i stedet lave en algoritme, der bruger alle de genererede variationsmuligheder, men hvor vi bruger Pwrand
til at gøre nogle af mulighederne mere sandsynlige end andre. Dertil noterer vi først to lister med navnene på de forskellige segmenter i prioriteret rækkefølge.
~ulige = [\downbeat, \dobbelt, \badass];
~lige = [\cyberpunk, \stagger, \surprise, \tynd];
Her genererer vi med Array.rand
en liste med tilfældige tal mellem 0,01 og 1. Derefter bliver tallene skaleret, så de tilsammen giver 1 (med .normalizeSum
), og derefter sorteret med .sort
, så de mindste kommer først. Da vi har noteret de navne først, som vi ønsker er mest sandsynlige, vender vi listernes rækkefølge om med .reverse
. På den måde bliver de første variationsmuligheder i listerne ~ulige
og ~lige
mere sandsynlige end de sidste.
~uligeSandsynligheder = Array.rand(~ulige.size, 0.01, 1).normalizeSum.sort.reverse;
// -> [ 0.56223882208174, 0.34494655317443, 0.092814624743825 ]
~ligeSandsynligheder = Array.rand(~lige.size, 0.01, 1).normalizeSum.sort.reverse;
// -> [ 0.46594787914676, 0.28348766391966, 0.19495731192664, 0.055607145006942 ]
Derefter kan vi enkelt bruge Pwrand
til at generere vores beat.
Psym(
Pseq([
Pwrand(~ulige, ~uligeSandsynligheder),
Pwrand(~lige, ~ligeSandsynligheder),
], 16),
b
).play;
Der er her stadig tale om et beat i konstant forandring, hvilket måske er at strække definitionen af et beat. Men der er ingen der siger, at det er æstetisk forkert, eller at man skal bruge den genererede lyd præcist, som den er skabt. For at opnå et mere repetitivt præg kan man eksempelvis lade algoritmen køre og generere en række forskellige beats, som man så kan klippe i, repetere og viderebehandle i sin DAW efter forgodtbefindende. Eller man kan modificere ovenstående kildekode, så der skabes et mere repetitivt udtryk. Under alle omstændigheder er det forhåbentlig klart, at de klanglige og kombinatoriske variationsmuligheder er et reelt og righoldigt supplement til mere strømlinede sampler-plugins. Som i resten af bogen anbefales det kraftigt, at du på egen hånd arbejder med ovenstående teknikker og udvikler dine egne beats.
-
Waveshaping er en digital form for distortion. Kort fortalt fungerer den typisk ved at tilføje overtoner ved hjælp af en såkaldt transfer-funktion. Jo kraftigere et signal, der fødes ind i transfer-funktionen, desto mere udtalte bliver overtonerne/forvrængningen (frem for at overstyre). Derfor skaleres drive-argumentet her til en faktor mellem 1 og 25. ↩
-
electro-drums er udgivet til det offentlige domæne under Creative Commons-licensen CC0 1.0 og kan findes via platformen freesound. ↩
-
soneproject. (2013). Pack: Electro-drums. In Freesound. https://freesound.org/people/soneproject/packs/12711/ ↩