Livecoding: Programació creativa en temps real

So amb Strudel

Strudel és un port del llenguatge de patrons TidalCycles a JavaScript. Strudel es pot executar directament al navegador i ens permet fer música en temps real. La seva sintaxi bàsica ens resultarà familiar després de veure Hydra, perquè tots dos són extensions de JavaScript: la forma d’escriure funcions, passar paràmetres, i encadenar-les entre elles serà la mateixa.

Strudel té una gran quantitat de funcions i efectes per treballar amb so, però veurem que podem començar a crear patrons interessants amb molta poca cosa, i anar-les incorporant de mica en mica.

A més de la generació de so directa, Strudel té capacitats de comunicació avançades que ens permeten interconnectar-lo amb sintetitzadors i control·ladors externs.

Tindrem dues formes de generar so directament amb Strudel: amb samples o amb els sintetitzadors que porta incorporats.

Un sample o mostra és un fragment d’un enregistrament de so. Els samples poden ser de qualsevol tipus: veu, instruments, sorolls, etc. Els samples es poden combinar entre ells per crear noves textures sonores.

Un sintetitzador és un programa o dispositiu electrònic que genera so mitjançant la síntesi de senyals. Els sintetitzadors poden crear una gran varietat de sons, des de sons d’instruments musicals fins a efectes sonors únics.

Treballarem amb els samples que ja porta Strudel incorporats, però també es poden carregar samples externs.

Primers exemples

Els exemples d’aquesta guia són interactius. Pots modificar el codi i executar-lo amb el botó ▶, o amb la combinació de tecles Ctrl + Enter.

Per parar el so, prem el botó ⏹ o la combinació de tecles Ctrl + . (Control + punt).

Per generar un so amb Strudel utilitzarem la funció sound (o la versió curta s), i li posarem com a paràmetre el so que volem reproduir.

A la pàgina d’Strudel, a la dreta, podem seleccionar sound per veure els samples disponibles. Podem clicar sobre un nom per escoltar-lo.

sound("east")

Pot passar una estona fins que el so es reprodueixi després d’executar el codi, o que no ho faci el primer cop que cliquem a sobre del seu nom. Això és perquè el so s’ha de carregar.

Després d’executar l’exemple, el sample anomenat east sonarà repetidament. Strudel organitza el temps en cicles, similars als compassos musicals. El nostre so s’està reproduint a l’inici de cada cicle.

Però si mirem la llista de samples, veurem que east té un 9 al costat. Aquest nombre ens diu que realment east és una col·lecció de samples que conté 9 samples diferents, numerats del 0 al 8. Per defecte, s’està reproduint el primer, que és el 0, però podem selccionar-ne qualsevol afegint uns dos punts i el número del sample que volem:

sound("east:4")

Alternativament, ho podem fer enllaçant el so amb la funció n:

sound("east").n(4)

O podem enllaçar n amb sound en l’ordre invers:

n(4).sound("east")

Veurem la diferència entre aquestes dues formes d’enllaçar funcions més endavant.

Podem escriure més d’un sample a la funció sound, separant-los amb un espai:

sound("east:4 casio")

Els dos samples sonen un després de l’altre, però el temps del cicle és el mateix que abans. Si posem més sons encara, sonaran més ràpidament:

sound("east:4 casio clap clap")

En aquest últim exemple, podem pensar que estem en un compàs de 4/4, i que cada so correspon a un temps. Si volem un silenci, el podem fer amb un guió:

sound("east:4 casio - clap")

Un sol nombre es pot escriure directament, com a n(4), però sempre que hi hagi alguna cosa més, cal escriure-ho entre cometes, com a sound("east").

Tempo

El tempo a Strudel es medeix en cicles per segon (CPS), i el seu valor per defecte és de 0,5. Podem modificar el tempo amb la funció global setcps() o amb la funció cps() aplicada a un patró:

sound("east:4 casio clap clap").cps(0.6)
setcps(0.6)
sound("east:4 casio clap clap")

Molts sistemes digitals de música utilitzen els BPM (beats per minut) com a unitat de mesura del tempo. Un beat correspon a un temps, mentre que un cicle correspon a un compàs. Per tant, si suposem un compàs 4/4, un cicle equival a 4 beats. Així, si volem aconseguir un tempo de 135 BPM, podem escriure setcps(135/60/4).

