/****************************************************************************
    SPHINX Programming (C) 2001.
    NAME:  DPMISERV.H--
    DESCRIPTION: Библиотека для работы с соунд бластером
    LAST MODIFIED:  21-05-2001 07:08:43
 ----------------------------------------------------------------------------
    PROCEDURES DEFINED IN THIS FILE:
     #ifdef __MSDOS__
      void fastcall SetupDma16Bit(BX,CX,DI);
      void fastcall PlaySample8BitVoice(BX,DI,CX,EDX);
      void fastcall PlaySample8BitRandomVoice(BX,DI,EDX);
     #endif

     #ifdef __DOS32__
      void fastcall PlaySample8BitVoice(EBX,EDI,ECX,EDX);
      void fastcall PlaySample8BitRandomVoice(EBX,EDI,EDX);
     #endif

     void fastcall InitSb();
     void fastcall DeInitSb();
     byte fastcall ResetDsp();
     byte fastcall ReadDsp();
     void fastcall Speaker(unsigned int AX);
     void fastcall SetupDma8Bit(BX,CX,DI,AL);
     word fastcall GetDmaPos();
     byte fastcall SbGetEnvirenmentConfig();
     word fastcall HexAscii2Word(AX,unsigned int SI);       // Cooperative
     void fastcall ResetDma();                              // Cooperative
     void fastcall StopPlaying();                           // Cooperative
     byte fastcall ReadMixer(AL);                           // Cooperative
     void fastcall SbSetMonoOutput();                       // Cooperative
     void fastcall SbSetStereoOutput();                     // Cooperative
     void fastcall SetRate(EBX);                            // Cooperative
     void fastcall EnableIrq();                             // Cooperative
     void fastcall WriteDsp(AH);                            // Cooperative
     void fastcall WriteMixer(AL,AH);                       // Cooperative
     void fastcall ResetMixer();                            // Cooperative
     void PlaySample8Bit(word Seg,Offs,Length);             // Cooperative
     void fastcall PausePlaying();                          // Cooperative
     void fastcall ContinuePlaying();                       // Cooperative
     void fastcall GetSbType();                             // Cooperative
     word fastcall GetDspVersion();                         // Cooperative
     void fastcall EssWriteReg(BH,BL);                      // Cooperative
     byte fastcall EssReadReg(BL);                          // Cooperative
 ----------------------------------------------------------------------------
    Alexey Dryamov (Din)
    E-Mail  : _Din_@Mail.ru
              _Din_@nm.ru
              Din@RbcMail.ru
    FidoNet : 2:5055/141.11

****************************************************************************/

#ifndef _SOUNDBLASTER_
 #define _SOUNDBLASTER_ TRUE
#endif



// Объявления процедур //////////////////////////////////////////////////////
#ifdef __MSDOS__
 void fastcall SetupDma16Bit(BX,CX,DI);
 void fastcall PlaySample8BitVoice(BX,DI,CX,EDX);
 void fastcall PlaySample8BitRandomVoice(BX,DI,EDX);
#endif

#ifdef __DOS32__
 void fastcall PlaySample8BitVoice(EBX,EDI,ECX,EDX);
 void fastcall PlaySample8BitRandomVoice(EBX,EDI,EDX);
#endif

void fastcall InitSb();
void fastcall DeInitSb();
byte fastcall ResetDsp();
byte fastcall ReadDsp();
void fastcall Speaker(unsigned int AX);
void fastcall SetupDma8Bit(BX,CX,DI,AL);
word fastcall GetDmaPos();
byte fastcall SbGetEnvirenmentConfig();
word fastcall HexAscii2Word(AX,unsigned int SI);       // Cooperative
void fastcall ResetDma();                              // Cooperative
void fastcall StopPlaying();                           // Cooperative
byte fastcall ReadMixer(AL);                           // Cooperative
void fastcall SbSetMonoOutput();                       // Cooperative
void fastcall SbSetStereoOutput();                     // Cooperative
void fastcall SetRate(EBX);                            // Cooperative
void fastcall EnableIrq();                             // Cooperative
void fastcall WriteDsp(AH);                            // Cooperative
void fastcall WriteMixer(AL,AH);                       // Cooperative
void fastcall ResetMixer();                            // Cooperative
void PlaySample8Bit(word Seg,Offs,Length);             // Cooperative
void fastcall PausePlaying();                          // Cooperative
void fastcall ContinuePlaying();                       // Cooperative
void fastcall GetSbType();                             // Cooperative
word fastcall GetDspVersion();                         // Cooperative
void fastcall EssWriteReg(BH,BL);
byte fastcall EssReadReg(BL);



// Подключение нужных библиотек /////////////////////////////////////////////
// Подключение библы для работы с фиксированной точкой (fixed)
#include "fixed.h--"
// Подключение #define-ов
#include "sb.inc"
// Подключение библиотеки для микширования
#include "mix.h--"
// Подключени библы для работой с MOD-ами, если это нужно
#ifdef _USEMOD_
 #include "modplay.h--"
#endif
#ifdef __DOS32__
 // Подключение библы для работы с DPMI сервисом
 #include "dpmiserv.h--"
#endif




// Временные переменные для отладки библы ///////////////////////////////////
:byte IrqCircle={0,4,'|','/','-','\\'};



/////////////////////////////////////////////////////////////////////////////
// Общие функции ////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

// Запись значения в регистр ESS
// BH - номер регистра ESS
// BL - значение
:void fastcall EssWriteReg(BH,BL)
{
 // Записываем в DSP номер регистра
 WriteDsp(BH);
 // Записываем в DSP значение регистра
 WriteDsp(BL);
}



// Чтение значения из регистра ESS
// BL - номер регистра
// На выходе: значение регистра
:byte fastcall EssReadReg(BL)
{
 // Записываем команду чтения в DSP
 WriteDsp(EssWriteCommand);
 // Записываем номер регистра в DSP
 WriteDsp(BL);
 // Считываем значение регистра из DSP
 ReadDsp();
}



// Преобразование HEX строки в вордовское число /////////////////////////////
// AX    - сегмент (селектор) строки с 16-ричным значением
// (E)SI - смещение к строке с 16-ричным значением
:word fastcall HexAscii2Word(AX,unsigned int SI)
{
 $push DS

  // Устанавливаем DS на сегмент строки
  DS=AX;

  // Обнуляем значение
  BX=0;

  // Узнаем значение
  loop(){
   // Считываем символ
   $lodsb
   IF (AL>='0')&&(AL<='9') AL-='0';
   ELSE IF (AL>='A')&&(AL<='F') AL-='A'-10;
   ELSE IF (AL>='a')&&(AL<='f') AL-='a'-10;
   ELSE BREAK;                              // Выходим, если символ не тот
   BX=BX<<4; BL+=AL;                        // Перерассчитываем значение
  }

 $pop DS

 // Возвращаем значение
 return(BX);
}



// Сброс DMA ////////////////////////////////////////////////////////////////
:void fastcall ResetDma()
{
 AH=SbDma; AL=AH;
 outp(AL+ResetCmd,DmaMask8Bit);
 outp(0,00Ch);
 outp(AH,DX);
}



// Остановка воспроизведения ////////////////////////////////////////////////
:void fastcall StopPlaying()
{
 // Выключение спикера (DSP command 0D3h)
 Speaker(FALSE);

 SWITCH(SbType){
  CASE SoundBlaster20...SoundBlasterPro:
   // Останавливаем 8 битную DMA операцию
   // Halt DMA Operation, 8-bit command (0D0h - for virtual speaker)
   WriteDsp(HaltDma);
   // Выходим из DMA операции с автоинициализацией
   // Exit Auto-Initialize DMA Operation, 8-bit command (0DAh)
   WriteDsp(ExitDma8BitAutoInitDac);
   // Останавливаем 8 битную DMA операцию
   // Halt DMA Operation, 8-bit command (0D0h - for virtual speaker)
   WriteDsp(HaltDma);
   BREAK;
//  CASE SoundBlaster10:
//   BREAK;
//  CASE SoundBlaster16:
//   BREAK;
//  CASE SoundBlasterAwe32:
//   BREAK;
  CASE SoundBlasterEss:
   // Останавливаем звук
   EssWriteReg(0B8h,EssReadReg(0B8h)^1);
   // Выходим из расширенного режима работы ESS-ки
   WriteDsp(EssExitExtendedMode);
   // Делаем сброс DSP
   ResetDsp();
   BREAK;
 }
}



// Чтение из порта миксера //////////////////////////////////////////////////
// AL - index
// return: Значение по индексу
:byte fastcall ReadMixer(AL)
{
 // Записываем index в индексный порт миксера (02x04h)
 outp(AL,SbIoAddr+MixerAddress);
 // Считываем данные из порта данных миксера (02x05h)
 inp(DX+1);
}



// Устанавливает моно вывод на SB ///////////////////////////////////////////
:void fastcall SbSetMonoOutput()
{
 WriteDsp(DisableStereoInputMode);
 AH=ReadMixer(MixerOutputStereoSelect)&!2; // &!2
 WriteMixer(MixerOutputStereoSelect,AH);
}



// Устанавливает моно вывод на SB ///////////////////////////////////////////
:void fastcall SbSetStereoOutput()
{
 WriteDsp(EnableStereoInputMode);
 AH=ReadMixer(MixerOutputStereoSelect)|2;
 WriteMixer(MixerOutputStereoSelect,AH);
}



// Установка rate звука /////////////////////////////////////////////////////
// EBX - rate
:void fastcall SetRate(EBX)
{
 // TimeConstant = 256 - (1000000 / (SampleChannels * SampleRate))
 EAX=1000000/EBX; EBX=65536-EAX;
 // Выбираем комманду TimeConstant
 WriteDsp(TimeConstant);
 // Записываем значение
 WriteDsp(BL);
}



// Узнать версию DSP ////////////////////////////////////////////////////////
:word fastcall GetDspVersion()
{
 // Узнаем версию DSP
 WriteDsp(DspVer);
 // Major version
 BH=ReadDsp();
 // Minor version
 ReadDsp();
 AH=BH;
}



/*
 SoundBlaster 1.0           1.??  (1.05???)
 SoundBlaster 1.5           1.??  (1.05???)
 SoundBlaster 2.0           2.xx  (2.01)
 SoundBlaster Pro           3.00  (???)
 SoundBlaster Pro 2         3.01+ (3.01, 3.02)
 SoundBlaster 16            4.0x  (4.04, 4.05)
 SoundBlaster 16 SCSI-2     4.11  (4.11)
 SoundBlaster AWE32         4.12+ (4.12)
*/
// Узнает и сохраняет тип SoundBlaster-а в переменной SbType ////////////////
:void fastcall GetSbType()
{
 // Узнаем версию DSP
 GetDspVersion();

 IF (AX>=400h) AX=300h;

 // Определяем по версии DSP тип соунд бластера (SbType)
 SWITCH(AX){
  CASE 0...1FFh:
   AL=SoundBlaster10;
   ECX=22222;
   BL=FALSE;
   BREAK;
  CASE 200h...2FFh:
   AL=SoundBlaster20;
   ECX=45454;
   BL=FALSE;
   BREAK;
  CASE 300h...3FFh:
   AL=SoundBlasterPro;
   ECX=44100;
   BL=TRUE;
   BREAK;
  CASE 400h...411h:
   AL=SoundBlaster16;
   ECX=45454*2;
   BL=TRUE;
   BREAK;
  DEFAULT:
   AL=SoundBlasterAwe32;
   ECX=45454*2;
   BL=TRUE;
 }
 SbType=AL;
 SbRate=ECX;
 SbStereo=BL;
 BL=0; IF (ECX>=23000) BL++;
 SbAutoInit=BL;
// SbAutoInit=FALSE;

// SbType=SoundBlasterEss;
// SbRate=44900*2;
// SbRate=44100;
// SbStereo=TRUE;
// SbAutoInit=TRUE;
}



// Продолжение приостановленного воспроизведения ////////////////////////////
:void fastcall ContinuePlaying()
{
 // Continue Dma Operaton (DSP command 0D4h)
 WriteDsp(ContinueDma);
}



// Приостановка воспроизведения /////////////////////////////////////////////
:void fastcall PausePlaying()
{
 // Halt Dma Operation (DSP command 0D0h)
 WriteDsp(HaltDma);
}



// Проигрывание семпла //////////////////////////////////////////////////////
:void PlaySample8Bit(word Seg,Offs,Length)
{
 // Сбрасываем Sb16BitOutput
 Sb16BitOutput=FALSE;

 switch(SbType){
  CASE SoundBlaster10:
   WriteStr("\n8 bit mono 22222 Hz single mode");
   // Устанавливаем моно режим воспроизведения
   SbSetMonoOutput();
   // Устанавливаем частоту воспроизведения семпла
   SetRate(SbRate);
   // Включаем спикер (DSP - 0D1h)
   Speaker(TRUE);
   // Программируем DMA канал для передачи данных рабочего буфера
   SetupDma8Bit(Seg,Offs,Length,58h);
   // Начало воспроизведения
    // DMA DAC, 8-bit (DSP command 014h)
    WriteDsp(Dma8BitDac);
    // Записываем длину семпла (SampleLength)/2-1 для генерации IRQ
    // каждые 1/2 длины буфера
    BX=Length>>1-1;
    WriteDsp(BL); // Low
    WriteDsp(BH); // Hight
   break;
  CASE SoundBlaster20:
   WriteStr("\n8 bit mono 45454 Hz autoinit mode");
   // Устанавливаем моно режим воспроизведения
   SbSetMonoOutput();
   // Устанавливаем частоту воспроизведения семпла
   SetRate(SbRate);
   // Устанавливаем размер блока данных для передачи через DMA
   WriteDsp(SetDmaBlockSize);
   BX=Length+1>>1-1;
   WriteDsp(BL); // Low
   WriteDsp(BH); // High
   // Включаем спикер
   Speaker(TRUE);
   // Программируем DMA канал для передачи данных рабочего буфера
   SetupDma8Bit(Seg,Offs,Length,58h);
   // Начало воспроизведения
    // Auto-Initialize DMA DAC, 8-bit command (01Ch)
    WriteDsp(Dma8BitDacAutoInit);
   break;
  case SoundBlasterPro:
   WriteStr("\n8 bit stereo 22222 Hz autoinit mode");
   // Устанавливаем стерео режим воспроизведения
   SbSetStereoOutput();
   // Устанавливаем частоту воспроизведения семпла
   SetRate(SbRate);
   // Устанавливаем размер блока данных для передачи через DMA
   WriteDsp(SetDmaBlockSize);
   BX=Length+1>>1-1;
   WriteDsp(BL); // Low
   WriteDsp(BH); // High
   // Включаем спикер
   Speaker(TRUE);
   // Программируем DMA канал для передачи данных рабочего буфера
   SetupDma8Bit(Seg,Offs,Length,58h);
   // Начало воспроизведения
    // Auto-Initialize DMA DAC, 8-bit (HighSpeed) command (090h)
    WriteDsp(HiSpeedDma8BitAutoInitDac);
   break;
  case SoundBlaster16:
   break;
  case SoundBlasterAwe32:
   break;
  case SoundBlasterEss:
   WriteStr("\n8 bit stereo 45454 Hz autoinit mode");
   // Сбрасываем DSP
   ResetDsp();
   // Входим в расширенный режим ESS-ки
   WriteDsp(EssEnterExtendedMode);
   // Устанавливаем режим автоинициализации (reg[0B8h]=4)
   EssWriteReg(0B8h,4);
   // Устанавливаем стерео вывод (reg[0A8h]=reg[0A8h]&0FCh|1)
   EssWriteReg(0A8h,EssReadReg(0A8h)&0FCh|1);
   // reg[0B9h]=0
   EssWriteReg(0B9h,0);
   // Устанавливаем частоту воспроизведения
    // Рассчитываем константу частоты воспроизведения
//    EAX=long -795500/SbRate+256;
//    EssWriteReg(0A1h,238);
    EssWriteReg(0A1h,218);
   // Программируем фильтр
    // Рассчитываем константу фильтра (90%)
//    EAX=long -205451/SbRate+256;
//    EssWriteReg(0A2h,AL);
//    EssWriteReg(0A2h,251);
    EssWriteReg(0A2h,247);
   // Устанавливаем размер блока DMA
//     reg[0A4h] = (DMABlkSize / 2 - 1) And 0FFh XOr 0FFh
//     reg[0A5h] = (DMABlkSize / 2 - 1) >> 8 XOr 0FFh
    BX=Length>>1-1&0FFh^0FFh;
    EssWriteReg(0A4h,BL);
    BX=Length>>1-1>>8^0FFh;
    EssWriteReg(0A5h,BL);

   // Устанавливаем беззнаковый тип семпла
    // reg[0B6h]=080h;
    EssWriteReg(0B6h,80h);
    // reg[0B7h]=051h;
    EssWriteReg(0B7h,51h);
   // Задаем формат данных (8 bit unsigned stereo)
   EssWriteReg(0B7h,10011000b);
   // Разрешаем DMA
   //  reg[0B1h] = reg[0B1h] And 0Fh Or 50h;
   EssWriteReg(0B1h,EssReadReg(0B1h)&00Fh|50h);
   // Разрешаем IRQ
   // reg[0B2h] = reg[0B2h] And 0Fh Or 50h.
   EssWriteReg(0B2h,EssReadReg(0B2h)&00Fh|50h);
   // Программируем DMA канал для передачи данных рабочего буфера
   SetupDma8Bit(Seg,Offs,Length,58h);
   // Включаем спикер
   Speaker(TRUE);
   // Начало воспроизведения
   // reg[0B8h] = reg[0B8h] Or 1
   EssWriteReg(0B8h,EssReadReg(0B8h)|1);
   BREAK;
 }
}



// Сброс миксера ////////////////////////////////////////////////////////////
:void fastcall ResetMixer()
{
 // Записываем 0 в индексный порт (02x04h)
 outp(0,SbIoAddr+MixerAddress);
 // Записываем 0 в порт данных миксера
 outp(AL,DX+1);
}



// Запись в порт миксера ////////////////////////////////////////////////////
// AL - index
// AH - value
// return: void
:void fastcall WriteMixer(AL,AH)
{
 // Записываем index в индексный порт миксера (02x04h)
 outp(AL,SbIoAddr+MixerAddress);
 // Записываем данные в порт данных миксера (02x05h)
 outp(AH,DX+1);
}



// Запись в DSP /////////////////////////////////////////////////////////////
// AH - DSP command or value
// return: Void
// DX - 2x0Ah
// AH - DSP command or value
:void fastcall WriteDsp(AH)
{
 // DX - порт состояния DSP (02x0Eh)
 DX=SbIoAddr+DspWriteData;
 (E)CX=10000;
 loop((E)CX){
   inp();
   $or al,al
   IF (!MINUSFLAG) BREAK;
 }
 // Записываем данные в порт данных DSP (02x0Ah)
 outp(AH,DX);
}



// Включение IRQ ////////////////////////////////////////////////////////////
:void fastcall EnableIrq()
{
 CX=SbIrq;              // SB IRQ
 DX=21h;
 IF (CL>7) DL|=80h;    // port 0xA1 for second PIC
 BL=inp(DX);
 AL=1;
 IF (CL>7) CL-=8;
 AL<<=CL;
 !AL;
 AL&=BL;
 outp(AL,DX);
}












#ifdef __MSDOS__

// 50 Hz ///////////////////////////////////////////////////////////////////
:interrupt NewSbIrqHandler()
{
 // Сохраняем все регистры
 $pushad
  // Сохраняем DS
  $push DS

   #ifdef __DEBUG__
    DS=0B800h;
    DSDWORD[74*2]='\015R\015I';
    DSDWORD[76*2]='\015:\015Q';
    DSBYTE[72*2]=CSBYTE[#SbWorkBufNum]+48;
    DSBYTE[72*2+1]=13+CSBYTE[#SbWorkBufNum];

    SI=#IrqCircle;
    CSBYTE[SI]++; IF (CSBYTE[SI]==CSBYTE[SI+1]) CSBYTE[SI]=0;
    SI+=CSBYTE[SI];
    AL=CSBYTE[SI+2];
    AH=15;
    DSWORD[79*2]=AX;
   #endif

   // Устанавливаем DS на CS
   DS=CS;

   // Переключение между рабочими буферами, точнее между половинами
   // рабочего буфера (результат: 0,1,0,1,.....)
   SbWorkBufNum^=1;

   // Считываем байт с порта 02x0Eh (8 Bit)
   inp(SbIoAddr+0Eh);
   // Считываем байт с порта 02x0Fh (16 Bit)
   inp(DX+1);

   // Если режим воспроизведения не с автоинициализацией, то...
   IF (!SbAutoInit){
      // DMA DAC, 8-bit (DSP command 014h)
      WriteDsp(Dma8BitDac);
      // Записываем половину длины рабочего буфера для генерации IRQ каждые
      // 1/2 длины рабочего буфера
      BX=SbWorkBufSize>>1-1;
      WriteDsp(BL); // Low
      WriteDsp(BH); // Hight
   }

   // EOI
   outp(20h,20h);
   outp(AL,0A0h);  // Для IRQs > 7

   // Очищаем буфер одну из половин буфера, которую только что воспроизвели
   ClearOutputBuf();

   // Очищаем буфер микширования
   ClearMixBuf();

   // Микшируем каналы в буфер, который мы только что воспроизвели
   MixVoices();

   // Переводим микшированный 16-битный буфер микширования в 8 битный и
   // сохраняем его в буфере воспроизведения (тот, который проиграли ;)
   MixBuf2OutputBuf();

   #ifdef __MODPLAYER__
    // Вызываем мод-млейер
    ModPlayer();
   #endif

  $pop DS
 $popad
}



// Проинициализировать SB ///////////////////////////////////////////////////
:void fastcall InitSb()
{
 // Определяем тип карточки и ее возможности
 GetSbType();

 // Вычисление размера рабочего буфера (кратно 4; должен быть в два раза
 // больше для двойной буферизации, чтобы избежать щелчков между блоками)
 SbWorkBufSize=dword SbRate/IrqRate>>2<<2<<1;

 // Выделяем память под рабочий буфер
 SbWorkBufSeg=GETMEM(SbWorkBufSize>>4+1);
 IF (AX==0) WriteStr("Не могу выделить память (5)");
 SbWorkBufOffs=0;

 // Инициализируем все каналы
 FOR (CX=0,SI=#Voice; CX<MaxVoices; CX++,SI+=sizeof(VOICE)){
     // Вырубаем канал
     SI.VOICE.Active=FALSE;
     // Устанавливаем флаги канала
     SI.VOICE.Flags=0;
 }

 FOR (CX=0,SI=#Sample; CX<MaxSamples; CX++,SI+=sizeof(SAMPLE)){
     // Устанавливаем длину семпла в 0, т.е. вырубаем его по сути дела
     SI.SAMPLE.Length=0;
     // Устанавливаем громкость семпла
     SI.SAMPLE.Volume=0FFh;
     // Устанавливаем баланс канала
     SI.SAMPLE.Balance=128;
 }

 // Инициализация микширования
 InitMixing();

 // Сбрасываем DSP
 ResetDsp();
 // Сбрасываем Mixer
// ResetMixer();

 // Сохраняем ES на стеке
 $push ES
  // Высчитываем номер прерывания по IRQ
  AL=SbIrq+8; IF (AL>7+8) AL+=60h;
  // Сохраняем старый обработчик прерывания
  AH=35h; $int 21h
  OldSbIrqHandler[0]=BX; OldSbIrqHandler[2]=ES;
  // Устанавливаем свой обработчик прерывания
  AH=25h; DX=#NewSbIrqHandler; $int 21h
 $pop ES

 // Включение IRQ
 EnableIrq();

// ESS 3D Sound Enable
// outp(40h,224h);
// outp(3,227h);

 // Проигрывание рабочего буфера (начало воспроизведения)
 PlaySample8Bit(SbWorkBufSeg,SbWorkBufOffs,SbWorkBufSize);
}



// Деинициализировать SB ////////////////////////////////////////////////////
:void fastcall DeInitSb()
{
 // Прекращаем воспроизведение
 StopPlaying();

 // Если это ESS-ка, то входим в расширенный режим
 IF (SbType==SoundBlasterEss){
    // Выходим из расширенного режима работы ESS-ки (DSP command 0C7h)
    WriteDsp(EssExitExtendedMode);
 }

 // Сохраняем DS на стеке
 $push DS
  // Высчитываем номер прерывания по IRQ
  AL=SbIrq+8; IF (AL>7+8) AL+=60h;
  // Восстанавливаем старый обработчик прерывания
  AH=25h; DX=OldSbIrqHandler[0]; DS=OldSbIrqHandler[2]; $int 21h
 $pop DS
}



// Очистка состояния DSP ////////////////////////////////////////////////////
// return:
// 1 - ОК
// 0 - No SB
:byte fastcall ResetDsp()
{
 // DX - базовый адрес соунд бластера
 DX=SbIoAddr;
 // Записываем 1 в порт сброса DSP
 AL=1; IF (SbType==SoundBlasterEss) AL+=2;
 outp(AL,DX+DspReset);
 // Ждем 3.3 микросекунды
 inp(); inp(); inp(); inp();
 // Записываем 0 в прот сброса DSP
 outp(0,DX);
 // DX - порт данных DSP (02x0Ah)
 DX+=4;
 CX=100;
 loop(CX){
   // Проверяем порт данных на наличие 0AAh
   IF (inp()==0AAh) return(TRUE);
 }
 // Нет SB на этом адресе
 return(FALSE);
}



// Чтение из DSP ////////////////////////////////////////////////////////////
// CX - цикл ожидания
// return: Data byte
:byte fastcall ReadDsp()
{
/* do{
  // DX - порт состояния DSP (02x0Eh)
  DX=SbIoAddr+DspDataAvail;
  // Если 7 бит установлен, то читаем данные
  CX=0; loop(CX) IF (byte inp()&80h) BREAK;
  // Читаем данные из порта данных DSP (02x0Ah)
 }while (inp(DX-4)==0AAh);*/

 // DX - порт состояния DSP (2x0Eh)
/* DX=SbIoAddr+DspDataAvail;
 // Длительность проверки
 ECX=10000;
 loop(ECX){
  // Считываем байт
  inp();
  // Знак флага установлен ?
  $test AL,AL
  IF (MINUSFLAG){
     // Считываем и возвращаем данные
     return(inp(DX-4));
  }
 }*/
 DX=SbIoAddr+DspDataAvail;
 (E)CX=10000;
 loop((E)CX){
   inp();
   $or al,al
   IF (MINUSFLAG) return(inp(DX-4));
 }
 AL=0;
}



// Включение/выключение спикера /////////////////////////////////////////////
// TRUE - on
// FALSE - off
:void fastcall Speaker(AX)
{
 IF (!AX) WriteDsp(SpeakerOff);
 ELSE WriteDsp(SpeakerOn);
}



// Программирование 8-битного DMA контроллера ///////////////////////////////
// BX - sample seg
// CX - sample offs
// DI - sample length
// AL - mode (48h - Single, 58h - AutoInit)
:void fastcall SetupDma8Bit(BX,CX,DI,AL)
{
 // Сохраняем режим воспроизведения
 $push AX
  // Включение маски DMA канала (00Ah)
  outp(ResetCmd|SbDma,DmaMask8Bit);
  // Очищаем byte-pointer (00Ch)
  outp(AL,BytePtr8Bit);
 // Восстанавливаем режим воспроизведения
 $pop AX
 // Настраиваем режим передачи данных по DMA каналу (00Bh)
 outp(AL|SbDma,Mode8Bit);
 // Рассчитываем страницу и смещение адреса блока данных
 AX=BX>>12;
 BX<<=4; CX+=BX; $adc AL,0
 // SI - SbDma
 SI=SbDma;
 // Записываем страницу адреса
 outp(AL,PagePort[SI]);
 // Записываем смещение адреса
 outp(CL,AddressPort[SI]); // Low
 outp(CH,DX);                     // Hight
 // Записываем (длину блока)-1 данных для передачи через DMA канал
 AX=DI-1;
 outp(AL,DX+1); // Low
 outp(AH,DX);   // Hight
 // Сброс маски DMA канала
 outp(SbDma,DmaMask8Bit);
}



// Программирование 16-битного DMA контроллера //////////////////////////////
// BX - sample seg
// CX - sample offs
// DI - sample length
:void fastcall SetupDma16Bit(BX,CX,DI)
{
 // Включение маски DMA канала (0D4h)
 outp(ResetCmd|Sb16BitDma,DmaMask16Bit);
 // Очищаем byte-pointer (0D8h)
 outp(AL,BytePtr16Bit);
 // Настраиваем режим передачи данных по DMA каналу (0D6h)
 outp(ReadCmd|Sb16BitDma,Mode16Bit);
 // Рассчитываем страницу и смещение адреса блока данных
 EBX=BX; ECX=CX; EBX<<=4;
 ECX=ECX+EBX>>1; EAX=ECX>>15;
 // Записываем страницу адреса
 outp(AL,PagePort[Sb16BitDma]);
 // Записываем смещение адреса
 outp(CL,AddressPort[Sb16BitDma]); // Low
 outp(CH,DX);                      // Hight
 // Записываем длину блока данных для передачи через DMA канал
 // SampleLength = SampleLength / 2 - 1
 AX=DI>>1-1;
 outp(AL,DX+2); // Low
 outp(AH,DX);   // Hight
 // Сброс маски DMA канала (0D4h)
 outp(Sb16BitDma,DmaMask16Bit);
}



// Автоопределение конфигурации Sound Blaster-а /////////////////////////////
:byte SbAutoDetect()
 byte SbPresent=FALSE;
{
 // Узнаем базовый адрес Sound Blaster-а
 FOR (SbIoAddr=210h; SbIoAddr<260h; SbIoAddr+=10h){
     IF (ResetDsp()){
        SbPresent=TRUE; BREAK;
     }
 }
 // Соунд бластер не найден
 IF (!SbPresent) return(FALSE);

 // Сбрасываем номер Irq
 SbIrq=0;

 // Узнаем IRQ и DMA
 $push ES
  ES=0;
  // Сохраняем старые обработчики IRQ-шек
  $cli
  $push ESDWORD[10*4],ESDWORD[11*4],ESDWORD[13*4],ESDWORD[15*4]
   // Save IRQs Mask
   BL=inp(21h);
   // Mask IRQ 2,3,5,7
   outp(AL|10101100b,21h);

    // Устанавливаем свои обработчики IRQ-шек
    DI=DS;
    ESWORD[10*4]=#SbIrq2Test; ESWORD[10*4+2]=DI;
    ESWORD[11*4]=#SbIrq3Test; ESWORD[11*4+2]=DI;
    ESWORD[13*4]=#SbIrq5Test; ESWORD[13*4+2]=DI;
    ESWORD[15*4]=#SbIrq7Test; ESWORD[15*4+2]=DI;

   // Включаем IRQ 2,3,5,7
   outp(BL&!10101100b,21h);
   $sti

   // Просим Sound Blaster сгенерить IRQ (DSP command 0F2h)
   WriteDsp(IntRequest);

   // Ждем ответа от SB
   CX=0; loop(CX) IF (SbIrq!=0) BREAK;

   $cli
   // Mask IRQ 2,3,5,7
   outp(AL|10101100b,21h);

  // Восстанавливаем старые обработчики IRQ-шек
  $pop ESDWORD[15*4],ESDWORD[13*4],ESDWORD[11*4],ESDWORD[10*4]
  $sti
  // Восстанавливаем рабочие IRQ
  outp(BL,21h);
 $pop ES

 // Если IRQ не удалось определить, возвращаем ошибку
 IF (!SbIrq) return(FALSE);

 return(TRUE);

SbIrq2Test:
 $pusha
  CSBYTE[#SbIrq]=2;
  inp(CSWORD[#SbIoAddr]+0Eh);
  outp(20h,20h);
 $popa
$iret

SbIrq3Test:
 $pusha
  CSBYTE[#SbIrq]=3;
  inp(CSWORD[#SbIoAddr]+0Eh);
  outp(20h,20h);
 $popa
$iret

SbIrq5Test:
 $pusha
  CSBYTE[#SbIrq]=5;
  inp(CSWORD[#SbIoAddr]+0Eh);
  outp(20h,20h);
 $popa
$iret

SbIrq7Test:
 $pusha
  CSBYTE[#SbIrq]=7;
  inp(CSWORD[#SbIoAddr]+0Eh);
  outp(20h,20h);
 $popa
$iret

}



// Проигрывание семпла //////////////////////////////////////////////////////
// BX - Sample Number
// DI - Sample Playing Start Position
// CX - Channel Number
// EDX - Rate
:void fastcall PlaySample8BitVoice(BX,DI,CX,EDX)
{
 // Вычисляем адрес семпла
 BX=BX*sizeof(SAMPLE)+#Sample;

 // Вычисляем в SI адрес структуры канала
 SI=#Voice[CX];

 // Инициализируем канал
 SI.VOICE.Sample.Seg=BX.SAMPLE.Seg;
 SI.VOICE.Sample.Offs=BX.SAMPLE.Offs;
 SI.VOICE.Sample.Length=BX.SAMPLE.Length;

 // Сохраняем частоту воспроизведения
 SI.VOICE.Sample.Rate=EDX;

 // Устанавливаем параметры канала
 SI.VOICE.Sample.Volume=BX.SAMPLE.Volume;
 SI.VOICE.Sample.Balance=BX.SAMPLE.Balance;
 SI.VOICE.Sample.RepeatOffs=BX.SAMPLE.RepeatOffs;
 SI.VOICE.Sample.RepeatLength=BX.SAMPLE.RepeatLength;

 // Вычисление позиции в семпле
 SI.VOICE.Sample.Pos=dword DI*EDX/SbRate<<8;  // fixed 24:8

 // Активизируем канал
 SI.VOICE.Active=TRUE;
}



// Проигрывание семпла на свободном канале //////////////////////////////////
// BX - Sample Number
// DI - Sample Playing Start Position
// EDX - Rate
:void fastcall PlaySample8BitRandomVoice(BX,DI,EDX)
{
 // Ищим свободный канал
 FOR (CX=0,SI=#Voice; CX<SbNumVoices; CX++,SI+=sizeof(VOICE)){
     // Если канал не занят
     IF (!SI.VOICE.Active)&&(!SI.VOICE.Flags&VoiceNoMixFlag){
        // Вычисляем адрес семпла
        BX=BX*sizeof(SAMPLE)+#Sample;

        // Инициализируем канал
        SI.VOICE.Sample.Seg=BX.SAMPLE.Seg;
        SI.VOICE.Sample.Offs=BX.SAMPLE.Offs;
        SI.VOICE.Sample.Length=BX.SAMPLE.Length;

        // Устанавливаем параметры канала
        SI.VOICE.Sample.Volume=BX.SAMPLE.Volume;
        SI.VOICE.Sample.Balance=BX.SAMPLE.Balance;
        SI.VOICE.Sample.RepeatOffs=BX.SAMPLE.RepeatOffs;
        SI.VOICE.Sample.RepeatLength=BX.SAMPLE.RepeatLength;

        // Сохраняем частоту воспроизведения
        SI.VOICE.Sample.Rate=EDX;

        // Вычисление позиции в семпле
        SI.VOICE.Sample.Pos=dword DI*EDX/SbRate<<8;  // fixed 24:8

        // Активизируем канал
        SI.VOICE.Active=TRUE;

        // Выходим
        BREAK;
     }
 }
}



// Возвращает количество байт, оставшихся до конца пересылки через DMA
:word fastcall GetDmaPos()
{
 // Считываем количество байт, оставшихся до конца пересылки через DMA
 AH=inp(SbDma<<1+1); // Low
 inp();              // Hight
 // Приводим в нормальный вид
 AL><AH;
}



// Определение конфигурации Sound Blaster-а по переменной окружения BLASTER
:byte fastcall SbGetEnvirenmentConfig()
{
 $push ES

  // Ищим переменную окружения BLASTER

   // Устанавливаем ES на сегмент переменных окружения (PSP:[2Ch])
   ES=environ;
   BX=0;            // Смещение в сегменте переменных окружения

   loop(){
    DI=BX;                    // Смещение в сегменте переменных окружения
    SI="BLASTER";             // Смещение к строке "BLASTER"
    CX=sizeof("BLASTER")-1;   // Длина сравнения
    $rep $cmpsb               // Сравниваем
    IF (ZEROFLAG) BREAK;      // Если нашли, то выходим
    // Приращиваем положение в сегменте переменных окружения
    BX++;
    // Если это конец переменных окружения, то выходим с ошибкой
    IF (ESWORD[BX]==0){
       $pop ES
       return(FALSE);
    }
   }

   // Считываем конфигурацию
   loop(){
    // Считываем символ
    AL=ESBYTE[DI]; DI++;

    // Переводим символ в верхний регистр
    IF (AL>='a') AL-='a'-'A';

    SWITCH(AL){
     case 'A': // Base IO
      SbIoAddr=HexAscii2Word(ES,DI);
      BREAK;
     CASE 'I': // IRQ
      // Считываем номер IRQ
      AX=ESWORD[DI];
      // Если номер IRQ - двузначное число
      IF (AH!=' '){
         // Преобразуем в байтовское значение
         AL><AH; AX-='00'; $aad
      }ELSE AL-='0';
      SbIrq=AL;
      BREAK;
     CASE 'D': // DMA
      // Считываем номер DMA
      SbDma=ESBYTE[DI]-'0';
      BREAK;
     CASE 0:
      BREAK 1;
     DEFAULT:
    }
   }

 $pop ES

 // Все ОК
 return(TRUE);
}



#endif























#ifdef __DOS32__

// Проинициализировать SB ///////////////////////////////////////////// 32 //
:void fastcall InitSb()
{
 // Определяем тип карточки и ее возможности
 GetSbType();

 // Вычисление размера рабочего буфера (кратно 4; должен быть в два раза
 // больше для двойной буферизации, чтобы избежать щелчков между блоками)
 SbWorkBufSize=dword SbRate/IrqRate>>2<<2<<1;

 // Выделяем память под рабочий буфер
 IF (!AllocateDosMemBlock(SbWorkBufSize>>4+1)) WriteStr("Не могу выделить память (5)");
 // Сохраняем селектор и смещение первой половины рабочего буфера
 SbWorkBufSeg=AX;
 SbWorkBufSel=DX;
 SbWorkBufOffs=0;

 // Инициализируем все каналы
 FOR (ECX=0,ESI=#Voice; ECX<MaxVoices; ECX++,ESI+=sizeof(VOICE)){
     // Вырубаем канал
     ESI.VOICE.Active=FALSE;
     // Устанавливаем флаги канала
     ESI.VOICE.Flags=0;
 }

 FOR (ECX=0,ESI=#Sample; ECX<MaxSamples; ECX++,ESI+=sizeof(SAMPLE)){
     // Устанавливаем длину семпла в 0, т.е. вырубаем его по сути дела
     ESI.SAMPLE.Length=0;
     // Устанавливаем громкость семпла
     ESI.SAMPLE.Volume=0FFh;
     // Устанавливаем баланс канала
     ESI.SAMPLE.Balance=128;
 }

 // Инициализация микширования
 InitMixing();

 // Сбрасываем DSP
 ResetDsp();
 // Сбрасываем Mixer
// ResetMixer();

 // Устанавливаем собственный обработчик IRQ-шки Sound Blaster-а
  // Высчитываем номер прерывания по IRQ
  BL=SbIrq+8; IF (BL>7+8) BL+=60h;
  // Сохраняем старый обработчик IRQ
  GetVirtualInterruptHandler(BL);
  OldSbIrqHandler[0]=EDX; OldSbIrqHandler[4]=ECX;
  // Устанавливаем свой обработчик прерывания
  SetVirtualInterruptHandler(BL,CS,#NewSbIrqHandler);

 // Включение IRQ
 EnableIrq();

// ESS 3D Sound Enable
// outp(40h,224h);
// outp(3,227h);

 // Проигрывание рабочего буфера (начало воспроизведения)
 PlaySample8Bit(SbWorkBufSeg,SbWorkBufOffs,SbWorkBufSize);
}



// Деинициализировать SB ////////////////////////////////////////////// 32 //
:void fastcall DeInitSb()
{
 // Прекращаем воспроизведение
 StopPlaying();

 // Если это ESS-ка, то входим в расширенный режим
 IF (SbType==SoundBlasterEss){
    // Выходим из расширенного режима работы ESS-ки (DSP command 0C7h)
    WriteDsp(EssExitExtendedMode);
 }

 // Высчитываем номер прерывания по IRQ
 BL=SbIrq+8; IF (BL>7+8) BL+=60h;
 // Восстанавливаем старый обработчик виртуального прерывания
 SetVirtualInterruptHandler(BL,OldSbIrqHandler[4],OldSbIrqHandler[0]);
}



// Очистка состояния DSP ////////////////////////////////////////////// 32 //
// return:
// 1 - ОК
// 0 - No SB
:byte fastcall ResetDsp()
{
 // DX - базовый адрес соунд бластера
 DX=SbIoAddr;
 // Записываем 1 в порт сброса DSP
 AL=1; IF (SbType==SoundBlasterEss) AL+=2;
 outp(AL,DX+DspReset);
 // Ждем 3.3 микросекунды
 inp(); inp(); inp(); inp();
 // Записываем 0 в прот сброса DSP
 outp(0,DX);
 // DX - порт данных DSP (02x0Ah)
 DX+=4;
 ECX=100;
 loop(ECX){
   // Проверяем порт данных на наличие 0AAh
   IF (inp()==0AAh) return(TRUE);
 }
 // Нет SB на этом адресе
 return(FALSE);
}



// Чтение из DSP ////////////////////////////////////////////////////// 32 //
// return: Data byte
:byte fastcall ReadDsp()
{
 do{
  // DX - порт состояния DSP (02x0Eh)
  DX=SbIoAddr+DspDataAvail;
  // Если 7 бит установлен, то читаем данные
  ECX=0FFFFh; loop(ECX) IF (byte inp()&80h) BREAK;
  // Читаем данные из порта данных DSP (02x0Ah)
 }while (inp(DX-4)==0AAh);
}



// Включение/выключение спикера /////////////////////////////////////// 32 //
// TRUE - on
// FALSE - off
:void fastcall Speaker(EAX)
{
 IF (EAX==0) WriteDsp(SpeakerOff);
 ELSE WriteDsp(SpeakerOn);
}



// Автоопределение конфигурации Sound Blaster-а /////////////////////// 32 //
:byte SbAutoDetect()
 byte SbPresent=FALSE;
{
 // Узнаем базовый адрес Sound Blaster-а
 FOR (SbIoAddr=210h; SbIoAddr<260h; SbIoAddr+=10h){
     IF (ResetDsp()){
        SbPresent=TRUE; BREAK;
     }
 }
 // Соунд бластер не найден
 IF (!SbPresent) return(FALSE);

 // Сбрасываем номер Irq
 SbIrq=0;

 // Узнаем IRQ и DMA

  // Сохраняем старые обработчики IRQ-шек
  GetVirtualInterruptHandler(10); $push CX,EDX
   GetVirtualInterruptHandler(11); $push CX,EDX
    GetVirtualInterruptHandler(13); $push CX,EDX
     GetVirtualInterruptHandler(15); $push CX,EDX

      // Save IRQs Mask
      inp(21h);
      // Включаем IRQ 2,3,5,7
      outp(AL&!10101100b,21h);

      // Устанавливаем свои обработчики IRQ-шек
      SetVirtualInterruptHandler(10,CS,#SbIrq2Test);
      SetVirtualInterruptHandler(11,CS,#SbIrq3Test);
      SetVirtualInterruptHandler(13,CS,#SbIrq5Test);
      SetVirtualInterruptHandler(15,CS,#SbIrq7Test);

      // Просим Sound Blaster сгенерить IRQ (DSP command 0F2h)
      WriteDsp(IntRequest);

      // Ждем ответа от SB
      ECX=010000h; loop(ECX) IF (SbIrq!=0) BREAK;

      // Восстанавливаем старые обработчики IRQ-шек
     $pop EDX,CX SetVirtualInterruptHandler(15,CX,EDX);
    $pop EDX,CX SetVirtualInterruptHandler(13,CX,EDX);
   $pop EDX,CX SetVirtualInterruptHandler(11,CX,EDX);
  $pop EDX,CX SetVirtualInterruptHandler(10,CX,EDX);

 // Если IRQ не удалось определить, возвращаем ошибку
 IF (!SbIrq) return(FALSE);

 return(TRUE);

SbIrq2Test:
 $pushad
  $push DS
   DS=CSWORD[#BaseSel];
   SbIrq=2;
   inp(SbIoAddr+0Eh);
   outp(20h,20h);
  $pop DS
 $popad
$iretd

SbIrq3Test:
 $pushad
  $push DS
   DS=CSWORD[#BaseSel];
   SbIrq=3;
   inp(SbIoAddr+0Eh);
   outp(20h,20h);
  $pop DS
 $popad
$iretd

SbIrq5Test:
 $pushad
  $push DS
   DS=CSWORD[#BaseSel];
   SbIrq=5;
   inp(SbIoAddr+0Eh);
   outp(20h,20h);
  $pop DS
 $popad
$iretd

SbIrq7Test:
 $pushad
  $push DS
   DS=CSWORD[#BaseSel];
   SbIrq=7;
   inp(SbIoAddr+0Eh);
   outp(20h,20h);
  $pop DS
 $popad
$iretd

}



// 50 Hz ////////////////////////////////////////////////////////////// 32 //
:interrupt NewSbIrqHandler()
{
 // Сохраняем все регистры
 $pushad
  // Сохраняем DS
  $push DS

   // Устанавливаем DS на CS
   DS=CSWORD[#BaseSel];

   #ifdef __DEBUG__
    $push ES
     AX=2; BX=0B800h; $int 31h
     ES=AX;
     ESDWORD[74*2]='\015R\015I';
     ESDWORD[76*2]='\015:\015Q';
     ESBYTE[72*2]=SbWorkBufNum+48;
     ESBYTE[72*2+1]=13+SbWorkBufNum;

     ESI=#IrqCircle;
     DSBYTE[ESI]++; IF (DSBYTE[ESI]==DSBYTE[ESI+1]) DSBYTE[ESI]=0;
     ESI+=DSBYTE[ESI];
     AL=DSBYTE[ESI+2];
     AH=15;
     ESWORD[79*2]=AX;
    $pop ES
   #endif

   // Переключение между рабочими буферами, точнее между половинами
   // рабочего буфера (результат: 0,1,0,1,.....)
   SbWorkBufNum^=1;

   // Считываем байт с порта 02x0Eh (8 Bit)
   inp(SbIoAddr+0Eh);
   // Считываем байт с порта 02x0Fh (16 Bit)
   inp(DX+1);

   // Если режим воспроизведения не с автоинициализацией, то...
   IF (!SbAutoInit){
      // DMA DAC, 8-bit (DSP command 014h)
      WriteDsp(Dma8BitDac);
      // Записываем половину длины рабочего буфера для генерации IRQ каждые
      // 1/2 длины рабочего буфера
      BX=SbWorkBufSize>>1-1;
      WriteDsp(BL); // Low
      WriteDsp(BH); // Hight
   }

   // EOI
   outp(20h,20h);
   outp(AL,0A0h);  // Для IRQs > 7

   // Очищаем буфер одну из половин буфера, которую только что воспроизвели
   ClearOutputBuf();

   // Очищаем буфер микширования
   ClearMixBuf();

   // Микшируем каналы в буфер, который мы только что воспроизвели
   MixVoices();

   // Переводим микшированный 16-битный буфер микширования в 8 битный и
   // сохраняем его в буфере воспроизведения (тот, который проиграли ;)
   MixBuf2OutputBuf();

  $pop DS
 $popad
}



// Программирование 8-битного DMA контроллера ///////////////////////// 32 //
// BX - sample seg
// CX - sample offs
// DI - sample length
// AL - mode (48h - Single, 58h - AutoInit)
:void fastcall SetupDma8Bit(BX,CX,DI,AL)
{
 // Сохраняем режим воспроизведения
 $push EAX
  // Включение маски DMA канала (00Ah)
  outp(ResetCmd|SbDma,DmaMask8Bit);
  // Очищаем byte-pointer (00Ch)
  outp(AL,BytePtr8Bit);
 // Восстанавливаем режим воспроизведения
 $pop EAX
 // Настраиваем режим передачи данных по DMA каналу (00Bh)
 outp(AL|SbDma,Mode8Bit);
 // Рассчитываем страницу и смещение адреса блока данных
 AX=BX>>12;
 BX<<=4; CX+=BX; $adc AL,0
 // ESI - SbDma
 ESI=SbDma;
 // Записываем страницу адреса
 outp(AL,PagePort[ESI]);
 // Записываем смещение адреса
 outp(CL,AddressPort[ESI]); // Low
 outp(CH,DX);                     // Hight
 // Записываем (длину блока)-1 данных для передачи через DMA канал
 AX=DI-1;
 outp(AL,DX+1); // Low
 outp(AH,DX);   // Hight
 // Сброс маски DMA канала
 outp(SbDma,DmaMask8Bit);
}



// Проигрывание семпла //////////////////////////////////////////////// 32 //
// EBX - Sample Number
// EDI - Sample Playing Start Position
// ECX - Channel Number
// EDX - Rate
:void fastcall PlaySample8BitVoice(EBX,EDI,ECX,EDX)
{
 // Вычисляем адрес семпла
 EBX=EBX*sizeof(SAMPLE)+#Sample;

 // Вычисляем в ESI адрес структуры канала
 ESI=#Voice[ECX];

 // Инициализируем канал
 ESI.VOICE.Sample.Sel=EBX.SAMPLE.Sel;
 ESI.VOICE.Sample.Offs=EBX.SAMPLE.Offs;
 ESI.VOICE.Sample.Length=EBX.SAMPLE.Length;

 // Сохраняем частоту воспроизведения
 ESI.VOICE.Sample.Rate=EDX;

 // Устанавливаем параметры канала
 ESI.VOICE.Sample.Volume=EBX.SAMPLE.Volume;
 ESI.VOICE.Sample.Balance=EBX.SAMPLE.Balance;
 ESI.VOICE.Sample.RepeatOffs=EBX.SAMPLE.RepeatOffs;
 ESI.VOICE.Sample.RepeatLength=EBX.SAMPLE.RepeatLength;

 // Вычисление позиции в семпле
 ESI.VOICE.Sample.Pos=EDI*EDX/SbRate<<8;  // fixed 24:8

 // Активизируем канал
 ESI.VOICE.Active=TRUE;
}



// Проигрывание семпла на свободном канале //////////////////////////// 32 //
// EBX - Sample Number
// EDI - Sample Playing Start Position
// EDX - Rate
:void fastcall PlaySample8BitRandomVoice(EBX,EDI,EDX)
{
 // Ищим свободный канал
 FOR (ECX=0,ESI=#Voice; CX<SbNumVoices; ECX++,ESI+=sizeof(VOICE)){
     // Если канал не занят
     IF (!ESI.VOICE.Active)&&(!ESI.VOICE.Flags&VoiceNoMixFlag){
        // Вычисляем адрес семпла
        EBX=EBX*sizeof(SAMPLE)+#Sample;

        // Инициализируем канал
        ESI.VOICE.Sample.Sel=EBX.SAMPLE.Sel;
        ESI.VOICE.Sample.Offs=EBX.SAMPLE.Offs;
        ESI.VOICE.Sample.Length=EBX.SAMPLE.Length;

        // Устанавливаем параметры канала
        ESI.VOICE.Sample.Volume=EBX.SAMPLE.Volume;
        ESI.VOICE.Sample.Balance=EBX.SAMPLE.Balance;
        ESI.VOICE.Sample.RepeatOffs=EBX.SAMPLE.RepeatOffs;
        ESI.VOICE.Sample.RepeatLength=EBX.SAMPLE.RepeatLength;

        // Сохраняем частоту воспроизведения
        ESI.VOICE.Sample.Rate=EDX;

        // Вычисление позиции в семпле
        ESI.VOICE.Sample.Pos=EDI*EDX/SbRate<<8;  // fixed 24:8

        // Активизируем канал
        ESI.VOICE.Active=TRUE;

        // Выходим
        BREAK;
     }
 }
}



// Определение конфигурации Sound Blaster-а по переменной ///////////// 32 //
// окружения BLASTER ////////////////////////////////////////////////////////
:byte fastcall SbGetEnvirenmentConfig()
{
 $push ES

  // Ищим переменную окружения BLASTER

   // Устанавливаем ES на сегмент переменных окружения (PSP:[2Ch])
   // Узнаем сегмент переменных окружения
   ES=environ;

   EBX=0;            // Смещение в сегменте переменных окружения

   loop(){
    EDI=BX;                    // Смещение в сегменте переменных окружения
    ESI="BLASTER";             // Смещение к строке "BLASTER"
    ECX=sizeof("BLASTER")-1;   // Длина сравнения
    $rep $cmpsb                // Сравниваем
    IF (ZEROFLAG) BREAK;       // Если нашли, то выходим
    // Приращиваем положение в сегменте переменных окружения
    EBX++;
    // Если это конец переменных окружения, то выходим с ошибкой
    IF (ESWORD[EBX]==0){
       $pop ES
       return(FALSE);
    }
   }

   // Считываем конфигурацию
   loop(){
    // Считываем символ
    AL=ESBYTE[EDI]; EDI++;

    // Переводим символ в верхний регистр
    IF (AL>='a') AL-='a'-'A';

    SWITCH(AL){
     case 'A': // Base IO
      SbIoAddr=HexAscii2Word(ES,EDI);
      BREAK;
     CASE 'I': // IRQ
      // Считываем номер IRQ
      AX=ESWORD[EDI];
      // Если номер IRQ - двузначное число
      IF (AH!=' '){
         // Преобразуем в байтовское значение
         AL><AH; AX-='00'; $aad
      }ELSE AL-='0';
      SbIrq=AL;
      BREAK;
     CASE 'D': // DMA
      // Считываем номер DMA
      SbDma=ESBYTE[EDI]-'0';
      BREAK;
     CASE 0:
      BREAK 1;
     DEFAULT:
    }
   }

 $pop ES

 // Все ОК
 return(TRUE);
}

#endif