int21h

Zvuky v Turbo Pascalu 7 - ovládání zařízení

Minule jsme se teoreticky seznámili se zvukovými formáty v počítači a naučili jsme se je různě upravovat. Nyní, když máme co přehrávat, se naučíme jak to přehrávat. Příklady budou funkční na 99% pod Turbo Pascalem 7. Ve Free Pascalu to bude chodit tak 50:50. Pokud nebudete používat DMA, tak máte možná 90% šanci.


Ovládání zvukové karty

Nyní už víme všechno o zvukových datech, která budeme používat pro naši kartu. Ale jak přinutit počítač, aby nám naše data přehrál (soubory WAVe jde samozřejmě přehrávat i na interním reproduktoru - bude to sice jen mono, trošku pištivé a v kvalitě pouze 6 bitů a 11025 Hz, ale musím uznat, že to fakt stojí za to - např. moje "hra" Dračí doupě tento systém používá)? Jednoduše, prostě si ho naprogramujeme :-). Uvidíte, že to všechno není nic jiného než napsání si obslužné rutiny přerušení (která je jednodušší než cokoliv jiného), vytvoření bufferu a spuštění přenosu dat. A pokud Vás zajímá i záznam zvuku z mikrofonu/CD/LineIn, tak zde také něco najdete (bohužel, na rozdíl od přehrávání neručím za to, že to bude vždy fungovat - prakticky to ale záleží spíše na tom, zda nemáte ztlumeny nějaké vstupy).


Zjištění parametrů zvukové karty

Abychom mohli začít pracovat se zvukovou kartou, musíme znát její typ, bázovou adresu, číslo přerušení, DMA a popř. i 16 bitové DMA. Máme prakticky tři možnosti, jak tyto údaje získat. Buď si je necháme zadat od uživatele (tj. buď přes READLN nebo přes nějaký CFG soubor), zjistíme si je z proměnné BLASTER, pokud je definovaná, nebo provedeme autodetekci.


var	IRQ,DMA8,DMA16 : byte;
	Adr : word;
	Typ : byte;  

Nejjednodušší jsou první dvě. Typ karty budeme potřebovat znát, abychom věděli, zda karta umí stereo, zda umí více než 22 kHz a zda umí 16 bitové zvuky. Zde je tabulka karet a toho, co umí (popravdě to hlavně závisí na verzi čipu DSP):


	1	Sound Blaster (old)
	2	Sound Blaster Pro (old)
	3	Sound Blaster 2.0 (dlx)
	4	Sound Blaster Pro
	5	MicroChannel Sound Blaster
	6	Sound Blaster 16, Vibra, AWE32  

SB 2.0 umí maximálně 11 kHz, 8 bitů a mono
SB Pro umí maximálně 22 kHz, 8 bitů stereo nebo 44 kHz, 8 bitů, mono
SB 16 umí maximálně 44 kHz, 16 bitů, stereo

	Verze čipu =		1.XX	2.00	2.01+	3.XX	4.XX
8 bit Single PCM		*	*	*	*	*
8 bit Auto PCM				*	*	*	*
8 bit Single ADCPM		*	*	*	*	*
8 bit Auto ADPCM			*	*	*	*
8 bit Sigle HSpeed PCM				*	*
8 bit Auto HS PCM				*	*
8 bit Stereo Single HS					*
8 bit Stereo Single HS					*
8/16 bit mono Single PCM					*
8/16 bit mono Auto PCM						*
8/16 bit stereo Single PCM					*
8/16 bit stereo Auto PCM					*  

Předpokládám, že zeptat se uživatele na pár hodnot (ať už při startu programu vždy, nebo v nějakém SETUPu vedle programu) Vám nebude činit potíže, takže přejdeme na druhou možnost. Zjistíme, zda je v prostředí proměnná BLASTER a pokud ano, získáme od ní různé parametry (budete potřebovat jednotku DOS, a to nejen na tuto funkci):


