7 minute read

Sistema di Transizione di Stato per Delta-Engine

Questo documento descrive il sistema di transizione di stato implementato per Delta-Engine, un framework di composizione algoritmica basato su Csound.

Visione d’Insieme

Il sistema di transizione di stato permette alla composizione di evolversi in modo organico attraverso diversi stati musicali definiti da tre dimensioni principali:

  1. Densità - Numero di eventi sonori attivi simultaneamente
  2. Registro - Distribuzione dei suoni nelle ottave
  3. Movimento - Grado di attività spaziale

Ogni dimensione è suddivisa in tre stati (0, 1, 2), creando uno spazio di 27 possibili stati complessivi.

Componenti Principali

Il sistema è implementato attraverso due file UDO (User Defined Opcodes) principali:

  1. stateMapping.udo - Converte gli stati astratti in parametri concreti
  2. stateTransition.udo - Gestisce le transizioni tra stati diversi

stateMapping.udo

Questo file contiene opcode per mappare gli stati (indici numerici) in parametri concreti utilizzati nella generazione degli eventi sonori.

mapStateToParameter

Converte un indice di stato in un range di valori per un parametro specifico.

opcode mapStateToParameter, ii, iS
    iStateIndex, SparamType xin
    
    iMin = 0
    iMax = 0
    
    if strcmp(SparamType, "density") == 0 then
        ; Legge le soglie direttamente dalla tabella density_thresholds
        iLowerBound tab_i iStateIndex, gi_density_thresholds
        iUpperBound tab_i iStateIndex+1, gi_density_thresholds
        
        ; Aggiunge un piccolo margine per evitare valori di confine
        iMin = iLowerBound + 0.1
        ; Limita il massimo a un valore ragionevole
        iMax = (iUpperBound > 100) ? 12 : iUpperBound - 0.1
        
    elseif strcmp(SparamType, "register") == 0 then
        ; Legge le soglie dalla tabella register_thresholds
        iLowerBound tab_i iStateIndex, gi_register_thresholds
        iUpperBound tab_i iStateIndex+1, gi_register_thresholds
        
        ; Converte i valori normalizzati (0-1) in valori di ottave (1-10)
        iMin = 1 + iLowerBound * ($OTTAVE - 1)
        iMax = 1 + iUpperBound * ($OTTAVE - 1)
        
    elseif strcmp(SparamType, "movement") == 0 then
        ; Legge le soglie dalla tabella movement_thresholds
        iLowerBound tab_i iStateIndex, gi_movement_thresholds
        iUpperBound tab_i iStateIndex+1, gi_movement_thresholds
        
        ; Inversione dei valori per il movimento (valori ritmici bassi = più movimento)
        iMoveLow = 1 - iUpperBound
        iMoveHigh = 1 - iLowerBound
        
        ; Scala i valori al range di ritmi appropriato (1-20)
        iMin = 1 + iMoveLow * 19
        iMax = 1 + iMoveHigh * 19
    endif
    
    ; Arrotonda i valori per maggiore chiarezza
    iMin = round(iMin)
    iMax = round(iMax)
    
    xout iMin, iMax
endop

Parametri di Input:

  • iStateIndex - Indice dello stato (0, 1, o 2)
  • SparamType - Tipo di parametro (“density”, “register”, o “movement”)

Output:

  • iMin - Valore minimo del range
  • iMax - Valore massimo del range

Funzionamento:

  1. Legge i valori di soglia dalle tabelle appropriate
  2. Calcola i valori minimi e massimi in base al tipo di parametro
  3. Applica logiche specifiche per ciascun tipo di parametro (es. inversione per il movimento)
  4. Arrotonda i valori e li restituisce come range

generateRhythmsForState

Genera una tabella di valori ritmici appropriati per un dato stato di movimento.

