/*
 *  Copyright (c) by Jaroslav Kysela (Perex soft)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pmod.h"
#include "mod_effects.h"

#define PAN_LEFT	3
#define PAN_RIGHT	( 15 - PAN_LEFT )

#define DEF_M_K_	(('M'<<24)|('.'<<16)|('K'<<8)|'.')
#define DEF_M_K_1	(('M'<<24)|('!'<<16)|('K'<<8)|'!')
#define DEF_M_K_2	(('M'<<24)|('&'<<16)|('K'<<8)|'!')
#define DEF_FLT4	(('F'<<24)|('L'<<16)|('T'<<8)|'4')
#define DEF_FLT8	(('F'<<24)|('L'<<16)|('T'<<8)|'8')
#define DEF_CD81	(('C'<<24)|('D'<<16)|('8'<<8)|'1')
#define DEF_OCTA	(('O'<<24)|('C'<<16)|('T'<<8)|'A')
#define DEF_CHN6	(('6'<<24)|('C'<<16)|('H'<<8)|'N')
#define DEF_CHN8	(('8'<<24)|('C'<<16)|('H'<<8)|'N')
#define DEF_CHXX	(('X'<<24)|('X'<<16)|('C'<<8)|'H')

static byte modPan[][ 32 ] = {
{ PAN_LEFT, PAN_RIGHT, PAN_RIGHT, PAN_LEFT },
{ PAN_LEFT, PAN_LEFT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_LEFT },
{ PAN_LEFT, PAN_LEFT, 
  PAN_RIGHT, PAN_RIGHT, 
  PAN_RIGHT, PAN_RIGHT, 
  PAN_LEFT, PAN_LEFT },
{ PAN_LEFT, PAN_LEFT, 
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT,
  PAN_RIGHT, PAN_RIGHT, 
  PAN_LEFT, PAN_LEFT, PAN_LEFT },
{ PAN_LEFT, PAN_LEFT, PAN_LEFT,
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT,
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT,
  PAN_LEFT, PAN_LEFT, PAN_LEFT },
{ PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, 
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, 
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, 
  PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT },
{ PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT,
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT,
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT,
  PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT },
{ PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT,
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT,
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT,
  PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT },
{ PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT,
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT,
  PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT, PAN_RIGHT,
  PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT, PAN_LEFT }
};

static word effTbl[ 32 ] = {
EFF_ARPEG,
EFF_SLIDE_UP,
EFF_SLIDE_DOWN,
EFF_SLIDE_TO,
EFF_VIBRATO,
EFF_PORT_AND_VOL,
EFF_VIBRA_AND_VOL,
EFF_TREMOLO,
EFF_SET_PAN_255,
EFF_MOD_SET_OFFSET,
EFF_VOL_SLIDE,
EFF_PATTERN_JUMP,
EFF_VOLUME,
EFF_PATTERN_BREAK,
0x7f,		/* wrong - EXTENDED */
EFF_MOD_SPEED,
EFF_FILTER,
EFF_FINE_SLIDE_UP,
EFF_FINE_SLIDE_DOWN,
EFF_GLISSANDO,
EFF_VIBRATO_WAVE,
EFF_FINETUNE,
EFF_PATTERN_LOOP,
EFF_TREMOLO_WAVE,
EFF_SET_PAN,
EFF_RETRIGGER,
EFF_FINE_VOL_UP,
EFF_FINE_VOL_DOWN,
EFF_CUT_NOTE,
EFF_DELAY_NOTE,
EFF_PATTERN_DELAY,
0x7f
};

static word amigaVals[ 84 ] = {
3424,3232,3048,2880,2712,2560,2416,2280,2152,2032,1920,1812,
1712,1616,1524,1440,1356,1280,1208,1140,1076,1016, 960, 906, /* C-0 to B-0 : +0 */
 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, /* C-1 to B-1 : +0 */
 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, /* C-2 to B-2 : +0 */
 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113, /* C-3 to B-3 : +0 */
 107, 101,  95,  90,  85,  80,  75,  71,  67,  63,  60,  56, /* C-4 to B-4 : +0 */
  53,  50,  47,  45,  42,  40,  37,  35,  33,  31,  30,  28,
};

#ifdef COMPUTE_MIN_AND_MAX_PERIOD
static int minSongPeriod;
static int maxSongPeriod;
#endif

byte scanNote( word period )
{
  short i;

  if ( period < MIN_PERIOD_AMIGA ||
       period > MAX_PERIOD_AMIGA )
    {
      wprintf( "scanNote: amiga period out of range (%i)\n", period );
      return 255;
    }
#ifdef COMPUTE_MIN_AND_MAX_PERIOD
  if ( period < minSongPeriod ) minSongPeriod = period;
  if ( period > maxSongPeriod ) maxSongPeriod = period;
#endif
  for ( i = 0; i < 84; i++ )
    if ( period > ( ( amigaVals[ i ] + amigaVals[ i + 1 ] ) >> 1 ) )
      return 12 + i;
  return 12 + 73;
}