function CtiBLASTER : boolean;
var PromBlast : string;
begin
 CtiBlaster := False;
 PromBlast := GetEnv('BLASTER');
 if PromBlast = '' then Exit;
 if Pos('A',PromBlast) = 0 then Exit else
  Adr := (Ord(PromBlast[Pos('A',PromBlast)+1])-Ord('0'))*256+
         (Ord(PromBlast[Pos('A',PromBlast)+2])-Ord('0'))*16+
         (Ord(PromBlast[Pos('A',PromBlast)+3])-Ord('0'));
 if Pos('I',PromBlast) = 0 then Exit else
  IRQ := Ord(PromBlast[Pos('I',PromBlast)+1])-Ord('0');
 if Pos('D',PromBlast) = 0 then Exit else
  DMA8 := Ord(PromBlast[Pos('D',PromBlast)+1])-Ord('0');
 if Pos('H',PromBlast) = 0 then DMA16 := 255 else
  DMA16 := Ord(PromBlast[Pos('H',PromBlast)+1])-Ord('0');
 if Pos('T',PromBlast) = 0 then Exit else
  Typ := Ord(PromBlast[Pos('T',PromBlast)+1])-Ord('0');
 if not (DMA8 in [0,1,3]) then Exit;
 if DMA16 <> 255 then
  if not (DMA16 in [5,6,7]) then Exit;
 if not (IRQ in [2,3,5,7,10,11,12,15]) then Exit;
 CtiBlaster := True;
end;  

Toto je většinou nejspolehlivější metoda, protože proměnná BLASTER většinou existuje (ve Windows na 100%, v DOSu na 90%, pokud ji uživatel nesmazal z AUTOEXEC.BAT). Nejjednodušší je detekovat bázovou adresu, kdy se vlastně na různých adresách pokoušíte resetovat DSP čip na zvukové kartě. To se dělá tak, že na něj nejprve pošlete "1", chvíli počkáte, pak "0", zase chvíli počkáte a pak zjisíte, zda Vám správně odpověděl. Pokud ne, na dané adrese zvuková karta není a musíte tedy zkoušet další. Existuje několik adres, které se mohou prohledávat ($220 až $280 po $10, kromě $270):


function ResetDSP(Adresa : word) : boolean; assembler;
asm  
	mov	dx,adresa
	add	dx,6
	mov	al,1
	out	dx,al
	push	10
	call	delay		(* funkce na zpoždění 10 ms *)
	xor	ax,ax
	out	dx,al
	push	10
	call	delay
	add	dx,8		(* 6+8 = $e *)
	in	al,dx
	test	al,$80
	jz	@neni
	dec	dx,4		(* $e-4 = $a *)
	in	al,dx
	cmp	al,$aa
	jne	@neni
	mov	al,1
	jmp	@konec
@neni:	xor	al,al
@konec:  
end;


function NajdiAdresu : boolean;
var A : word;
begin
 NajdiAdresu := False;
 for A := 1 to 9 do
  if not (A in [7,9]) do
   if ResetDSP($200+A shl 4) then
   begin
    Adr := $200+A shl 4;
    Break;
   end;
 if A <> 9 then
  NajdiAdresu := True;
end;  

Tato metoda má tu nevýhodu, že může na některých systémech, kde na dané adrese sídlí nějaká jiná karta (síťová, SCSI řadič) způsobit zasknutí počítače nebo nějaké jiné vedlejší efekty. Je však možné kartu detekovat, pokud již byla inicializována (Windows, její ovladač, jiný program), a přitom se vyhnout této možné katastrofě. Říkám tomu "střelba na slepo" a používá se to zvláště v kombinaci s detekcí IRQ (když už detekujeme jednu věc, můžeme i ty ostatní). Prostě jednoduše pošleme na první adresu DSP čipu (bázová + $c) příkaz např. pro vyvolání přerušení (kdy jsme před tím všechna obsadili), a pokud se alespon jedno z nich projeví, máme správnou adresu (a zároveň i IRQ). Pokud ne, zkusíme další adresu. Je to bezpečnější než provádět resetování čipu (pokud však máte ještě kartu Sound Blaster originál, tak potom vlastně ani ovladače nepotřebujete; karta Adlib je ještě lepší, ale ovládá se trochu jinak). Vše si však probereme až později (pokud tomu zatím nerozumíte, nic se neděje - zatím nejste připraveni).

