LINGUAGGI

CAPIRE E PILOTARE IL SUONO DI AMIGA
Ovvero il suono tra le dita


di Mr. Lambda


   Disporre nel dispositivo audio di Amiga per capire come questo straordinario audio device lavori, e quindi sfruttare la capacità manipolatoria del Forth per creare

   Ecco l'occasione per trasformare bn incontro in un avvenimento determinante. E noi non vogliamo perderla. Come avrete già capito inizieremo subito Forth ... issimo, ma siate sempre pronti al peggio. In ogni senso. Il Forth, infatti, è un linguaggio che offre, come pochi, ampie capacità di manipolazione e interattività, ma richiede pure, come pochi, spiccate doti di creatività. Qualsiasi cosa si voglia, bisogna essere innanzitutto capaci di progettarsela e realizzarsela da sè. Il programma che accompagna questo articolo è una valida dimostrazione di ciò. Insomma, la programmazione all'insegna dell'inventività e del bricolage. Ma credeteci, si può arrivare davvero molto lontano. E intanto incominceremo dal suono,

   Il metodo della riproduzione del suono utilizzato da Amiga è simile a quello utilizzato dai Compact Disc che molti di voi ormai possiederanno. Anche se l'audio di Amiga non può essere paragonato con la fedeltà dei CD, la qualità del suono che può produrre risulta coxunque impressionante. Ciò che limita le prestazioni di Amiga, se lo paragoniamo con il Compact Disc, è la frequenza di campionamento e il numero di bit a disposizione per campione. Infatti un disco digitale ha una risposta in frequenza che raggiunge i 20 kilohertz, mentre l'Amiga è limitato teoricamente a circa 14 kilohertz, e praticamente consente una riproduzione corretta fino a 7,5 kilohertz cioè alla metà della cifra teorica precedente. Inoltre, come si diceva, un Compact Disc utilizza più bit per rappresentare ciascun punto della forma d'onda campionata e quindi ha un più ampio spettro dinamico.

   Vediamo ora come l'Amiga produca effettivamente un suono. Innanzituto il suono che deve venire riprodotto deve essere definito come una serie di numeri che rappresentano il livello di voltaggio della forma d'onda campionata a determinati intervalli. I diversi voltaggi rappresentati da quei numeri devono quindi essere filtrati, amplificati, e inviati a uno o più altoparlanti per produrre finalmente il suono desiderato. In questa maniera è possibile creare qualsiasi suono concepibile e inconcepibile all'interno, naturalmente, dei limiti imposti dalla frequenza a cui la forma d'onda può venire campionata, che ne determinerà la risposta in frequenza, con il numero di bit disponibili per ciascun campione, che ne determinerà la quantità di rumore nel segnale e la dinamica, e, infine, con la memoria che abbiamo a disposizione. Naturalmente per una più dettagliata comprensione della produzione del suono in Amiga vi suggeriamo di consultare la sezione riguardante l'audio nell'HARDWARE REFERENCE MANUAL.

Come tutto è iniziato ...