Bancs de sons

Si mirem a la pestanya drum-machines de la pàgina d’Strudel, veurem que hi ha diversos samples que comencen pel mateix nom, per exemple tr909, i acaben sempre amb les mateixes terminacions: bd, sn, hh, etc.

Els noms com tr909 fan referència a màquines de ritmes, com la Roland TR-909.

Una màquina de ritmes és un dispositiu electrònic que genera ritmes i percussions. Les màquines de ritmes són molt populars en la música electrònica i el hip-hop. La TR-808 i la TR-909 són dues de les màquines de ritmes més icòniques de la història de la música electrònica.

Les lletres del final d’aquests noms fan referència a les parts d’una bateria:

Kit de bateria
  1. bd, bass drum, és el bombo.

  2. lt, low tom, és el tom baix o goliat.

  3. sn o rim, snare drum o rimshot, són el caixa i el rimshot.

  4. mt o ht, mid tom o high tom, són els tom mitjà i alt.

  5. hh o oh, hi-hat tancat o obert.

  6. rd o cr, ride cymbal o crash cymbal, són els plats de ride i crash.

També tenim altres noms de percussió com cb (cow bell) o cp (clap).

En comptes d’escriure el nom de cada so, podem seleccionar el nom d’un dels bancs i utilitzar les abreviatures per cada tipus de so de percussió:

sound("bd hh sd hh bd hh sd oh").bank("tr909")

Strudel selecciona un banc per defecte, així que és perfectament vàlid no especificar-ne cap:

sound("bd cb hh hh lt sd sd")

Mini-notation

Els patrons que hem estat utilitzant, com "bd sd" o east:4 casio, són una forma de notació que anomenem mini-notation i que ens permet descriure patrons musicals ràpidament. En aquest apartat veurem algunes de les notacions de la mini-notation. La pàgina de referència de la mini-notation conté totes les possibilitats.

Agrupació de sons []

Podem agrupar sons amb claudàtors []. Els sons agrupats ocupen un sol temps:

sound("bd [sd cp] [cp bd] sd")

Això és equivalent a:

sound("bd - sd cp cp bd sd -")

Multiplicació *

La multiplicació * ens permet repetir un so diverses vegades. Totes les repeticions juntes ocupen un temps:

sound("bd sd cp*4 sd")

Això és equivalent a:

sound("bd sd [cp cp cp cp] sd")

Podem combinar les diverses notacions que anem aprenent:

sound("bd [sd hh*4] [cp bd]*2 sd")

Divisió /

La divisió alenteix una seqüència. Per exemple, si dividim per dos, el fragment de codi s’executarà durant dos cicles en comptes d’un:

sound("cp hh/3")

En aquest exemple podem escoltar com el hi-hat s’activa només un de cada tres cicles, mentre que el clap sona a cada cicle.

sound("bd sd bd [sd sd/4]*2")

Aquí, la caixa del quart temps sona sempre dues vegades per la multiplicació, però una de cada dues vegades sona un tercer cop.

Claus angulars < >

Habitualment, la nostra seqüència de sons dura un cicle i, si hi afegim més sons, el què passa és que s’accelera la seqüència per encabir-los tots en un sol cicle. Amb les claus angulars, cada so es produeix en un cicle diferent. L’ús de claus angular és equivalent a dividir pel nombre de sons, amb l’avantatge de no haver d’escriure el nombre i modificar-lo si afegim o traiem sons.

sound("<cp sd hh>")

En aquest exemple la seqüència completa dura tres cicle, i en cadascun d’ells només hi ha un so.

sound("<cp sd hh>*5")

Ara estan sonant 5 sons per cicle, de manera que la seqüència es deplaça en el temps (és a dir, el clap no sempre està al primer temps). Això tindrà més gràcia quan fem sonar diverses seqüències simultànies i podem mantenir una estructura estable a la vegada que una altra va variant.

sound("[bd <sd cp*2>]*2")

La seqüència que obtenim aquí és equivalent a:

sound("bd sd bd [cp cp]")

Comes ,

Separar dos sons amb una coma farà que sonin simultàniament.

sound("[bd,siren] [bd,sd] [bd clap] [sd,hh hh*2]")

Arroba @

