Haskell e il Jazz
2019-01-09
(home)

Scrivere un programmino in Haskell è, credo, una delle più grandi soddisfazioni che ci possano essere nella vita di un script kid adolescente come me. Il codice finalmente ha… senso, ha senso, è coerente ed è stranamente limpido. MA.

L’ovvio MA è la complessità, perché Haskell è un linguaggio seriamente complicato da imparare. Al contrario di ciò che credono in molti (in particolare io), superato lo scoglio delle monadi la strada è tutt’altro che in discesa. È come quando stai andando a fare capodanno in una baita seicento metri di dislivello più in alto di dove sei, con uno zaino strapieno di provviste e vestiti e bottiglie di alcool, e dopo mezz’ora arrivi stremato ad una casupola di montagna ed esulti perché dici “Oh Dio ce l’ho fatta chi l’avrebbe mai detto sono stanco morto” salvo poi scoprire che la destinazione è a un’ora di salita da dove sei e il tuo amico te la indica ed è sulla cima della montagna che credevi di aver appena scalato e allora pensi “Porca merda ma quanto manca ancòra”.

Ecco, le monadi rappresentano quella casupola e la vetta rappresenta una sufficiente conoscenza di Haskell e la persona stremata che credeva di essere arrivato in cima sono, beh, io, nel momento in cui ho deciso di affrontare il dopo-monadi.

Ciononostante è possibile fare cose carine con Haskell pur senza inoltrarsi tra catamorfismi, lenti e banane. Una di queste (spero) è una cosa simpatica che sfrutta alcune proprietà di Haskell per generare voicing per accordi jazz.

Di recente, qualcuno su hackernews si è chiesto:

Can we write a programming language for biology? [...] an abstracted language that compiles down to nucleic acids.

Questa domanda (che mi ha portato a scoprire questa stupenda presentazione) è simile a quella che mi sono posto mentre leggevo The Jazz Piano Book di Mark Levine: si può costruire un linguaggio di programmazione per la teoria musicale (jazz, nello specifico)?

Da qualche parte ho letto che per risolvere un problema in Haskell bisogna partire dalla definizione di tipi; per cui il mio file comincia con queste poche righe:

data Note = C | Cs | D | Ds | E | F | Fs | G | Gs | A | As | B deriving (Show, Enum)
data Mode = Major | Minor deriving (Show, Eq)

type Scale = [Note]
type Chord = [Note]

I nomi delle note dovrebbero prevedere la doppia denominazione per le note alterate (ad es. Cs, che corrisponde a C#, equivale anche a Db), ma per ora non mi interessa (si tratta fondamentalmente di una questione estetica, visto che i calcoli del programmino vengono effettuati su intervalli numerici invece che sui nomi delle note).

Andiamo quindi a definire gli intervalli nelle scale più comuni:

scaleIntervals :: Mode -> [Int]
scaleIntervals Major = [0,2,4,5,7,9,11]
scaleIntervals Minor = [0,2,3,5,7,9,11]

Definiamo quindi una funzione ausiliaria jump che calcola la nota distante n semitoni da una nota data:

-- |Applies an offset (in semitones) from a note.   
jump :: Note -> Int -> Note
jump tonic interval = toEnum $ (fromEnum tonic + interval) `rem` 12

La costruzione di una scala, in un determinato modo e con una certa tonica viene quindi quasi naturale: ricordando che abbiamo definito Note come Enum, basta invocare scaleIntervals Major|Minor e traslarla alla giusta tonica per ottenere la scala desiderata:

-- |Returns the notes of a scale.
scale :: Note -> Mode -> Scale
scale tonic mode = converter $ scaleIntervals mode
    where converter = map (jump tonic)

> scale C Major
[C,D,E,F,G,A,B]
> scale Fs Minor
[Fs,Gs,A,B,Cs,Ds,F]

Se è vero che, nel jazz, gli accordi sono semplicemente note di una scala suonate tutte assieme, non dovrebbe essere difficile creare accordi partendo da una determinata scala con Haskell; la costruzione di triadi e “quadriadi” (triadi + settima) è relativamente semplice (si noti che la variabile scale' ha un pedice in coda, per differenziarla dalla funzione scale descritta sopra):

-- |Given a Scale, returns the corresponding triad.
triad :: Scale -> Chord
triad scale' = [scale' !! 0, scale' !! 2, scale' !! 4]

-- |Given a Scale, returns the corresponding triad plus the seventh.
quadriad :: Scale -> Chord
quadriad scale' = (triad scale') ++ [scale' !! 6]

> quadriad $ scale C Major
[C,E,G,B] -- CMaj
> quadriad $ scale C Minor
[C,Ds,G,B] -- CMaj- 

Costruire tutte le quadriadi di una scala è fondamentalmente un map un po’ elaborato:

-- | Translate a scale (given a scale, starts with the i-th note instead of the tonic).
-- It is in fact a circular rotation of the scale.
translate :: [a] -> Int -> [a]
translate s i = newStart ++ newEnd
    where newStart = drop (rem i (length s)) s
          newEnd   = take (rem i (length s)) s

scaleChords :: Scale -> [Chord]
scaleChords scale' = map quadriad $ map (translate scale') [0..6]

