6.2.2. Gravis Ultrasound lehetőségei és programozása
A Gravis hangkártyák általános felépítése helyett nézzük meg, milyen lehetőségekkel bír az általam használt GUS ACE:
- Erősített vonali sztereó kimenet (Line Out),
- Erősített vonali sztereó bemenet más audio eszközöktől (Line In),
- 512 K, illetve 1 MB saját memória hullámformák tárolására (GUS DRAM),
- 32 digitális csatorna,
- GF1 hangprocesszor (Voice Sound Synthetizer),
- hullámforma táblákkal megvalósított FM hangkeltés,
- minden csatornán különálló hangerő és balansz,
- állítható mintavételi frekvencia 44,1 kHz-ig,
- 8- vagy 16 bites sztereó, illetve mono lejátszás,
- szoftverből állítható IRQ vonal és DMA csatorna,
- jumperrel állítható I/O báziscím,
- a hangkártya minden egysége elérhető processzorral, ugyanakkor képes függetlenül dolgozni,
- több pontos burkológörbe előállítása hardveres úton.
Itt most nem térünk ki részletesen a regiszterek ismertetésére, mivel elég sok van belőlük és véleményem szerint azok felépítése sokkal bonyolultabb a Sound Blaster esetén tapasztaltnál. Akit részletesebben érdekel a téma, bőven talál leírásokat az ezzel foglalkozó szakirodalomban. Inkább [4] alapján a konkrét programozást bemutató, a feladat szempontjából fontosabb Pascal unitot közlök:
{$G+}
unit gus;
interface uses DOS,CRT;
type
memptr = ^memory;
memory = array[0..65534] of byte;
const
Hdg : array[0..15] of char=’0123456789ABCDEF’;
{ Hangkártya hardverbeállítások }
Base : word = $250; { I/O báziscím }
GF1irq : byte = 11; { Megszakítási vonal }
MIDIirq : byte = 7; { MIDI megszakítási vonal }
DMA_1 : byte = 3; { GUS DRAM DMA csatorna }
DMA_2 : byte = 3; { Felvétel DMA csatorna }
{ Frekvenciatáblázat 5 oktáv számára }
FreqTAB : Array[0..59] of word=(
$0038,$003B,$003E,$0042,$0046,$004A,$004F,$0053,$0058,$005E,$0063,$0069,
$0070,$0076,$007D,$0085,$008D,$0095,$009E,$00A7,$00B1,$00BC,$00C7,$00D3,
$00E0,$00ED,$00FB,$010A,$011A,$012B,$013D,$014F,$0163,$0179,$018F,$01A7,
$01C0,$01DB,$01F7,$0214,$0234,$0256,$027A,$029F,$02C7,$02F2,$031E,$034E,
$0380,$03B6,$03EE,$0429,$0469,$04AD,$04F4,$053F,$058F,$05E4,$063D,$069C);
{ Hangerőtáblázat a 128 lépéses lineáris szabályozáshoz }
VolTAB: Array[0..127] of word=(
$0000,$8ff0,$9ff0,$a800,$aff0,$b400,$b800,$bc00,
$bff0,$c200,$c400,$c600,$c800,$ca00,$cc00,$ce00,
$cff0,$d100,$d200,$d300,$d400,$d500,$d600,$d700,
$d800,$d900,$da00,$db00,$dc00,$dd00,$de00,$df00,
$dff0,$e080,$e100,$e170,$e200,$e280,$e300,$e370,
$e400,$e480,$e500,$e580,$e600,$e680,$e700,$e780,
$e800,$e880,$e900,$e990,$ea00,$ea80,$eb00,$eb80,
$ec00,$ec80,$ed00,$ed90,$ee00,$ee80,$ef00,$ef90,
$eff0,$f030,$f070,$f0c0,$f100,$f140,$f170,$f1c0,
$f200,$f240,$f280,$f2b0,$f300,$f340,$f380,$f3b0,
$f400,$f440,$f470,$f4c0,$f500,$f540,$f590,$f5c0,
$f600,$f650,$f690,$f6c0,$f700,$f740,$f790,$f7c0,
$f800,$f840,$f880,$f8b0,$f900,$f940,$f980,$f9b0,
$fa00,$fa50,$fa80,$fac0,$fb00,$fb40,$fb80,$fbc0,
$fc00,$fc40,$fc80,$fcd0,$fd00,$fd40,$fd80,$fdb0,
$fe00,$fe40,$fe80,$fec0,$ff00,$ff40,$ff80,$ffd0);
function GetByte(Address:longint):byte;
procedure PutByte(Address:longint; value:byte);
procedure MoveSample(Adr:longint; var Src; count:word);
procedure GravisWait;
procedure GravisRESET;
function GravisTEST:boolean;
procedure SetGF1Byte(Ch,Reg,Value:byte);
procedure SetGF1Word(Ch,Reg:byte; Value:word);
function GetGF1Byte(Ch,Reg:byte):byte;
function GetGF1Word(Ch,Reg:byte):word;
function GravisFindBase:word;
function GravisMemory:word;
procedure GravisVolume(ch:byte; volume:word);
procedure GravisBalance(ch:byte; balance:byte);
procedure GravisFreq(ch:byte; freq:word);
procedure PlayVoice(ch,mode:byte; b,s,e:longint);
procedure StopVoice(ch:byte);
function VoicePos(ch:byte):longint;
function Hex(b:byte):string;
function HexW(w:word):string;
procedure Note(ch,n:byte);
procedure Volume(ch,n:byte);
procedure SetIRQservice(Irqr:pointer);
procedure ResetIRQservice;
implementation
function GetByte(Address:longint):byte; assembler;
asm
mov dx,Base
add dx,$103 { Globálisregiszter-kiválasztás }
mov al,$43
out dx,al { A DRAM alsó szó regiszter }
inc dx
mov ax,Address.word
out dx,ax { Kiírjuk a cím alsó 16 bitjét }
dec dx
mov al,$44 { A DRAM felső bájt regiszter }
out dx,al
add dx,$0002
mov al,[Address+2].byte
and al,00001111b
out dx,al { Kiírjuk a cím felső 4 bitjét }
add dx,$0002 { DX: $3×7, DRAM read/write}
in al,dx { Beolvassuk az adatot az AL-be }
end;
GetByte: a GUS DRAM tetszőleges bájtjának lekérdezése.
procedure PutByte(Address:longint; value:byte); assembler;
asm
mov dx,Base
add dx,$103 { Globálisregiszter-kiválasztás }
mov al,$43
out dx,al { A DRAM alsó szó regiszter }
inc dx
mov ax,Address.word
out dx,ax { Kiírjuk a cím alsó 16 bitjét }
dec dx
mov al,$44 { A DRAM felső bájt regiszter }
out dx,al
add dx,$0002
mov al,[Address+2].byte
and al,00001111b
out dx,al { Kiírjuk a cím felső 4 bitjét }
add dx,$0002 { DX: $3×7, DRAM read/write}
mov al,value { AL: a bájt új értéke }
out dx,al { Beírjuk az értéket a DRAM-ba }
end;
PutByte: egy GUS DRAM bájt beírása.
procedure MoveSample(Adr:longint; var Src; count:word); assembler;
asm
mov di,Adr.word;
mov bl,[Adr+2].byte
and bl,00001111b { BL:DI=DRAM cím (első bájt)}
cld
les si,Src
mov ax,ES:[si]
mov ES,ES:[si+2]
mov si,ax { ES:SI a forrásadatra mutat }
mov cx,count { CX a bájtok számát tartalmazza }
@putbytes: { Kiírás: PutByte-hoz hasonlóan }
mov dx,Base
add dx,$103
mov al,$43
out dx,al
inc dx
mov ax,di
out dx,ax
dec dx
mov al,$44
out dx,al
add dx,$0002
mov al,bl
out dx,al
add dx,$0002
segES lodsb { Beolvasás a forrásadatokból }
out dx,al { Kiírás a célhelyre (GUS DRAM)}
add di,$01
adc bl,$00 { Növeljük a DRAM címet (BL:DI)}
loop @putbytes { CX darab bájtot írunk ki }
end;
MoveSample: egy legfeljebb 64K hosszú memóriaterület betöltése a GUS DRAM-ba.
procedure GravisWait; assembler;
asm
push dx { Tároljuk a DX, AX regisztereket }
push ax
mov dx,$0000 { Egy semleges I/O báziscím }
in al,dx
in al,dx
in al,dx
in al,dx
in al,dx
in al,dx { Várakozási ciklusok }
pop ax { AX, DX visszahozása }
pop dx
end;
GravisWait: a Gravis csővezetékes rendszere miatt néhol várakozásra van szükség.
procedure GravisRESET; assembler;
asm
mov dx,Base
add dx,$103 { Globálisregiszter-kiválasztás }
mov al,$4C
out dx,al { $4C: Reset regiszter}
add dx,$0002
mov al,$00 { A GF1 inicializálása }
out dx,al
call GravisWait { Várakozás }
sub dx,$0002
mov al,$4C
out dx,al { Ismét Reset regiszter}
add dx,$0002
mov al,$07 { Az eredeti normális működés visszaállítása}
out dx,al
sub dx,$0002
mov al,$0E
out dx,al { 14 aktív csatorna }
mov al,$CE
add dx,$0002
out dx,al
mov dx,Base
mov al,$0C
out dx,al { Bemenetek engedélyezése }
end;
GravisReset: a hangkártya alapállapotba állítása.
function GravisTEST:boolean;
begin
asm
mov dx,Base
add dx,$103
mov al,$4C { A hangkártya inicializálásának megkísérlése }
out dx,al
add dx,$0002
xor al,al
out dx,al
call GravisWait
call GravisWait
sub dx,$0002
mov al,$4C
out dx,al
add dx,$0002
mov al,$01
out dx,al
end;
PutByte(256,$55); { Néhány bájt kiírása a memóriába }
PutByte(512,$aa);
PutByte(000,$0f);
{ Ha ugyanazt olvassuk vissza amit kiírtunk, akkor van Gravis }
GravisTest:=(GetByte(256)=$55)and(GetByte(512)=$aa);
end;
GravisTest: a függvény igaz értékkel tér vissza, ha az adott báziscímen található Gravis hangkártya.
procedure SetGF1Byte(Ch,Reg,Value:byte); assembler;
asm
mov dx,Base
add dx,$102 { Csatorna lapregiszter ($3×2)}
mov al,&Ch { Kiírjuk a csatornaszámot többszőr}
out dx,al
out dx,al
out dx,al
inc dx { Regiszter-kiválasztás ($3×3)}
mov al,Reg
out dx,al { Kiírjuk a regiszter számát }
add dx,$0002 { Regiszter adatai I/O cím ($3×5)}
mov al,Value
out dx,al { Kiírjuk a regiszter új értékét }
end;
procedure SetGF1Word(Ch,Reg:byte; Value:word); assembler;
asm
mov dx,Base
add dx,$102 { Csatorna lapregiszter ($3×2)}
mov al,&Ch { Kiírjuk a csatornaszámot többszőr}
out dx,al
out dx,al
out dx,al
inc dx { Regiszter-kiválasztás ($3×3)}
mov al,Reg
out dx,al { Kiírjuk a regiszter számát }
inc dx { Regiszter word adat cím ($3×4)}
mov ax,Value
out dx,ax { Kiírjuk a word adatot }
end;
SetGF1Byte és SetGF1Word: a GF1 processzor globális és csatorna specifikus regisztereinek közvetlen módosítása.
function GetGF1Byte(Ch,Reg:byte):byte; assembler;
asm
mov dx,Base
add dx,$102 { Csatorna lapregiszter ($3×2)}
mov al,&Ch { Kiírjuk a csatornaszámot többszőr}
out dx,al
out dx,al
out dx,al
inc dx { Regiszter-kiválasztás ($3×3)}
mov al,Reg
out dx,al { Kiírjuk a regiszter számát }
add dx,$0002 { Regiszter adata I/O cím ($3×5)}
in al,dx { Beolvassuk a regiszter értékeket }
end;
function GetGF1Word(Ch,Reg:byte):word; assembler;
asm
mov dx,Base
add dx,$102
mov al,&Ch
out dx,al
out dx,al
out dx,al
inc dx
mov al,Reg
out dx,al
inc dx
in ax,dx
end;
GetGF1byte és GetGF1word: a GF1 regisztereinek kiolvasása.
function GravisFindBase:word;
var i:byte;
w:word;
begin
w:=0;
for i:=1 to 6 do
begin
Base:=$200 or i*16;
if GravisTEST then w:=Base;
end;
GravisFindBase:=w;
Base:=w;
end;
GravisFindBase: az I/O báziscím meghatározása.
function GravisMemory:word;
var b:word;
begin
PutByte($3ffff,$01); { 256*1024-1 }
if GetByte($3ffff)=$01 then b:=256;
PutByte($7ffff,$02); { 512*1024-1 }
if GetByte($7ffff)=$02 then b:=512;
PutByte($bffff,$04); { 768*1024-1 }
if GetByte($bffff)=$04 then b:=768;
PutByte($fffff,$08); { 1024*1024-1 }
if GetByte($fffff)=$08 then b:=1024;
GravisMemory:=b;
end;
GravisMemory: a memória meghatározása.
procedure GravisVolume(ch:byte; volume:word);
begin
SetGF1Word(ch,$09,volume);
end;
GravisVolume: direkt hangerő szabályozás.
procedure GravisBalance(ch:byte; balance:byte);
begin
SetGF1Byte(ch,$0C,balance);
end;
GravisBalance: egy csatorna balanszértékének beállítása.
procedure GravisFreq(ch:byte; freq:word);
begin
SetGF1Word(ch,$01,freq);
end;
GravisFreq: a lejátszási frekvencia beállítása.
procedure PlayVoice(ch,mode:byte; b,s,e:longint);
begin
SetGF1Word(Ch,$03,(s and $7f) shl 9);
SetGF1Word(Ch,$02,(s shr 7));
SetGF1Word(Ch,$05,(e and $7f) shl 9);
SetGF1Word(Ch,$04,(e shr 7));
SetGF1Word(Ch,$0B,(b and $7f) shl 9);
SetGF1Word(Ch,$0A,(b shr 7));
SetGF1Byte(Ch,$00,mode);
{SetGF1Byte(Ch,$4C,$03);}
end;
PlayVoice: egy GUS DRAM-ba betöltött hangminta lejátszása, ch: a lejátszáshoz használt csatorna száma, mode: a lejátszás módja, b: a hangminta aktuális címe, s: a hangminta kezdőcíme, e: a hangminta végcíme.
procedure StopVoice(ch:byte); assembler;
asm
push &ch.word
push $0080 { Lekérdezzük a voice control regiszter értékét}
call GetGF1Byte
and al,$DF
or al,$03 { Voice Stop bit=1 }
xor ah,ah
push ax
push &ch.word
push $0000
push ax
call SetGF1Byte { Beállítjuk a Voice controlt }
call GravisWait
pop ax
push &ch.word
push $0000
push ax
call SetGF1Byte
end;
StopVoice: a hangminta lejátszásának leállítása.
function Hex(b:byte):string;
begin
Hex:=Hdg[b shr 4]+Hdg[b and 15];
end;
function HexW(w:word):string;
begin
HexW:=Hdg[w shr 12]+Hdg[(w shr 8 ) and 15]+
Hdg[(w shr 4) and 15]+Hdg[w and 15];
end;
Hex és Hexw: bájt és word hexadecimális érétkének meghatározása.
procedure Note(ch,n:byte);
begin
GravisFreq(ch,FreqTAB[n]);
end;
Note: egy adott hangjegyhez tartozó frekvencia beállítása.
procedure Volume(ch,n:byte);
begin
GravisVolume(ch,VolTAB[n and 127]);
end;
Volume: egy csatorna hangerejének lineáris beállítása.
procedure FirstIRQ; interrupt;
var a:byte;
begin
a:=port[Base+$0006];
a:=$00;
while a and $c0<>$c0 do
a:=getgf1byte($00,$8f);
port[$20]:=$20;
port[$a0]:=$20;
end;
FirstIRQ: a unit belső megszakítási rutinja, amely kívülről nem érhető el.
procedure SetIRQservice(Irqr:pointer);
var i:byte;
begin
SetGF1Byte($00,$4C,$03); { Minden GF1 IRQ-t tiltunk }
SetIntVec($73,@FirstIRQ); { Beállítjuk az init IRQ-t }
port[Base]:=$4c; { Kiválasztjuk az IRQ regisztert }
port[Base+$000b]:=$05; { IRQ11 , int 73h 00001000 }
port[$a1]:=port[$a1] and $F7; { Engedélyezzük a vonalat }
for i:=0 to 31 do
begin { Minden csatorna letiltva }
setgf1byte(i,$0d,$02);
setgf1byte(i,$00,$02);
end;
SetGF1byte($00,$41,$00); { DRAM DMA IRQ tiltva }
SetGF1byte($00,$49,$00); { Felvétel IRQ tiltva }
SetGF1Byte($00,$4C,$07); { GF1 IRQ engedélyezve }
delay(400); { várakozás }
SetIntVec($73,IRQR); { Az új megszakítási rutin }
end;
SetIRQsercvice: a megszakítás inicializálása és a megszakítási rutin címének beállítása.
procedure ResetIRQservice;
begin
port[$a1]:=port[$a1] or $08; { Tiltjuk a vonalat }
SetGF1Byte($00,$4C,$03); { GF1 IRQ letiltva }
end;
ResetIRQservice: a GF1 megszakítás tiltása.
[4] László József: Hangkártya programozása Pascal és Assembly nyelven, Computer Books, Budapest, 1999