Rubrica PROGRAMMAZIONE

MeepMeep!

L'ultima volta che ci siamo letti, abbiamo imparato a far emettere al nostro speaker i simpatici versi che credevamo fosse in grado di emettere solo una SoundBlaster. Oggi renderemo quei versi ancora più HiFi, ad un punto tale che molti di voi si chiederanno perchè mai hanno inventato le schede sonore...

di Francesco Sileno


NOTE
Anche per questo numero, i sorgenti completi sono nel file ASM_SRC.ZIP - 13Kb, compresso con PkZip 2.04g.
Qualcuno mi ha fatto notare che non fornisco indirizzi per reperire i vari testi/documentazioni che nomino. Il fatto è semplicemente che non li conosco nemmeno io, avendole reperite via FidoNet!
Tuttavia, con un qualsiasi motore di ricerca tipo AltaVista o FTP Search, non dovreste avere problemi...


PIT e Clock

Come prima cosa, vediamo in che modo utilizzare il timer 0, l'unico dotato di IRQ, per rendere la frequenza di riproduzione CPU-indipendent. E' immediata la necessità di riprogrammarlo ad una nuova velocità; il problema è che cambiando il timer 0 modifichiamo la anche velocità con cui il clock avanza: a parte l'orario di sistema sfasato, questo potrebbe confondere altri programmi che usano delle temporizzazioni. E comunque non è cosa buona a farsi.
Facciamo quindi un paio di considerazioni:
  • alla sua velocità abituale, il timer 0 viaggia a circa 18tick al secondo, con il contatore a 0x10000. Un tick ha durata circa 0.055s.
  • la nostra routine viaggerà a 22050Hz [1], con il contatore a 0x1234DD / 0x5622 = 0x36. Ogni tick dura circa 0.000045s.
  • per non alterare la velocità del clock, la nostra routine dovrà chiamare quella vecchia ogni 0.055 / 0.000045 = 1222 [2] tick.
  • quest'ultima affermazione la possiamo anche tradurre con:
    se il contatore originale doveva fare 0x10000 decrementi per ogni IRQ,
    se il nostro contatore ne deve fare solo 0x36 per ogni IRQ,
    ne segue che noi possiamo sommare ad una variabile il valore 0x36, fino a quando il valore di tale variabile non sia uguale o superiore a 0x10000, nel qual caso la decrementeremo di 0x10000 e provvederemo a chiamare il vecchio handler dell'INT8.
[1]Raddoppiamo la frequenza di riproduzione, per 'eliminare' il fischio a 11Khz che altrimenti si sovrappone al suono. Avete presente il teorema del campionamento e le sue conseguenze? No? Beh, manco io, però dicono che spieghi tutto...
[2]Ovviamente le approssimazioni sono molto... approssimate.

Tutto ovvio no?
In pratica:

PROC New_INT8    FAR
    pushf

    [...]

    add     [word ptr cs:clock_ticks],036h
    adc     [word ptr cs:clock_ticks+2],0
    cmp     [cs:clock_ticks],10000h
    jae     N_INT8_call_old
    push    ax
    mov     al,20h
    out     20h,al
    pop     ax
    popf
    iret
N_INT8_call_old:
    sub     [word ptr cs:clock_ticks+2],1
    sbb     [word ptr cs:clock_ticks],0
    popf
    jmp     [dword ptr cs:old_INT8_handler]

old_INT8_handler    dd 0
clock_ticks         dd 0
ENDP New_INT8
Poichè l'INT8 è collegato ad un dispositivo hardware, prima di uscirne dobbiamo segnalare al PIT di aver compiuto il nostro dovere: a questo servono le istruzioni di out sulla porta 0x20, istruzioni che dobbiamo dare noi a meno che non sia arrivato il momento di chiamare il vecchio handler, che sicuramente provvederà per conto suo.
Se invece di azzerare il contatore, gli sottraiamo il valore 0x10000, evitiamo di perdere dei tick residui che saltano fuori per motivi di approssimazione: così facendo raggiungiamo una precisione maggiore nel chiamare il vecchio handler, e questo non è affatto male.
Facendo bene i calcoli, vedrete che arrivati al JMP finale, lo stack e i lag si troveranno esattamente come si trovavano appena entrati nella procedura: il vecchio handler non si accorgerà nemmeno che ora è alle dipendenze di un player.

WAV Header