opcode generateRhythmsForState, i, i
    iTargetMovement xin
    
    iTblSize = 5  ; Dimensione della tabella ritmica
    iTableNum ftgen 0, 0, iTblSize, -2, 0
    
    ; Ottiene i range di valori appropriati dalle soglie di movimento
    iMinVal, iMaxVal mapStateToParameter iTargetMovement, "movement"
    
    ; Genera valori ritmici appropriati
    iIdx = 0
    while iIdx < iTblSize do
        iRhythmVal random iMinVal, iMaxVal
        iRhythmVal = round(iRhythmVal)  ; Arrotonda al valore intero più vicino
        
        ; Assicura che non ci siano valori ritmici nulli o negativi
        if iRhythmVal < 1 then
            iRhythmVal = 1
        endif
        
        tabw_i iRhythmVal, iIdx, iTableNum
        iIdx += 1
    od
    
    xout iTableNum
endop

Parametri di Input:

  • iTargetMovement - Indice dello stato di movimento target (0, 1, o 2)

Output:

  • iTableNum - Numero identificativo della tabella generata

Funzionamento:

  1. Crea una nuova tabella di dimensione predefinita
  2. Ottiene il range di valori ritmici appropriato utilizzando mapStateToParameter
  3. Genera valori ritmici casuali all’interno di questo range
  4. Restituisce l’identificatore della tabella generata

stateTransition.udo

Questo file contiene opcode per gestire le transizioni tra stati diversi.

initTransitionMatrix

Inizializza la matrice di transizione con probabilità predefinite.

opcode initTransitionMatrix, 0, 0
    ; Per ogni stato possibile (27 stati totali = 3×3×3)
    iStateIdx = 0
    while iStateIdx < 27 do
        ; Scomponiamo l'indice nei suoi componenti
        iCurrentDensity = int(iStateIdx / 9)
        iRemainder = iStateIdx % 9
        iCurrentRegister = int(iRemainder / 3)
        iCurrentMovement = iRemainder % 3
        
        ; Per ogni possibile stato successivo
        iNextStateIdx = 0
        while iNextStateIdx < 27 do
            ; Scomponiamo l'indice del prossimo stato
            iNextDensity = int(iNextStateIdx / 9)
            iNextRemainder = iNextStateIdx % 9
            iNextRegister = int(iNextRemainder / 3)
            iNextMovement = iNextRemainder % 3
            
            ; Calcoliamo la "distanza" tra stati (quanti parametri cambiano)
            iChanges = 0
            if iCurrentDensity != iNextDensity then
                iChanges += 1
            endif
            if iCurrentRegister != iNextRegister then
                iChanges += 1
            endif
            if iCurrentMovement != iNextMovement then
                iChanges += 1
            endif
            
            ; Assegniamo probabilità basate sulla distanza
            iProb = 0
            if iChanges == 0 then
                iProb = 0.4  ; 40% probabilità di rimanere nello stesso stato
            elseif iChanges == 1 then
                iProb = 0.3  ; 30% probabilità di cambiare un solo parametro
            elseif iChanges == 2 then
                iProb = 0.2  ; 20% probabilità di cambiare due parametri
            else
                iProb = 0.1  ; 10% probabilità di cambiare tutti i parametri
            endif
            
            ; Salva la probabilità nella matrice
            tabw_i iProb, iStateIdx*27+iNextStateIdx, gi_transition_matrix
            
            iNextStateIdx += 1
        od
        
        ; Normalizza le probabilità per assicurarsi che sommino a 1
        iSum = 0
        iNextIdx = 0
        while iNextIdx < 27 do
            iProb tab_i iStateIdx*27+iNextIdx, gi_transition_matrix
            iSum += iProb
            iNextIdx += 1
        od
        
        if iSum > 0 then
            iNextIdx = 0
            while iNextIdx < 27 do
                iProb tab_i iStateIdx*27+iNextIdx, gi_transition_matrix
                iNormProb = iProb / iSum
                tabw_i iNormProb, iStateIdx*27+iNextIdx, gi_transition_matrix
                iNextIdx += 1
            od
        endif
        
        iStateIdx += 1
    od
endop

Funzionamento:

  1. Itera attraverso tutti i 27 stati possibili
  2. Per ogni stato, assegna probabilità di transizione verso tutti gli altri stati
  3. Le probabilità sono assegnate in base alla “distanza” tra stati:
    • Rimanere nello stesso stato: 40%
    • Cambiare un parametro: 30%
    • Cambiare due parametri: 20%
    • Cambiare tutti i parametri: 10%
  4. Normalizza le probabilità per assicurarsi che sommino a 1 per ogni stato