translate è un po’ brutta da vedere ma non è troppo complicata: fondamentalmente sposta in su o in giù di n note una scala. Un semplice esempio è:

> translate (scale C Major) 1
[D,E,F,G,A,B,C]

scaleChords, data una scala (poniamo C Major), ritorna le sette scale con 7 toniche differenti (cioè: trasla la scala di C Major in su di [0..6] note) e ad ognuna di essere applica quadriad. Un esempio:

> scaleChords (scale G Major)
[[G,B,D,Fs],[A,C,E,G],[B,D,Fs,A],[C,E,G,B],[D,Fs,A,C],[E,G,B,D],[Fs,A,C,E]]

Voilà, ecco le quadriadi su scala di sol maggiore!

Gli accordi (quadriads) sui sette gradi della scala di Do Maggiore, con relativa notazione
Gli accordi (quadriads) sui sette gradi della scala di Do Maggiore, con relativa notazione

Il bello inizia ora. Avendo a disposizione queste funzioni per generare accordi, possiamo iniziare a costruire sequenze di accordi più ampie; e siccome parliamo di jazz, la prima cadenza che viene in mente è la ii-V-I, che è presto implementata:

ii_v_i :: Scale -> [Chord]
ii_v_i scale' = [chords !! 1, chords !! 4, chords !! 0]
    where chords = scaleChords scale' -- le sette quadriadi della scala scelta!

> ii_v_i $ scale C Major
[[D,F,A,C],[G,B,D,F],[C,E,G,B]]

Altro celebre concetto della musica jazz sono le Coltrane Changes: sequenze di accordi V-I ripetute a distanza di una terza maggiore tra di loro; Giant Steps di Coltrane fu il primo pezzo ad usare estensivamente queste sostituzioni ed è… impressionante, voglio dire guardate QUANTI ACCORDI vengono suonati e QUANTO VELOCEMENTE. Una storia simpatica che si racconta spesso: Tommy Flanagan, pianista di Coltrane nel disco, ricevette gli accordi di Giant Steps pochi minuti prima di incidere (a quanto pare era/è una cosa comune in ambiti jazz); se ascoltate l’assolo di pianoforte nel video vi accorgerete di quanto Tommy, porello, faccia fatica a star dietro a Coltrane, che invece viaggia sparato a trecento all’ora nei suoi assoli.

Tradurre in Haskell le Coltrane Changes, di nuovo, non è complicato: si può sfruttare la pigrezza di Haskell per costruire una lista infinita di V-I distanziati da una terza maggiore:

