/* MOD Info */

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

#include <exec/types.h>

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

#define VERBOSE 0

//*************************************
// known file formats:
#define PT_UNKNOWNFORMAT 0
#define PT_MOD4CH        (PT_UNKNOWNFORMAT + 1)    // "M!K!"
#define PT_MOD6CH        (PT_UNKNOWNFORMAT + 2)    // "6CHN"
#define PT_MOD8CH        (PT_UNKNOWNFORMAT + 3)    // "8CHN"

//*************************************
// original Protracker's file format structures
// used by loader only

typedef struct PT_Sample {             // sample info
  char           SampleName[22];
  unsigned short SampleLength;
  unsigned char  FineTune;
  unsigned char  Volume;
  unsigned short LoopStart;
  unsigned short LoopLength;
} PT_Sample;

typedef struct PT_ModHeader {          // mod header (1084 Bytes long)
  char             ModName[20];
  struct PT_Sample Instruments[31];
  unsigned char    SongLength;
  unsigned char    RestPos;
  unsigned char    PatternOrder[128];
  char             ModID[4];           // "M!K!", "6CHN" or "8CHN"
} PT_ModHeader;

//*************************************
// global module descriptors

typedef struct PT_SampleInfo {    // info about instruments
  BYTE   SampleName[24];          // name of sample (22 characters)
  BYTE  *SampleData;              // ptr to samples
  ULONG  SampleLength;            // length of sample
  UBYTE  FineTune;                // finetune
  UBYTE  Volume;                  // volume
  ULONG  LoopStart;               // start of loop
  ULONG  LoopLength;              // length of loop
} PT_SampleInfo;

typedef struct PT_Note {          // notes in patterns
  UBYTE Sample;                   // sample 1 - 31
  UBYTE Note;                     // note 0 - ?   (periods[Note])
  UBYTE Effect;                   // effect
  UBYTE EffData;                  // effect data
} PT_Note;

typedef struct PT_Pattern {       // patterns
  UBYTE           Channels;       // # of channels
  UBYTE           Lines;          // # of lines in pattern
  struct PT_Note *Notes;          // ptr to notes (size = Channels * Lines * 
} PT_Pattern;                     // * sizeof(struct PT_Note))

typedef struct PT_PlayerCtrl {    // pointers initialized by PT_InitPlayer()
  LONG player;
};

typedef struct PT_ModInfo {               // global song structure
  struct PT_PlayerCtrl *PlayerCtrl;       // ptr to player's data
  BYTE                  SongName[22];     // module name (20 chars)
  struct PT_SampleInfo  Instruments[31];  // instruments info
  UBYTE                 SongLength;       // length of song in patts (max 127)
  UBYTE                 RestPos;          // restart position
  UBYTE                 MaxChannels;      // maximum of channels used in module
  UBYTE                 Patterns;         // # of patterns (max 127)
  UBYTE                 PatternOrder[128];// pattern order
  struct PT_Pattern     PatternData[128];    // ptrs to patterns
} PT_ModInfo;

//*************************************
// allocates memory for notes stored in "pattern"
// reads # of channels and # lines from specified pattern,
// so they MUST be inicialized!

BOOL PT_AllocPatternData(struct PT_ModInfo *mi, LONG pattern)
{
  LONG pattsize = 0;

  if (mi == NULL) return FALSE;

  mi->PatternData[pattern].Notes = NULL;
  pattsize = mi->PatternData[pattern].Channels * mi->PatternData[pattern].Lines * sizeof(struct PT_Note);
  mi->PatternData[pattern].Notes = (struct PT_Note *) malloc(pattsize);

  if (mi->PatternData[pattern].Notes == NULL) return FALSE;

  memset((void *) mi->PatternData[pattern].Notes, 0, pattsize); // clear 'em
  return TRUE;
}

//*************************************
// try to identify file format

LONG CheckMODFormat(FILE *fi)
{
  char id[40];

  if (fi == NULL) return FALSE;

  memset(id, 0, 40);
 
  if (fseek(fi, 1080, SEEK_SET) != 0) // seek to place, where PT stores IDs
    return (PT_UNKNOWNFORMAT);        // but if we can't
  fread(id, 1, 4, fi);        // and read them

  if (strncmp(id, "M.K.", 4) == 0)      return PT_MOD4CH;
  else if (strncmp(id, "6CHN", 4) == 0) return PT_MOD6CH;
  else if (strncmp(id, "8CHN", 4) == 0) return PT_MOD8CH;
  else return PT_UNKNOWNFORMAT;
}

