/*
 *  load_mtm.c - Loads Multitracker modules.
 *
 *  (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/ultrasound.h>
#include <limits.h>
#include <string.h>
#include <ctype.h>

#include "mod.h"

/* External global variables */

SEQ_DECLAREBUF();
extern int seqfd, gus_dev;

extern struct mod_info M;
extern struct options opt;

extern char quit;

extern char effect_used[NR_EFX];

/* Local functions */

static int process_header(int);
static int read_patterndata(int);
static void fix_effect(unsigned char *, unsigned char *);

/* Defines and variables used while loading module */

#define MTM_HEADER_SIZE     66 
#define MTM_SAMPLEINFO_SIZE 37

#define MTM_MAGIC          0
#define MTM_VERSION        3
#define MTM_NAME           4
#define MTM_TRACKS        24
#define MTM_PATTERNS      26
#define MTM_SONGLENGTH    27
#define MTM_COMMENT_LEN   28
#define MTM_NOS           30
#define MTM_UNUSEDATTR    31
#define MTM_BPT           32
#define MTM_PLAYEDTRACKS  33
#define MTM_PANPOS        34

#define MTM_S_NAME         0
#define MTM_S_LEN         22
#define MTM_S_LOOPSTART   26
#define MTM_S_LOOPEND     30
#define MTM_S_FINETUNE    34
#define MTM_S_VOLUME      35
#define MTM_S_ATTR        36

#define MTM_FLAG_BITS_16   1

static int expected_length, tracks, commentlen;

int load_mtm(int fd)
{
    char tmpbuf[80];

    int i, diff, fatalerr;

    print_status("Loading MTM");
    M.nr_tracks=0;

    if(!process_header(fd))
	return 0;

    if(!read_patterndata(fd)) {
	info("Unable to read patterndata.\n");
	return 0;
    }
    
    ioctl(seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev);
    
    info("\nUploading instruments to GUS...\n");
    
    fatalerr=0;

    for(i=1; i <= M.nr_samples; ++i) {
	if(M.sample[i].length > 4) { /* Only load non-silent samples */
	    if(!read_and_upload_sample(fd, i)) {
		fatalerr=1;
		break;
	    }
	}
	else { /* Skip sample */
	    if(lseek(fd, M.sample[i].length, SEEK_CUR) == -1){
		fatalerr=1;
		break;
	    }
	}
	if(quit == QUIT_SIGNAL) /* Bail out if we got QUIT_SIGNAL */
	    return 0;
    }

    if(fatalerr) {
	diff=expected_length-lseek(fd, 0, SEEK_CUR);
	sprintf(tmpbuf, "File %d bytes short", diff);
	print_status(tmpbuf);
	info("\nShort file (%d bytes missing).\n",0);
	sleep(1);
	return 0;
    }
    
    /* Check if filelength was as expected. This is done with two 
     * statements to make sure we don't move the filepointer to the 
     * end before checking current position.
     */

    diff=-lseek(fd, 0, SEEK_CUR);
    diff+=lseek(fd, 0, SEEK_END);

    if(diff && opt.verbose) {
	warning("\nFound %d garbagebytes after samples. Ignoring.\n", diff);
    }
    
    print_songname(M.name);
    print_samples(1);
    print_songinfo(M.nr_voices, M.nr_samples, "MTM", expected_length,
		   M.songlength, M.nr_patterns);
    return 1;
}


