В обе стороны.

С. А. Андрианов
МИР ПК #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
Команда Описание
14h8-разрядное воспроизведение через DMA без автоинициализации. Команда состоит из 3 байт, за ее кодом следует длина передаваемых данных, уменьшенная на 1
1Ch8-разрядное воспроизведение с автоинициализацией. Команда состоит из 1 байта, длина воспроизводимого блока задается командой 48h
24h8-разрядная запись, аналогичная команде 14h
2Ch8-разрядная запись с автоинициализацией, аналогичная 1Ch
40hЗадание константы времени, 2 байта: после кода команды - константа
41hЗадание частоты дискретизации вывода, 3 байта: после команды 2 байта частоты дискретизации в диапазоне 5000-45 000 Гц
42hЗадание частоты дискретизации ввода, аналогичное 41h
48hЗадание длины передаваемых данных, 3 байта, включая 2 байта данных. Определяет, по истечении какого объема переданных данных должно поступить прерывание от звуковой платы
Bxh16-разрядный ввод-вывод
Cxh8-разрядный ввод-вывод
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.