Questo Notebook vuole illustrare una possibile strategia nella generazione e manipolazione di dati simbolici orientati alla composizione algoritmica (Computer Aided Composition) in python nonchè alla loro trascrizione in notazione musicale attraverso il software Lilypond.
Dividiamo il processo in due fasi distinte:
La prima consiste nel predisporre l'ambiente informatico integrato più consono allo scopo:
La seconda consiste nel formalizzare la generazione o elaborazione algoritmica dei diversi parametri musicali.
Scarichiamo ed installiamo:
La finalità che ci siamo prefissati è la generazione automatica di codice di lilypond attraverso algoritmi realizzati in python.
Per farlo abbiamo principalmente due possibilità.
Dalle celle di questo Notebook.
page = '''
\\version \"2.20.0\"
\\language \"english\"
#(set! paper-alist (cons \'(\"mio formato\" . (cons (* 80 mm) (* 30 mm))) paper-alist))
\\paper {#(set-paper-size \"mio formato\") top-margin = 4 left-margin = 0}
\\header {tagline = \"\"}\n\n'''
seq = "a\' b\' c\'\' d\'\'"
out = page + '{ ' + seq + ' } \n\n'
print(out) # scrive il codice in ouput (opzionale)
\version "2.20.0" \language "english" #(set! paper-alist (cons '("mio formato" . (cons (* 80 mm) (* 30 mm))) paper-alist)) \paper {#(set-paper-size "mio formato") top-margin = 4 left-margin = 0} \header {tagline = ""} { a' b' c'' d'' }
Possiamo anche programmare una sequenza di operazioni automatiche in python che svolgono tutti i passaggi necessari (compresa le generazione di eventuali files intermedi) alla creazione del file finale (pdf o png) sia in VSCode (dopo aver installato l'estensione VSLilyPond).
import os # Libreria per eseguire comandi da Terminale
# ------------------------------- 1. Sintassi di lilypond opportunamente formattata in python
file = "prova_1" # nome dei files generati senza estensione
seq = "a\' b\' c\'\' d\'\'"
out = '{ ' + seq + ' } \n\n'
# -------------------------------- 2. Generiamo un file di testo con estensione .ly
f = open(file + ".ly", "w") # crea un file di testo...
f.write(out) # lo scrive...
f.close() # lo chiude in python
# -------------------------------- 3. Compiliamo il file generato da Terminale in python
cmd = "lilypond -dresolution=300 --pdf "+file+".ly"
os.system(cmd)
Elaborazione di «prova_1.ly» Analisi... prova_1.ly:1: attenzione: no \version statement found, please add \version "2.24.0" for future compatibility Interpretazione della musica... Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in una pagina... Disegno dei sistemi... Converting to `prova_1.pdf'... Success: compilation successfully completed
0
Ricordo che in questo caso i passaggi da seguire sono:
Compaiono all'interno della cartella altri due files:
Selezionandoli all'interno di esplora risorse possiamo visualizzarli passando da uno all'altro oppure aprirli con altri software.
Possiamo anche editare file di lilypond direttamente in VScode ma prestiamo attenzione che le modifiche saranno cancellate da un'eventuale nuova esecuzione del file di python.
Se salviamo il file (cmd+s) di lilypond lanciamo l'esecuzione e le modifiche saranno visibili nel pdf.
Aggiungendo poche linee di codice possiamo eseguire il tutto anche da questo Notebook.
import os # Libreria per eseguire comandi da Terminale
from IPython.display import Image # Libreria per visualizzare immagini e altro
# ------------------------------- 1. Sintassi di lilypond opportunamente formattata in python
file = "esempi/prova_1" # Nome dei files generati senza estensione
page = '''
\\version \"2.24.0\"
\\language \"english\"
#(set! paper-alist (cons \'(\"mio formato\" . (cons (* 80 mm) (* 30 mm))) paper-alist))
\\paper {#(set-paper-size \"mio formato\") top-margin = 4 left-margin = 0}
\\header {tagline = \"\"}\n\n'''
seq = "a\' e\' g\'\' c\'\'"
out = page + '{ ' + seq + ' } \n\n'
# -------------------------------- 2. Generiamo un file di testo con estensione .ly
f = open(file + ".ly", "w") # crea un file di testo...
f.write(out) # lo scrive...
f.close() # lo chiude in python
# -------------------------------- 3. Compiliamo il file generato da Terminale in python
cmd = "lilypond -dresolution=300 -dpixmap-format=pngalpha --output=esempi --png " + file + ".ly"
os.system(cmd)
# -------------------------------- 4. Eventualmente visualizziamo il file creato nel Notebook
Image(filename = file + ".png", width = 350)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/prova_1.ly» Analisi... Interpretazione della musica... Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in una pagina... Disegno dei sistemi... Converting to PNG... Success: compilation successfully completed
Terminale
Python
Notebook
VScode
Negli algoritmi di generazione ed elaborazione di dati simbolici (altezze, durate, intensità, etc.) possiamo utilizzare numeri e/o collezioni di diverso tipo ma come output del file di python dovremo formattare tutto il codice come stringhe affinchè possa essere compilato in lilypond.
Per questo motivo rivediamo alcune operazioni specifiche legate a questo tipo di data.
seqa = "c d e f " # anche gli spazi sono character...
seqb = 'g a b'
seqc = '{ ' + seqa + seqb + ' }' # concatenazione
print(seqc)
{ c d e f g a b }
Se vogliamo compiere determinate operazioni all'interno di una stringa oppure inserire caratteri particolari come virgolette e apici dobbiamo farli precedere da un backslash.
Questo costrutto sintattico si chiama escape character ed è necessario a differenziare particolari tipi di carattere dalla loro funzione sintattica all'interno del linguaggio di programmazione.
Ci sono alcuni escape characters che ci possono tornare utili nella formattazione delle stringhe:
In taluni casi una sintassi simile riguarda le parentesi graffe.
Se le vogliamo definire come semplice carattere all'interno di una stringa in situazioni particolari che incontreremo le dobbiamo raddoppiare - {{ }}.
Confrontiamo il seguente file scritto nella sintassi di lilypond con la sua traduzione in python.
# Sintassi lilypond (NON eseguire)
\version "2.20.0"
\language "english"
\header {
title = "Bella musica"
composer = "L'ho scritta io"
}
{ c'' a' b' c'' }
# Sintassi python
versione = '\\version \"2.20.0\"\n' # assegna stringhe a variabili
lingua = '\\language \"english\"\n\n'
titolo = '''\\header {
title = \"Bella musica\"
composer = \"L'ho scritta io\"
}\n\n'''
expr = "{ c\'\' a\' b\' c\'\' }"
out = versione + lingua + titolo + expr # concatena più stringhe
print(out) # stampa il risultato
\version "2.20.0" \language "english" \header { title = "Bella musica" composer = "L'ho scritta io" } { c'' a' b' c'' }
Se vogliamo inserire all'interno di una stringa altri tipi di dato come int in precise posizioni possiamo utilizzare quattro diverse modalità:
Parametri ordinati
# Sintassi lilypond (NON eseguire)
\version "2.20.0"
\language "english"
voceA = \relative c'' { c e d c }
voceB = \relative c'' { c g b c }
\score {
\new Staff {
<<
\new Voice = "Ciaccio" {\voiceOne \voceA }
\new Voice = "Ciuccio" {\voiceTwo \voceB }
>>
}
}
# Sintassi python
# --------------------- Variabili che andramo a generare o elaborare algoritmicamente
voceA = '\\relative c\'\' { c e d c }'
voceB = '\\relative c\'\' { c g b c }'
# --------------------- Costanti che costruiscono la partitura
SCORE = '''\\score {{
\\new Staff {{
<<
\\new Voice = \"Ciaccio\" {{\\voiceOne {} }}
\\new Voice = \"Ciuccio\" {{\\voiceTwo {} }}
>>
}}
}}'''
versione = '\\version \"2.20.0\"\n' # assegna stringhe a variabili
lingua = '\\language \"english\"\n\n'
# --------------------- Concatenazione per l'output
out = versione + lingua + SCORE.format(voceA,voceB) # .format(ordine_inserimento)
print(out)
\version "2.20.0" \language "english" \score { \new Staff { << \new Voice = "Ciaccio" {\voiceOne \relative c'' { c e d c } } \new Voice = "Ciuccio" {\voiceTwo \relative c'' { c g b c } } >> } }
Come nel caso precedente ma specifichiamo gli indici direttamente nelle parentesi graffe.
Se come nel caso seguente all'interno della stringa da formattare vogliamo utilizzare le parentesi graffe come carattere non possiamo utilizzare l'escape character ma dobbiamo duplicarle per distinguerle dalle variabili di formattazione.
# Sintassi lilypond (NON eseguire)
\version "2.20.0"
\language "english"
seqA = \relative c' { c e g c }
seqB = \relative c' { f a c f }
seqC = \relative c' { g' b d g }
\score {
\new Staff {
\seqA \seqB \seqC \seqA
}
}
# Sintassi python
# --------------------- Variabili che andramo a generare o elaborare algoritmicamente
I = 'c\' e\' g\' c\''
IV = 'f\' a\' c\' f\''
V = 'g\' b\' d\' g\''
# --------------------- Costanti che costruiscono la partitura
SCORE = '''\\score {{\n\t\\new Staff {{\n\t\t\\new Voice {{ {0}
{1}
{2}
{0}
}}\n\t\t}}\n\t}}'''
versione = '\\version \"2.20.0\"\n' # assegna stringhe a variabili
lingua = '\\language \"english\"\n\n'
# --------------------- Concatenazione per l'output
out = versione + lingua + SCORE.format(I,IV,V,I) # .format()
print(out)
\version "2.20.0" \language "english" \score { \new Staff { \new Voice { c' e' g' c' f' a' c' f' g' b' d' g' c' e' g' c' } } }
Come nei casi precedenti ma al posto degli indici specifichiamo i nomi direttamente nelle parentesi graffe
seqA = 'a b c d'
seqB = 'c f g d'
seqC = 'b\' g d e'
SCORE = '''\\score {{\n\t\\new Staff {{\n\t\t\\new Voice \\relative c\' {{ {seqA}
{seqB}
{seqC}
{seqB}
{seqA}}}\n\t\t}}\n\t}}'''
versione = '\\version \"2.20.0\"\n' # assegna stringhe a variabili
lingua = '\\language \"english\"\n\n'
# --------------------- Concatenazione per l'output
out = versione + lingua + SCORE.format(seqA=seqA, # modifichiamo i parametri da qua
seqB=seqB,
seqC=seqC) # .format()
print(out)
\version "2.20.0" \language "english" \score { \new Staff { \new Voice \relative c' { a b c d c f g d b' g d e c f g d a b c d} } }
Operatore di formato
Se utilizziamo il simbolo % all'interno di una stringa viene interpretato come operatore di formato.
La sintassi è quella illustrata nell'esempio.
In questo caso le parentesi graffe non devono essere raddoppiate in quanto presenti solo come carattere all'interno della stringa.
Non si usa .format()
var = ('Violino', 'Vl.', 3, 'Andante', 100, '2/4', 'treble', 'e \\major') # Tuple
SCORE = '''\\score{
\\new Staff \\with {
instrumentName = \"%s\"
shortInstrumentName = \"%s\"
}
\\new Voice {
\\override Score.MetronomeMark.padding = %d
\\tempo \"%s\" 4 = %d
\\time %s
\\clef %s
\\key %s
\\relative c\' '{a b cs ds cs ds a b gs a}
\\bar \"|.\"
}
}''' % var # include i parametri specificati nella tuple nello stesso ordine
versione = '\\version \"2.20.0\"\n' # assegna stringhe a variabili
lingua = '\\language \"english\"\n\n'
out = versione + lingua + SCORE
print(out) # non si usa .format()
\version "2.20.0" \language "english" \score{ \new Staff \with { instrumentName = "Violino" shortInstrumentName = "Vl." } \new Voice { \override Score.MetronomeMark.padding = 3 \tempo "Andante" 4 = 100 \time 2/4 \clef treble \key e \major \relative c' '{a b cs ds cs ds a b gs a} \bar "|." } }
Vediamo ora due esempi di come possiamo formalizzare in funzioni di python alcune parti del codice precedente che potremo voler riutilizzare in diversi files.
La prima funzione è dedicata alla formattazione di una pagina custom specificando larghezza (x) e altezza (y) in pixels.
def pag(x=100,y=30):
''' In: x, y in pixel per la pagina
Out: codice di lilypond per la formattazione custom
Definisce il formato della pagina di lilypond
'''
out = '''\\version \"2.24.0\" \n\\language \"english\"\n\n
#(set! paper-alist (cons \'(\"mio formato\" . (cons (* {0} mm) (* {1} mm))) paper-alist))
\\paper {{#(set-paper-size \"mio formato\") top-margin = 4 left-margin = 0}}
\\header {{tagline = \"\"}}\n\n'''.format(x,y)
return out
Richiamiamola...
largh = 12
alt = 200
print(pag(largh,alt))
\version "2.24.0" \language "english" #(set! paper-alist (cons '("mio formato" . (cons (* 12 mm) (* 200 mm))) paper-alist)) \paper {#(set-paper-size "mio formato") top-margin = 4 left-margin = 0} \header {tagline = ""}
La seconda include automaticamente un'espressione musicale di lilypond in uno staff singolo.
def staff(seq='{c d e f}'):
''' In: espressione musicale di lilypond
Out: uno staff singolo di lilypond
Formatta sequenze musicali in uno staff
'''
out = '''\\score {{
\\new Staff {{
\\new Voice = "Voce1" {{
{0}
}}
}}
}}'''.format(seq)
return out
Richiamiamola...
espr = "{a b c d e f g}"
print(staff(espr))
\score { \new Staff { \new Voice = "Voce1" { {a b c d e f g} } } }
Possiamo infine formattarle ulteriormente combinandole tra loro ottenendo un file compilabile da lilypond.
largh = 12
alt = 100
espr = "{a b c d e f g}"
pagina = pag(largh,alt) # Restituisce una stringa
rigo = staff(espr) # Restituisce una stringa
out = pagina + rigo # Combina due stringhe
print(out)
\version "2.24.0" \language "english" #(set! paper-alist (cons '("mio formato" . (cons (* 12 mm) (* 100 mm))) paper-alist)) \paper {#(set-paper-size "mio formato") top-margin = 4 left-margin = 0} \header {tagline = ""} \score { \new Staff { \new Voice = "Voce1" { {a b c d e f g} } } }
Vediamo ora come automatizzare i processi intermedi che intercorrono tra il file di python (dati in input) e il risultato finale (dati in output - partitura in pdf o png) rendendo la procedura meno macchinosa.
Scriviamo una funzione (non produttiva) che:
import os
def png(path="esempi/prova_2", out="no input"):
''' In: nome file (path)
Out: un file di lilypond
Genera un file png nella cartella esempi
'''
f = open(path + ".ly", "w") # crea un file di testo...
f.write(out) # lo scrive...
f.close() # lo chiude in python
# Se vogliamo un file pdf modifichiamo la stringa seguente secondo i flag di lilypond
cmd = "lilypond -dresolution=300 -dpixmap-format=pngalpha --output=esempi --png " + file + ".ly"
os.system(cmd)
largh = 80
alt = 25
espr = "\\relative c'' {a b c d e f g a}"
file = "esempi/prova_2"
pagina = pag(largh,alt) # Restituisce una stringa
rigo = staff(espr) # Restituisce una stringa
out = pagina + rigo # Combina due stringhe
png(file, out)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/prova_2.ly» Analisi... Interpretazione della musica... Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in una pagina... Disegno dei sistemi... Converting to PNG... Success: compilation successfully completed
Cercare nella cartella esempi il file _prova2.png.
Oppure se vogliamo visualizzarlo nel Notebook.
from IPython.display import Image
file = "esempi/prova_2"
Image(filename = file + ".png", width = 350)
\version "2.20.0"
\language "english"
\paper {#(set-paper-size "a8landscape") top-margin = 10 left-margin = 5 right-margin = 10 bottom-margin = 5
system-system-spacing.basic-distance = 15}
\layout {#(layout-set-staff-size 20) indent = 15 short-indent = 5 }
\header {tagline = ""}
dit = \relative c' {\override Score.MetronomeMark.padding = 4
\tempo "Andante" 4 = 124
\time 4/4
b8-1 c'-4 d-5 e,-1 f-2 g'-5 a,-1 b-2
}
\score{
\new Staff \with {instrumentName = "Fiddle"}
\new Voice {
\dit
}
}
\version "2.20.0"
\language "english"
\paper {#(set-paper-size "a7landscape") top-margin = 10 left-margin = 5 right-margin = 10 bottom-margin = 5
system-system-spacing.basic-distance = 15}
\layout {#(layout-set-staff-size 20) indent = 25 short-indent = 5 }
\header {tagline = ""}
bark = \relative c'' { g1 | %m2
e1 | %m3
c2. c'4 | %m4
g4 c g e | %m5
c4 r c'1 | %m6 % errore di durata...
}
\score{
\new Staff \with {instrumentName = "Ocarina"}
\new Voice {
\override Score.MetronomeMark.padding = 3
\tempo "Andante" 4 = 100
\time 4/4
\clef treble
\key ef \major
\bark
}
}
\version "2.20.0"
\language "english"
\header {tagline = ""}
\new PianoStaff <<
\new Staff = "su" {
<e' c'>8
\change Staff = "giù"
g8 fis g
\change Staff = "su"
<g'' c''>8
\change Staff = "giù"
e8 dis e
\change Staff = "su"
}
\new Staff = "giù" {
\clef bass
% conserva il contesto
s1
}
>>
In base ai parametri musicali con i quali vogliamo lavorare dobbiamo:
Stabilire un formato simbolico per i dati in ingresso che ci permetta di manipolarli algoritmicamente con facilità.
Formalizzare e automatizzare il processo di conversione dei simboli scelti in ingresso in quelli propri della sintassi di lilypond in uscita.
I parametri di qualsiasi evento sonoro indipendenti dal linguaggio musicale impiegato sono:
Per ulteriori informazioni possiamo aprire il Notebook dedicato.
Tutti possono essere rappresentati in notazione simbolica e le tecniche di composizione algoritmica (Computer Aided Composition) forniscono un aiuto nel generarli e/o manipolarli secondo regole sintattiche proprie di una qualsiasi tradizione musicale o nella creazione di nuovi linguaggi.
In questo Notebook ci occuperemo solo dei primi tre in quanto gli algoritmi e le tecniche illustrate sono facilmente applicabili anche per gli altri due.
Stabiliamo le unità di misura dei diversi parametri e i tipi di data più appropriati al mapping necessario per la conversione simbolica tra python (input) e lilypond (outpt).
# midi 60 61 pausa 63 64 65 (valori assoluti midi)
# indici 0 1 2 3 4 5 (rapporti intervallari)
alt = ("c\'", "cs\'", "r\'", "ds\'", "e\'","f\'") # tuple
# suddivisione_unità : simbolo
dur = {1:"4", 0.75: "8.", 0.5:"8", 0.25:"16"} # dictionary
# midi_velocity : simbolo
vel = {54:"\\p", 74:"\\mf",94:"\\ff"} # dictionary (discreto)
# 0 1 2 3 4 5
vel = ("\\pp", "\\p", "\\mp", "\\mf", "\\f"", \\ff") # tuple (sequenziale)
# simbolo musicale : simbolo informatico
sim = {">":"->",".": "-.", "!":"-!"} # dictionary (discreto)
A questo punto si tratta di sviluppare strategie di mapping che a seconda del simbolo in ingresso richiamino quello corrispondente nella sintassi di lilypond in uscita.
Scegliamo di utilizzare come formato sia in ingresso che in uscita delle liste (non np.array) monodimensionali o N-dimensionali per tutti i parametri in modo da poter formattare ulteriormente il codice attraverso altre funzioni.
Per ogni parametro la strategia è differente.
input: lista di valori numerici (Accordi: List 2D).
output: lista di simboli sotto forma di stringa.
# input
singole = [60,62,63,64]
accordi = [[60,64,67],-1,[59,-2,67]]
Definiamo una tuple con tutti i simboli musicali corrispondenti alle note midi.
Gli indici dei simboli corrispondono all'altezza MIDI.
# 0 1 2 3 4 5 6 7 8 9 10 11 ...
sca = ( 'c,,,,','cs,,,,','d,,,,','ds,,,,','e,,,,','f,,,,','fs,,,,','g,,,,','gs,,,,','a,,,,','as,,,,','b,,,,',
'c,,,', 'cs,,,', 'd,,,', 'ds,,,', 'e,,,', 'f,,,', 'fs,,,', 'g,,,', 'gs,,,', 'a,,,', 'as,,,', 'b,,,',
'c,,', 'cs,,', 'd,,', 'ds,,', 'e,,', 'f,,', 'fs,,', 'g,,', 'gs,,', 'a,,', 'as,,', 'b,,',
'c,', 'cs,', 'd,', 'ds,', 'e,', 'f,', 'fs,', 'g,', 'gs,', 'a,', 'as,', 'b,',
'c ', 'cs ', 'd ', 'ds ', 'e ', 'f ', 'fs ', 'g ', 'gs ', 'a ', 'as ', 'b ',
"c'", "cs'", "d'", "ds'", "e'", "f'", "fs'", "g'", "gs'", "a'", "as'", "b'",
"c''", "cs''", "d''", "ds''", "e''", "f''", "fs''", "g''", "gs''", "a''", "as''", "b''",
"c'''", "cs'''", "d'''", "ds'''", "e'''", "f'''", "fs'''", "g'''", "gs'''", "a'''", "as'''", "b'''",
"c''''","cs''''","d''''","ds''''","e''''","f''''","fs''''","g''''","gs''''","a''''","as''''","b''''",
"c'''''","cs'''''","d'''''","ds'''''","e'''''","f'''''","fs'''''","g'''''","gs'''''","a'''''",
"as'''''","b'''''", "c''''''", "cs''''''", "d''''''", "ds''''''", "e''''''", "f''''''", "fs''''''",
"g''''''", "gs''''''", "a''''''", "as''''''", "b''''''")
# 123 124 125 126 127
Definiamo una lista vuota e un ciclo all'interno del quale:
out = [] # lista vuota per output
for i in accordi: # Itera la collezione in ingresso (cambiare 'accordi' con 'singole'...)
if type(i) is list: # se è un'array 2D (accordo)
x = '< ' # Formatta l'elemento correttamente
for n in i:
if n == -1:
x += 'r '
elif n == -2:
x += 's '
else:
x += sca[n] + ' ' # utilizza i valori midi come indici e aggiunge il simbolo corrispondente
out.append(x + '>') # alla lista 'out'
else: # se invece è una nota singola...
if i == -1:
out.append('r ')
elif i == -2:
out.append('s ')
else:
out.append(sca[i])
print(out)
["< c' e' g' >", 'r ', "< b s g' >"]
A questo punto possiamo unire tutto in una funzione dedicata.
def pitch(a=[60]):
''' In: Una lista di valori numerici (midinote)
Out: Una lista di stringhe (simboli)
Mappa note midi (int) in forma simbolica interpretabile da lilypond
'''
sca = ('c,,,,','cs,,,,','d,,,,','ds,,,,','e,,,,','f,,,,','fs,,,,','g,,,,','gs,,,,','a,,,,','as,,,,','b,,,,',
'c,,,', 'cs,,,', 'd,,,', 'ds,,,', 'e,,,', 'f,,,', 'fs,,,', 'g,,,', 'gs,,,', 'a,,,', 'as,,,', 'b,,,',
'c,,', 'cs,,', 'd,,', 'ds,,', 'e,,', 'f,,', 'fs,,', 'g,,', 'gs,,', 'a,,', 'as,,', 'b,,',
'c,', 'cs,', 'd,', 'ds,', 'e,', 'f,', 'fs,', 'g,', 'gs,', 'a,', 'as,', 'b,',
'c ', 'cs ', 'd ', 'ds ', 'e ', 'f ', 'fs ', 'g ', 'gs ', 'a ', 'as ', 'b ',
"c'", "cs'", "d'", "ds'", "e'", "f'", "fs'", "g'", "gs'", "a'", "as'", "b'",
"c''", "cs''", "d''", "ds''", "e''", "f''", "fs''", "g''", "gs''", "a''", "as''", "b''",
"c'''", "cs'''", "d'''", "ds'''", "e'''", "f'''", "fs'''", "g'''", "gs'''", "a'''", "as'''", "b'''",
"c''''","cs''''","d''''","ds''''","e''''","f''''","fs''''","g''''","gs''''","a''''","as''''","b''''",
"c'''''","cs'''''","d'''''","ds'''''","e'''''","f'''''","fs'''''","g'''''","gs'''''","a'''''",
"as'''''","b'''''", "c''''''", "cs''''''", "d''''''", "ds''''''", "e''''''", "f''''''", "fs''''''",
"g''''''", "gs''''''", "a''''''", "as''''''", "b''''''")
out = []
for i in a:
if type(i) is list:
x = '< '
for n in i:
if n == -1:
x += 'r '
elif n == -2:
x += 's '
else:
x += sca[n] + ' '
out.append(x + '>')
else:
if i == -1:
out.append('r ')
elif i == -2:
out.append('s ')
else:
out.append(sca[i])
return out
alt = [60,[61,-1,65],-1,[78,65],-2] # input
a = pitch(alt) # funzione
print(a) # output
print(len(a), 'elementi musicali')
["c'", "< cs' r f' >", 'r ', "< fs'' f' e'' >", 's '] 5 elementi musicali
Stabiliamo una sintassi per specificare le durate sotto forma di suddivisioni dell'unità:
input: lista di valori numerici.
1 = intero 2 = metà 4 = quarto 8 = ottavo 16 = sedicesimo 32 = trentaduesimo
[valore_da suddividere, [suddivisioni]] [4 , [3, 1]] = Ottavo puntato + sedicesimo [2 , [1, 1, 1 ]] = Terzina di quarti [4 , [1.5, 0.5,1 ]] = Terzina puntata...
output: lista di simboli sotto forma di stringa (per i ritmi irregolari o puntati: array 2D).
# input
# 1 2 3 4
regolari = [4, 8, 16, 16, 4, 4 ]
puntati = [4, [4,[3,1]], 4, [4,[1,3]]]
irregolari = [4, [4,[3,2,2,3]], [4,[1,1,1]], 4 ]
Definiamo una collezione di tutti i simboli musicali corrispondenti alle durate dal trentaduesimo all'intero con un passo incrementale di 1/32 (avremmo potuto partire dal sessantaquattresimo ma la collezione sarebbe stata molto più lunga...)
s = ( '32', '16', '16.', '8',
'8~32', '8.', '8..', '4',
'4~32', '4~16', '4~16.', '4.',
'4.~32', '4..', '4...', '2',
'2~32', '2~16', '2~16.', '2~8',
'2~8~32','2~8.', '2~8..', '2.',
'2.~32', '2.~16','2.~16.','2..',
'2..~32','2...', '2....', '1')
print(len(s))
32
Definiamo un numpy array corrispondente di valori assoluti (vedremo più avanti le caratteristiche).
import numpy as np # utilizza numpy
r = 1/32 * np.arange(1,33,1) # da 1 a 33 (escluso) con passo di 1
print(r)
print(len(r))
[0.03125 0.0625 0.09375 0.125 0.15625 0.1875 0.21875 0.25 0.28125 0.3125 0.34375 0.375 0.40625 0.4375 0.46875 0.5 0.53125 0.5625 0.59375 0.625 0.65625 0.6875 0.71875 0.75 0.78125 0.8125 0.84375 0.875 0.90625 0.9375 0.96875 1. ] 32
Definiamo una tuple di liste derivate dalla precedente con valori assoluti riscalati su ratio irregolari (valori assoluti non compresi nella suddivisione in trentaduesimi).
vals = (r, r/(3/2), r/(5/4), r/(6/4), r/(7/4), r/(9/8), r/(11/8), r/(13/8), r/(15/8))
print(vals[1])
print(len(vals[1]))
[0.02083333 0.04166667 0.0625 0.08333333 0.10416667 0.125 0.14583333 0.16666667 0.1875 0.20833333 0.22916667 0.25 0.27083333 0.29166667 0.3125 0.33333333 0.35416667 0.375 0.39583333 0.41666667 0.4375 0.45833333 0.47916667 0.5 0.52083333 0.54166667 0.5625 0.58333333 0.60416667 0.625 0.64583333 0.66666667] 32
Definiamo una tuple di dizionari vuoti uno per ogni lista della tuple precedente.
Popoliamo ogni singolo dizionario associando ogni valore assoluto (key) con il simbolo corrispondente (val).
I valori assoluti sono arrotondati a cinque decimali per diminuire le possibilità di errore.
dizi = ({}, {}, {}, {}, {}, {}, {}, {}, {})
for n in range(len(vals)): # Riempie i dizionari con chiave:valore
for i in range(32):
dizi[n][np.round_(vals[n][i], decimals=5)] = s[i] # Round a 5 decimali per debugging
print(dizi[2])
{0.025: '32', 0.05: '16', 0.075: '16.', 0.1: '8', 0.125: '8~32', 0.15: '8.', 0.175: '8..', 0.2: '4', 0.225: '4~32', 0.25: '4~16', 0.275: '4~16.', 0.3: '4.', 0.325: '4.~32', 0.35: '4..', 0.375: '4...', 0.4: '2', 0.425: '2~32', 0.45: '2~16', 0.475: '2~16.', 0.5: '2~8', 0.525: '2~8~32', 0.55: '2~8.', 0.575: '2~8..', 0.6: '2.', 0.625: '2.~32', 0.65: '2.~16', 0.675: '2.~16.', 0.7: '2..', 0.725: '2..~32', 0.75: '2...', 0.775: '2....', 0.8: '1'}
A questo punto possiamo mappare i valori in input con i dizionari definendo tre liste vuote e un ciclo all'interno del quale:
regolari = [4, 8, 16, 16, 4, 4 ]
puntati = [4, [4,[3,1]], 4, [4,[1,3]]]
irregolari = [4, [4,[3,2,2,3]], [4,[1,1,1]], 4 ]
out = [] # lista vuota per output
for i in irregolari: # CAMBIA con 'puntati' o 'irregolari'
if type(i) == list: # Se è una lista (valori puntati o irregolari)
irr = [] # Crea una lista vuota per valori irregolari
sudd = [] # Crea una lista vuota per suddivisioni irregolari
if sum(i[1]) in (4,8,16,32): # Se la somma delle suddivisioni è compresa nella tuple
for d in i[1]:
out.append(dizi[0][round((1/i[0] / sum(i[1])) * d,5)]) # Aggiunge alla lista
elif sum(i[1]) in (3,3):
irr.append('\\tuplet 3/2')
for d in i[1]:
sudd.append(dizi[1][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (5,10):
irr.append('\\tuplet 5/4')
for d in i[1]:
sudd.append(dizi[2][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (6,12):
irr.append('\\tuplet 6/4')
for d in i[1]:
out.append(dizi[3][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (7,14):
irr.append('\\tuplet 7/4')
for d in i[1]:
out.append(dizi[4][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (9,12):
irr.append('\\tuplet 9/8')
for d in i[1]:
out.append(dizi[5][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (11,22):
irr.append('\\tuplet 11/8')
for d in i[1]:
out.append(dizi[6][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (13,26):
irr.append('\\tuplet 13/8')
for d in i[1]:
out.append(dizi[7][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (15,30):
irr.append('\\tuplet 15/8')
for d in i[1]:
out.append(dizi[8][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
else: # Se NON E' una lista (valori regolari)
out.append(dizi[0][1/i])
print(out)
['4', ['\\tuplet 5/4', ['16.', '16', '16', '16.']], ['\\tuplet 3/2', ['8', '8', '8']], '4']
A questo punto possiamo unire tutto in una funzione dedicata.
import numpy as np # utilizza numpy...
def dur(a=[4,4,4,4]):
''' In: Una collezione 1D o 2D con valori numerici
Out: Tuple di stringhe che comprende i valori di durata
in forma simbolica compilabile da lilypond
'''
s = ( '32', '16', '16.', '8',
'8~32', '8.', '8..', '4',
'4~32', '4~16', '4~16.', '4.',
'4.~32', '4..', '4...', '2',
'2~32', '2~16', '2~16.', '2~8',
'2~8~32','2~8.', '2~8..', '2.',
'2.~32', '2.~16','2.~16.','2..',
'2..~32','2...', '2....', '1')
r = 1/32 * np.arange(1,33,1)
vals = (r, r/(3/2), r/(5/4), r/(6/4), r/(7/4), r/(9/8), r/(11/8), r/(13/8), r/(15/8))
dizi = ({}, {}, {}, {}, {}, {}, {}, {}, {})
for n in range(len(vals)):
for i in range(32):
dizi[n][np.round_(vals[n][i], decimals=5)] = s[i]
# ----------------------------------------------------------------------------------
out = []
for i in a:
if type(i) == list:
irr = []
sudd = []
if sum(i[1]) in (4,8,16,32):
for d in i[1]:
out.append(dizi[0][round((1/i[0] / sum(i[1])) * d,5)])
elif sum(i[1]) in (3,3):
irr.append('\\tuplet 3/2')
for d in i[1]:
sudd.append(dizi[1][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (5,10):
irr.append('\\tuplet 5/4')
for d in i[1]:
sudd.append(dizi[2][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (6,12):
irr.append('\\tuplet 6/4')
for d in i[1]:
sudd.append(dizi[3][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (7,14):
irr.append('\\tuplet 7/4')
for d in i[1]:
sudd.append(dizi[4][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (9,12):
irr.append('\\tuplet 9/8')
for d in i[1]:
sudd.append(dizi[5][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (11,22):
irr.append('\\tuplet 11/8')
for d in i[1]:
sudd.append(dizi[6][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (13,26):
irr.append('\\tuplet 13/8')
for d in i[1]:
sudd.append(dizi[7][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
elif sum(i[1]) in (15,30):
irr.append('\\tuplet 15/8')
for d in i[1]:
sudd.append(dizi[8][round((1/i[0] / sum(i[1])) * d,5)])
irr.append(sudd)
out.append(irr)
else:
out.append(dizi[0][1/i])
return out
ritm = [4, [4,[3,1]], 4, [4,[1,1,3]]] # input
a = dur(ritm) # funzione
print(a) # output
print(len(a), 'elementi musicali')
['4', '8.', '16', '4', ['\\tuplet 5/4', ['16', '16', '8.']]] 5 elementi musicali
input: lista di valori numerici (MIDI velocity da 0 a 127).
output: lista di simboli sotto forma di stringa.
# input
vels = [5,11,0,33,44,0,66,77,0,99,102,112,123]
Definiamo una tuple di simboli musicali corrispondenti alle principali dinamiche.
din = ('\\ppppp','\\pppp','\\ppp','\\pp','\\p','\\mp','\\mf','\\f','\\ff','\\fff','\\ffff','\\fffff')
Definiamo una lista vuota e un ciclo all'interno del quale:
vels = [5,11,0,33,44,0,66,77,0,99,102,112,123]
out = [] # lista vuota per output
for i in vels: # itera la tuple in ingresso
if i > 0: # se è maggiore di 0
if 1 <= i <= 9: # se compreso tra 1 e 9
out.append(din[0]) # ppppp
elif 10 <= i <= 19:
out.append(din[1]) # pppp
elif 20 <= i <= 29:
out.append(din[2]) # ppp
elif 30 <= i <= 39:
out.append(din[3]) # pp
elif 40 <= i <= 49:
out.append(din[4]) # p
elif 50 <= i <= 59:
out.append(din[5]) # mp
elif 60 <= i <= 69:
out.append(din[6]) # mf
elif 70 <= i <= 79:
out.append(din[7]) # f
elif 80 <= i <= 89:
out.append(din[8]) # ff
elif 90 <= i <= 109:
out.append(din[9]) # fff
elif 100 <= i <= 119:
out.append(din[10]) # ffff
else:
out.append(din[11]) # fffff
else:
out.append('') # Se è uguale a 0
print(out)
['\\ppppp', '\\pppp', '', '\\pp', '\\p', '', '\\mf', '\\f', '', '\\fff', '\\fff', '\\ffff', '\\fffff']
A questo punto possiamo unire tutto in una funzione dedicata.
def vel(a=['nil']): # nome(parametri)
''' In: Una collezione con valori numerici (velocity)
Out: Una tuple con stringhe
Converte valori midi in forma simbolica per le dinamiche compilabile da lilypond
'''
din = ('\\ppppp','\\pppp','\\ppp','\\pp','\\p','\\mp','\\mf','\\f','\\ff','\\fff','\\ffff','\\fffff')
out = []
if a == ['nil']: # se non specifichiamo le intensità in input...
a = [''] * 1 # genera una lista vuota...
else:
for i in a:
if i > 0:
if 1 <= i <= 9:
out.append(din[0]) # ppppp
elif 10 <= i <= 19:
out.append(din[1]) # pppp
elif 20 <= i <= 29:
out.append(din[2]) # ppp
elif 30 <= i <= 39:
out.append(din[3]) # pp
elif 40 <= i <= 49:
out.append(din[4]) # p
elif 50 <= i <= 59:
out.append(din[5]) # mp
elif 60 <= i <= 69:
out.append(din[6]) # mf
elif 70 <= i <= 79:
out.append(din[7]) # f
elif 80 <= i <= 89:
out.append(din[8]) # ff
elif 90 <= i <= 99:
out.append(din[9]) # fff
elif 100 <= i <= 109:
out.append(din[10]) # ffff
else:
out.append(din[11]) # fffff
else:
out.append('') # 0
return out
din = [127,64,0,20,50] # input
#din = ['nil']
a = vel(din) # funzione
print(a) # output
print(len(a), 'elementi musicali')
['\\fffff', '\\mf', '', '\\ppp', '\\mp'] 5 elementi musicali
input: lista di simboli semplificati in ingresso (stringhe).
output: lista di simboli sotto forma di stringa.
# input
exps = ('>', ' ', '!', ' ', '.')
Definiamo un dictionary di simboli musicali corrispondenti ai principali segni di espressione.
expr = {'>':'->', '^':'-^', '!':'-!', '.':'-.', '_':'-_', '-':'--', 'expr':'\\espressivo',
'tr':'\\trill', 'm':'\\prall', 'cor':'\\fermata', ' ':''}
Definiamo una lista vuota e un ciclo all'interno del quale:
out = [] # lista vuota per output
for i in exps: # itera la collezione in ingresso
out.append(expr[i])
print(out)
['->', '', '-!', '', '-.']
A questo punto possiamo unire tutto in una funzione dedicata.
def expr(a=['nil']):
''' In: Una collezione di simboli semplificati
Out: Una tuple con stringhe
Converte simboli elementari in simboli compresi da lilypond
'''
expr = {'>':'->', '^':'-^', '!':'-!', '.':'-.', '_':'-_', '-':'--', 'expr':'\\espressivo',
'tr':'\\trill', 'm':'\\prall', 'cor':'\\fermata', ' ':''}
out = []
if a == ['nil']: # se non specifichiamo le espressioni in input...
a = [' '] * 1 # genera una lista vuota...
for i in a:
out.append(expr[i])
return out
acc = ['>', ' ', '!', ' ', '.'] # input
#acc = ['nil']
a = expr(acc) # funzione
print(a) # output
print(len(a), 'elementi musicali')
['->', '', '-!', '', '-.'] 5 elementi musicali
Abbiamo ottenuto una lista di simboli per ogni parametro.
Tutte le liste devono avere lo stesso numero di elementi.
# 0 1 2 3 4
alt = ["c'" , "< cs' e' f' >", "a'", "< fs'' f' >", "gs'" , "a'"]
dur = ['4' , '8.' , '16', ['\\tuplet 5/4', ['16' , '16' , '8.']]]
amp = ['\\fffff', '\\mf' , '' , '\\ppp' , '' , '\\mp']
exp = ['->' , '' , '-!', '' , '-.' , '']
Ora per ogni nota dobbiammo reuperare i diversi simboli dalle diverse liste formattarli correttamente in una sola stringa in output.
Prendiamo solo altezze e durate.
Per il loop necessario a iterare le liste in sincrono prendiamo come lista di riferimento quella delle durate in quanto più complessa a causa della bidimensionalità.
a = '\\version "2.24.0"' # Necessari alla compilazione in lilypond
b = '\\language "english"'
out = '' # Stringa vuota
id = -1 # Inizializzazione dell'indice per richiamare gli elementi dalle liste
for i in dur: # RIFERIMENTO: lista delle DURATE
if type(i)==list: # Se è una lista (irregolare o puntato)
irr = ''
irr = irr + i[0] + ' { ' # Recupera il simbolo '\tuplet' e aggiunge la parentesi
for n in i[1]:
id += 1 # Aggiorna indice
irr = irr + alt[id] + n + ' ' # Per ogni nota: altezza + durata + uno spazio
irr = irr + '} ' # Chiude la parentesi
out = out + irr # Aggiunge l'evento all'ouput
else: # Se è un valore regolare
id += 1 # Aggiorna indice
out = out + alt[id] + i + ' ' # Per ogni nota: altezza + durata + uno spazio
out = '{ ' + out + '}' # Aggiunge le parentesi dell'espressione musicale
out = a + '\n' + b + '\n \n' + out # Formatta
print(out)
\version "2.24.0" \language "english" { c'4 < cs' e' f' >8. a'16 \tuplet 5/4 { < fs'' f' >16 gs'16 a'8. } }
Anche in questo caso possiamo programmare una funzione dedicata.
Possiamo strutturare la funzione in modo modulare: gli unici argomenti (parametri) che dobbiamo passare alla funzione sono:
Le dinamiche e le intensità le possiamo specificare o meno, in questo ultimo caso vengono generate automaticamente liste vuote che saranno formattate.
def score(note=[60],dur=[4],din=['nil'], esp=['nil']): # Parametri di default
a = '\\version "2.24.0"' # Possiamo includerli o meno...
b = '\\language "english"'
if din == ['nil']: # se non specifichiamo le intensità in input...
din = [''] * len(note) # genera una lista vuota...
if esp == ['nil']: # se non specifichiamo le espressioni in input...
esp = [' '] * len(note) # genera una lista vuota...
out = ''
id = -1
for i in dur:
if type(i)==list:
irr = ''
irr = irr + i[0] + ' { '
for n in i[1]:
id += 1
irr = irr + note[id] + n + din[id] + esp[id] + ' ' # Aggiunto din[id] + esp[id]
irr = irr + '} '
out = out + irr
else:
id += 1
out = out + note[id] + i + din[id] + esp[id] + ' ' # Aggiunto din[id] + esp[id]
out = a + '\n' + b + '\n \n' + '{ ' + out + '}' # Formatta
return out
alt = ["c'" , "< cs' e' f' >", "a'", "< fs'' f' >", "gs'" , "a'"]
dur = ['4' , '8.' , '16', ['\\tuplet 5/4', ['16' , '16' , '8.']]]
amp = ['\\fffff', '\\mf' , '' , '\\ppp' , '' , '\\mp']
exp = ['->' , '' , '-!', '' , '-.' , '']
a = score(alt,dur,amp,exp)
print(a)
\version "2.24.0" \language "english" { c'4\fffff-> < cs' e' f' >8.\mf a'16-! \tuplet 5/4 { < fs'' f' >16\ppp gs'16-. a'8.\mp } }
Possiamo copiare le funzioni in un file di python esterno e richiamarle come moduli custom.
Per questo Notebook utilizzeremo il file: 'moduli/py2ly.py'
Principalmente il modulo score che accetta in input:
Se non vogliamo specificare tutti gli argomenti nell'ordine dobbiamo utilizzare la keyword notation.
import os
import sys
path = os.path.abspath('moduli') # Restituisce il path assoluto della cartella
sys.path.insert(0, path) # Aggiunge la cartella moduli alla directory di lavoro
import py2ly as ply # Importa i moduli custom
from IPython.display import Image # Libreria per visualizzare immagini e altro
# -----------------------------------------------
file = 'esempi/acci'
pitch = [67,[61,64,65],-1,[78,65],68]
durs = [4, [4,[1,1,1]], 2]
acc = [" ",'>', ' ','-', '-']
out = ply.score(file,pitch=pitch,dur=durs,expr=acc) # Conversione
## print(out) # Visualizza il codice per lilypond
Image(filename = file+".png", width = 400) # Visualizza immagine nel Notebook
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/acci.ly» Analisi... Interpretazione della musica... Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in una pagina... Disegno dei sistemi... Conversione a PNG... Riuscito: compilazione completata con successo
Se vogliamo formalizzare un pensiero musicale possiamo farlo attraverso tre tipi di operazioni:
Ognuna di queste operazioni può essere effettuata attraverso tre diversi approcci:
Spesso questi approcci sono impiegati contemporaneamente all'interno dello stesso algoritmo e le operazioni concatenate tra loro.
Esploriamo alcune procedure per generare diversi tipi di materiale musicale in modo deterministico.
Quanto generato in questo modo può essere considerato come il materiale di base da elaborare e modificare successivamente nella composizione di uno o più brani secondo le regole del linguaggio musicale di volta in volta impiegato.
Possiamo definire sequenze intervallari predefinite.
magg = [0,2,4,5,7,9,11]
mina = [0,2,3,5,7,8,11]
minm = [0,2,3,5,7,8,10]
toni = [0,2,4,6,8,10]
Per poi richiamarle e sommarle ad una nota perno (root).
In questo caso dobbiamo:
N.B. Avremmo potuto effettuare un casting e trasformare la lista in un numpy array per poi sommare diretttamente tutto il contenuto con l'intero senza il ciclo, ma per ragioni di coerenza che scopriremo in seguito non lo abbiamo fatto.
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Scale" # Nome file
mina = [0,2,3,5,7,8,11] # Intervalli
root = 64 # Nota perno
n = len(mina) # Numero di note della scala
scala = [] # Lista vuota
for i in mina: # Itera tutti gli elementi
i += root # Somma il valore della nota perno e aggiorna la variabile
scala.append(i) # Aggiunge alla lista vuota
dur = [1] * n # List: non è un'operazione matematica (duplica n volte)
print(scala)
print(dur)
# ---------------------------------------------------------------
ply.score(file,200,30,scala,dur)
Image(filename = file+".png", width = 850)
[64, 66, 67, 69, 71, 72, 75] [1, 1, 1, 1, 1, 1, 1]
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Scale.ly» Analisi... Interpretazione della musica...[8] Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in una pagina... Disegno dei sistemi... Conversione a PNG... Riuscito: compilazione completata con successo
Possiamo anche definire direttamente sequenze di altezze assolute (con o senza pause o spazi vuoti).
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Sequenze"
seqa = [70,66,-1,75,83,80,-2,69] # Note MIDI
dur = [8] * len(seqa) # Durate
# ---------------------------------------------------------------
ply.score(file,200,30,seqa,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Sequenze.ly» Analisi... Interpretazione della musica... Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in una pagina... Disegno dei sistemi... Conversione a PNG... Riuscito: compilazione completata con successo
Così come per le scale possiamo specificare le altezze di riserve armoniche come rapporti intervallari per poi sommare i valori a una nota perno che in un sistema tonale assumerà la funzione di tonica.
Possiamo farlo attraverso liste 2D.
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Accordi_3"
harm = {'I':[0,4,7],'IV':[0,5,9],'V':[-1,2,7]} # Riserva armonica (dictionary)
seqa = [harm['I'],harm['IV'],harm['V'],harm['I'],harm['V'],harm['IV'],harm['I']]
tonica = 60
out = [] # Lista vuota che conterrà i valori trasposti
for i in seqa: # Itera gli elementi...
if type(i) == list: # Se accordo...
acc = [] # Crea una lista vuota (seconda dimensione)
for n in i: # Itera gli elementi dell'accordo...
acc.append(n + tonica) # Esegue la somma su di ognuno aggiungendo il risultato all'array
out.append(acc) # Aggiunge alla lista principale (prima dimensione)
else:
out.append(i + tonica) # Se nota singola...
print(out)
dur = [2] * len(seqa)
# ---------------------------------------------------------------
ply.score(file,200,30,out,dur)
Image(filename = file+".png", width = 850) # Visualizza immagine nel Notebook
[[60, 64, 67], [60, 65, 69], [59, 62, 67], 72, [60, 64, 67], [59, 62, 67], [60, 65, 69], [60, 64, 67]]
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Accordi_3.ly» Analisi... Interpretazione della musica... Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in una pagina... Disegno dei sistemi... Conversione a PNG... Riuscito: compilazione completata con successo
Siccome questo ultimo codice realizza una trasposizione e può essere applicato sia per note singole che per accordi (anche di densità differenti) possiamo scrivere una funzione dedicata ed eventualmente salvarla come modulo.
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import numpy as np
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
def trasp(seq=[60],root=0):
out = []
for i in seq:
if type(i) == list:
acc = []
for n in i:
acc.append(n + root)
out.append(acc)
else:
out.append(i + root)
return out
file = "esempi/Accordi_3"
a = [[0,4,7],[0,5,9],[2,5,9],[2,5,11],[4,7,12]] # Lista 2D
a = np.array(a) + 60 # Casting in np.array e somma nota perno
a = a.tolist() # Casting da np.array a list
b = trasp(a,5)
c = trasp(a,4)
pit = a + b + c # Concatena sequenza come progressione armonica...
dur = [2] * (len(pit)-1) +[1] # Durate differenti...
print(a)
print(b)
print(c)
# ---------------------------------------------------------------
ply.score(file,200,40,pit,dur)
Image(filename = file+".png", width = 850) # Visualizza immagine nel Notebook
[[60, 64, 67], [60, 65, 69], [62, 65, 69], [62, 65, 71], [64, 67, 72]] [[65, 69, 72], [65, 70, 74], [67, 70, 74], [67, 70, 76], [69, 72, 77]] [[64, 68, 71], [64, 69, 73], [66, 69, 73], [66, 69, 75], [68, 71, 76]]
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Accordi_3.ly» Analisi... Interpretazione della musica...[8] Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in una pagina... Disegno dei sistemi... Conversione a PNG... Riuscito: compilazione completata con successo
Notiamo la differenza: La stessa operazione di trasposizione l'abbiamo effettuata in due modi differenti per sottolineare una caratteristica dei numpy array:
Nel primo caso il numero di elementi dei subarray (numero di note dell'accordo) deve essere uguale mentre nel secondo no.
A seconda delle circostanze musicali possiamo scegliere di utilizzare un metodo oppure l'altro.
Attraverso liste 2D possiamo definire (generare) sia riserve armoniche con densità uniforme ovvero con lo stesso numero di note per ogni accordo che con densità non uniforme ovvero con un numero diverso di note per ogni accordo.
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Accordi_2"
a = [[60,64,67,69],[60,66,69,71,75],[69,77],87,92,[70,74,87,93]] # non uniforme
b = [[68,84,87],[73,77,79],[63,69,77],[64,67,72]] # uniforme
seqa = a + b # concatena
dur = [4,2,8,8,[1,[1,3]]] + [2] * 4
# ---------------------------------------------------------------
ply.score(file,200,30,seqa,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Accordi_2.ly» Analisi... Interpretazione della musica... Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in una pagina... Disegno dei sistemi... Conversione a PNG... Riuscito: compilazione completata con successo