Un Sid engine in basic

Ovvero: come suonargliele (le voci) a colpi di DATAlogo sid engine

1.    Introduzione

Il Commodore 64 è una macchina meravigliosa, su questo saremo tutti d’accordo, ma il basic v2 è un po’ ostico quando si voglia fare qualcosa di più che stampare “ciao” in un ciclo.

In particolare, l’accesso alle funzionalità grafiche e sonore è famoso per il ricorso massivo a pokes vari, nessuna scorciatoia, nessuna pietà!

Soffermandoci sul sonoro, quindi sul SID, l’integrato che svolge a meraviglia tale compito, ci si imbatte per lo più in listati che insegnano come suonare una nota, al più una canzoncina a singola voce, usando tipicamente l’istruzione for…next come metodo per separare una nota da un’altra.

Ma in un contesto videoludico l’ottimo è dato non solo dalla possibilità di suonare contemporaneamente le tre voci, ma dal rendere la stessa esecuzione del pezzo il meno bloccante possibile. Immaginiamo di usare tre voci e di impiegare il sopracitato for…next per ognuna. Nel caso migliore, tre note della stessa durata e con la stessa pausa rispetto alla nota successiva, basterà un solo ciclo di attesa, ma se le note durassero in modo diverso? Dovremmo gestire i tre for…next partendo dal più breve, e passando per i delta di differenza rispetto al primo per le altre note… non c’avete capito niente? Bene, tanto non ci serve, perché qui adotteremo una soluzione completamente diversa.

NB: Tengo a precisare che il programma al quale si perverrà alla fine è una mia espansione di un codice trovato in rete, all’indirizzo http://retro64.altervista.org/blog/commodore-64-sid-music-programming-with-basic-playing-a-simple-three-voices-tune/, al cui autore va la maggior parte dei credits! Il mio contributo consiste nel parametrizzare gli strumenti, normalizzare alcuni aspetti di durata degli stessi, e soprattutto l’implementazione della notazione anglosassone nei DATA piuttosto che l’inserimento a mano delle frequenze.

1.1.           Di cosa parliamo e di cosa non parliamo

L’articolo presenta un codice basic (diciamo un “engine”) che consente di suonare un pezzo inserendo le note e le relative durate in sequenza nella sezione Data, la quale è suddivisa idealmente in tre blocchi, uno per voce. Attraverso la personalizzazione di alcune (poche) variabili si possono poi scegliere gli strumenti per ogni voce. Non molti, a dire il vero, basso, batteria e piano, ma nulla vieta di estendere il codice con le forme d’onda e i parametri di proprio piacimento.

Si accennerà, inevitabilmente, ai registri del Sid, ma -volendo- il lettore non interessato potrà saltare direttamente alla sezione con il codice, copiarlo e adattarlo.

Quindi, in definitiva, quanto esposto non è un trattato sul Sid, né un Bignami di teoria musicale.

2.    Sid for very dummies

Come accennato, non tratteremo estensivamente l’integrato 6581 deputato al suono, ma qualche accenno è necessario per capire come funziona l’engine che andiamo a costruire.

È prassi comune – in praticamente tutti gli esempi ricercabili in rete – di impostare un valore di base per il primo registro coinvolto nelle operazioni sonore, il 54272. Per cui spesso si trova l’inizializzazione della variabile s=54272, e a partire da questa si ricavano gli altri registri coinvolti incrementandola di opportune unità.

Ma nel nostro caso, dovendo gestire tre voci, dal punto di vista mnemonico questo sarebbe un approccio deleterio, perché i registri deputati alle stesse funzioni per ogni voce avrebbero ognuno il proprio offset rispetto ad s.

Ecco perché partiremo subito con tre variabili che svolgono la funzione della succitata “s”, una per ogni voce, e le chiameremo L1, L2 ed L3. In immagine la loro rappresentazione e le altre variabili / registri coinvolti.

tabella registri sid

In questo modo, se vogliamo riferirci ad esempio al registro per il valore dell’alta frequenza della nota, l’offset sarà sempre 1 rispetto al valore base. Quindi L1+1 per la prima voce, L2+1 per la seconda, L3+1 per la terza.

