/****************************************************************************
    SPHINX Programming (C) 2001.
    NAME:  DPMISERV.H--
    DESCRIPTION: Библиотека для микширования звука
    LAST MODIFIED:  21-05-2001 07:04:34
 ----------------------------------------------------------------------------
    PROCEDURES DEFINED IN THIS FILE:
     :void fastcall CreateClipTable();
     :void fastcall MixVoice(unsigned int BX);
     :void fastcall MixVoices();
     :void fastcall MixBuf2OutputBuf();
     :void fastcall ClearMixBuf();
     :void fastcall ClearOutputBuf();
     :void fastcall InitMixing();
     :void fastcall DeinitMixixg();
     :void fastcall StereoMixChannel();
     :void fastcall MonoMixChannel();
 ----------------------------------------------------------------------------
    Alexey Dryamov (Din)
    E-Mail  : _Din_@Mail.ru
              _Din_@nm.ru
              Din@RbcMail.ru
    FidoNet : 2:5055/141.11

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

#ifndef _MIX_
 #define _MIX_ TRUE
#endif



// Подключение библы с дефайнами и переменными
#include "mix.inc"



// Объявление процедур
void fastcall CreateClipTable();
void fastcall MixVoice(unsigned int BX);
void fastcall MixVoices();
void fastcall MixBuf2OutputBuf();
void fastcall ClearMixBuf();
void fastcall ClearOutputBuf();
void fastcall InitMixing();
void fastcall DeinitMixixg();
void fastcall StereoMixChannel();
void fastcall MonoMixChannel();



#ifdef __MSDOS__

// Создание таблицы перевода 16 bit -> 8 bit (-128...127) ///////////////////
:void fastcall CreateClipTable()
{
 $push ES

  // Устанавливаем ES:DI на сегмент:смещение буфера таблицы перевода
  ES=SbClipTableBufSeg;
  DI=SbClipTableBufOffs;

  FOR (DX=SbNumVoices*128,CX=-DX; int CX<DX; CX++){
      // Отсекаем по -128...127
      AX=CX;
      IF (int AX<-128) AL=-128;
      IF (int AX>127) AL=127;

      AL+=128;

      $stosb  // ESBYTE[DI]=AL; DI++;
  }

 $pop ES
}



// Микширование каналов /////////////////////////////////////////////////////
:void fastcall MixVoices()
{
 // Микшируем каналы
 FOR (BX=0; BX<SbNumVoices; BX++) MixVoice(BX);
}



// Преобразование буфера микширования в буфер воспроизведения ///////////////
:void fastcall MixBuf2OutputBuf()
{
 $push ES,FS,GS,BP

  // Устанавливаем FS:BP на Seg:Offs Mixing Buffer
  FS=SbMixBufSeg;
  BP=SbMixBufOffs;
  // Устанавливаем ES:DI на Seg:Offs Output Buffer
  ES=SbWorkBufSeg;
  DI=SbWorkBufOffs;
  IF (SbWorkBufNum) DI=SbWorkBufSize>>1;
  // Устанавливаем GS:BX на середину ClipTable Buffer
  GS=SbClipTableBufSeg;
  BX=128*SbNumVoices+SbClipTableBufOffs;

  // CX - размер буфера
  CX=SbWorkBufSize>>1;
  // Преобразуем
  loop(CX){
   // Считываем volume с буфера микширования (int)
   SI=FSINT[BP]; $add BP,2 //BP+=2;
   // Преобразуем по ClipTable в 8 bit
   AL=GSBYTE[SI+BX];
   // Сохраняем результат в Output буфере
   $stosb // ESBYTE[DI]=AL; DI++;
  }

 $pop BP,GS,FS,ES
}



// Очистка Mixing Buffer-а //////////////////////////////////////////////////
:void fastcall ClearMixBuf()
{
 // Очищаем SbMixBuf
 $push ES
  ES=SbMixBufSeg;
  DI=SbMixBufOffs;         // SbMixBuf Offs
  CX=SbWorkBufSize>>2;     // (SbMixBuf Size)/2, т.к. 16 бит
  EAX=0;                   // ClearDWord
  $rep $stosd              // Clear Data by ClearDWord
 $pop ES
}



// Очистка буфера воспроизведения ///////////////////////////////////////////
:void fastcall ClearOutputBuf()
{
 // Очищаем SbWorkBuf
 $push ES
  ES=SbWorkBufSeg;
  DI=SbWorkBufOffs;
  IF (SbWorkBufNum) DI=SbWorkBufSize>>1;
  CX=SbWorkBufSize>>3;      // (SbWorkBuf Size)/4
  EAX=80808080h;            // ClearDWord
  $rep $stosd               // Clear Data by ClearDWord
 $pop ES
}



// Инициализация микширования ///////////////////////////////////////////////
:void fastcall InitMixing()
{
 // Выделение памяти под буфер перевода
 SbClipTableBufSeg=GETMEM(SbNumVoices*256/16+1);
 IF (AX==0) WriteStr("Не могу выделить память (3)");
 SbClipTableBufOffs=0;

 // Выделение памяти под буфер микширования (16 bit)
 SbMixBufSeg=GETMEM(SbWorkBufSize>>4+1);
 IF (AX==0) WriteStr("Не могу выделить память (4)");
 SbMixBufOffs=0;

 // Создание таблицы клипирования
 CreateClipTable();

 // Стартовый рабочий буфер
 SbWorkBufNum=0;
 // Очищаем буфера воспроизведения
 ClearOutputBuf(); // First
 SbWorkBufNum^=1;  // Switch 1
 ClearOutputBuf(); // Second

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



// Деинициализация микширования /////////////////////////////////////////////
:void fastcall DeinitMixixg()
{
 $push BP

  // Распределяем память буфера клипирования
  FOR (BP=0; SbNumVoices*256/16+1>BP; BP++) FREEMEM(SbClipTableBufSeg+BP);
  // Распределяем память буфера микширования
  FOR (BP=0; SbWorkBufSize>>4+1>BP; BP++) FREEMEM(SbMixBuf+BP);

 $pop BP
}



// Микширование канала //////////////////////////////////////////////////////
// BX - Voice Number
:void fastcall MixVoice(BX)
{
 $push ES,FS,EBP,EBX

  // Вычисление смещения к структуре канала
  BP=BX*sizeof(VOICE)+#Voice;

  // Если этот канала активный
  if (BP.VOICE.Active)/*&&(!BP.VOICE.Flags&VoiceNoMixFlag)*/{
     // Вычисление масштабных единиц
     SbRate_SampleRate=FDIV_24_8(SbRate,BP.VOICE.Sample.Rate);
     SampleRate_SbRate=FDIV_24_8(BP.VOICE.Sample.Rate,SbRate);

     // Устанавливаем FS:SI на Seg:Offs+Pos семпла канала
     FS=BP.VOICE.Sample.Seg;
     ESI=BP.VOICE.Sample.Pos>>8+BP.VOICE.Sample.Offs;

     // Вычисление длины проигрываемого семпла канала
     ECX=FMUL_24_8(BP.VOICE.Sample.Length<<8-BP.VOICE.Sample.Pos,SbRate_SampleRate)>>8;
     EAX=SbWorkBufSize>>1;
     IF (ECX>EAX) ECX=SbWorkBufSize>>1;
     EAX<<=8;

     // Вычисление прироста положения в семпле
     FMUL_24_8(EAX,SampleRate_SbRate);
     // Освежаем положение в буфере семпла (fixed 24:8)
     BP.VOICE.Sample.Pos+=EAX;
     // Проверяем на последний блок
     IF (CARRYFLAG)||(BP.VOICE.Sample.Pos>>8+1>=BP.VOICE.Sample.Length){
        // Деактивизируем канал
        BP.VOICE.Active=FALSE;
     }

     $push EBP

      // Если размер выборки не равен 0
      if (CX!=0){
         // Устанавливаем ES:DI на буфер микширования
         ES=SbMixBufSeg;
         DI=SbMixBufOffs;

         IF (SbStereo){
            // Делим буфер на два (левый канал/правый канал)
            CX>>=1;

            // Вычисляем громкость для левого канала
            BX=BP.VOICE.Sample.Balance<<1; IF (BX>256) BX=256;
            BX=BX*BP.VOICE.Sample.Volume>>8; DX=BX;

            $push EBP,CX
             // Микшируем левый канал
             StereoMixChannel();
            $pop CX,EBP

            // Вычисляем громкость для правого канала
            BX=-BP.VOICE.Sample.Balance<<1+512; IF (BX>256) BX=256;
            BX=BX*BP.VOICE.Sample.Volume>>8; DX=BX;

            // Устанавливаем начальное смещение в буфере микширования на
            // правый канал
            DI=SbMixBufOffs+2;

            // Микшируем правый канал
            StereoMixChannel();
         }ELSE{
            // DX - громкость канала
            DX=BP.VOICE.Sample.Volume;
            // Микшируем канал
            @MonoMixChannel();
         }
      }

     $pop EBP
  }

 $pop EBX,EBP,FS,ES
}



// Стерео микширования канала ///////////////////////////////////////////////
:void fastcall StereoMixChannel()
{
 // Приращение в буфере семпла
 EAX=SampleRate_SbRate<<1;
 $ror EAX,8  // Меняем дробную и целую части
 EBP=0;      // Начальное смещение в буфере семпла

 // Микшируем правый канал
 loop(CX){
  // Масштабируем семпл в BX и учитываем громкость канала
  BX=FSCHAR[BP+SI]; $imul BX,DX
  // Микшируем промасштабированный семпл с буфером микширования
  $movsx BX,BH
  ESINT[DI]+=BX;
  // Двигаемся по буферу семпла
  EBP+=EAX; $adc EBP,0
  // Двигаемся по буферу микширования (пропускаем правый канал)
  DI+=4;
 }
}



// Моно микширования канала /////////////////////////////////////////////////
:void fastcall MonoMixChannel()
{
 // Приращение в буфере семпла
 EAX=SampleRate_SbRate;
 $ror EAX,8  // Меняем дробную и целую части
 EBP=0;      // Начальное смещение в буфере семпла

 // Микшируем
 loop(CX){
  // Масштабируем семпл в BX и учитываем громкость канала
  BX=FSCHAR[BP+SI]; $imul BX,DX
  // Микшируем промасштабированный семпл с буфером микширования
  $movsx BX,BH
  ESINT[DI]+=BX;
  // Двигаемся по буферу семпла
  EBP+=EAX; $adc EBP,0
  // Двигаемся по буферу микширования (пропускаем правый канал)
  DI+=2;
 }
}
#endif


















#ifdef __DOS32__

// Создание таблицы перевода 16 bit -> 8 bit (-128...127) ///////////// 32 //
:void fastcall CreateClipTable()
{
 $push ES

  // Устанавливаем ES:EDI на селектор:смещение буфера таблицы перевода
  ES=SbClipTableBufSel;
  EDI=SbClipTableBufOffs;

  FOR (EDX=SbNumVoices*128,ECX=-EDX; long ECX<EDX; ECX++){
      // Отсекаем по -128...127
      EAX=ECX;
      IF (long EAX<-128) AL=-128;
      IF (long EAX>127) AL=127;

      AL+=128;

      $stosb  // ESBYTE[DI]=AL; DI++;
  }

 $pop ES
}



// Микширование каналов /////////////////////////////////////////////// 32 //
:void fastcall MixVoices()
{
 // Микшируем каналы
 FOR (EBX=0; BX<SbNumVoices; EBX++) MixVoice(EBX);
}



// Преобразование буфера микширования в буфер воспроизведения ///////// 32 //
:void fastcall MixBuf2OutputBuf()
{
 $push ES,FS,GS,EBP

  // Устанавливаем FS:EBP на Sel:Offs Mixing Buffer
  FS=SbMixBufSel;
  EBP=SbMixBufOffs;
  // Устанавливаем ES:EDI на Sel:Offs Output Buffer
  ES=SbWorkBufSel;
  EDI=SbWorkBufOffs;
  IF (SbWorkBufNum) EDI=SbWorkBufSize>>1;
  // Устанавливаем GS:EBX на середину ClipTable Buffer
  GS=SbClipTableBufSel;
  EBX=128*SbNumVoices+SbClipTableBufOffs;

  // ECX - размер буфера
  ECX=SbWorkBufSize>>1;
  // Преобразуем
  loop(ECX){
   // Считываем volume с буфера микширования (int)
   ESI=FSINT[EBP]; EBP+=2;
   // Преобразуем по ClipTable в 8 bit
   AL=GSBYTE[ESI+EBX];
   // Сохраняем результат в Output буфере
   $stosb // ESBYTE[EDI]=AL; EDI++;
  }

 $pop EBP,GS,FS,ES
}



// Очистка Mixing Buffer-а //////////////////////////////////////////// 32 //
:void fastcall ClearMixBuf()
{
 // Очищаем SbMixBuf
 $push ES

  // Устанавливаем ES:EDI на адрес буфера микширования
  ES=SbMixBufSel;       // Sel
  EDI=SbMixBufOffs;     // Offs
  ECX=SbWorkBufSize>>2; // (SbMixBuf Size)/2, т.к. 16 бит
  EAX=0;                // ClearDWord
  $rep $stosd           // Clear Data by ClearDWord

 $pop ES
}



// Очистка буфера воспроизведения ///////////////////////////////////// 32 //
:void ClearOutputBuf()
{
 // Очищаем SbWorkBuf
 $push ES

  // Устанавливаем ES:EDI на адрес рабочего буфера
  ES=SbWorkBufSel;
  EDI=SbWorkBufOffs;
  IF (SbWorkBufNum) EDI=SbWorkBufSize>>1;
  ECX=SbWorkBufSize>>3;   // (SbWorkBuf Size)/4
  EAX=80808080h;          // ClearDWord
  $rep $stosd             // Clear Data by ClearDWord

 $pop ES
}



// Инициализация микширования ///////////////////////////////////////// 32 //
:void fastcall InitMixing()
{
 // Выделение памяти под буфер перевода
// IF (!AllocateDosMemBlock(SbNumVoices*256/16+1)) WriteStr("Не могу выделить память (3)");
 IF (!AllocateDosMemBlock(SbNumVoices<<4+1)) WriteStr("Не могу выделить память (3)");
 SbClipTableBufSeg=AX;
 SbClipTableBufSel=DX;
 SbClipTableBufOffs=0;

 // Выделение памяти под буфер микширования (16 bit)
// IF (!AllocateDosMemBlock(SbWorkBufSize*2/16+1)) WriteStr("Не могу выделить память (4)");
 IF (!AllocateDosMemBlock(SbWorkBufSize>>4+1)) WriteStr("Не могу выделить память (4)");
 SbMixBufSeg=AX;
 SbMixBufSel=DX;
 SbMixBufOffs=0;

 // Создание таблицы клипирования
 CreateClipTable();

 // Стартовый рабочий буфер
 SbWorkBufNum=0;
 // Очищаем буфера воспроизведения
 ClearOutputBuf(); // First
 SbWorkBufNum^=1;  // Switch 1
 ClearOutputBuf(); // Second

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



// Деинициализация микширования /////////////////////////////////////// 32 //
:void fastcall DeinitMixixg()
{
 $push BP

  // Распределяем память буфера клипирования
  FOR (BP=0; SbNumVoices*256/16+1>BP; BP++) FREEMEM(SbClipTableBufSeg+BP);
  // Распределяем память буфера микширования (16 bit)
  FOR (BP=0; SbWorkBufSize>>4+1>BP; BP++) FREEMEM(SbMixBuf+BP);

 $pop BP
}



// Микширование канала //////////////////////////////////////////////// 32 //
// EBX - Voice Number
:void fastcall MixVoice(EBX)
{
 $push ES,FS,EBP,EBX

  // Вычисление смещения к структуре канала
  EBP=EBX*sizeof(VOICE)+#Voice;

  // Если этот канала активный
  if (EBP.VOICE.Active)/*&&(!BP.VOICE.Flags&VoiceNoMixFlag)*/{
     // Вычисление масштабных единиц
     SbRate_SampleRate=FDIV_24_8(SbRate,EBP.VOICE.Sample.Rate);
     SampleRate_SbRate=FDIV_24_8(EBP.VOICE.Sample.Rate,SbRate);

     // Устанавливаем FS:ESI на Sel:Offs+Pos семпла канала
     FS=EBP.VOICE.Sample.Sel;
     ESI=EBP.VOICE.Sample.Pos>>8+EBP.VOICE.Sample.Offs;

     // Вычисление длины проигрываемого семпла канала
     ECX=FMUL_24_8(EBP.VOICE.Sample.Length<<8-EBP.VOICE.Sample.Pos,SbRate_SampleRate)>>8;
     EAX=SbWorkBufSize>>1;
     IF (ECX>EAX) ECX=SbWorkBufSize>>1;
     EAX<<=8;

     // Вычисление прироста положения в семпле
     FMUL_24_8(EAX,SampleRate_SbRate);
     // Освежаем положение в буфере семпла (fixed 24:8)
     EBP.VOICE.Sample.Pos+=EAX;
     // Проверяем на последний блок
     IF (CARRYFLAG)||(EBP.VOICE.Sample.Pos>>8+1>=EBP.VOICE.Sample.Length){
        // Деактивизируем канал
        EBP.VOICE.Active=FALSE;
     }

     $push EBP

      // Если размер выборки не равен 0
      if (ECX!=0){
         // Устанавливаем ES:EDI на буфер микширования
         ES=SbMixBufSel;
         EDI=SbMixBufOffs;

         IF (SbStereo){
            // Делим буфер на два (левый канал/правый канал)
            ECX>>=1;

            // Вычисляем громкость для левого канала
            EBX=EBP.VOICE.Sample.Balance<<1; IF (EBX>256) EBX=256;
            EBX=EBX*EBP.VOICE.Sample.Volume>>8;

            $push EBP,ECX
             // Микшируем левый канал
             StereoMixChannel();
            $pop ECX,EBP

            // Вычисляем громкость для правого канала
            EBX=-EBP.VOICE.Sample.Balance<<1+512; IF (EBX>256) EBX=256;
            EBX=EBX*EBP.VOICE.Sample.Volume>>8;

            // Устанавливаем начальное смещение в буфере микширования на
            // правый канал
            EDI=SbMixBufOffs+2;

            // Микшируем правый канал
            StereoMixChannel();
         }ELSE{
            // EBX - громкость канала
            EBX=EBP.VOICE.Sample.Volume;
            // Микшируем канал
            @MonoMixChannel();
         }
      }

     $pop EBP
  }

 $pop EBX,EBP,FS,ES
}



// Стерео микширования канала ///////////////////////////////////////// 32 //
:void fastcall StereoMixChannel()
{
 // Приращение в буфере семпла
 EAX=SampleRate_SbRate<<1;
 $ror EAX,8  // Меняем дробную и целую части
 EBP=0;      // Начальное смещение в буфере семпла

 // Микшируем правый канал
 loop(ECX){
  // Масштабируем семпл в DX и учитываем громкость канала
  EDX=EBP&0FFFFFFh;
  DX=FSCHAR[EDX+ESI]; $imul DX,BX
  // Микшируем промасштабированный семпл с буфером микширования
  $movsx DX,DH
  ESINT[EDI]+=DX;
  // Двигаемся по буферу семпла
  EBP+=EAX; $adc EBP,0
  // Двигаемся по буферу микширования (пропускаем правый канал)
  EDI+=4;
 }
}



// Моно микширования канала /////////////////////////////////////////// 32 //
:void fastcall MonoMixChannel()
{
 // Приращение в буфере семпла
 EAX=SampleRate_SbRate;
 $ror EAX,8  // Меняем дробную и целую части
 EBP=0;      // Начальное смещение в буфере

 // Микшируем
 loop(ECX){
  // Масштабируем семпл в DX и учитываем громкость канала
  EDX=EBP&0FFFFFFh;
  DX=FSCHAR[EDX+ESI]; $imul DX,BX
  // Микшируем промасштабированный семпл с буфером микширования
  $movsx DX,DH
  ESINT[EDI]+=DX;
  // Двигаемся по буферу семпла
  EBP+=EAX; $adc EBP,0
  // Двигаемся по буферу микширования
  EDI+=2;
 }
}

#endif