static int process_header(int fd)
{
    unsigned char buf[MTM_HEADER_SIZE];
    int tmp, i;
    struct sample_info *s;
    
    if(read(fd, buf, MTM_HEADER_SIZE) != MTM_HEADER_SIZE) {
	info("Unable to read header from file.\n");
	return 0;
    }
    
    expected_length=MTM_HEADER_SIZE; /* Start counting expected filesize */
    
    if(strncmp("MTM", &buf[MTM_MAGIC], 3)) {
	info("Not a Multitracker module.\n");
	return 0;
    }
    
    info("Format version %d.%d\n", (buf[MTM_VERSION]>>4)&0x0f,
	 buf[MTM_VERSION]&0x0f);
    
    /* Copy songname and strip trailing spaces */
    strncpy(M.name, &buf[MTM_NAME], 20);
    M.name[20]=0;
    fix_string(M.name);

    tracks=*(unsigned short *)&buf[MTM_TRACKS];
    M.nr_patterns=buf[MTM_PATTERNS]+1;
    M.songlength=buf[MTM_SONGLENGTH]+1;
    commentlen=*(unsigned short *)&buf[MTM_COMMENT_LEN];
    M.nr_samples=buf[MTM_NOS];

    info("Tracks saved: %d\n", tracks);

    if(!M.nr_samples || M.nr_samples > 127) {
	info("Invalid number of samples in module.\n");
	return 0;
    }

    if(M.nr_patterns > 127) {
	info("Invalid number of patterns.\n");
	return 0;
    }

    if(buf[MTM_BPT] != 64) {
	info("Beats (lines) per track: %d (!=64!).\n", buf[MTM_BPT]);
	return 0;
    }
    
    M.sample=(struct sample_info *)malloc((1+M.nr_samples)*
					  sizeof(struct sample_info));

    expected_length+=M.nr_samples*MTM_SAMPLEINFO_SIZE;
    print_sample_info_header();

    for(i=1; i <= M.nr_samples; ++i) {
	if(read(fd, buf, MTM_SAMPLEINFO_SIZE) != MTM_SAMPLEINFO_SIZE) {
	    info("Unable to read sampleheader (%d).\n", i);
	    return 0;
	}
	
	s=&M.sample[i];
        s->valid=0;             /* Valid isn't set to TRUE until it's
				 * sampledata has been read.
				 */
	strncpy(s->name, &buf[MTM_S_NAME], 22);
	s->name[22]=0;
        fix_string(s->name);
	
	s->length=*(unsigned long *)&buf[MTM_S_LEN];
	s->repeat_start=*(unsigned long *)&buf[MTM_S_LOOPSTART];
	s->repeat_end=*(unsigned long *)&buf[MTM_S_LOOPEND];
	s->finetune=buf[MTM_S_FINETUNE];
	s->volume=buf[MTM_S_VOLUME];

        s->c2freq=-1; /* Use PAL/NTSC */
	
	if(buf[MTM_S_ATTR]&MTM_FLAG_BITS_16)
	    s->bits_16=1;
	else
	    s->bits_16=0;

	if(s->repeat_end > s->repeat_start+4)
	    s->looped=LOOP_FORWARD;
	else
	    s->looped=0;
	
	s->unsigned_data=1;

	s->repeat_start=MIN(MAX(0, s->repeat_start), s->length-2);
	
	/* Is this one including/excluding the last sample? */
	s->repeat_end=MIN(MAX(0, s->repeat_end), s->length-2);

	expected_length+=s->length;
	
	print_sample_info(i);
    }

    if(read(fd, M.patterntable, 128) != 128) {
	info("Unable to load patterntable.\n");
	return 0;
    }
    expected_length+=128;

    for(i=0; i < 128; ++i)
	if(M.patterntable[i] >= M.nr_patterns)
	    break;

    M.songlength=MIN(i, M.songlength);

    for(tmp=0; tmp < MAX_VOICES; ++tmp)
	M.panning[tmp]=get_voice_balance(tmp);

    M.restartpos=0;
    M.volrange=64;
    
    info("\nSamples: %d  Patterns: %d  Songlength: %d\n",
	 M.nr_samples, M.nr_patterns, M.songlength);
    
    return 1;
}


