11 minute read

Introduzione

Csound offre diverse funzioni di generazione di tabelle (GEN), ciascuna con caratteristiche specifiche. GEN02 è una funzione particolarmente utile che consente di trasferire dati direttamente in una tabella. Tuttavia, i test condotti rivelano un comportamento peculiare riguardante la dimensione effettiva delle tabelle generate e l’accesso ai loro elementi. (I test sono presenti in fondo)

Sintassi di GEN02

f # time size 2 v1 v2 v3 ...

Oppure con ftgen:

iTabella ftgen 0, 0, size, -2, v1, v2, v3, ...

Dove:

  • size è la dimensione dichiarata della tabella
  • -2 è il numero di routine GEN (negativo per evitare la normalizzazione)
  • v1, v2, v3, ... sono i valori da inserire nella tabella

Il Problema del Guard Point

Dall’analisi dei test e del log, emerge chiaramente un pattern ricorrente: la discrepanza tra la dimensione dichiarata della tabella e la dimensione effettiva restituita da ftlen().

Pattern osservati nei test

Test Dichiarata Valori inseriti Dimensione ftlen() Accesso ultimo valore
Test 1 3 3 valori 2 Fallisce l’accesso all’indice 2
Test 2 5 5 valori 4 Fallisce l’accesso all’indice 4
Test 4 4 3 valori 4 Funziona correttamente
Caso 1.1 4 3 valori 4 Funziona correttamente
Caso 1.2 10 10 valori 10 Funziona correttamente

Spiegazione del Guard Point

Il “guard point” è un elemento speciale riservato in alcune tabelle Csound, che occupa l’ultimo indice della tabella. Nei test, si è riscontrato che:

  1. Quando la tabella è riempita completamente (dimensione = numero di valori), il guard point “ruba” l’ultimo elemento, rendendo la dimensione effettiva size-1.

  2. Quando la tabella non è completamente riempita (dimensione > numero di valori), il guard point occupa lo spazio rimanente, e la dimensione effettiva rimane size.

  3. C’è un’apparente incoerenza nei casi 1.2 e 1.3 del test, dove tabelle completamente riempite sembrano mantenere la dimensione dichiarata. Questo potrebbe dipendere da ottimizzazioni interne di Csound o da particolari condizioni di test.

Formula per la dimensione effettiva

Basandosi sui test, possiamo dedurre la seguente regola:

dimensione_effettiva = min(dimensione_dichiarata, numero_valori_inseriti)

Tuttavia, è importante notare che esistono eccezioni e comportamenti apparentemente incoerenti.

Raccomandazioni pratiche

  1. Sovradimensionare intenzionalmente le tabelle:
    iTabella ftgen 0, 0, num_valori + 1, -2, ...
    
  2. Verificare sempre la dimensione effettiva:
    iSize = ftlen(iTabella)
    
  3. Rispettare i limiti della tabella:
    if (iIdx < iSize) then
        iValore tab_i iIdx, iTabella
    endif
    
  4. In alternativa, usare la dimensione automatica:
    iTabella ftgen 0, 0, 0, -2, val1, val2, ... ; dimensione determinata dai valori
    

Conclusione

Comprendere il comportamento del guard point in GEN02 è essenziale per prevenire errori di accesso alla memoria e garantire la corretta memorizzazione e recupero dei dati nelle tabelle. Il comportamento osservato, sebbene non completamente documentato, è consistente con l’implementazione interna di Csound e deve essere considerato nello sviluppo di opcode che manipolano tabelle GEN02.

<CsoundSynthesizer>
<CsOptions>
-odac -d -m0
</CsOptions>
<CsInstruments>
sr = 44100
ksmps = 32
nchnls = 2
0dbfs = 1

; ====================================================================
; MINIMA INIZIALIZZAZIONE NECESSARIA PER IL TEST
; ====================================================================

; Livello debug (0 = nessun debug, 1 = base, 2 = dettagliato, 3 = tutto)
gi_debug init 2

; Contatore degli ID dei comportamenti
gi_compId init 0

; Numero massimo di comportamenti
gi_NUMComportamenti init 100

; Tabelle per memorizzare i parametri dei comportamenti (versione ristrutturata)
gi_comp_RITMI      ftgen 0, 0, gi_NUMComportamenti*11, -2, 0      ; Nuova struttura: 10 ritmi + 1 lunghezza
gi_comp_POSIZIONI  ftgen 0, 0, gi_NUMComportamenti*11, -2, 0      ; Nuova struttura: 10 posizioni + 1 lunghezza

