Transizioni Organiche: Refactoring del Sistema di Stati in Delta-Engine
Negli ultimi mesi ho lavorato intensamente al mio sistema di composizione algoritmica Delta-Engine, affrontando una sfida fondamentale: come rendere le transizioni musicali più organiche e naturali. Oggi voglio condividere un importante refactoring del nucleo del sistema, che ha trasformato profondamente il modo in cui Delta-Engine comprende e manipola gli stati musicali.
Il Problema: Confrontare Mele con Aranci
Nel sistema originale, ho identificato un problema concettuale fondamentale nella funzione determineCurrentState
, il cuore del sistema di transizione:
iDeviation = abs(iCurrentDensityState - iExpectedDensity)
Questa semplice riga di codice nascondeva una seria incongruenza: stavo confrontando grandezze di natura diversa:
iCurrentDensityState
: Un valore discreto (0, 1, 2) prodotto dadetermineCurrentState
iExpectedDensity
: Un valore continuo (es. 0.7, 1.3) derivato da interpolazione cubica
Questa discrepanza portava a valutazioni imprecise delle deviazioni durante le transizioni, con conseguenti adattamenti non proporzionali e “salti” udibili nei parametri sonori. Come potevo aspettarmi che il sistema producesse transizioni fluide quando la sua percezione dello stato era limitata a soli tre valori discreti?
Percezione Umana: Pensare in Rapporti, non in Differenze Assolute
La soluzione è arrivata riflettendo sulla natura della percezione umana, in particolare quella uditiva. Noi percepiamo il mondo in modo logaritmico, non lineare. I rapporti tra valori sono più significativi delle differenze assolute.
Per esempio:
- La differenza tra 1 e 5 eventi sovrapposti (rapporto 5:1) è percepita come più significativa di
- La differenza tra 50 e 55 eventi sovrapposti (rapporto 1.1:1)
Eppure, in una scala lineare, entrambe le differenze sono di 4 unità. Questo è il motivo per cui in acustica usiamo i decibel (scala logaritmica) per misurare l’intensità del suono, e le ottave (anch’esse logaritmiche) per le frequenze.
Stati Continui e Logaritmici: La Soluzione
La nuova implementazione di determineCurrentState
risolve entrambi i problemi:
- Produce valori continui invece che discreti
- Utilizza trasformazioni logaritmiche per riflettere la percezione umana
Ecco il nucleo della nuova implementazione:
opcode determineCurrentState, kkk, 0
; Inizializzazione variabili di output
kDensityState = 0
kRegisterState = 0
kMovementState = 0
; CALCOLO STATO DENSITÀ (CONTINUO, LOGARITMICO)
kMinOverlap = 1 ; Valore minimo significativo
kMaxOverlap = 300 ; Valore massimo di overlap
; Applica trasformazione logaritmica
kSafeOverlap = max(kMinOverlap, gk_current_overlap)
kLogMin = log(kMinOverlap)
kLogMax = log(kMaxOverlap)
kLogRange = kLogMax - kLogMin
kDensityState = (log(kSafeOverlap) - kLogMin) / kLogRange * 2.999
; CALCOLO STATO REGISTRO (CONTINUO, LOGARITMICO CON RIMAPPATURA)
; INVERSIONE DEL VALORE DI SPREAD (alto->basso, basso->alto)
kInvertedSpread = 1 - gk_current_octave_spread
kMinRegMap = 1 ; Minimo valore rimappato
kMaxRegMap = 100 ; Massimo valore rimappato
; Rimappa octave_spread invertito da [0,1] a [kMinRegMap,kMaxRegMap]
kMappedSpread = kMinRegMap + kInvertedSpread * (kMaxRegMap - kMinRegMap)
; Applica trasformazione logaritmica al valore rimappato
kLogMin = log(kMinRegMap)
kLogMax = log(kMaxRegMap)
kLogRange = kLogMax - kLogMin
kRegisterState = (log(kMappedSpread) - kLogMin) / kLogRange * 2.999
; CALCOLO STATO MOVIMENTO (CONTINUO, LOGARITMICO CON RIMAPPATURA)
; ... codice simile per il movimento spaziale ...
xout kDensityState, kRegisterState, kMovementState
endop
La Matematica Dietro la Trasformazione Logaritmica
Per ogni dimensione dello stato musicale, applico una formula di normalizzazione logaritmica:
stato = (log(valore) - log(min)) / (log(max) - log(min)) * 2.999
Questa formula mappa un valore da un range arbitrario [min, max] a un range standard [0, 2.999], utilizzando una scala logaritmica.
Per parametri che sono già nell’intervallo [0, 1] (come octave_spread
), applico prima una rimappatura a un intervallo più adatto alla trasformazione logaritmica, tipicamente [1, 100]:
valoreMappato = minMap + valore * (maxMap - minMap)
La Questione dell’Octave Spread
Un punto interessante emerso durante il refactoring riguarda gk_current_octave_spread
. In Delta-Engine, questo valore rappresenta il rapporto tra ottave attive e totali, ma con una logica inversa:
- Quando ci sono poche ottave attive, lo spread è alto (vicino a 1)
- Quando ci sono molte ottave attive, lo spread è basso (vicino a 0)
Ciò ha richiesto un’inversione esplicita (kInvertedSpread = 1 - gk_current_octave_spread
) per mantenere la logica intuitiva nel sistema di stati: alto spread armonico → alto stato di registro.
Benefici del Nuovo Approccio
Il passaggio a stati continui e logaritmici ha portato miglioramenti significativi:
- Transizioni più organiche e naturali: Niente più “salti” udibili tra stati discreti
- Valutazione precisa delle deviazioni: Confronto tra grandezze omogenee (continuo vs continuo)
- Percezione naturale: La scala logaritmica riflette il modo in cui percepiamo davvero i cambiamenti musicali
- Maggiore sensibilità nelle regioni significative: Più risoluzione nei range percettivamente importanti
- Estendibilità: Il sistema è ora progettato per accogliere facilmente altri parametri di stato in futuro
Una Metafora Concettuale: Classi e Istanze
Mi piace pensare al nuovo sistema come a una distinzione tra “classi” e “istanze” nella programmazione orientata agli oggetti:
- I valori discreti (0, 1, 2) rappresentano le “classi” astratte (densità bassa, media, alta)
- I valori continui (0.7, 1.5, 2.3) rappresentano le “istanze” concrete di quelle classi
Questo ci permette di mantenere la semplicità concettuale della matrice di transizione (che lavora con le classi), aggiungendo al contempo la ricchezza espressiva degli stati continui (le istanze).
Conclusioni e Lavoro Futuro
Questo refactoring rappresenta un passo importante verso un sistema di composizione algoritmica che rispecchia più fedelmente il modo in cui percepiamo la musica. Le prime prove d’ascolto mostrano transizioni notevolmente più fluide e organiche.
Il prossimo passo sarà estendere questo approccio continuo all’intero sistema di transizione, adattando selectNextState
e predictNextState
per lavorare nativamente con stati continui, pur mantenendo la compatibilità con la matrice di transizione esistente.
Vi terrò aggiornati sui progressi e sulla musica che emergerà da questo sistema rinnovato.