L’arroba ens permet allargar un so. En el següent exemple, el cicle està dividit en tres parts; el primer so ocupa dos terços del cicle i el segon un terç:

sound("saxello_stacc@2 saxello_stacc")

Admiratiu !

L’admiratiu serveix per repetir un so. A diferència de l’asterisc, cadascun dels sons ocupa un temps diferent:

sound("bd sd cp!4")

Això és equivalent a:

sound("bd sd cp cp cp cp")

Barra vertical |

La barra vertical ens permet triar entre diversos sons o expressions aleatòriament:

sound("bd!3 - | [bd sd]!2 | clap*4")

Interrogant ?

L’interrogant farà que un so o expressió tingui un 50% de probabilitat de sonar:

sound("bd [sd sd?] [bd,clap?] [sd,hh?*4]")

Ritmes euclidians ()

Els parèntesis ens permeten crear ritmes euclidians. Els ritmes euclidians són una forma de distribuir un nombre determinat d’esdeveniments en una certa quantitat de caselles, de manera que cada esdeveniment ocupa una casella i volem que quedin el més uniformament distribuïts possible.

Per exemple, si volem repartir 3 esdeveniments en 8 espais, podem fer-ho de la següent manera:

Distribució euclidiana

Els ritmes euclidians són molt útils per crear patrons rítmics interessants i variats, i són extremadament fàcils de generar amb Strudel. El ritme de l’esquema anterior es pot escriure així (amb el bombo posat com a referència d’on comença cada cicle):

sound("bd,hh(3,8)")

Haguéssim pogut distribuir els 3 esdeveniments en els 8 espais de diferents maneres, per exemple, movent tot el patró una posició a la dreta:

sound("bd,hh(3,8,1)")

Distribució euclidiana desplaçada una posició

O desplaçant-lo dues posicions (noteu com l’últim esdeveniment ha tornat a la primera posició):

sound("bd,hh(3,8,2)")

Distribució euclidiana desplaçada dues posicions

sound("sd(2,4,1), bd(4,4), hh(<5!3 3>,8,1), oh(1,4,3)/8")
  .bank("linndrum")

Mini-notation a qualsevol lloc!

Hem vist al principi que podem seleccionar els samples d’una col·lecció amb la funció n. També podem posar mini-notation a aquest (i a qualsevol altra) funció:

n("[0 0] 1 5, 2 [<3 7 8*2> 4]").sound("wind")

Podem fins i tot utilitzar mini-notation a diverses funcions alhora:

n("[<0 1> 3 0*2 1!2]").sound("east jazz")

En aquest cas, hem de pensar que l’estructura de sons sempre ve donada pel primer patró que hi ha. En aquest exemple, el patró a n determina la quantitat i ritme dels sons, i el patró a sound determina quines col·leccions de samples s’utilitzen: a la primera mitat del cicle s’utilitza east, i a la segona jazz. Si ens hi fixem, el patró de n divideix el cicle en 5 parts, així que el canvi de col·lecció es produirà just al temps 2 i mig. És per això que a la part 0*2 no sonen dos cops el mateix so: el primer 0 sona east:0 i el segon jazz:0.

Notes

Si has estat provant diversos samples, potser hauràs observat que alguns d’ells (per exemple, piano), no responen bé a l’ús de n(). Aquests samples estan preparats per utilitzar-los de forma melòdica: els diferents sons de la col·lecció corresponen a mostres del mateix instrument en diferents tons. Els tons que no han estat mostrejats es creen a partir de la modificació de la mostra que té un to més proper. Aquest procés és automàtic.

Podem crear melodies utilitzant la funció note(). Per especificar les notes ho podem fer de dues maneres:

  • Amb el número de la nota (segons l’estàndard MIDI).

    Notes MIDI
  • Amb el nom de la nota (amb la notació anglesa).

    Notes
    1. Equivalència entre sistemes de notació

Sistema llatí

Sistema anglès

Do

C

Re

D

Mi

E

Fa

F

Sol

G

La

A

Si

B

El número després del nom de la nota indica la seva octava. Per exemple, C4 és el Do de la quarta octava, que correspon al Do central del piano. C5 és el Do de la cinquena octava, i així successivament.

Si no s’especifica l’octava, per defecte Strudel utilitza la tercera octava.

