Windows ModPlayer

Source Code Snippets

Windows ModPlayer

Postby ravenger7 » Tue Feb 23, 2010 9:50 pm

This code might need some massaging to work like replacing the diagnostics methods.

Code: Select all
// ModPlayer.h: interface for the CModPlayer class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(_MODPLAYER_H_)
#define _MODPLAYER_H_

#include <QString>
#include <QStringList>

// The following lines are required in order to use the multi-media functions
#include <mmsystem.h>

enum Effect
{
   EFFECT_ARPEGGIO            = 0x00,
   EFFECT_PORTA_UP            = 0x01,
   EFFECT_PORTA_DOWN         = 0x02,
   EFFECT_PORTA_TO_NOTE      = 0x03,
   EFFECT_VIBRATO            = 0x04,
   EFFECT_PORTA_AND_VOL      = 0x05,
   EFFECT_VIBRATO_AND_VOL      = 0x06,
   EFFECT_TREMOLO            = 0x07,
   EFFECT_PAN               = 0x08,
   EFFECT_SAMPLE_OFFSET      = 0x09,
   EFFECT_VOLUME_SLIDE         = 0x0A,
   EFFECT_JUMP_TO_PATTERN      = 0x0B,
   EFFECT_SET_VOLUME         = 0x0C,
   EFFECT_PATTERN_BREAK      = 0x0D,
   EFFECT_EXTENDED            = 0x0E,
   EFFECT_SET_SPEED         = 0x0F,
   EFFECT_SET_GLOBAL_VOLUME   = 0x10,
   EFFECT_PANNING_SLIDE      = 0x1B
};

enum ExtenedEffects
{
   EXTENDED_SET_FILTER       = 0x00,
   EXTENDED_FINE_PORTA_UP      = 0x01,
   EXTENDED_FINE_PORTA_DOWN   = 0x02,
   EXTENDED_GLISSANDO         = 0x03,
   EXTENDED_VIBRATO_WAVE      = 0x04,
   EXTENDED_FINE_TUNE         = 0x05,
   EXTENDED_PATTERN_LOOP      = 0x06,
   EXTENDED_TREMOLO_WAVE      = 0x07,
   EXTENDED_PAN            = 0x08,
   EXTENDED_RETRIG_NOTE      = 0x09,
   EXTENDED_FINE_VOLUME_UP      = 0x0A,
   EXTENDED_FINE_VOLUME_DOWN   = 0x0B,
   EXTENDED_CUT_NOTE         = 0x0C,
   EXTENDED_DELAY_NOTE         = 0x0D,
   EXTENDED_PATTERN_DELAY      = 0x0E,
   EXTENDED_INVERT_LOOP      = 0x0F
};


class CModPlayer;


#define NUM_PERIODS   296
#define NUM_NOTES   96

// Patterns[Num_Patterns].Rows[m_Num_Rows].Notes[Num_Tracks]
typedef struct
{
   BYTE   Sample_Num;         // The sample number (ie "instrument") to play, 1->31
   BYTE   Effect;             // Contains the effect code
   BYTE   Effect_Parms;       // Used to store control parameters for the various effects
   BYTE   Note;
   BYTE   Volume;
} NoteData;

typedef struct
{
   NoteData * Note;
} RowData;

typedef struct
{
   RowData   Row[256];
   int      Num_Rows;
} PatternData;


// The Sample structure is used to store all the information for a single sample, or "instrument". Most of the
// member's should be self-explanatory. The "data" member is an array of bytes containing the raw sample data
// in 8-bit signed format.
typedef struct
{
   char   Name[28];
   int      Length;
   BYTE   Volume;
   int      Loop_Start;
   int      Loop_End;
   BYTE *   Data;
   int      Frequency;      // C-5 Frequency
   int      XM_Note;
   int      XM_Fine_Tune;
} SampleData;


// This #define is used to convert an Amiga period number to a frequency. The freqency returned
// is the frequency that the sample should be played at.
#define Period2Freq(period)            (3579545.0f/(period)) 
#define Freq2Period(note,sample_freq)   (Period_Freq[note] / (sample_freq))

#define LinearPeriod2Freq(period)      (8363 * pow(2,(double)(6*12*16*4 - period) / (double)(12*4*16)))
#define LinearFreq2Period(note,sample)   (10*12*4*16 - (note + m_Samples[sample].XM_Note)*4*16 - m_Samples[sample].XM_Fine_Tune/2)

extern float Period_Freq[96];

extern WORD FineTuneFrequency[16];


// TrackData is used to store ongoing information about a particular track.
typedef struct
{
   BYTE   Sample;         // The current sample being played (0 for none)
   int      Pos;         // The current playback position in the sample, stored in fixed-point format
   short   Period;         // The period number that period_index corresponds to (needed for various effects)
   BYTE   Note;         // Note to play 8 octaves, 12 notes per octave = 96 total notes
   BYTE   Next_Note;      // Note to Porta or Delay to
   float   Freq;         // This is the actual frequency used to do the mixing. It's a combination of the value calculated from the period member and an "adjustment" made by various effects.
   BYTE   Volume;         // Volume that this track is to be mixed at
   BYTE   Mix_Volume;      // This is the actual volume used to do the mixing. It's a combination of the volume member and an "adjustment" made by various effects.
   short   Porta;         // Used by the porta effect, this stores the note we're porta-ing (?) to
   short   Porta_Speed;   // The speed at which to porta
   short   Vib_Speed;      // Vibrato speed
   short   Vib_Depth;      // Vibrato depth
   short   Trem_Speed;      // Tremolo speed
   short   Trem_Depth;      // Tremolo depth
   BYTE   Retrig_Tick;   // Tick to retrig on
   BYTE   Delay_Tick;      // Tick to delay to
   BYTE   Pan;         // Pan value 0x00 Left, 0x40 Center, 0x80 Right
   int      Sine_Pos;      // These next two values are pointers to the sine table. They're used to do
   int      Sine_Neg;      //  various effects.
} TrackData;

// The following structure is used to store all the information for a particular block of sound.
// This is needed so that the callback function can clean it up once it's played.
typedef struct SoundBlockType
{
   CModPlayer *      Tracker;      // Pointer to the parent player object
   LPBYTE            Sound_Data;      // Points to the actual sound data
   LPWAVEHDR         Wave_Header;
   SoundBlockType *   Next_Block;
} SoundBlock;

class CModPlayer
{
public:

   CModPlayer(QString filename,int frequency,BOOL stereo,BOOL surround,BOOL oversample,BOOL looping);
   virtual ~CModPlayer();

   void SetMasterVolume(int volume);
   int GetMasterVolume()      { return m_Master_Volume; }

   BOOL Play(int order = 0,int row = 0);
   BOOL Stop();
   BOOL Pause();

   BOOL IsPaused()            { return m_Paused; }
   BOOL IsPlaying()         { return m_Playing; }
   BOOL IsFinished()         { return m_Order >= m_Song_Length && m_Finished && m_Blocks == NULL; }   
   
   int   GetOrder()            { return m_Order; }
   int GetRow()            { return m_Row; }
   int GetLength()            { return m_Song_Length; }
   const char * GetSongName()   { return m_Song_Name; }
   void GetSampleNames(QStringList & list);
   int GetNumPatterns()      { return m_Num_Patterns; }
   int GetNumTracks()         { return m_Num_Tracks; }

private:

   int ConvertMODWord(WORD data);
   int ConvertWord(BYTE * data,int & index);

   // Loading functions defined in Loader.cpp
   BOOL LoadMOD(BYTE * data,int length);
   BOOL LoadS3M(BYTE * data,int length);
   BOOL LoadSTM(BYTE * data,int length);
   BOOL Load669(BYTE * data,int length);
   BOOL LoadWOW(BYTE * data,int length);
   BOOL LoadMTM(BYTE * data,int length);
   BOOL LoadXM (BYTE * data,int length);

   // Do special effects
   void DoTremalo(int track);
   void DoVibrato(int track);
   void DoPorta(int track);
   void DoSlideVolume(int track, int amount);

   void UpdateEffects();   
   void UpdateRow();         

   BOOL MixChunk();
   BOOL MixSubChunk(int * left,int * right,int numsamples);
   int  MixTrack(int track,int * buffer,int volume,int numsamples);
   

   void AddBlock(SoundBlock * block);
   void RemoveBlock();

   static DWORD WINAPI ThreadProc(LPVOID param);
   static void CALLBACK waveOutProc(HWAVEOUT hwo,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2);

   short            m_Volume_Table[65][256];

   char            m_Song_Name[28];
   BYTE            m_Song_Length;
   BYTE             m_Orders[128];
   PatternData *      m_Patterns;
   BYTE            m_Num_Patterns;
   SampleData *      m_Samples;
   BYTE            m_Num_Samples;
   BYTE            m_Default_Pan[32];
   
   TrackData *         m_Track_Data;   // Stores info for each track being played
   int               m_Speed;      // Speed of mod being played
   BYTE            m_Order;      // Current order being played
   BYTE            m_Row;         // Current row being played
   int               m_Tick;         // Current tick number (there are "m_Speed" ticks between each row)
   int               m_BPM;         // Beats-per-minute...controls length of each tick

   HWAVEOUT         m_Wave_Handle;   // Handle to the wave device
   int               m_Samples_Left;   // Number of samples left to mix for the current tick
   BYTE            m_Num_Tracks;   // Number of tracks in this mod
   RowData *         m_Current_Row;   // Pointer to the current row being played
   BOOL            m_Playing;      // Set to true when a mod is being played
   int   *            m_Left_Buffer;   // Used to delay the left  tracks when they're played to the right channel
   int   *            m_Right_Buffer;   // Used to delay the right tracks when they're played to the left  channel
   int               m_Frequency;   // Sound playback frequency
   BOOL            m_Over_Sample;
   BOOL            m_Surround;
   BOOL            m_Stereo;
   BYTE            m_Num_Channels;   // 2 for stereo, 1 for mono
   HANDLE            m_Mixing_Thread;
   HANDLE            m_Mix_Event;
   BOOL            m_Paused;
   BOOL            m_Looping;
   BOOL            m_Finished;
   SoundBlock *      m_Blocks;      // Blocks to free on next thread loop
   int               m_Surround_Delay;
   CRITICAL_SECTION   m_Critical_Section;
   int               m_Sample_Rate;   // Rate samples are added to sound buffer (Frequency / 5 or something)
   BOOL            m_Loaded;      // Successfully loaded the file
   LONG            m_Blocks_To_Mix;
   int               m_Master_Volume;
   BOOL            m_Linear_Frequency;
};

#endif

ravenger7
Site Admin
 
Posts: 31
Joined: Sun Nov 16, 2008 1:18 pm
Location: Illinois

Postby ravenger7 » Tue Feb 23, 2010 9:51 pm

Code: Select all
//////////////////////////////////////////////////////////////////////////////////////////////////
//
//   Robert Morgan "Sourcerer"
//      March 15, 1999
//
//////////////////////////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#include "ModPlayer.h"
#include <math.h>
#include "Diagnostics.h"

// As a sample is being mixed into the buffer it's position pointer is updated. The position pointer
// is a 32-bit integer that is used to store a fixed-point number. The following constant specifies
// how many of the bits should be used for the fractional part.
#define FRAC_BITS 10

// BITS_PER_SAMPLE should be declared as either 8 or 16.
#define BITS_PER_SAMPLE 16

// At this point the sound buffers are all 16-bit signed samples. If we are playing 8-bit signed samples
// then we need to convert them by shifting right by 8 (SAMPLE_SHIFT) and adding 128 (BIAS). We also need to
// clip each sample to it's appropriate range. The following defines ensure that the mixing routines take
// care of all this for us regardless of the current playback mode.
#if BITS_PER_SAMPLE == 8
   #define SAMPLE_SHIFT 8
   #define BIAS         128
   #define LOW_CLIP     0
   #define HI_CLIP      255
#else
   #define SAMPLE_SHIFT 0
   #define BIAS         0
   #define LOW_CLIP     -0x8000
   #define HI_CLIP      0x7FFF
#endif   

// This table is for vibrato and contains half a sine wave.
BYTE Sine_Table[32] =
{
     0, 24, 49, 74, 97,120,141,161,180,197,212,224,235,244,250,253,
   255,253,250,244,235,224,212,197,180,161,141,120, 97, 74, 49, 24
};

// Periods used to compute the Period_Freq table
short Period_Table[12] =
{
   1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907
};

float Period_Freq[96];

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