; Tabelle standard per i parametri dei comportamenti
gi_comp_ATTACCO     ftgen 0, 0, gi_NUMComportamenti, -2, 0       ; Tempo di attacco
gi_comp_DURARMONICA ftgen 0, 0, gi_NUMComportamenti, -2, 0       ; Durata armonica
gi_comp_DURATA      ftgen 0, 0, gi_NUMComportamenti, -2, 0       ; Durata complessiva
gi_comp_AMPIEZZA    ftgen 0, 0, gi_NUMComportamenti, -2, 0       ; Ampiezza in dB
gi_comp_OTTAVA      ftgen 0, 0, gi_NUMComportamenti, -2, 0       ; Ottava
gi_comp_REGISTRO    ftgen 0, 0, gi_NUMComportamenti, -2, 0       ; Registro

; ====================================================================
; IMPLEMENTAZIONE DELL'OPCODE DI TEST
; ====================================================================

; Implementazione dell'opcode storeTransitionBehaviorParameters
opcode storeTransitionBehaviorParameters, i, iiiiiiii
    iAttacco, iDurata, iDurataArmonica, iAmpiezza, iOttava, iRegistro, iRhythmsTable, iPositionsTable xin
    
    ; Incrementa il contatore globale per ottenere un nuovo ID
    gi_compId += 1
    iIdComp = gi_compId
    
    ; Controlla che l'ID sia entro i limiti
    if (iIdComp >= gi_NUMComportamenti) then
        prints "ERRORE: Superato il numero massimo di comportamenti (%d)\n", gi_NUMComportamenti
        iIdComp = gi_NUMComportamenti - 1  ; Limita all'ultimo disponibile
    endif
    
    ; Memorizza i parametri principali nelle tabelle
    tabw_i iAttacco, iIdComp, gi_comp_ATTACCO
    tabw_i iDurata, iIdComp, gi_comp_DURATA
    tabw_i iDurataArmonica, iIdComp, gi_comp_DURARMONICA
    tabw_i iAmpiezza, iIdComp, gi_comp_AMPIEZZA
    tabw_i iOttava, iIdComp, gi_comp_OTTAVA
    tabw_i iRegistro, iIdComp, gi_comp_REGISTRO
    
    ; Conta il numero di ritmi nella tabella di input
    iRhythmSize = ftlen(iRhythmsTable)
    iNumRitmi = min(iRhythmSize, 10)  ; Limita a max 10 ritmi
    
    ; Calcola l'indice base per i ritmi
    iRitmiBaseIndex = iIdComp * 11
    
    ; Memorizza la lunghezza come primo elemento
    tabw_i iNumRitmi, iRitmiBaseIndex, gi_comp_RITMI
    
    ; Copia i valori dei ritmi
    iRIdx = 0
    while (iRIdx < iNumRitmi) do
        iRitmo tab_i iRIdx, iRhythmsTable
        tabw_i iRitmo, iRitmiBaseIndex + 1 + iRIdx, gi_comp_RITMI
        iRIdx += 1
    od
    
    ; Conta il numero di posizioni nella tabella di input
    iPosSize = ftlen(iPositionsTable)
    iNumPos = min(iPosSize, 10)  ; Limita a max 10 posizioni
    
    ; Calcola l'indice base per le posizioni
    iPosBaseIndex = iIdComp * 11
    
    ; Memorizza la lunghezza come primo elemento
    tabw_i iNumPos, iPosBaseIndex, gi_comp_POSIZIONI
    
    ; Copia i valori delle posizioni
    iPIdx = 0
    while (iPIdx < iNumPos) do
        iPos tab_i iPIdx, iPositionsTable
        tabw_i iPos, iPosBaseIndex + 1 + iPIdx, gi_comp_POSIZIONI
        iPIdx += 1
    od
    
    ; Debug output se richiesto
    if (gi_debug >= 2) then
        prints "Comportamento %d memorizzato nelle tabelle:\n", iIdComp
        prints "  Attacco: %.2f, Durata: %.2f, DurArmonica: %.2f\n", 
               iAttacco, iDurata, iDurataArmonica
        prints "  Ampiezza: %.2f, Ottava: %d, Registro: %d\n",
               iAmpiezza, iOttava, iRegistro
        prints "  Ritmi (%d): ", iNumRitmi
        
        iIdx = 0
        while (iIdx < iNumRitmi) do
            iVal tab_i iRitmiBaseIndex + 1 + iIdx, gi_comp_RITMI
            prints "%d ", iVal
            iIdx += 1
        od
        prints "\n"
        
        prints "  Posizioni (%d): ", iNumPos
        iIdx = 0
        while (iIdx < iNumPos) do
            iVal tab_i iPosBaseIndex + 1 + iIdx, gi_comp_POSIZIONI
            prints "%d ", iVal
            iIdx += 1
        od
        prints "\n"
    endif
    
    xout iIdComp