No, a teď jak detekovat DMA a IRQ. Zjistit přerušení pro Vás bude jednoduché, až budete muset zvukovou kartu používat. Jednoduše obsadíte jedno z přerušení, které může karta vyvolat, a normálně spustíte přehrávání karty na prázdném bufferu (ticho), avšak bez Autoinitu (viz. dále), tj. karta přehraje buffer a skončí. Ovšem během přehrávání karta vyvolá přerušení, že dosáhla konce bufferu (pokud vše správně nastavíte). No a nám stačí zjistit, zda vyvolala zrovna to naše přerušení. Jak se dá zjistit, že už karta dohrála? Existuje na to jeden její (resp. DMA) registr, který se naučíme číst později. Pokud karta už nehraje, a přitom naše přerušení nevyvolala, zkusíme další (můžeme samozřejmě obsadit všechny najednou pokaždé jiným kódem, abychom je rozlišili). Jakmile naše přerušení karta vyvolá, uložíme si ho a budeme jej používat. Karta má také příkaz $f2, který, když pošlete do DSP, způsobí, že karta vyvolá 1 přerušení i když nehraje (tj. nepotřebujete znát správné DMA). Je to jednodušší než jí spouštět a pak zastavovat.

A jak se detekuje DMA? Dost blbě. Vlastně, nejprve musíte znát bázovou adresu a IRQ. Pokud jste ji již detekovali, je to celkem snadné. Tentokrát se však spuštění karty naprázdno nevyhneme. Jednoduše naprogramovujeme kartu tak, aby čekala data z DMA proudu a spustíme jedno z možných DMA. A budeme čekat. Pokud karta po nějaké době vyvolá přerušení, že jí došla data (tj. přišlo jich tolik, kolik má nastaveno jako maximum), je naše DMA správné, protože jí posílalo data. Pokud karta mlčí bez přerušení nebo prská, znamená to, že do ní žádná data přes DMA nejdou a tedy máme špatný kanál. Musíme tedy zkusit další.


Inicializace a ukončení

Dobrá. Víme, kde naše karta sídlí. Víme také, co umí. Nyní si musíme rozmyslet, co od ní budeme požadovat. Ale ať už karta bude dělat cokoliv, potřebuje mít buffer, ze kterého se do ní budou přenášet zvuková data. Tento buffer může být velký maximálně 65520 bytů, takže je vhodné ho vytvořit např. velký 32 kB. A virtuálně si jej rozdělíme na 2-4 části. To se dělá z jednoho důvodu: pokud totiž do karty posíláte data, ta vyvolá přerušení, že jí už došla, teprve, až dorazí poslední byte. V tomto okamžiku se buď karta zastaví (Single režim) nebo se přesune na začátek bufferu a začne jej hrát od začátku (Auto Init). Dobře, my během přerušení naplníme buffer novými daty (vložíme nový zvuk, přimixujeme další, vložíme ticho, pokud již nejsou žádné zvuky na hraní). Ale karta nám během tohoto zpracování bude prskat.

Proč? Protože měníme data, která už zase přehrává (popř. se zastaví a tedy jí musíme znovu spustit, ale vznikne tam pauza s tichou mezerou). Právě proto se buffer dělí na 2-4 části (dle nálady). Zvláště vhodné to je v režimu s použitím DMA. Řadiči DMA se sdělí, aby posílal do karty celý buffer (32 kB), ale samotné kartě se sdělí, že počet dat je jen 8 kB (16 kB pro 1/2). Tedy karta v okamžiku, kdy přehraje svých 8 kB vyvolá přerušení. Ale pokud má AutoInit, bude pokračovat ve hraní, protože DMA do ní stále láduje data. My mezitím změníme daných 8 kB za jiná data a nic nám nekřupe (karta se k těm našim 8 kB dostane až za 3 další přerušení). Pokud se využívá DMA (což je více než vhodné), je také podmínka, aby celý buffer (tj. jeho začátek a konec současně) ležel v jednom 64 kB segmentu DMA (stránce). To zajistíme tak, že pokud námi vytvořený buffer 32 kB leží přes hranici, tak buffer, co vytvoříme za ním, už bude někde v něm. Pak můžeme ten původní smazat:


var SBbuffer : pointer;


function VytvorBuffer : boolean;
var PracBuf : pointer;
    Stranka1,Stranka2 : word;