//*************************************
// periods for finetune 0
UWORD periodtable[] = { 0, 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};

// "precomputed" note strings
BYTE *notes[] = {"---", // no note
                 "C-1","C#1","D-1","D#1","E-1","F-1","F#1","G-1","G#1", "A-1", "B-1", "H-1",
                 "C-2","C#2","D-2","D#2","E-2","F-2","F#2","G-2","G#2", "A-2", "B-2", "H-2",
                 "C-3","C#3","D-3","D#3","E-3","F-3","F#3","G-3","G#3", "A-3", "B-3", "H-3"};

//*************************************
// extract note info from original PT junk
// PT format is:
// ssss-pppp|pppppppp|ssss-eeee|dddddddd
// where: s = sample #
//        p = period
//        e = effect
//        d = effect's data

void ExtractNote(struct PT_Note *enote, ULONG *note)
{
  ULONG tmp = 0;
  int i;

  if (enote == NULL || note == NULL) return;

  // get instrument #
  tmp = ((*note >> 24) & 0xF0UL) + ((*note >> 12) & 0x0FUL);
  enote->Sample = tmp;
  
  // get note #
  tmp = (*note >> 16) & 0xFFFUL;
  for (i = 0; i < 38; i++) {
    if (periodtable[i] == tmp) { tmp = i; break; }
  }
  enote->Note = tmp;

  // get effect
  tmp = (*note >> 8) & 0x0FUL;
  enote->Effect = tmp;
  
  // get effect data
  tmp = *note & 0xFFUL;
  enote->EffData = tmp;
}

//*************************************
// loads Protracker's "M!K!" modules, and 
// Fast Trackers' "6CHN" and "8CHN" modules
// allocates PT_ModInfo structure and loads module in 
// (if it is possible :-))

struct PT_ModInfo *PT_LoadMOD(FILE *fi, LONG type)
{
  struct PT_ModHeader  ptmh;
  struct PT_ModInfo   *ptmi = NULL;
  ULONG *pattbuff = NULL;
  int i, j, tmp;
  int chans = 4;
  BYTE *samples = NULL;
  ULONG sampleslength = 0;

#if VERBOSE
  printf("nahravam PT4 ...\n");
#endif

  if (fi == NULL) return NULL;

  // how many channels...
  if (type == PT_MOD4CH)      chans = 4; 
  else if (type == PT_MOD6CH) chans = 6; 
  else if (type == PT_MOD8CH) chans = 8;
  else return NULL;

  // get buffer for patterns readed from module
  if ((pattbuff = (ULONG *) malloc(chans * 64 * 4)) == NULL) return NULL;

  // load module header
  fseek(fi, 0, SEEK_SET);
  if (fread((char *) &ptmh, sizeof(struct PT_ModHeader), 1, fi) != 1) {
    free(pattbuff);
    fclose(fi);
    return NULL;
  } 

#if VERBOSE
  printf("nacten pt header\n");
#endif

  // alloc module info structure and clear it
  if ((ptmi = (struct PT_ModInfo *) malloc(sizeof(struct PT_ModInfo))) == NULL) {
    free(pattbuff);
    return NULL;
  }
  memset((void *) ptmi, 0, sizeof(struct PT_ModInfo));

#if VERBOSE
  printf("ptmi alokovano\n");
#endif

  // get song name
  memcpy((void *) ptmi->SongName, (void *) ptmh.ModName, 20);

#if VERBOSE
  printf("song name zkopirovano\n");
#endif

  //get instrumemts (without data)
  for (i = 0; i < 31; i++) {
    memcpy((void *) ptmi->Instruments[i].SampleName, 
           (void *) ptmh.Instruments[i].SampleName, 22);
    ptmi->Instruments[i].SampleData   = NULL;
    ptmi->Instruments[i].SampleLength = ptmh.Instruments[i].SampleLength << 1;
    ptmi->Instruments[i].FineTune     = ptmh.Instruments[i].FineTune;
    ptmi->Instruments[i].Volume       = ptmh.Instruments[i].Volume;
    ptmi->Instruments[i].LoopStart    = ptmh.Instruments[i].LoopStart << 1;
    ptmi->Instruments[i].LoopLength   = ptmh.Instruments[i].LoopLength << 1;

    sampleslength += ptmi->Instruments[i].SampleLength;
  }

#if VERBOSE
  printf("instrumenty1 inicializovany\n");
#endif