Innanzitutto precisiamo che per la scrittura e il collaudo del programma che vi andiamo a presentare abbiamo utilizzato il Multi-Forth della Creative Solutions perché più facilmente reperibile e più completo. Le versioni più recenti di questo prodotto, tra le altre meraviglie, supportano anche un driver audio, rendendo quindi il tutto ancor più appetibile. Ma il Multi-Forth in nostro possesso, che è di gran lunga una versione più antica, per l'audio di Amiga non supporta alcunché. Ci siamo decisi a presentarvi questo programma innanzitutto perché rappresenta comunque un'ottimo esempio di applicazione del Forth alla soluzione di un problema fondamentale come quello del suono in Amiga e ne traccia in modo chiaro i passaggi e le possibilità permettendovi i più ampi sviluppi e miglioramenti, ma soprattutto perché non sarete certamente in molti a possedere le più recenti versioni del Multi- Forth e morirete, come noi, dalla voglia di poter controllare il suono di Amiga subito. E veniamo ai fatti. Dobbiamo incominciare con l'ammettere che realizzare il programma non è stato semplice e immediato come ci aspettavamo, ma eccovi il resoconto della nostra avventura.

   Come ormai sapete, il nostro Multi-Forth non supportava alcunché per l'audio e non c'era alcun esempio nel manuale che ci potesse assistere. Insomma, possiamo dichiarare che non una parola del vocabolario a disposizione ci permetteva di fare alcunché con il suono! Ma questo è Forth! E se qualcosa vi è necessario, dovete costruirvela, diciamo bene?

   Quasi subito fummo in grado di capire che avevamo bisogno di una funzione chiamata nei manuali BeginlO. I manuali indicavano anche che la funzione si aspettava come argomento l'indirizzo di una struttura IORequest. Ma nulla di tutto questo esisteva nel nostro Multi-Forth. E a nulla sono servite le ricerche dettagliate nel nostro manuale Multi-Forth: non c'era nulla, neanche qualcosa che potesse richiamare indirettamente questo concetto. La cosa che più ci ispirava era la funzione CallIO, ma questa non era chiaramente documentata, anzi non era documentata affatto, se non per una breve menzione nel glossario del manuale. Prima di incazzarci definitivamente con il nostro Multi-Forth, abbiamo considerato il fatto che non c'era alcuna documentazione sulla funzione Begin10 nemmeno negli AMIGA REFERENCE MANUAL. Il solo riferimento che avevamo potuto trovare era nella appendice 6 dello EXEC MANUAL. Esso consisteva semplicemente in questa macro in linguaggio assembler:

_BeginIO:

move.l 4(sp),a1
BEGINIO
rts


   Ma non ci è stata di molto aiuto. Per farla breve abbiamo contattato tutti coloro che sembravano disposti a darci una mano e alla fine abbiamo deciso che il solo modo di risolvere questo problema era di arrangiarsi.

   Ci siamo procurati una copia di Metascope - il debugger simbolico della Metadign - e un compilatore C, abbiamo compilato un programma demo che riguardava l'audio di Amiga, e quindi abbiamo incominciato a curiosare. Quasi immediatamente abbiamo individuato la famigarata funzione BeginlO e l'abbiamo disassemblata.

Eccola:

_BeginIO:

movea.l 4(a7),a1
m0ve.l  a6,-(a7)
movea.l $14(a1),a6
jsr     $-lE(a6)
movea.l (a7)+ ,a6
rts


    Quando assegnamo come parametro l'indirizzo di una struttura IORequest, la funzione BeginIO dapprima preleva da quella struttura un puntatore a un device node, cioè ad un nodo del dispositivo software dell'audio, quindi utilizza quel puntatore come offset alla base di una jump table o tavola di salti nella memoria bassa efinalmente da là salta al ROM Kernel. Che cosa ci potrebbe essere di più semplice, eh?

   Una delle difficoltà che si possono incontrare nella traduzione di routine in Forth risiede nel differente modo in cui C e Forth passano i parametri. Perché tutto funzioni a dovere in Multi-Forth, la prima istruzione deve infatti essere: 

movea.l (a7)+,a1

che muove l'indirizzo della struttura IORequest dallo stack dati del Multi-Forth (A7 è lo stack pointer) nel registro Al. E ancora lo RTS deve essere sostituito da:

move.w  (a2)+,d3
jmp     $18(a6,d3.w)

che è la macro per il NEXT in Multi-Forth. Nel listato che accompagna questo articolo voi troverete un'altra definizione CODE per la funzione NewList. Naturalmente è possibile scrivere questafunzione anche direttamente in Forth, ma non ci siamo potuti trattenere dalla tentazione di continuare il lavoro iniziato con BeginlO! Per evitarvi la fatica di caricare l'assembler vi proponiamo una versione delle due word che inserisce i valori in codice macchina esadecimale direttamente nel vostro dizionario. Ma a questo punto, era anche necessario implementare la chiamata OpenDevice della libreria Exec (che, per fortuna, è documentata nella nostra versione di Multi-Forth) per poter fornire a BeginlO un puntatore al device node. L'implementazione delle altre chiamate Exec è stata più semplice, e non si sono incontrati grossi problemi.

   Un altro lavoro preparatorio riguardava la definizione delle strutture dati richieste dal device audio. Vediamone dapprima la versione in C e la versione in Assembler che ne danno i manuali e quindi la traduzione che ne abbiamo fatto in Forth. Vi facciamo presente che le recenti versioni del Multi-Forth includono tutte le strutture disponibili presentate dai AMIGA REFERENCE MANUAL, ma la versione in nostro in C:

