Home

Composizione algoritmica¶

Indice¶

  • Introduzione
  • Prerequisiti
  • Installazione
  • Risorse in rete
  • Interfaccia utente
  • Utilità
  • Stringhe
  • Automazione dei processi
  • Esercitazione n°1
  • Mapping parametrici e conversioni simboliche
    • Formati simbolici in ingresso
    • Altezze
    • Durate
    • Intensità
    • Segni di espressione
  • Formattazione
  • Moduli
  • Strategie musicali
  • Prospettiva libera
    • Scale e modi
    • Sequenze
    • Riserve armoniche
    • Sequenze accordali
    • Pattern ritmici
    • Pattern dinamici
    • Elaborazioni motiviche
  • Suddivisioni
    • Altezze
    • Beat
  • Random
  • Serie
    • con ripetizioni
    • senza ripetizioni
  • Sequenze o pattern
  • RM
    • folding
  • Armonie arbitrarie
  • Random

    • Seed
    • Ambito
    • Collezione
  • Stocastico

  • NumPy Array

Introduzione Indice

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:

  • Formalizzazione e automazione del processo di generazione di codice di python opportunamente formattato per poter essere compilato in lilypond.
  • Formalizzazione e automazione del processo di creazione e salvataggio dei file intermedi necessari.
  • Formalizzazione e automazione del processo di mapping e conversione dei simboli musicali.

La seconda consiste nel formalizzare la generazione o elaborazione algoritmica dei diversi parametri musicali.

Prerequisiti Indice

  • Competenze base/intermedie nella programmazione in Lilypond.
  • Competenze base nella programmazione in python.
  • Competenze approfondite sui concetti di sintassi e linguaggi musicali.

Installazione Indice

Scarichiamo ed installiamo:

  • Lilypond (in Mac con Homebrew da Terminale in Windows VERIFICA LA PROCEDURA CON LA NUOVA VERSIONE) CAMBIA LINKKKKKK
  • Python (Anaconda distribution)
  • VScode (Incluso con l'Anaconda distribution)

Risorse in rete Indice

  • SCAMP.
  • Abjad.
  • Music21.

Interfaccia utenteIndice

La finalità che ci siamo prefissati è la generazione automatica di codice di lilypond attraverso algoritmi realizzati in python.

Per farlo abbiamo principalmente due possibilità.

Notebook e un editor lilypond¶

Dalle celle di questo Notebook.

  1. Scriviamo codice di python per la generazione o manipolazione di dati simbolici (numeri, lettere, parole, etc.) mappati su parametri musicali (altezze, durate, intensità, etc.) formattati in output sotto forma di stringhe.
  1. Selezioniamo la cella da eseguire e poi ctrl + enter.</br> I dati a schermo (output) sono visualizzati sotto la cella.
  1. Copiamo, incolliamo e compiliamo i dati ottenuti in un editor dedicato di lilypond come Frescobaldi o altri.
In [2]:
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'' } 


VSCode o Notebook¶

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).

In [3]:
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
Out[3]:
0

Ricordo che in questo caso i passaggi da seguire sono:

  1. Scriviamo il file di python e lo salviamo in una nuova cartella.
  2. Clicchiamo su esplora risorse (l'icona con le due pagine in alto a sinistra).
  3. Selezioniamo la cartella (apri cartella) che contiene il file.
  4. Eseguiamo il codice python (click sulla freccia in alto a destra).
  5. Compaiono all'interno della cartella altri due files:

    • prova_1.ly - il file lilypond che possiamo aprire con qualsiasi editor lilypond.
    • prova_1.pdf - il file pdf risultato dell'esecuzione del file lilypond.

    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.

In [5]:
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
Out[5]:

UtilitàIndice

Terminale

  • Pulire il Terminale: cmd + k.
  • Uscire dal Terminale: scrivere q + enter.

Python

  • I commenti sono preceduti da #.
  • Help di un oggetto in python: scrivere help(nome_oggetto)
  • Best pratics.

Notebook

  • Se vogliamo eseguire comandi da Terminale (Shell) all'interno di una cella scriviamo ! all'inizio della linea.

VScode

  • Commentare e decommentare: cmd + shift + /
  • Eseguire python: tasto destro: Run Python File in Terminal oppure schiacciare freccina in alto a dx.
  • Eseguire lilypond: salvare il file.
  • Uscire da una cartella di lavoro: tasto destro: Rimuovi cartella dall'area di lavoro.

Stringhe Indice

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.

Concatenare¶

In [6]:
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 }