La struttura dell'header dei WAV l'ho estratta dalla PCGPE (PC Game Programming Encyclopedia), altro pacco utile e freeware reperibile su internet. Tuttavia ho dovuto correggere un paio di campi, raddoppiandone la dimensione: non avendo altre informazioni, posso supporre che l'autore abbia sbagliato, in ogni caso i MIEI file WAV sono tutti diversi da come lui li intende. In formato struttura assembler, ecco come appare:
STRUC WAV_Header
    rID             dd ? ; "RIFF"       ; RIFF Header
    rLen            dd ?
    wID             dd ? ; "WAVE"       ; WAVE Header
    fID             dd ? ; "fmt "       ; - Format Block
    fLen            dd ?
    wFormatTag      dw ?
    nChannels       dw ?
    nSamplePerSec   dd ?                ; WAS WORD!?!
    nAvgBytesPerSec dd ?                ; WAS WORD!?!
    nBlockAlign     dw ?
    FormatSpecific  dw ?
    dID             dd ? ; "data"       ; - Data Block
    dLen            dd ?
ENDS WAV_Header
Di tutto ciò, quello che interessa noi è il campo dLen, ovvero la lunghezza del campionamento in byte. In più, possiamo usare wID per verificare che il file sia un WAV.

NOTA:
il formato RIFF, se non erro, doveva definire uno standard per i file multimediali (video, audio, etc.). Il bello è che la MicroSoft, nella sua infinita saggezza, ha messo in cima la sigla 'RIFF', inventanodosi poi il resto secondo l'istinto.


Al lavoro!

Questa volta ho sperimentato la modalità IDEAL del TASM, con conseguenti modifiche di keyword e sintassi di indirizzamento. Nel sorgente, tutte le righe che hanno come commento:
; <-- IDEAL (...)
hanno una di queste modifiche, e tra parentesi è indicata la sintassi da dover usare quando non si lavora in modo IDEAL. Consiglio questa modalità ai chi usa il TASM, per la sua 'rigidezza' che a volte può evitare errori, soprattutto quando si tratta di lavorare con puntatori e indici.

Notate come abbia spostato tutte le variabili usate dal nuovo handler all'interno dell'handler stesso, usando poi CS come segmento di indirizzamento? Pare inutile, e forse a ben guardare in questo caso lo e'; tuttavia è bene cominciare a fare in questo modo, poichè una routine di interrupt che viene chiamata indipendentemente dal nostro programma, lo puo' essere in qualsiasi momento. E se il nostro programma è un eseguibile multi-segmento, o meglio ancora un TSR, l'unico registro di segmento su cui possiamo fare affidamento per ritrovare i nostri dati è CS.

Come promesso la volta scorsa, nell'analisi della riga di comando, teniamo conto anche delle tabulazioni. Il metodo rimane tuttavia limitato ad un solo argomento, ma è facile modificarlo per poter 'catturare' uno qualsiasi degli eventuali argomenti. Per non offendere la vostra intelligenza, lascio a voi tale compito<grin>.

Possiamo provare a dare un occhiata più approfondita all'MCB, per capire come si libera la memoria necessaria... orbene, come vi spiegavo la volta scorsa, quando voi fare partire un eseguibile, il dos gli riserva tutta la memoria disponibile fino al 640esimo Kb. Solo che la memoria allocata in questo modo è accessibile solo lavorando direttamente coi registri segmento e sperando di non andare a rompere le scatole a nessuno. Per poter usare le funzioni DOS di allocazione/disallocazione memoria, dobbiamo prima liberarne un po'.
E come si fa?
Avete già visto che ad ogni eseguibile caricato in memoria viene fatto precedere un PSP (Program Segment Prefix), che abbiamo usato per estrarre la linea di comando. Bene, nel segmento precedente quello del PSP (che in modo reale vuol dire 16 byte prima), troviamo l'MCB (Memory Control Block), una struttura che il DOS usa per tenere traccia dei blocchi di memoria allocati.
Includo l'estratto da HelpPC per chi ancora non avesse provveduto a prenderselo.
Tutto ciò che interessa noi è la word contenuta all'offset 0x03 dell'MCB: la dimensione in paragrafi (1 paragrafo = 16 byte) del blocco di memoria allocata. Pare assurdo, ma modificando il valore di questo campo, automaticamente facciamo credere al DOS che in effetti ci sia meno memoria allocata per quel blocco. A questo punto di deve stare attenti a non ridurrne troppo la dimensione, altrimenti corriamo il rischio di tagliare fuori pezzi di codice/dati!
Ci sono due metodi da seguire:

  • calcolare in anticipo o in fase di compilazione la dimensione totale dell'eseguibile più i dati, e ridurre il blocco al minimo indispensabile. Con i file .COM, che hanno dimensione massima di 64Kb, non ci sono problemi... qualcuno potrebbe nascere con i .EXE.
  • calcolare la dimensione dei blocchi da allocare necessari al programma, e modificare l'MCB in modo da fare spazio SOLO per quelli. Anche in questo caso, controllate che la memoria da liberare non sia troppa rispetto a quella effettivamente 'disponibile', si potrebbe avere lo stesso problema del metodo precedente.