struct Message
{
  struct Node mn_Node;
  struct MsgPort *mn_ReplyPort;
  UWORD mn_Length;
}

   E ora vediamone la versione in Assembler:

STRUCTUREMN,LN_SIZE
   APTR MN_REPLYPORT
   UWORD MN_LENGTH
   LABEL MN_SIZE

   E finalmente presentiamo la nostra versione
in Forth:

structure Message
    LinkNode struct:+mnNode
    ptr:+mnReplyPort
    short:+mnLength
structure.end

   E ora interessiamoci della struttura IORequest, che, come potrete vedere, presenta delle particolarità. Innanzitutto presentiamo le due strutture utilizzabili in C:

struct IORequest
{
   struct Message io_Message;
   struct Device *io_Device;
   struct Unit *io_Unit;
   UWORD io_Command;
   UBYTE io_Flags;
   BYTE io_Error;
}

struct IOStdReq
{
   struct Message io_Message;
   struct Device *io_Device;
   struct Unit *io_Unit;
   UWORD io_Command;
   UBYTE io_Flags;
   BYTE io_Error;
   ULONG ioActual;
   ULONG io_Length;
   APTR io_Data;
   ULONG io_Offset;
}

   E ora vediamo come queste strutture vengano tradotte in Assembler, considerando semplicemente la struttura IOquest un sottoinsieme della struttura IOStdReq:

STRUCTURE   IO,MN_SIZE
   APTR     IO_DEVICE
   APTR     IO_UNIT
   UWORD    IO_COMMAND
   UBYTE    IO_FLAGS
   BYTE     IO_ERROR
   LABEL    IO_SIZE
   ULONG    IO_ACTUAL
   ULONG    IO_LENGTH
   APTR     IO_DATA
   ULONG    IO_OFFSET
   LABEL    IOSTD_SIZE

   Per adeguare queste strutture al Forth si adotta il medesimo concetto visto nella precedente traduzione Assembler anche se la sintassi chiaramente ne differisce: 
structure IOStdReq
    structure IORequest
        Message struct: + ioMessage
              ptr: + ioDevice
              ptr: + ioUnit
            short: + iocommand
             byte: + ioFlags
             byte: + ioError
structure.end     IORequest +
             long: + ioActual
             long: + ioLength
              ptr: + ioData
             long: + ioOffset
structure.end

   Consideriamo brevemente il costituirsi di questa struttura e la soluzione programmativa che le permette di operare correttamente. La word STRUCTURE crea una nuova word, fondamentalmente una costante, e lascia l'indirizzo del campo parametri (parameter field) di quella word e uno zero nello stack. Lo zero è un valore fittizio in cui viene accumulata la dimensione della struttura. Quando STRUCTURE. END viene incontrata la dimensione accumulata della struttura viene memorizzata all'indirizzo del campo parametri della word. Come risultato di queste operazioni, quando viene eseguito il nome della struttura, ne viene restituita la dimensione. Dal momento che il nome della struttura ritorna la sua dimensione, l'inserzione di ' IORequest + ' alla fine della struttura IORequest aggiunge I'offset appropriato perché + ioActual venga eseguita correttamente. 
   E finalmente consideriamo il template della struttura dati utilizzata dal device audio (audio.device). Essa include una struttura IORequest seguita da informazioni riguardanti la locazione e la lunghezza della tavola dati del suono, il periodo e il volume della forma d'onda e il numero di volte che il suono deve essere ripetuto. Incominciamo considerandone la struttura in C: 
struct Audio
{
    struct    IORequest ioa_Request;
    WORD      ioa_AllocKey;
    UBYTE     * ioaData;
    ULONG     ioa_Length;
    UWORD     ioa_Period;
    UWORD     ioa_Volume;
    UWORD     ioa_Cycles;
    struct    Message ioa_WriteMsg;
}
 

Ecco la versione in sintassi Assembler:
 