Amb aquest coneixement, podem començar a crear melodies:

note("g eb d c").sound("piano")

Podem combinar el que ja sabem sobre mini-notation per crear jocs melòdics més complexos:

note("<g c> [eb d] ab c,c2 g2 <d2 bb2 f2> f2,[c6 - - c6 - - g6 -]")
.sound("piano")

Escales

En comptes d’escriure totes les notes d’una melodia a mà, podem especificar una escala i utilitzar els graus relatius de l’escala seleccionada.

L’escala de Do major està formada per les notes: Do, Re, Mi, Fa, Sol, La i Si. Do és el primer grau (nota 0 en Strudel), Re és el segon (nota 1), i així successivament. La nota Si és el setè grau de l’escala de Do major.

L’escala de do major sonaria així:

n("0 1 2 3 4 5 6 7")
.scale("c:major")
.sound("gm_acoustic_bass")

Algunes escales que podem utilitzar són:

  • major

  • minor

  • dorian

  • mixolydian

  • minor:pentatonic

  • major:pentatonic

n("<0!4 -3> 3 <4 7*2 [4 6 2]> -1 <[1 4] [0,4] 2>")
.scale("bb4:<mixolydian dorian>")
.sound("piano")

Velocitat

Modificar la velocitat de reproducció d’un sample és similar a canviar-ne el to: un sample reproduït al doble de velocitat sona una octava més aguda, i un sample reproduït a la meitat de velocitat sona una octava més greu. Normalment, canvíem la velocitat dels samples no per crear melodies (per això és molt més pràctic fer-ho amb note), sinó per crear efectes sonors interessants. Podem modificar la velocitat de reproducció dels samples amb la funció speed():

sound("sd*4").speed("1.3")
sound("numbers").n("<0 1>").speed("<1.4 1 .5 .8 1.8>")

Múltiples patrons simultanis

Per fer sonar més d’un patró alhora només cal que escrivim $: al principi de cadascun d’ells.

Aquest exemple, extret de la documentació oficial, mostra com podem combinar el que hem après per crear música:

$: note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
.sound("gm_synth_bass_1").lpf(800)

$: n(`<
[~ 0] 2 [0 2] [~ 2]
[~ 0] 1 [0 1] [~ 1]
[~ 0] 3 [0 3] [~ 3]
[~ 0] 2 [0 2] [~ 2]
>*4`).scale("C4:minor")
.sound("gm_synth_strings_1")

$: sound("bd*4, [~ <sd cp>]*2, [~ hh]*4")
.bank("RolandTR909")

Podem entendre totes les parts de l’exemple excepte la funció lpf, que veureu una mica més endavant.

Pots afegir _ al davant d’un $ per silenciar només una de les parts.

Efectes de so

Strudel ens ofereix múltiples efectes per modificar el so. Els efectes es poden aplicar a qualsevol so, i es poden combinar entre ells, i en aquest cas, l’ordre afecta al resultat. Pots consultar la llista d’efectes disponibles a la documentació oficial. Aquí ens veurem alguns dels més comuns.

Efectes bàsics

Aquests són alguns dels efectes més senzills i d’ús comú:

  • gain: controla el volum del so. El valor per defecte és 1, i 0 silencia el so. El guany és exponencial, així que amb variacions petites n’hi ha prou.

    s("hh*8").gain(".4!2 1 .4!2 1 .4 1").fast(2)
  • pan: controla la posició estèreo del so. Per defecte és 0.5, centrat. 0 és completament a l’esquerra i 1 completament a la dreta.

    s("hh*4").pan("[0 1]*2")

Filtres

Els filtres són efectes que modifiquen el so eliminant-ne algunes freqüències.

Els filtres més comuns són:

  • lpf (low-pass filter, filtre passa-baix): permet el pas de les freqüències més baixes que el límit (threshold) especificat, i atenua les freqüències que són més altes.

  • hpf (high-pass filter, filtre passa-alt): el contrari que un filtre passa-baix: permet el pas de les freqüències més altes que el límit, i atenua les freqüències més baixes.

  • bpf (band-pass filter, filtre passa-banda): permet les freqüències al voltant de la freqüència especificada i atenua les que són més altes o més baixes.

Nota com canvia el so quan canviem la freqüència de tall:

sound("sawtooth").lpf("<400 800 2000 4000 8000>")._scope()

_scope() dibuixa un petit oscil·loscopi a sota del codi. Si no el veus, intenta executar el codi un cop més sense parar-lo abans.

En el següent exemple, podem sentir com el so es va apagar a mesura que abaixem la freqüència de tall del filtre. El hihat, que té sobretot freqüències altes, desapareix quan la freqüència de tall és baixa. Prova el mateix exemple amb un filtre passa-alt (hpf) i sentiràs com és el bombo el que desapareix amb freqüències altes. Amb un filtre passa-banda (bpf), només s’escoltaran els sons que tenen una freqüència propera a la freqüència de tall, així que el hihat i el bombo es tornaran el protagonisme. La caixa, com que és un so amb un rang molt ampli de freqüències, modificarà el seu so, però sempre estarà present:

s("bd sd [~ bd] sd,hh*6").lpf("<4000 2000 1000 500 200 100>")._scope()

Tots els filtres tenen un segon paràmetre relacionat, l’anomenat factor Q o factor de qualitat, que controla el comportament del filtre al voltant de la freqüència de tall. El factor Q està molt vinculat amb la resonància del filtre i podem sentir com amb Q elevats es distorsiona el so. Un valor de Q baix fa que el filtre sigui més suau i natural, mentre que un valor de Q alt fa que el filtre sigui més agressiu i afilat. En filtres passa-banda, el factor Q determina l’amplitud del filtre, és a dir, si freqüències properes a la de tall es veuen afectades o no; un Q alt fa que el filtre sigui més selectiu, mentre que un Q baix fa que el filtre sigui més ampli.

Podem indicar el factor Q de dues maneres:

  1. Amb una funció específica: lpq(), hpq() o bpq() depenent del filtre que estiguem utilitzant.

  2. Amb mini-notation, com un segon paràmetre després de la freqüència: lpf("2000:2") o hpf("2000:4").

Un factor Q elevat augmenta el volum del so al voltant de la freqüència de tall, així que hem d’anar amb compte.

sound("square").lpf("<200 400 800 1200>:<.5 1 5 10 20>")._scope()

En aquest exemple, podem escoltar com el filtre fa destacar cadascuna de les veus per torns:

note(`<c2*2 [g2 f2]>,
c4 [c3 <d3 f3> g3 <e3 g4 g5>],
<[c6*3 <a6 [a6@2 g6]>] ->`)
.sound("piano")
.bpf("<1500!4 300!4 100!4>").bpq(.5)._scope()

