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

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

#define CHECK_SIZE( add, str ) \
{ \
  if ( convPosL + add > MAX_SONG_LEN ) \
    fatal( "conv: I need more memory. (%s)\n", str ); \
  convPosL += add; \
}

short songError;

byte *song = NULL;

byte songName[ 31 ];
byte songType[ 20 ];
byte songChannels;
byte songInstr;
struct INSTR *songInstrs[ MAX_INSTRUMENTS ];
byte songPlaySpeed;
byte songTempo;
short songGlobalVolume;
short songMaxOrder;
short songOrder[ MAX_ORDER_VALUE ];
short songRestart;
short songMaxPattern;
byte *songPatternPos[ MAX_PATTERN_VALUE ];

double tickDuration;
int ticksPerDivision;

struct VOICE voices[ GUS_CHANNELS ];

byte *convPos;
long convPosL;
byte *convRowPos;
int convRowSize;

static void initInstruments()
{
  short i;

  for ( i = 0; i < MAX_INSTRUMENTS; i++ )
    songInstrs[ i ] = NULL;
}

static void doneInstruments()
{
  short i, j;

  for ( i = 0; i < MAX_INSTRUMENTS; i++ )
    {
      struct INSTR *is;
      
      if ( ( is = songInstrs[ i ] ) != NULL )
        {
          for ( j = 0; j < MAX_SAMPLES; j++ )
            if ( is -> samples[ j ] ) 
              free( is -> samples[ j ] );
          free( is );
        }
      songInstrs[ i ] = NULL;
    }
}

struct INSTR *newInstrument( short instrument )
{
  struct INSTR *result;
  
  result = songInstrs[ instrument ] = 
  			(struct INSTR *)malloc( sizeof( struct INSTR ) );
  if ( !result ) 
    fatal( "newInstrument: not enough memory\n" );
  memset( result, 0, sizeof( struct INSTR ) );
  return result;
}

struct SAMPLE *newSample( short instrument, short sample )
{
  struct SAMPLE *result;
  
  result = songInstrs[ instrument ] -> samples[ sample ] =
  		(struct SAMPLE *)malloc( sizeof( struct SAMPLE ) );
  if ( !result )
    fatal( "newSample: not enough memory\n" );
  memset( result, 0, sizeof( struct SAMPLE ) );
  return result;
}

void convNewSong()
{
  if ( !( song = malloc( MAX_SONG_LEN ) ) )
    fatal( "conv: no enough memory to song" );
  initInstruments();
  convPos = song;
  convPosL = 0;
  songMaxPattern = -1;
  songRestart = 0;
  convRowSize = -1;
}

void convReallocSong()
{
  short i, j;

  if ( !( song = realloc( song, convPosL ) ) )
    fatal( "conv: realloc" );
  for ( i = songMaxOrder; i >= 0; i-- )
    if ( songOrder[ i ] < 0 || songOrder[ i ] > songMaxPattern )
      {
        songMaxOrder--;
        for ( j = i; j < songMaxOrder; j++ )
          songOrder[ j ] = songOrder[ j + 1 ];
      }
    
#if 0
  dprintf( "conv: used %li bytes for song\n", convPosL );
#endif
}

void convClearSong()
{
  doneInstruments();
  free( song );
  song = convPos = NULL;
}

void convInitPattern()
{
  songMaxPattern++;
  if ( songMaxPattern >= MAX_PATTERN_VALUE )
    fatal( "conv: songMaxPattern >= MAX_PATTERN_VALUE" );
  songPatternPos[ songMaxPattern ] = convPos;
#ifdef 0
  dprintf( "pattern = %i, pos = 0x%x\n", songMaxPattern, (int)(convPos - song) );
#endif
  convRowSize = -1;
}

void convDonePattern()
{
  CHECK_SIZE( 2, "convDonePattern" );
  *(convPos++) = LO_BYTE( SROW_TERMINATE );
  *(convPos++) = HI_BYTE( SROW_TERMINATE );
}

void convInitRow()
{
  CHECK_SIZE( 2, "convInitRow" );
  convRowPos = convPos;
  convPos += 2;
  convRowSize = 0;
}

void convDoneRow()
{
  if ( convRowSize < 0 )
    fatal( "conv: convRowSize < 0" );
  *(convRowPos++) = LO_BYTE( convRowSize );
  *(convRowPos++) = HI_BYTE( convRowSize );
  convRowSize = -1;
}

void convAddNote( byte channel, byte instr, byte note, byte volume, 
                  byte effect1, byte arg1,
                  byte effect2, byte arg2 )
{
  byte win = 0;
  byte *pos;
  short new_size;

#ifdef 0
  if ( effect != 0 )
    dprintf( "conv: eff: %02x/%02x\n", effect, arg );
#endif
/*
  if ( instr != 255 && instr >= songInstr )
    fatal( "conv: add note - instrument out of range (%i)", instr );
  if ( instr != 255 && instr > 0 && !songInstrs[ instr ].used )
    fatal( "conv: add note - bad instrument (%i)", instr );
*/
  if ( channel > songChannels )
    fatal( "conv: add note - channel out of range (%i)", channel );
  if ( effect1 > 0x70 )
    fatal( "conv: effect1 > 0x70 (0x%x)\n", effect1 );
  if ( effect2 > 0x70 )
    fatal( "conv: effect2 > 0x70 (0x%x)\n", effect2 );
#ifdef 0
  dprintf( "conv: add note - ch %02i, in %02i, note: %04x, eff: %02x, arg: %02x\n",
           channel, instr, note, effect1, arg1 );
#endif
  
  new_size = ( instr != 255 ) + 
             ( note != 255 ) + 
             ( volume != 255 ) +
             ( effect1 != 0 ) + 
             ( arg1 != 0 ) +
             ( effect2 != 0 ) +
             ( arg2 != 0 );
  if ( new_size <= 0 ) return;		/* no new events */ 
  CHECK_SIZE( 1 + 1 + new_size, "convAddNote" );
  convRowSize += 1 + 1 + new_size;
  pos = convPos + 2;
  if ( instr != 255 ) { win |= 1; *pos++ = instr; }
  if ( note != 255 ) { win |= 2; *pos++ = note; }
  if ( volume != 255 ) { win |= 4; *pos++ = volume; }
  if ( effect1 != 0 ) { win |= 0x10; *pos++ = effect1; }
  if ( arg1 != 0 ) { win |= 0x20; *pos++ = arg1; }
  if ( effect2 != 0 ) { win |= 0x40; *pos++ = effect2; }
  if ( arg2 != 0 ) { win |= 0x80; *pos++ = arg2; }
  *convPos++ = channel & 0x1f;
  *convPos = win;
#ifdef 0
  if ( ( convRowPos - song ) == 0x3ba )
    dprintf( "add ok (conv), convPos = 0x%x, win = 0x%x, chn = 0x%x\n", (int)(convPos - 1 - song), win, channel & 0x1f );
#endif
  convPos = pos;
}