STRUCTURE Audio,lO-SIZE
WORD   ioa_AllocKey
APTR   ioa_Data
ULONG  ioa_Length
UWORD  ioa_Period
UWORD  ioa_volume
UWORD  io_acycles
STRUCT io_aWriteMsg,MN_SIZE
LABEL  io_aSlZEOF
 

E quella in Multi-Forth:
    

structure IOAudio
IORequest struct: + ioaRequest
           short: + ioaAllocKey
             ptr: + ioaData
            long: + ioaLength
           short: + ioaPeriod
           short: + ioaVolume
           short: + ioaCycles
  Message struct: + ioaWriteMsg
structure.end

   Tutte le costanti che sono comandi del device e che trovate nel listato sono spiegate dettagliatamente negli AMIGA REFERENCE MANUAL. 
   Vi abbiamo messo a disposizione, traducendole in sintassi Forth, le funzioni Createporto e DeletePortO che sono disponibili in C e che si rivelerrano molto utili. CreatePort richiede e inizializza una message port che vi mette a disposizione un meccanismo per la comunicazione tra differenti task e interrupt. Una porta puo' essere sia pubblica, cioè con un nome e conosciuta al resto del sistema sistema: 

0" nome" priorità CreatePort

   Oppure privata, e cioè senza nome e
non conosciuta al resto del sistema:

0 priorità CreatePort

   Nel caso dell'audio device noi abbiamo utilizzato una porta privata e senza nome dal momento che nessun altro task nel sistema aveva necessità di sapere della sua esistenza. 
   Noi ci scusiamo fin d'ora se non riusciremo a soddisfare ogni vostro dubbio, ma il metodo migliore per comprendere a fondo questo nostro programma è studiarlo consultando opportunamente gli AMIGA REFERENCE MANUAL. E non trascurate lo EXEC REFERENCE MANUAL anche se all'inizio vi può sembrare ostico e difficile de comprendere. Noi abbiamo trovato il manuale veramente chiaro sotto molti punti di vista, anche se naturalmente necessita di essere letto molte volte per riuscire ad afferrare alcuni punti più complessi. A ogni successiva lettura ci siamo accorti che ogni cosa ci riusciva più chiara e conseguente. 

Ed è diventato suono

   Il listato che vi presentiamo vi permette di generare semplici forme d'onda all'interno di un limitato ambito di frequenze. Vi forniamo le tavole dei dati per I'onda sinusoidale (Sine), I'onda quadra (Square), I'onda triangolare (Triangle) e I'onda a dente di sega (Sawtooth) che possono produrre buoni risultati tra i 1200 e i 2400 Hz. Anche frequenze tra 600 e 1200 Hz possono essere riprodotte con buoni risultati anche se ciò non è strettamente in accordo con quanto enunciato nel manuale dello Hardware. Le frequenze al di fuori di questi limiti produranno suoni distintamente distorti. Ciascuna tavola di dati audio consiste di dodici campioni che descrivono un ciclo completo. Le frequenze più basse richiederebbero unatavola di dati più lunga, e le frequenze più alte meno campioni. Per creare un suono continuo basta che puntiate il DMA audio alla tavola dei dati del- I'onda e gli comunichiate quante volte desiderate ripeta i dati. Un ciclo è sufficiente per generare un suono continuo. Il DMA si riposiziona automaticamente all'inizio dell'array dei dati ogni voltache raggiunge la sua fine e ripete questa operazione fino a quando ha riletto I'array per il numero di volte che avete specificato. E adesso vi diamo alcune spiegazioni che riguardano la word Hz. Il manuale dello Hardware indica un metodo per determinare adeguatamente la frequenza di campionamento necessaria a produrre una data frequenza da un precostituito numero di campioni che noi chiaramente non abbiamo seguito. Questo metodo è basato sul periodo del timer audio e il periodo della frequenza desiderata, ma è molto più semplice utilizzare le frequenze per eseguire i calcoli necessari in modo da evitare reciproci o calcoli con virgola mobile. Ecco l'equazione: 

Frequenza_di_Campionamento = AudioClock / (Frequenza_Desiderata * Campioni)

