\nuovoarticolo{Giuseppe Ligorio}{OperBlitting: operazioni aritmetiche con il
Blitter e player musicale a 16 voci}{%
Giuseppe Ligorio \\
Via Valeggio 15 \\
I-10128 Torino TO \\
Tel.:~(011)9494530 \\
}{}{italian}{%
{\em Actually}, adv.: Perhaps; possibly.}{Ambrose Bierce}

\sez{Introduzione}


Nel lontano inizio del 1993 mi posi il problema di poter realizzare il
multiplexing delle 4 voci audio per ottenerne 8; era il periodo in cui
imperversavano TFMX e Oktalyzer.

Esistono due modi per sovrappore due o più onde sonore in una sola:
{\em interleaved\/} e {\em averaged}.

La tecnica interleaved consiste nell'utilizzare alternativamente un campione
della prima onda e uno della seconda, in maniera così veloce da ingannare
l'orecchio; la caratteristica negativa di questa tecnica è il degrado della
qualità di campionamento. Se infatti voglio eseguire due suoni a 10\,000~Hz
dovrò attivare il canale per 20~KHz, dato che il numero di campioni e il
periodo raddoppia; questa tecnica la esclusi subito, giacché non bastava un
semplice interscambio per ingannare l'orecchio (si manifestavano terribili
fischi), per cui la massima frequenza di campionamento utilizzabile si
riduceva a un quarto (il che significava passare dagli attuali 28~KHz a soli
7~KHz!).

La seconda tecnica, averaged, consiste nella somma algebrica delle forme
d'onda; per far entrare il risultato negli 8 bit occorre quindi dimezzare
l'ampiezza delle due onde andando incontro al problema della diminuzione
della qualità dinamica. Scelta questa tecnica come soluzione del problema
rimaneva il limite delle 8 voci: volevo realizzarne di più, ma come fare,
dato che Oktalyzer che sfruttava la stessa tecnica e appieno il 68000
riusciva a ottenerne massimo 8? Semplice: non far effettuare le somme alla
CPU; ma a chi allora?

E qui nacque il lampo di genio: ma al Blitter, e a chi altri se no? Ma anche
questo sembrava un vicolo cieco, dato che il Blitter è in grado di effettuare
delle semplici operazioni logiche e non aritmetiche.

Dato che non mi arrendo facilmente iniziai a eseguire delle prove con
svariate funzioni logiche; dopo una settimana, qualche notte insonne e
centinaia di foglietti pieni di numeri binari arrivai alla soluzione: la
combinazione di ben 8 operazioni logiche per effettuare la somma che quindi
poteva e doveva essere calcolata dal Blitter; così è nata OperBlitting, la
tecnica per effettuare le operazioni aritmetiche con il Blitter.


\sez{OperBlitting}


L'implementazione di questa tecnica va realizzata quando si hanno una serie
di valori da sommare (non vale la pena utilizzare 10 istruzioni per pilotare
il Blitter quando si devono sommare solo due numeri); ciò potrebbe diminuire
i possibili utilizzi di questa tecnica, ma basta pensare oltre
all'utilizzo già accennato (creazione delle 16 voci) anche ad altre
possibili applicazioni (animazioni 3D dove vi sono una serie di coordinate
su cui operare) per rendere questo problema minimo. Per dare la possibilità
a tutti gli sviluppatori di poter utilizzare questa tecnica ho realizzato
una libreria comprendente diverse procedure che permettono di effettuare
operazioni come somma, differenza e prodotto sfruttando il semaforo per
l'utilizzo del Blitter, rispettando così il sistema operativo, senza
bloccare eventuali altre operazioni e mettendosi in coda per essere eseguite
lasciando così piena libertà al microprocessore. Attenzione: per utilizzare
OperBlitting occorre aprire la ¶{graphics.library} (qualsiasi versione), giacché
viene utilizzata una funzione di questa libreria.

Le procedure a disposizione della libreria sono:

\smallskip\noindent {\bf Num = GetNumOper()}
restituisce il numero dell'ultima operazione completata;
serve per poter stabilire quando un'operazione OperBlitting sia
effettivamente finita nel caso si voglia far uso dei risultati; infatti, una
volta chiamata una procedura OperBlitting, questa ritorna subito il controllo
al programma chiamante (inserisce semplicemente la richiesta con il puntatore
alla coda di sistema tramite la funzione ¶{QBlit()}); per fortuna,
Exec, prevedendo questi casi, ha dato la possibilità di chiamare
automaticamente e in maniera trasparente una routine alla fine
dell'operazione del Blitter, che si occupa di incrementare di 1 la variabile
di conteggio; ed è proprio tale valore che viene restituito dalla funzione
¶{GetNumOper()}. Quindi dovrete memorizzare il valore restituito da tale
funzione o comunque settarne uno nuovo per sapere quale valore controllare
per determinare la terminazione delle operazioni. Ad esempio:

\begin{verbatim}

ULONG num;
  .
  .
num = GetNumOper();
/* Chiamata procedura */
/* OperBlitting */
/* fai quello che vuoi */
while(GetNumOper() == num);
/* attendi che sia ultimata */
/* l'operazione */
/* elaborazione dati */
/* risultati */

\end{verbatim}

\smallskip\noindent {\bf SetNumOper(Num)}
cambia il valore del contatore prima spiegato; rivediamo
l'esempio di prima sfruttando stavolta anche questa procedura:

\begin{verbatim}

SetNumOper(0);
/* Chiamata procedura */
/* OperBlitting */
/* fai quello che vuoi */
while(GetNumOper() == 0);
/* attendi che sia */
/* ultimata l'operazione */
/* elaborazione dati */
/* risultati */

\end{verbatim}

\smallskip\noindent {\bf Blitt\-Add (Buff\-Call, Op1, Op2, OpD, Num\-Words,
Len\-Word, Mod\-Op1,
Mod\-Op2, Mod\-OpD)}
realizza la somma con il Blitter. Esaminiamo i diversi
parametri. Per ¶{BuffCall} va precisato un discorso che sarà valido per tutte le
operazioni OperBlitting: infatti alla chiamata di ¶{QBlit()} (la funzione che
inserisce una procedura per l'utilizzo del Blitter in coda) viene
passata una struttura dati (che necessita alla funzione) con in più, in coda,
dati per la regolazione corretta del Blitter; occorre quindi fornire un buffer
(il cui puntatore è contenuto in ¶{BuffCall}) che dovrà contenere tali
dati (attenzione: il buffer risulterà utilizzato per tutta
l'esecuzione dell'operazione e quindi non sarà immediatamente
utilizzabile al ritorno della procedura, ma solo quando l'operazione sarà
effettivamente conclusa; occorre controllare, come già visto); la
dimensione del buffer varia da operazione a operazione; viene comunque
definita nel file di inclusione una costante ¶{SOMMSPACE} che contiene lo spazio
occupato per una singola operazione Blitter; i diversi quantitativi verranno
indicati da operazione in operazione. Nel caso di ¶{BlittAdd()} vale
$¶{SOMMSPACE}*n$, dove $n$ è il numero di bit di cui si compone la parola da
sommare (nel caso di byte abbiamo $¶{SOMMSPACE}*8$).

¶{Op1} e ¶{Op2} sono i puntatori ai due buffer contenenti gli operandi da sommare e
¶{OpD} è il puntatore al buffer che conterrà i risultati alla fine
dell'operazione; ¶{Op1}, ¶{Op2} e ¶{OpD} devono essere zone di memoria
differenti (non può essere utilizzato, ad esempio, lo stesso buffer per ¶{Op2} e
¶{OpD} perché i valori in ¶{Op2} non servono più). ¶{NumWords} indica il numero di
parole da sommare (con parola si intende valore che può essere costituito da
uno o più byte), e può valere al massimo 32768; per un numero maggiore di
valori da sommare bisogna quindi effettuare più chiamate consecutive. ¶{LenWord}
indica di quanti byte è composta la parola, che può valere 1 o un numero
pari per un massimo di 4096 (quindi parole fino a 32768 bit); attenzione
però che al raddoppiare della lunghezza della parola il tempo di calcolo si
riduce a un quarto.

¶{ModOp1}, ¶{ModOp2} e ¶{ModOpD} indicano il modulo, ossia lo spazio tra un valore e
l'altro nei buffer dei rispettivi dati; tale valore deve essere pari e
indica il numero di byte; nel caso $¶{LenWord} = 1$ (ossia si sommano byte) il
modulo indica lo spazio tra ogni parola del buffer (vale a dire due byte
consecutivi si sommano, si salta il modulo e si sommano altri due byte
ecc.).

\smallskip\noindent {\bf Blitt\-Complement (Mem\-Use, Op1, Op2, BufL, Num\-Words,
Len\-Word)}
serve per effettuare il complemento a 2 dei valori contenuti in ¶{Op1}, rendendo
così possibile il calcolo della differenza in accoppiata con ¶{BlittAdd()}; ¶{Op2}
conterrà il risultato dell'operazione e ¶{BufL} è il puntatore a un buffer
utilizzato per operazioni di transizione per l'esecuzione del complemento, e
deve essere della stessa grandezza di ¶{Op1} e ¶{Op2}; il numero di parole è
indicato in ¶{NumWords} e la loro lunghezza è ¶{LenWord}; per ¶{MemUse} valgono le
considerazioni precedenti e la dimensione equivale a $¶{SOMMSPACE}*n$ (dove
$n$ è il numero di bit della parola); anche qui se la grandezza della parola
raddoppia, il tempo di calcolo si riduce a un quarto.

\smallskip\noindent {\bf Blitt\-Mul (Mem\-Use, Op1, Op2, BufL, Num\-Words, Len\-Word)}
permette di effettuare i prodotti tramite il Blitter; in questo caso però i
dati nei buffer dovranno essere disposti in maniera particolare. Nel caso dei
primi operandi (quelli contenuti nel buffer di indirizzo ¶{Op1}) questi dovranno
essere inseriti nella seguente forma: op, val, op, val,$\ldots$, dove op è l'operando
effettivo per il calcolo del prodotto e val è un valore inutilizzato (può
essere un qualsiasi numero); tutto ciò è dovuto a un fatto di allineamento
dei dati. I secondi operandi, contenuti nel buffer ¶{Op2} dovranno invece avere
questa forma: val, op, val, op$\ldots$.
Stavolta il buffer inizia con un valore non utilizzato, poi continua con un
operando (che andrà moltiplicato con il primo di ¶{Op1}) e così via; attenzione
però, che i val del secondo operando devono necessariamente essere impostati
a 0; ¶{BufL} è un buffer di lavoro (sempre di memoria Chip) che deve avere
grandezza pari a $¶{NumWords}*¶{LenWord}*6$; ¶{NumWords} indica il numero di parole di
cui effettuare il prodotto e ¶{LenWord} indica di quanti byte è composta la
parola secondo le modalità già viste, ma deve essere maggiore di 1 (quindi
solo multipli di due, per il prodotto di byte è presente una procedura
apposita); ¶{MemUse} vale in questo caso $¶{SOMMSPACE}*(2*n^2+6*n)$.


\smallskip\noindent {\bf Blitt\-Word\-Shift\-Op1 (Mem\-Use, Op1, Op2, Num\-Words,
Len\-Word)},
{\bf Blitt\-Word\-Shift\-Op2 (Mem\-Use, Op1, Op2, Num\-Words, Len\-Word)}.
Per venire incontro al problema della strana sistemazione degli operandi, ho
inserito altre 2 routine nella libreria (anche queste funzionanti
completamente con il Blitter); la prima sistema gli operandi ¶{Op1} e la seconda
gli operandi ¶{Op2}. I parametri per tutt'e due sono: ¶{MemUse} che vale
$¶{SOMMSPACE}*2$, ¶{Op1} che contiene gli operandi in forma consueta, inseriti
consecutivamente, ¶{Op2} che conterrà gli operandi nella giusta forma richiesta
dall'operazione di moltiplicazione, ¶{NumWords} che indica quanti sono gli
operandi e ¶{LenWord} la loro lunghezza in byte. Come prima accennato, vi sono i
duali delle tre procedure appena viste per il calcolo del prodotto su byte:

\smallskip\noindent {\bf Blitt\-Mul\-Byte (Mem\-Use, Op1, Op2, BufL, Num\-Bytes)},
dove vale tutto quanto già detto
eccetto che per ¶{MemUse} che vale $¶{SOMMSPACE}*144$ e ¶{BufL} che deve essere lungo
$¶{NumBytes}*2$ e ¶{NumBytes} indica il numero di byte di cui fare il prodotto.

\smallskip\noindent {\bf Blitt\-Byte\-Shift\-Op1 (Mem\-Use, Op1, Op2, Num\-Bytes)},
{\bf Blitt\-Byte\-Shift\-Op2 (Mem\-Use, Op1, Op2, Num\-Bytes)},
in cui il significato e il valore dei parametri non cambia.

\sez{Vick Music}

Una volta ideato OperBlitting, pensai, sarà una passeggiata realizzare
qualcosa di funzionante, basta implementare un player e un programma per
l'editing tipo ProTracker ed è fatta; bene, il semplice fatto che ci abbia
impiegato 2 anni (iniziai alla fine del 1992, quando possedevo ancora l'\amiga{} 500,
anche se ho lavorato nei ritagli di tempo) per lo sviluppo di Vick Music vi
può far capire che non sia stato proprio facile; e infatti fu molto peggio:
solo per sviluppare il codice del player ho dovuto cambiare completamente
direzione ben 4 volte perché i risultati non erano soddisfacenti

Ma il piccolo codice del player (quasi 300~K di sorgente in Assembly) mi ha
dato i più grandi grattacapi alla sua ultimazione; infatti quando pensavo
oramai alla gloria e al successo (funzionava perfettamente sul 1200) fui
riportato improvvisamente con i piedi per terra al momento del test su 4000:
blocco completo della macchina. Dopo un mese di revisione in cui
migliorai notevolmente il codice, tant'è che su \amiga{} 1200 le 16 voci venivano
implementate quasi alla massima qualità, rieffettuando il test capii quale
era stato l'inconveniente: con questo particolare codice il 4000 andava più
lento del 1200 (?!?).

Tale problema è dovuto probabilmente ai tempi di
accesso degli integrati della memoria che, su quelli montati nel 1200, sono
inferiori rispetto a quelli del 4000; ciò fu poi confermato con un test di
velocità (un piccolo programma che effettuava una serie di somme e
restituiva il tempo impiegato), ma che avevo avuto modo di osservare con i
test AIBB per controllare le caratteristiche di un espansione di memoria (il
MemTest girava più veloce su A1200 con semplice memoria fast che non su
4000: non si finisce mai di imparare).

Non solo, il piccolo programma dell'editing non si mostrò più essere tale,
dato che dopo un po' iniziava a possedere anche lui caratteristiche tutte
particolari. Vick Music infatti è basato sulla filosofia dei pattern (come
ProTracker e svariati altri player sul mercato), solo che i suoi pattern
sono composti da una sola voce, per cui viene inserito un nuovo livello di
editing, le section in cui vengono indicati a quali voci effettive
associare i pattern e quando suonarli; l'idea di questa modifica è nata
utilizzando gli altri editor musicali e in particolare la domanda che mi
ponevo era: perché visto che ho già inserito la batteria e questa non
cambia, la devo reinserire ogni volta che creo un pattern?

Invece, in questo nuovo ambito basta inserire il pattern della batteria una
volta sola e segnarlo nelle section ogni qualvolta occorre eseguirlo; non
solo: ogni pattern può avere delle particolari caratteristiche che indicano
al player cosa fare quando la sua esecuzione giunge al termine; lo script
(così si chiama) può dire al player di ripetere lo stesso pattern più volte,
di saltare all'esecuzione di un altro pattern o tutte e due le cose insieme.

Il programma risulta essere composto di 4 ambienti: Edit Song, Edit Pattern,
Edit Instrument e Play Song; il primo serve per effettuare l'editing di una
musica inserendo i codici dei pattern nelle section relative a quest'ultima;
Edit Pattern permette di creare i pattern; Edit Instrument serve per la
gestione dei suoni (raw o IFF), il loro campionamento e operazioni varie di
risintesi; Play Song è una schermata con poche opzioni e molti vumetri per
ascoltare ma anche vedere la musica. Il programma nel complesso può essere
migliorato (il file requester ad esempio è un po' antico) ma si tratta pur
sempre di un programma di dominio pubblico; penso di produrre una versione
2.0 notevolmente migliorata e rilasciarla nel '95.

\finearticolo
