GEN02 e guard-point
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:
-
Quando la tabella è riempita completamente (dimensione = numero di valori), il guard point “ruba” l’ultimo elemento, rendendo la dimensione effettiva
size-1
. -
Quando la tabella non è completamente riempita (dimensione > numero di valori), il guard point occupa lo spazio rimanente, e la dimensione effettiva rimane
size
. -
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
- Sovradimensionare intenzionalmente le tabelle:
iTabella ftgen 0, 0, num_valori + 1, -2, ...
- Verificare sempre la dimensione effettiva:
iSize = ftlen(iTabella)
- Rispettare i limiti della tabella:
if (iIdx < iSize) then iValore tab_i iIdx, iTabella endif
- 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>