E’ un modo elegante e mnemonico di riferirci agli indirizzi di nostro interesse.

Ma a cosa servono tali registri? Vediamoli brevemente uno ad uno, facendo riferimento alla tabella:

  • Valori per bassa frequenza e alta frequenza (Li e Hi nella tabella): mettiamola così: la frequenza d’onda è un numero che può raggiungere valori troppo alti per 8 bit, soprattutto da una certa ottava musicale in poi, per cui è necessario utilizzare due registri ad 8 bit per memorizzare la parte a bassa frequenza e quella ad alta frequenza. Il codice basic che verrà presentato più avanti inizializza un array con otto ottave già pronte [1]con le frequenze fisiche reali di ogni nota, per poi scomporle nelle due parti tramite le formule fh=int(ff/256):fl=ff-fh*256 , dove ff è la frequenza reale, fh l’alta, fl quella bassa.
  • Ampiezza bassa e alta dell’impulso (Li+2 e Li+3 nella tabella): Quando si suona una forma d’onda ad impulsi, il segnale può assumere solo due valori: alto o basso. Questi due registri regolano esattamente l’ampiezza di tali estremi.
  • Registro di controllo (Vi=Li+4 nella tabella): questo registro dice quale forma d’onda si è scelta e stabilisce l’inizio e la fine del ciclo di Attacco – Decadimento – Sostegno – Rilascio (vedi punto successivo). I bit che lo compongono sono illustrati nell’ immagine di seguito:La forma d’onda deve essere una, in altre parole i valori possibili per tale registro non contemplano attivazione di bit di onde diverse, sebbene ciò sia tecnicamente possibile ma con risultati…strani. Una volta scelta la forma d’onda, il “bit di porta” stabilisce l’inizio e la fine del ciclo ADSR, per cui se ad esempio si è scelto il valore 16, la forma d’onda triangolare, lo strumento inizierà la sua nota con il registro di controllo impostato a 16+1=17 e inizierà il rilascio quando lo stesso registro verrà settato a 16.
  • Attacco / decadimento e sostegno/rilascio (Li+5 e Li+6 – o Vi+1 e Vi+2, che è lo stesso – nella tabella)
    Questi parametri rappresentano la nascita, la vita e la morte di una nota, come da immagine che segue:

    ADSRIl volume di un tono musicale cambia dal momento in cui si comincia a sentirlo fino a quando non scompare e non è più udibile. Quando una nota viene prodotta la prima volta, aumenta da un volume a zero fino al suo volume di picco. L’andamento in cui avviene si chiama attacco. In seguito perde il picco e si assesta a un livello medio di volume. L’andamento in cui avviene questo assestamento si chiama decadimento. Una volta raggiunto il volume medio, tutto il tempo in cui rimane la nota si chiama livello di sostegno. Quando infine la nota smette di suonare, perde il livello di sostegno fino al volume a zero. L’andamento in cui cade si chiama rilascio.
    Mentre per le frequenze delle note si è dovuto ricorrere a due registri per ogni nota – alto e basso – qui accade il contrario, sfruttando mezzo registro per ogni parametro.
    Le impostazioni per l’attacco delle tre voci sono contenute nei nibble alti dei registri 5 (Li+5). Per esempio, gli andamenti d’attacco occupano i bit 2^7, 2^6, 2^5 e 2^4, per cui i valori saranno 128, 64, 32 e 16.
    Le impostazioni di decadimento sono contenute nei nibble bassi degli stessi registri. Gli andamenti di decadimento usano i bit 2^3, 2^2, 2^1 e 2^0, per cui i valori saranno 8, 4, 2 e 1.
    Sommando il valore di attacco e di decadimento scelti abbiamo il numero a 8 bit da inserire nel registro Li+5.
    Similmente accade per Sostegno e Rilascio, con l’unica differenza che dei quattro parametri il sostegno è l’unico che riguarda un livello, gli altri sono andamenti. In altre parole, il sostegno è un  volume, che inizia a subire il suo abbassamento nella fase di rilascio (ovvero, quando il bit di porta torna a 0).