void translateModEffect( byte *eff, byte *arg )
{
  if ( *eff == MEF_EXTENDED )
    {
      *eff = 0x10 | ( *arg >> 4 );
      *arg &= 0x0f;
    }
  *eff = effTbl[ *eff ];
  /* little correction */
  if ( *arg == 0 && 
       ( *eff == EFF_ARPEG || *eff == EFF_VOL_SLIDE ) ) *eff = 0;
}

void loadMOD( FILE *file, char *filename )
{
  byte rbuf[ 16 ];
  byte *header;
  byte *pos;
  dword x;
  long fpos;
  int iok;
  byte lastPattern;

  pprintf( "Loading MOD file %s... ", filename );
  fflush( stdout );

  if ( ( header = malloc( 1084 ) ) == NULL )
    fatal( "loadMOD: header alloc" );
  if ( fread( header, 1084, 1, file ) != 1 )
    fatal( "loadMOD: header read" );

  strncpy( songName, header, 20 );
  songName[ 20 ] = 0;
  strcpy( songType, "MOD" );

  x = ( header[ 1080 ] << 24 ) | ( header[ 1081 ] << 16 ) |
      ( header[ 1082 ] << 8 ) | header[ 1083 ];
  songChannels = 4;
  songInstr = 31;
  pos = header + 20;
#if 0
  dprintf( "\nx: %c%c%c%c\n", ( x >> 24 ) & 0xff, ( x >> 16 ) & 0xff, ( x >> 8 ) & 0xff, x & 0xff );
#endif  
  if ( header[ 1082 ] == 'C' && header[ 1083 ] == 'H' )
    {
      rbuf[ 0 ] = header[ 1080 ];
      rbuf[ 1 ] = header[ 1081 ];
      rbuf[ 2 ] = 0;
      sscanf( rbuf, "%i", &iok );
      if ( iok < 2 || iok > 32 )
        fatal( "loadMOD: songChannels out of range (%d)\n", iok );
      songChannels = iok;
      x = DEF_CHXX;
#if 0
      if ( freqType == FTYPE_NOTHING ) freqType = FTYPE_S3M;
#endif
    }
  switch ( x ) {
    case DEF_M_K_:
#if 0
      if ( freqType == FTYPE_NOTHING ) freqType = FTYPE_S3M;
      break;
#endif
    case DEF_M_K_1:
    case DEF_M_K_2:
    case DEF_FLT4:
    case DEF_CHXX:	break;
    case DEF_CHN6:	songChannels = 6; break;
    case DEF_FLT8:
    case DEF_CD81:
    case DEF_OCTA:
    case DEF_CHN8:	songChannels = 8; break;
    default:
#if 0
      if ( freqType == FTYPE_NOTHING ) freqType = FTYPE_S3M;
#endif
      songInstr = 15;
      fseek( file, 600, SEEK_SET );
  }
  if ( freqType == FTYPE_NOTHING ) freqType = FTYPE_LINEAR;
  for ( x = 0; x < songInstr; x++ )
    {
      struct INSTR *is = newInstrument( x );
      struct SAMPLE *smp = newSample( x, 0 );
    
      strncpy( is -> name, pos, 22 ); is -> name[ 22 ] = 0; 
      strncpy( smp -> name, pos, 22 ); smp -> name[ 22 ] = 0;
      pos += 22;
      smp -> length   = (word)*(pos++) << 9;
      smp -> length  |= *(pos++) << 1;
      smp -> freqC4 = palFreq ? PAL_FREQ : NTSC_FREQ;
      smp -> finetune = finetuneTable[ *(pos++) & 0x0f ];
      smp -> volume   = *(pos++);
      smp -> lstart   = (word)*(pos++) << 9;
      smp -> lstart  |= *(pos++) << 1;
      smp -> lend     = (word)*(pos++) << 9;
      smp -> lend    |= *(pos++) << 1;
      if ( smp -> lend < 4 )
        smp -> lstart = smp -> lend = 0;
       else
        smp -> lend = smp -> lstart + smp -> lend;
      smp -> relativeNote = 0;
      smp -> type     = 0;
      smp -> ident    = -1;
      is -> used = smp -> used = smp -> length > 4;
    }  
  songMaxOrder = *(pos++) - 1;
#ifdef 0
  dprintf( "Song length: %i\n", patterns );
  dprintf( "Little byte: %i\n", *(pos++) );
  dprintf( "Pattern order:\n" );
#else
  pos++;
#endif
  fpos = ftell( file ); 
  for ( lastPattern = x = 0; x <= songMaxOrder; x++, pos++ )
    {
      if ( lastPattern < *pos ) lastPattern = *pos;
      songOrder[ x ] = *pos;
    }
#ifdef COMPUTE_MIN_AND_MAX_PERIOD
  minSongPeriod = MAX_PERIOD_AMIGA;
  maxSongPeriod = MIN_PERIOD_AMIGA;
#endif
  for ( x = 0; x <= lastPattern; x++ )
    {
      int row, chn;

      convInitPattern();
      for ( row = 0; row < 64; row++ )
        {
          convInitRow();
          for ( chn = 0; chn < songChannels; chn++ )
            {
              byte instr = 255, note = 255, volume = 255, eff = 0, arg = 0;
            
              if ( fread( rbuf, 4, 1, file ) != 1 )
                fatal( "loadMOD: note read\n" );
              if ( ( rbuf[ 2 ] & 0xf0 ) != 0 || ( rbuf[ 0 ] & 0xf0 ) != 0 )
                instr = ( ( rbuf[ 0 ] & 0xf0 ) | ( ( rbuf[ 2 ] >> 4 ) & 0x0f ) ) - 1;
              if ( ( rbuf[ 0 ] & 0x0f ) != 0 || rbuf[ 1 ] )
                note = scanNote( ( ( (int)rbuf[ 0 ] & 0x0f ) << 8 ) | rbuf[ 1 ] );
              if ( rbuf[ 2 ] & 0x0f )
                eff = rbuf[ 2 ] & 0x0f;
              arg = rbuf[ 3 ];
              
              if ( !instr && !note && !eff && !arg ) continue;
              
              if ( instr != 255 && 
                   ( instr >= songInstr || !songInstrs[ instr ] -> used ) )
                continue;
              
	      if ( eff == MEF_VOLUME )
		{
		  volume = arg;
		  if ( volume == 255 ) volume--;
		  eff = arg = 0;
	    	}
	    	
	      translateModEffect( &eff, &arg );
              if ( eff == 0x7f )
                dprintf( "\nloadMOD: wrong or unimplemented effect (0x%x/0x%x)\n", 
	                   	rbuf[ 2 ] & 0x0f, rbuf[ 3 ] );
              convAddNote( chn,
                           instr,
                           note,
                           volume,
                           eff,
                           arg,
                           0, 0 );
            }
          convDoneRow();
        }
      convDonePattern();
    }
#ifdef COMPUTE_MIN_AND_MAX_PERIOD
  dprintf( "\nmin = %d, max = %d\n", minSongPeriod, maxSongPeriod );
#endif
  lastPattern++;
#ifdef 0
  dprintf( "\n" );
  dprintf( "Position of samples: %08x\n", (int)( fpos + 64 * 4 * songChannels * lastPattern ) );
#endif
  free( header );
  fseek( file, fpos + 64 * 4 * songChannels * lastPattern, SEEK_SET ); 
#ifdef 0
  dprintf( "Downloading samples:\n" );
#endif
  iok = 0;
  if ( songChannels < 10 )
    {
      switch ( songChannels ) {
        case 32: 	iok++;
        case 28:	iok++;
        case 24:	iok++;
        case 20:	iok++;
        case 16:	iok++;
        case 12:	iok++;
        case 10:	iok++;
        case  8:	iok++;
        case  6:	iok++;
        case  4:	break;
        default:
          fatal( "load_mod: unknown default pan positions\n" );
      }
      for ( x = 0; x < 32; x++ )
        voices[ x ].pan = modPan[ iok ][ x ];
    }
   else
    {
      for ( x = 0; x < 32; x++ )
        voices[ x ].pan = x & 4 ? PAN_RIGHT : PAN_LEFT;
    }

  fpos = 0;
  gusSetupChannels();
  for ( x = 0; x < songInstr; x++ )
    if ( songInstrs[ x ] && songInstrs[ x ] -> used ) 
      {
#ifdef 0
        dprintf( "%02i  ", (int)x );
#endif
        gusDownload( file, x, 0 );
        if ( songInstrs[ x ] -> samples[ 0 ] -> length ) 
          songInstrs[ x ] -> samples[ 0 ] -> ident = fpos++;
         else
          songInstrs[ x ] -> used = songInstrs[ x ] -> samples[ 0 ] -> used = 0;
      }
#ifdef 0
  dprintf( "\n" );
#endif
  pprintf( "Done.\n" );
#ifdef 0
  dprintf( "*** GUS memory remaining %i bytes ***\n", gusMemFree() );
#endif
  songPlaySpeed = 6;
  songTempo = 125;
  songGlobalVolume = 128;
  currentPalFreq = palFreq;
}
