// Библиотека для проигрывания MOD файлов



// Подключаем инклюды
#include "modload.inc"
#include "modplay.inc"
#include "modload.h--"
#include "modmtds.h--"



// Объявление процедуры
void fastcall Frame(EAX);
void fastcall InitSampleInfo(BL);
void fastcall PlayModVoiceWorkBuf(EAX);
void fastcall ProcessNotePeriodAndSampleNumber(BX,DL);



// Инициализация мод системы
:void static MODMODULE::Init()
{
 $push ES,EBP

  // Сбрасываем все структуры
  ModModule=0;
  ModVoice=0;
  // Устанавливаем по умолчанию некоторые переменные
  ModModule.BPM=125;     // Устанавливаем количество ударов в минуту
  ModModule.SetSpeed(6); // Устанавливаем скорость воспроизведения
  ModModule.MasterVolume=0FFh;   // Устанавливаем громкость плеера
  ModModule.PatternDelayCount=1; // Устанавливаем задержку строки
  // Инициализируем мод каналы
  FOR (ECX=0,EBP=#ModVoice; ECX<MaxModVoices; ECX++,EBP+=sizeof(MODVOICE)){
      $push ECX
       // Устанавливаем громкость по дефолту
//       EBP.MODVOICE.CurrentVolume=40h;
       // Устанавливаем баланс по дефолту
       EAX=ECX&3; IF (!EAX)||(EAX==3) EAX=0; ELSE EAX=-1;
       EBP.MODVOICE.Balance=AL;
       // Выделяем память под рабочий буфер канала (SbWorkBufSize/2);
       ModModule.AllocateBuffer(SbWorkBufSize,#EBP.MODVOICE.WorkBuf);
      $pop ECX
  }

 $pop EBP,ES
}



// Деинициализация мод системы
:void static MODMODULE::DeInit()
{
 // Останавливаем воспроизведение
 ModModule.Play=FALSE;

 // Выгружаем модуль, если он подгружен
 IF (ModModule.ModuleLoaded) ModModule.UnLoadModule();

 // Распределяем выделенные рабочие буфера под каналы
 FOR (ECX=0; ECX<MaxModVoices; ECX++){
     // Распределяем выделенный рабочий буфер канала
     ModModule.FreeBuffer(#ModVoice[ECX].WorkBuf);
 }
}



// Проигрывание семпла на свободном канале //////////////////////////// 32 //
// EBX - Sample Number
// EDX - Sample Rate
// EDI - Voice Number
// CL - Balance (0-left, 255-right)
:void fastcall PlayModSample8BitVoice(EBX,EDX,EDI,CL)
{
 // EDI - смещение к каналу
 EDI=sizeof(VOICE)*EDI+#Voice;

 // Инициализируем канал
 EDI.VOICE.Sample.Sel=ModModule.Sample[EBX].Sel;
 // Вычисляем адрес структуры мод-семпла
 EBX=sizeof(MODSAMPLE)*EBX+#ModModule;

 EDI.VOICE.Sample.Offs=0;
 EDI.VOICE.Sample.Length=EBX.MODMODULE.ModSample.Length;

 // Устанавливаем параметры канала
 EDI.VOICE.Sample.Volume=40;
 EDI.VOICE.Sample.Balance=CL;
 EDI.VOICE.Sample.RepeatOffs=0;
 EDI.VOICE.Sample.RepeatLength=0;

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

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

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



// Собственно мод плеер /////////////////////////////////////////////////////
ModPlayer()
{
 $push EBP

  // Установлен флаг воспроизведения и модуль подгружен ?
  IF (ModModule.Play)&&(ModModule.ModuleLoaded){
     // Очищаем рабочие буфера каналов
     FOR (EDX=0,EBP=#ModVoice; EDX<ModModule.NumVoices; EDX++,EBP+=sizeof(MODVOICE)){
         // Обнуляем количество отмикшированных выборок
         EBP.MODVOICE.NumMixedSamples=0;
     }

     // Продолжаем микширование музона
     ContinueModMix();

     // Воспроизводим рабочие буфера каналов
     FOR (EBP=0; EBP<ModModule.NumVoices; EBP++){
         IF (ModVoice[EBP].NumMixedSamples){
            // Воспроизводим рабочий буфер канала
            PlayModVoiceWorkBuf(EBP);
         }
     }
  }


 $pop EBP
}




FillVoicesPatternLineInfo()
{
 $push ES

  // ES:EDI - адрес к текущей строке в паттерне
  EDI=ModModule.PatternLine*ModModule.NumVoices*sizeof(PATTERNELEMENT);
  EAX=ModModule.OrderPos; EAX=ModModule.Order[EAX];
  ES=ModModule.Pattern.Sel[EAX*sizeof(BUFFERDESCRIPTOR)];

  for (ECX=0,EBP=#ModVoice; ECX<ModModule.NumVoices; ECX++,EBP+=sizeof(MODVOICE),EDI+=sizeof(PATTERNELEMENT)){
      // Восстанавливаем оригинальные параметры каналов, измененные эффектами
      // Vibrato, Tremolo,Arpeggio
      SWITCH(EBP.MODVOICE.EffectNumber){
       CASE Vibrato:
        // Восстанавливаем текущий период ноты
        EBP.MODVOICE.CurrentNotePeriod=EBP.MODVOICE.VibratoNotePeriod;
        BREAK;
       CASE Arpeggio:
        // Был параметр к эффекту ?
        IF (EBP.MODVOICE.EffectParametr){
           // Восстанавливаем текущий период ноты
           EBP.MODVOICE.CurrentNotePeriod=EBP.MODVOICE.ArpeggioNotePeriod;
        }
        BREAK;
       CASE Tremolo:
        // Восстанавливаем текущую громкость канала
        EBP.MODVOICE.CurrentVolume=EBP.MODVOICE.TremoloVolume;
        BREAK;
      }

      // BX - период ноты
      BX=ES:EDI.PATTERNELEMENT.NotePeriod;
      // DL - номер семпла
      DL=ES:EDI.PATTERNELEMENT.SampleNumber;
      // Сохраняем параметр эффекта
      EBP.MODVOICE.EffectParametr=ES:EDI.PATTERNELEMENT.EffectParametr;
      // Сохраняем номер эффекта
      EBP.MODVOICE.EffectNumber=ES:EDI.PATTERNELEMENT.EffectNumber;

      SWITCH(AL){
       CASE Vibrato: // Задан эффект Vibrato ?
        // Установлен флаг сброса положения в волновой форме ?
        IF (EBP.MODVOICE.VibratoTrig){
           // Сбрасываем положение в волновой форме
           EBP.MODVOICE.VibratoWaveFormOffs=0;
        }
        BREAK;

       CASE Tremolo: // Задан эффект Tremolo ?
        // Установлен флаг сброса положения в волновой форме ?
        IF (EBP.MODVOICE.TremoloTrig){
           // Сбрасываем положение в волновой форме
           EBP.MODVOICE.TremoloWaveFormOffs=0;
        }
        BREAK;

       CASE TonePortamento:
       CASE TonePortamentoPlusVolumeSlide:
        // Период ноты указан ?
        IF (BX){
           // Сохраняем период ноты, к которой мы должны скользить
           EBP.MODVOICE.TonePortamentoNotePeriod=BX;
        }
        // Указан семпл ?
        IF (DL){
           // Сохраняем громкость семпла
           EBP.MODVOICE.CurrentVolume=ModModule.ModSample[EBP.MODVOICE.CurrentSampleNumber-1].Volume;
        }
        // Переходим на следующий канал
        CONTINUE;

       CASE RetrigNote: // Сброс ноты
        // Сбрасываем счетчик фреймов
        EBP.MODVOICE.RetrigNoteFrameCounter=0;
        BREAK;

       CASE ExtendedEffects: // Расширенные эффекты
        // Это эффект NoteDelay ?
        IF (EBP.MODVOICE.EffectParametr>>4==NoteDelay){
           // Если указан период ноты, то сохраняем ее
           IF (BX) EBP.MODVOICE.NoteDelayNotePeriod=BX;
           // Если указан номер семпла, то сохраняем его
           IF (DL) EBP.MODVOICE.NoteDelaySampleNumber=DL;
           // Переходим на следующий канал
           CONTINUE;
        }
        BREAK;
      }

      // Обрабатываем комбинации периода ноты и номера семпла
      ProcessNotePeriodAndSampleNumber(BX,DL);
  }

 $pop ES
}



// Процедура инициализации информации о семпле
// BL - производить полную инициализацию информации о семпле (TRUE/FALSE)
void fastcall InitSampleInfo(BL)
{
 // Смещение к структуре мод семпла (семплы нумеруются с 1)
 ESI=EBP.MODVOICE.CurrentSampleNumber-1*sizeof(MODSAMPLE)+#ModModule.ModSample;

 // Громкость выставлять ?
 IF (BL){
    // Сохраняем только громкость семпла
    EBP.MODVOICE.CurrentVolume=ESI.MODSAMPLE.Volume;
 }

 // Инициализируем информацию о семпле
  // Сбрасываем смещение в семпле
  EBP.MODVOICE.SampleOffs=0;
  // Сохраняем длину семпла
  EBP.MODVOICE.SampleLength=ESI.MODSAMPLE.Length<<8;
  // Сохраняем смещение зацикливания семпла
  EBP.MODVOICE.SampleLoopOffs=ESI.MODSAMPLE.LoopOffs<<8;
  // Сохраняем длину зацикливания семпла
  EBP.MODVOICE.SampleLoopLength=ESI.MODSAMPLE.LoopLength<<8;

 // Не указан ли эффект NoteDelay ?
 IF (EBP.MODVOICE.EffectNumber==ExtendedEffects)&&(EBP.MODVOICE.EffectParametr>>4==NoteDelay);
 ELSE{
    // Активизируем канал
    EBP.MODVOICE.Active=TRUE;
 }
}



// Процедура обработки фрейма канала ////////////////////////////////////////
// EAX - номер канала
void fastcall Frame(EAX)
{
 static dword LoopEndOffs;

 $push EBP

  // Рассчитываем адрес к текущему мод каналу
  EBP=EAX*sizeof(MODVOICE)+#ModVoice;

  // Канал активен и текущий семпл не нулевой ?
  if (EBP.MODVOICE.Active)&&(EBP.MODVOICE.CurrentSampleNumber)&&(EBP.MODVOICE.CurrentNotePeriod){
     $push ES,FS
      // Масштабируем семпл в рабочий буфер канала
      // Вычисляем приращение смещения в семпле
//      EAX=@FDIV_24_8(6D3D3280h,EBP.MODVOICE.CurrentNotePeriod<<9)>>8;
      EAX=@FDIV_24_8(6C3E1D33h,EBP.MODVOICE.CurrentNotePeriod<<9)>>8;
      // Сохраняем частоту воспроизведения
      EBP.MODVOICE.OldRate=EAX;
      // Рассчитываем отношение CurrentRate/SbRate
      EBX=@FDIV_24_8(EAX,SbRate);

      // FS:ESI - положение в семпле
      FS=ModModule.Sample[EBP.MODVOICE.CurrentSampleNumber-1].Sel;
      ESI=EBP.MODVOICE.SampleOffs;
      // ES:EDI - положение в рабочем буфере канала
      ES=EBP.MODVOICE.WorkBuf.Sel;
      EDI=ModModule.WorkBufOffs;
      // ECX - количество выборок
      ECX=ModModule.NumMixSamples;
      // EDX - громкость канала
      EDX=EBP.MODVOICE.CurrentVolume*2;

      $push EBP
       // Семпл зацикленный ?
       IF (EBP.MODVOICE.SampleLoopLength>2<<8){
           // Смещение конца зацикливания
           LoopEndOffs=EBP.MODVOICE.SampleLoopLength+EBP.MODVOICE.SampleLoopOffs;
           // EBP - смещение начала зацикливания
           EBP=EBP.MODVOICE.SampleLoopOffs;
           do{
              // Вышли за пределы зацикливания ?
              IF (ESI>=LoopEndOffs) ESI=EBP;
              // Помещаем выборку из семпла в рабочий буфер
              EAX=ESI>>8;
              EAX=FSCHAR[EAX];
              $imul EAX,EDX
              ESBYTE[EDI]=AH;
              // Приращиваем положение в рабочем буфере
              EDI++;
              // Приращиваем положение в буфере семпла
              ESI+=EBX;
              // Уменьшаем количество оставшихся выборок для микширования
              ECX--;
           }while (!ZEROFLAG);
       }ELSE{
          // EBP - Sample Length
          EBP=EBP.MODVOICE.SampleLength;
          do{
             // Вышли за пределы семпла ?
             IF (ESI>=EBP) BREAK;
             // Помещаем выборку из семпла в рабочий буфер
             EAX=ESI>>8;
             EAX=FSCHAR[EAX];
             $imul EAX,EDX
             ESBYTE[EDI]=AH;
             // Приращиваем положение в рабочем буфере
             EDI++;
             // Приращиваем положение в буфере семпла
             ESI+=EBX;
             // Уменьшаем количество оставшихся выборок для микширования
             ECX--;
          }while (!ZEROFLAG);
       }
      $pop EBP

      // Сохраняем положение в семпле канала
      EBP.MODVOICE.SampleOffs=ESI;
      // Приращиваем количество отмикшированных выборок
      EBP.MODVOICE.NumMixedSamples+=ModModule.NumMixSamples-ECX;
      // Семпл проигран полностью ?
      IF (ECX){
         // Деактивизируем канал
         EBP.MODVOICE.Active=FALSE;
         // Дополняем рабочий буфер тишиной
         EAX=0; $rep $stosb
      }

     $pop FS,ES
  }ELSE{
     $push ES
      // ES:EDI - положение в рабочем буфере канала
      ES=EBP.MODVOICE.WorkBuf.Sel;
      EDI=ModModule.WorkBufOffs;
      // Заполняем рабочий буфер тишиной
      ECX=ModModule.NumMixSamples;
      EAX=0;        // clear byte
      $rep $stosb
     $pop ES
  }

 $pop EBP
}



// Проигрывание семпла на свободном канале //////////////////////////// 32 //
// EAX - номер канала
:void fastcall PlayModVoiceWorkBuf(EAX)
{
 // EDI - смещение к каналу
 EDI=EAX*sizeof(VOICE)+#Voice;
 // EBX - смещение к мод каналу
 EBX=EAX*sizeof(MODVOICE)+#ModVoice;

 // Устанавливаем параметры канала
 EDI.VOICE.Sample.Balance=EBX.MODVOICE.Balance;
 // Устанавливаем общую громкость мод системы
 EDI.VOICE.Sample.Volume=ModModule.MasterVolume;
 EDI.VOICE.Sample.RepeatOffs=0;
 EDI.VOICE.Sample.RepeatLength=0;

 // Инициализируем канал
 EDI.VOICE.Sample.Sel=EBX.MODVOICE.WorkBuf.Sel;
 EDI.VOICE.Sample.Offs=0;
 EDI.VOICE.Sample.Length=SbWorkBufSize/2;

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

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

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



// Продолжение микширования
ContinueModMix()
{
   // Это начало фрейма ?
  if (!ModModule.CurrentFrameSample){
     // Это нулевой фрейм ?
     IF (!ModModule.Frame){
        // Считываем данные строки паттерна в дополнительный буфер
        FillVoicesPatternLineInfo();
        // Обрабатываем эффекты, влияющие на порядок воспроизведения
        ModModule.OrderEffects();
     }

     // Обрабатываем эффекты, влияющие на воспроизведение семплов
     ModModule.SampleEffects();
  }

  // Оставшееся количество выборок в фрейме
  EAX=ModModule.SamplesPerFrame-ModModule.CurrentFrameSample;
  // Оставшееся количество выборок до заполнения рабочего буфера
  EBX=SbWorkBufSize>>1-ModModule.WorkBufOffs;
  // Выбираем наименьшее
  IF (EAX>EBX) EAX=EBX;
  // Сохраняем количество выборок для микширования
  ModModule.NumMixSamples=EAX;

  // Заполняем рабочие буфера каналов промасштабированными семплами
  FOR (ECX=0; ECX<ModModule.NumVoices; ECX++){
      $push ECX
       // Обрабатываем текущий фрейм канала
       Frame(ECX);
      $pop ECX
  }

  // Приращиваем количество отмикшированных выборок фрейма
  ModModule.CurrentFrameSample+=ModModule.NumMixSamples;
  // Фрейм отмикширован полностью ?
  IF (ModModule.CurrentFrameSample+1>=ModModule.SamplesPerFrame){
     // Сбрасываем номер текущего фрейма
     ModModule.CurrentFrameSample=0;
     // Приращиваем текущий фрейм
     ModModule.Frame++;
     // Это последний фрейм в строке ?
     IF (ModModule.Speed*ModModule.PatternDelayCount==ModModule.Frame){
        // Если множитель > 1, то сбрасываем его в 1 ?
        IF (ModModule.PatternDelayCount>1) ModModule.PatternDelayCount=1;
        // Переходим на другую строку в паттерне
        ModModule.NextLine();
        // Сбрасываем фрейм в 0
        ModModule.Frame=0;
     }
  }

  // Приращиваем положение в рабочем буфере
  ModModule.WorkBufOffs+=ModModule.NumMixSamples;

  // Буфер заполнен до отказа ?
  EBX=SbWorkBufSize/2;
  IF (ModModule.WorkBufOffs+1<EBX){
     // Вызываем себя еще разок (надо буфер заполнить до отказа)
     ContinueModMix();
  }
}



// Процедура обработки комбинаций номера семпла и периода ноты
// BX - период ноты
// DL - номер семпла
:void fastcall ProcessNotePeriodAndSampleNumber(BX,DL)
{
 // Период ноты не 0 ?
 IF (BX){
    // Текущий период ноты -> старый период ноты
    EBP.MODVOICE.OldNotePeriod=EBP.MODVOICE.CurrentNotePeriod;
 }
 // Номер семпла не 0 ?
 IF (DL){
    // Текущий номер семпла -> старый номер семпла
    EBP.MODVOICE.OldSampleNumber=EBP.MODVOICE.CurrentSampleNumber;
 }

 // Если одновременно задан период ноты и номер семпла
 IF (BX)&&(DL){
    // Сохраняем текущий период ноты
    EBP.MODVOICE.CurrentNotePeriod=BX;
    // Сохраняем текущий номер семпла
    EBP.MODVOICE.CurrentSampleNumber=DL;
    // Инициализируем информацию о семпле
    InitSampleInfo(TRUE);
 }
 // Если задан период ноты без номера семпла, то сохраняем его
 ELSE IF (BX){
    // Сохраняем период ноты
    EBP.MODVOICE.CurrentNotePeriod=BX;
    // Семпл задан на канале ?
    IF (EBP.MODVOICE.CurrentSampleNumber){
       // Инициализируем информацию о семпле
       InitSampleInfo(FALSE);
    }
 }
 // Задан только номер семпла ?
 ELSE IF (DL){
    // Нота задана на канале ?
    IF (EBP.MODVOICE.CurrentNotePeriod){
       // Инициализируем громкость на канале
       EBP.MODVOICE.CurrentVolume=ModModule.ModSample[EBP.MODVOICE.CurrentSampleNumber-1].Volume;
    }ELSE{
       // Сохраняем номер семпла
       EBP.MODVOICE.CurrentSampleNumber=DL;
       // Инициализируем информацию о семпле
       InitSampleInfo(FALSE);
    }
 }

}