static int read_patterndata(int fd)
{
    int track_mapping[128*32]; /* Maximum number of tracks */
    unsigned char buf[2*32*128]; /* Max track seq. data */
    char used_voices[32];
    unsigned char *b;
    unsigned short effect;
    struct event *e;
    int t, p, l, v, tmp;
    struct event events[64];
    
    for(v=0; v < NR_EFX; ++v)
	effect_used[v]=0;
    
    /* Allocate for worst case (no identical tracks) */
    M.tracks=(struct event **)malloc(tracks*sizeof(struct event *));

    /* Add the empty track */
    bzero((void *)events, sizeof(struct event)*64);
    track_mapping[0]=get_track_idx(events);
    
    for(t=1; t <= tracks; ++t) {
	if(read(fd, buf, 64*3) != 64*3)
	    return 0;
	expected_length+=64*3;

	b=buf;
	for(l=0; l < 64; ++l, b+=3) {
	    e=&events[l];
	    /* 2*12 because X-2 => X-4 */
	    e->note=(b[0]&0xfc ? ((b[0]&0xfc)>>2)+BASE_NOTE+2*12 : 0); 
	    e->sample=((b[0]&0x03)<<4)|((b[1]>>4)&0x0f);
	    effect=b[1]&0x0f;
	    
	    if(effect != 0x0e)
		e->arg=b[2];
	    else {
		effect=0x10|((b[2]>>4)&0x0f);
		e->arg=b[2]&0x0f;
	    }
	    e->effect=effect;
	    fix_effect(&e->effect, &e->arg);
	    
	    if(e->effect ||e->arg)
		effect_used[e->effect]=1;
	    
	    e->effect2=e->arg2=0;
	}
	track_mapping[t]=get_track_idx(events);
    }
    print_used_effects();
    
    if(read(fd, buf, M.nr_patterns*32*2) != M.nr_patterns*32*2)
	return 0;
    expected_length+=M.nr_patterns*32*2;
    
    bzero((void *)used_voices, 32);
    for(p=0; p < M.nr_patterns; ++p)
	for(v=0; v < 32; ++v)
	    if(track_mapping[*(unsigned short *)&buf[(p*32+v)*2]])
		used_voices[v]=1;

    for(v=31; v >= 0 && !used_voices[v]; --v)
	;
    M.nr_voices=v+1;
    
    if(!M.nr_voices) {
	info("Song empty (no tracks used).\n");
	return 0;
    }
    info("Used voices: %d\n", M.nr_voices);
    
    for(v=0; v < M.nr_voices; ++v)
	M.track_idx[v]=(int *)malloc(M.nr_patterns*sizeof(int));

    for(p=0; p < M.nr_patterns; ++p) {
	for(v=0; v < M.nr_voices; ++v) {
	    tmp=*(unsigned short *)&buf[(p*32+v)*2];
	    if(tmp > tracks) {
		info("Illegal track specified (%d).\n", tmp);
		return 0;
	    }
	    else {
		M.track_idx[v][p]=track_mapping[tmp];
	    }
	}
    }
    
    /* C-0 through D#5 (before "transposing" 2 octaves) */
    opt.low_note=BASE_NOTE+2*12;
    opt.high_note=BASE_NOTE+7*12+4;

    if(commentlen) {
	M.songtext=(void *)malloc(commentlen+1);
	if(read(fd, M.songtext, commentlen) != commentlen) {
	    info("Unable to load comment.\n");
	    return 0;
	}
	*(M.songtext+commentlen)=0;
	fix_string(M.songtext);
	expected_length+=commentlen;
    }
    else
	M.songtext=0;

    if(M.songtext && opt.verbose) {
	printf("\n--------------- Message: ---------------\n");
	tmp=0;
	while(tmp < commentlen) {
	    printf("%.39s\n", &M.songtext[tmp]);
	    tmp+=40;
	}
	printf("----------------------------------------\n");
    }

    return 1;
}

/* Filters out those effects not supported by Multitracker and adds the
 * balance-effect.
 */

static void fix_effect(unsigned char *effect, unsigned char *arg)
{
    switch(*effect) {
      case EFX_ARPEGGIO:           /* Effects compatible with PT */
      case EFX_PORTAUP:
      case EFX_PORTADOWN:
      case EFX_PORTANOTE:
      case EFX_VIBRATO:
      case EFX_PORTANOTEVOLSLIDE:
      case EFX_VIBRATOVOLSLIDE:
      case EFX_TREMOLO:
      case EFX_UNUSED1:
      case EFX_SAMPLEOFFSET:
      case EFX_VOLSLIDE:
      case EFX_JUMP:
      case EFX_VOLUME:
      case EFX_BREAK:
      case EFX_FINEPORTAUP:
      case EFX_FINEPORTADOWN:
      case EFX_FINETUNE:
      case EFX_RETRIGGER:
      case EFX_FINEVOLSLIDEUP:
      case EFX_FINEVOLSLIDEDOWN:
      case EFX_NOTECUT:
      case EFX_NOTEDELAY:
      case EFX_PTSPEED:
	break;

      case EFX_UNUSED2:           /* MTM: Balance */
	*effect=EFX_BALANCE;
	break;
#if 0	
      /* These effects are not supported by Multitracker */
      case EFX_FILTER:
      case EFX_GLISSANDO:
      case EFX_VIBWAVEFORM:
      case EFX_LOOP:
      case EFX_TREMWAVEFORM:
      case EFX_INVERTEDLOOP:
#endif
      default:
	*effect=*arg=0;
    }
}