coltraneChanges :: Note -> [Chord]
coltraneChanges tonic = drop 1 (ii_v_i scale') ++ coltraneChanges majThird
    where scale'   = scale tonic Major
          majThird = jump tonic 4

> coltraneChanges G
[[D,Fs,A,C],[G,B,D,Fs],     -- D7  GMaj
 [Fs,As,Cs,E],[B,Ds,Fs,As], -- F#7 BMaj
 [As,D,F,Gs],[Ds,G,As,D],   -- Bb7 EbMaj
 [D,Fs,A,C],[G,B,D,Fs],     -- D7  GMaj (si ripete!)
 [Fs,As,Cs,E],[B,Ds,Fs,As] ... -- è una lista infinita!
Giant Steps (via)
Giant Steps (via)

(detto questo: Giant Steps non è una monotona ripetizione circolare di V-I (ci sono anche ii di tanto in tanto e altre variazioni minori), ma il concetto che sta alla base del pezzo è comunque in larga misura riconducibile alla funzione definita qui sopra)

Altra sostituzione, decisamente più classica e più semplice da implementare (e da suonare!) è la sostituzione del tritono, amichevolmente detta tritone: in una cadenza ii-V-I, il V viene sostituito con un iib; al posto di avere D-7 | G7 | CMaj7 si ha quindi D-7 | Db7 | CMaj7. La ragione per cui questa sostituzione funziona (a volte) è che tanto nell’accordo di G7 quanto nell’accordo di Db7 ci sono le note Fa e Si, note caratterizzanti (sono settime o terze nei rispettivi accordi) distanti una quarta eccedente (un tritone, per l’appunto).

Sostituzione del tritone
Sostituzione del tritone

Non è difficile implementare la sostituzione tritonica in Haskell, ma la funzione qui di seguito è scritta un po’ (molto) col culo: in pratica dato l’accordo CMaj7 (quindi la scala di Do Maggiore) sali di un tritone dalla tonica (C -> F#), costruisci la scala maggiore usando come tonica la nota così ottenuta, quindi prendi la quadriade presente sul quinto grado di questa scala (F# -> C# -> C#7 == Db7) ed ecco la tua sostituzione del tritono. C’è sicuramente un modo più semplice per costuire il tritono ma ora non mi viene in mente, cavoletti, anche perché così com’è presentata la funzione non restituirà risultati corretti in presenza di accordi rivolti (v. dopo).

tritoneChange :: [Chord] -> [Chord]
tritoneChange [ii,v,i] = [ii,tritone,i]
    where tritone = scaleChords (scale (jump (i !! 0) 6) Major) !! 4

> tritoneChange $ ii_v_i $ scale C Major
[[D,F,A,C],   -- D-7
 [Cs,F,Gs,B], -- Db7 (G7 traslato sotto di un tritone)
 [C,E,G,B]]   -- CMaj7

(la tentazione di dire “Ok ma allora mi conviene chiedere la quadriade sulla tonica della scala maggiore di Db!” è forte, ma la suddetta quadriade sarebbe un accordo di DbMaj7, non di Db7 come lo vogliamo noi!)

Questa breve trafila di sostituzioni scale intervalli ecc può anche essere eccitante MA c’è un problema: gli accordi, così come vengono presentati dal programma, sono insuonabili. Cioè, sono suonabili ma saltano un po’ troppo per essere ascoltabili: il fatto è che generando accordi nella loro posizione base (c’è un modo formale per esprimere il concetto di “posizione base”?), ovvero con la tonica nel basso e le note rimanenti disposte ordinatamente sopra di essa. È bene scrivere una funzione che, dato un accordo, restituisca i rivolti di quell’accordo:

inversions :: Chord -> [Chord]
inversions c = take (length c) $ iterate (flip translate 1) c

iterate è una funzione della libreria standard di Haskell con segnatura iterate :: (a -> a) -> a -> [a]: in pratica, data una funzione e un valore iniziale, ti viene restituita una lista infinita che avrà la forma [x, f x, f (f x), f (f (f x)), ...].

Riutilizzando la funzione translate (definita prima) che ruota una lista, inversions prende un accordo (che in ultima istanza è una lista di note) e restituisce una lista con tutte le rotazioni delle note di quell’accordo (aka: con tutti i rivolti). La grandezza di quest’ultima lista varia in base al numero di note nell’accordo dato alla funzione (una triade ha una forma base + 2 rivolti, una quadriade ha 3 rivolti, ecc), e così si spiega il take (length c).

Ok: ora abbiamo la possibilità di generare i rivolti di un accordo. Ammettiamo quindi di avere una sequenza di accordi, tutti nella loro “posizione base”; come facciamo a minimizzare i salti tra accordi contigui, in modo da tenere il più possibile ferma la mano durante l’esecuzione di un pezzo? In altre parole: come posso scegliere il voicing migliore, in modo tale che le voci di ogni accordo debbano muoversi il meno possibile?

Prima di tutto stabiliamo una metrica: come misurare la distanza tra due note?

distance :: Note -> Note -> Int
distance n1 n2 = min (abs $ idx1 - idx2) (11 - (abs $ idx1 - idx2))
    where (idx1, idx2) = (fromEnum n1, fromEnum n2)

Forse è inutilmente convoluta come funzione, ma il concetto alla base è: la distanza tra le note B e C può essere di 1 semitono (es: B3-C4) o di 11 semitoni (es: B3-C3); prendiamo quindi la distanza minore tra le due.

La distanza fra due accordi segue il medesimo principio:

shortestPath :: Chord -> Chord -> Chord
shortestPath c1 c2 = inversions' !! indexMin
    where inversions' = inversions c2
          ratedInversions = map (zipWith distance c1) inversions'
          indexMin  = fromJust $ elemIndex (minimum ratedInversions) ratedInversions

Anche qui c’è un po’ di foschia ma il concetto è sempre lo stesso: dati due accordi, per semplicità contenenti lo stesso numero di note, calcola la distanza tra le coppie di note corrispondenti ad ogni voce nell’accordo. Per fare un esempio: se dovessimo scegliere il voicing migliore per un accordo di G7 che segue un accordo di D-7 in posizione base, calcoleremmo la distanza tra D-7 e tutti i rivolti di G7 (qui di seguito mostriamo solo due casi):

     dist.
D -----5-----> G \
F -----6-----> B  \
A -----5-----> D   +--> G7 (pos. base)
C -----5-----> F  /

e

     dist.
D -----0-----> D \
F -----0-----> F  \
A -----2-----> G   +--> G7 (secondo rivolto)
C -----1-----> B  /

Utilizzando il secondo rivolto del G7 la distanza tra le voci è minima; pertanto, verrà scelto questo accordo:

> [ii,v,_] = ii_v_i $ scale C Major
> [ii,v]
[[D,F,A,C], -- D-7 base
 [G,B,D,F]] -- G7  base
> shortestPath ii v
[D,F,G,B]   -- G7 secondo rivolto

A questo punto ci rimane solo di applicare il “nearest voicing” ad una sequenza pre-esistente di accordi; c’è un modo piuttosto elegante per farlo ricorrendo alla funzione built-in scanl1:

shortifyChords :: [Chord] -> [Chord]
shortifyChords = scanl1 shortestPath

Che fa esattamente ciò che state pensando: prende gli accordi a due a due, modifica il secondo affinché segua l’euristica appena descritta e voilà! Eccoci coi nostri accordi vicini vicini e facili da suonare!

Per un esempio pratico, applichiamo il “nearest voicing” ad una sequenza di Coltrane Changes:

> take 6 $ coltraneChanges G  -- senza voicing
[[D,Fs,A,C],[G,B,D,Fs],
 [Fs,As,Cs,E],[B,Ds,Fs,As],
 [As,D,F,Gs],[Ds,G,As,D]]
> shortifyChords $ take 6 $ coltraneChanges G   -- con voicing 
[[D,Fs,A,C],[D,Fs,G,B],
 [Cs,E,Fs,As],[B,Ds,Fs,As],
 [As,D,F,Gs],[As,D,Ds,G]]

Da qui in poi ci sono un po’ di cose che si potrebbero aggiungere; un paio mi interessano in particolare: la costruzione di accordi più colorati (none undicesime tredicesime b9 b5 e chi più ne ha più ne metal) e la possibilità di esportare una sequenza di accordi in un file midi. Mentre la prima richiede un po’ di modifiche strutturali (nuove scale, generalizzazione di alcune funzioni, ecc) e quindi un po’ di tempo per approfondire meglio la situazione, la seconda è relativamente facile da implementare; senonché sto scrivendo e lavorando da un sistema lINUX E MANNAGGIA A NON DICO CHE COSA POSSIBILE CHE NEANCHE PREGANDO IN SUMERO RIESCO A FAR ESEGUIRE UN FILE MIDI A VLC ma PORCA MERDA NEL 2019 SIAMO ANCORA QUI COSÌ?!?!?!!?!????!?!!?!?!!?


se notate imprecisioni o errori grossolani o se semplicemente vi sentite soli mandatemi una mail a cvd00 chiocciola insicuri punto net e vi risponderò subitissimo