MOD-Player [C++, RealMode]
Пример программы
Простейшая программа, демонстрирующая работу со звуковой платой в защищенном режиме процессора, приведена в листинге 1. В примере решено было ограничиться лишь воспроизведением 8-битного звука, что обусловлено двумя причинами. Первая из них заключается в том, что все необходимые регистры и команды DSP (Digital Signal Processor - цифровой сигнальный процессор) звуковой платы были описаны ранее в статье Д.В. Солдатенкова, и это позволяет сократить объем журнальной публикации. Вторая причина связана с тем, что подавляющее большинство выпускаемых сегодня звуковых плат являются 16-разрядными и совместимы с Sound Blaster лишь на уровне 8-битного звука, т.е. подавляющее большинство звуковых плат, выпускаемых не Creative Labs, не совместимы с Sound Blaster 16, и соответственно 16-битный звук в них реализуется по-другому. Следовательно, стандарта, даже de facto, для воспроизведения 16-битного звука не существует.
Пример программы написан специально в демонстрационных целях, поэтому, с одной стороны, содержит явное ограничение на длину воспроизводимого участка файла, с другой - количество проверок в нем минимизировано. В начале программы описаны константы, характеризующие аппаратные ресурсы, используемые Sound Blaster. В реальной программе их следует брать из переменных окружения. Все это сделано для того, чтобы сократить размер листинга и сделать его более читаемым.
Программа содержит обработчик прерываний, поступающих от звуковой платы, а также дополнительную процедуру завершения, которая введена для восстановления старого вектора прерывания при любом, в том числе и аварийном выходе из программы.
Процедуры выделения и освобождения нижней памяти называются GetSBMem и FreeSBMem соответственно. Для облегчения переноса программы на другой компилятор или в другой язык программирования в листинге 2 приведены ассемблерные варианты этих процедур.
Выход из программы предусмотрен по аппаратному прерыванию, генерируемому звуковой платой после окончания воспроизведения фрагмента. Если такое прерывание по каким-либо причинам не поступает, программа завершается по тайм-ауту, составляющему чуть менее 3 с, для чего предусмотрена функция, возвращающая текущее значение системной переменной таймера.
Листинг 1. Воспроизведение звука из WAV-файла через звуковую плату Sound Blaster.
program playwavp; {воспроизведение 8-битного звука}
{в защищенном режим
е процессора}
{$D+,L+,I+,R+,S+,V+}
uses dos,winapi;
const
BlasterPort:word = $220; {номер порта Sound Blaster}
BlasterIRQ:word = 5; {номер прерывания Sound Blaster}
BlasterDMAl:word = 1; {номер канала DMA Sound Blaster}
maxlen = 35000; {длина звукового буфера}
{ Seg0040 = $40;}
SouBlas : Boolean = False; {было ли прерывание от SB }
function clock:longint; {функция определения времени}
begin
clock := MemL[Seg0040:$6c];
end;
var
ExitSave:Pointer; {адрес старой программы выхода в DOS}
SBold:pointer;{адрес обработчика прерывания Sound Blaster'a}
SBIRQ:byte; { номер прерывания Sound Blaster'a }
{$F+}
procedure SBint ; interrupt; { обработчик прерывания SB }
begin
SouBlas:=True;
Port[$20]:=$20;
end;
procedure MyExit; {дополнительная процедура при выходе в DOS}
begin
ExitProc:=ExitSave;
SetIntVec(SBIRQ+8,SBold);
end;
{$F-}
procedure IntInit(sbi:byte); {установка нового вектора}
&n
bsp; {прерывания и т.д.}
begin { sbi - номер аппаратного прерывания SB }
if sbi < 8 then SBIRQ:=sbi else SBIRQ:=2;
GetIntVec(SBIRQ+8,SBold);
SetIntVec(SBIRQ+8,@SBint); {переопределение}
&n
bsp; {прерывания SB}
ExitSave:=ExitProc; {переопределение}
&n
bsp; {процедуры выхода}
ExitProc:=@MyExit;
end;
procedure WriteCommand(Comm:byte); { процедура записи}
&n
bsp; {команды в регистр SB }
begin
while (Port[BlasterPort+$0C] and $80) <> 0 do;
Port[BlasterPort+$0C]:=Comm;
end;
procedure NotSupport; { вывод на экран сообщения }
begin
writeln('Format not supported');
halt;
end;
var
longi:longint; {рабочая ячейка}
arr1:pointer; {для выравнивающего массива}
pusto:word; {длина выравнивающего массива}
sndp:pointer; {для массива звуковых отсчетов}
DMAPage:byte; {номер 64K сегмента для записи}
{
в регистр DMA}
DMAOfs :word; {смещение звуковой последовательности}
{
в нижней памяти}
procedure GetSBMem; {выделение буфера }
{нижней памят
и для SB/DMA}
begin
longi := GlobalDosAlloc(16);
arr1 := ptr(longi and $0FFFF,0); {селектор нижней памяти}
longi := $FFFF-(((longi shr 16) shl 4) and $ffff)+1;
pusto := longi; {столько осталось до начала}
{
следующего 64k сегмента}
GlobalDosFree(seg(arr1^));
longi := GlobalDosAlloc(pusto);
arr1 := ptr(longi and $0FFFF,0); {селектор нижней}
&n
bsp; {памяти (выравн.масс.)}
longi := GlobalDosAlloc(maxlen);
sndp := ptr(longi and $0FFFF,0); {селектор нижней}
&n
bsp; {памяти (для звука)}
DMAPage := longi shr 28;
DMAofs := 0;
end;
procedure FreeSBMem; {возвращение нижней памяти в систему}
begin
GlobalDosFree(seg(sndp^));
GlobalDosFree(seg(arr1^));
end;
{*****************************************}
const
dmap : array[0..3]of byte = ($87,$83,$81,$82); {номера}
&n
bsp; {регистров DMA}
var
i : word; {рабочая ячейка}
t,friq:word; {временной параметр/частота дискретизации}
lenfil:word; {длина считываемой части звуковых данных}
snd:file; {звуковой файл}
riff:array[0..15]of char; {массив для чтения}
&n
bsp; {неиспольз. частей заголовка}
Begin {the main program}
if paramcount <> 1 then begin
writeln;
writeln(" Usage: playwavp filename.wav');
writeln;
halt;
end;
IntInit(BlasterIRQ); { переустанавливаем прерывания }
Assign (snd,paramstr(1));
Reset (snd,1);
BlockRead(snd,riff,16); { $00}
BlockRead(snd,longi,4); { $10 длина заголовка}
BlockRead(snd,i,2); { $14}
BlockRead(snd,i,2); { $16 число каналов}
if i <> 1 then notsupport;
BlockRead(snd,friq,2); { $18 частота дискретизации}
Seek(snd,longi+$12); { $1A пропускаем заголовок}
&n
bsp; {до предпосл.слова}
BlockRead(snd,i,2); { разрядность}
if i <> 8 then notsupport;
GetSBMem; {запрашиваем нижнюю память для звука}
BlockRead(snd,longi,4); { 'data'}
BlockRead(snd,longi,4); { длина данных}
if longi > maxlen then lenfil := maxlen else lenfil := longi;
BlockRead(snd,sndp^,lenfil); {звуковые данные}
close (snd);
WriteCommand($D3); {включаем звук}
t:=256 - 1000000 div friq;
WriteCommand($40); {задаем частоту дискретизации}
WriteCommand(t);
Port[$21] := Port[$21] and not (1 shl BlasterIRQ);
&n
bsp; {разрешаем прерывание}
Port[$A]:=BlasterDMAl + 4; {маскируем DMA }
Port[$C]:=0; {сбрасываем триггер}
Port[$B]:=BlasterDMAl + $48; {задаем режим передачи }
Port[$2] := lo(DMAOfs); {задаем адрес буфера}
Port[$2] := hi(DMAOfs);
Port[dmap[BlasterDMAl]] := DMAPage; {задаем 64k страницу}
Port[BlasterDMAl*2 + 1]:=lo(lenfil-1); {длина звуковой}
&n
bsp; {последовательности}
Port[BlasterDMAl*2 + 1]:=hi(lenfil-1);
Port[$A]:=BlasterDMAl; {размаскируем DMA }
WriteCommand($14); {начинаем воспроизведение звука}
WriteCommand(lo(lenfil-1));
WriteCommand(hi(lenfil-1));
longi:=clock; {на всякий случай ограничим по времени}
while (SouBlas = false) and (longi+50 > clock) do ;
if not SouBlas then Port[$20]:=$20; {сбрасываем DMA }
i:=Port[BlasterPort+$0E]; {сбрасываем Sound Blaster}
WriteCommand($D1); {выключаем звук}
Port[$21] := Port[$21] or (1 shl BlasterIRQ);
&n
bsp; {запрещаем прерывание}
FreeSBMem; {возвращаем память в систему}
End.
Листинг 2. Процедуры выделения и освобождения
нижней памяти для звукового буфера
var m1,m2,m3 : word; { флаги процедур выделения памяти: }
{ 4 - успешно, 5 - ошибка, 2 - не выделялась }
procedure GetSBMem; {выделение буфера нижней памяти}
{для SB/DMA}
var j:word;
begin
j := (longint(maxlen) + 15) div 16; {длина блока}
&n
bsp; {в параграфах}
m1 := 2; m2 := 2; m3 := 2;
asm
mov ax,$0100
mov bx,j
int $31 {запрашиваем память}
rcl m1,1 {запоминаем CF}
mov bx,$1000
mov cx,ax
and cx,$fff
sub bx,bc {вычисляем размер до конца сегмента}
cmp j,bx
jb @l1 {если достаточно места, уходим}
push bx
mov ax,$0101
int $31 {возвращаем память}
pop bx
mov ax,$0100
int $31 {забираем память до конца 64k-сегмента}
rcl m2,1 {запоминаем CF}
mov word ptr [arr1+2],dx {сохраняем селектор}
mov ax,$0100
mov bx,j
int $31 {запрашиваем память для звукового буфера}
rcl m3,1 {запоминаем CF}
mov bx,ax
mov ax,0
adc ax,ax
mov m3,ax {запоминаем CF}
@l1:
mov word ptr [sndp+2],dx {сохраняем селектор}
xor dx,dx
mov word ptr [sndp],dx {сохраняем смещение}
mov ax,bx
shl ax,4
mov DMAOfs,ax
shr bx,12
mov DMAPage,bl
end;
if((m1 and 1) or (m2 and 1) or (m3 and 1)) <> 0 then halt;
end;
procedure FreeSBMem; {возвращение нижней памяти в систему}
begin
asm
mov ax,$0101
mov dx,word ptr[sndp+2]
int $31
end;
if m2 = 4 then
asm
mov ax,$0101
mov dx,word ptr[arr1+2]
int $31
end;
end;