Escape character¶

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:

  • \' - apice. Definisce un apice come semplice carattere.
  • \" - doppie virgolette. Definisce le doppie virgolette come semplice carattere.
  • \\ - doppio backslash. Definisce il backslash come semplice carattere.
  • \n - newline. Va a capo in una nuova linea.
  • \r - carriage return. Va a capo.
  • \t - tab. Aggiunge una tabulazione (4 spazi).
  • \b - backspace.

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.

In [ ]:
# Sintassi lilypond (NON eseguire)

\version "2.20.0"
\language "english"

\header { 
         title = "Bella musica"                
         composer = "L'ho scritta io" 
         }

{ c'' a' b' c'' }
In [7]:
# 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'' }

Formattare¶

Se vogliamo inserire all'interno di una stringa altri tipi di dato come int in precise posizioni possiamo utilizzare quattro diverse modalità:

  1. Parametri ordinati

    • Definiamo le variabili con i valori che vogliamo inserire nella stringa (possono essere qualsiasi tipo di dato).
    • Inseriamo nella stringa alle posizioni desiderate la formula { }.
    • Invochiamo sulla variabile il metodo .format( ) specificando l'ordine in cui vogliamo inserire i dati collegati alle variabili.
In [ ]:
# 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 }
                    >>
                   }
        }
In [6]:
# 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 } }
                    >>
                    }
        }
  1. Parametri indicizzati

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.

In [ ]:
# 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
                   }
        }
In [8]:
# 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' 
                             }
		}
	}
  1. Indici nominali

Come nei casi precedenti ma al posto degli indici specifichiamo i nomi direttamente nelle parentesi graffe

In [34]:
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}
		}
	}
  1. Operatore di formato

    Se utilizziamo il simbolo % all'interno di una stringa viene interpretato come operatore di formato.

    • %d = decimale
    • %q = floating-point
    • %s = stringa

    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()

In [35]:
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.

In [9]:
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...

In [11]:
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.

In [12]:
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...

In [13]:
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.

In [14]:
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} 
                              }
                }
        }

Automazione dei processi Indice

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:

  • accetta come argomento (input) una stringa formattata (come quelle del paragrafo precedente).
  • genera e salva (o sovrascrive) un file di testo con estensione .ly
  • compila da linea di comando il file salvato generando un nuovo file con il risultato finale (png o pdf).
In [15]:
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)   
In [16]:
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.

In [17]:
from IPython.display import Image  

file  = "esempi/prova_2"

Image(filename = file + ".png", width = 350) 
Out[17]:

Esercitazione n°1 Indice

* **Formattare** in python (stringhe) **almeno uno** a scelta tra i seguenti codici di lilypond. * **Modificare** le funzioni illustrate **aggiungendo** funzionalità (come la possibilità di stabilire **chiavi** o **tempo** sotto forma di ulteriori **argomenti**). * **Programmare** una **nuova funzione** che soddisfi una vostra **necessità** (come la scelta di formati di pagina tra quelli standard). * **Compilarli** in un unico file di python che automatizza tutte le operazioni.
1.
In [ ]:
\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
                        }
}
2.
In [ ]:
\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    
                        }
}
3.
In [ ]:
\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
                                    }
                >>

Mapping parametrici e conversioni simboliche Indice

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.

Formati simbolici in ingresso Indice

I parametri di qualsiasi evento sonoro indipendenti dal linguaggio musicale impiegato sono:

  • Altezza (frequenza)
  • Durata (tempo)
  • Dinamica (ampiezza)
  • Timbro (inviluppo spettrale)
  • Spazio (diffusione)

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).

  • altezze --> note midi
    • tuple o liste in quanto i valori sono sequenziali.
  • durate --> suddivisioni dell'unità
    • dizionari in quanto i valori sono discreti.
  • dinamiche --> velocity midi
    • dizionari, tuple o liste a seconda della situazione musicale e dell'algoritmo impiegato.
  • segni di espressione --> stringhe
    • dizionari in quanto i valori sono discreti.