Dove Frequenza_di_campionamento è il numero di tick che il clock dell'audio deve aspettare prima di prelevare un campione, la frequenza di AudioClock è 3,579546 Mhz, e Campioni è il numero di campioni che devono essere riprodotti in un periodo della forma d'onda. La word Hz calcola il periodo richiesto per generare una determinata frequenza da una tavola di dodici campioni e memorizza quel valore in una variabile chiamata Period per servirsene successivamente. Ora parleremo delle altre word descrivendo l'azione della word Wave. La prima cosa che Wave fa è tentare di aprire unA port audio. La word AudioPort? ci restituisce l'indirizzo della port allocata dalla word CreatePort, se, naturalmente questa ha successo, e il flag O se questa non ha successo. Se il tentativo di creare una message port non ha successo Wave termina con il messaggio "La port del device audio non è stata aperta."
    Se la message port è stata aperta con successo Wave continua inizializzando dapprima la struttura ioaRequest della struttura IOAudio sound con i valori richiesti dalla chiamata di sistema OpenDevice. 
 
 

\
\
\  AudioDemo
\
\

anew AudioDemo

\  Ecco il codice Assembler di cui 
\  abbiamo bisogno per includere 
\  le funzioni BeginIO e NewList
\  nei nostri programmi.

\ CODE BeginIO ( IORequest -- )
\   SP )+ A1 LONG MOVEA,
\   A6 A7 -) LONG MOVE,
\   20 A1 I) A6 LONG MOVEA,
\   -30 A6 I) JSR,
\   A7 )+ A6 LONG MOVEA,
\   NEXT END-CODE

HEX  \  e per evitarvi la fatica 
     \  di caricare l'Assembler,
     \  eccovi una versione 
     \  alternativa.

CREATE BeginIO ( IORequest -- )
  -4 ALLOT 225F w, 2F0E w, 
   2C69 w, 0014 w, 4EAE w,
   FFE2 w, 2C5F w, 361A w,
   4EF6 w, 3018 w, 

\ CODE NewList ( list -- )
\   SP )+ A0 LONG MOVEA,
\   A0 A0 () LONG MOVE,
\   A0 () 04 LONG ADDQ,
\   4 A0 I) LONG CLR,
\   A0 8 A0 I) LONG MOVE,
\   NEXT END-CODE

CREATE NewList ( list -- )
  -4 ALLOT 205F w, 2088 w,
   5890 w, 42A8 w, 0004 w,
   2148 w, 0008 w, 361A w,
   4EF6 w, 3018 w,

DECIMAL

: AllocMem 
( dimensione_in_byte\caratteristiche
  -- blocco_di_memoria )
  !d1 !d0 exec@ 33 ;

: FreeMem
( blocco_di_memoria\dimensione_in_byte
  -- )
  !d0 !a1 exec 35 ;

: AllocSignal 
( num_segnale -- num_segnale )
  !d0 exec@ 55 ;

: FreeSignal ( num_segnale -- )
  !d0 exec 56 ;

: AddPort ( port -- ) 
  !a1 exec 59 ;

: RemPort ( port -- )
  !a1 exec 60 ;

: OpenDevice 
( nome_dev\unita'\ioRequest\flag 
  -- errore )
  !d1 !a1 !d0 !a0 exec@ 74 ;

: CloseDevice ( ioRequest -- )
  !a1 exec 75 ;

: WaitIO ( ioRequest -- error )
  !a1 exec@ 79 ;

SYMTABLE DEFINITIONS

4 wconstant NT_MSGPORT

0 wconstant PA_SIGNAL

structure Message
  LinkNode struct: +mnNode
              ptr: +mnReplyPort
            short: +mnLength
structure.end

1 0  scale wconstant MEMF_PUBLIC
1 16 scale constant  MEMF_CLEAR
 

\ osservate bene ora un esempio
\ di struttura nidificata

structure IOStdReq
  structure IORequest
    Message struct: +ioMessage
               ptr: +ioDevice
               ptr: +ioUnit
             short: +ioCommand
              byte: +ioFlags
              byte: +ioError
  structure.end  IORequest +
              long: +ioActual
              long: +ioLength
               ptr: +ioData
              long: +ioOffset
structure.end