  // get song info
  ptmi->SongLength  = ptmh.SongLength;
  ptmi->RestPos     = ptmh.RestPos;
  ptmi->MaxChannels = chans;

#if VERBOSE
  printf("song info ok\n");
#endif

  // get pattern order
  memcpy((void *) ptmi->PatternOrder, (void *) ptmh.PatternOrder, 128);

  // get # of patterns
  tmp = 0;
  for (i = 0; i < 128; i++) {
    if (tmp < ptmi->PatternOrder[i]) tmp = ptmi->PatternOrder[i];
  }
  ptmi->Patterns = tmp + 1;

#if VERBOSE
  printf("pattern info nacteno\n");
#endif

  // read and extract patterns
  fseek(fi, 1084, SEEK_SET);
  for (i = 0; i < ptmi->Patterns; i++) {
    ptmi->PatternData[i].Channels = chans;
    ptmi->PatternData[i].Lines = 64;
    if (PT_AllocPatternData(ptmi, i) == FALSE) {
      for (tmp = 0; tmp < i; tmp++) free((void *) ptmi->PatternData[tmp].Notes);
      free(pattbuff);
      free((void *) ptmi); 
      fclose(fi);
      return NULL;
    }

    if (fread((char *) pattbuff, 4, chans * ptmi->PatternData[i].Lines, fi) != chans * ptmi->PatternData[i].Lines) {
      for (tmp = 0; tmp < i; tmp++) free((void *) ptmi->PatternData[tmp].Notes);
      free(pattbuff);
      free((void *) ptmi); 
      return NULL;
    }

    for (j = 0; j < (chans * ptmi->PatternData[i].Lines); j++) {
      ExtractNote(&ptmi->PatternData[i].Notes[j], &pattbuff[j]);
    }
  }

#if VERBOSE
  printf("patterny nacteny\n");
#endif

    // read samples
//    fseek(fi, 1084 + (ptmi->Patterns * 1024), SEEK_SET);
  if ((samples = (UBYTE *) malloc(sampleslength)) != NULL) {
    if (fread((char *) samples, 1, sampleslength, fi) == sampleslength) {
      sampleslength = 0;
      for (i = 0; i < 31; i++) {
         ptmi->Instruments[i].SampleData = (BYTE *) (samples + sampleslength);
         sampleslength += ptmi->Instruments[i].SampleLength;
      }
    }
    else {
      free((void *) samples);
      for (i = 0; i < ptmi->Patterns; i++) free((void *) ptmi->PatternData[i].Notes);
      free(pattbuff);
      free((void *) ptmi); 
      return NULL;
    }
  }
  else {
    for (i = 0; i < ptmi->Patterns; i++) free((void *) ptmi->PatternData[i].Notes);
    free(pattbuff);
    free((void *) ptmi); 
    return NULL;
  }

#if VERBOSE
  printf("samples nacteny\n");
#endif

  free(pattbuff);
  return ptmi;
}

//*************************************
// loads song

struct PT_ModInfo *PT_LoadSong(UBYTE *name)
{
  FILE *fi = NULL;
  struct PT_ModInfo   *ptmi = NULL;
  int tmp;

#if VERBOSE
  printf("nahravam...\n");
#endif

  if (name == NULL) return NULL;

#if VERBOSE
  printf("name zadano\n");
#endif

  if ((fi = fopen(name, "rb")) != NULL) {

#if VERBOSE
  printf("soubor otevren\n");
#endif

    // check format
    if ((tmp = CheckMODFormat(fi)) != PT_UNKNOWNFORMAT) {
      switch (tmp) {
      case PT_MOD4CH :
      case PT_MOD6CH :
      case PT_MOD8CH :
        ptmi = PT_LoadMOD(fi, tmp);
        break;
      
      default :
        fclose(fi);
        return NULL;
      }
    }
    else { fclose(fi); return NULL; }

    fclose(fi);
    return ptmi;
  }
  else {
    return NULL;
  }
}

//*************************************
// frees all memory allocated by PT_LoadSong()

void PT_UnloadSong(struct PT_ModInfo *song)
{
  int i;
  BYTE *samples = NULL;

  // don't free nothink
  if (song == NULL) return;

  // free samples
  samples = song->Instruments[0].SampleData;
  if (samples != NULL)
    free(samples);

#if VERBOSE
printf("Samples uvolneny OK!\n");
#endif

  // free patterns
  for (i = 0; i < song->Patterns; i++) { 
    if (song->PatternData[i].Notes != NULL)
      free((void *) song->PatternData[i].Notes);
  }

#if VERBOSE
printf("Patterns uvolneny OK!\n");
#endif

  // free song
  free((void *) song); 

#if VERBOSE
printf("Song uvolnen OK!\n");
#endif
}

//*************************************
// prints pattern (it's very slow!)

void PrintPattern(struct PT_ModInfo *song, int pattern)
{
  int i, j, count;

  if (song == NULL) return;

  printf("Pattern %d:\n", pattern);
  count = 0;
  for (i = 0; i < song->PatternData[pattern].Lines * song->PatternData[pattern].Channels; i += song->PatternData[pattern].Channels) {
    printf("%03d ", count);
    for (j = 0; j < song->PatternData[pattern].Channels; j++) {
      printf("%s %02d %01X%02X  ", 
             notes[song->PatternData[pattern].Notes[i + j].Note],
             song->PatternData[pattern].Notes[i + j].Sample,
             song->PatternData[pattern].Notes[i + j].Effect,
             song->PatternData[pattern].Notes[i + j].EffData);      
    }
    printf("\n");
    count++;
  }
}

void PrintSampleInfo(struct PT_ModInfo *song, int sample)
{
  if (song == NULL) return;

  printf("Sample %d \"%s\"\n", sample, song->Instruments[sample].SampleName); 
  printf("  Length %06d, FineTune %02d, Volume %02d, LoopStart %06d, LoopLength %06d\n", 
            song->Instruments[sample].SampleLength,
            song->Instruments[sample].FineTune,
            song->Instruments[sample].Volume,
            song->Instruments[sample].LoopStart,
            song->Instruments[sample].LoopLength);
}

void PrintPatternOrder(struct PT_ModInfo *song)
{
  int i;

  if (song == NULL) return;

  printf("Pattern order:\n--------------\n"); 
  for (i = 0; i < 128; i += 8) {
    printf("  %03d, %03d, %03d, %03d, %03d, %03d, %03d, %03d\n", 
           song->PatternOrder[i],
           song->PatternOrder[i+1],
           song->PatternOrder[i+2],
           song->PatternOrder[i+3],
           song->PatternOrder[i+4],
           song->PatternOrder[i+5],
           song->PatternOrder[i+6],
           song->PatternOrder[i+7]);
  }
}

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

struct PT_ModInfo *module = NULL;

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

void exitus(int err)
{
  printf("\nBye, bye!\n");

  exit(err);
}

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

int main(int argc, char *argv[])
{
  int i;

  printf("Mod Info - (C) 2000 ENIF ^ hLA\n");

  if (argc <= 1) { printf("Suxx! Right iz: modinfo <phile>\n"); exitus(10); }

  if ((module = PT_LoadSong(argv[1])) == NULL) {
    printf("Song not loaded!\n");
    exitus(20);
  }
 
  printf("\nSong info:\n----------\n");  
  printf("Module name:    \"%s\".\n", module->SongName);
  printf("Song length:    %d\n", module->SongLength);
  printf("Rest. position: %d\n", module->RestPos);
  printf("Channels:       %d\n", module->MaxChannels);
  printf("Patterns        %d\n\n", module->Patterns);

  printf("Samples info:\n-------------\n");
  for (i = 0; i < 31; i++) {
    PrintSampleInfo(module, i);
  }
  printf("\n");

  PrintPatternOrder(module);
  printf("\n");

  printf("Patterns list:\n--------------\n");
  for (i = 0; i < module->Patterns - 1; i++) {  
    PrintPattern(module, i);
    printf("\n");
  }
  PrintPattern(module, module->Patterns - 1);

#if VERBOSE
printf("End of pattern.\n");
#endif

  PT_UnloadSong(module);
  exitus(0);
}