In [16]:
# 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.

Altezze Indice

input: lista di valori numerici (Accordi: List 2D).

  • note: valori MIDI
  • pause: -1
  • spazio vuoto: -2

output: lista di simboli sotto forma di stringa.

In [13]:
# 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.

In [8]:
#         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:

  • separare gli accordi e le note singole
  • formattarli correttamente.
  • aggiungerli alla lista vuota (output).
In [28]:
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.

In [14]:
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                    
In [19]:
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

Durate Indice

Stabiliamo una sintassi per specificare le durate sotto forma di suddivisioni dell'unità:

input: lista di valori numerici.

  • per i tempi regolari:
    1  = intero
    2  = metà
    4  = quarto
    8  = ottavo
    16 = sedicesimo
    32 = trentaduesimo
    
  • per ritmi puntati e i tempi irregolari:
    [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).

In [20]:
# 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...)

In [23]:
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).

In [24]:
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).

In [25]:
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.

In [27]:
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:

  • separare i valori regolari da quelli irregolari
  • formattarli correttamente.
  • aggiungerli alla lista vuota (output).
In [31]:
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.

In [32]:
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                 
In [33]:
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

Intensità Indice

input: lista di valori numerici (MIDI velocity da 0 a 127).

  • Se non vogliamo specificare alcuna intensità su una nota dobbiamo ugualmente scrivere una velocity uguale a 0.

output: lista di simboli sotto forma di stringa.

In [34]:
# 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.

In [35]:
din = ('\\ppppp','\\pppp','\\ppp','\\pp','\\p','\\mp','\\mf','\\f','\\ff','\\fff','\\ffff','\\fffff')

Definiamo una lista vuota e un ciclo all'interno del quale:

  • riconoscere velocity uguale a zero.
  • stabilire dei limiti entro i quali quantizzare le velocity maggiori di zero.
  • formattare correttamente i simboli.
  • aggiungerli alla lista vuota (output).
In [37]:
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.

In [38]:
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                 
In [41]:
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

Segni di espressione Indice

input: lista di simboli semplificati in ingresso (stringhe).

  • se non vogliamo specificare alcun simbolo dobbiamo scrivere uno spazio vuoto: ' '.

output: lista di simboli sotto forma di stringa.

In [42]:
# input

exps = ('>', ' ', '!', ' ', '.')

Definiamo un dictionary di simboli musicali corrispondenti ai principali segni di espressione.

In [43]:
expr = {'>':'->', '^':'-^', '!':'-!', '.':'-.', '_':'-_', '-':'--', 'expr':'\\espressivo',
        'tr':'\\trill', 'm':'\\prall', 'cor':'\\fermata', ' ':''}

Definiamo una lista vuota e un ciclo all'interno del quale:

  • mappare i simboli semplificati con quelli prori della sintassi di lilypond.
  • aggiungerli alla lista vuota (output).
In [44]:
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.

In [45]:
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                    
In [47]:
acc = ['>', ' ', '!', ' ', '.'] # input
#acc = ['nil']
a    = expr(acc)                # funzione
print(a)                         # output
print(len(a), 'elementi musicali')
['->', '', '-!', '', '-.']
5 elementi musicali

Formattazione Indice

Abbiamo ottenuto una lista di simboli per ogni parametro.

Tutte le liste devono avere lo stesso numero di elementi.

In [49]:
#      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à.

In [50]:
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:

  • altezza e durata

Le dinamiche e le intensità le possiamo specificare o meno, in questo ultimo caso vengono generate automaticamente liste vuote che saranno formattate.

In [51]:
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
In [52]:
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 } }

Moduli Indice

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:

  • Nome file - senza estensione e con un eventuale percorso per sottocartelle.
  • x in pixel - formato del file png generato.
  • y in pixel - formato del file png generato.
  • pitch - una lista di altezze nel formato già descritto.
  • dur - una lista di durate nel formato già descritto.
  • vel - una lista di velocity nel formato già descritto.
  • expr - una lista di segni d'espressione nel formato già descritto.