begin
 VytvorBuffer := False;
 if MaxAvail >= 65536 then
 begin
  GetMem(PracBuf,32768);
  LinearAdr := Seg(PracBuf^);
  LinearAdr := LinearAdr shl 4+Ofs(PracBuf^);
  Stranka1 := LinearAdr shr 16;
  Stranka2 := (LinearAdr+32767) shr 16;
  if Stranka1 <> Stranka2 then
  begin
   GetMem(SBbuffer,32768);
   FreeMem(PracBuf,32768);
  end
   else SBBuffer := PracBuf;
  {FillChar(SBBuffer^,$8000,$7f);}
  VytvorBuffer := True;
 end;
end;  

Na konci programu tento buffer zase musíme smazat pomocí FreeMem (a hrající kartu zastavit, viz. dále). Všimněte si zakomentované instrukce FillChar. Tato slouží k vymazání celého bufferu, nebo-li nastavení ticha. Je vhodné ji použít. Ovšem funguje pouze pro 8 bitové zvuky. Pro 16 bitové musíte vložit wordy $8000 (v Turbo Pascalu přes cykl FOR, v FP je na to instrukce FillWord; nebo si v obou případech můžete pomoci assemblerem - v FP pozor! Buffer musí ležet v konvenční paměti, tj. použijte selektor FS). Samotné zvuky mohou ležet také v konvenční paměti nebo v XMS (popř. ve VAR/GetMem ve Free Pascalu) a do bufferu karty je budete přenášet (jak mixovat jsme si ukazáli minule, tedy zvuky budete přidávat do bufferu pomocí INC instrukce, resp. první zvuk do ticha přes MOVE).

Pro použití zvukové karty budete potřebovat alespoň trochu vědět, jak se programuje IRQ a DMA řadič. Oba využívají své porty, na které zapisujete různá data. Vytvoříme si zde tabulku pro DMA řadič, kde budou přístupné registry pro 8/16 bitový maskovací port (0), nastavení módu (1) a flip-flop (2) počitadlo přenesených dat (všimněte si, že 16 bitové registry zabírají logicky 2x tolik bytů):


const	DMA8reg : array[0..2] of byte = ($a,$b,$c);
	DMA16reg : array[0..2] of byte = ($d4,$d6,$d8);  

Dále budeme potřebovat znát, kam máme zapsat adresy bufferu pro různá DMA a zároveň, kde najdeme počitadla aktuální pozice přenášených data (informace je pozice bufferu ve stránce (offset) a velikost tohoto bufferu)


const	OfsDMA : array[0..7] of byte = (0,2,4,6,$c0,$c4,$c8,$cc);
	VelDMA : array[0..7] of byte = (1,3,5,7,$c2,$c6,$ca,$ce);  

Jak poslední údaj pro DMA je registr, kam se zapisuje stránka, ve které leží náš buffer pro zvuková data:


const	DMAstr : array[0..7] of byte = ($87,$83,$81,$82,$89,$8a,$8b,$8f);  

Ale i řadič IRQ má své registry. Naštěstí jich není už tolik. Najdeme zde jen oznámení o zpracování přerušení (pošleme $20 na port $20 pro 8bitové IRQ, popř. ještě $20 na $a0 pro 16bitové IRQ a IRQ = 2). Na portu $21 se standarně povolují/zakazují přerušení 0-7: "0" je povolené, "1" je zakázané). Přerušení 8-15 se nastavují stejně na portu $a1 (stačí odečíst 8 od IRQ). Bit pro dané přerušení dostanete pomocí "1 shl IRQ", popř. "1 shl (IRQ-8)". Je nutné podotknout, že pro přerušení 2,10 a 11 (resp. pro všechna 16bitová) je nutné povolit také 8bitové přerušení 2 (kaskáda). Více zatím vědět o IRQ řadiči nepotřebujete.

Co ale potřebujete vědět je číslo INT vektoru, který musíme obsadit pro obsluhu zvukové karty při daném IRQ přerušení. Inu, zde je daná tabulka:


const	INTvec : array[0..15] of byte = (8,9,10,11,12,13,14,15,$70,$71,$72,$73,$74,$75,$76,$77)  

Obvykle se ale pro IRQ = 2 obsazuje vektor $71. Daný vektor obsadíte pomocí funkce SetIncVec, kde předáte číslo přerušení a pak pointer na Vaši obsluhu zvukové karty (viz. dále). Toto všechno se dělá ještě před samotným zahájením provozu karty. Na konci programu musíte zase obnovit vektor adresou, kterou si musíte na začátku uschovat pomocí GetIntVec:


var   StaraObsluha : pointer;


begin
 GetIncVec(INTvec[IRQ],StaraObsluha);
 SetIncVec(INTvec[IRQ],@NovaObsluha);
  {...}
 SetIncVec(INTvec[IRQ],StaraObsluha);
end;  

Ještě potřebujeme znát, jak vytvořit příkaz pro DMA řadič. Inu jedná se o byte, který obsahuje celkem 4 skupinky bitů po dvou v této sekvenci "aa bb cc dd". Jejich význam je následující: AA označuje způsob přenosu, jedna volba je 10 (kromě jiných), tj. blokový mód, ale v 99% případů se používá tzv. mód sigle (01 - pozor, to nesouvisí s AutoInit/Single módem); BB určuje způsob inicializace po dokončení přenosu (x1 je Auto Init, x0 je Single mód; vyšší bit X určuje, zda se má adresa zvyšovat (0) nebo snižovat (1) - v některých dokumentacích je to zřejmě špatně obráceně); CC určuje směr přenosu (01 je zápis do zvukové karty, tj. přehrávání, zatímco 10 je čtení z karty, tj. nahrávání zvuku do počítače) a DD určuje kanál DMA OR 3 (jak pro 8bitové, tak DMA16 OR 3 pro 16bitové).

Dále se posílá ještě něco na maskovací port. Zde je struktura jednodušší: první (dolní) 2 bity opět určují DMAx OR 3 a bit 2, pokud je nastaven na "1" znamená, že se má kanál zakázat, zatímco "0", že se má povolit (tj. přenos je možný). Zakázat kanál je nutné např. během jeho programování (nastavování módu a velikosti dat).


Komunikace s čipem DSP a mixerem

Nyní bychom měli vědět již všechno, co budeme potřebovat (vřele doporučuji dokumentaci od Miroslava Němečka) k programování DMA a IRQ. Ale teď přejdeme na programování přímo dané zvukové karty. Opět zde nebudu vypisovat všechny možné příkazy a módy, protože bychom tu byli do rána. Vypíši jen ty nejpoužívanější.

Teď, zvuková čast (WAV) karty obsahuje tzv. DSP čip. Ten ji řídí, takže se budeme obracet s našimi požadavky na něj. Zároveň se zde nachází tzv. mixer, kde nastavujete hlasitost různých vstupů a výstupů, výšky, basy, zesílení, a další věci. Můžeme do něj zapisovat a můžeme z něj číst. Ke zvukové kartě jste dostali bázovou adresu, když k ní přičtete různé offsety, dostanete se na různé funkce:


	$4	příkaz pro ovládání mixeru
	$5	zde se pak nacházejí data pro mixer (čtení/zápis)
	$6	zde se resetuje čip (viz. příklady výše)
	$a	kontrolní $AA konstanta
	$c	je-li bit7 = 0, pak můžete na tento port zapsat příkaz a data
	$e	je-li bit7 = 1, pak se dají z DSP číst data
		zároveň tím oznamujeme, že zpracováváme 8bitové IRQ
	$f	čtením oznámíme, že zpracováváme 16bitové IRQ  

Data se do DSP posílají (a čtou z něj) následujícím příkazy v assembleru (je možné je samozřejmě přepsat i do Pascalu pomocí příkazů PORT, ale znáte mne):


procedure DSPwrite(Data : byte); assembler;
asm  
	mov	dx,adr
	add	dx,$c
@cekame:
	in	al,dx
	and	al,$80
	jnz	@cekame
	mov	al,data
	out	dx,al  
end;


function DSPread : byte; assembler;
asm  
	mov	dx,adr
	add	dx,$e
@cekame:
	in	al,dx
	and	al,$80
	jz	@cekame
	mov	al,prikaz
	in	al,dx  
end;  