selectNextState

Seleziona il prossimo stato in base alle probabilità definite nella matrice di transizione.

opcode selectNextState, iii, iii
    iCurrentDensity, iCurrentRegister, iCurrentMovement xin
    
    ; Calcola l'indice di base nella matrice di transizione
    iStateIdx = (iCurrentDensity * 9) + (iCurrentRegister * 3) + iCurrentMovement
    
    ; Genera un numero casuale
    iRand random 0, 1
    
    ; Seleziona il prossimo stato in base alla probabilità cumulativa
    iCumulativeProb = 0
    iNextStateIdx = 0
    
    iIdx = 0
    while iIdx < 27 do
        iTransProb tab_i iStateIdx*27+iIdx, gi_transition_matrix
        iCumulativeProb += iTransProb
        
        if iRand < iCumulativeProb then
            iNextStateIdx = iIdx
            igoto found_next
        endif
        
        iIdx += 1
    od
    found_next:
    
    ; Converti l'indice nei parametri di stato
    iNextDensity = int(iNextStateIdx / 9)
    iRemainder = iNextStateIdx % 9
    iNextRegister = int(iRemainder / 3)
    iNextMovement = iRemainder % 3
    
    xout iNextDensity, iNextRegister, iNextMovement
endop

Parametri di Input:

  • iCurrentDensity - Stato corrente della densità (0-2)
  • iCurrentRegister - Stato corrente del registro (0-2)
  • iCurrentMovement - Stato corrente del movimento (0-2)

Output:

  • iNextDensity - Prossimo stato della densità
  • iNextRegister - Prossimo stato del registro
  • iNextMovement - Prossimo stato del movimento

Funzionamento:

  1. Calcola l’indice dello stato corrente nella matrice di transizione
  2. Genera un numero casuale
  3. Utilizza un metodo di “probabilità cumulativa” per selezionare il prossimo stato
  4. Converte l’indice del prossimo stato nei suoi componenti
  5. Restituisce i componenti del prossimo stato

Tabelle di Supporto

Il sistema si basa su diverse tabelle di soglia definite in first.orc:

Tabelle di Soglia

; Definisce le soglie per i diversi stati
gi_density_thresholds ftgen 0, 0, 4, -2, 0, 3, 7, 999    ; Sparse, Medium, Dense
gi_register_thresholds ftgen 0, 0, 4, -2, 0, 0.3, 0.7, 1 ; Low, Mid, High
gi_movement_thresholds ftgen 0, 0, 4, -2, 0, 0.2, 0.5, 1 ; Static, Moderate, Dynamic

Queste tabelle definiscono i confini tra i diversi stati per ciascun parametro.

Matrice di Transizione

; Matrice di transizione per le probabilità di passaggio tra stati
gi_transition_matrix ftgen 0, 0, 27*27, -2, 0

Tabelle di Storia degli Stati

; Storia recente degli stati
gi_state_history_density ftgen 0, 0, gi_state_history_size, -2, 0
gi_state_history_register ftgen 0, 0, gi_state_history_size, -2, 0
gi_state_history_movement ftgen 0, 0, gi_state_history_size, -2, 0

Utilizzo nel Sistema

Il sistema di transizione di stato è integrato nel ciclo compositivo come segue:

  1. L’Analizzatore misura parametri musicali in tempo reale
  2. determineCurrentState classifica questi parametri in stati discreti
  3. Le tabelle di storia vengono aggiornate con i nuovi stati
  4. GeneraComportamenti legge lo stato corrente e seleziona uno stato target
  5. I parametri dello stato target vengono convertiti in valori concreti
  6. I comportamenti vengono generati usando questi valori
  7. Gli eventi sonori creati influenzano a loro volta l’analisi

Questo ciclo di feedback crea un’evoluzione compositiva organica e strutturata.

Note di Implementazione

  • Il sistema è progettato per essere flessibile: le soglie possono essere modificate per creare diversi profili compositivi.
  • Le probabilità di transizione possono essere adattate per favorire transizioni più graduali o più contrastanti.
  • Il sistema mantiene una coerenza tra la classificazione degli stati e la generazione dei parametri utilizzando le stesse tabelle di soglia per entrambi i processi.