Se non vogliamo specificare tutti gli argomenti nell'ordine dobbiamo utilizzare la keyword notation.

In [2]:
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
Out[2]:

Esercitazione n°2 Indice

Scrivere un file di python come quelli illustrati in precedenza che generi la seguente partitura in formato pdf (senza le legature).
scIDE

Strategie musicali Indice

Se vogliamo formalizzare un pensiero musicale possiamo farlo attraverso tre tipi di operazioni:

  1. Generare collezioni di dati simbolici (numerici o letterali) che rappresentano altezze, ritmi, intensità o altro.
  2. Modificare collezioni di dati simbolici (numerici o letterali) tramite operazioni che seguono regole prefissate.
  3. Estrarre elementi da collezioni di dati simbolici (numerici o letterali) in tempo reale (durante una performance) o in tempo differito (realizzazione di una partitura musicale).

Ognuna di queste operazioni può essere effettuata attraverso tre diversi approcci:

  1. Randomico - la sequenza di operazioni include operazioni pseudocasuali con distribuzione lineare delle probabilità e diversi modelli compositivi.
  2. Stocastico - la sequenza di operazioni include operazioni pseudocasuali con distribuzioni non lineari e/o maschere di tendenza.
  3. Deterministico - la sequenza di operazioni non include operazioni pseudocasuali ma segue regole ben precise.

Spesso questi approcci sono impiegati contemporaneamente all'interno dello stesso algoritmo e le operazioni concatenate tra loro.

Prospettiva libera Indice

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.

Scale e modi Indice

Possiamo definire sequenze intervallari predefinite.

In [2]:
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:

  • iterare gli elementi della lista.
  • sommarli uno ad uno al valore della nota perno.
  • aggiungerli a una nuova lista vuota che conterrà i nuovi valori.

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.

In [5]:
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
Out[5]:

Sequenze Indice

Possiamo anche definire direttamente sequenze di altezze assolute (con o senza pause o spazi vuoti).

In [1]:
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
Out[1]:

Riserve armoniche Indice

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.

In [8]:
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
Out[8]:

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.

In [9]:
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
Out[9]:

Notiamo la differenza: La stessa operazione di trasposizione l'abbiamo effettuata in due modi differenti per sottolineare una caratteristica dei numpy array:

  • prima effettuando un casting della lista in np.array e chedendo di eseguire l'operazione direttamente su tutte le subliste.
  • poi attraverso una funzione che itera tutti gli elementi per effetture l'operazione.

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.

Sequenze accordali Indice

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.

In [3]:
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
Out[3]:

Pattern ritmici Indice

Possiamo specificare cellule ritmiche di base che saranno poi eleborate in diverso modo (esattamente come per le altezze).

In [11]:
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
Out[11]:

Ricordiamo che le pause sono definite da -1 nella lista delle altezze.

In [4]:
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
Out[4]:

Mentre gli spazi da -2.

In [5]:
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
Out[5]:

Pattern dinamici Indice

Possiamo anche definire pattern dinamici come ad esempio un alternarsi di accenti o note staccate e legate.

In [13]:
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
Out[13]:

Elaborazioni motiviche Indice

Le principali tecniche di elaborazione motivica del materiale nella tradizione musicale occidentale sono:

  • Estrazione e ricomposizione di incisi motivici di diversa lunghezza.
  • Trasposizione delle altezze.
  • Retrogrado degli incisi motivici.
  • Inversione degli intervalli.
  • Dilatazione delle durate.
In [14]:
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
Out[14]:

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.

In [6]:
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.

In [275]:
2**19937-1
Out[275]:
340282366920938463463374607431768211456

Se non specificato viene generato a ogni esecuzione del codice dal sistema operativo (current system time) e la sequenza è diversa ad ogni valutazione.

In [35]:
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.

In [76]:
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:

In [77]:
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:

In [698]:
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:

In [78]:
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.

In [8]:
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
Out[8]:

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.

In [9]:
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
Out[9]:

Trasposizione delle altezze.

Possiamo aggiungere al codice precedente una sequenza di trasposizioni deterministiche

In [88]:
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
Out[88]:

Oppure non determinisctiche (random.choice())

In [104]:
import random as rn