Příkazy jsou psané tak, že očekávájí již přítomnost karty. Pokud si nejste jisti, doplňte cykl JZ/JNZ na LOOP s určitým počtem opakování a pak prohlašte příkaz za neprovedený. Seznam hlavních příkazů a jejich dat, které se do DSP čipu posílají (vždy zapište byte příkazu a pak hned případná jeho data):


	$1c	nastaví auto-init přehrávání pomocí 8 bitového DMA
		(nejprve je nutné nastavit délku dat pomocí $48)
		(pro všechny karty, SB16 se dá spustit ale i jinak)
	$2c	totéž jako $1c, ale pro záznam zvuku
	$40	u starších karet se zde zapisuje rychlost dat
		závisí na frekvenci, bitáži a počtu kanálů
		pošlete 1 byte dat
	$14	jako $40, ale pro staré DSP v1.0
	$41	nastavení frekvence (náhrada $40) u SB16 (DSP=4.0)
		nejprve pošlete Lo a pak Hi byte frekvence
	$42	totéž jako $41, ale pro záznam zvuku
	$48	nastavení délky přenášených dat (i pro $41)
		u 8 bitové je to počet BYTŮ - 1, u 16bit počet WORDŮ-1
		nejprve Lo a pak Hi byte
		pošlete buď velikost bufferu - 1
		nebo lépe velikost 1/4 či 1/2 mínus 1
	$a4	příkaz nastaví mono/stereo mód pro 16 bitové zvuky
		(určí se to bitem v mixeru)
	$a8	stereo mód pro 8 bitové zvuky
		(většinou je potřeba zapnout stereo i v mixeru)
	$bx	nastavení a spuštění 16 bitového přenosu (SB16)
		dolní nibble má bity 4321
			1	vždy "0"
			2	"0" - hraní, "1" - záznam
			3	"0" - single, "1" - auto-init
			4	"0" - FIFO off, "1" - fronta zapnutá
		pak následuje byte módu:
			bit5	"0" = mono, "1" = stereo
			bit4	"0" = unsigned, "1" = signed
		pak Lo a Hi délka dat (jako u $48) -1
	$cx	totéž, ale pro 8 bitové zvuky (pouze SB16)
	$d0	pozastav 8 bitové hraní (pause)
	$d1	povolení výstupu u starších zvukových karet (DSP 1.0)
	$d3	zakázání reproduktoru (výstupu)
	$d4	pokračuj v 8 bitovém hraní
	$d5	pauza pro 16 bitové
	$d6	konec pauzy pro 16 bitové
	$d9	pauza až na konci aktuálního bloku dat (8 bit)
	$da	pauza na konci bloku dat pro 16 bitová data
	$e0	ověření portu. Pošlete nějaký byte a měli byste ho
		pak přečíst invertovaný (např. $aa -> $55)
	$e1	vrátí verzi DSP čipu (což se hodí)
		čtěte: 1. byte = verze, 2. byte = podverze (pouze SB16)
		Verze: 1 (SB), 2 (SB2), 3 (SBPro), 4 (SB16)
		SB a SBpro vrací pouze 1 byte  

Co se týče příkazu $40, někde se uvádí pro výpočet konstanty vzoreček "256-Round(1000000/Freq)" nebo dokonce "256-Round(1000/Freq[kHz])" nebo "(65536-(256000000 div frekvence)) shr 8". Záleží jen na Vás. Např. pro kvalitu 22050 Hz 8 bitů mono dostanete konstantu 211, pro stereo už to bude 233. U novějších karet se už nastavuje pouze frekvence, která je nezávislá na počtu kanálů a bitů (protože tyto údaje jsou obsaženy ve startovacím příkazu pro SB16).

Než začneme programovat přehrávání zvuku, podíváme se ještě na funkce mixeru. To je velmi užitečná věc. Funkce pro zápis a čtení dat jsou následující (vždy určíte typ dat a pak samotná data):


procedure WriteMixer(Typ,Data : byte); assembler;
asm  
	mov	dx,word ptr adr		(* mov dx,adr *)
	add	dx,$4
	mov	al,typ
	out	dx,al
	inc	dx
	mov	al,data
	out	dx,al  
end;


function WriteRead(Typ : byte) : byte; assembler;
asm  
	mov	dx,adr
	add	dx,$4
	mov	al,typ
	out	dx,al
	inc	dx
	in	al,dx  
end;  

Někdy se ovšem setkáte s požadavkem, kdy potřebujete nastavit nebo vynulovat jen některý bit v údaji. Řešením je přečíst údaj z Mixeru, provést patřičné úpravy a pak ho zase zapsat zpět. Je možné si na to ale napsat už přímo dané funkce:


procedure SetMixer(Typ,Bit : byte); assembler;
asm  
	mov	dx,adr
	add	dx,$4
	mov	al,typ
	out	dx,al
	inc	dx
	in	al,dx
	mov	cl,bit
	mov	bl,1
	shl	bl,cl
	or	al,bl
	out	dx,al  
end;


procedure ClearMixer(Typ,Bit : byte); assembler;
asm  
	mov	dx,adr
	add	dx,$4
	mov	al,typ
	out	dx,al
	inc	dx
	in	al,dx
	mov	cl,bit
	mov	bl,1
	shl	bl,cl
	not	bl
	and	al,bl
	out	dx,al  
end;


procedure InvertMixer(Typ,Bit : byte); assembler;
asm  
	mov	dx,adr
	add	dx,$4
	mov	al,typ
	out	dx,al
	inc	dx
	in	al,dx
	mov	cl,bit
	mov	bl,1
	shl	bl,cl
	xor	al,bl
	out	dx,al  
end;  

A jaká data můžete najít v mixeru? Staré zvukové karty až do typu Sound Blaster Pro (to je dneska také většinou jediný typ, který Vám pro DOSový program milostivě novější karty naemulují, pokud tedy nepoužíváte DosBox) měly jednoduché mixery, které vypadaly takto:


	$4	zvuk (L) 4 bity Hi	Zvuk (R) 4 bity Lo
	$a				Mikrofon 4 bity Lo
	$c	bit 6 (vstup.filtr)	Hi (b3), Line(b2), CD(1), Mic(0)
	$e	bit 1: "1" - stereo, "0" - mono
	$d	bit 6 (výstupní filtr On/Off)
	$22	hlavní hlas. (L)	Hlavní hlas. (R)
	$26	FM (L)			FM (R)
	$28	CD (L)			CD (R)
	$2e	Line (L)		Line (R)  

Monofonní karty se nastavovaly přes R kanál. Zvuková karta SB16 dokáže produkovat i zesílení (1x až 8x po mocninách dvou). Obsahuje tyto parametry (původní registry zůstávají pro zachování kompatibility):


	$30	levý hlavní (master) výstup (bity 3-7)
	$31	pravý hlavní výstup (hlasitost)
	$32	levý zvukový (sound) výstup
	$33	pravý wave výstup (voice)
		(jen bity 7-3 po 2dB)
	$3c	povolení/zakázání výstupů
		(mic=b0,CDr=1,CDl=2,LineR=3,LineL=4,FMr=5,FMl=6)
	$3d	povolení/zakázání L vstupů
	$3e	povolení/zakázání R vstupů
	$41	výstupní zesílení (gain) levé
	$42	výstupní zesílení (gain) pravé
		(bity 7-6, 1x,2x,4x a 8x (Gain=0 je 1x))
	$44	levé nastavení výšek (horní 4 bity)
	$45	pravé nastavení výšek (horní 4 bity)
	$46	levé nastavení basů (horní 4 bity)
	$47	pravé nastavení basů (horní 4 bity)
		(bity 7-4, po 2dB od -16dB do +14dB)
	$80	které IRQ karta používá (!!!)
	$81	které DMA karta používá (!!!)
	$82	určení, který IRQ vyvolal přerušení
		(bit 0 je 8bitové, bit 1 je 16 bitové)  

Jak vidíte, u karty SB16 stačí znát jen bázovou adresu a sama nám pak sdělí, na které IRQ a DMA je nakonfigurovaná, takže zde odpadá složitá detekce. Registr $82 je zde proto, jelikož přerušení může vyvolat jak 8 bitový kanál DMA, tak 16 bitový (pokud jej používáte pro přenos dat), resp. karta na daném kanálu, když počet přenesených dat dosáhne počtu, který jste jí sdělili (příkaz $48).


Tak, konec druhého dílu (nějak se to protahuje). Nastudujte si nyní všechny tyto údaje, protože v příštím dílu se do toho pustíme tak říkajíc po hlavě. Uvidíte ale, že to není tak hrozné, jak to vypadá.


POKRAČOVÁNÍ PŘÍŠTĚ ...

2006-12-06 | Martin Lux
Reklamy: