// Подключение библы для работы с фиксированной точкой
#include "soundsys\fixed.h--"
// Подключаем файл с дефайнами
#include "soundsys\modplay.inc"
// Подключаем инклюд для подгрузки мод-файлов
#include "soundsys\modload.h--"



// Объявление процедур
void fastcall PlayModVoice8BitVoice(BX,DI);
void fastcall PlayModSample8BitVoice(BX,EDX,DI);
void fastcall PlayModSample8BitRandomVoice(BX,EDX);
void fastcall ClearModWorkBuf(AX);
//void fastcall far ModPlayer();
void fastcall InitModSystem();
void fastcall DeInitModSystem();
void fastcall NextLine();
void fastcall NextPattern();
void fastcall FillModVoicesInfo();
void fastcall SetBPM(AL);
void fastcall EffectVolumeSlide(AL);
void fastcall EffectTonePortamento();
void fastcall EffectPortamentoUp();
void fastcall EffectPortamentoDown();
void fastcall EffectArpeggio();
void fastcall EffectSetSpeed();
void fastcall EffectPositionJump();
void fastcall EffectSetVolume();
void fastcall EffectPatternBreak();
void fastcall EffectVibrato(AL);
void fastcall EffectTremolo();
void fastcall InitSampleInfo();
void fastcall EffectFinePortamentoUp();
void fastcall EffectFinePortamentoDown();
void fastcall EffectFineVolumeSlideUp();
void fastcall EffectFineVolumeSlideDown();



// Проигрывание семпла на свободном канале //////////////////////////////////
// BX - ModVoice Number
// DI - Voice Number
:void fastcall PlayModVoice8BitVoice(BX,DI)
{
 // DI - смещение к каналу
 DI=sizeof(VOICE)*DI+#Voice;
 // Рассчитываем баланс
 IF (!$test BL,1) DI.VOICE.Sample.Balance=0FFh;
 ELSE DI.VOICE.Sample.Balance=000h;
 // BX - смещение к структуре мод канала
 BX=sizeof(MODVOICE)*BX+#ModVoice;

 // Инициализируем канал
 DI.VOICE.Sample.Seg=BX.MODVOICE.ModWorkBufSeg;
 DI.VOICE.Sample.Offs=0;
 DI.VOICE.Sample.Length=BX.MODVOICE.ModWorkBufSampleLength;

 // Устанавливаем параметры канала
 DI.VOICE.Sample.Volume=int BX.MODVOICE.CurrentVolume*ModModule.MasterVolume/64;
// DI.VOICE.Sample.RepeatOffs=BX.MODMODULE.ModSample.RepeatOffs;
// DI.VOICE.Sample.RepeatLength=BX.MODMODULE.ModSample.RepeatLength;

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

 // Вычисление позиции в семпле
 DI.VOICE.Sample.Pos=0;  // fixed 24:8

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



// Проигрывание семпла на свободном канале //////////////////////////////////
// BX - Sample Number
// EDX - Sample Rate
// DI - Voice Number
:void fastcall PlayModSample8BitVoice(BX,EDX,DI)
{
 // DI - смещение к каналу
 DI=sizeof(VOICE)*DI+#Voice;

 // SI - смещение в таблице сегментов семплов
 SI=BX<<1;
 // Инициализируем канал
 DI.VOICE.Sample.Seg=ModModule.SampleSeg[SI];
 // Вычисляем адрес структуры мод-семпла
 BX=sizeof(MODSAMPLE)*BX+#ModModule;

 DI.VOICE.Sample.Offs=0;
 DI.VOICE.Sample.Length=BX.MODMODULE.ModSample.Length;

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

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

 // Вычисление позиции в семпле
 DI.VOICE.Sample.Pos=0;  // fixed 24:8

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




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

        DI.VOICE.Sample.Offs=0;
        DI.VOICE.Sample.Length=BX.MODMODULE.ModSample.Length;

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

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

        // Вычисление позиции в семпле
        DI.VOICE.Sample.Pos=0;  // fixed 24:8

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

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



// Очистка рабочего буфера
// AX - сегмент рабочего буфера канала
:void fastcall ClearModWorkBuf(AX)
{
 // Очищаем рабочий буфер
 $push ES
  ES=AX;                // Сегмент рабочего буфера
  DI=0;                 // Смещение в рабочем буфере
  CX=ModWorkBufSize>>2; // Длина рабочего буфера
  EAX=80808080h;        //
  $rep $stosd           // Очищаем рабочий буфер
 $pop ES
}



// Мод-плейер
/*:void fastcall far*/ ModPlayer()
{
 if (PlayModModule){
    $push ES,FS,BP

     // Нулевой кадр ?
     IF (!ModModule.Frame){
        // Подгружаем информаци текущей строки в паттерне
        FillModVoicesInfo();
     }

     // Обрабатываем эфекты
     for (CX=0,BP=#ModVoice; CL<ModModule.NumVoices; CX++,BP+=sizeof(MODVOICE)){
         $push CX
          // Какой эффект ?
          AL=BP.MODVOICE.PatternElement.EffectNumber;
          switch(AL){
           CASE Arpeggio:                            // 000h
            // Применяем эффект Arpeggio
            EffectArpeggio();
            break;
           CASE PortamentoUp:                        // 001h
            // Применяем эффект Portamento Up
            EffectPortamentoUp();
            break;
           CASE PortamentoDown:                      // 002h
            // Применяем эффект Portamento Down
            EffectPortamentoDown();
            break;
           CASE TonePortamento:                      // 003h
            // Применяем эффект Tone Portamento
            EffectTonePortamento();
            break;
           CASE Vibrato:                             // 004h
            // Применяем эффект Vibrato
            EffectVibrato(BP.MODVOICE.PatternElement.EffectParametr);
            break;
           CASE TonePortamentoPlusVolumeSlide:       // 005h
            // Применяем эффект Tone Portamento
            EffectTonePortamento();
            // Применяем эффект Volume Slide
            EffectVolumeSlide(BP.MODVOICE.PatternElement.EffectParametr);
            BREAK;
           CASE VibratoPlusVolumeSlide:              // 006h
            // Применяем эффект Vibrato
            EffectVibrato(BP.MODVOICE.VibratoEffectParametr);
            // Применяем эффект Volume Slide
            EffectVolumeSlide(BP.MODVOICE.PatternElement.EffectParametr);
            BREAK;
           CASE Tremolo:                             // 007h
            // Используем эффект тремоло
            EffectTremolo();
            BREAK;
           CASE Pan:                                 // 008h
            BREAK;
           CASE SampleOffset:                        // 009h
            // Это нулевой кадр ?
            IF (!ModModule.Frame){
               // Рассчитываем смещение в семпле (XY00h)
               EAX=BP.MODVOICE.PatternElement.EffectParametr<<16;
               // Сохраняем смещение в семпле
               BP.MODVOICE.SampleOffs=EAX;
            }
            BREAK;
           CASE VolumeSlide:                         // 00Ah
            // Применяем эффект Volume Slide
            EffectVolumeSlide(BP.MODVOICE.PatternElement.EffectParametr);
            BREAK;
           CASE PositionJump:                        // 00Bh
            // Применяем эффект Position Jump
            EffectPositionJump();
            BREAK;
           case SetVolume:                           // 00Ch
            // Применяем эффект Set Volume
            EffectSetVolume(BP.MODVOICE.PatternElement.EffectParametr);
            BREAK;
           case PatternBreak:                        // 00Dh
            // Применяем эффект Pattern Break
            EffectPatternBreak();
            BREAK;
           CASE ExtendedEffects:                     // Расширенные эффекты
            SWITCH(BP.MODVOICE.PatternElement.EffectParametr>>4){
             CASE FinePortamentoUp:                    // 0E1h
              // Применяем эффект Fine Portamento Up
              EffectFinePortamentoUp();
              BREAK;
             CASE FinePortamentoDown:                  // 0E2h
              // Применяем эффект Fine Portamento Down
              EffectFinePortamentoDown();
              BREAK;
             CASE FineVolumeSlideUp:                   // 0EAh
              // Применяем эффект Fine Volume Slide Up
              EffectFineVolumeSlideUp();
              BREAK;
             CASE FineVolumeSlideDown:                 // 0EBh
              // Применяем эффект Fine Volume Slide Down
              EffectFineVolumeSlideDown();
              BREAK;
            }
            BREAK;
           case SetSpeed:                            // 00Fh
            // Применяем эффект Set Speed
            EffectSetSpeed();
            BREAK;
          }
         $pop CX
     }

     // Обрабатываем семплы каналов
     for (BP=#ModVoice,CX=0; CL<ModModule.NumVoices; BP+=sizeof(MODVOICE),CX++){
         #ifdef __DEBUG__
/*          $pushad
           IF (BP.MODVOICE.PatternElement.NotePeriod>MaxPeriod)||(BP.MODVOICE.PatternElement.NotePeriod<MinPeriod)&&(BP.MODVOICE.PatternElement.NotePeriod!=0){
              $push CX
               AH=3; BH=0; $int 10h
              $pop CX
              $push DX
               $push CX
                GotoXY(20,0);
                WriteByteHex(ModModule.OrderPos); Write(' ');
                WriteByteHex(ModModule.PatternLine); Write(' ');
               $pop CX
               WriteByteHex(CL); Write(' ');
               WriteWordHex(BP.MODVOICE.PatternElement.NotePeriod);
              $pop DX
              AH=2; BH=0; $int 10h
              PlayModModule=FALSE;
           }
          $popad*/
         #endif
         // Канал активен ?
         if (BP.MODVOICE.Active){
            $push CX

             // Период ноты ненулевой ?
             if (BP.MODVOICE.PatternElement.NotePeriod){
                // Очищаем рабочий буфер канала
//                ClearModWorkBuf(BP.MODVOICE.ModWorkBufSeg);

                // Рассчитываем частоту выборки на канале по формуле:
                // F=A/(период*2), где A - либо 7093789.2, либо 7159090.5
//                 SI=#ModModule.ModSample[BP.MODVOICE.PatternElement.SampleNumber-1];
//                 EBX=SI.MODSAMPLE.FineTune*6+BP.MODVOICE.PatternElement.NotePeriod<<9;
//                 BP.MODVOICE.CurrentRate=@FDIV_24_8(6D3D3200h,EBX)>>8;
                BP.MODVOICE.CurrentRate=@FDIV_24_8(6C3E1D33h,BP.MODVOICE.PatternElement.NotePeriod<<9)>>8;

                // Рассчитываем отношение CurrentRate/SbRate
                CurrentRate_SbRate=@FDIV_24_8(BP.MODVOICE.CurrentRate,SbRate);

                // Вычисляем размер рабочего буфера канала (SbWorkBufSize/2)
                EAX=SbWorkBufSize<<7; // fixed 24:8

                ECX=EAX>>8;
                BP.MODVOICE.ModWorkBufSampleLength=CX;

                // Длина рабочего буфера не 0 ?
                if (CX!=0){
                   // FS - сегмент семпла
                   FS=BP.MODVOICE.SampleSeg;
                   // ESI - смещение в сегменте семпла (fixed 24:8)
                   ESI=BP.MODVOICE.SampleOffs;
                   // ES - сегмент рабочего буфера канала
                   ES=BP.MODVOICE.ModWorkBufSeg;
                   // DI - смещение в рабочем буфере канала
                   DI=0;

                   // Переносим семпл в рабочий буфер канала
                   IF (BP.MODVOICE.RepeatLength>2){
                      $push EBP

                       // EAX - приращение в буфере семпла CurrentRate/SbRate
                       EAX=CurrentRate_SbRate;
                       // Обрабатываем зацикленный семпл
                       EDX=BP.MODVOICE.RepeatOffs<<8;
                       EBP=BP.MODVOICE.RepeatLength<<8+EDX;

                       do{
                         EBX=ESI>>8;
                         BL=FSBYTE[BX];
                         ESBYTE[DI]=BL;
                         // Приращиваем положение в буфере семпла
                         ESI+=EAX;
                         // Приращиваем положение в рабочем буфере канала
                         DI++;
                         // Граница зацикливания ?
                         IF (ESI>=EBP) ESI=EDX;
                         CX--;
                       }while (!ZEROFLAG);

                      $pop EBP

                      // Сохраняем текущее положение в семпле
                      BP.MODVOICE.SampleOffs=ESI;

                   }ELSE{
                      // EAX - приращение в буфере семпла CurrentRate/SbRate
                      EAX=CurrentRate_SbRate;
                      EDX=BP.MODVOICE.SampleLength;
                      do{
                        IF (ESI>=EDX) BREAK;
                        EBX=ESI>>8;
                        BL=FSBYTE[BX];
                        ESBYTE[DI]=BL;
                        // Приращиваем положение в буфере семпла
                        ESI+=EAX;
                        // Приращиваем положение в рабочем буфере
                        DI++;
                        CX--;
                      }while (!ZEROFLAG);

                      // Сохраняем положение в семпле
                      BP.MODVOICE.SampleOffs=ESI;

                      // Положение в семпле >= длины семпла ?
                      IF (CX){
                         // Вырубаем канал
                         BP.MODVOICE.Active=FALSE;
                         // Перерассчитываем длину рабочего буфера канала
//                         BP.MODVOICE.ModWorkBufSampleLength=BP.MODVOICE.SampleLength-BP.MODVOICE.SampleOffs>>8;
                         BP.MODVOICE.ModWorkBufSampleLength=DI;
                      }
                   }

                   // Узнаем номер мод-канала
                   $pop BX $push BX
                   // Воспроизводим рабочий буфер мод-канала
                   PlayModVoice8BitVoice(BX,BX);
                }
             }

            $pop CX
         }
     }

     #ifdef __DEBUG__
      $pushad
       AH=3; BH=0; $int 10h
       $push DX
        GotoXY(0,0); WriteStr("Name: "); FOR (BX=0; BX<20; BX++) Write(ModModule.Name[BX]);
        GotoXY(25,14); WriteStr("Speed: "); WriteByteHex(ModModule.Speed);
        GotoXY(25,15); WriteStr("Master Volume: "); WriteByteHex(ModModule.MasterVolume);
        GotoXY(0,16);
        WriteStr("Order Pattern Line Frame\n");
        WriteByteHex(ModModule.OrderPos); Write('/'); WriteByteHex(ModModule.OrderLength-1); WriteStr(" ");
        WriteByteHex(ModModule.Order[ModModule.OrderPos]); WriteStr("      ");
        WriteByteHex(ModModule.PatternLine); WriteStr("   ");
        WriteByteHex(ModModule.Frame);
        WriteStr("\n\nSm NPrd SPrd Rate       Eff EffParam Vol Use");
        FOR (CX=0,BP=#ModVoice; CL<ModModule.NumVoices; CX++,BP+=sizeof(MODVOICE)){
            $push CX
             Writeln();
             WriteByteHex(BP.MODVOICE.PatternElement.SampleNumber); WriteStr(" ");
             WriteWordHex(BP.MODVOICE.PatternElement.NotePeriod); WriteStr(" ");
             WriteWordHex(BP.MODVOICE.TonePortamentoNotePeriod); WriteStr(" ");
             WriteDWordNDigits(BP.MODVOICE.CurrentRate,10); WriteStr(" ");
             WriteByteHex(BP.MODVOICE.PatternElement.EffectNumber); WriteStr("  ");
             WriteByteHex(BP.MODVOICE.PatternElement.EffectParametr); WriteStr("       ");
             WriteByteHex(BP.MODVOICE.CurrentVolume); WriteStr("  ");
             WriteByte(BP.MODVOICE.Active);
            $pop CX
        }
       $pop DX
       GotoXY(DL,DH);
      $popad
     #endif


     // Приращиваем кадр
     ModModule.Frame++;

     // Это последний кадр ?
     IF (ModModule.Frame==ModModule.Speed)&&(ModModule.Speed){
        // Сбрасываем кадр
        ModModule.Frame=0;
        // Переходим на следующую строку
        NextLine();
     }

    $pop BP,FS,ES
 }
}



// Инициализация мод-системы
:void fastcall InitModSystem()
{
 $push ES,BP

  // Сбрасываем все структуры
  ModModule=0;
  ModVoice=0;
  // Устанавливаем по умолчанию некоторые переменные
  ModModule.Speed=6;
  ModModule.BPM=125;
  ModModule.OrderPos=0;
  ModModule.PatternLine=0;
  ModModule.Frame=0;
  ModModule.MasterVolume=40h;
  // Рассчитываем длину рабочего буфера
  FOR (CX=0,BP=#ModVoice; CL<MaxModVoices; CX++,BP+=sizeof(MODVOICE)){
      $push CX
       // LastSampleNumber = 0
       BP.MODVOICE.LastSampleNumber=0;
       BP.MODVOICE.LastNotePeriod=0;
       BP.MODVOICE.LastRate=0;
       BP.MODVOICE.Active=FALSE;
       BP.MODVOICE.CurrentVolume=40h;
       BP.MODVOICE.PatternElement.SampleNumber=0;
       BP.MODVOICE.PatternElement.NotePeriod=0;
       BP.MODVOICE.VibratoNegative=FALSE;
       // Выделяем память под рабочий буфер канала (SbRate/102)
       BP.MODVOICE.ModWorkBufSeg=GETMEM(/*SbRate/102>>4+1*/256);
      $pop CX
  }

  // Рассчитываем длину рабочих буферов каналов
  BX=ModModule.BPM*2/5; ModWorkBufSize=SbRate/BX;

 $pop BP,ES
}



// Деинициализация мод-системы
:void fastcall DeInitModSystem()
{
 $push ES,BP

  // Выключаем воспроизведение мод-модуля
  PlayModModule=FALSE;

  // Освобождаем память рабочих буферов каналов
  FOR (BP=#ModVoice,CX=0; CL<MaxModVoices; CX++,BP+=sizeof(MODVOICE)){
      // Освобождаем рабочий буфер канала
      FOR (DX=0; DX<256; DX++) FREEMEM(BP.MODVOICE.ModWorkBufSeg+DX);
  }

  BX=sizeof(PATTERNELEMENT)*64/16*ModModule.NumVoices;
  // Освобождаем память паттернов
  FOR (BP=0,CX=0; CL<ModModule.NumPatterns; CX++,BP+=2){
      // Освобождаем память паттерна
      FOR (DX=0; DX<BX; DX++) FREEMEM(ModModule.PatternSeg[BP]+DX);
  }

  // Освобождаем память семплов
  FOR (BP=#ModModule.ModSample,CX=0; CL<ModModule.NumSamples; CX++,BP+=sizeof(MODSAMPLE)){
      // Узнаме начальный адрес семпла
//      SI=ModModule.SampleSeg[CX*2];
      SI=CX*2;
      BX=BP.MODSAMPLE.Length/16+1;
      // Освобождаем память из-под семпла
      FOR (DX=0; DX<BX; DX++) FREEMEM(ModModule.SampleSeg[SI]+DX);
  }

  #ifdef __DEBUG__
/*   BX=256*MaxModVoices;
   BX+=sizeof(PATTERNELEMENT)*64/16*ModModule.NumVoices;
   FOR (BP=#ModModule.ModSample,CX=0; CL<ModModule.NumSamples; BP+=sizeof(MODSAMPLE),CX++){
       BX+=BP.MODSAMPLE.Length/16;
   }
   Writeln();
   WriteWordHex(BX);*/
  #endif

 $pop BP,ES
}



// Переход на следующую строку в паттерне
:void fastcall NextLine()
{
 // Приращиваем номер строки в паттерне
 ModModule.PatternLine++;
 // Номер строки в паттерне > 3Fh ?
 IF (char ModModule.PatternLine>3Fh){
    // Сбрасываем текущую линию в паттерне
    ModModule.PatternLine=0;
    // Переходим на следующий паттерн
    NextPattern();
 }
}



// Переход на следующий паттерн
:void fastcall NextPattern()
{
 // Приращиваем положение в списке очередности паттернов
 ModModule.OrderPos++;
 // Текущее положение в списке очередности паттернов == длины списка ?

 IF (ModModule.OrderPos==ModModule.OrderLength){
// IF (ModModule.OrderPos==2){
    // Переходим на начало списка
    ModModule.OrderPos=ModModule.ReStart;
//    ModModule.OrderPos=1;
    // Вырубаем воспроизведение мод-модуля
//    PlayModModule=FALSE;
 }
}



// Переход на предыдущий паттерн
:void fastcall PreviousPattern()
{
 // Самое начало списка ?
 IF (!ModModule.OrderPos){
    // Положение в списке очередности паттернов = длине списка
    ModModule.OrderPos=ModModule.OrderLength;
 }
 // Уменьшаем положение в списке очередности паттернов
 ModModule.OrderPos--;
}



// Заполнение структур каналов информацией
:void fastcall FillModVoicesInfo()
{
 $push ES,FS,BP

  // Устанавливаем FS на сегмент текущего паттерна
  FS=ModModule.PatternSeg[ModModule.Order[ModModule.OrderPos]];
  // SI - смещение в сегменте паттерна
  SI=sizeof(PATTERNELEMENT)*ModModule.NumVoices*ModModule.PatternLine;
  // Устанавливаем ES на сегмент данных
  ES=DS;

  for (DX=0,BP=#ModVoice; DL<ModModule.NumVoices; DX++,BP+=sizeof(MODVOICE)){
      $push DX

       // Сохраняем последние использовавшиеся данные на канале
        // Номер семпла не 0 ?
        IF (BP.MODVOICE.PatternElement.SampleNumber){
           // Сохраняем последний использовавшийся на канале семпл
           BP.MODVOICE.LastSampleNumber=BP.MODVOICE.PatternElement.SampleNumber;
        }
        // Период ноты не 0 ?
        IF (BP.MODVOICE.PatternElement.NotePeriod){
           // На канале был использован эффект Vibrato или VibratoPlusVolumeSlide ?
           IF (BP.MODVOICE.PatternElement.EffectNumber==Vibrato)||(BP.MODVOICE.PatternElement.EffectNumber==VibratoPlusVolumeSlide){
              // Берем период ноты у эффекта Vibrato
              AX=BP.MODVOICE.VibratoNotePeriod;
           }ELSE{
              // Берем текущий период ноты
              AX=BP.MODVOICE.PatternElement.NotePeriod;
           }
           // Сохраняем последний период ноты на канале
           BP.MODVOICE.LastNotePeriod=AX;
        }

       // DI - смещение к структуре паттерна в текущем канале
       DI=#BP.MODVOICE.PatternElement.SampleNumber;
       // CX - длина элемента паттерна
       CX=sizeof(PATTERNELEMENT);
       $rep $FS: $movsb

       // Если встретилась и нота, и семлп, то они становятся текущими
       IF (BP.MODVOICE.PatternElement.SampleNumber)&&(BP.MODVOICE.PatternElement.NotePeriod){
          // Полностью инициализируем информацию о семпле
          InitSampleInfo();
          // Инициализируем громкость канала
          BP.MODVOICE.CurrentVolume=BX.MODSAMPLE.Volume;
          // Активизируем канал
          BP.MODVOICE.Active=TRUE;
       }
       // Если встретилась нота при незаданном номере семпла
       else IF (BP.MODVOICE.PatternElement.NotePeriod)&&(!BP.MODVOICE.LastSampleNumber){
          // Сохраняем текущую ноту
          BP.MODVOICE.LastNotePeriod=BP.MODVOICE.PatternElement.NotePeriod;
       }
       // Если встретился семпл при незаданном периоде ноты
       ELSE IF (BP.MODVOICE.PatternElement.SampleNumber)&&(!BP.MODVOICE.LastNotePeriod){
          // Сохраняем текущий семпл
          BP.MODVOICE.LastSampleNumber=BP.MODVOICE.PatternElement.SampleNumber;
       }
       // Если встретилась только нота при заданном семпле
       ELSE IF (BP.MODVOICE.PatternElement.NotePeriod)&&(BP.MODVOICE.LastSampleNumber){
          // Заданный номер семпла становится текущим
          BP.MODVOICE.PatternElement.SampleNumber=BP.MODVOICE.LastSampleNumber;
          // Полностью инициализируем информацию о семпле
          InitSampleInfo();
          // Активизируем канал
          BP.MODVOICE.Active=TRUE;
       }
       // Если встретился только семпл при заданной ноте
       ELSE IF (BP.MODVOICE.PatternElement.SampleNumber)&&(BP.MODVOICE.LastNotePeriod){
          // Если семпл совпадает с текущим семлом
          IF (BP.MODVOICE.PatternElement.SampleNumber==BP.MODVOICE.LastSampleNumber){
             // BX - смещение структуры семпла
             // !!! (номера семплов в мод формате начинаются с 1)
             BX=BP.MODVOICE.PatternElement.SampleNumber-1*sizeof(MODSAMPLE)+#ModModule.ModSample;
          }ELSE{
             // Полностью инициализируем информацию о семпле
             InitSampleInfo();
          }
          // Инициализируем громкость семпла
          BP.MODVOICE.CurrentVolume=BX.MODSAMPLE.Volume;
          // Период ноты - предыдущий период ноты
          BP.MODVOICE.PatternElement.NotePeriod=BP.MODVOICE.LastNotePeriod;
          // Активизируем канал
          BP.MODVOICE.Active=TRUE;
       }

       // Если это зацикленный семпл
       IF (BP.MODVOICE.Active)&&(!BP.MODVOICE.PatternElement.SampleNumber)&&(!BP.MODVOICE.PatternElement.NotePeriod){
          // Сохраняем текущий семпл
          BP.MODVOICE.PatternElement.SampleNumber=BP.MODVOICE.LastSampleNumber;
          // Сохраняем текущую ноту
          BP.MODVOICE.PatternElement.NotePeriod=BP.MODVOICE.LastNotePeriod;
       }

      $pop DX
  }

 $pop BP,FS,ES
}



// Установка количества ударов в мин.
// AL - количество ударов в минуту
:void fastcall SetBPM(AL)
{
 // Сохраняем новую BPM
 ModModule.BPM=AL;
 // Рассчитываем длину рабочих буферов каналов
 BX=ModModule.BPM*2/5; ModWorkBufSize=SbRate/BX;
}



// Эффект Volume Slide (00Ah)
// AL - параметр эффекта
// BP - адрес структуры канала
:void fastcall EffectVolumeSlide(AL)
{
 // Ненулевой кадр ?
 IF (ModModule.Frame){
    IF ($test AL,0F0h) AL>>=4;
    ELSE{
       AL&=0Fh;
       -AL;
    }
    // Делаем скольжение громкости на канале
    EffectSetVolume(AL+BP.MODVOICE.CurrentVolume);
 }
}



// Эффект Tone Portamento (003h)
// BP - адрес структуры канала
:void fastcall EffectTonePortamento()
{
 // Ненулевой кадр ?
 IF (ModModule.Frame){
    // Надо уменьшать период ноты ?
    IF (int BP.MODVOICE.PatternElement.NotePeriod>BP.MODVOICE.TonePortamentoNotePeriod){
       // Уменьшаем период ноты
       BP.MODVOICE.PatternElement.NotePeriod-=BP.MODVOICE.TonePortamentoEffectParametr;
       // Период ноты < периода ноты-параметра
       IF (int BP.MODVOICE.PatternElement.NotePeriod<BP.MODVOICE.TonePortamentoNotePeriod){
          // Фиксируем период ноты
          BP.MODVOICE.PatternElement.NotePeriod=BP.MODVOICE.TonePortamentoNotePeriod;
       }
    }ELSE IF (int BP.MODVOICE.PatternElement.NotePeriod<BP.MODVOICE.TonePortamentoNotePeriod){
       // Увеличиваем период ноты
       BP.MODVOICE.PatternElement.NotePeriod+=BP.MODVOICE.TonePortamentoEffectParametr;
       // Период ноты > периода ноты-параметра
       IF (int BP.MODVOICE.PatternElement.NotePeriod>BP.MODVOICE.TonePortamentoNotePeriod){
          // Фиксируем период ноты
          BP.MODVOICE.PatternElement.NotePeriod=BP.MODVOICE.TonePortamentoNotePeriod;
       }
    }
 }ELSE{
    // Параметр эффекта не 0 ?
    IF (BP.MODVOICE.PatternElement.EffectParametr){
       // Сохраняем текущий параметр эффекта
       BP.MODVOICE.TonePortamentoEffectParametr=BP.MODVOICE.PatternElement.EffectParametr;
    }ELSE{
       // Восстанавливаем ненулевой параметр эффекта
       BP.MODVOICE.PatternElement.EffectParametr=BP.MODVOICE.TonePortamentoEffectParametr;
    }
    // Сохраняем период ноты, к которой необходимо скользить
    BP.MODVOICE.TonePortamentoNotePeriod=BP.MODVOICE.PatternElement.NotePeriod;
    // Текущая нота - предыдущая
    BP.MODVOICE.PatternElement.NotePeriod=BP.MODVOICE.LastNotePeriod;
 }
}



// Эффект Portamento Up (001h)
// BP - адрес структуры канала
:void fastcall EffectPortamentoUp()
{
 // Это ненулевой кадр ?
 IF (ModModule.Frame){
    // AX - параметр эффекта
    AX=BP.MODVOICE.PatternElement.EffectParametr;
    // Перерассчитываем текущий период ноты
    BX=BP.MODVOICE.PatternElement.NotePeriod-AX;
    // Клипируем период ноты
    IF (BX>MaxPeriod) BX=MaxPeriod;
    IF (BX<MinPeriod) BX=MinPeriod;
    // Сохраняем период ноты
    BP.MODVOICE.PatternElement.NotePeriod=BX;
 }
}



// Эффект Portamento Down (002h)
// BP - адрес структуры канала
:void fastcall EffectPortamentoDown()
{
 // Это ненулевой кадр ?
 IF (ModModule.Frame){
    // AX - параметр эффекта
    AX=BP.MODVOICE.PatternElement.EffectParametr;
    // Перерассчитываем текущий период ноты
    BX=BP.MODVOICE.PatternElement.NotePeriod+AX;
    // Клипируем период ноты
    IF (BX>MaxPeriod) BX=MaxPeriod;
    IF (BX<MinPeriod) BX=MinPeriod;
    // Сохраняем период ноты
    BP.MODVOICE.PatternElement.NotePeriod=BX;
 }
}



// Эффект Arpeggio (000h)
// BP - адрес структуры канала
:void fastcall EffectArpeggio()
{
 // Есть какой-нибудь параметр ?
 if (BP.MODVOICE.PatternElement.EffectParametr){
    // Это нулевой кадр ?
    IF (!ModModule.Frame){
       // Инициализируем эффект Arpeggio
       // Ищим по таблице периодов нужный период
        // AX - период ноты
        AX=BP.MODVOICE.PatternElement.NotePeriod;
        // BX - смещение в таблице периодов
        BX=#PeriodTable;
        // Длина таблицы периодов
        CX=36;
        do{
         // Период ноты >= периоду в таблице периодов
         IF (AX>=DSWORD[BX]) BREAK;
         BX+=2;
         CX--;
        }while (!ZEROFLAG);
        // Период для нулевого кадра
        AX=DSWORD[BX];
        IF (AX>MaxPeriod) AX=MaxPeriod;
        IF (AX<MinPeriod) AX=MinPeriod;
        BP.MODVOICE.ArpeggioFramePeriod[0]=AX;
        // DL - xy
        DL=BP.MODVOICE.PatternElement.EffectParametr;
        // Период для первого кадра
        SI=DL>>4<<1;
        AX=DSWORD[SI+BX];
        IF (AX>MaxPeriod) AX=MaxPeriod;
        IF (AX<MinPeriod) AX=MinPeriod;
        BP.MODVOICE.ArpeggioFramePeriod[2]=AX;
        // Период для второго кадра
        SI=DL&0Fh<<1;
        AX=DSWORD[SI+BX];
        IF (AX>MaxPeriod) AX=MaxPeriod;
        IF (AX<MinPeriod) AX=MinPeriod;
        BP.MODVOICE.ArpeggioFramePeriod[4]=AX;

        // Сбрасываем индекс
        BP.MODVOICE.ArpeggioIndex=0;
    }ELSE{
        // Приращиваем индекс
        AX=BP.MODVOICE.ArpeggioIndex+1;
        // Если Index == 3
        IF (AL==3) AL=0;
        // Сохраняем текущий индекс
        BP.MODVOICE.ArpeggioIndex=AL;
        // Устанавливаем текущий период ноты
        SI=AX<<1;
        BP.MODVOICE.PatternElement.NotePeriod=BP.MODVOICE.ArpeggioFramePeriod[SI];
    }

 }
}



// Эффект Set Speed (00Fh)
// BP - адрес структуры канала
:void fastcall EffectSetSpeed()
{
 // AL - параметр эффекта
 AL=BP.MODVOICE.PatternElement.EffectParametr;
 // 0<AL<20h ?
 IF (AL<20h){
    // Скорость не равна нулю ?
//    IF (AL){
       // Устанавливаем текущую скорость
       ModModule.Speed=AL;
//    }
 }ELSE{
    // Установка количества ударов в мин.
    SetBPM(AL);
 }
}



// Эффект Position Jump (00Bh)
// BP - адрес структуры канала
:void fastcall EffectPositionJump()
{
 // Меняем паттерн
 ModModule.OrderPos=BP.MODVOICE.PatternElement.EffectParametr;
 // Нулевая строка
 ModModule.PatternLine=-1;
}



// Эффект Set Volume (00Ch)
// BP - адрес структуры канала
// AL - громкость канала
:void fastcall EffectSetVolume(AL)
{
 // Клипируем громкость по границам [0,64]
 IF (char AL<0) AL=0;
 ELSE IF (char AL>40h) AL=40h;
 // Устанавливаем громкость канала
 BP.MODVOICE.CurrentVolume=AL;
}




// Эффект Pattern Break (00Dh)
// BP - адрес структуры канала
:void fastcall EffectPatternBreak()
{
 // Это нулевой кадр ?
 IF (!ModModule.Frame){
    // Число в десятичном формате
    AL=BP.MODVOICE.PatternElement.EffectParametr;
    // Строка - XY
    IF (AL>3Fh) AL=3Fh;
    ModModule.PatternLine=AL-1;
    // Переходим на следующий паттерн
    NextPattern();
 }
}



// Эффект Vibrato (004h)
// BP - адрес структуры канала
// AL - параметр эффекта
:void fastcall EffectVibrato(AL)
{
 // Это ненулевой кадр ?
 IF (ModModule.Frame){
    // Рассчитываем смещение в массиве волновой формы
    // i=i+x%64
    BP.MODVOICE.VibratoWaveFormOffs=BP.MODVOICE.VibratoEffectParametr>>4+BP.MODVOICE.VibratoWaveFormOffs&03Fh;

    // Вычисляем период текущей ноты
    // CurrentNotePeriod=Y*2*Sin[i]+CurrentNotePeriod
    AX=int BP.MODVOICE.VibratoEffectParametr&0Fh*SinTable[BP.MODVOICE.VibratoWaveFormOffs]>>6+BP.MODVOICE.VibratoNotePeriod;
    // Клипируем период ноты
    IF (AX>MaxPeriod) AX=MaxPeriod;
    IF (AX<MinPeriod) AX=MinPeriod;
    // Сохраняем период ноты
    BP.MODVOICE.PatternElement.NotePeriod=AX;
 }ELSE{
    // Параметр эффекта нулевой ?
    IF (!AL){
       // Берем последний параметр этого эффекта, использованный на канале
       AL=BP.MODVOICE.VibratoEffectParametr;
    }
    // Сохраняем параметр эффекта
    BP.MODVOICE.VibratoEffectParametr=AL;
    // Сохраняем текущий период ноты
    BP.MODVOICE.VibratoNotePeriod=BP.MODVOICE.PatternElement.NotePeriod;
    // Устанавливаем смещение в массиве волновой формы
//    BP.MODVOICE.VibratoWaveFormOffs=0;
 }

/* // Это ненулевой кадр ?
 IF (ModModule.Frame){
    // Рассчитываем смещение в массиве волновой формы
    // i=i+x%32
    AL=BP.MODVOICE.VibratoEffectParametr>>4+BP.MODVOICE.VibratoWaveFormOffs;
    IF (AL>=32){
       BP.MODVOICE.VibratoNegative^=1;
       AL=0;
    }
    // Сохраняем положение в таблице волновой формы
    BP.MODVOICE.VibratoWaveFormOffs=AL;

    // Вычисляем период текущей ноты
    // CurrentNotePeriod=y*2*Sin[i]+CurrentNotePeriod
    AX=BP.MODVOICE.VibratoEffectParametr&0Fh*SinTable[BP.MODVOICE.VibratoWaveFormOffs]>>7;
    IF (BP.MODVOICE.VibratoNegative) -AX;
    AX+=BP.MODVOICE.PatternElement.NotePeriod;
    // Клипируем период ноты
    IF (AX>MaxPeriod) AX=MaxPeriod;
    IF (AX<MinPeriod) AX=MinPeriod;
    // Сохраняем период ноты
    BP.MODVOICE.PatternElement.NotePeriod=AX;

/*    AX=BP.MODVOICE.VibratoEffectParametr&0Fh;
    AX=int AX*SinTable[BP.MODVOICE.VibratoWaveFormOffs]<<1;
    $movsx AX,AH
    AX+=BP.MODVOICE.PatternElement.NotePeriod;
    IF (AX>MaxPeriod) AX=MaxPeriod;
    IF (AX<MinPeriod) AX=MinPeriod;
    // Сохраняем период ноты
    BP.MODVOICE.PatternElement.NotePeriod=AX;*/

 }ELSE{
    // Параметр эффекта нулевой ?
    IF (!AL){
       // Берем последний параметр этого эффекта, использованный на канале
       AL=BP.MODVOICE.VibratoEffectParametr;
    }
    // Сохраняем параметр эффекта
    BP.MODVOICE.VibratoEffectParametr=AL;
    // Сохраняем текущий период ноты
    BP.MODVOICE.VibratoNotePeriod=BP.MODVOICE.PatternElement.NotePeriod;
    // Устанавливаем смещение в массиве волновой формы
//    BP.MODVOICE.VibratoWaveFormOffs=0;
 }*/
}



// Эффект Tremolo (007h)
// BP - адрес структуры канала
:void fastcall EffectTremolo()
{
 // Это ненулевой кадр ?
 IF (ModModule.Frame){
    // Рассчитываем смещение в буфере волновой формы
    // i=i+x%64
    BP.MODVOICE.TremoloWaveFormOffs=BP.MODVOICE.PatternElement.EffectParametr>>4+BP.MODVOICE.TremoloWaveFormOffs&3Fh;
    // Рассчитываем текущую громкость на канале
    // CurrentVolume=TremoloWaveForm[i]*y*4+CurrentVolume
    EffectSetVolume(int BP.MODVOICE.PatternElement.EffectParametr&0Fh*SinTable[BP.MODVOICE.TremoloWaveFormOffs]>>5+BP.MODVOICE.TremoloVolume);
 }ELSE{
    // Сохраняем текущую громкость на канале
    BP.MODVOICE.TremoloVolume=BP.MODVOICE.CurrentVolume;
    // Устанавливаем смещение в таблице волновой формы
    BP.MODVOICE.TremoloWaveFormOffs=0;
 }
}



// Эффект Fine Portamento Up (0E1h)
// BP - адрес структуры канала
:void fastcall EffectFinePortamentoUp()
{
 // Нулевой кадр ?
 IF (!ModModule.Frame){
    // AX - Y
    AX=BP.MODVOICE.PatternElement.EffectParametr&0Fh;
    // Понижаем период ноты на Y периодов
    BX=BP.MODVOICE.PatternElement.NotePeriod-AX;
    // Клипируем период ноты
    IF (BX>MaxPeriod) BX=MaxPeriod;
    IF (BX<MinPeriod) BX=MinPeriod;
    // Сохраняем период ноты
    BP.MODVOICE.PatternElement.NotePeriod=BX;
 }
}



// Эффект Fine Portamento Down (0E2h)
// BP - адрес структуры канала
:void fastcall EffectFinePortamentoDown()
{
 // Нулевой кадр ?
 IF (!ModModule.Frame){
    // AX - y
    AX=BP.MODVOICE.PatternElement.EffectParametr&0Fh;
    // Повышаем период ноты на Y периодов
    BX=BP.MODVOICE.PatternElement.NotePeriod+AX;
    // Клипируем период ноты
    IF (BX>MaxPeriod) BX=MaxPeriod;
    IF (BX<MinPeriod) BX=MinPeriod;
    // Сохраняем период ноты
    BP.MODVOICE.PatternElement.NotePeriod=BX;
 }
}



// Эффект Fine Volume Slide Up (0EAh)
// BP - адрес структуры канала
:void fastcall EffectFineVolumeSlideUp()
{
 // Нулевой кадр ?
 IF (!ModModule.Frame){
    // Повышаем громкость канала, и устанавливаем ее текущей
    EffectSetVolume(BP.MODVOICE.PatternElement.EffectParametr&0Fh+BP.MODVOICE.CurrentVolume);
 }
}



// Эффект Fine Volume Slide Down (0EBh)
// BP - адрес структуры канала
:void fastcall EffectFineVolumeSlideDown()
{
 // Нулевой кадр ?
 IF (!ModModule.Frame){
    // Повышаем громкость канала, и устанавливаем ее текущей
    AH=BP.MODVOICE.PatternElement.EffectParametr&0Fh;
    EffectSetVolume(BP.MODVOICE.CurrentVolume-AH);
 }
}



// Инициализация информации о семпле
// Вход:  BP - смещение структуры канала
// Выход: BX - смещение структуры семпла
:void fastcall InitSampleInfo()
{
 // Сегмент семпла
 BX=BP.MODVOICE.PatternElement.SampleNumber-1<<1;
 BP.MODVOICE.SampleSeg=ModModule.SampleSeg[BX];
 // Устанавливаем смещение в семпле
 AL=BP.MODVOICE.PatternElement.EffectNumber;
 // Не используется ли эффект TonePortamento ?
 IF (AL!=TonePortamento)&&(AL!=TonePortamentoPlusVolumeSlide){
    // Смещение в семпле
    BP.MODVOICE.SampleOffs=0;
 }
 // BX - смещение структуры семпла
 // !!! (номера семплов в мод формате начинаются с 1)
 BX=BP.MODVOICE.PatternElement.SampleNumber-1*sizeof(MODSAMPLE)+#ModModule.ModSample;
 // CurrentVolume
// BP.MODVOICE.CurrentVolume=BX.MODSAMPLE.Volume;
 // Длина семпла (fixed 24:8)
 BP.MODVOICE.SampleLength=BX.MODSAMPLE.Length<<8;
 // Repeat Offset
 BP.MODVOICE.RepeatOffs=BX.MODSAMPLE.RepeatOffs;
 // Repeat Length
 BP.MODVOICE.RepeatLength=BX.MODSAMPLE.RepeatLength;
}