2000 г
В обе стороны.
С. А. Андрианов
МИР ПК #11/99
О программировании звуковых плат Sound Blaster 16 в режиме full duplex.
Первые вычислительные машины были совершенно непохожи на нынешние. Обычно они оснащались лишь набором тумблеров и рядами лампочек и не имели ни клавиатуры, ни дисплея, не говоря уж об аудиоустройствах. Однако уже тогда программисты пытались заставить этих монстров издавать различные звуки, причем иногда даже воспроизводить какое-нибудь музыкальное произведение. Например, они добились того, что магнитные сердечники, используемые в качестве запоминающих устройств, исполняли полонез Огинского.
Но естественно, ЭВМ постоянно совершенствовались, и потому к моменту появления ПК клавиатура и дисплей воспринимались всеми как вполне стандартные устройства ввода-вывода. Не был забыт и звук. В дисплейный блок или клавиатуру, подключаемую к главной ЭВМ, как правило, встраивался маленький динамик, издававший всякие гудки, щелчки, а порой и что-то более сложное. Терминалом большой ЭВМ и руководствовались при создании ПК.
С самого начала IBM PC не повезло со звуком. К сожалению, фирма-разработчик решила, что ее детище будет предназначено исключительно для делового применения. Поэтому при достаточно высокой производительности (16-разрядный процессор) и широких графических возможностях (цветной графический дисплей) этот ПК не только не превосходил, но зачастую и уступал по звучанию своим 8-разрядным собратьям. Его одноразрядный звук использовался в основном лишь для сигнализации о неисправностях аппаратуры или об ошибках оператора.
Однако IBM PC имел открытую архитектуру, и как только звук понадобился (сначала для игр), сразу была создана отдельная аудиоплата, вставляемая в разъем расширения. Такие устройства (Game Blaster фирмы Adlib) умели синтезировать несложный музыкальный звук, но с появлением Sound Blaster стало возможным записывать на IBM-совместимом компьютере и воспроизводить монофонический звук, хотя лишь 8-разрядный. Позднее появились платы, обеспечивающие стереофоническое звучание, а затем и использующиеся для оцифровки 16 разрядов. Правда, если первые звуковые платы старались сделать совместимыми сначала с Creative Sound Blaster, а затем с Creative Sound Blaster Pro (8-разрядные моно- и стереоплаты соответственно), то после перехода на 16-разрядный звук каждый разработчик пошел своим путем.
Что-то похожее происходило позже и с видеоадаптерами. Пока законодателем мод в области ПК считалась IBM, существовали и стандарты де-факто на видеоадаптеры: сначала был CGA, потом EGA, на смену которому пришел VGA... Однако когда IBM уступила лидерство, на рынке ПК появилась масса видеоплат, объединяемых общим названием SuperVGA (SVGA), но совершенно несовместимых друг с другом.
Обратимся снова к аудиоплатам. К тому времени как наметился переход с 8- на 16-разрядный звук, фирма Creative Labs, пионер в области разработки аудиоплат, перестала играть на рынке доминирующую роль. Да и старания производителей защититься от конкурентов путем патентования всех новшеств явно не способствовали формированию нового стандарта де-факто. Но если в области видеоадаптеров благодаря усилиям ассоциации VESA удалось навести хоть какой-то порядок, то с аудиоплатами подобная идея была обречена на провал. Звуковые платы, в отличие от видеоадаптеров, не имеют ПЗУ с драйверами, позволяющими нивелировать особенности аппаратуры и создавать более или менее унифицированный интерфейс с прикладными программами. Да и сама поддержка звука на уровне BIOS не была предусмотрена конструкторами IBM. Все это привело к тому, что до сих пор не появился стандарт на 16-разрядный звук, и послужило, пожалуй, одной из главных причин отказа от DOS в качестве основной платформы для компьютерных игр и перехода на Windows+DirectX.
Однако если в офисе и дома Windows практически вытеснила другие ОС, в некоторых областях профессиональной сферы, например там, где нужны работающие в реальном времени программы, DOS еще не сдала своих позиций. Тем более что отдельные возможности, имеющиеся в DOS, попросту нереализуемы в Windows.
Таблица 1. Регистры микшера
индекс | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
00h | Сброс микшера |
04h | Громкость FM-синтезатора ЛК | Громкость FM-синтезатора ПК |
0Ah | - | Громкость микрофона |
22h | Общий регулятор громкости ЛК | Общий регулятор громкости ПК |
26h | Громкость MIDI ЛК | Громкость MIDI ПК |
28h | Громкость CD ЛК | Громкость CD ПК |
2Eh | Громкость линейного входа ЛК | Громкость линейного входа ПК |
30h | Общий регулятор громкости ЛК | - |
31h | Общий регулятор громкости ПК | - |
32h | Громкость FM-синтезатора ЛК | - |
33h | Громкость FM-синтезатора ПК | - |
34h | Громкость MIDI ЛК | - |
35h | Громкость MIDI ПК | - |
36h | Громкость CD ЛК | - |
37h | Громкость CD ПК | - |
38h | Громкость линейного входа ЛК | - |
39h | Громкость линейного входа ПК | - |
3Ah | Громкость микрофона - |
3Bh | Громкость динамика - | |
3Ch | - | - | - | Лин. ЛК (1) | Лин. ПК (1) | CD ЛК (1) | CD ПК (1) | Микр. (1) |
3Dh | - | MIDI ЛК (2) | MIDI ПК (2) | Лин. ЛК (2) | Лин. ПК (2) | CD ЛК (2) | CD ПК (2) | Микр.(2) |
3Eh | - | MIDI ЛК (3) | MIDI ПК (3) | Лин. ЛК (3) | Лин. ПК (3) | CD ЛК (3) | CD ПК (3) | Микр.(3) |
3Fh | Входной аттенюатор ЛК | - |
40h | Входной аттенюатор ПК | - |
41h | Выходной аттенюатор ЛК | - |
42h | Выходной аттенюатор ПК | - |
43h | - | АРУ |
44h | Тембр высоких частот ЛК | - |
45h | Тембр высоких частот ПК | - |
46h | Тембр низких частот ЛК | - |
47h | Тембр низких частот ПК | - |
Сокращения: ЛК - левый канал, ПК - правый канал, (1) - выходные выключатели микшера, (2) - входные выключатели левого канала микшера, (3) - входные выключатели правого канала микшера, АРУ - включение автоматической регулировки уровня сигнала микрофона. |
Таблица 2. Регистры 0Bh и D6h контроллера DMA
Номер бита | Назначение | Значение |
D0-D1 | Номер канала DMA | 0-3 (для 4-7 - два младших бита) |
D2-D3 | Режим работы | 00 - проверка; 01 - запись в память; 10 - чтение из памяти; 11 - недопустимая комбинация |
D4 | Автоинициализация | 0 - нет; 1 - есть |
D5 | Направление изменения адреса | 0 - увеличение; 1 - уменьшение |
D6-D7 | Тип передачи | 00 - по требованию; 01 - одиночная передача; 0 - блочная передача; 11 - каскадный режим |
Когда фирма Creative Labs разрабатывала свою первую 16-разрядную плату Sound Blaster 16, она, видимо, и не предполагала, что эту аудиоплату можно будет использовать одновременно и для записи, и для воспроизведения звука (работа в так называемом режиме full duplex). Наверное, именно поэтому данный режим не поддерживается и в драйверах для Windows. Однако его можно запрограммировать в DOS. Правда, передача в таком случае получается несколько несимметричной: в одном направлении звук будет 16-, а в другом - 8-разрядным. Впрочем, вряд ли это серьезно ограничивает реальные применения подобной технологии. При использовании ПК для общения "как по телефону" 8-разрядного звука вполне достаточно, а при записи и сведении фонограммы одновременно с 16-разрядным режимом записи можно обойтись контрольным прослушиванием всего в 8 разрядов. Когда же ПК играет роль измерительного прибора, а звуковая плата - дешевых ЦАП/АЦП, разрядности получаемого сигнала обычно бывает достаточно, по крайней мере в одну сторону. В противном случае следует заменить звуковую плату специализированным прибором. Правда, такая несимметричность затрудняет использование ПК в качестве гитарного процессора, ревербератора или эквалайзера, но вообще-то для концертной деятельности компьютер не слишком подходит.
* * *
Программа, иллюстрирующая, каким образом можно применять звуковую плату Creative Labs Sound Blaster 16 или совместимые с ней, например AWE32, приведена в листинге 1. Она реализует простейшее эхо: записанный через микрофон звук спустя некоторое время воспроизводится через громкоговорители, подключенные к выходу аудиоплаты.
Листинг 1. Простейшее цифровое эхо
program echo;
uses dsp_dma,getsbinf;
{Ввод звука - 16 бит со знаком, вывод - 8 бит со знаком.}
const
BufSize = 2*1024; { размер буфера DMA }
TimeConst = 156; { 156 - примерно 10 кГц }
HalfBufToFill : integer = 0;
{ которая половина буфера DMA свободна }
BothBuf : byte = 0;
{ индикатор заполнения обоих буферов }
type
RecBufType = array[0..BufSize-1]of integer;
{ для буфера DMA записи }
PlayBufType = array[0..BufSize-1]of shortint;
{ для буфера DMA воспроизведения }
var
RecBuf : ^RecBufType; { буфер DMA для записи}
PlayBuf : ^PlayBufType;{буфер DMA для воспроизведения}
inpage, outpage : word; {страницы для буферов DMA}
inoffset, outoffset : word; {смещения для буферов DMA}
{$F+}
procedure SBint;interrupt;
{обработчик прерывания от звуковой платы}
var
intstat : integer;
i : integer;
begin
Port[base + $04] := $82;
{проверяем, по какому каналу пришло прерывание}
intstat := Port[base + $05] and 3;
BothBuf := BothBuf or intstat;
if (intstat and 2 <> 0) then begin {16-битовый канал}
i := Port[base + $0F];
end;
if (intstat and 1 <> 0) then begin {8-битовый канал}
i := Port[base + $0E];
end;
if BothBuf = 3 then begin
{если прошли прерывания от обоих каналов}
for i := 0 to BufSize div 2 - 1 do
PlayBuf^[HalfBufToFill*BufSize div 2 + i] :=
hi(RecBuf^[HalfBufToFill*BufSize div 2 + i]);
write(HalfBufToFill,#8);
{выводим на экран номер половинки буфера}
HalfBufToFill := HalfBufToFill xor 1;
BothBuf := 0;
end;
if (irq > 8) then
{для IRQ 10, посылаем сигнал EOI во второй контроллер}
Port[$A0] := $20;
Port[$20] := $20; { посылаем EOI в первый контроллер}
end;
{$F-}
var
SkipLength : longint;
{размер памяти до границы 64-Кбайт страницы}
SkipBlock : pointer;
begin
writeln(' Эхо - Sound Blaster 16 в ',
'режиме full duplex');
writeln(' для завершения работы ',
'нажмите Enter');
GetBlasterInfo; {определяем характеристики карты}
if (cardtype <> 6) then begin
{Проверка, что на плате возможен full duplex}
writeln(cardtype);
writeln(
'Для работы программы необходим Sound Blaster 16.');
halt;
end;
if (dma8 = dma16) then begin
writeln('Ошибка: совпадение 8-битового и ',
'16-битового каналов DMA.');
halt;
end;
SetMixer; {сброс DMAC и установки микшера}
getmem(SkipBlock,16);
{проверка, чтобы буферы не пересекали границу 64К}
SkipLength := $10000 - (seg(SkipBlock^) shl 4)
- ofs(SkipBlock^);
freemem(SkipBlock,16);
if SkipLength > 3*BufSize then
getmem(SkipBlock,SkipLength);
getmem(RecBuf,2*BufSize);
{выделение памяти для буфера записи}
inpage := ((longint(seg(RecBuf^)) * 16)
+ ofs(RecBuf^)) div $10000;
inoffset := ((longint(seg(RecBuf^)) * 16)
+ ofs(RecBuf^)) and $FFFF;
getmem(PlayBuf,BufSize);
{выделение памяти для буфера воспроизведения}
outpage := ((longint(seg(PlayBuf^)) * 16)
+ ofs(PlayBuf^)) div $10000;
outoffset := ((longint(seg(PlayBuf^)) * 16)
+ ofs(PlayBuf^)) and $FFFF;
fillchar(PlayBuf^,BufSize,0);
{очистка буфера воспроизведения}
EnableInterrupt( @SBint);
SetupDMA(dma16,inpage,inoffset,BufSize, $54);
{DMA на ввод}
SetupDSP($BE,$10,BufSize div 2,TimeConst);
{16 бит со знаком FIFO моно}
SetupDMA(dma8,outpage,outoffset,BufSize, $58);
{DMA на вывод}
SetupDSP($C6,$10,BufSize div 2,TimeConst);
{8 бит со знаком FIFO моно}
readln;
dspout($D5); {приостанавливаем 16-битовый ввод-вывод}
dspout($D0); {приостанавливаем 8-битовый ввод-вывод}
DisableInterrupt;
freemem(PlayBuf,BufSize);
freemem(RecBuf,2*BufSize);
if SkipLength < 3*BufSize then
freemem(SkipBlock,SkipLength);
end.
Сначала необходимо убедиться, что звуковая плата способна работать в режиме full duplex. Проще (и безопаснее) всего это сделать с помощью переменной окружения 'BLASTER'. Подобным способом следует определить и базовый адрес порта ввода-вывода, а также номера используемых IRQ и канала DMA. Программа, выполняющая разбор переменной окружения, приведена в листинге 2. Плата должна быть 6-го типа, а номера 8- и 16-разрядного каналов DMA - различаться.
Листинг 2. Извлечение данных из переменной окружения
unit GetSBInf;
interface
var
base :integer; { базовый адрес ввода-вывода}
irq :integer; { номер IRQ }
dma8 :integer; { 8-битный канал DMA }
dma16 :integer; { 16-битный канал DMA }
midi :integer; { порт MIDI }
cardtype :integer; { номер типа платы }
procedure GetBlasterInfo; {извлечение информации о плате}
implementation
uses dos;
var
s : string; {переменная окружения 'BLASTER'}
e : byte; {позиция в этой строке}
function str2hex:word;
{преобразует последовательность hex-цифр в число}
var
val : word;
begin
val := 0;
inc(e);
while (s[e] <> ' ') and (s[e] <> char(0)) and
(e <= length(s)) do begin
case UpCase(s[e]) of
'0'..'9' : val := val * 16
+ (byte(s[e]) - byte('0'));
'A'..'F' : val := val * 16
+ (byte(s[e]) - byte('A') + 10);
else begin
writeln(
'Ошибка в цифровых параметрах переменной окружения');
halt;
end;
end;
inc(e);
end;
str2hex := val;
end;
procedure GetBlasterInfo; {информация о плате}
begin
s := getenv('BLASTER');
e := 1;
if (length(s)>0) then begin
while (e < length(s)) do begin
case UpCase(s[e]) of
'A':base := str2hex;
'I':irq := str2hex;
'D':dma8 := str2hex;
'H':dma16 := str2hex;
'P':midi := str2hex;
'T':cardtype := str2hex;
end; {case}
inc(e);
end; {while}
end else begin
writeln(
'Отсутствует переменная окружения BLASTER');
halt;
end; {if}
end;
end.
Затем следует проинициализировать DSP (Digital Signal Processor - цифровой процессор сигналов) и установить режим микшера, для управления которым имеются два адреса портов: базовый+4 (для задания номера регистра) и базовый+5 (для записи/чтения нужной величины). Назначение регистров микшера приведено в табл. 1.
Несколько пояснений к табл. 1. Регистры до 2Еh включительно служат для совместимости с предыдущими моделями Sound Blaster, однако, поскольку глубина регулировки уровня в последних моделях возросла, необходимо ввести новые регистры. Старые дублируют старшие биты новых регистров того же назначения. Шаг регулировки громкости у старых регистров - 4 дБ, а у новых - 2 дБ. Появление регистра 3Сh позволяет отключить источники сигнала без изменения положения регуляторов уровня, а добавление регистров 3Dh-3Eh - подключать входные сигналы в любом порядке. Например, можно подсоединить правый канал CD к левому звуковой платы, а правый канал линейного входа смешать с микрофоном и снова послать в правый канал. Кроме того, появились входные и выходные аттенюаторы с шагом 6 дБ и регуляторы тембра с шагом 2 дБ, а также стала возможной автоматическая регулировка уровня микрофонного входа. В случае монофонического сигнала все регулировки осуществляются по левому каналу.
Таблица 3. Формат первого байта команды DSP Bx/Cx
Номер бита | Назначение | Значение |
D0 | Зарезервирован | 0 |
D1 | FIFO | 0 - выключен; 1 - включен |
D2 | Автоинициализация | 0 - режим одного цикла; 1 - режим с автоинициализацией |
D3 | Вид преобразования | 0 - цифроаналоговое (воспроизведение); 1 - аналого-цифровое (запись) |
D4-D7 | Разрядность | 1011 (Bh) - 16 разрядов; 1100 (Сh) - 8 разрядов |
Примечание: другие комбинации соответствуют остальным командам |
Таблица 4. Формат второго байта команды DSP Bx/Cx
Номер бита | Назначение | Значение |
D0-D3 | Зарезервированы | 0000 |
D4 | Представление отсчетов | 0 - беззнаковое; 1 - знаковое |
D5 | Число каналов (-1) | 0 - моно; 1 - стерео |
D6-D7 | Зарезервированы | 00 |
Таблица 5. Регистр статуса прерываний
Номер бита | Источник прерывания |
D0 | 8-разрядный ввод-вывод |
D1 | 16-разрядный ввод-вывод |
D2 | Внешний MIDI-интерфейс (MPU-401) |
D3-D7 | Зарезервированы |
После сброса DSP и установки режима работы микшера следует создать в оперативной памяти два буфера: для записываемого звука и для воспроизводимого. Поскольку и запись и воспроизведение будут осуществляться через DMAC (Direct Memory Access Controller - контроллер прямого доступа к памяти), к расположению буферов предъявляются некоторые дополнительные требования. Во-первых, они должны находиться в нижнем мегабайте адресного пространства. В реальном режиме работы процессора это выполняется всегда, а о том, как сделать такое в защищенном, рассказано в статье "Программирование Sound Blaster в защищенном режиме процессора" (см. "Мир ПК", № 3/98, с. 48). Во-вторых, буфер не должен пересекать границы 64-Кбайт страниц, поэтому при выделении памяти под него сначала следует проверить, хватит ли места для размещения буферов записи и воспроизведения до конца текущей страницы. Если его окажется недостаточно, то нужно запросить всю память до конца данной страницы, чтобы начало свободной памяти (кучи) совпало с началом следующей, где будут размещены буферы.
Для каждого из буферов определяются номер 64-Кбайт страницы и смещение в ней, которые надо затем сообщить контроллеру прямого доступа к памяти (DMAC). Процедуры работы с DMAC и цифровым сигнальным процессором (DSP) приведены в листинге 3. При инициализации режим работы контроллера необходимо записать в регистр 0Bh для 8-разрядного режима или в регистр D6h - для 16-разрядного. Значения отдельных битов этих регистров приведены в табл. 2.
Запись и воспроизведение звука - процессы непрерывные и требующие одновременной работы как пары DMAC-звуковая плата, так и процессора для подготовки данных или их использования. Поэтому возникает вопрос, каким образом организовать работу, чтобы процессор и DMAC не мешали друг другу, используя одну и ту же область памяти. Выход был найден. Звуковой буфер стали делить на две части, причем в DMAC передается полная длина буфера, а в DSP звуковой платы - только половина ее. Тогда аппаратные прерывания будут генерироваться в начале и в середине периода воспроизведения всего буфера. А в случае, когда DMAC работает с первой половиной буфера, процессор может обрабатывать вторую, и наоборот.
Листинг 3. Работа с DSP и DMA
unit DSP_DMA;
interface
procedure DspOut(val:byte); {выводит байт на DSP}
procedure SetupDMA(dmach,page,ofs,DMAcount,dmacmd:word);
{установка режима DMA}
procedure SetupDSP(dspcmd, mode, DSPcount, tc:word);
{установка режима DSP}
procedure SetMixer;
{сброс платы и выбор источника сигнала}
procedure EnableInterrupt(newvect:pointer);
{установка векторов прерываний}
procedure DisableInterrupt;
{восстановление векторов прерываний}
implementation
uses getsbinf,crt,dos;
var
intvecsave :pointer; { старый вектор прерывания}
intrnum :integer; { номер прерывания }
intrmask :integer; { маска прерывания }
{ структура, содержащая данные контроллера DMA }
type
DmaPortRec = record
addr,count,page : byte;
end;
const
DmaPorts : array[0..7]of DmaPortRec = (
(addr:$00; count:$01; page:$87), {0}
(addr:$02; count:$03; page:$83), {1}
(addr:$04; count:$05; page:$81), {2 не используется}
(addr:$06; count:$07; page:$82), {3}
(addr:$00; count:$00; page:$00), {4 не используется}
(addr:$C4; count:$C6; page:$8B), {5}
(addr:$C8; count:$CA; page:$89), {6}
(addr:$CC; count:$CE; page:$8A)); {7}
procedure DspOut(val:byte);{выводит байт в DSP}
begin
while (Port[base + $0C] and $80) <> 0 do;
Port[base + $0C] := val;
end;
function DspIn:byte;{читает байт из DSP}
begin
while (Port[base + $0E] and $80) = 0 do;
dspin := Port[base + $0A];
end;
procedure SetupDMA(dmach,page,ofs,DMAcount,dmacmd:word);
{ Программирует контроллер DMA}
{ для 8- или 16-разрядного канала}
var
mask,mode,ff : byte;
begin
if (dmach < 4) then begin
mask := $0A;
mode := $0B;
ff := $0C;
end else begin
mask := $D4;
mode := $D6;
ff := $D8;
ofs := (ofs shr 1) + ((page and 1) shl 15);
end;
Port[mask] := 4 or dmach; { маскируем DMA}
Port[FF] := 0; { сбрасываем триггер-защелку}
Port[mode] := dmacmd or (dmach and 3);
{ уст.режима DMA}
Port[dmaports[dmach].addr] := lo(ofs);
{ младший байт адреса}
Port[dmaports[dmach].addr] := hi(ofs);{ старший байт}
Port[dmaports[dmach].page] := page; { номер страницы}
Port[dmaports[dmach].count] := lo(DMAcount-1);
{ младший байт счетчика}
Port[dmaports[dmach].count] := hi(DMAcount-1);
{ старший байт}
Port[mask] := (dmach and 3); { сброс бита маски}
end;
procedure SetupDSP(dspcmd, mode, DSPcount, tc:word);
{ Программирует DSP звуковой платы}
begin
DspOut($40); {установка константы времени}
DspOut(tc);
DspOut(dspcmd); {команда Bx/Cx}
DspOut(mode);
DspOut(lo(DSPcount-1));
DspOut(hi(DSPcount-1));
end;
procedure SetMixer;{сброс платы и выбор источника сигнала}
var
val:byte;
begin
Port[base + $06] := 1; {сброс DSP}
delay(1);
Port[base + $06] := 0;
if (dspin <> $AA) then {проверка готовности}
writeln('Sound Blaster не готов.');
Port[base + $04] := $3D;
Port[base + $05] := 1;
{ левый канал:источник сигнала - микрофон}
{ Port[base + $04] := $3E; {для моно - не обязательно}
{ Port[base + $05] := 1; }
{ правый канал:источник сигнала - микрофон}
Port[base + $04] := $3C;
Port[base + $05] := 0;
{ на выходе отключаем все, что можно}
end;
procedure EnableInterrupt(newvect:pointer);
{установка векторов прерываний}
var intrmask1:word;
begin
if (irq < 8) then {вычисляем номера прерывания}
intrnum := irq + 8 { для IRQ 0-7 прерывания 8-15.}
else
intrnum := irq - 8 + $70;
{ для IRQ 8-15 прерывания 70H-78H.}
intrmask := 1 shl irq; {маска}
GetIntVec(intrnum,intvecsave);
{ сохраняем старый вектор}
SetIntVec(intrnum, newvect);
{ устанавливаем новый вектор}
intrmask1 := intrmask;
{разрешаем прерывания}
Port[$21] := Port[$21] and not intrmask1;
intrmask1 := intrmask1 shr 8;
Port[$A1] := Port[$A1] and not intrmask1;
end;
procedure DisableInterrupt;
{восстановление векторов прерываний}
var intrmask1:word;
begin
intrmask1 := intrmask; {запрещаем прерывания}
Port[$21] := Port[$21] or intrmask1;
intrmask1 := intrmask1 shr 8;
Port[$A1] := Port[$A1] or intrmask1;
SetIntVec(intrnum,intvecsave);{восстанавливаем вектор}
end;
end.
После программирования DMAC то же самое проделывается и с DSP звуковой платы. Сначала надо установить частоту дискретизации, сообщив ему константу времени
t = 256 - 1 000 000 / f,
где f - частота дискретизации.
Затем следует задать команду на запись/воспроизведение звука. Для Sound Blaster 16 проще всего выбрать команды Bx/Cx, состоящие из четырех байтов: Command, Mode, LenLo, LenHi.
Формат первого байта Command приведен в табл. 3, а второго байта Mode - в табл. 4.
Байты LenLo и LenHi - младший и старший в соответствии с длиной передаваемого блока, уменьшенной на единицу.
Команды Bx/Cx позволяют задавать как знаковый, так и беззнаковый вид представления отсчетов. При знаковом отсчет представляет собой целое число со знаком, принимающее значение 0 при отсутствии входного сигнала, при беззнаковом - целое число без знака, равное 80h для 8-разрядного режима и 8000h для 16-разрядного при отсутствии входного сигнала.
Стандартом де-факто является представление 8-разрядных отсчетов в беззнаковой форме, а 16-разрядных - в знаковой, однако для упрощения процедуры преобразования в приводимой программе обе величины выбраны знаковыми.
Таблица 6. Команды DSP
Команда | Описание |
14h | 8-разрядное воспроизведение через DMA без автоинициализации. Команда состоит из 3 байт, за ее кодом следует длина передаваемых данных, уменьшенная на 1 |
1Ch | 8-разрядное воспроизведение с автоинициализацией. Команда состоит из 1 байта, длина воспроизводимого блока задается командой 48h |
24h | 8-разрядная запись, аналогичная команде 14h |
2Ch | 8-разрядная запись с автоинициализацией, аналогичная 1Ch |
40h | Задание константы времени, 2 байта: после кода команды - константа |
41h | Задание частоты дискретизации вывода, 3 байта: после команды 2 байта частоты дискретизации в диапазоне 5000-45 000 Гц |
42h | Задание частоты дискретизации ввода, аналогичное 41h |
48h | Задание длины передаваемых данных, 3 байта, включая 2 байта данных. Определяет, по истечении какого объема переданных данных должно поступить прерывание от звуковой платы |
Bxh | 16-разрядный ввод-вывод |
Cxh | 8-разрядный ввод-вывод |
D0h | Пауза 8-разрядного ввода-вывода |
D1h | Выключение динамика |
D3h | Включение динамика |
D4h | Продолжение 8-разрядного ввода-вывода, приостановленного командой D0h |
D5h | Пауза 16-разрядного ввода-вывода |
D6h | Продолжение 16-разрядного ввода-вывода, приостановленного командой D5h |
D8h | После этой команды чтение из DSP возвращает статус динамика: 0 - выключен; FFh - включен |
D9h | Выход из 16-разрядного ввода-вывода с автоинициализацией |
DAh | Выход из 8-разрядного ввода-вывода с автоинициализацией |
E1h | После этой команды чтение 2 байт из DSP приведет к получению номера версии DSP, причем 1-й байт - старший, а 2-й - младший |
После программирования микшера следует установить свои процедуры обработки прерываний от звуковой платы и только потом можно будет задавать режимы DMA и DSP. Затем процессор свободен для выполнения любой другой работы, например с экраном, как это практикуется в компьютерных играх. В данной же программе просто происходит ожидание ввода с клавиатуры. Однако время от времени работу процессора будут приостанавливать прерывания, поступающие со звуковой платы по окончании пересылки очередной порции данных. В задачу обработчика прерываний входит определение номера канала, по которому пришло прерывание. Дело в том, что и 8-разрядный, и 16-разрядный ввод-вывод, и даже внешний MIDI-интерфейс (MPU-401) генерируют одно и то же аппаратное прерывание. Для того чтобы различать их между собой, в адресном пространстве регистров микшера имеется порт номер 82h (регистр статуса прерываний), определяющий источник прерывания (табл. 5).
Обработчик прерывания должен сообщить звуковой плате, что ее прерывание принято и обработано, для чего необходимо осуществить чтение из порта 0Eh или 0Fh для 8- либо 16-разрядного режимов соответственно.
После прихода прерываний от канала записи и от канала воспроизведения можно считать, что соответствующие половины буферов записи и воспроизведения уже обработаны звуковой платой и пора копировать данные из одного буфера в другой. Так как в обоих случаях была выбрана одинаковая (знаковая) форма представления данных, то их преобразование сводится лишь к переписыванию старших байтов значений двухбайтовых звуковых отсчетов из входного буфера в выходной.
По завершении отработки прерывания следует осведомить об этом контроллер прерываний (с учетом каскадирования).
После нажатия на программа приостанавливает и 8-, и 16-разрядные операции ввода-вывода и восстанавливает векторы прерываний.
Выше приведен выборочный список команд DSP, которые применяются при записи и воспроизведении звука (табл. 6). Здесь не рассматриваются непосредственный ввод-вывод, не использующий DMAC, ввод-вывод с компрессией и MIDI-команды.
ОБ АВТОРЕ
Андрианов Сергей Андреевич - канд. техн. наук; e-mail: pcworld@pcworld.ru или fidonet: 2:5017/11.40.