endop

; ====================================================================
; STRUMENTI DI TEST
; ====================================================================

; Strumento per testare con ritmi e posizioni di varie lunghezze
instr TestCase1
    prints "\n=== TEST CASE 1: Ritmi e posizioni di varie lunghezze ===\n"
    
    ; Parametri base di test
    iAttacco = 0
    iDurata = 20
    iDurataArmonica = 5
    iAmpiezza = -12
    iOttava = 4
    iRegistro = 5
    
    ; CASO 1: Pochi elementi (3 ritmi, 2 posizioni)
    prints "CASO 1.1: Pochi elementi (3 ritmi, 2 posizioni)\n"
    iRhythmsTable ftgen 0, 0, 4, -2, 4, 5, 6
    iPositionsTable ftgen 0, 0, 3, -2, 1, 2
    iSize = ftlen(iRhythmsTable)
    prints "Dimensione dichiarata: 10, Dimensione effettiva ftlen(): %d\n", iSize
    
    iIdComp storeTransitionBehaviorParameters iAttacco, iDurata, iDurataArmonica, 
                                             iAmpiezza, iOttava, iRegistro, 
                                             iRhythmsTable, iPositionsTable
    
    ; CASO 2: Esattamente 10 elementi (limite)
    prints "\nCASO 1.2: Esattamente 10 elementi (limite)\n"
    iRhythmsTable ftgen 0, 0, 10, -2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    iPositionsTable ftgen 0, 0, 10, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
    iSize = ftlen(iRhythmsTable)
    prints "Dimensione dichiarata: 10, Dimensione effettiva ftlen(): %d\n", iSize
    iAttacco = 10 ; Modifica l'attacco per distinguere i casi
    iIdComp storeTransitionBehaviorParameters iAttacco, iDurata, iDurataArmonica, 
                                             iAmpiezza, iOttava, iRegistro, 
                                             iRhythmsTable, iPositionsTable
    
    ; CASO 3: Più di 10 elementi (dovrebbe limitare)
    prints "\nCASO 1.3: Più di 10 elementi (dovrebbe limitare a 10)\n"
    iRhythmsTable ftgen 0, 0, 15, -2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
    iPositionsTable ftgen 0, 0, 12, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
    
    iAttacco = 20 ; Modifica l'attacco per distinguere i casi
    iIdComp storeTransitionBehaviorParameters iAttacco, iDurata, iDurataArmonica, 
                                             iAmpiezza, iOttava, iRegistro, 
                                             iRhythmsTable, iPositionsTable
endin

; Strumento per testare valori limite e casi particolari
instr TestCase2
    prints "\n=== TEST CASE 2: Valori limite e casi particolari ===\n"
    
    ; CASO 1: Valori estremi
    prints "CASO 2.1: Valori estremi\n"
    iAttacco = 0
    iDurata = 1000     ; Durata molto lunga
    iDurataArmonica = 0.001  ; Durata armonica molto breve
    iAmpiezza = -90    ; Ampiezza molto bassa
    iOttava = 10       ; Ottava alta
    iRegistro = 1      ; Registro basso
    
    iRhythmsTable ftgen 0, 0, 5, -2, 100, 200, 300, 400, 500  ; Ritmi molto grandi
    iPositionsTable ftgen 0, 0, 5, -2, 99, 199, 299, 399, 499  ; Posizioni elevate
    
    iIdComp storeTransitionBehaviorParameters iAttacco, iDurata, iDurataArmonica, 
                                             iAmpiezza, iOttava, iRegistro, 
                                             iRhythmsTable, iPositionsTable

    ; CASO 2: Valori negativi
    prints "\nCASO 2.2: Valori negativi\n"
    iAttacco = 30
    iDurata = 5
    iDurataArmonica = 1
    iAmpiezza = -6
    iOttava = 2
    iRegistro = 3
    
    iRhythmsTable ftgen 0, 0, 5, -2, -1, 2, -3, 4, -5  ; Ritmi negativi (non dovrebbero esistere)
    iPositionsTable ftgen 0, 0, 5, -2, -1, 0, 1, -2, 2  ; Posizioni negative
    
    iIdComp storeTransitionBehaviorParameters iAttacco, iDurata, iDurataArmonica, 
                                             iAmpiezza, iOttava, iRegistro, 
                                             iRhythmsTable, iPositionsTable
    
    ; CASO 3: Tabelle vuote o molto piccole
    prints "\nCASO 2.3: Tabelle vuote o molto piccole\n"
    iAttacco = 40
    
    iRhythmsTable ftgen 0, 0, 1, -2, 1  ; Solo un elemento
    iPositionsTable ftgen 0, 0, 0, -2, 0  ; Tabella vuota (dovrebbe gestire questo caso)
    
    iIdComp storeTransitionBehaviorParameters iAttacco, iDurata, iDurataArmonica, 
                                             iAmpiezza, iOttava, iRegistro, 
                                             iRhythmsTable, iPositionsTable
endin

; Strumento per verificare la lettura dei valori memorizzati
instr VerifyStorage
    prints "\n=== VERIFICA LETTURA VALORI MEMORIZZATI ===\n"
    
    ; Verifica tutti i comportamenti creati finora
    iMaxId = gi_compId
    prints "Comportamenti creati: %d\n", iMaxId
    
    iId = 1 ; Inizia dal primo ID (1)
    while (iId <= iMaxId) do
        prints "\nLETTURA COMPORTAMENTO %d:\n", iId
        
        ; Leggi e stampa i parametri principali
        iAttacco tab_i iId, gi_comp_ATTACCO
        iDurata tab_i iId, gi_comp_DURATA
        iDurataArmonica tab_i iId, gi_comp_DURARMONICA
        iAmpiezza tab_i iId, gi_comp_AMPIEZZA
        iOttava tab_i iId, gi_comp_OTTAVA
        iRegistro tab_i iId, gi_comp_REGISTRO
        
        prints "  Parametri: A=%.2f, D=%.2f, DA=%.2f, Amp=%.2f, Oct=%d, Reg=%d\n",
               iAttacco, iDurata, iDurataArmonica, iAmpiezza, iOttava, iRegistro
        
        ; Leggi e stampa ritmi
        iRitmiBaseIndex = iId * 11
        iNumRitmi tab_i iRitmiBaseIndex, gi_comp_RITMI
        
        prints "  Ritmi (%d): ", iNumRitmi
        iIdx = 0
        while (iIdx < iNumRitmi) do
            iRitmo tab_i iRitmiBaseIndex + 1 + iIdx, gi_comp_RITMI
            prints "%d ", iRitmo
            iIdx += 1
        od
        prints "\n"
        
        ; Leggi e stampa posizioni
        iPosBaseIndex = iId * 11
        iNumPos tab_i iPosBaseIndex, gi_comp_POSIZIONI
        
        prints "  Posizioni (%d): ", iNumPos
        iIdx = 0
        while (iIdx < iNumPos) do
            iPos tab_i iPosBaseIndex + 1 + iIdx, gi_comp_POSIZIONI
            prints "%d ", iPos
            iIdx += 1
        od
        prints "\n"
        
        iId += 1
    od
    
    prints "\nTest completato con successo!\n"
endin

; Questo strumento tenta di generare più comportamenti del limite massimo
instr TestOverflow
    prints "\n=== TEST OVERFLOW: Più comportamenti del limite ===\n"
    
    ; Parametri base di test
    iAttacco = 100
    iDurata = 10
    iDurataArmonica = 2
    iAmpiezza = -12
    iOttava = 3
    iRegistro = 4
    
    iRhythmsTable ftgen 0, 0, 3, -2, 1, 2, 3
    iPositionsTable ftgen 0, 0, 3, -2, 0, 1, 2
    
    ; Calcola quanti comportamenti dobbiamo creare per raggiungere il limite
    iToCreate = gi_NUMComportamenti - gi_compId
    if (iToCreate <= 0) then
        prints "Già raggiunto il limite di comportamenti (%d)\n", gi_NUMComportamenti
        goto skip
    endif
    
    prints "Creo %d comportamenti per raggiungere il limite...\n", iToCreate
    
    ; Crea comportamenti fino al limite
    iCount = 0
    while (iCount < iToCreate) do
        iIdComp storeTransitionBehaviorParameters iAttacco + iCount, iDurata, iDurataArmonica, 
                                                 iAmpiezza, iOttava, iRegistro, 
                                                 iRhythmsTable, iPositionsTable
        iCount += 1
    od
    
    ; Prova a creare un comportamento oltre il limite
    prints "\nProvo a creare un comportamento oltre il limite...\n"
    iIdComp storeTransitionBehaviorParameters iAttacco + 1000, iDurata, iDurataArmonica, 
                                             iAmpiezza, iOttava, iRegistro, 
                                             iRhythmsTable, iPositionsTable
    
    skip:
endin

instr DiagnosticTest
    prints "\n=== TEST DIAGNOSTICO PER ANOMALIE TABELLE ===\n"
    
    ; Test 1: Tabella di dimensione 3
    prints "TEST 1: Tabella di dimensione 3\n"
    iRhythmsTable ftgen 0, 0, 3, -2, 4, 5, 6
    
    ; Stampa la dimensione effettiva della tabella
    iSize = ftlen(iRhythmsTable)
    prints "Dimensione dichiarata: 3, Dimensione effettiva ftlen(): %d\n", iSize
    
    ; Tenta di accedere a tutti gli elementi
    prints "Contenuto della tabella secondo tab_i:\n"
    iIdx = 0
    while (iIdx < 5) do  ; Proviamo a leggere anche oltre la dimensione dichiarata
        iValue = -999    ; Valore di default per identificare errori
        
        ; Usa una struttura try-catch per evitare errori fatali
        if (iIdx < iSize) then
            iValue tab_i iIdx, iRhythmsTable
            prints "  Elemento[%d] = %d\n", iIdx, iValue
        else
            prints "  Elemento[%d] = INACCESSIBILE (oltre dimensione)\n", iIdx
        endif
        
        iIdx += 1
    od
    
    ; Test 2: Tabella di dimensione 5 (valori positivi)
    prints "\nTEST 2: Tabella di dimensione 5 (valori positivi)\n"
    iRhythmsTable2 ftgen 0, 0, 5, -2, 100, 200, 300, 400, 500
    
    ; Stampa la dimensione effettiva
    iSize2 = ftlen(iRhythmsTable2)
    prints "Dimensione dichiarata: 5, Dimensione effettiva ftlen(): %d\n", iSize2
    
    ; Accedi a ogni elemento
    prints "Contenuto della tabella secondo tab_i:\n"
    iIdx = 0
    while (iIdx < 6) do
        if (iIdx < iSize2) then
            iValue tab_i iIdx, iRhythmsTable2
            prints "  Elemento[%d] = %d\n", iIdx, iValue
        else
            prints "  Elemento[%d] = INACCESSIBILE (oltre dimensione)\n", iIdx
        endif
        
        iIdx += 1
    od

    ; Test 3: Verifica della funzione di dump tabella
    prints "\nTEST 3: Dump completo della tabella con ftprint\n"
    prints "Tabella 1 (dimensione 3):\n"
    ftprint iRhythmsTable, 0, 0, 5
    
    prints "\nTabella 2 (dimensione 5):\n"
    ftprint iRhythmsTable2, 0, 0, 6
    
    ; Test 4: Alternativa di creazione tabella
    prints "\nTEST 4: Creazione tabella alternativa\n"
    
    ; Crea tabella con size+1 per vedere se cambia qualcosa
    iRhythmsTable4 ftgen 0, 0, 4, -2, 10, 20, 30
    
    iSize4 = ftlen(iRhythmsTable4)
    prints "Dimensione dichiarata: 4 (per 3 elementi), Dimensione effettiva: %d\n", iSize4
    
    prints "Contenuto tabella:\n"
    ftprint iRhythmsTable4, 0, 0, 5
    
    prints "\nTest diagnostico completato.\n"
endin
; ====================================================================
; STRUMENTO PRINCIPALE DI TEST
; ====================================================================

instr RunTests
    ; Esegui i vari casi di test
    event_i "i", "TestCase1", 0, 0.1
    event_i "i", "TestCase2", 0.5, 0.1
    
    ; Verifica i risultati
    event_i "i", "VerifyStorage", 1, 0.1
        
    ; Test di overflow (commenta se non necessario, richiede molto tempo)
    ; event_i "i", "TestOverflow", 1.5, 0.1
    
    prints "\nTutti i test completati.\n"
    
    ; Termina Csound dopo i test
    turnoff
endin

</CsInstruments>
<CsScore>
; Avvia il test principale
i "RunTests" 0 2
i "DiagnosticTest" 2 2
</CsScore>
</CsoundSynthesizer>