a = [0,1,2,3,4]
b = rn.choice(a)
print(b)
0
In [90]:
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
Out[90]:

Retrogrado delle altezze e/o durate e/o ordine dei frammenti.

AGGIUNGI IMMAGINE SEQUENZA ORIGINALE

In [91]:
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.

In [92]:
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
Out[92]:

Retrogrado dei frammenti di una sequenza.

In [94]:
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
Out[94]:

Suddivisioni Indice

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:

  • vai da a fino a b con passo di n (interpolazione lineare)
    • non conosciamo il numero di elementi generati che dipende dal passo.
    • il valore di fine non è incluso.
    • arrotondiamo all'intero.
    • casting all'int in quanto di default questo oggetto restituisce float.
    • casting da np.array a lista.
In [43]:
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]
  • vai da a fino a b in n passi con delta regolare (interpolazione lineare)
    • conosciamo il numero di elementi generati
    • specifichiamo se includere il valore di fine (True) o meno (False)
    • arrotondiamo all'intero.
    • casting all'int in quanto di default questo oggetto restituisce float.
    • casting da np.array a lista.
In [66]:
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)

    • conosciamo il numero di elementi generati.
    • specifichiamo se includere il valore di fine (True) o meno (False).
    • utilizziamo una funzione di trasferimento per riscalare tra min e max valori compresi tra 0.0 e 1.0.
    • arrotondiamo all'intero.
    • casting all'int in quanto di default questo oggetto restituisce float.
    • casting da np.array a lista.
In [205]:
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]

Altezze Indice

Vediamo alcuni esempi su come applicare quanto appena esposto nella generazione di altezze.

Scale modi o sequenze Indice

In [1]:
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
Out[1]:

Riserve armoniche o sequenze accordali Indice

Trasformiamo semplicemente la lista da monodimensionale a bidimensionale.

In [95]:
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
Out[95]:

Se vogliamo sequenze di accordi derivate da curve differenti delle suddivisioni di un ambito possiamo utilizzare un ciclo.

In [96]:
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
Out[96]:

Possiamo anche computare armonie spettrali:

  • Generiamo un np.array che comprende il numero dei suoni armonici che vogliamo includere.
  • Moltiplichamo una frequenza fondamentale per l'array precedente.
  • Convertiamo e appriossimiamo i valori da Hz in note midi.
In [103]:
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
Out[103]:

Beat Indice

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.

In [2]:
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
Out[2]:

Random Indice

Minimo e massimo

Interi - con duplicati

In [ ]:
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.

In [23]:
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

  • Prima generiamo un array con tutti i valori compresi tra min e max (escluso).
  • Dopo scegliamo n valori dall'array.
In [ ]:
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.

In [ ]:
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) 

Stocastico Indice

In [ ]:
 

NumPy Array Indice

Osserviamone le principali caratteristiche e differenze con le liste.

In [4]:
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:

  • liste: gli elementi possono essere di diverso tipo.
  • np.array: gli elementi devono essere dello stesso tipo (usualmente numeri int o float).
  • liste: il numero di elementi può essere modificato dopo la loro creazione quindi possiamo dichiarare una lista vuota.
  • np.array: il numero di elementi non può essere modificato dopo la loro creazione quindi non possiamo dichiarare un np.array vuoto.
In [5]:
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.

In [6]:
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.

In [7]:
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.

In [8]:
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]]]
  • le dimensioni sono chiamate anche assi. </br> Ad esempio le coordinate di un punto in uno spazio tridimensionale [x, y, z] hanno un solo asse.</br> Nell'esempio seguente invece l'array ha 2 assi, il primo cun una lunghezza di 2 mentre il secondo di 3.
In [9]:
# primo (colonne)                    
a =      [[1., 0., 0.], # 0 
          [1., 2., 0.]] # 1
# secondo  0   1   2      (righe)  

Possiamo chiamare:

  • vettore un'array a un asse (1-D)
  • matrice un'array a due assi (2-D)
  • tensore un'array a tre o più assi (3-D o n-D)

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:

    • accordi o sequenze accordali
    • valori ritmici irregolari o puntati
  • 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.

In [10]:
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.

In [11]:
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.

In [12]:
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.