CModPlayer::CModPlayer(QString file_name,int frequency,BOOL stereo,BOOL surround,BOOL oversample,BOOL looping)
{
   BYTE *      data;
   DWORD      bytes_read;
   HANDLE      file_handle;
   DWORD      file_size;
   DWORD      file_size_high;
   int         octave_factor = 1;
   int         index = 0;

   // Generate Period Frequency Table
   for ( int octave = 0 ; octave < 8 ; octave++ )
   {
      for ( int note = 0 ; note < 12 ; note++,index++ )
         Period_Freq[index] = (float)(8363 * 4 * Period_Table[note]) / (float)octave_factor;
      octave_factor *= 2;
   }

   m_Wave_Handle      = NULL;
   m_Frequency         = frequency;
   m_Surround_Delay   = m_Frequency / 44;
   m_Over_Sample      = oversample;
   m_Surround         = surround;
   m_Stereo         = stereo;
   m_Num_Channels      = m_Stereo ? 2 : 1;
   m_Mix_Event         = CreateEvent(NULL,FALSE,FALSE,"MixEvent");
   m_Looping         = looping;
   m_Track_Data      = NULL;
   m_Patterns         = NULL;
   m_Samples         = NULL;
   m_Left_Buffer      = NULL;
   m_Right_Buffer      = NULL;
   m_Playing         = FALSE;
   m_Blocks         = NULL;
   m_Paused         = FALSE;
   m_Loaded         = FALSE;
   m_Sample_Rate      = m_Frequency / 15;
   m_Linear_Frequency   = FALSE;

   InitializeCriticalSection(&m_Critical_Section);

   if ( m_Surround )
   {
      m_Left_Buffer   = new int [m_Surround_Delay];
      m_Right_Buffer   = new int [m_Surround_Delay];
   }

   // Open the file
   file_handle = (HANDLE)CreateFile(file_name.toAscii(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
   if ( file_handle != NULL )
   {
      file_size = GetFileSize(file_handle,&file_size_high);
      if ( file_size != 0xFFFFFFFF )
      {
         // Load the entire mod file into memory first
         data = new BYTE [file_size];
         ReadFile(file_handle,data,file_size,&bytes_read,NULL);
         CloseHandle(file_handle);

         file_name.toUpper();

         if ( file_name.indexOf(".MOD") != -1 )
            m_Loaded = LoadMOD(data,bytes_read);
         else if ( file_name.indexOf(".669") != -1 )
            m_Loaded = Load669(data,bytes_read);
         else if ( file_name.indexOf(".WOW") != -1 )
            m_Loaded = LoadWOW(data,bytes_read);
         else if ( file_name.indexOf(".STM") != -1 )
            m_Loaded = LoadSTM(data,bytes_read);
         else if ( file_name.indexOf(".MTM") != -1 )
            m_Loaded = LoadMTM(data,bytes_read);
         else if ( file_name.indexOf(".XM") != -1 )
            m_Loaded = LoadXM(data,bytes_read);
         else if ( file_name.indexOf(".S3M") != -1 )
            m_Loaded = LoadS3M(data,bytes_read);
      }

      SetMasterVolume(64);
   }
   
   delete [] data;
}


CModPlayer::~CModPlayer()
{
   // If I'm still playing then stop myself
   if ( m_Playing )
      Stop();

   // close the Mixing Event and Critical Section
   CloseHandle(m_Mix_Event);
   DeleteCriticalSection(&m_Critical_Section);

   // Free all the song data buffers
   delete [] m_Track_Data;

   if ( m_Patterns != NULL )
   {
      for ( int pattern = 0 ; pattern < m_Num_Patterns ; pattern++ )
         for ( int row = 0 ; row < m_Patterns[pattern].Num_Rows ; row++ )
            delete [] (m_Patterns[pattern].Row[row].Note);
      delete [] m_Patterns;
   }
   if ( m_Samples != NULL )
   {
      for ( int i = 0 ; i <= m_Num_Samples ; i++ )
         delete [] m_Samples[i].Data;
      delete [] m_Samples;
   }

   delete [] m_Left_Buffer;
   delete [] m_Right_Buffer;
}


BOOL CModPlayer::Play(int order,int row)
{
   // See if I actually have a song to play
   if ( m_Loaded == FALSE )
      return FALSE;

   // See if I'm paused
   if ( m_Paused && m_Wave_Handle != NULL )
   {
      Pause();      // unpause and then play
      return TRUE;
   }

   // See if I'm already playing
   if ( m_Playing )
      return TRUE;

   // If our device is already open then close it
   if ( m_Wave_Handle )
      waveOutClose(m_Wave_Handle);

   // Set up the kind of waveform data we want
   WAVEFORMATEX format;
   format.wFormatTag      = WAVE_FORMAT_PCM;
   format.nChannels      = m_Num_Channels;
   format.nSamplesPerSec   = m_Frequency;
   format.wBitsPerSample   = BITS_PER_SAMPLE;
   format.nBlockAlign      = format.nChannels * format.wBitsPerSample / 8;
   format.nAvgBytesPerSec   = format.nSamplesPerSec * format.nBlockAlign;
   format.cbSize         = 0;

   WAVEOUTCAPS caps;
   MMRESULT result = waveOutGetDevCaps(WAVE_MAPPER,&caps,sizeof(caps));

   // Open the playback device
   result = waveOutOpen(&m_Wave_Handle,WAVE_MAPPER,&format,(LONG)waveOutProc,(DWORD)this,CALLBACK_FUNCTION);

   memset(m_Track_Data,0,sizeof(TrackData) * m_Num_Tracks);

   for ( int track = 0 ; track < m_Num_Tracks ; track++ )
      m_Track_Data[track].Pan = m_Default_Pan[track];

   // Get ready to play
   m_Current_Row   = &m_Patterns[m_Orders[m_Order]].Row[m_Row];
   m_Samples_Left   = 0;
   m_Order         = order;
   m_Row         = row;
   m_Tick         = 0;
   m_Playing      = TRUE;
   m_Paused      = FALSE;
   m_Blocks_To_Mix   = 1;
   m_Finished      = FALSE;

   // If we're playing in surround then clear the delay buffers
   if ( m_Surround )
   {
      memset(m_Left_Buffer,0,sizeof(int) * m_Surround_Delay);
      memset(m_Right_Buffer,0,sizeof(int) * m_Surround_Delay);
   }

   waveOutPause(m_Wave_Handle);

   // Jump start the buffer
   if ( MixChunk() && MixChunk() && MixChunk() )
   {
      ULONG thread_id;

      m_Playing = TRUE;
      m_Mixing_Thread = CreateThread(NULL,0,ThreadProc,this,0,&thread_id);
      SetThreadPriority(m_Mixing_Thread,THREAD_PRIORITY_HIGHEST);

      waveOutRestart(m_Wave_Handle);

      return TRUE;
   }
   else
      m_Playing = FALSE;

   return FALSE;
}


BOOL CModPlayer::Pause()
{
   if ( m_Wave_Handle == NULL )
      return FALSE;

   if ( m_Paused == FALSE )
   {
      m_Paused = TRUE;
      waveOutPause(m_Wave_Handle);
   }
   else
   {
      m_Paused = FALSE;
      waveOutRestart(m_Wave_Handle);
   }

   return TRUE;
}


BOOL CModPlayer::Stop()
{
   if ( m_Playing == FALSE )
      return TRUE;

   // Close it
   m_Playing = FALSE;
   m_Paused = TRUE;
   
   if ( m_Wave_Handle )
   {
      waveOutReset(m_Wave_Handle);
      waveOutClose(m_Wave_Handle);
      m_Wave_Handle = NULL;
   }

   RemoveBlock();
   SetEvent(m_Mix_Event);
   Sleep(200);
   TerminateThread(m_Mixing_Thread,0);

   return TRUE;
}


// This function mixes an entire chunk of sound which is then sent to the waveout driver device.
BOOL CModPlayer::MixChunk()
{
   if ( m_Playing == FALSE )
      return FALSE;

   int   i;
   int   j;
   int   k;
   int   l;
   int   thissample;
   int   num_samples         = m_Sample_Rate;   
   int   tickdata         = 0;
   int   samples_to_mix      = num_samples;   
   
   // left and right buffers that will be mixed for stereo
   int * left   = new int [num_samples];
   int * right   = new int [num_samples];

   assertf(left != NULL);
   assertf(right != NULL);

   // Create a soundblock structure to hold all the data for this chunk
   SoundBlock *   block   = new SoundBlock;   
   LPWAVEHDR      wave_header;

   assertf(block != NULL);

   // Allocate memory for both the header and the actual sound data itself
   block->Tracker = this;
   block->Sound_Data = new BYTE [sizeof(WAVEHDR) + num_samples * (BITS_PER_SAMPLE / 8) * m_Num_Channels];

   assertf(block->Sound_Data != NULL);

   wave_header   = (LPWAVEHDR)block->Sound_Data;

#if BITS_PER_SAMPLE == 8
   LPBYTE chunk_data = (LPBYTE)&block->Sound_Data[sizeof(WAVEHDR)];
#else
   short * chunk_data = (short *)&block->Sound_Data[sizeof(WAVEHDR)];
#endif

   // Keep looping until we've filled the buffer
   while ( samples_to_mix )
   {
      // Only move on to the next tick if we finished mixing the last
      if ( m_Samples_Left <= 0 )
      {
         // If we're on tick 0 then update the row
         if ( m_Tick == 0 )
         {
            while ( m_Orders[m_Order] == 255 && m_Order < m_Song_Length )
               m_Order++;
            if ( m_Order >= m_Song_Length )
            {
               if ( m_Looping )
                  m_Order = 0;
               else
               {
                  num_samples -= samples_to_mix;
                  m_Finished = TRUE;
                  goto skip;
               }
            }

            // Get this row
            m_Current_Row = &m_Patterns[m_Orders[m_Order]].Row[m_Row];

            // Set up for next row (effect might change these values later)
            m_Row++;
            if ( m_Row >= m_Patterns[m_Orders[m_Order]].Num_Rows )
            {
               m_Row = 0;
               m_Order++;
               if ( m_Order >= m_Song_Length )   // loop to beginning
               {
                  if ( m_Looping )
                     m_Order = 0;
                  else
                  {
                     num_samples -= samples_to_mix;
                     m_Finished = TRUE;
                     goto skip;
                  }
               }
            }

            // Now update this row
            UpdateRow();
         }
         else      // Otherwise, all we gotta do is update the effects
            UpdateEffects();

         // Move on to next tick
         m_Tick++;
         if ( m_Tick >= m_Speed )
            m_Tick = 0;

         // Set the number of samples to mix in this chunk
         m_Samples_Left = m_Frequency / ((m_BPM * 2) / 5);
      }

      // Ok, so we know that we gotta mix 'm_Samples_Left' samples into this buffer, see how much room we actually got
      int thiscount = m_Samples_Left;
      if ( thiscount > samples_to_mix )
         thiscount = samples_to_mix;

      // Make a note that we've added this amount
      m_Samples_Left -= thiscount;
      samples_to_mix -= thiscount;

      // Now mix it!
      MixSubChunk(&left[tickdata],&right[tickdata],thiscount);

      tickdata += thiscount;
   }

skip:
   if ( m_Stereo )
   {
      for ( i = 0 , j = 0 ; i < num_samples ; i++ , j += 2 )
      {
         thissample = ((left[i]) >> SAMPLE_SHIFT) + BIAS;
         chunk_data[j] = thissample < LOW_CLIP ? LOW_CLIP : thissample > HI_CLIP ? HI_CLIP : thissample;
         thissample = ((right[i]) >> SAMPLE_SHIFT) + BIAS;
         chunk_data[j+1] = thissample < LOW_CLIP ? LOW_CLIP : thissample > HI_CLIP ? HI_CLIP : thissample;
      }

      if ( m_Surround )
      {
         for ( i = 0 , j = 0 ; i < m_Surround_Delay ; i++ , j += 2 )
         {
            thissample = ((left[i] + m_Right_Buffer[i]) >> SAMPLE_SHIFT) + BIAS;
            chunk_data[j] = thissample < LOW_CLIP ? LOW_CLIP : thissample > HI_CLIP ? HI_CLIP : thissample;
            thissample = ((right[i] + m_Left_Buffer[i]) >> SAMPLE_SHIFT) + BIAS;
            chunk_data[j + 1] = thissample < LOW_CLIP ? LOW_CLIP : thissample > HI_CLIP ? HI_CLIP : thissample;
         }

         for ( k = 0 ; i < num_samples - m_Surround_Delay ; i++ , j += 2 , k++ )
         {
            thissample = ((left[i] + right[k]) >> SAMPLE_SHIFT) + BIAS;
            chunk_data[j] = thissample < LOW_CLIP ? LOW_CLIP : thissample > HI_CLIP ? HI_CLIP : thissample;
            thissample = ((right[i] + left[k]) >> SAMPLE_SHIFT) + BIAS;
            chunk_data[j + 1] = thissample < LOW_CLIP ? LOW_CLIP : thissample > HI_CLIP ? HI_CLIP : thissample;
         }

         for ( l = 0 ; i < num_samples ; i++ , j += 2 , k++ , l++ )
         {
            thissample = ((left[i] + right[k]) >> SAMPLE_SHIFT) + BIAS;
            chunk_data[j] = thissample < LOW_CLIP ? LOW_CLIP : thissample > HI_CLIP ? HI_CLIP : thissample;
            m_Right_Buffer[l] = right[i];
            thissample = ((right[i] + left[k]) >> SAMPLE_SHIFT) + BIAS;
            chunk_data[j + 1] = thissample < LOW_CLIP ? LOW_CLIP : thissample > HI_CLIP ? HI_CLIP : thissample;
            m_Left_Buffer[l] = left[i];
         }
      }
   }
   else
   {
      for ( i = 0 ; i < num_samples ; i++ )
      {
         thissample      = ((left[i] + right[i]) >> SAMPLE_SHIFT) + BIAS;
         chunk_data[i]   = thissample < LOW_CLIP ? LOW_CLIP : thissample > HI_CLIP ? HI_CLIP : thissample;
      }
   }

   delete [] left;
   delete [] right;

   if ( num_samples > 0 )
   {
      // Set up and prepare header.
      wave_header->lpData = (char *)chunk_data;
      wave_header->dwBufferLength = num_samples * m_Num_Channels * (BITS_PER_SAMPLE / 8);
      wave_header->dwFlags = 0L;
      wave_header->dwLoops = 0L;
      wave_header->dwUser = (DWORD)block;
      waveOutPrepareHeader(m_Wave_Handle,wave_header,sizeof(WAVEHDR));

      // Now the data block can be sent to the output device. The
      // waveOutWrite function returns immediately and waveform
      // data is sent to the output device in the background.
      MMRESULT result = waveOutWrite(m_Wave_Handle,wave_header,sizeof(WAVEHDR));
      if ( result != 0 )
      {
         waveOutUnprepareHeader(m_Wave_Handle,wave_header,sizeof(WAVEHDR));
         delete [] block->Sound_Data;
         errorf("Failed to write block to device");
         return FALSE;
      }
   }
   
   return TRUE;
}


// This function is called whenever a new row is encountered. It loops through each track,
// checks its appropriate NoteData structure and updates the track accordingly.
void CModPlayer::UpdateRow()
{
   int new_order   = m_Order;
   int new_row      = m_Row;

   // Loop through each track
   for ( int track = 0 ; track < m_Num_Tracks ; track++ )
   {
      // Get note data
      NoteData * note = &m_Current_Row->Note[track];

      // Make a copy of each value in the NoteData structure so they'll be easier to work with (less typing)
      int sample = note->Sample_Num;
      int effect = note->Effect;
      int eparm  = note->Effect_Parms;
      int volume = note->Volume;
      int eparmx = eparm >> 4;                  // effect parameter x
      int eparmy = eparm & 0xF;                 // effect parameter y

//      TRACE3("%02i %02i %02i ",sample,note->Note,volume);
//      TRACE2("0x%02x 0x%02x   ",effect,eparm);

      if ( volume > 0 )
      {
         if ( volume < 0x60 )
         {
            m_Track_Data[track].Volume = volume;
            DoSlideVolume(track,0);
         }
         else if ( volume < 0x70 )
         {
            DoSlideVolume(track,-(volume - 0x60));
         }
         else if ( volume < 0x80 )
         {
            DoSlideVolume(track,volume - 0x70);
         }
         else if ( volume >= 0xC0 && volume < 0xD0 )
         {
            m_Track_Data[track].Pan = (volume - 0xC0) * 16;
         }

         m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume;
      }

      // Are we changing the sample being played?
      if ( sample != 0 )
      {
         m_Track_Data[track].Sample = sample;

         if ( volume > 64 )
         {
            m_Track_Data[track].Volume = m_Samples[m_Track_Data[track].Sample].Volume;
            m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume;
         }

         if ( effect != EFFECT_PORTA_TO_NOTE && effect != EFFECT_PORTA_AND_VOL )
            m_Track_Data[track].Pos = 0;
      }

      // Are we changing the frequency being played?
      if ( note->Note > 0 )
      {
         if ( note->Note >= 96 )
         {
            // stop this track from playing
            m_Track_Data[track].Sample = 0;
            continue;
         }

         // Remember the note
         m_Track_Data[track].Next_Note = note->Note;
      
         // If not a porta effect, then set the channels frequency to the
         // looked up amiga value + or - any finetune
         if ( effect != EFFECT_PORTA_TO_NOTE && effect != EFFECT_PORTA_AND_VOL && effect != 0xED)
         {
            m_Track_Data[track].Note = note->Note;

            if ( m_Linear_Frequency )
               m_Track_Data[track].Period = (int)LinearFreq2Period(note->Note,m_Track_Data[track].Sample);
            else
               m_Track_Data[track].Period = (int)Freq2Period(note->Note,m_Samples[m_Track_Data[track].Sample].Frequency);
         }

         // If there is no sample number or effect then we reset the position
         if ( sample == 0 && effect == 0 )
            m_Track_Data[track].Pos = 0;

         // Now reset a few things
         m_Track_Data[track].Vib_Speed   = 0;
         m_Track_Data[track].Vib_Depth   = 0;
         m_Track_Data[track].Trem_Speed   = 0;
         m_Track_Data[track].Trem_Depth   = 0;
         m_Track_Data[track].Sine_Pos   = 0;
         m_Track_Data[track].Sine_Neg   = 0;
         m_Track_Data[track].Retrig_Tick   = 0;
      }

      // Process any effects - need to include 1, 2, 3, 4 and A
      switch ( effect )
      {
         case EFFECT_ARPEGGIO:   
            break; // tick effect

         case EFFECT_PORTA_UP:
            break; // tick effect

         case EFFECT_PORTA_DOWN:   
            break; // tick effect

         case EFFECT_PORTA_TO_NOTE:
         case EFFECT_PORTA_AND_VOL:
            if ( m_Linear_Frequency )
               m_Track_Data[track].Porta = (int)LinearFreq2Period(m_Track_Data[track].Next_Note,m_Track_Data[track].Sample);
            else
               m_Track_Data[track].Porta = (int)Freq2Period(m_Track_Data[track].Next_Note,m_Samples[m_Track_Data[track].Sample].Frequency);

            if ( eparm > 0 && effect == EFFECT_PORTA_TO_NOTE )
            {
               m_Track_Data[track].Porta_Speed = eparm;
            //   TRACE2("freq = %f porta = %i ",m_Track_Data[track].Freq,m_Track_Data[track].Porta);
            //   infof("eparm = %i\r\n",eparm);
            }

            break;

         case EFFECT_VIBRATO:
            if ( eparmx > 0 )
               m_Track_Data[track].Vib_Speed = eparmx;
            if ( eparmy > 0 )
               m_Track_Data[track].Vib_Depth = eparmy;
            break;

         case EFFECT_VIBRATO_AND_VOL:   
            break;  // tick effect

         case EFFECT_TREMOLO:
            if ( eparmx > 0 )
               m_Track_Data[track].Trem_Speed = eparmx;
            if ( eparmy > 0 )
               m_Track_Data[track].Trem_Depth = eparmy;
            break;

         case EFFECT_PAN:
            if ( eparm == 0xA4 ) // surround
               m_Track_Data[track].Pan = 128;
            else
               m_Track_Data[track].Pan = eparm * 2;   // 0 to 128, need to go 0 to 256
            break;

         case EFFECT_SAMPLE_OFFSET:
            m_Track_Data[track].Pos = note->Effect_Parms << (FRAC_BITS + 8);
            break;

         case EFFECT_VOLUME_SLIDE:   
            break; // tick effect

         case EFFECT_JUMP_TO_PATTERN:
            new_order = note->Effect_Parms;
            if ( new_order >= m_Song_Length )
               new_order = 0;
            new_row = 0;

         case EFFECT_SET_VOLUME:
            m_Track_Data[track].Volume = note->Effect_Parms;
            DoSlideVolume(track,0);
            m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume;
            break;

         case EFFECT_PATTERN_BREAK:
            new_row = eparmx * 10 + eparmy;
            if ( new_row > 63 )
               new_row = 0;
            new_order = m_Order + 1;
            if ( new_order >= m_Song_Length )
               new_order = 0;
            break;

         case EFFECT_EXTENDED:
            switch ( eparmx )
            {
               // Set filter
               case EXTENDED_SET_FILTER:   
                  infof("Extended Effect 0x%x Not Supported\r\n",eparmx);
                  break;

               // Fine porta up
               case EXTENDED_FINE_PORTA_UP:
                  m_Track_Data[track].Period -= eparmy;
                  break;

               // Fine porta down
               case EXTENDED_FINE_PORTA_DOWN:
                  m_Track_Data[track].Period += eparmy;
                  break;

               // Glissando
               case EXTENDED_GLISSANDO:   
                  infof("Extended Effect 0x%x Not Supported\r\n",eparmx);
                  break;

               case EXTENDED_VIBRATO_WAVE:   
                  infof("Extended Effect 0x%x Not Supported\r\n",eparmx);
                  break;

               case EXTENDED_FINE_TUNE:
                  m_Samples[sample].Frequency = FineTuneFrequency[eparmy];
                  break;

               case EXTENDED_PATTERN_LOOP:   
                  infof("Extended Effect 0x%x Not Supported\r\n",eparmx);
                  break;

               case EXTENDED_TREMOLO_WAVE:   
                  infof("Extended Effect 0x%x Not Supported\r\n",eparmx);
                  break;

               case EXTENDED_PAN:
                  m_Track_Data[track].Pan = eparmy * 16;   // need to go 0 to 256
                  break;

               case EXTENDED_RETRIG_NOTE:
                  m_Track_Data[track].Retrig_Tick   = eparmy;
                  break; // tick effect

               case EXTENDED_FINE_VOLUME_UP:
                  DoSlideVolume(track, eparmy);
                  m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume;
                  break;

               case EXTENDED_FINE_VOLUME_DOWN:
                  DoSlideVolume(track, -eparmy);
                  m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume;
                  break;

               case EXTENDED_CUT_NOTE:   
                  break; // tick effect

               case EXTENDED_DELAY_NOTE:   
                  m_Track_Data[track].Delay_Tick = eparmy;
                  break;

               case EXTENDED_PATTERN_DELAY:   
                  infof("Extended Effect 0x%x Not Supported\r\n",eparmx);
                  break;

               case EXTENDED_INVERT_LOOP:   
                  infof("Extended Effect 0x%x Not Supported\r\n",eparmx);
                  break;
            }
            break;

         case EFFECT_SET_SPEED:
            if ( eparm < 0x20 )
               m_Speed = note->Effect_Parms;
            else
               m_BPM = note->Effect_Parms;
            break;

         case EFFECT_SET_GLOBAL_VOLUME:
            SetMasterVolume(eparm);
            break;

         case EFFECT_PANNING_SLIDE:
            m_Track_Data[track].Pan = eparmy * 16;   // need to go 0 to 256
            break;

         // If anything makes it this far then we have a problem
         #ifdef _DEBUG
            default:
               warnf(
                        "Oh oh! Weird effect code 0x0%X at pattern %li row %li track %li\n",
                  (int)note->Effect,
                        (int)m_Orders[m_Order],
                        (int)m_Row,
                        (int)track );
               break;
         #endif
      }

      // If we have something playing then set the frequency
      if ( m_Track_Data[track].Period > 0 )
      {
         if ( m_Linear_Frequency )
            m_Track_Data[track].Freq = (float)LinearPeriod2Freq(m_Track_Data[track].Period);
         else
            m_Track_Data[track].Freq = Period2Freq(m_Track_Data[track].Period);
      }
   }

   // Update our row and orders
   m_Row   = new_row;
   m_Order = new_order;

//   infof("\r\n");
}


BOOL CModPlayer::MixSubChunk(int * left,int * right,int num_samples)
{
   // Set up a mixing buffer and clear it
   for ( int i = 0 ; i < num_samples ; i++ )
      left[i] = right[i] = 0;

   // Loop through each channel and process note data
   for ( int track = 0 ; track < m_Num_Tracks ; track++ )
   {
      // Make sure I'm actually playing something
      if ( m_Track_Data[track].Sample <= 0 )
         continue;

      // Make sure this sample actually contains sound data
      if ( !m_Samples[m_Track_Data[track].Sample].Length )
         continue;

      int left_volume      = m_Track_Data[track].Mix_Volume;
      int right_volume   = m_Track_Data[track].Mix_Volume;
      int panning         = m_Track_Data[track].Pan;
      int pos;

      //left_volume = (m_Track_Data[track].Mix_Volume * (255 - panning)) / 255;
      //right_volume = (m_Track_Data[track].Mix_Volume * panning) / 255;

      if ( panning < 128 )
         right_volume = ((255 - (128 - panning) * 2) * m_Track_Data[track].Mix_Volume) / 255;
      if ( panning > 128 )
         left_volume = ((255 - (panning - 128) * 2) * m_Track_Data[track].Mix_Volume) / 255;

      if ( left_volume > 0 )
         pos = MixTrack(track,left,left_volume,num_samples);

      if ( right_volume > 0 )
         pos = MixTrack(track,right,right_volume,num_samples);

      m_Track_Data[track].Pos = pos;
   }

   return TRUE;
}


int CModPlayer::MixTrack(int track,int * buffer,int volume,int num_samples)
{
   // Set up for the mix loop
   int *   mixed         = buffer;
   BYTE *   sample         = m_Samples[m_Track_Data[track].Sample].Data;
   int      length         = m_Samples[m_Track_Data[track].Sample].Length << FRAC_BITS;
   int      loop_start      = m_Samples[m_Track_Data[track].Sample].Loop_Start << FRAC_BITS;
   int      loop_end      = m_Samples[m_Track_Data[track].Sample].Loop_End << FRAC_BITS;
   float   freq         = m_Track_Data[track].Freq;
   int      pos            = m_Track_Data[track].Pos;
   int      delta_pos      = (int)((freq * (1 << FRAC_BITS)) / m_Frequency);
   short *   volume_table   = m_Volume_Table[volume];
   int      mix_pos         = 0;
   int      samples_to_mix   = num_samples;
   int      i;

   if ( pos < 0 )
      pos = 0;

   if ( delta_pos == 0 )
      return TRUE;

   while ( samples_to_mix )
   {
      int this_count;

      // If I'm a looping sample then I need to check if it's time to loop back. I also need to figure out
      // how many samples I can mix before I need to loop again
      if ( abs(loop_start - loop_end) > (2 << FRAC_BITS) )
      {
         if ( loop_end > loop_start )
         {
            if ( pos >= loop_end )
               pos = loop_start + (pos - loop_end);

            if ( pos < 0 )
               pos = 0;

            this_count = min(samples_to_mix,(loop_end - pos - 1) / delta_pos + 1);

            if ( this_count < 0 || (pos + (this_count - 1) * delta_pos) >= loop_end )
            {
               this_count = 0;
               samples_to_mix = 0;
            }
         }
         else
         {
            if ( pos >= length )
               pos -= length;
            if ( pos < loop_start && pos > loop_end )
               pos = loop_start + (pos - loop_end);

            if ( pos < 0 )
               pos = 0;

            if ( pos < loop_end )
            {
               this_count = min(samples_to_mix,(loop_end - pos - 1) / delta_pos + 1);

               if ( this_count < 0 || (pos + (this_count - 1) * delta_pos) >= loop_end )
               {
                  this_count = 0;
                  samples_to_mix = 0;
               }
            }
            else
            {
               this_count = min(samples_to_mix,(length - pos - 1) / delta_pos + 1);

               if ( this_count < 0 || (pos + (this_count - 1) * delta_pos) >= length )
               {
                  this_count = 0;
                  samples_to_mix = 0;
               }
            }
         }

         samples_to_mix -= this_count;
      }

      // If I'm not a looping sample then mix until I'm done playing the entire sample
      else
      {
         // If we've already reached the end of the sample then forget it
         if ( pos >= length )
            this_count = 0;
         else
            this_count = min(num_samples,(length - pos - 1) / delta_pos + 1);

         samples_to_mix = 0;

         assertf(this_count == 0 || (pos + (this_count - 1) * delta_pos) < length);
      }

      for ( i = 0 ; i < this_count ; i++ )
      {
         // Do some error checking - this is only compiled into debug builds
      //   assertf( ( (loop_end > (2 << FRAC_BITS) ) && (pos >= 0) && (pos < loop_end) ) ||
      //         ( (loop_end <= (2 << FRAC_BITS)) && (pos >= 0) && (pos < length) ) );
         assertf(i < num_samples);

         // Mix this sample in and update our position
         if ( m_Over_Sample )
         {
            int sample1 = volume_table[sample[pos >> FRAC_BITS]];
            int sample2 = volume_table[sample[(pos >> FRAC_BITS) + 1]];
            int frac1   = pos & ((1 << FRAC_BITS) - 1);
            int frac2   = (1 << FRAC_BITS) - frac1;

            mixed[mix_pos++] += ((sample1 * frac2) + (sample2 * frac1)) >> FRAC_BITS;
         }
         else
            mixed[mix_pos++] += volume_table[sample[pos >> FRAC_BITS]];

         pos += delta_pos;
      }
   }

   // return current position, only need to save if both left and right have been mixed
   return pos;
}


void CModPlayer::UpdateEffects()
{
   // Loop through each channel
   for (int track = 0 ; track < m_Num_Tracks ; track++ )
   {
      // Get note data
      NoteData * note = &m_Current_Row->Note[track];

      // Parse it
      int effect = note->Effect;                // grab the effect number
      int eparm  = note->Effect_Parms;          // grab the effect parameter
      int eparmx = eparm >> 4;                  // grab the effect parameter x
      int eparmy = eparm & 0xF;                 // grab the effect parameter y

      // Process it
      switch ( effect )
      {
         case EFFECT_ARPEGGIO:
            if ( eparm > 0 )
            {
               if ( m_Linear_Frequency )
                  m_Track_Data[track].Period = (int)LinearFreq2Period(m_Track_Data[track].Note,m_Track_Data[track].Sample);
               else
                  m_Track_Data[track].Period = (int)Freq2Period(m_Track_Data[track].Note,m_Samples[m_Track_Data[track].Sample].Frequency);
               break;

               switch ( m_Tick % 3 )
               {
                  case 0:
                     break;
                  case 1:
                     m_Track_Data[track].Period += eparmx;
                     break;
                  case 2:
                     m_Track_Data[track].Period += eparmy;
                     break;
               }
            }
            break;

         case EFFECT_PORTA_UP:
            m_Track_Data[track].Period -= eparm;         // subtract freq
            if ( m_Track_Data[track].Period < 54 )
               m_Track_Data[track].Period = 54;         // stop at C-5
            break;

         case EFFECT_PORTA_DOWN:
            m_Track_Data[track].Period += eparm;           // add freq
            break;

         case EFFECT_PORTA_TO_NOTE:
            DoPorta(track);
            break;

         case EFFECT_VIBRATO:
            DoVibrato(track);
            break;

         case EFFECT_PORTA_AND_VOL:
            DoPorta(track);
            DoSlideVolume(track, eparmx - eparmy);
            m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume;
            break;

         case EFFECT_VIBRATO_AND_VOL:
            DoVibrato(track);
            DoSlideVolume(track, eparmx - eparmy);
            m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume;
            break;

         case EFFECT_TREMOLO:
            DoTremalo(track);
            break;

         case EFFECT_VOLUME_SLIDE:
            DoSlideVolume(track, eparmx - eparmy);
            m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume;
            break;

         case EFFECT_EXTENDED:
            switch (eparmx)
            {
               case EXTENDED_RETRIG_NOTE:   
                  if ( m_Tick % m_Track_Data[track].Retrig_Tick == 0 )
                     m_Track_Data[track].Pos = 0;
                  break;

               case EXTENDED_CUT_NOTE:
                  if ( m_Tick == eparmy )
                  {
                     m_Track_Data[track].Volume = 0;
                     m_Track_Data[track].Mix_Volume = 0;
                  }
                  break;

               case EXTENDED_DELAY_NOTE:   
                  if ( m_Tick == m_Track_Data[track].Delay_Tick )
                  {
                     m_Track_Data[track].Note = m_Track_Data[track].Next_Note;

                     if ( m_Linear_Frequency )
                        m_Track_Data[track].Period = (int)LinearFreq2Period(m_Track_Data[track].Note,m_Track_Data[track].Sample);
                     else
                        m_Track_Data[track].Period = (int)Freq2Period(m_Track_Data[track].Note,m_Samples[m_Track_Data[track].Sample].Frequency);
                  }
                  break;

               // All other Exy effects are note effects
            }
            break;

         // All other effects are note effects
      }

      // If we have something playing then set the frequency
      if ( m_Track_Data[track].Period > 0 )
      {
         if ( m_Linear_Frequency )
            m_Track_Data[track].Freq = (float)LinearPeriod2Freq(m_Track_Data[track].Period);
         else
            m_Track_Data[track].Freq = Period2Freq(m_Track_Data[track].Period);
      }
   }
}

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

inline void CModPlayer::DoSlideVolume(int track, int amount)
{
   amount += m_Track_Data[track].Volume;
   m_Track_Data[track].Volume = amount < 0 ? 0 : amount > 64 ? 64 : amount;
}


void CModPlayer::DoPorta(int track)
{
   if (  m_Track_Data[track].Period < m_Track_Data[track].Porta )
   {      
      m_Track_Data[track].Period += m_Track_Data[track].Porta_Speed;
      if ( m_Track_Data[track].Period > m_Track_Data[track].Porta )
         m_Track_Data[track].Period = m_Track_Data[track].Porta;
   }
   else if ( m_Track_Data[track].Period > m_Track_Data[track].Porta )
   {      
      m_Track_Data[track].Period -= m_Track_Data[track].Porta_Speed;
      if ( m_Track_Data[track].Period < m_Track_Data[track].Porta )
         m_Track_Data[track].Period = m_Track_Data[track].Porta;
   }

   if ( m_Linear_Frequency )
      m_Track_Data[track].Freq = (float)LinearPeriod2Freq(m_Track_Data[track].Period);
   else
      m_Track_Data[track].Freq = Period2Freq(m_Track_Data[track].Period);

//   infof(_T("Porta %i Speed %i Period %i\r\n"),m_Track_Data[track].Porta,m_Track_Data[track].Porta_Speed,m_Track_Data[track].Period);
}


void CModPlayer::DoVibrato(int track)
{
   int vib = (m_Track_Data[track].Vib_Depth * Sine_Table[m_Track_Data[track].Sine_Pos]) >> 7; // div 128

   int period = m_Track_Data[track].Period;

   if ( m_Track_Data[track].Sine_Neg == 0 )
      period += vib;
   else
      period -= vib;

   if ( period != 0 )
   {
      if ( m_Linear_Frequency )
         m_Track_Data[track].Freq = (float)LinearPeriod2Freq(period);
      else
         m_Track_Data[track].Freq = Period2Freq(period);
   }

   m_Track_Data[track].Sine_Pos += m_Track_Data[track].Vib_Speed;
   if ( m_Track_Data[track].Sine_Pos > 31 )
   {
      m_Track_Data[track].Sine_Pos -= 32;
      m_Track_Data[track].Sine_Neg = ~m_Track_Data[track].Sine_Neg;      // flip pos/neg flag
   }
}


void CModPlayer::DoTremalo(int track)
{
   int vib = m_Track_Data[track].Trem_Depth * Sine_Table[m_Track_Data[track].Sine_Pos] >> 6; // div 64

   if ( m_Track_Data[track].Sine_Neg == 0 )
   {
      if ( m_Track_Data[track].Volume + vib > 64 )
         vib = 64-m_Track_Data[track].Volume;
      m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume + vib;
   }
   else
   {
      if ( m_Track_Data[track].Volume - vib < 0 )
         vib = m_Track_Data[track].Volume;
      m_Track_Data[track].Mix_Volume = m_Track_Data[track].Volume + vib;
   }

   m_Track_Data[track].Sine_Pos += m_Track_Data[track].Trem_Speed;
   if ( m_Track_Data[track].Sine_Pos > 31 )
   {
      m_Track_Data[track].Sine_Pos -= 32;
      m_Track_Data[track].Sine_Neg = ~m_Track_Data[track].Sine_Neg;         // flip pos/neg flag
   }
}

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

// Sets the master volume for the mod being played. The value should be from 0 (silence) to 64 (max volume)
void CModPlayer::SetMasterVolume(int volume)
{
   assertf(volume >= 0 && volume <= 64);
   // Initialize my volume table
   for ( int i = 0 ; i < 65 ; i++ )
      for ( int j = 0 ; j < 256 ; j++ )
         m_Volume_Table[i][j] = (volume * i * (int)(char)j) / 64;

   m_Master_Volume = volume;
}


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

// This is callback procedure. It's called when each block of sound is finished playing.
void CALLBACK CModPlayer::waveOutProc(HWAVEOUT m_Wave_Handle, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
   if ( uMsg == MM_WOM_DONE )
   {
      LPWAVEHDR      Wave_Header   = (LPWAVEHDR)dwParam1;
      SoundBlock *   block      = (SoundBlock *)Wave_Header->dwUser;
      CModPlayer *   me         = block->Tracker;

      block->Wave_Header = Wave_Header;
      me->AddBlock(block);

      // If we're still playing then signal a mix
      if ( me->m_Playing )
      {
         InterlockedIncrement(&me->m_Blocks_To_Mix);
         SetEvent(me->m_Mix_Event);
      }
   }
}


DWORD WINAPI CModPlayer::ThreadProc(LPVOID param)
{
   CModPlayer * me = (CModPlayer *)param;

   while ( 1 )
   {
      if ( me->m_Paused == FALSE )
      {
         if ( me->m_Finished == FALSE )
            me->m_Playing = me->MixChunk();

         me->RemoveBlock();
         int blocks = InterlockedDecrement(&me->m_Blocks_To_Mix);
         if ( blocks > 0 )
         {
            infof("Continue Mix\r\n");
            ResetEvent(me->m_Mix_Event);   
            continue;
         }
         else if ( blocks < 0 )
         {
            infof("Over Decremented\r\n");
            InterlockedIncrement(&me->m_Blocks_To_Mix);
         }
      }

      WaitForSingleObject(me->m_Mix_Event,INFINITE);
      ResetEvent(me->m_Mix_Event);
   }

   return 0;
}


void CModPlayer::AddBlock(SoundBlock * block)
{
   EnterCriticalSection(&m_Critical_Section);

   block->Next_Block = NULL;

   if ( m_Blocks == NULL )
      m_Blocks = block;
   else
   {
      SoundBlock * next = m_Blocks;

      while ( next->Next_Block != NULL )
         next = next->Next_Block;

      next->Next_Block = block;
      block->Next_Block = NULL;
   }

   LeaveCriticalSection(&m_Critical_Section);
}


void CModPlayer::RemoveBlock()
{
   EnterCriticalSection(&m_Critical_Section);

   while ( m_Blocks != NULL )
   {
      SoundBlock * next = m_Blocks->Next_Block;

      // Free this block up
      waveOutUnprepareHeader(m_Wave_Handle,m_Blocks->Wave_Header,sizeof(WAVEHDR));
      delete [] m_Blocks->Sound_Data;
      delete m_Blocks;

      m_Blocks = next;
   }

   LeaveCriticalSection(&m_Critical_Section);
}


void CModPlayer::GetSampleNames(QStringList& list)
{
   list.clear();
   for ( int i = 0 ; i <= m_Num_Samples ; i++ )
      list.append(m_Samples[i].Name);
}
ravenger7
Site Admin
 
Posts: 31
Joined: Sun Nov 16, 2008 1:18 pm
Location: Illinois

Postby ravenger7 » Tue Feb 23, 2010 9:51 pm

Code: Select all
//////////////////////////////////////////////////////////////////////////////////////////////////
//
//   Robert Morgan "Sourcerer"
//      March 15, 1999
//
//////////////////////////////////////////////////////////////////////////////////////////////////


#include <windows.h>
#include <math.h>
#include "ModPlayer.h"
#include "Diagnostics.h"

#pragma pack(push,DataAlign,1)

typedef struct
{
    char    Sample_Name[22];
    WORD    Length;
    BYTE    Fine_Tune;
    BYTE    Volume;
    WORD    Loop_Start;
    WORD    Loop_Length;
} MODSample;

typedef struct
{
    char      Song_Name[20];
    MODSample   Samples[31];
    BYTE      Num_Orders;
    BYTE      Restart;
    BYTE      Orders[128];
    DWORD      Magic;
} MODHeader;

typedef struct
{
    BYTE    Type;
    char    File_Name[13];
    WORD    Data_Offset;
    DWORD   Length;
    DWORD   Loop_Start;
    DWORD   Loop_End;
    BYTE    Volume;
    BYTE    Lib_Disk;
    BYTE    Pack;
    BYTE    Flags;
    DWORD   Frequency;
    char    Pad0[12];
    char    Sample_Name[28];
    DWORD   Magic;
} S3MSample;

typedef struct
{
    char    Song_Name[28];
    BYTE    EOF_Mark;
    BYTE    Song_Type;
    WORD    Pad0;
    WORD    Num_Orders;
    WORD    Num_Samples;
    WORD    Num_Patterns;
    WORD    Flags;
    WORD    Tracker;
    WORD    File_Format;
    DWORD   Magic;
    BYTE    Global_Volume;
    BYTE    Tempo;
    BYTE    BPM;
    BYTE    Master_Volume;
    BYTE    Ultra_Click;
    BYTE    Def_Pan;
    BYTE    Pad1[8];
    WORD    Special;
    BYTE    ChanMap[32];
} S3MHeader;

typedef struct
{
    char    File_Name[13];
    DWORD   Length;
    DWORD   Loop_Start;
    DWORD   Loop_End;
} GG9Sample;

typedef struct
{
    WORD    Magic;
    char    Song_Name[108];
    BYTE    Num_Samples;
    BYTE    Num_Patterns;
    BYTE    ReStart;
    BYTE    Orders[128];
    BYTE    Tempos[128];
    BYTE    Breaks[128];
} GG9Header;

typedef struct
{
    char    File_Name[13];
    BYTE    Lib_Disk;
    WORD    Data_Ptr;
    WORD    Length;
    WORD    Loop_Start;
    WORD    Loop_End;
    BYTE    Volume;
    BYTE    Pad0;
    WORD    Frequency;
    BYTE    Pad1[6];
} STMSample;

typedef struct
{
    char      Song_Name[20];
    char      Tracker[8];
    BYTE      EOF_Mark;
    BYTE      File_Type;
    WORD      Tracker_Ver;
    BYTE      Tempo;
    BYTE      Num_Patterns;
    BYTE      Global_Volume;
    BYTE      Pad[13];
    STMSample   Samples[31];
    BYTE      Orders[128];
} STMHeader;

typedef struct
{
    BYTE    Data[192];
} MTMTrack;

typedef struct
{
    char    Sample_Name[22];
    DWORD   Length;
    DWORD   Loop_Start;
    DWORD   Loop_End;
    BYTE    Fine_Tune;
    BYTE    Volume;
    BYTE    Flags;
} MTMSample;

typedef struct
{
    DWORD   Magic;
    char    Song_Name[20];
    WORD    Num_Tracks;
    BYTE    Last_Pattern;
    BYTE    Last_Order;
    WORD    Comment_Length;
    BYTE    Num_Samples;
    BYTE    Flags;
    BYTE    Beats_Per_Track;
    BYTE    Num_Chans;
    BYTE    Pan_Pos[32];
} MTMHeader;

typedef struct
{
   DWORD   Length;
   DWORD   Loop_Start;
   DWORD   Loop_End;
   BYTE   Volume;
   char   Fine_Tune;
   BYTE   Type;
   BYTE   Panning;
   char   Relative_Note;
   BYTE   Reserved;
   char   Name[22];
} XMSampleHeader;

typedef struct
{
   DWORD   Header_Size;
   BYTE   Notes[96];
   BYTE   Volume_Envelope[48];
   BYTE   Panning_Envelope[48];
   BYTE   Volume_Points;
   BYTE   Panning_Poings;
   BYTE   Volume_Sustain;
   BYTE   Volume_Loop_Start;
   BYTE   Volume_Loop_End;
   BYTE   Panning_Sustain;
   BYTE   Panning_Loop_Start;
   BYTE   Panning_Loop_End;
   BYTE   Volume_Type;
   BYTE   Panning_Type;
   BYTE   Vibrato_Type;
   BYTE   Vibrato_Sweep;
   BYTE   Vibrato_Depth;
   BYTE   Vibrato_Rate;
   WORD   Volume_Fadeout;
   WORD   Reserved;
} XMSampleInfo;

typedef struct
{
   DWORD   Size;
   char   Name[22];
   BYTE   Type;
   WORD   Num_Samples;   
} XMInstrument;

typedef struct
{
   DWORD   Header_Length;
   BYTE   Packing_Type;
   WORD   Rows;
   WORD   Data_Size;
} XMPatternHeader;

typedef struct
{
   char   ID[17];
   char   Name[20];
   BYTE   Magic;
   char   Tracker[20];
   WORD   Version;
   DWORD   Header_Size;
   WORD   Song_Length;
   WORD   Restart;
   WORD   Num_Tracks;
   WORD   Num_Patterns;
   WORD   Num_Samples;
   WORD   Flags;
   WORD   Tempo;
   WORD   BPM;
   BYTE   Orders[256];
} XMHeader;


#pragma pack(pop,DataAlign)

// MOD Magic Numbers
#define MOD_MK      0x2E4B2E4DL
#define MOD_FLT4    0x34544C46L
#define MOD_FLT8    0x38544C46L
#define MOD_6CHN    0x4E484336L
#define MOD_8CHN    0x4E484338L

// S3M Magic Numbers
#define S3M_SCRM    0x4D524353L
#define S3M_SCRS    0x53524353L

// 669 Magic Numbers
#define GG9_MAGIC   0x6669

// STM Magic Numbers
#define STM_MAGIC   "!Scream!"

// MTM Magic Number
#define MTM_MAGIC   0x104D544DL
#define MTM_MASK    0xF0FFFFFFL

// XM Magic Number
#define XM_MAGIC   0x1A
#define XM_ID      "Extended module: "


WORD PeriodToNote[NUM_NOTES] =
{
    6848,6464,6096,5760,5424,5120,4832,4560,4304,4064,3840,3624,
    3424,3232,3048,2880,2712,2560,2416,2280,2152,2032,1920,1812,
    1712,1616,1524,1440,1356,1280,1208,1140,1076,1016, 960, 906,
     856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
     428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
     214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113,
     107, 101,  95,  90,  85,  80,  75,  71,  67,  63,  60,  56,
      53,  50,  47,  45,  42,  40,  37,  35,  33,  31,  30,  28
};

WORD FineTuneFrequency[16] =
{
   8363,8424,8485,8546,8608,8670,8733,8797,
   7894,7951,8008,8066,8125,8184,8243,8303
};


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

BOOL CModPlayer::LoadMOD(BYTE * data,int bytes_read)
{
   int         num_patterns;
   int         row;
   int         note;
   int         i;
   int         index;
   int         num_samples = 31;
   MODHeader *   header = (MODHeader *)data;

   m_Num_Tracks   = 4;
   m_Speed         = 6;
   m_BPM         = 125;

   // The number of samples we have depends on whether the "M.K." identifier exists or not   
   if ( header->Magic == MOD_MK )
      m_Num_Tracks = 4;
   else if ( header->Magic == MOD_FLT4 )
      m_Num_Tracks = 4;
   else if ( header->Magic == MOD_FLT8 )
      m_Num_Tracks = 8;
   else if ( header->Magic == MOD_6CHN )
      m_Num_Tracks = 6;
   else if ( header->Magic == MOD_8CHN )
      m_Num_Tracks = 8;
   else
      num_samples = 16;

   m_Track_Data = new TrackData[m_Num_Tracks];

   // Get the name
   memcpy(m_Song_Name,header->Song_Name,20);
   m_Song_Name[20] = '\0';

   // Read in all the instrument headers - mod files have 31, sample #0 is ignored
   m_Samples = new SampleData [num_samples + 1];
   m_Num_Samples = num_samples;

   memset(m_Samples,0,(m_Num_Samples + 1) * sizeof(SampleData));
   
   for ( i = 1 ; i <= num_samples ; i++ )
   {
      // Read the sample name
      strncpy(m_Samples[i].Name,header->Samples[i - 1].Sample_Name,22);
      m_Samples[i].Name[22] = '\0';
   
      // Read remaining info about sample
      m_Samples[i].Length = ConvertMODWord(header->Samples[i - 1].Length);
      m_Samples[i].Frequency = FineTuneFrequency[header->Samples[i - 1].Fine_Tune];
      m_Samples[i].Volume = header->Samples[i - 1].Volume;
      m_Samples[i].Loop_Start = ConvertMODWord(header->Samples[i - 1].Loop_Start);
      m_Samples[i].Loop_End = m_Samples[i].Loop_Start + ConvertMODWord(header->Samples[i - 1].Loop_Length);
      
      // Fix loop end in case it goes too far
      if ( m_Samples[i].Loop_End > m_Samples[i].Length )
         m_Samples[i].Loop_End = m_Samples[i].Length;
   }

   // Read in song data
   m_Song_Length = header->Num_Orders;
   
   memcpy(m_Orders,header->Orders,128);

   num_patterns = 0;
   for ( i = 0 ; i < m_Song_Length ; i++ )
   {
      if ( m_Orders[i] > num_patterns   )
         num_patterns = m_Orders[i];
   }
   num_patterns++;

   index = sizeof(MODHeader);

   // Load in the pattern data
   m_Patterns = new PatternData [num_patterns];
   m_Num_Patterns = num_patterns;
   for ( i = 0 ; i < num_patterns ; i++ )
   {
      // Loop through each row
      m_Patterns[i].Num_Rows = 64;
      for ( row = 0 ; row < m_Patterns[i].Num_Rows ; row++ )
      {
         // Set the number of notes for this pattern
         m_Patterns[i].Row[row].Note = new NoteData [m_Num_Tracks];

         // Loop through each note
         for ( int track = 0 ; track < m_Num_Tracks ; track++ )
         {
            // Get the 4 bytes for this note
            int b0      = data[index++];
            int b1      = data[index++];
            int b2      = data[index++];
            int b3      = data[index++];
            int sample   = (b0 & 0xF0) | (b2 >> 4);
            int period   = ((b0 & 0x0F) << 8) | b1;
         
            for ( note = 0 ; note < NUM_NOTES ; note++ )
               if ( period >= PeriodToNote[note])
                  break;

            if ( note >= NUM_NOTES )
               note = 0;

            m_Patterns[i].Row[row].Note[track].Sample_Num   = sample;
            m_Patterns[i].Row[row].Note[track].Effect      = b2 & 0x0F;
            m_Patterns[i].Row[row].Note[track].Effect_Parms   = b3;
            m_Patterns[i].Row[row].Note[track].Note         = note;
            m_Patterns[i].Row[row].Note[track].Volume      = m_Samples[sample].Volume;
         }
      }
   }

   // Load in the sample data
   for ( i = 1 ; i <= num_samples ; i++ )
   {
      m_Samples[i].Data = new BYTE [m_Samples[i].Length + 1];

      if ( m_Samples[i].Length )
         memcpy(&m_Samples[i].Data[0], &data[index], m_Samples[i].Length);

      index += m_Samples[i].Length;

      // Duplicate the last byte, we'll need an extra one in order to safely anti-alias
      int length = m_Samples[i].Length;
      if ( length > 0 )
      {
         m_Samples[i].Data[length] = m_Samples[i].Data[length - 1];
         if ( m_Samples[i].Loop_End - m_Samples[i].Loop_Start > 2 )
            m_Samples[i].Data[m_Samples[i].Loop_End] = m_Samples[i].Data[m_Samples[i].Loop_Start];
      }
   }

   for ( int track = 0 ; track < m_Num_Tracks ; track++ )
   {
      // set left to right panning
      m_Default_Pan[track] = ( ( track & 3) == 0 ) || ( (track & 3) == 3 ) ? 0 : 255;
   }

   return TRUE;
}

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

BOOL CModPlayer::LoadS3M(BYTE * data,int bytes_read)
{
   S3MHeader * header = (S3MHeader *)data;
   S3MSample *   sample;
   int         pattern;

   // Get the name
   memcpy(m_Song_Name,header->Song_Name,20);
   m_Song_Name[20] = '\0';

   m_Song_Length   = (BYTE)header->Num_Orders;
   m_Num_Patterns   = (BYTE)header->Num_Patterns;
   m_BPM         = header->BPM;
   m_Speed         = header->Tempo;
   m_Num_Samples   = (BYTE)header->Num_Samples;
   m_Samples      = new SampleData [m_Num_Samples + 1];

   memcpy(m_Orders,data + sizeof(S3MHeader),m_Song_Length);

   for ( int i = 0 ; i < 32 ; i++ )
      if ( !(header->ChanMap[i] & 0x80) )
         m_Num_Tracks = i + 1;
   infof("Tracks = %i\r\n",m_Num_Tracks);

   m_Track_Data = new TrackData[m_Num_Tracks];

   memset(m_Samples,0,(m_Num_Samples + 1) * sizeof(SampleData));

   for ( int i = 1 ; i <= m_Num_Samples ; i++ )
   {
      int index = sizeof(S3MHeader) + m_Song_Length + (i - 1) * 2;
      int sample_offset = ConvertWord(data,index) * 16;

      sample = (S3MSample *)(data + sample_offset);

      if ( sample->Type == 1 && sample->Magic == S3M_SCRS )
      {
         memcpy(m_Samples[i].Name,sample->Sample_Name,28);

         m_Samples[i].Data = new BYTE [sample->Length + 1];
         m_Samples[i].Length = sample->Length;

         if ( m_Samples[i].Length )
         {
            memcpy(m_Samples[i].Data,data + sample->Data_Offset * 16,m_Samples[i].Length);

            // convert signed/unsigned
            for ( int j = 0 ; j < m_Samples[i].Length ; j++ )
               m_Samples[i].Data[j] ^= 0x80;
         }

         m_Samples[i].Volume = sample->Volume;
         m_Samples[i].Frequency = sample->Frequency;

         if ( sample->Flags & 0x01 )
         {
            m_Samples[i].Loop_Start      = sample->Loop_Start;
            m_Samples[i].Loop_End      = sample->Loop_End;
         }

         if ( m_Samples[i].Loop_End -  m_Samples[i].Loop_Start > 2 )
            m_Samples[i].Data[m_Samples[i].Loop_End] = m_Samples[i].Data[m_Samples[i].Loop_Start];
      }
   }

   m_Patterns = new PatternData [m_Num_Patterns];

   for ( pattern = 0 ; pattern < m_Num_Patterns ; pattern++ )
   {
      m_Patterns[pattern].Num_Rows = 64;
      for ( int row = 0 ; row < m_Patterns[pattern].Num_Rows ; row++ )
      {
         m_Patterns[pattern].Row[row].Note = new NoteData [m_Num_Tracks];
         memset(m_Patterns[pattern].Row[row].Note,0,sizeof(NoteData) * m_Num_Tracks);
      }
   }

   int pattern_offset = sizeof(S3MHeader) + m_Song_Length + m_Num_Samples * 2;

   for ( pattern = 0 ; pattern < m_Num_Patterns ; pattern++ )
   {
      int      index = 0;
      int      row = 0;
      BYTE   flag;
      BYTE   track;
      int      note;
      BYTE   volume;
      BYTE   effect;
      BYTE   param;
      int      max_length;
      int      sample;
      int      track_param[32];
   
      index = ConvertWord(data,pattern_offset) * 16;
      
      if ( index < sizeof(S3MHeader) )
      {
         m_Orders[pattern] = 255;   // skip this one
         continue;
      }

      max_length = ConvertWord(data,index) + index - 2;      

      while ( row < m_Patterns[pattern].Num_Rows && index < max_length )
      {
         flag = data[index++];
         if ( flag == 0 )
            row++;
         else
         {
            note   = 0;
            volume   = 0;
            effect   = 0;
            param   = 0;
            sample   = 0;
            track   = flag & 31;

            if ( flag & 0x20 )   // note and sample
            {
               note   = data[index++];
               sample   = data[index++];
            }
            if ( flag & 0x40 )   // volume
            {
               volume   = data[index++];
            }
            if ( flag & 0x80 )   // effect and parameter
            {
               effect   = data[index++] + 64;
               param   = data[index++];
            }
            
            if ( note == 0xFE )
            {
               note   = 0;
               volume   = 0;
            }
            else if ( note == 0xFF )
            {
               note   = 0;
            }
            else
            {
               note = (note & 0x0F) + 12 * (note >> 4);
               if ( note >= NUM_NOTES )
                  note = NUM_NOTES - 1;
            }

            if ( param )
               track_param[track] = param;
            else if ( ( (effect >= 'D') && (effect <= 'G') ) ||
               (effect == 'K') || (effect == 'L') || (effect == 'Q') )
            {
               param = track_param[track];
            }

            if ( track >= m_Num_Tracks )
               continue;
            
            switch ( effect )
            {
               case 'A':
                  if ( param <= 0x1F )
                     effect   = EFFECT_SET_SPEED;
                  else
                  {
                     effect   = 0;
                     param   = 0;
                  }
                  break;
               case 'B':
                  effect = EFFECT_JUMP_TO_PATTERN;
                  break;
               case 'C':
                  effect = EFFECT_PATTERN_BREAK;
                  break;
               case 'D':
                  if ( (param & 0xF0) == 0x00 )
                  {
                     effect   = EFFECT_VOLUME_SLIDE;
                     param  &= 0x0F;
                  }
                  else if ( (param & 0x0F) == 0x00 )
                  {
                     effect   = EFFECT_VOLUME_SLIDE;
                     param  &= 0xF0;
                  }
                  else if ( (param & 0xF0) == 0xF0 )
                  {
                     effect   = EFFECT_EXTENDED;
                     param   = (EXTENDED_FINE_VOLUME_DOWN << 4) | (param & 0x0F);
                  }
                  else if ( (param & 0x0F) == 0x0F )
                  {
                     effect   = EFFECT_EXTENDED;
                     param   = (EXTENDED_FINE_VOLUME_UP << 4) | (param >> 4);
                  }
                  else
                  {
                     effect   = 0;
                     param   = 0;
                  }
                  break;
               case 'E':
                  if ( (param & 0xF0) == 0xF0 )
                  {
                     effect   = EFFECT_EXTENDED;
                     param   = (EXTENDED_FINE_PORTA_DOWN << 4) | (param & 0x0F);
                  }
                  else if ( (param & 0xF0) == 0xE0 )
                  {
                     effect   = EFFECT_EXTENDED;
                     param   = (EXTENDED_FINE_PORTA_DOWN << 4) | ((param & 0x0F) >> 2);
                  }
                  else
                  {
                     effect   = EFFECT_PORTA_DOWN;
                     param   = 0x01;
                  }
                  break;
               case 'F':
                  if ( (param & 0xF0) == 0xF0 )
                  {
                     effect   = EFFECT_EXTENDED;
                     param   = (EXTENDED_FINE_PORTA_UP << 4) | (param & 0x0F);
                  }
                  else if ( (param & 0xF0) == 0xE0 )
                  {
                     effect   = EFFECT_EXTENDED;
                     param   = (EXTENDED_FINE_PORTA_UP << 4) | ((param & 0x0F) >> 2);
                  }
                  else
                  {
                     effect   = EFFECT_PORTA_UP;
                     param   = 0x01;
                  }
                  break;
               case 'G':
                  effect   = EFFECT_PORTA_TO_NOTE;
                  break;
               case 'H':
                  effect   = EFFECT_VIBRATO;
                  break;
               case 'I':
                  effect   = EFFECT_EXTENDED;
                  param   = (EXTENDED_RETRIG_NOTE << 4) | (param & 0x0F); // emu tremor with retrig
                  break;
               case 'J':
                  effect   = EFFECT_ARPEGGIO;
                  break;
               case 'K':
                  effect   = EFFECT_VIBRATO_AND_VOL;
                  break;
               case 'L':
                  effect   = EFFECT_PORTA_AND_VOL;
                  break;
               case 'O':
                  effect   = EFFECT_SAMPLE_OFFSET;
                  break;
               case 'Q':
                  effect = EFFECT_VOLUME_SLIDE;  // emu retrigvol with volslide
                  if ( (param >> 4) >= 8 )
                     param = (((param >> 4) / (param & 0x0F)) + 1) << 4;
                  else if ( (param & 0x0F) > 0 )
                     param = (((param >> 4) / (param & 0x0F)) + 1);
                  break;
               case 'R':
                  effect = EFFECT_TREMOLO;
                  break;
               case 'S':
                  switch (param & 0xF0)
                  {
                     case 0x00:
                        effect = EFFECT_EXTENDED;
                        param = (EXTENDED_SET_FILTER << 4) | (param & 0x0F);
                        break;
                     case 0x10:
                        effect = EFFECT_EXTENDED;
                        param = (EXTENDED_GLISSANDO << 4) | (param & 0x0F);
                        break;
                     case 0x20:
                        effect = EFFECT_EXTENDED;
                        param = (EXTENDED_FINE_TUNE << 4) | (param & 0x0F);
                        break;
                     case 0x30:
                        effect = EFFECT_EXTENDED;
                        param = (EXTENDED_TREMOLO_WAVE << 4) | (param & 0x0F);
                        break;
                     case 0x40:
                        effect = EFFECT_EXTENDED;
                        param = (EXTENDED_VIBRATO_WAVE << 4) | (param & 0x0F);
                        break;
                     case 0x80:
                        effect = EFFECT_EXTENDED;
                        param = (EXTENDED_PAN << 4) | (param & 0x0F);
                        break;
                     case 0xA0:
                        effect = EFFECT_PAN;
                        switch (param & 0x0F)
                        {
                           case 0x00:
                           case 0x02:
                              param   = 0;
                              break;
                           case 0x01:
                           case 0x03:
                              param   = 255;
                              break;
                           case 0x04:
                              param   = (0 + 128) / 2;
                              break;
                           case 0x05:
                              param   = (128 + 255) / 2;
                              break;
                           case 0x06:
                           case 0x07:
                              param   = 128;
                              break;
                           default:
                              effect   = 0;
                              param   = 0;
                              break;
                        }
                        break;
                     default:
                        effect   = 0;
                        param   = 0;
                        break;
                  }
                  break;
               case 'T':
                  if ( param >= 0x20 )
                     effect   = EFFECT_SET_SPEED;
                  else
                  {
                     effect   = 0;
                     param   = 0;
                  }
                  break;
               case 'X':
                  effect   = EFFECT_PAN;
                  break;
               case 'Z':
                  //effect   = EFFECT_JUMP_TO_PATTERN;
                  //param   = 0x80 | param;
                  effect   = 0;
                  param   = 0;
                  break;
               default:
                  effect   = 0;
                  param   = 0;
                  break;
            }
         
            if ( note && !(flag & 0x40) )
               volume = m_Samples[sample].Volume;

            assertf(volume <= 64);
      
            m_Patterns[pattern].Row[row].Note[track].Volume         = volume;
            m_Patterns[pattern].Row[row].Note[track].Note         = note;
            m_Patterns[pattern].Row[row].Note[track].Sample_Num      = sample;
            m_Patterns[pattern].Row[row].Note[track].Effect         = effect;
            m_Patterns[pattern].Row[row].Note[track].Effect_Parms   = param;

            if ( row == 42 )
               assertf(volume <= 64);
         }
      }
   }

   for ( int track = 0 ; track < m_Num_Tracks ; track++ )
   {
      // set left to right panning
      m_Default_Pan[track] = ( header->ChanMap[track] & 0x31 < 8 ) ? 0 : 255;
   }

   return TRUE;
}

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

BOOL CModPlayer::Load669(BYTE * data,int bytes_read)
{
   static BYTE VolumeTable[16] =
   {
        0x00,0x06,0x0B,0x10,0x14,0x18,0x1C,0x20,
        0x24,0x28,0x2C,0x30,0x34,0x38,0x3C,0x40
   };

   GG9Header *   header = (GG9Header *)data;

   if ( header->Magic != GG9_MAGIC )
      return FALSE;
   
   memset(m_Song_Name,0,sizeof(m_Song_Name));
   memcpy(m_Song_Name,header->Song_Name,27);

   m_Num_Samples   = header->Num_Samples;
   m_Num_Patterns   = header->Num_Patterns;
   m_Num_Tracks   = 8;
   m_Speed         = 4;
   m_BPM         = 78;
   m_Song_Length   = 0;
   m_Samples      = new SampleData [m_Num_Samples + 1];
   m_Patterns      = new PatternData [m_Num_Patterns];
   m_Track_Data   = new TrackData[m_Num_Tracks];

   memcpy(m_Orders,header->Orders,128);

   while ( m_Orders[m_Song_Length] != 255 )
      m_Song_Length++;

   memset(m_Samples,0,(m_Num_Samples + 1) * sizeof(SampleData));

   for ( int i = 1 ; i <= m_Num_Samples ; i++ )
   {
      GG9Sample * sample = (GG9Sample *)(data + sizeof(GG9Header) + (i - 1) * sizeof(GG9Sample));

      memcpy(m_Samples[i].Name,sample->File_Name,13);
      m_Samples[i].Name[13]      = '\0';
      m_Samples[i].Length         = sample->Length;
      m_Samples[i].Loop_Start      = sample->Loop_Start;
      m_Samples[i].Loop_End      = sample->Loop_End;

      // Fix loop end in case it goes too far
      if ( m_Samples[i].Loop_End > m_Samples[i].Length )
         m_Samples[i].Loop_End = 0;

      m_Samples[i].Volume         = 64;
      m_Samples[i].Frequency      = 8363;
   }

   // Load in the pattern data
   int index = sizeof(GG9Header) + m_Num_Samples * sizeof(GG9Sample);
   for ( int pattern = 0 ; pattern < m_Num_Patterns ; pattern++ )
   {
      m_Patterns[pattern].Num_Rows = 64;
      for ( int row = 0 ; row < m_Patterns[pattern].Num_Rows ; row++ )
      {
         int insert_tempo = (row == 0);
         int insert_break = ((row == header->Breaks[pattern]) && (row != 63));

         m_Patterns[pattern].Row[row].Note = new NoteData[m_Num_Tracks];

         for ( int track = 0 ; track < m_Num_Tracks ; track++ )
         {
            int b0 = data[index++];
            int b1 = data[index++];
            int b2 = data[index++];

            int note   = b0 >> 2;
            int sample = ((b0 & 0x03) << 4) | (b1 >> 4);
            int volume = VolumeTable[b1 & 0x0F];
            int effect = b2 >> 4;
            int param  = b2 & 0x0f;

            if ( b0 == 0xFE )
            {
               note   = 0;
               sample = 0;
            }
            else if ( b0 == 0xFF )
            {
               note   = 0;
               sample = 0;
               volume = 0;
            }
            else
            {
               note++;
               sample++;
            }

            switch ( effect )
            {
               case 0x00:
                  effect = EFFECT_PORTA_UP;
                  break;
               case 0x01:
                  effect = EFFECT_PORTA_DOWN;
                  break;
               case 0x02:
                  effect = EFFECT_PORTA_TO_NOTE;
                  break;
               case 0x03:
                  effect = EFFECT_EXTENDED;
                  param  = (EXTENDED_FINE_TUNE << 4) + 1;
                  break;
               case 0x04:
                  effect = EFFECT_VIBRATO;
                  param  = (param << 4) + 1;
                  break;
               case 0x05:
                  effect = EFFECT_SET_SPEED;
                  break;
               default:
                  effect = 0;
                  param  = 0;      
                  break;
            }

            if ( insert_tempo && ( !(effect | param) || (track == 7) ) )
            {
               effect         = EFFECT_SET_SPEED;
               param         = header->Tempos[pattern];
               insert_tempo   = 0;
            }
            if ( insert_break && ( !(effect | param) || (track == 7) ) )
            {
               effect         = EFFECT_PATTERN_BREAK;
               param         = 0x00;
               insert_break   = 0;
            }

            if ( note )
               note += 12 * 2;

            m_Patterns[pattern].Row[row].Note[track].Note         = note;
            m_Patterns[pattern].Row[row].Note[track].Sample_Num      = sample;
            m_Patterns[pattern].Row[row].Note[track].Volume         = volume;
            m_Patterns[pattern].Row[row].Note[track].Effect         = effect;
            m_Patterns[pattern].Row[row].Note[track].Effect_Parms   = param;
         }
      }
   }
   
   // Load in the sample data
   for ( int i = 1 ; i <= m_Num_Samples ; i++ )
   {
      m_Samples[i].Data = new BYTE [m_Samples[i].Length + 1];

      if ( m_Samples[i].Length )
         memcpy(&m_Samples[i].Data[0],&data[index],m_Samples[i].Length);

      index += m_Samples[i].Length;

      // Duplicate the last byte, we'll need an extra one in order to safely anti-alias
      int length = m_Samples[i].Length;
      if ( length > 0 )
      {
         m_Samples[i].Data[length] = m_Samples[i].Data[length - 1];
         m_Samples[i].Data[m_Samples[i].Loop_End] = m_Samples[i].Data[m_Samples[i].Loop_Start];
      }

      // convert signed/unsigned
      for ( int j = 0 ; j < m_Samples[i].Length ; j++ )
         m_Samples[i].Data[j] ^= 0x80;
   }

   for ( int track = 0 ; track < m_Num_Tracks ; track++ )
   {
      // set left to right panning
      m_Default_Pan[track] = ( ( track & 1) == 0 ) ? 0 : 255;
   }

   return TRUE;
}

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

BOOL CModPlayer::LoadWOW(BYTE * data,int bytes_read)
{
   int         num_patterns;
   int         row;
   int         note;
   int         i;
   int         index;
   int         num_samples = 31;
   MODHeader *   header = (MODHeader *)data;

   m_Num_Tracks   = 8;
   m_Speed         = 6;
   m_BPM         = 125;

   if ( header->Magic != MOD_MK )
      return FALSE;

   m_Track_Data = new TrackData[m_Num_Tracks];

   // Get the name
   memcpy(m_Song_Name,header->Song_Name,20);
   m_Song_Name[20] = '\0';

   // Read in all the instrument headers - mod files have 31, sample #0 is ignored
   m_Samples = new SampleData [num_samples + 1];
   m_Num_Samples = num_samples;

   memset(m_Samples,0,(m_Num_Samples + 1) * sizeof(SampleData));
   
   for ( i = 1 ; i <= num_samples ; i++ )
   {
      // Read the sample name
      strncpy(m_Samples[i].Name,header->Samples[i - 1].Sample_Name,22);
      m_Samples[i].Name[22] = '\0';
   
      // Read remaining info about sample
      m_Samples[i].Length = ConvertMODWord(header->Samples[i - 1].Length);
      m_Samples[i].Frequency = FineTuneFrequency[header->Samples[i - 1].Fine_Tune];
      m_Samples[i].Volume = header->Samples[i - 1].Volume;
      m_Samples[i].Loop_Start = ConvertMODWord(header->Samples[i - 1].Loop_Start);
      m_Samples[i].Loop_End = m_Samples[i].Loop_Start + ConvertMODWord(header->Samples[i - 1].Loop_Length);
      
      // Fix loop end in case it goes too far
      if ( m_Samples[i].Loop_End > m_Samples[i].Length )
         m_Samples[i].Loop_End = m_Samples[i].Length;
   }

   // Read in song data
   m_Song_Length = header->Num_Orders;
   
   memcpy(m_Orders,header->Orders,128);

   num_patterns = 0;
   for ( i = 0 ; i < m_Song_Length ; i++ )
   {
      if ( m_Orders[i] > num_patterns   )
         num_patterns = m_Orders[i];
   }
   num_patterns++;

   index = sizeof(MODHeader);

   // Load in the pattern data
   m_Patterns = new PatternData [num_patterns];
   m_Num_Patterns = num_patterns;
   for ( i = 0 ; i < num_patterns ; i++ )
   {
      // Loop through each row
      m_Patterns[i].Num_Rows = 64;
      for ( row = 0 ; row < m_Patterns[i].Num_Rows ; row++ )
      {
         // Set the number of notes for this pattern
         m_Patterns[i].Row[row].Note = new NoteData [m_Num_Tracks];

         // Loop through each note
         for ( int track = 0 ; track < m_Num_Tracks ; track++ )
         {
            // Get the 4 bytes for this note
            int b0      = data[index++];
            int b1      = data[index++];
            int b2      = data[index++];
            int b3      = data[index++];
            int sample   = (b0 & 0xF0) | (b2 >> 4);
            int period   = ((b0 & 0x0F) << 8) | b1;
         
            for ( note = 0 ; note < NUM_NOTES ; note++ )
               if ( period >= PeriodToNote[note])
                  break;

            if ( note >= NUM_NOTES )
               note = 0;

            m_Patterns[i].Row[row].Note[track].Sample_Num   = sample;
            m_Patterns[i].Row[row].Note[track].Effect      = b2 & 0x0F;
            m_Patterns[i].Row[row].Note[track].Effect_Parms   = b3;
            m_Patterns[i].Row[row].Note[track].Note         = note;
            m_Patterns[i].Row[row].Note[track].Volume      = m_Samples[sample].Volume;
         }
      }
   }

   // Load in the sample data
   for ( i = 1 ; i <= num_samples ; i++ )
   {
      m_Samples[i].Data = new BYTE [m_Samples[i].Length + 1];

      if ( m_Samples[i].Length )
         memcpy(&m_Samples[i].Data[0], &data[index], m_Samples[i].Length);

      index += m_Samples[i].Length;

      // Duplicate the last byte, we'll need an extra one in order to safely anti-alias
      int length = m_Samples[i].Length;
      if ( length > 0 )
      {
         m_Samples[i].Data[length] = m_Samples[i].Data[length - 1];
         m_Samples[i].Data[m_Samples[i].Loop_End] = m_Samples[i].Data[m_Samples[i].Loop_Start];
      }
   }

   for ( int track = 0 ; track < m_Num_Tracks ; track++ )
   {
      // set left to right panning
      m_Default_Pan[track] = ( ( track & 3) == 0 ) || ( (track & 3) == 3 ) ? 0 : 255;
   }

   return TRUE;
}

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

BOOL CModPlayer::LoadSTM(BYTE * data,int bytes_read)
{
   STMHeader *   header = (STMHeader *)data;
   int         index;

   // Get the name
   memcpy(m_Song_Name,header->Song_Name,20);
   m_Song_Name[20] = '\0';

   m_Num_Tracks   = 4;
   m_Num_Patterns   = header->Num_Patterns;
   m_BPM         = 125;
   m_Speed         = header->Tempo / 16;
   m_Track_Data   = new TrackData[m_Num_Tracks];
   m_Num_Samples   = 31;
   m_Samples      = new SampleData [m_Num_Samples + 1];
   m_Song_Length   = 1;

   memcpy(m_Orders,header->Orders,128);
   while ( m_Orders[m_Song_Length] < 99 )
      m_Song_Length++;

   memset(m_Samples,0,(m_Num_Samples + 1) * sizeof(SampleData));

   for ( int sample = 1 ; sample <= m_Num_Samples ; sample++ )
   {
      memcpy(m_Samples[sample].Name,header->Samples[sample - 1].File_Name,13);

      m_Samples[sample].Length      = header->Samples[sample - 1].Length;
      m_Samples[sample].Loop_Start   = header->Samples[sample - 1].Loop_Start;
      m_Samples[sample].Loop_End      = header->Samples[sample - 1].Loop_End;

      if ( m_Samples[sample].Loop_End > m_Samples[sample].Length )
      {
         if ( m_Samples[sample].Loop_End == 65535 )
         {
            m_Samples[sample].Loop_End = 0;
            m_Samples[sample].Loop_Start = 0;
         }
         else
            m_Samples[sample].Loop_End = m_Samples[sample].Length;
      }

      m_Samples[sample].Volume      = header->Samples[sample - 1].Volume;
      m_Samples[sample].Frequency      = header->Samples[sample - 1].Frequency;
   }

   m_Patterns = new PatternData [m_Num_Patterns];

   index = sizeof(STMHeader);

   for ( int pattern = 0 ; pattern < m_Num_Patterns ; pattern++ )
   {
      m_Patterns[pattern].Num_Rows = 64;
      for ( int row = 0 ; row < m_Patterns[pattern].Num_Rows ; row++ )
      {
         m_Patterns[pattern].Row[row].Note = new NoteData [m_Num_Tracks];
         memset(m_Patterns[pattern].Row[row].Note,0,sizeof(NoteData) * m_Num_Tracks);

         for ( int track = 0 ; track < m_Num_Tracks ; track++ )
         {
            int b0 = data[index++];
            int b1 = data[index++];
            int b2 = data[index++];
            int b3 = data[index++];

            int note = b0;
            
            if ( note >= 251 )
               continue;

            int sample = b1 >> 3;
            int volume = (b1 & 0x07) | ((b2 & 0xF0) >> 2);
            int effect = b2 & 0x0F;
            int param  = b3;
         
            switch ( effect )
            {
               case 0x00:
                  param = 0;
                  break;
               case 0x01:
                  effect = EFFECT_SET_SPEED;
                  param >>= 4;
                  break;
               case 0x02:
                  effect = EFFECT_JUMP_TO_PATTERN;
                  break;
               case 0x03:
                  effect = EFFECT_PATTERN_BREAK;
                  break;
               case 0x04:
                  if ( (param & 0xF0) == 0x00 )
                  {
                     effect = EFFECT_VOLUME_SLIDE;
                     param &= 0x0F;
                  }
                  else if ( (param & 0x0F) == 0x00 )
                  {
                     effect = EFFECT_VOLUME_SLIDE;
                     param &= 0xF0;
                  }
                  else
                  {
                     effect = 0;
                     param  = 0;
                  }
                  break;
               case 0x05:
                  effect = EFFECT_PORTA_DOWN;
                  break;
               case 0x06:
                  effect = EFFECT_PORTA_UP;
                  break;
               case 0x07:
                  effect = EFFECT_PORTA_TO_NOTE;
                  break;
               case 0x08:
                  effect = EFFECT_VIBRATO;
                  break;
               case 0x09:
                  effect = EFFECT_EXTENDED;      /* emu tremor with retrig */
                  param  = (EXTENDED_RETRIG_NOTE << 4) | (param & 0x0F);
                  break;
               case 0x0A:
                  effect = EFFECT_ARPEGGIO;
                  break;
               default:
                  effect = 0;
                  param  = 0;
                  break;
            }

            note = (note & 0x0F) + 12 * (note >> 4);
            
            if ( note )
               note += 2 * 12;

            m_Patterns[pattern].Row[row].Note[track].Note         = note;
            m_Patterns[pattern].Row[row].Note[track].Sample_Num      = sample;
            m_Patterns[pattern].Row[row].Note[track].Volume         = volume;
            m_Patterns[pattern].Row[row].Note[track].Effect         = effect;
            m_Patterns[pattern].Row[row].Note[track].Effect_Parms   = param;
         }
      }
   }

   index = 0x410 + 4 * 64 * 4 * m_Num_Patterns;
   for ( int sample = 1 ; sample <= m_Num_Samples ; sample++ )
   {
      m_Samples[sample].Data = new BYTE [m_Samples[sample].Length + 1];

      if ( m_Samples[sample].Length )
         memcpy(m_Samples[sample].Data,&data[index],m_Samples[sample].Length);

      m_Samples[sample].Data[m_Samples[sample].Loop_End] = m_Samples[sample].Data[m_Samples[sample].Loop_Start];

      index += m_Samples[sample].Length;
   }

   for ( int track = 0 ; track < m_Num_Tracks ; track++ )
   {
      // set left to right panning
      m_Default_Pan[track] = ( (track & 1) == 0 ) ? 0 : 255;
   }

   return TRUE;
}

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

BOOL CModPlayer::LoadMTM(BYTE * data,int length)
{
   MTMHeader *   header = (MTMHeader *)data;
   int         index;
   MTMTrack *   tracks;
   WORD       track_sequence[32];

   if ( (header->Magic & MTM_MASK) != MTM_MAGIC || header->Beats_Per_Track != 64)
      return FALSE;

   // Get the name
   memcpy(m_Song_Name,header->Song_Name,20);
   m_Song_Name[20] = '\0';

   m_Num_Tracks   = header->Num_Chans;
   m_Num_Patterns   = header->Last_Pattern + 1;
   m_BPM         = 125;
   m_Speed         = 6;
   m_Track_Data   = new TrackData[m_Num_Tracks];
   m_Num_Samples   = header->Num_Samples;
   m_Samples      = new SampleData [m_Num_Samples + 1];
   
   memset(m_Samples,0,(m_Num_Samples + 1) * sizeof(SampleData));
   for ( int sample = 1 ; sample <= m_Num_Samples ; sample++ )
   {
      MTMSample * mtm_sample = (MTMSample *)(data + sizeof(MTMHeader) + (sample - 1) * sizeof(MTMSample));

      memcpy(m_Samples[sample].Name,mtm_sample->Sample_Name,22);

      infof("%s\r\n",m_Samples[sample].Name);

      m_Samples[sample].Length      = mtm_sample->Length;
      m_Samples[sample].Loop_Start   = mtm_sample->Loop_Start;
      m_Samples[sample].Loop_End      = mtm_sample->Loop_End;

      if ( m_Samples[sample].Loop_End > m_Samples[sample].Length )
      {
         if ( m_Samples[sample].Loop_End == 65535 )
         {
            m_Samples[sample].Loop_End = 0;
            m_Samples[sample].Loop_Start = 0;
         }
         else
            m_Samples[sample].Loop_End = m_Samples[sample].Length;
      }

      m_Samples[sample].Volume      = mtm_sample->Volume;
      m_Samples[sample].Frequency      = FineTuneFrequency[mtm_sample->Fine_Tune];
   }
   
   // read orders
   index = sizeof(MTMHeader) + m_Num_Samples * sizeof(MTMSample);
   memcpy(m_Orders,data + index,128);
   
   for ( int order = 0 ; order < 128 ; order++ )
      if ( m_Orders[order] < m_Num_Patterns && m_Orders[order] > 0 )
         m_Song_Length = order;
   m_Song_Length++;

   // read tracks
   index += 128;
   tracks = new MTMTrack [header->Num_Tracks];
   memcpy(tracks,data + index,sizeof(MTMTrack) * header->Num_Tracks);

   // read track pattern sequence
   index += sizeof(MTMTrack) * header->Num_Tracks;
   m_Patterns = new PatternData [m_Num_Patterns];
   for ( int pattern = 0 ; pattern < m_Num_Patterns ; pattern++ )
   {
      memcpy(track_sequence,data + index,sizeof(track_sequence));
      index += sizeof(track_sequence);
      m_Patterns[pattern].Num_Rows = 64;
      for ( int row = 0 ; row < m_Patterns[pattern].Num_Rows ; row++ )
      {
         m_Patterns[pattern].Row[row].Note = new NoteData [m_Num_Tracks];
         memset(m_Patterns[pattern].Row[row].Note,0,sizeof(NoteData) * m_Num_Tracks);

         for ( int track = 0 ; track < m_Num_Tracks ; track++ )
         {
            if ( track_sequence[track] )
            {
               int b0 = tracks[track_sequence[track] - 1].Data[row * 3 + 0];
               int b1 = tracks[track_sequence[track] - 1].Data[row * 3 + 1];
               int b2 = tracks[track_sequence[track] - 1].Data[row * 3 + 2];
               
               int note = (b0 >> 2) & 0x3F;
               int sample = ((b0 & 0x03)<<4) + ((b1>>4) & 0x0F);
               int effect = (b1 & 0x0F);
               int param = b2;

               if ( note )
                  note += 2 * 12; // + 1;

               m_Patterns[pattern].Row[row].Note[track].Note = note;
               m_Patterns[pattern].Row[row].Note[track].Sample_Num = sample;
               m_Patterns[pattern].Row[row].Note[track].Effect = effect;
               m_Patterns[pattern].Row[row].Note[track].Effect_Parms = param;
               m_Patterns[pattern].Row[row].Note[track].Volume = m_Samples[sample].Volume;
            }
         }
      }
   }
   index -= sizeof(track_sequence);

   // skip comment
   index += header->Comment_Length;

   // read samples
   for ( int sample = 1 ; sample <= m_Num_Samples ; sample++ )
   {
      m_Samples[sample].Data = new BYTE [m_Samples[sample].Length + 1];

      if ( m_Samples[sample].Length )
         memcpy(m_Samples[sample].Data,&data[index],m_Samples[sample].Length);

      // convert signed/unsigned
      for ( int j = 0 ; j < m_Samples[sample].Length ; j++ )
         m_Samples[sample].Data[j] ^= 0x80;

      m_Samples[sample].Data[m_Samples[sample].Loop_End] = m_Samples[sample].Data[m_Samples[sample].Loop_Start];

      index += m_Samples[sample].Length;
   }

   delete [] tracks;

   for ( int track = 0 ; track < m_Num_Tracks ; track++ )
   {
      // set left to right panning
      m_Default_Pan[track] = header->Pan_Pos[track];
   }

   return TRUE;
}

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

BOOL CModPlayer::LoadXM(BYTE * data,int length)
{
   XMHeader * header = (XMHeader *)data;

   if ( header->Magic != XM_MAGIC )
      return FALSE;

   int index;

   memcpy(m_Song_Name,header->Name,20);
   m_Song_Name[20] = '\0';

   m_Song_Length   = (BYTE)header->Song_Length;
   m_Speed         = header->Tempo;
   m_BPM         = header->BPM;
   m_Num_Samples   = (BYTE)header->Num_Samples;
   m_Num_Tracks   = (BYTE)header->Num_Tracks;
   m_Num_Patterns   = (BYTE)header->Num_Patterns;

   m_Linear_Frequency = header->Flags & 1;

   memcpy(m_Orders,&header->Orders,128);

   m_Track_Data = new TrackData [m_Num_Tracks];
   m_Patterns = new PatternData [m_Num_Patterns];

   int * commands = new int [256];
   int last_command = 0;
   memset(commands,0,256*4);

   index = sizeof(XMHeader);
   for ( int pattern = 0 ; pattern < m_Num_Patterns ; pattern++ )
   {
      XMPatternHeader * pattern_header = (XMPatternHeader *)(data + index);

      m_Patterns[pattern].Num_Rows = pattern_header->Rows;
   
      infof("pattern %i length %i type %i ",pattern,pattern_header->Header_Length,pattern_header->Packing_Type);
      infof("rows %i data size %i\r\n",pattern_header->Rows,pattern_header->Data_Size);

      index += pattern_header->Header_Length;

      if ( pattern_header->Data_Size == 0 )
         continue;

      for ( int row = 0 ; row < pattern_header->Rows ; row++ )
      {
         m_Patterns[pattern].Row[row].Note = new NoteData [m_Num_Tracks];

         for ( int track = 0 ; track < m_Num_Tracks ; track++ )
         {
            int flag         = data[index++];
            int note         = 0;
            int sample         = 0;
            int volume         = 0;
            int effect         = 0;
            int effect_param   = 0;

            if ( flag & 0x80 )
            {
               if ( flag & 0x01 )
                  note = data[index++] - 1;
               if ( flag & 0x02 )
                  sample = data[index++];
               if ( flag & 0x04 )
                  volume = data[index++];
               if ( flag & 0x08 )
                  effect = data[index++];
               if ( flag & 0x10 )
                  effect_param = data[index++];
            }
            else
            {
               note = flag - 1;
               sample = data[index++];
               volume = data[index++];
               effect = data[index++];
               effect_param = data[index++];
            }

            if ( note && volume == 0 )
               volume = 64;

            if ( volume > 0x00 && volume <= 0x50 )
               volume -= 0x10;
            else
            {
            //   if ( volume > 0 )
            //      infof("Volume 0x%0x Effect 0x%0x\r\n",volume,effect);
            //   volume = 0;
            }

            if ( effect_param > 0 )
            {
               commands[effect] = effect_param;
               last_command = effect_param;
            }

            switch ( effect )
            {
               case EFFECT_PORTA_UP:
               case EFFECT_PORTA_DOWN:
               case EFFECT_PORTA_TO_NOTE:
                  if ( effect_param == 0 )
                  {
                     effect_param = commands[effect];
               //      infof("Track=%i Effect=%i Last Command=%i\r\n",track,effect,effect_param);
                  }
               //   else
               //      infof("Track=%i Real Effect=%i Command=%i\r\n",track,effect,effect_param);

                  //effect_param *= 16;
                  break;
               case EFFECT_VIBRATO:
               case EFFECT_PORTA_AND_VOL:
               case EFFECT_VIBRATO_AND_VOL:
               case EFFECT_TREMOLO:
               case EFFECT_VOLUME_SLIDE:
                  if ( effect_param == 0 )
                  {
                     effect_param = commands[track];
                  //   infof("Effect=%i Last Command=%i\r\n",effect,effect_param);
                  }
                  break;

               case EFFECT_EXTENDED:
                  switch ( effect_param >> 4 )
                  {
                     case EXTENDED_FINE_PORTA_UP:
                     case EXTENDED_FINE_PORTA_DOWN:
                     case EXTENDED_FINE_VOLUME_UP:
                     case EXTENDED_FINE_VOLUME_DOWN:
                        if ( effect_param == 0 )
                           effect_param = commands[track];
                        break;
                  }
                  break;
            }

            m_Patterns[pattern].Row[row].Note[track].Note = note;
            m_Patterns[pattern].Row[row].Note[track].Sample_Num = sample;
            m_Patterns[pattern].Row[row].Note[track].Volume = volume;
            m_Patterns[pattern].Row[row].Note[track].Effect = effect;
            m_Patterns[pattern].Row[row].Note[track].Effect_Parms = effect_param;
         }
      }
   }

   delete [] commands;

   m_Samples = new SampleData [m_Num_Samples + 1];
   memset(m_Samples,0,sizeof(SampleData) * (m_Num_Samples + 1));

   for ( int sample = 1 ; sample <= m_Num_Samples ; sample++ )
   {
      int            start_index = index;
      XMInstrument *   instrument = (XMInstrument *)(data + index);
      char         name[23];

      //assertf(instrument->Num_Samples <= 1);
      index += sizeof(XMInstrument);

      strncpy(name,instrument->Name,22);
      name[22] = 0;
      infof("%i Instrument Name %s\r\n",sample,name);

      if ( instrument->Size > 0 && instrument->Num_Samples > 0 && instrument->Num_Samples < 16 )
      {
         XMSampleInfo * sample_info = (XMSampleInfo *)(data + index);

         index += instrument->Size - sizeof(XMInstrument);

         for ( int ins_sample = 0 ; ins_sample < instrument->Num_Samples ; ins_sample++ )
         {   
            XMSampleHeader * header = (XMSampleHeader *)(data + index);

            index += sizeof(XMSampleHeader);

            memcpy(m_Samples[sample].Name,header->Name,22);

            // check for 8 bit or 16 bit sample
            if ( header->Type & 0x10 )
            {
               WORD * sample_data = new WORD [header->Length];
               
               // Have to copy to avoid a memory alignment problem on MIPS
               memcpy(sample_data,data + index,header->Length);

               m_Samples[sample].Length = header->Length / 2;
               m_Samples[sample].Data = new BYTE [m_Samples[sample].Length + 1];

               // convert 16 bit deltas to signed 8 bit
               WORD old_value = 0;
               WORD new_value = 0;
               for ( int j = 0 ; j < m_Samples[sample].Length ; j++ )
               {
                  new_value = sample_data[j] + old_value;
                  m_Samples[sample].Data[j] = (BYTE)(new_value >> 8);
                  old_value = new_value;
               }

               delete [] sample_data;
            }
            else
            {
               m_Samples[sample].Length = header->Length;
               m_Samples[sample].Data = new BYTE [m_Samples[sample].Length + 1];
               memcpy(m_Samples[sample].Data,&data[index],m_Samples[sample].Length);

               // convert deltas to signed 8 bit
               int old_value = 0;
               int new_value = 0;
               for ( int j = 0 ; j < m_Samples[sample].Length ; j++ )
               {
                  new_value = m_Samples[sample].Data[j] + old_value;
                  m_Samples[sample].Data[j] = new_value;
                  old_value = new_value;
               }
            }
   
            m_Samples[sample].Volume      = header->Volume;
            m_Samples[sample].Frequency      = 8363;

      //      if ( header->Type & 0x03 )
            {
               if ( header->Loop_Start > header->Loop_End )
               {
                  m_Samples[sample].Loop_Start   = header->Loop_End;
                  m_Samples[sample].Loop_End      = header->Loop_Start;
               }
               else
               {
                  m_Samples[sample].Loop_Start   = header->Loop_Start;
                  m_Samples[sample].Loop_End      = header->Loop_End;
               }

               if ( m_Samples[sample].Loop_End > m_Samples[sample].Length )
               {
                  if ( m_Samples[sample].Loop_End == 65535 )
                  {
                     m_Samples[sample].Loop_End = 0;
                     m_Samples[sample].Loop_Start = 0;
                  }
                  else
                     m_Samples[sample].Loop_End = m_Samples[sample].Length;
               }
      
               m_Samples[sample].Data[m_Samples[sample].Loop_End] = m_Samples[sample].Data[m_Samples[sample].Loop_Start];
            }

            m_Samples[sample].XM_Fine_Tune = header->Fine_Tune;
            m_Samples[sample].XM_Note = header->Relative_Note;
            m_Samples[sample].Frequency = 8363;

            infof("Fine Tune %i Note %i Type %i Loop Start %i Loop End %i\r\n",
               header->Fine_Tune,header->Relative_Note,header->Type,header->Loop_Start,header->Loop_End);
            infof("%s %i\r\n",m_Samples[sample].Name,m_Samples[sample].Length);

            index += header->Length;
         }
      }
      else
         index += instrument->Size - sizeof(XMInstrument);
   }

   return TRUE;
}


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

// 16-bit word values in a mod are stored in the Motorola Most-Significant-Byte-First format. They're also
// stored at half their actual value, thus doubling their range. This function accepts such a word and
// returns it's integer value.
int CModPlayer::ConvertMODWord(WORD data)
{
   BYTE byte1 = HIBYTE(data);
   BYTE byte2 = LOBYTE(data);
   return (byte2 * 256 + byte1) * 2;
}


int CModPlayer::ConvertWord(BYTE * data,int & index)
{
   BYTE byte1 = data[index++];
   BYTE byte2 = data[index++];
   return byte2 * 256 + byte1;
}

ravenger7
Site Admin
 
Posts: 31
Joined: Sun Nov 16, 2008 1:18 pm
Location: Illinois


Return to Code Snippets

Who is online

Users browsing this forum: No registered users and 1 guest