1 wconstant IOF_QUICK
3 wconstant CMD_WRITE
0" audio.device" constant AUDIONAME
1 4 scale wconstant ADIOF_PERVOL

structure IOAudio
  IORequest struct: +ioaRequest
             short: +ioaAllocKey
               ptr: +ioaData
              long: +ioaLength
             short: +ioaPeriod
             short: +ioaVolume
             short: +ioaCycles
    Message struct: +ioaWriteMsg
structure.end

FORTH DEFINITIONS

\ Il formato adeguato per creare
\ una port senza nome e quindi 
\ privata e':
\ 0 pri CreatePort

: CreatePort ( 0" nome"\pri -- 
               MsgPort oppure 0 )
        LOCALS| pri name |
  -1 AllocSignal 

\ controlliamo innanzitutto se
\ abbiamo ottenuto un bit per il 
\ segnale

  dup -1 = not

\ e' gia' allocata la memoria per la 
\ nostra port ? Se no, facciamolo

IF MessagePort 
   MEMF_CLEAR MEMF_PUBLIC OR 
   AllocMem dup 0=

\ abituale controllo sul risultato
\ dell' allocazione . . .
\ in caso di risultato negativo
\ concludiamo inserendo nello stack 
\ uno zero o NULL

    IF DROP FreeSignal 0 

\ altrimenti ci decidiamo ad 
\ inizializzare la struttura
\ MessagePort

    ELSE 
      name       over +mpLinkNode 
                      +lnName !
      pri        over +mpLinkNode
                      +lnPPri C!
      NT_MSGPORT over +mpLinkNode
                      +lnType C!
      PA_SIGNAL  over +mpFlags C!
      swap       over +mpSigBit C!
      0 FindTask over +mpSigTask !
      dup name

\ se la port e' 'pubblica', cioe'
\ ha un nome, AddPort permette al 
\ resto del sistema di individuarla
\ e 0" name" FindPort ci restituira'
\ il suo indirizzo

  IF AddPort 
  ELSE +mpLinkHeader NewList THEN
    THEN
  ELSE drop 0 
  THEN ;

: DeletePort  ( port -- )
  LOCALS| port |
  port +mpLinkNode +lnName @
  IF port RemPort
    THEN
  255 port +mpLinkNode +lnType C!
  -1  port +mpLinkHeader +lhHead !
  port +mpSigBit C@ FreeSignal
  port MessagePort FreeMem ;

struct IOAudio sound
sound IOAudio ERASE

DECIMAL

CREATE ChannelMask 15 C,
\ in questo modo potremo utilizzare
\ tutti e quattro i canali 
\ disponibili

CREATE Samples 12 ,
\ la lunghezza dei dati del suono

CREATE Period 400 W,
\ intervallo che intercorre tra
\ un campione e il successivo
\ in tick del timer

CREATE Duration 400 W,
\ numero di volte che vogliamo far
\ ripetere il suono

VARIABLE >Sound
\ puntatore ai dati del suono