Bene, tutto ciò non è strettamente necessario da conoscere per il nostro scopo, sta dietro alle quinte della definizione degli strumenti suonabili, ma spero abbia soddisfatto la curiosità di qualcuno.

3.    Let’s rock (cit. Duke Nuke’m)

Veniamo alla parte che ci interessa di più: il codice. Di seguito lo copio incollo, per poi commentarlo. Ma prima di proseguire con la lettura invito a farlo eseguire, penso che a tutti voi ricorderà qualcosa…

NB: il codice è in formato CBM Prg Studio, sebbene non vi siano token particolari interpretabili esclusivamente da tale IDE. Tuttavia il copia incolla su Vice darebbe dei syntax error per alcune righe che sforano il numero massimo di caratteri, cosa che non accade se, appunto, si passa per CBM Prg Studio e si lancia il tutto da lì.

5 dim fq(96)
10 fq(1)=268:fq(2)=284:fq(3)=301:fq(4)=318:fq(5)=337:fq(6)=358:fq(7)=379:
20fq(8)=401:fq(9)=425:fq(10)=451:fq(11)=477:fq(12)=506:fq(13)=536:fq(14)=568:
30fq(15)=602:fq(16)=637:fq(17)=675:fq(18)=716:fq(19)=758:fq(20)=803:fq(21)=851:
40fq(22)=902:fq(23)=955:fq(24)=1012:fq(25)=1072:fq(26)=1136:fq(27)=1204:
50fq(28)=1275:fq(29)=1351:fq(30)=1432:fq(31)=1517:fq(32)=1607:fq(33)=1703:
60fq(34)=1804:fq(35)=1911:fq(36)=2025:fq(37)=2145:fq(38)=2273:fq(39)=2408:
70fq(40)=2551:fq(41)=2703:fq(42)=2864:fq(43)=3034:fq(44)=3215:fq(45)=3406:
80fq(46)=3608:fq(47)=3823:fq(48)=4050:fq(49)=4291:fq(50)=4547:fq(51)=4817:
90fq(52)=5103:fq(53)=5407:fq(54)=5728:fq(55)=6069:fq(56)=6430:fq(57)=6812:
100fq(58)=7217:fq(59)=7647:fq(60)=8101:fq(61)=8583:fq(62)=9094:fq(63)=9634:
110fq(64)=10207:fq(65)=10814:fq(66)=11457:fq(67)=12139:fq(68)=12860:fq(69)=13625
120fq(70)=14435:fq(71)=15294:fq(72)=16203:fq(73)=17167:fq(74)=18188:fq(75)=19269
130fq(76)=20415:fq(77)=21629:fq(78)=22915:fq(79)=24278:fq(80)=25721:fq(81)=27251
140fq(82)=28871:fq(83)=30588:fq(84)=32407:fq(85)=34334:fq(86)=36376:fq(87)=38539
150fq(88)=40830:fq(89)=43258:fq(90)=45830:fq(91)=48556:fq(92)=51443:fq(93)=54502
160fq(94)=57743:fq(95)=61176:fq(96)=64874
170 rem instruments defs:1=bass,2=drum,3=piano
180 dim ad(3):dim sr(3):dim il(3):dim ih(3):dim wi(3)
190 ad(1)=8:ad(2)=9:ad(3)=9
200 sr(1)=9:sr(2)=10:sr(3)=4*16+9
210 il(1)=10:il(2)=0:il(3)=100
220 ih(1)=30:ih(2)=0:ih(3)=90
225 wi(1)=64:wi(2)=128:wi(3)=64
230 s1=1:s2=2:s3=3:rem assegno le voci agli strumenti.
970 r$="000000"
980 dim f%(1000,2):dim tc(3)
990 tc(0)=4:tc(1)=1:tc(2)=1:rem MINIMUM DURATION
1000 ?"processing data..."
1010 vn=0:cn=0:rem reset counters v n #
1020 read f$,d: dr=d/tc(vn)
1030 if f$="-1" then f%(cn,vn)=-1:vn=vn+1:cn=0:if vn=3 then print"done.":goto1150
1040 if f$="-1" then 1020
1050 ac=-2:if f$="0" then ac=0
1060 gosub1380
1070 ff=f:fh=int(ff/256):fl=ff-fh*256
1080 f%(cn,vn)=fh:f%(cn+1,vn)=fl
1090 if dr =1 then 1140
1100 for t = 1 to dr-1
1110 cn=cn+2
1120 f%(cn,vn)=ac:f%(cn+1,vn)=ac
1130 next t
1140 cn=cn+2: goto 1020
1150 rem let's rock
1160 l1=54272:l2=54279:l3=54286
1170 forj=l1 to 54296:poke j,0:next j
1180 h1=l1+1:h2=l2+1:h3=l3+1
1190 v1=l1+4:v2=l2+4:v3=l3+4
1200 poke 54296,15
1210 poke v1+1,ad(s1):poke v1+2,sr(s1)
1220 pokel1+2,il(s1):pokel1+3,ih(s1)
1230 poke v2+1,ad(s2):poke v2+2,sr(s2)
1235 pokel2+2,il(s2):pokel2+3,ih(s2)
1240 poke v3+1,ad(s3):poke v3+2,sr(s3)
1250 pokel3+2,il(s3):pokel3+3,ih(s3)
1270 cn=0
1280 ti$=r$:t=ti
1290 x1=f%(cn,0):y1=f%(cn+1,0):x2=f%(cn,1):y2=f%(cn+1,1):x3=f%(cn,2):y3=f%(cn+1,2)
1300 ifx1 =-1 then 1370
1310 ifx1>-2 then pokev1,wi(s1):if x1 > 0 then poke h1,x1:poke l1,y1:poke v1,wi(s1)+1
1320 ifx2>-2 then pokev2,wi(s2):if x2 > 0 then poke h2,x2:poke l2,y2:poke v2,wi(s2)+1
1330 ifx3>-2 then pokev3,wi(s3):if x3 > 0 then poke h3,x3:poke l3,y3:poke v3,wi(s3)+1
1340 cn=cn+2:t=t+4:rem the biggest t, the slower timing
1350 ifti<t then 1350
1360 goto 1280
1370 poke54296,0:end
1380 rem desumo freq. dalla nota.Formato:n(n)o, il secondo n serve per i diesis
1390 f=0
1400 k1$=left$(f$,len(f$)-1):o1$=right$(f$,1)
1401 if f$="-1" then f=-1:return
1408 if f$="0" then f=0:return
1410 if k1$="c" then i1=1:goto1530
1420 if k1$="cs" then i1=2:goto1530
1430 if k1$="d" then i1=3:goto1530
1440 if k1$="ds" then i1=4:goto1530
1450 if k1$="e" then i1=5:goto1530
1460 if k1$="f" then i1=6:goto1530
1470 if k1$="fs" then i1=7:goto1530
1480 if k1$="g" then i1=8:goto1530
1490 if k1$="gs" then i1=9:goto1530
1500 if k1$="a" then i1=10:goto1530
1510 if k1$="as" then i1=11:goto1530
1520 if k1$="b" then i1=12:goto1530
1530 i1=12*val(o1$)+i1:f=fq(i1)
1540 return
1550 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1560 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1570 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1580 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1590 :data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1600 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1610 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1620 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1630 :data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1640 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1650 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1660 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1670 :data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1680 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1690 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1695 data "d2",2,"d2",2,"f2",2,"f2",2,"g2",2,"g2",2,"gs2",2,"a2",2
1700 data"-1",-1
1710 ::data "0",64
1718 :data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1728 data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1738 data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1748 data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1758 :data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1768 data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1778 data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1788 data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1798 :data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1808 data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1816 data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1820 data "c4",4,"b5",4,"c4",2,"c4",2,"b5",2,"b5",2
1825 data"-1",-1
1830 ::data "0",64,"0",64
1840 data"d5",10,"a4",2,"as4",2,"g4",2
1845 data"a4",10,"f4",2,"g4",2,"e4",2
1848 data"f4",10,"d4",2,"e4",2,"cs4",2
1849 data"d4",10,"0",6
1850 data"d5",10,"a4",2,"as4",2,"g4",2
1855 data"a4",10,"f4",2,"g4",2,"e4",2
1858 data"f4",10,"d4",2,"e4",2,"cs4",2
1859 data"d4",10,"0",6
1860 data"-1",-1

Scarica il listato

 

3.1.           Righe 5-60: le frequenze in gioco

Le righe 5-60 non sono altro che l’array di tutte le frequenze per le note delle prime 8 ottave. Sebbene sia comune parlare di “sette note”, in realtà sappiamo bene che dalle nostre parti, da diverse centinaia di anni, le note sono 12 perché abbiamo anche 5 semitoni ( i tasti neri del pianoforte, per intenderci). Per cui l’array contiene in sequenza, a partire dal do più basso, 12*8=96 valori di frequenza.

3.2.           Righe 170-990: inizializzazioni variabili e definizione strumenti in uso

Poiché il programma implementa tre possibili strumenti da assegnare alle voci, in queste righe si preparano gli array dim ad(3):dim sr(3):dim il(3):dim ih(3):dim wi(3) che conterranno rispettivamente: i tre valori scelti di attack-decay, i tre valori scelti di sustain-release, i tre valori scelti di ampiezza bassa e ampiezza alta dell’impulso, i tre valori scelti per la forma d’onda ovvero il registro di controllo.

L’unica parte di codice da personalizzare qui è la riga

230 s1=1:s2=2:s3=3:rem assegno le voci agli strumenti.

dove decidiamo, per ognuna delle tre voci del sid, quale strumento dovrà suonare, tenendo presente che, per come sono impostati i parametri, avremo 1=bass, 2=drum, 3=piano.

Per cui, nella configurazione attuale, la prima voce suona il basso (s1=1), la seconda la batteria e la terza il piano.

Facendo però uso di sostanze stupefacenti possiamo andare a reperire i parametri per altri strumenti e andare ad aggiungere ulteriori voci (dimensionando opportunamente gli array ad, sr, il, ih, wi visti sopra)

Il blocco di codice qui presentato si chiude con dim f%(1000,2) , un array di interi che conterrà nella prima dimensione le frequenze alte e basse (alta, bassa, alta, bassa…) delle note scelte per il pezzo da suonare, e tutto ciò per ogni voce (per questo è bidimensionale, la seconda dimensione va da 0 a 2, ovvero tre voci). Il suo popolamento sarà il risultato della fase di inizializzazione che andiamo subito qui a descrivere.

3.3.           Righe 1000-1140: inizializzazione array delle frequenze del pezzo

Prima di accennare al ruolo di queste righe, c’è da fare una premessa. L’accesso ai DATA non è di tipo random, questo significa che in uno scenario di lettura ed esecuzione contemporanea avremmo dovuto mischiare le note dei tre track inserendole ad ogni colpo (oppure di tre in tre colpi) di read, con conseguente difficoltà di modifica e di lettura dello “spartito” memorizzato. Pertanto, un compromesso ragionevole è quello già accennato: perdiamo un po’ di tempo in una fase di inizializzazione di un array bidimensionale (f%) con il vantaggio di scrivere lo spartito in maniera ordinata, suddividendo i DATA in tre blocchi, uno per ogni track.

I DATA presentano lo spartito attraverso le coppie “notaottava”,durata_in_sedicesimi, ad esempio “d2”,2 significa RE della seconda ottava, durata pari a due (sedicesimi). Perché “sedicesimi”? perchè la battuta dello spartito è qui suddivisa in 16 quanti, “semicrome” per chi mastica un minimo di musica.

Il lettore non uso alla notazione musicale non si spaventi, non lo sono neanche io! Semplicemente, quando la somma delle durate raggiunge 16 vuol dire che, in un ipotetico pentagramma, si sta passando alla battuta successiva, quello spazio di note tra due |     | per intenderci.

Uniche eccezioni al formato nota-durata sono la coppia -1,-1 che sta a demarcare la fine del track, e il valore di nota “0” (senza ottava), che significa una pausa (la durata viene presa dal valore di durata, come per le altre note). Nello “spartito” di esempio, si vede che la seconda voce inizia con “0”,64 perché deve subentrare dopo 64/16 = 4 battute.

Per i più musicalmente skillati: non è implementata nessuna facilitazione per il tempo.  Se volete fare un tre quarti dovete regolarvi da soli con i sedicesimi.

Non ci soffermiamo sul codice più di tanto, perché esula dagli scopi di questo articolo. Del resto questa sezione non contiene alcunché di personalizzabile. Segnaliamo soltanto che il passaggio dalla notazione “amichevole” anglosassone al valore di frequenza avviene tramite la gosub 1380, la quale fa un parse di quanto arrivato tramite read e prende le dovute decisioni. In particolare, se trattasi di una nota, elabora a quale indice dell’array delle frequenze essa debba riferirsi, tramite un semplice calcolo basato posizione della nota nella generica ottava e il numero di ottava indicato (riga 1530).

3.4.           Righe 1150-1360: il player

Diciamo subito che nel player c’è un solo – ma importante – parametro personalizzabile, la velocità di esecuzione. A dire il vero non è proprio un parametro ma una costante (nulla vieta però di assegnargli una variabile), e precisamente ci riferiamo alla riga   1340 cn=cn+2:t=t+4:rem the biggest t, the slower timing

Quel “t=t+4” indica il timing, se invece che 4 si mette un valore più alto il pezzo andrà più lento. Maggiore è l’incremento di t, minore sarà la velocità di esecuzione.

Detto questo, è utile spendere qualche parola su come funziona il player.

La riga 1160 dovrebbe essere familiare se avete letto il capitolo 2.  l1=54272:l2=54279:l3=54286 sono le tre variabili di base per i vari registri di ognuna delle tre voci. Dopo la riga 1170, che azzera tutti i registri sonori, le due successive seguono la logica vista nella tabella degli offset per accedere ai registri delle tre voci, illustrata sempre al capitolo 2.

Le righe 1210-1250 tirano fuori gli strumenti dalle loro custodie e foderi, dopodichè inizia l’esecuzione vera e propria con la riga 1280.

Tale riga sarà richiamata nel loop come inizio del ciclo, e compie un’operazione importante: azzera la variabile di tempo ti$, assegnandogli la costante r$=”000000”. Questo è il modo di azzerare il timer, notare che ha effetto anche sulla variabile gemella e numerica “ti”. L’istruzione successiva assegna il valore di timer attuale alla variabile t (quella di cui si accennava sopra, dal cui incremento dipende la lentezza di esecuzione).

Successivamente vengono letti, dall’array bidimensionale f% precedentemente inizializzato, i valori di frequenze alti e bassi per ogni voce. Qui si vede chiaramente come questa modalità non sarebbe stata possibile con un accesso non random quale quello dei read-data, dato che ci muoviamo agevolmente tra le tre voci:

1290 x1=f%(cn,0):y1=f%(cn+1,0):x2=f%(cn,1):y2=f%(cn+1,1):x3=f%(cn,2):y3=f%(cn+1,2)

 

Notare che la riga 1300 vede se x1 è pari a -1. In tal caso passa la palla alla linea di chiusura che azzera il volume e termina il programma. Siccome x1 è letto dal track numero 1, questo significa che non importa quanto lungo sia il track 2 o il track 3, ma la lunghezza del pezzo la definisce il track 1. Del resto, è comunque necessario che la somma dei sedicesimi di ogni track sia la stessa, eventualmente infarcendo il tutto con adeguate pause. In altre parole, i tre blocchi di DATA devono avere le durate che, sommate, sono uguali.

Le righe 1310-1330 suonano le note con frequenze alte-basse xi,yi, ma prima verificano se il valore trovato non sia diverso da una nota. In particolare, il valore -2 (che viene inserito opportunamente in fase di inizializzazione array ) sta a indicare che la nota deve continuare a suonare durante questo passaggio di ciclo, per cui le istruzioni if xi>-2 … qui indicate non fanno assolutamente nulla: il che vuol dire che la nota continua indisturbata a suonare. Notare che le note delle tre voci suonano contemporaneamente (o, meglio, così rapidissimamente vicine da sembrare contemporanee).

La riga 1350, l’unico vero momento di freeze del codice, mostra il motivo per cui abbiamo detto che maggiore è l’incremento di t, meno veloce è l’esecuzione. Infatti, 1350 if ti<t then 1350 attende che il contatore interno – azzerato ad ogni ciclo, come già detto – si incrementi fino al valore di t. Per cui , maggiore è t, maggiore sarà l’attesa.

4.    Righe 1550-fine: lo spartito

Veniamo alla parte di maggior personalizzazione: lo “spartito” suddiviso nei tre track, uno per voce, uno strumento per ognuno.

Ricapitolando ed estendendo quanto detto sopra in maniera sparsa:

  • le note di ogni track finiscono con la coppia -1,-1
  • i DATA contengono coppie di dati del formato notaottava,durata, ad esempio “c7”,4.
  • le note sono stringhe, precisamente lettere secondo la notazione anglosassone, seguite da un numero compreso tra zero e 8 a indicare l’ottava. “c7” significa “do della settima ottava”.
  • Le note dei semitoni hanno una “s” dopo il nome della nota. Per cui “cs” sta per “c sharp”, ovvero “Do #”. Pertanto, “cs4” indica il do diesis della quarta ottava.
  • Le pause sono “note” indicate con “0”. La durata che le segue indica la durata della pausa.
  • La durata esprime il numero di 16mi di durata della nota. In sostanza, misuriamo in semicrome.
  • Tuttavia, per non incappare in un overflow, probabilmente per pezzi poco più lunghi di uno medio-corto è da evitare di utilizzare durate minori di 2, per motivi di dimensionamento dell’array.
  • Le somme delle durate di ogni singolo track devono coincidere. Se, come nell’esempio qui trattato, la linea del basso (qui è la voce 1) ha una durata complessiva di 256, lo stesso deve valere per le altre voci, eventualmente facendo uso della pausa “0” di opportuna durata.

Lo “spartito” in esempio – che avrete riconosciuto essere un arrangiamento dell’iconico riff di basso + batteria + melodia di Ghost’n Goblins – mostra quanto detto sopra, riferitevi a quel set di data per comprendere quanto detto.

Vedrete che le durate sommate nelle tre voci sono uguali, noterete che il basso (primo track) parte subito, mentre la batteria (secondo track) attende 64 sedicesimi perché inizia con data “0”,64 , dando così il tempo al riff di basso di concludere la sua introduzione (lunga sempre 64), e la melodia inizia dopo uno stesso tempo di attesa per l’introduzione della batteria, per cui 64+64=128 sedicesimi di attesa.

Insomma, prima di scrivere i DATA è bene aver chiaro in testa come deve suonare il pezzo. Se sapete leggere un minimo la musica, consiglio di trovare un adattamento per piano del pezzo che volete riprodurre. È perfetto per i nostri scopi, perché si suppone che una persona abbia due sole mani, per cui potete prendere la linea della mano sinistra e quella della mano destra e assegnare le rispettive note ai primi due track, impostandoli come pianoforte. Con la terza voce libera ci fate invece quello che vi pare.

5.    Conclusione

Essendo questo un engine  a tre voci scritto in basic, c’è da gioire– intanto – che funzioni.

Teoricamente è possibile “immergere” l’esecuzione del pezzo dentro un codice più generale, un gioco ad esempio, basta ad esempio uscire momentaneamente dal loop che esegue le note tramite gosub a qualcosa per poi riprendere il controllo del tempo trascorso nell’esecuzione delle note,  e eventualmente proseguire con la successiva.

Tuttavia, dati i limiti del basic, tutto ciò resta teoria, a meno che non si voglia stampare un semplice “ciao mondo” a tempo di musica.

Ma per un title screen è perfetto: una get a$ per leggere la pressione di un tasto, oppure una peek sul joystick non sono così bloccanti.

 

Francesco Clementoni aka Arturo Dente

[1] Le frequenze delle note sono state prese da https://www.edicolac64.com/public/GuidaUtenteC64.pdf

 

Have your say