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
Possiamo specificare cellule ritmiche di base che saranno poi eleborate in diverso modo (esattamente come per le altezze).
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/Ritmi_1"
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
dur = [4,2,8,8,[1,[1,3]]] + [16] * 4 + [4,[4,[1,1,1,2]],4]
n = 0 # init variabile
for i in dur: # calcola il numero di note...
if type(i) == list: # se ritmo puntato o irregolare
for g in i[1]:
n += 1
else: # se ritmo regolare
n += 1
print(n)
alt = [72] * n
# ---------------------------------------------------------------
ply.score(file,200,30,alt,dur)
Image(filename = file+".png", width = 850)
16
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Ritmi_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... Conversione a PNG... Riuscito: compilazione completata con successo
Ricordiamo che le pause sono definite da -1 nella lista delle altezze.
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/Ritmi_2"
pit = [[72,64],-1,63,[65,67,69], 75,[71,67], -1, 65,66, -1 ,60, 63,67,-1,70,[60,64,72]]
dur = [4, 2, 8, 8,[1,[1,3]]] + [16] * 4 + [4,[4,[1,1,1,2]],4]
# ---------------------------------------------------------------
ply.score(file,200,30,pit,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Ritmi_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
Mentre gli spazi da -2.
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/Ritmi_2"
pit = [72] * 3 + [-2,-2,72] + [72] * 3 + [-2,72,-2]
dur = [16] * 12
# ---------------------------------------------------------------
ply.score(file,200,30,pit,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Ritmi_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
Possiamo anche definire pattern dinamici come ad esempio un alternarsi di accenti o note staccate e legate.
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/Accenti_1"
acc = [' '] * 12 + ['>'] + [' ']*3
#acc = ['>',' ',' ','>','>','.',' ','>'] * 2
pit = [72,65,65,72,72,69,-1,72] * 2
n = len(acc)
dur = [16] * n
# ---------------------------------------------------------------
ply.score(file,200,30,pit,dur,expr=acc)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Accenti_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... Conversione a PNG... Riuscito: compilazione completata con successo
Le principali tecniche di elaborazione motivica del materiale nella tradizione musicale occidentale sono:
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/Elabora_1"
alt = [-1,60,62,64,65,67,65,64,69,62,67,69,67,65,64]
dur = [8,8,8,8,[4,[6,1,1]],8,8,8,[4,[3,1]],16,16,16]
# ---------------------------------------------------------------
ply.score(file,200,30,alt,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Elabora_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... Conversione a PNG... Riuscito: compilazione completata con successo
Estrazione e ricomposizione di incisi motivici.
Possiamo estrarre frammenti ritmico melodici attraverso lo slicing per poi concatenarli a piacere costruendo nuovi profili melodici in modo deterministico.
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import py2ly as ply
from IPython.display import Image
from IPython.display import display
# ---------------------------------------------------------------
fileo = "esempi/Elabora_1" # Originale
filee = "esempi/Elabora_2" # Elaborato
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # ID altezze
alt = [-1,60,62,64, 65,67,65, 64,69,62, 67,69, 67, 65, 64] # Originale
dur = [8, 8, 8, 8,[4,[6, 1, 1]], 8,8, 8,[4,[3, 1]], 16, 16, 16]
# 0 1 2 3 4 5 6 7 8 9 10 11 # ID ritmi
# 0 1 2 3 4 5 6 7
a = [alt[:2], alt[1:3], alt[:4], alt[4:7], alt[7:9], alt[7:10], alt[10:12], alt[12:]] # Riserve frammenti
b = [dur[:2], dur[1:3], dur[:4], dur[4:5], dur[5:7], dur[5:8], dur[8:9], dur[9:]]
# Ri-composizione
alta = a[0] + a[4] + a[4] + a[6] + a[3] + a[1] + a[5] + a[5] + a[3] + a[4] + a[3] + a[1] + a[6] + a[6] + a[4]
dura = b[0] + b[4] + b[4] + b[6] + b[3] + b[1] + b[5] + b[5] + b[3] + b[4] + b[3] + b[1] + b[6] + b[6] + b[4]
# ---------------------------------------------------------------
ply.score(fileo,230,20,alt,dur)
ply.score(filee,230,20,alta,dura)
x = Image(filename = fileo+".png", width = 990)
y = Image(filename = filee+".png", width = 990)
display(x, y)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Elabora_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... attenzione: compressione di una pagina strapiena di 0.2 spazi rigo attenzione: la pagina 1 è stata compressa Conversione a PNG... Riuscito: compilazione completata con successo Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Elabora_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... attenzione: compressione di una pagina strapiena di 0.2 spazi rigo attenzione: la pagina 1 è stata compressa Conversione a PNG... Riuscito: compilazione completata con successo
Nell'esempio percedente la ricomposizione è effettuata attraverso una procedura deterministica ma potremmo impiegare un algoritmo non deterministico per generare in modo randomico gli indici dei frammenti.
Tutti i software hanno generatori di numeri pseudocasuali che restituiscono una sequenza finita di numeri casuali.
Infatti dopo un certo numero di numeri generati la sequenza ricomincia da capo in quanto è controllata attraverso un algoritmo deterministico.
In python ci dono diverse librerie dedicate e nei prossimi paragrafi utilizzeremo le funzioni del modulo Random della libreria Random.
Tutti i generatori di numeri pseudocasuali hanno un parametro chiamato seed (seme) che è un termine dell'algoritmo deterministico che inizializza la sequenza numerica randomica.
Nei generatori della libreria random questa si ripete in modo ciclico dopo $2^{19937-1}$ numeri.
2**19937-1
340282366920938463463374607431768211456
Se non specificato viene generato a ogni esecuzione del codice dal sistema operativo (current system time) e la sequenza è diversa ad ogni valutazione.
import random as rn
a = []
for i in range(5):
i = rn.randint(0,10)
a.append(i)
print(a)
[3, 10, 4, 2, 9]
Se invece specificato la sequenza è uguale ad ogni valutazione ma non abbiamo alcun controllo sulle caratteristiche della sequenza stessa.
import random as rn
rn.seed(11)
a = []
for i in range(5):
i = rn.randint(0,10)
a.append(i)
print(a)
[7, 8, 7, 7, 8]
Possiamo permettere duplicati e non includere il valore max:
import random as rn
a = []
for i in range(20):
i = rn.randrange(0,12,3) # start, stop, step
a.append(i)
print(a)
[3, 3, 9, 3, 0, 9, 6, 3, 0, 0, 9, 9, 3, 0, 0, 0, 0, 3, 3, 0]
Possiamo permettere duplicati e includere il valore max:
import random as rn
a = []
for i in range(5):
i = rn.randint(0,10) # start, stop+1
a.append(i)
print(a)
[10, 4, 10, 5, 2]
Possiamo non permettere duplicati:
import random as rn
a = [i for i in range(0,12,1)] # List comprehension genera una sequenza di interi (min range, step)
print(a)
rn.shuffle(a) # Cambia le posizioni in modo randomico
print(a)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] [1, 4, 6, 0, 9, 2, 10, 8, 3, 11, 5, 7]
Scegliamo una di queste modalità per generare gli indici dei frammenti che saranno ricomposti in modo pseudocasuale ad ogni esecuzione.
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import random as rn
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Elabora_3"
alt = [-1,60,62,64, 65,67,65, 64,69,62, 67,69, 67, 65, 64] # Originale
dur = [8, 8, 8, 8,[4,[6, 1, 1]], 8,8, 8,[4,[3, 1]], 16, 16, 16]
a = [alt[:2], alt[1:3], alt[2:4], alt[4:8], alt[8:10], alt[9:11], alt[11:13]] # Riserve frammenti
b = [dur[:2], dur[1:3], dur[2:4], dur[4:5], dur[5:7], dur[6:8], dur[8:9]]
# Ri-composizione
id = [i for i in range(len(a))] # Tutti gli indici
rn.shuffle(id) # Cambia le posizioni in modo randomico
alt = []
dur = []
for i in id:
alt += a[i] # crea sequenza altezze
dur += b[i] # crea sequenza durate
# ---------------------------------------------------------------
ply.score(file,200,30,alt,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Elabora_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
Come possiamo notare i risultati sono mensurali in quanto i frammenti hanno tutti la stessa lunghezza e sono suddivisioni del beat.
Con gli opportuni accorgimenti possiamo anche separare le scelte randomiche tra altezze e ritmo.
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import random as rn
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Elabora_3"
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
alt = [-1,60,62,64, 65,67,65, 64,69,62, 67,69, 67, 65, 64] # Originale
dur = [8, 8, 8, 8,[4,[6, 1, 1]], 8,8, 8,[4,[3, 1]], 16, 16, 16]
# 0 1 2 3 4 5 6 7 8 9 10 11
# 0 1 2 3 4 5 6
a = [alt[:2], alt[1:3], alt[2:4], alt[4:8], alt[8:10], alt[9:11], alt[11:13]] # Riserve frammenti
b = [dur[:2], dur[1:3], dur[2:4], dur[4:5], dur[5:7], dur[6:8], dur[8:9]]
alt = []
dur = []
nfr = 15 # numero di frammenti desiderati
for i in range(nfr):
x = rn.randrange(len(a)) # indice randomico per altezze (con duplicati)
alt += a[x] # sincro tra altezze e durate
dur += b[x]
# ---------------------------------------------------------------
ply.score(file,200,30,alt,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Elabora_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
Trasposizione delle altezze.
Possiamo aggiungere al codice precedente una sequenza di trasposizioni deterministiche
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import random as rn
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Elabora_3"
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
alt = [-1,60,62,64, 65,67,65, 64,69,62, 67,69, 67, 65, 64] # Originale
dur = [8, 8, 8, 8,[4,[6, 1, 1]], 8,8, 8,[4,[3, 1]], 16, 16, 16]
# 0 1 2 3 4 5 6 7 8 9 10 11
# 0 1 2 3 4 5 6
a = [alt[:2], alt[1:3], alt[2:4], alt[4:8], alt[8:10], alt[9:11], alt[11:13]] # Riserve frammenti
b = [dur[:2], dur[1:3], dur[2:4], dur[4:5], dur[5:7], dur[6:8], dur[8:9]]
trsp = [0, 1, 3, 4, 9, 10, 11]
alt = []
dur = []
for i in range(len(trsp)): # per ogni trasposizione
x = rn.randrange(len(a)) # indice randomico
for t in a[x]: # itera altezze frammento
if t == -1:
alt.append(-1) # se è pausa rimani pausa
else:
t += trsp[i] # somma (traspone)
alt.append(t) # aggiunge alla lista
dur += b[x]
# ---------------------------------------------------------------
ply.score(file,200,30,alt,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Elabora_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
Oppure non determinisctiche (random.choice())
import random as rn
a = [0,1,2,3,4]
b = rn.choice(a)
print(b)
0
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import random as rn
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Elabora_3"
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
alt = [-1,60,62,64, 65,67,65, 64,69,62, 67,69, 67, 65, 64] # Originale
dur = [8, 8, 8, 8,[4,[6, 1, 1]], 8,8, 8,[4,[3, 1]], 16, 16, 16]
# 0 1 2 3 4 5 6 7 8 9 10 11
# 0 1 2 3 4 5 6
a = [alt[:2], alt[1:3], alt[2:4], alt[4:8], alt[8:10], alt[9:11], alt[11:13]] # Riserve frammenti
b = [dur[:2], dur[1:3], dur[2:4], dur[4:5], dur[5:7], dur[6:8], dur[8:9]]
trsp = [0, 1, 3, -4, 19, 10, 11]
alt = []
dur = []
for i in range(len(trsp)):
x = rn.randrange(len(a))
for t in a[x]:
if t == -1:
alt.append(-1)
else:
t += rn.choice(trsp) # trasposizione casuale
alt.append(t)
dur += b[x]
# ---------------------------------------------------------------
ply.score(file,200,30,alt,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Elabora_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... attenzione: compressione di una pagina strapiena di 0.1 spazi rigo attenzione: la pagina 1 è stata compressa Conversione a PNG... Riuscito: compilazione completata con successo
Retrogrado delle altezze e/o durate e/o ordine dei frammenti.
AGGIUNGI IMMAGINE SEQUENZA ORIGINALE
a = [0,1,2,3,4]
a.reverse() # modifica la lista originale come shuffle()
print(a)
[4, 3, 2, 1, 0]
Retrogrado di tutta una sequenza, separando altezze e durate.
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import random as rn
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Elabora_3"
alt = [-1,60,62,64, 65,67,65, 64,69,62, 67,69, 67, 65, 64] # Originale
dur = [8, 8, 8, 8,[4,[6, 1, 1]], 8,8, 8,[4,[3, 1]], 16, 16, 16]
alt.reverse() # Retrogrado altezze (provare le diverse combinazioni)
#dur.reverse() # Retrogrado durate
# ---------------------------------------------------------------
ply.score(file,200,30,alt,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Elabora_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
Retrogrado dei frammenti di una sequenza.
import os
import sys
path = os.path.abspath('moduli')
sys.path.insert(0, path)
import random as rn
import py2ly as ply
from IPython.display import Image
# ---------------------------------------------------------------
file = "esempi/Elabora_3"
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
alt = [-1,60,62,64, 65,67,65, 64,69,62, 67,69, 67, 65, 64] # Originale
dur = [8, 8, 8, 8,[4,[6, 1, 1]], 8,8, 8,[4,[3, 1]], 16, 16, 16]
# 0 1 2 3 4 5 6 7 8 9 10 11
# 0 1 2 3 4 5 6
a = [alt[:2], alt[1:3], alt[2:4], alt[4:8], alt[8:10], alt[9:11], alt[11:13]] # Riserve frammenti
b = [dur[:2], dur[1:3], dur[2:4], dur[4:5], dur[5:7], dur[6:8], dur[8:9]]
alt = []
dur = []
for i in range(len(a),0,-1): # da 7 a 1
for e in a[i-1]:
alt += a[i-1]
dur += b[i-1]
# ---------------------------------------------------------------
ply.score(file,200,30,alt,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Elabora_3.ly» Analisi... Interpretazione della musica... Pre-elaborazione degli oggetti grafici... Determinazione del numero ottimale di pagine... Compressione della musica in 1 o 2 pagine... Disegno dei sistemi... Conversione a PNG... Riuscito: compilazione completata con successo
Un concetto comune a tutte le tradizioni musicali sia occidentali che non è quello della suddivisione di un ambito.
Penso che questa pratica sia propria della natura umana e che abbia a che fare con il voler misurare e dare un ordine percepibile (comprensibilità) alle cose (non solo musicali).
Per quanto riguarda i diversi linguaggi musicali (organizzazione nel tempo di eventi sonori secondo regole predefinite e condivise) questo concetto è legato principalmente alle altezze (scale o modi) e al ritmo (suddivisioni del tactus).
In python possiamo ottenere delle collezioni che contengono i valori risultanti da questo processo in diversi modi ma quello più esaustivo consiste nello sfruttare le potenziaalità dei Numpy array che sono un particolare tipo di collezioni appartenenti alla libreria NumPy.
Se stiamo usando python della distribuzione Anaconda questa libreria è già installata, altrimenti dobbiamo scaricarla ed installarla nel consueto modo.
I concetti astratti applicati per effettuare delle suddivioni più comuni sono legati a quello dell'interpolazione e comprendono alcune piccole varianti:
import numpy as np
a = np.arange(0,12,2) # inizio, fine, passo
a = np.around(a).astype(int).tolist() # Arrotonda all'intero, doppio casting
print(a)
[0, 2, 4, 6, 8, 10]
import numpy as np
a = np.linspace(0,12,7,True) # inizio, fine, numero, incluso (true)
a = np.around(a).astype(int).tolist() # Arrotonda all'intero, doppio casting
print(a)
[0, 2, 4, 6, 8, 10, 12]
vai da a fino a b in n passi con delta esponenziale (interpolazione esponenziale)
inizio = 60
fine = 72
sudd = 16
curva = 2
inclu = True
d = abs(fine - inizio) # Calcola l'ambito
a = np.linspace(0,1,sudd,inclu)** curva * d + inizio # Tra 0 e 1
a = np.around(a).astype(int).tolist() # Arrotonda all'intero, doppio casting
print(a)
[60, 61, 62, 62, 63, 64, 65, 66, 66, 67, 68, 69, 70, 70, 71, 72]
Vediamo alcuni esempi su come applicare quanto appena esposto nella generazione di altezze.
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
# ---------------------------------------------------------------
file = "esempi/Accenti_1"
inizio = 0
fine = 24
sudd = 17
curva = 1
inclu = True
root = 60
d = abs(fine - inizio) # Calcola l'ambito
a = np.linspace(0,1,sudd,inclu)** curva * d + inizio # Tra 0 e 1...
a = a + root # Aggiunge offset
a = np.around(a).astype(int).tolist() # Arrotonda all'intero, doppio casting
a = np.unique(a) # Elimina i duplicati
n = len(a)
dur = [8] * n
# ---------------------------------------------------------------
ply.score(file,200,30,a,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Accenti_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
Trasformiamo semplicemente la lista da monodimensionale a bidimensionale.
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
# ---------------------------------------------------------------
file = "esempi/Accenti_1"
inizio = 0
fine = 24
sudd = 8
curva = 3
inclu = True
root = 60
d = abs(fine - inizio)
a = np.linspace(0,1,sudd,inclu)** curva * d + inizio
a = a + root
a = np.unique(np.around(a).astype(int)).tolist()
a = [a] # lista 2D
n = len(a)
dur = [1] * n
# ---------------------------------------------------------------
ply.score(file,200,30,a,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Accenti_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... Conversione a PNG... Riuscito: compilazione completata con successo
Se vogliamo sequenze di accordi derivate da curve differenti delle suddivisioni di un ambito possiamo utilizzare un ciclo.
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
# ---------------------------------------------------------------
file = "esempi/Accenti_1"
inizio = 0
fine = 48
sudd = [8,9,5,10,7] # Lista di densità differenti
curva = [1,2,3,4,8] # Lista di curve differenti
inclu = True
root = 60
d = abs(fine - inizio)
out = [] # Lista 2D
for i in range(len(sudd)): # Counter sulla lunghezza della lista di suddivisioni
a = np.linspace(0,1,sudd[i],inclu)** curva[i] * d + inizio + root
a = np.unique(np.around(a).astype(int)).tolist()
out.append(a)
print(out)
n = len(a)
dur = [1] * n
# ---------------------------------------------------------------
ply.score(file,200,40,out,dur)
Image(filename = file+".png", width = 850)
[[60, 67, 74, 81, 87, 94, 101, 108], [60, 61, 63, 67, 72, 79, 87, 97, 108], [60, 61, 66, 80, 108], [60, 61, 62, 65, 69, 78, 90, 108], [60, 62, 71, 108]]
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Accenti_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... Conversione a PNG... Riuscito: compilazione completata con successo
Possiamo anche computare armonie spettrali:
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
# ---------------------------------------------------------------
file = "esempi/Accenti_1"
inizio = 1
fine = 20
sudd = 8
curva = 2 # Lista di curve differenti
inclu = True
fond = 60-12
mtof = (440/32)*(2**((fond-9)/12)) # converte da midi a freq
d = abs(fine - inizio)
a = np.linspace(0,1,sudd,inclu)** curva * d + inizio
a = np.unique(np.around(a).astype(int)).tolist()
spek = a * np.array(mtof)
ftom = 12 * (np.log2(spek) - np.log2(440.0)) + 69 # converte da freq a midi
a = [np.unique(np.around(ftom).astype(int)).tolist() ]
n = len(a)
dur = [1] * n
# ---------------------------------------------------------------
ply.score(file,200,40,a,dur)
Image(filename = file+".png", width = 850)
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Accenti_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
Per quanto riguarda le suddivisioni del beat o tactus con questa procedura possiamo principalmente definire accelerando e/o ritardando scritti suddividendo un numero finito di beats con diversa densità di eventi.
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
# ---------------------------------------------------------------
file = "esempi/Accenti_1"
inizio = 1
fine = 15
sudd = 7
curva = 2
inclu = True
d = abs(fine - inizio) # Calcola l'ambito
a = np.linspace(0,1,sudd,inclu)** curva * d + inizio # Tra 0 e 1...
a = np.unique(np.around(a).astype(int)).tolist() # Arrotonda all'intero, doppio casting, Elimina i duplicati
print(a)
dur = [] # Costruisce le suddivisioni
for i in a:
if i == 1:
x = 4
dur.append(x)
elif i == 2:
for i in range(2):
dur.append(8)
elif i == 4:
for i in range(4):
dur.append(16)
elif i == 8:
for i in range(8):
dur.append(32)
else:
x = [4,[1]*i]
dur.append(x)
print(dur)
# Calcola il numero di note
n = 0
for i in dur:
if type(i) == list:
x = sum(i[1])
#print(sum(i[1]))
else:
x = 1
# print(x)
n = n + x
p = [72] * n
# ---------------------------------------------------------------
ply.score(file,200,40,p,dur)
Image(filename = file+".png", width = 850)
[1, 3, 4, 7, 11, 15] [4, [4, [1, 1, 1]], 16, 16, 16, 16, [4, [1, 1, 1, 1, 1, 1, 1]], [4, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], [4, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]]
Si cambia la directory di lavoro a: «esempi» Elaborazione di «esempi/Accenti_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
Minimo e massimo
Interi - con duplicati
import numpy as np
num = 5 # Numero di elementi
low = 60
hig = 72
end = True # Compreso max o no
rng = np.random.default_rng(seed)
a = rng.integers(low, hig, num, endpoint=end) # .integers()
print(a)
Possiamo utilizzarlo principalmente per le altezze.
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 m2ly as mly # Importa i moduli custom
from IPython.display import Image
import numpy as np
# ------------------------------- Parametri musicali
num = 32 # Numero di elementi
low = 60 # Altezza minima
hig = 72 # Altezza massima
end = True # Compreso max o no
dur = '32 ' # Durata
# ------------------------------- Algoritmo
rng = np.random.default_rng()
altezze = rng.integers(low, hig, num, endpoint=end) # Altezze
# ------------------------------- Layout
page = mly.pag(200,30)
# ------------------------------- Conversione (simboli musicali)
altezze = mly.note(altezze)
# ------------------------------- Formattazione
seq = ''
for i in range(0,len(b)):
seq += altezze[i] + dur # Formatta le note
seq = '{ ' + seq + '}'
staff = mly.staff(seq)
out = page + staff
# ------------------------------- Output
mly.lys("esempi/05_Rand_1.ly", out)
# -------------------------------- Notebook
!lilypond -dresolution=300 -dpixmap-format=pngalpha --output=esempi --png esempi/05_Rand_1.ly
Image(filename = 'esempi/05_Rand_1.png', width = 850)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) /var/folders/sk/v1k0nf8n0vl19k7h2gyp9ykr0000gn/T/ipykernel_6653/145783491.py in <module> 43 # ------------------------------- Output 44 ---> 45 mly.lys("esempi/05_Rand_1.ly", out) 46 47 # -------------------------------- Notebook AttributeError: module 'm2ly' has no attribute 'lys'
Interi - senza duplicati
import numpy as np
num = 5 # Numero di elementi (deve essere inferiore al range...)
low = 60
hig = 72 # Sempre non compreso
dup = False # Con o senza duplicati
rng = np.random.default_rng()
a = np.arange(low,hig,1) # Genera Array da min a max con passo di 1
a = rng.choice(a, num, dup) # Sceglie a caso elementi dell'Array
print(a)
Possiamo generare sequenze di altezze senza ripetizioni oppure serie dodecafoniche random.
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 mtoly as mly # Importa i moduli custom
from IPython.display import Image
import numpy as np
# ------------------------------- Parametri musicali
num = 8 # Numero di elementi (deve essere inferiore al range)
low = 60
hig = 92 # Sempre non compreso
dup = False # Con o senza duplicati
dur = '8 ' # Durata
# ------------------------------- Algoritmo
rng = np.random.default_rng()
altezze = rng.choice(np.arange(low,hig),num,dup) # Cambia solo qua.......
# ------------------------------- Layout
page = mly.page(200,30)
# ------------------------------- Conversione (simboli musicali)
altezze = mly.note(altezze)
# ------------------------------- Formattazione
seq = ''
for i in range(0,len(altezze)):
seq += altezze[i] + dur
seq = '{ ' + seq + '}'
staff = mly.staff(seq)
out = page + staff
# ------------------------------- Output
mly.lys("esempi/06_Rand_2.ly", out)
# -------------------------------- Notebook
!lilypond -dresolution=300 -dpixmap-format=pngalpha --output=esempi --png esempi/06_Rand_2.ly
Image(filename = 'esempi/06_Rand_2.png', width = 850)
Osserviamone le principali caratteristiche e differenze con le liste.
import numpy as np # Importa la libreria numpy
a = [0, 2, 4, 5] # Lista
b = np.array([0,2,4,5]) # np.array
print(type(a))
print(a)
print('--------------------')
print(type(b))
print(b)
<class 'list'> [0, 2, 4, 5] -------------------- <class 'numpy.ndarray'> [0 2 4 5]
Entrambe sono collezioni indicizzate di elementi ma:
import numpy as np
# ------------------ Lista
a = []
a.append(0) # inserisco alla fine
a.append(4)
a.append(6)
print(a)
a.append(8)
print(a)
print('--------------------')
# ------------------ np.array
b = np.empty(4) # non è vuoto ma riempito con valori randomici
print(b)
b[0] = 0 # sostituisce l'elemento
b[1] = 4
b[2] = 6
b[3] = 8
print(b)
[0, 4, 6] [0, 4, 6, 8] -------------------- [0. 0. 0. 0.] [0. 4. 6. 8.]
Casting
Una procedura che può tornare utile è quella della conversione da np.array a liste e viceversa.
import numpy as np
# ------------------ Da lista a np.array
a = [0,4,6,8]
print(type(a))
b = np.array(a) # specificare la lista come argomento
print(type(b))
# ------------------ Da np.array a lista
b = b.tolist() # tolist() oppure list(b)
print(type(b))
<class 'list'> <class 'numpy.ndarray'> <class 'list'>
Tipo di data
Nei np.array possiamo specificare anche il tipo di data degli elementi e realizzare conversioni (casting) tra int e float.
import numpy as np
a = np.array([5,6,8,9], dtype=int) # Coincide
print(a)
print(type(a))
print(type(a[0]))
a = np.array([5,6,8,9], dtype=float) # Casting int -> float
print(a)
print(type(a[0]))
a = np.array([5.6,6.45,8.7,0.9], dtype=int) # Casting float -> int
print(a)
print(type(a[0]))
a = a.astype(float) # Casting
print(a)
print(type(a[0]))
a = a.astype(int) # Casting
print(a)
print(type(a[0]))
a = a.astype(object) # Casting object (python nativo, NON numpy)
print(a)
print(type(a)) # è sempre un np.array ma
print(type(a[0])) # gli elementi sono int di python e non di numpy
[5 6 8 9] <class 'numpy.ndarray'> <class 'numpy.int64'> [5. 6. 8. 9.] <class 'numpy.float64'> [5 6 8 0] <class 'numpy.int64'> [5. 6. 8. 0.] <class 'numpy.float64'> [5 6 8 0] <class 'numpy.int64'> [5 6 8 0] <class 'numpy.ndarray'> <class 'int'>
Dimensioni
Entrambe sono collezioni N-dimensionali.
import numpy as np
a = [1,2,3,4] # Lista 1-D
b = [[1,2,3], [4,5,6], [7,8,9]] # Lista 2-D
c = [[[1,2,3,4], [5,6,7,8]], [[9,10,11,12],[13,14,15,16]]] # Lista 3-D
print(a)
print(b)
print(c)
print('--------------------')
a = np.array(a)
b = np.array(b)
c = np.array(c)
print(a)
print(b)
print(c)
[1, 2, 3, 4] [[1, 2, 3], [4, 5, 6], [7, 8, 9]] [[[1, 2, 3, 4], [5, 6, 7, 8]], [[9, 10, 11, 12], [13, 14, 15, 16]]] -------------------- [1 2 3 4] [[1 2 3] [4 5 6] [7 8 9]] [[[ 1 2 3 4] [ 5 6 7 8]] [[ 9 10 11 12] [13 14 15 16]]]
# primo (colonne)
a = [[1., 0., 0.], # 0
[1., 2., 0.]] # 1
# secondo 0 1 2 (righe)
Possiamo chiamare:
Nell'utilizzo in questo notebook impiegheremo:
Liste o array 1-D (monodimensionali) per rappresentare sequenze o insiemi dei diversi parametri.
Liste o array 2-D (bi-dimensionali) per rappresentare:
Liste o array 3-D (tri-dimensionali) per rappresentare database di sequenze accordali o di pattern ritmici da richiamare in qualche modo.
Se vogliamo impiegare collezioni con subliste di dimensioni diverse (ad esempio per sequenze accordali con un numero diverso di note per accordo) dovremo impiegare preferibilmnete Liste in quanto gli np.array non sono ottimizzati per questo tipo di operazioni e potremmo incorrere in errori.
Se proprio vogliamo utilizzare np.arry dovremo specificare come tipo di data: dtype=object
Numero di elementi
Come ottenere il numero di elementi delle collezioni mono e N-dimensionali.
import numpy as np
# ------------------ Liste
a = [0,4,6,8] # 1-D
print('1-D:', len(a))
a = [[0,3], # 2-D
[0,4,7],
[0,2,4,6]]
print('2-D:',len(a)) # Numero di elementi (righe)
print('Sub:', len(a[0]),len(a[1]),len(a[2])) # Numero di elementi delle subliste (colonne)
print('--------------------')
# ------------------ np.array
b = np.array([0,4,6,8])
print('1-D:',len(b)) # oppure b.size
b = np.array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
print('2-D:',len(b)) # Numero di elementi (righe)
print('NDim:',b.ndim) # .ndim riporta il numero di dimensioni
print('Dsize:',b.shape) # .shape riporta il size di ogni dimensione (righe, colonne)
print('Nitems:',b.size) # .size riporta il numero totale di elementi
1-D: 4 2-D: 3 Sub: 2 3 4 -------------------- 1-D: 4 2-D: 3 NDim: 2 Dsize: (3, 5) Nitems: 15
Operazioni aritmetiche
Se vogliamo effettuare dei calcoli aritmetici con le liste dobbiamo iterare tutti gli elementi con un loop.
a = [0,4,7,12]
b = 60
print(a)
for i in range(len(a)): # counter
a[i] = a[i] + b # sostituisce
print(a)
a = [0, 4, 7,12]
b = [60,70,80,90]
c = []
for i in range(len(a)): # counter
c.append(a[i] + b[i]) # aggiunge
print(c)
[0, 4, 7, 12] [60, 64, 67, 72] [60, 74, 87, 102]
Con i np.array no.
import numpy as np
a = np.array([0,4,7,12])
b = 60
print(a)
a = a + b
print(a)
a = np.array([0, 4, 7,12])
b = np.array([60,70,80,90])
c = a + b
print(c)
[ 0 4 7 12] [60 64 67 72] [ 60 74 87 102]
Di entrambe le tipologie di collezioni ci sono numerose altre caratteristiche che illustreremo di volta in volta nei paragrafi successivi.