Io ho seguito il secondo metodo, ma effettivamente non ho fatto alcun controllo sulla memoria massima disponibile, ma per 64Kb non dovreste correre rischi... a meno che non stiate usando un XT a 128Kb con su il DOS 6.2.

Tutto il resto è normale routine (se mi perdonate lo quallido gioco di parole).


Conclusioni

Tutto quanto detto fin ora funziona benissimo con il DOS puro... non è così con le shell di altri sistemi operativi, sopratutto se multitask. Questo perch` il sistema di temporizzazioni salta, e non potrebbe essere altrimenti con uno scheduler sotto che decide per quanti centesimi di secondo il programma deve girare.
A dire il vero, da me il DOSEMU di LINUX si incanta completamente (niente che un telnet dall'altro PC non possa risolvere - avete idea di quanto sia gustoso dare un comando stile 'kill dos'?), ma poichè lo fa spesso, non credo sia colpa del mio innocente programmino.

Il mio carissimo amico Fernando qualche giorno fa mi ha chiesto a cosa potrebbe mai servire riprodurre suoni su speaker, quando ormai una scheda sonora a 16bit costi una miseria.
Glielo spiegate voi? <bestialic grin>

suonatamente,
Cthulhu

--- Segue estratto da HelpPC ---

[Torna all'articolo]
 HelpPC 2.10           Quick Reference Utility     Copyright 1991 David Jurgens 
                                                                                
                     MCB - DOS Memory Control Block Format                      
                                                                                
      Offset Size               Description                                     
                                                                                
        00   byte       'M' 4Dh  member of a MCB chain, (not last)              
                        'Z' 5Ah  indicates last entry in MCB chain              
                        other values cause "Memory Allocation Failure" on exit  
        01   word       PSP segment address of MCB owner (Process Id)           
                        possible values:                                        
                            0 = free                                            
                            8 = Allocated by DOS before first user pgm loaded   
                            other = Process Id/PSP segment address of owner     
        03   word       number of paras related to this MCB (excluding MCB)     
        05 11bytes      reserved                                                
        08  8bytes      ASCII program name, NULL terminated if less than max    
                        length (DOS 4.x+)                                       
        10  nbytes      first byte of actual allocated memory block             
                                                                                
                                                                                
        - to find the first MCB in the chain, use  INT 21,52                    
        - DOS 3.1+ the first memory block contains the DOS data segment         
          ie., installable drivers, buffers, etc                                
        - DOS 4.x the first memory block is divided into subsegments,           
          with their own memory control blocks; offset 0000h is the first       
        - the 'M' and 'Z' are said to represent Mark Zbikowski                  
        - the MCB chain is often referred to as a linked list, but              
          technically isn't                                                     
                                                                                
                                                                                
            DOS 4.x Initial Data Segment Subsegment Control Blocks:             
                                                                                
      Offset Size         Description                                           
        00   byte       subsegment type                                         
                        'D'  device driver                                      
                        'E'  device driver appendage                            
                        'I'  Installable File System driver                     
                        'F'  FILES= control block storage area (for FILES>5)    
                        'X'  FCBS= control block storage area, if present       
                        'C'  BUFFERS EMS workspace area if BUFFERS /X is used   
                        'B'  BUFFERS= storage area                              
                        'L'  LASTDRIVE= current directory structure array       
                        'S'  STACKS= code/data area, if present (see below)     
        01   word       paragraph of subsegment start                           
        03   word       subsegment size in paragraphs                           
        05  3bytes      unused                                                  
        08              types "D" and "I", filename of driver loaded driver     
                                                                                
                                                                                
        - see  INT 21,48  INT 21,49  INT 21,4A                                  
                                                                                
                                                                                
 Esc or Alt-X to exit               MCB                 Home/PgUp/PgDn/End
[Torna all'articolo]


Francesco Sileno è reperibile su Internet tramite la redazione


Copyright © 1996 BETA. Tutti i diritti riservati.
Precedente Principale Sommario Informazioni Redazione Browser Successivo