Klangflade og tekstur
Som vi så tidligere, giver granular syntese adgang til at strække eller transponere lyd på meget fleksible måder. Men teknik er også attraktiv på grund af dens evne til at skabe brede klangflader og abstrakte teksturer. Disse kompositionsmuligheder antyder vi herunder, men det er væsentligt at bemærke, at potentialet i denne teknik bedst kan udforskes gennem egne eksperimenter, da forskelligt lydmateriale vil give anledning til vidt forskellige klange, når det bearbejdes med granular syntese.
I det følgende bruger vi samme kalimba-sample som i de foregående afsnit, indlæst i buffer under variablen ~kalimba
.
Teksturdannelse i granular syntese
Ved at læse korte grains fra forskellige positioner i et sample, kan vi skabe en interessant, lydlig tekstur. For at skabe spredning og variation i teksturen kan vi fordele de læste grains i stereofeltet og transponere hvert enkelt grain et tilfældigt antal halvtoner op og ned.
Til dette formål kan vi bruge nogle specielle UGens, der genererer nyt output, hver gang de modtager en trigger. Her kan vi især bruge tilfældighedsgeneratorer som TRand.kr(minimum, maksimum, trigger)
eller TIRand.kr(minimum, maksimum, trigger)
, som henholdsvis genererer tilfældige decimaltal og heltal mellem et givet minimum og maksimum. Der findes også UGens som TExpRand
, der i sin funktion minder om vores gode ven Pexprand
, og TChoose
, der tilsvarende minder om Prand
, idet den vælger tilfældigt mellem et antal givne muligheder.
Lad os skabe en lydlig tekstur med GrainBuf
og nogle af disse redskaber:
- Her sætter vi tæthed til 10 for at få en kontinuerlig strøm af lyd.
- Grainvarigheden sættes til 25 ms.
- Triggerfrekvensen udregnes automatisk.
- Vi transponerer det enkelte grain op til to oktaver op eller ned med en kombination af
TIRand
og.midiratio
. - Vi læser med
TRand
det enkelte grain fra et tilfældigt sted i bufferen. - Vi placerer med
TRand
det enkelte grain tilfældigt i hele stereofeltets bredde.
{
var density = 10;
var grainDur = 0.025;
var trigFreq = (density / grainDur);
var trigger = Dust.kr(trigFreq);
GrainBuf.ar(
numChannels: 2,
trigger: trigger,
dur: grainDur,
sndbuf: ~kalimba,
rate: TIRand.kr(-24, 24, trigger).midiratio,
pos: TRand.kr(0, 1, trigger),
pan: TRand.kr(-1, 1, trigger)
);
}.play;
Dette må siges at resultere i en intens, abstrakt og tæt tekstur, der ville egne sig til en tæt baggrundslyd eller et lydligt intermezzo. Men som jeg tidligere har bemærket, holder den konstante og totale tilfældighed hurtigt op med at være perceptuelt interessant, og øret mættes hurtigt af den tætte tekstur. Men kombinerer vi disse teknikker med en mere ordinær sample-afspilning, kan vi bevæge os ind på nogle ganske interessante områder.
Klangtekstur med jitter
Vi kan kombinere elementer af den kaotiske tekstur med den teknik, vi bruger til at styre aflæsningspositionen med en pointer. Det gør vi konkret ved at tilføje lidt "støj" til pointeren. Man kan forestille sig en lynhurtig DJ, der dog ryster lidt på hånden og derfor springer lidt frem og tilbage i lyden. Støjen, som man med et engelsk udtryk kan kalde for jitter, kan vi generere med TRand
og lignende UGens, som vist ovenfor. Vi indfører dertil et argument jitter
, som angiver den maksimale afstand til pointeren, vi læsepositionen på springe til - målt i sekunder1. Vi laver også justerbar spredning i stereofeltet med et argument kaldet spread
.
~shaky = {
arg transpose = 0, moveRate = 1,
jitter = 0.01, spread = 0.1;
var buf = ~kalimba;
var numFrames = BufFrames.kr(buf);
var pointer = Phasor.ar(
rate: moveRate,
start: 0,
end: numFrames
) / numFrames;
var trigger = Dust.kr(200);
var jit = TRand.kr(jitter.neg, jitter, trigger) / BufDur.kr(buf);
var pan = TRand.kr(spread.neg, spread, trigger);
GrainBuf.ar(
numChannels: 2,
trigger: trigger,
dur: 0.1,
sndbuf: buf,
rate: transpose.midiratio,
pos: pointer + jit,
pan: pan
) * 0.1;
}.play;
Når ovenstående er startet, kan vi justere på indstillingerne og introducere jitter og stereo-spredning med .set
-method'en. Vi kan også justere transponering og pointerens fart.
// Fordeling af grains i stereofelt (høres bedst i hovedtelefoner)
~shaky.set(\spread, 0.3)
~shaky.set(\spread, 1)
// Jitter
~shaky.set(\jitter, 0.1)
~shaky.set(\jitter, 0.5)
// Langsom eller stillestående pointer
~shaky.set(\moveRate, 0.5)
~shaky.set(\moveRate, 0)
// Tilfældig transponering
~shaky.set(\transpose, rrand(-12, 12))
Ønsker man at gå videre ud ad denne sti, kan man starte med at tilføje en tilsvarende form for jitter til transponering. Dette overlades til den nysgerrige læser at implementere på egen hånd.
Klangflader
Hidtil har vi anvendt Phasor
som pointer, hvilket resulterer i en lineær bevægelse frem eller tilbage. Men vi er på ingen måde tvunget til at tænke statisk eller lineært på denne eller andre parametre. Ved at modulere parametrene med envelopes og LFO'er kan vi fremmane andre lydlige forløb og måske endda abstrakte rytmer, som skaber klanglige mønstre over tid. Her handler det procesmæssigt om at vælge og indstille modulatorerne omhyggeligt, lytte til resultatet, og tune modulatorerne, lytte igen og så fremdeles.
En atmosfærisk rejse baglæns i kalimba-samplet
Når vi komponerer på dette abstraktionsniveau, dvs. uden brug af patterns som formgivende element, kan vi i stedet bruge envelopes. Som eksempel kan vi tage grain-tætheden som en central kompositorisk parameter. Til at styre denne opretter jeg en ny envelope med en varighed på i alt 30 sekunder, som kan ses på et plot herunder.
~densityEnv = Env.new(
levels: [0.1, 15, 75, 15],
times: [5, 10, 15],
curve: [4, -5, 3]
);
~densityEnv.plot;
Sammen med envelopen til styring af tæthed anvendes der en Env.linen
til den overordnede volumen i kompositionen, og en simpel Line
styrer sammen med en smule jitter læsepositionen i bufferen. Grains med en varighed på 200 ms kan måske knap nok kaldes grains, men det er en mindre detalje.
{
var density = EnvGen.kr(~densityEnv);
var grainDur = 0.200;
var trigFreq = density / grainDur;
var trigger = Dust.ar(trigFreq);
var env = Env.linen(sustainTime: 30, releaseTime: 3, curve: \sin).kr(2);
GrainBuf.ar(
numChannels: 2,
trigger: trigger,
dur: grainDur,
sndbuf: ~kalimba,
rate: TRand.ar(-0.1, 0.1, trigger).midiratio,
pos: Line.kr(0.95, 0.05, 30) + TRand.ar(-0.01, 0.01, trigger),
pan: TRand.ar(-1, 1, trigger),
) * 0.1 * env;
}.play;
Bemærk, at der ikke er tilføjet rumklang til dette lydeksempel. Fornemmelsen af rumlighed stammer fra dels stereospredningen, dels fra en chorus-lignende effekt af de mange samtidigt klingende grains.
En transponeringssekvens, som går i stå og bevæger sig på samme tid
Hvis vi vil arbejde med lidt en lidt mere tonalt dynamisk komposition, kan vi bruge en envelope til at modulere GrainBuf
s rate
-argument. Herunder anvender jeg en Env.circle
med en række transponeringstrin, der fungerer lidt ligesom en LFO og omregnes til afspilningshastighed med .midiratio
. Til envelopens curve
-argument angives værdien \step
, hvilket får envelopen til at bevæge sig i trin frem for i gradvise kurver.
Derudover kan vi under en lokal variabel xline
definere en XLine
, som bevæger sig i en eksponentiel bane fra 1 til 3 over 30 sekunder. Denne UGen spiller en central rolle, da den modulerer flere andre parametre:
- Den føromtalte envelope til modulation af tonehøjde bliver gradvist strakt, da segmenternes varigheder skaleres med
xline
. Tempoet i sekvensen bliver derfor langsommere og langsommere. - Skiftene mellem modulationstrinnene sker pludseligt, men med
.lag
glider vi kort mellem værdierne.xline
modulerer her hvor hurtigt dette slide sker. GrainBuf
producerer i modsætning til tidligere eksempler ikke to kanaler men én. Til gengæld bliver den fordoblet på grund af multichannel expansion i triggerenDust
samt denLFTri
, der bestemmer aflæsningspositionen. I begyndelsen læser vi fra den samme position i bufferen (0.1), men over tid vokserLFTri
s rækkevidde medxline
, og vi hører som resultat større og større forskel på venstre og højre kanal.
{
var density = 25;
var grainDur = 0.100;
var trigFreq = density / grainDur;
var trigger = Dust.ar(trigFreq.dup(2));
var xline = XLine.kr(1, 3, 30);
var rate = EnvGen.kr(
Env.circle(
levels: [-12, -8, -5, -14],
times: [1, 1, 2, 1],
curve: \step
), timeScale: xline).lag(0.05 * xline.pow(2)).midiratio;
var env = Env.linen(sustainTime: 30, releaseTime: 5, curve: \sin).kr(2);
GrainBuf.ar(
numChannels: 1,
trigger: trigger,
dur: grainDur,
sndbuf: ~kalimba,
rate: rate,
pos: LFTri.kr([10, 10.1]).range(0.1, 0.1 * xline),
) * 0.1 * env;
}.play;
Videre perspektiver med granular syntese
Det er helt forståeligt, hvis eksempler som det foregående er vanskelige at gennemskue ved første øjekast. Men hvis man læser grundigt op på de grundlæggende emner som envelopes, skalering og multichannel expansion, er det bestemt muligt at forstå og lade sig inspirere af kildekoden. Rent teknisk er granular syntese et komplekst emne med mange muligheder og parametre, og i dette kapitel er kun et par grundlæggende teknikker og kompositionsmuligheder antydet. For videregående emner såsom granulering af et live-lydsignal henvises nysgerrige læsere til Eli Fieldsteels SuperCollider Tutorial: 26. Granular Synthesis, Part I/II (Fieldsteel, 2020a, 2020b)2 3.
Da vi grundet de tekniske kompleksiteter ikke her har implementeret granular syntese i en SynthDef-form, er dette naturligvis en oplagt øvelse for læseren. Her gælder det blot om at læse godt op på SynthDef-konstruktion og så ellers eksperimentere løs på egen hånd. God fornøjelse med det!
-
Bemærk her, at vi både kan springe frem og tilbage. Det effektive "vindue" vi læser i bufferen fra er derfor i praksis dobbelt så langt som jitter-argumentets værdi. ↩
-
Fieldsteel, E. (2020a). SuperCollider Tutorial: 25. Granular Synthesis, Part I. https://www.youtube.com/watch?v=WBqAM\_94TW4 ↩
-
Fieldsteel, E. (2020b). SuperCollider Tutorial: 26. Granular Synthesis, Part II. https://www.youtube.com/watch?v=MnD8stNB5tE ↩