: :AudioData  ( nome ( -- ) 
  CREATE DOES> >Sound ! ;

  :AudioData Sine
    0 c,  63 c,  109 c,  
     126 c,  109 c,  63 c,
    0 c, -63 c, -109 c,
    -126 c, -109 c, -63 c,

  Sine  \ forma d'onda di default

  :AudioData Square 
     80 c,  80 c,  80 c, 
     80 c,  80 c,  80 c,
    -80 c, -80 c, -80 c,
    -80 c, -80 c, -80 c,

  :AudioData Triangle
     0 c,  42 c,  84 c,
   126 c,  84 c,  42 c,
     0 c, -42 c, -84 c,
  -126 c, -84 c, -42 c,

  :AudioData Sawtooth
     0 c,   25 c,  50 c,
    57 c,  100 c, 125 c,
  -125 c, -100 c, -75 c,
   -50 c,  -25 c,   0 c,

3579546 ( Hz ) CONSTANT AudioClock
\ intervallo di frequenza del timer

: Hz ( freq -- )
   Samples @ * AudioClock swap /
   Period W! ;

: FreeAudioPort ( -- )
   sound +ioaRequest +ioMessage
   +mnReplyPort @ DeletePort ;

: AudioPort? ( -- MsgPort oppure 0 )
   0 0 CreatePort dup 
   IF dup sound +ioaRequest
   +ioMessage +mnReplyPort !
   THEN ;

: AudioDevice? ( -- vero oppure falso )
   AUDIONAME 0 sound 0 OpenDevice NOT ;

: initSoundPort  ( -- )
   10 sound +ioaRequest +ioMessage
   +mnNode +lnPpri C!
   ChannelMask sound +ioaData !
   1 sound +ioaLength ! ;

: initSoundStruct ( -- )
   CMD_WRITE sound +ioaRequest
   +ioCommand W!
   ADIOF_PERVOL IOF_QUICK or sound
   +ioaRequest +ioFlags C! 
   >Sound    @ sound +ioaData    !
   Duration W@ sound +ioaCycles W!
   Samples   @ sound +ioaLength  !
   Period   W@ sound +ioaPeriod W!
   64          sound +ioaVolume W!
   ;

: Wave ( -- )
   AudioPort?
   IF initSoundPort
     AudioDevice?
     IF initSoundStruct
       sound BeginIO sound WaitIO
       drop sound CloseDevice 
     ELSE 
." Il device audio non e' stato aperto."
cr
     THEN FreeAudioPort
   ELSE
." La port audio non e' stata aperta. "
   THEN 

;

\ ed eccoci, finalmente, con un esempio,
\ all'utilizzo del programma:
\   300 Hz Sine Wave 

La priorità richiesta viene memorizzata nel campo +InPpri della struttura IOAudio. A proposito di + InPpri permetteteci un commento. Probabilmente a molti di voi sarà sembrato un errore o un refuso, dal momento che si riferisce a LN_PRI della relativa struttura in Assembler oppure a In_Pri della relativa struttura in C, e invece è proprio così. Ma tant'è. E dal momento che cosi appare nella nostra versione di Multi-Forth, noi siamo stati costretti ad accettarlo proprio così, nudo e crudo, e sbagliato. Quindi per non perdere il controllo del vostro sistema nervoso per un errore che vi sembra sciocco e impossibile, date un'occhiata alla word relativa che è presente nella vostra versione. Bene. E ora continuiamo. Si diceva della word Wave e delle sue successive inizializzazioni. Dopo la priorità, l'indirizzo di ChannelMask viene memorizzato nel campo +ioData, che richiederà per noi al sistema tutti quattro i canali. La mask per la richiesta dei canali consiste in un solo byte, ecosì + ioalength viene settato a uno.
   La word AudioDevice? tenta poi di aprire il device audio con una chiamata alla funzione della libreria EXEC OpenDevice e ritorna un flag vero se ha successo. Se la chiamata ha successo la struttura IOAudio viene riinizializzata affinché punti ai dati per la generazione della forma d'onda. Altrimenti l'esecuzione termina con il messaggio "Il device audio non è stato aperto." e quindi viene liberata la memoria allocata per la message port. Se I'apertura del device audio ha successo e la funzione OpenDevice ha memorizzato I'indirizzo del device node nel campo + ioDevice, l'indirizzo della struttura sound viene passato alla funzione BeginlO che abilita il DMA audio e produce il suono. WaitlO attende che il suono finisca, e CloseDevice abbandona il device audio. FreeAudioPort restituisce allo heap del sistema la memoria allocata per la message port.
La sintassi appropriata per generare un suono è:

1200 Hz Sine Wave 2000 Hz Square Wave

e cosi via. Naturalmente, una volta che la frequenza sia stata specificata utilizzando Hz, non è più necessario ripetere il comando se intendiamo generare un altro tipo di forma d'onda della stessa frequenza. In altre parole, una volta che il comando 2000 Hz viene eseguito, Triangle Wave oppure Sawtooth Wave genereranno le forme d'onda appropriate alla frequenza 2 kHz. E ancora, dopo che siano stati specificati la frequenza e i dati della forma d'onda, Wave può venire utilizzato nuovamente per rigenerare lo stesso tono.
   Bene, ora che la strada verso il controllo del suono in Forth è stata aperta non possiamo che augurarci che possiate proseguire nel migliore dei modi. Al prossimo incontro.  
 

Settembre 1988