El símbol ` permet escriure un patró llarg en diverses línies.

Podem consultar les freqüències de cada nota del piano a la Wikipedia.

Envolvents

Un envolvent decriu com evoluciona un so al llarg del temps. Per exemple, imaginem que toquem la tecla d’un piano. Quan ho fem, el so augmenta de volum ràpidament, arriba a un màxim que després disminueix, però que continua sonant mentre tenim la tecla premuda, i finalment, quan alliberem la tecla, el so disminueix ràpidament fins a apagar-se. L’envolvent és la representació gràfica d’aquest procés, que és diferent segons l’instrument.

En sintetitzadors, a diferència dels instruments més tradicionals, l’envolvent és pot controlar i modificar (amb els anomenats generadors d’envolvent).

En el següent gràfic podem veure un dels esquemes d’envolvent més utilitzat, l’ADSR:

Envolvent ADSR

A partir de l’obra d’Abdull, disponible a https://commons.wikimedia.org/wiki/File:ADSR_parameter.svg, llicència CC BY-SA 3.0. Modificat per Joan Queralt.

Un envolvent ADSR té quatre paràmetres:

  • Attack: el temps (en segons) que triga el so a arribar al seu màxim volum.

  • Decay: el temps (en segons) que triga el so a baixar del màxim volum al nivell de sosteniment.

  • Sustain: el volum del so mentre es manté la tecla premuda. És la proporció del volum màxim assolit després de l’atac, de 0 a 1.

  • Release: el temps (en segons) que triga el so a desaparèixer després d’alliberar la tecla.

Per crear un envolvent a un so, ho podem fer de dues maneres:

  • Amb la funció adsr(), i els quatre valors anteriors separats per dos punts: adsr(".1:.2:.5:.3").

  • Amb les funcions attack(), decay(), sustain() i release(), i els valors que volem per a cadascun d’ells.

note("<c a f e> -")
  .sound("sawtooth")
  .lpf(400)
  .adsr(".1:.2:.5:.9")

En aquest exemple, podem escoltar com el so augmenta ràpidament al principi fins al seu màxim, després baixa fins a un punt estable que es manté la mitat del cicle i, finalment, a la segona mitat del cicle, el so disminueix lentament fins a desaparèixer.

Prova a modificar els valors de l’envolvent per comprovar com canvia el so amb cadascun dels paràmetres.

Encara que aplicar un envolvent a l’amplitud (volum) és molt comú, es poden aplicar envolvents a altres paràmetres d’un so, com a la freqüència d’un filtre o el to. A la documentació oficial hi ha exemples d’ús d’envolvents aplicats a altres paràmetres.

Delay

El retard o delay és un efecte que consisteix a repetir un so amb un cert retard, cosa que crea una sensació d’eco.

L’efecte de delay és controla amb tres paràmetres:

  • delay: és la proporció del volum de les repeticions respecte el so original.

  • delaytime o dt: el temps que passa entre repetició i repetició.

  • delayfeedback o dfb: aquest paràmetre es permet tornar a alimentar l’efecte de retard amb la seva pròpia sortida. És la proporció de senyal que tornem a enviar a l’entrada de l’efecte.

Un delayfeedback superior 1 generarà un so que augmentarà de volum constantment, és recomanable no fer-ho.

Com en el cas dels envolvents, podem optar per indicar cadascun dels paràmetres amb una funció diferent, o afegir-los tots a delay:

note("c g ef af").sound("gm_electric_bass_pick").delay("0.5:0.33:0.8")
sound("bd,hh*4,[- sd]!2").delay(0.8).dt("<0.125 0.06>")

Reverb

L’efecte de reverb consisteix a simular una reverbació del so com la que típicament es produeix en recintes amplis, i provocada per la reflexió de les ones sonores.

Hi ha diversos paràmetres que es poden utilitzar amb reverb, però els dos principals són:

  • room: és el volem de les reverberacions, entre 0 i 1.

  • roomsize o sz: és la mida de la sala que es simula, entre 0 i 10.

Com abans, podem especificar els dos paràmetres dins de room, o utilitzant les dues funcions per separat.

note("c g ef af").sound("gm_electric_bass_pick").room(4).sz(3)
sound("bd,hh*4,[- sd]!2").room(".6:7")

Modulació amb senyals contínus

A més de modificar qualsevol paràmetre utilitzant valors fixos, podem utilitzar senyals contínus per modificar paràmetres dinàmicament.

Podem veure una llista de tots els senyals disponibles a la documentació oficial. Els més comuns són sine, una one sinusoidal, i saw, una ona en forma de serra. Les dues estan escalades perquè retornin valors entre 0 i 1.

sound("hh*16").gain(sine)
sound("hh*8").gain(.7).delay(0.2).dt(saw)

Per defecte, un senyal fa una oscil·lació completa en un cicle. Per exemple, saw comença a 0 i puja fins a 1 al final del cicle, moment en què torna a començar. Tenim funcions per modificar la velocitat d’aquests senyals, així com per modificar el seu rang de valors:

sound("piano*4")
  .n(sine.fast(.31).range(0,16))
  .scale("d:dorian").room(.3)

Aquí, hem alentit el cicle de sine (amb un nombre més petit que 1 a fast()), de manera que les notes no coincideixin a un múltiple del cicle, cosa que fa que el patró de notes sigui similar cada vegada, però lleugerament diferent. També hem usat range() per obtenir notes entre 0 i 16 dins de l’escala triada.

Noteu també que és sound() qui determina que hi haurà 4 notes per cicle. Els senyals contínus són precisament contínus i no contenen un patró rítmic, així que no funcionaria:

n(sine.fast(.31).range(0,16))
  .scale("d:dorian").room(.3)
  .sound("piano*4")

Si necessitem samplejar un senyal contínu, ho podem fer amb la funció segment():

n(sine.fast(.31).range(0,16).segment(4))
  .scale("d:dorian").room(.3)
  .sound("piano")

Aquest exemple és equivalent a l’anterior, però ara és n() qui determina que hi haurà 4 notes per cicle.

Efectes de patró

Els efectes de patró són aquells que modifiquen l’estructura dels patrons, i no el so en si mateix. La creació i manipulacions de patrons és un punt fort de TidalCycles i Strudel, i existeixen múltiples funcions que ens permeten modificar els patrons de moltes formes diferents.

En aquest apartat en veurem algunes de representatives de cadascuna de les categories que hi ha.

Modificació del temps

  • fast() i slow(): com en el cas dels senyals contínus, podem utilitzar aquestes dues funcions per accelerar o alentir un patró. Són equivalents a * i / en mini-notation.

    note("[c,f] [d,a] [ef,g] [c,ef]")
      .attack(.2)
      .release(.4)
      .lpf(800)
      .sound("supersaw")
      .slow(4)
    sound("bd").fast("<4 8>")
  • clip(): allarga o escurça la duració de cada esdeveniment. Per defecte, un so acaba quan s’activa un altre so del mateix sample o instrument. Amb clip() podem escurçar la duració per crear un efecte de staccato o allargar-la per crear un efecte de legato o suspensió. L’argument de clip() és la proporció de temps que volem que duri el so respecte a la duració original.

    n("0 7 <5 <1 -1>> 3")
      .scale("c:mixolydian")
      .sound("sawtooth")
      .lpf(800)
      .clip("<0.2 1 2>")
  • rev(): inverteix l’ordre dels esdeveniments d’un patró.

    sound("bd [sd hh] [hh hh] bd,clap [sd hh]").rev()
  • ply(): repeteix cada esdeveniment el nombre especificat de vegades.

    sound("bd [sd hh] hh [bd,clap hh]").ply("<1 2>")

Operadors

Els operadors apliquen una operació matemàtica als nombres d’un patró. En veurem només un:

  • add(): afegeix un valor a cada nombre d’un patró.

    n("0 [-2 1] [5 4] [- -1]".add("<0 [0,2] 4>"))
      .scale("c4:lydian")
      .sound("piano")
    sound("hh").delay(2).dt("0.2".add("<0.13 0.05>"))

    Noteu com add() s’aplica directament al text que es vol modificar: n("0".add("1")) i no n("0").add("1").

Aleatorietat

Les funcions d’aleatorietat ens permeten afegir variabilitat a les nostres seqüències. En un context de música algorítmica generada a temps real, la possibilitat d’afegir una certa aleatorietat als patrons ens permet afegir variabilitat als patrons de forma ràpida.

  • degradeBy(): assigna una probabilitat a cada esdeveniment de què no es produeixi.

    sound("hh*8").degradeBy(0.3)
    sound("hh*8").degradeBy("[0 0.5]*4")
  • sometimesBy(): assigna una probabilitat a cada esdeveniment que se li apliqui un cert efecte.

    sound("[bd sd]*2,[- hh]*4").sometimesBy(.4, x=>x.ply(2))

    Fixem-nos en la sintaxi d’aquest exemple. sometimesBy rep un primer paràmetre que és la probabilitat que s’apliqui la modificació indicada al segon paràmetre. En el segon paràmetre no podem posar directament ply(2), perquè ply() s’aplica a sobre d’un patró (sounnd("bd").ply(2)), però tampoc podem escriure el patró directament, perquè no el sabem (cada vegada serà un esdeveniment diferent dels que hi ha definits al patró original). En aquests casos, el que necessitem és passar una funció que realitzi la modificació sobre un patró qualsevol que rep com a paràmetre (en el nostre cas, l’hem anomenat x). Aquest és un concepte una mica avançat de programació, però en tenim prou amb recordar la forma com s’ha d’escriure.

    Les funcions anònimes (en JavaScript s’anomenen funcions de fletxa per la forma com s’escriuren) són funcions que no tenen nom i que es poden passar com a paràmetre a una altra funció. En el nostre cas, la funció x⇒x.ply(2) és una funció anònima que rep un patró x i li aplica ply(2).

    note("ef d [f g] c".sometimesBy(0.3, x=>x.add(7))).sound("piano")

    En aquest cas estem usant sometimesBy() a sobre del patró de notes directament, de manera que en alguns casos s’afegeixen 7 semitons a la nota original. Encara que estiguem escrivint les notes com a text, internament no deixen de ser els nombres que corresponen a cada nota, així que té sentit sumar-hi o restar-hi quantitats.

    En la teoria musical occidental, un semito és la distància entre dues notes consecutives (per exemple, dues tecles del piano, o un trast d’una guitarra). 7 semitons equivalen a un interval de cinquena justa, que és un interval molt comú. Per exemple, si la nota original és Do ©, una cinquena per sobre és Sol (G).

  • someCyclesBy(): igual que sometimesBy(), però l’efecte s’aplica o no a cicles sencers.

    note("ef d [f g] c".someCyclesBy(0.3, x=>x.add(7))).sound("piano")
    n("2 1 [3 4] 0".someCyclesBy(
      0.4, x=>x.add(chooseCycles(1,4,7)))
    ).scale("c:major").sound("piano")

    En aquest exemple utilitzem chooseCycles() per triar, en cada cicle, un nombre aleatori d’entre 1, 4 o 7.

Condicionals

Les funcions condicionals serveixen, com les funcions d’aleatorietat, per aplicar variacions als patrons però, en aquest cas, es defineix una regla no aleatòria que determina quan s’aplica la variació.

  • struct(): aplica una estructura a un patró. L’estructura es defineix com una seqüència binària: podem posar guions - o zeros 0 per indicar un silenci, i qualsevol caràcter per indicar un esdeveniment. struct() és especialment útil quan volem aplicar el mateix patró rítmic a un conjunt de sons.

    note("<[c,g]!2 [ef,bf] [df,af]>").s("sawtooth").lpf(800)
      .struct("[- x]*2")
      .clip(.5)
      .delay(".5:.125:.8").room(1)
  • mask(): aplica una màscara a un patró. La màscara es defineix de la mateixa manera que a struct(). La diferència entre struct() i mask() és que struct() modifica l’estructura del patró original (és a dir, quants esdeveniments hi ha i on són dins del cicle), mentre que mask() silencia o no esdeveniments que ja estan definits en el patró original. mask és especialment útil per crear composicions més llargues, on hi ha sons que es van activant i desactivant al llarg del temps.

    $: n("<0 3>")
      .scale("c2:mixolydian")
      .struct("x@2 x!2 x")
      .sound("gm_acoustic_bass")
      .lpf(400)
      .mask("<0!4 1!8>")
    
    $: note("c@2 f a g [c4 bf]")
      .slow(2)
      .sound("gm_epiano2")
      .release(1)
      .attack(0.2)

    En aquest exemple, hem utilitzat mask() per fer que el baix entri després de dues voltes de la melodia. En canvi, struct() està definint el patró rítmic del baix.

Acumuladors

Les funcions d’acumulació afegeixen una versió modificada del patró original a sobre seu. Ens permeten afegir complexitat rítimica o melòdica a un patró senzill.

  • off(): afegeix una còpia del patró original, desplaçada en el temps i aplicant-li alguna transformació.

    n("0 4 1 0 2 3 6 5 4 0 1 0"
      .off(1/4,x=>x.add("<2 0 3 6>"))
    ).someCyclesBy(.2,x=>x.rev())
      .scale("c2:minor:blues")
      .sound("gm_acoustic_bass")
      .lpf(800)

    Aquí s’ha utilitzat 1/4 en comptes de 0.25 per fer referència a una part d’un cicle, cosa que resulta més fàcil d’entendre i pensar. Això és possible perquè l’expressió no està entre cometes, i s’interpreta com un nombre, així que es fa la divisió. Si estigués entre cometes, s’hauria interpretat com a mini-notation.

  • echo(): afegeix múltiples còpies del patró original, desplaçades en el temps i disminuint gradualment el volum. L’efecte pot ser similar a un delay, però el delay és un efecte que s’aplica sobre el so i no modifica el patró, mentre que echo() sí que el modifica. echo rep tres paràmetres: la quantitat de repeticions, el temps entre repeticions, i la proporció de volem que es manté en cada repetició.

    note("<[c,g]!2 [ef,bf] [df,af]>").s("sawtooth").lpf(800)
      .struct("[- x]*2")
      .clip(.5)
      .room(1)
      .echo(5,1/8,.8)