/*
 * playmod.c
 *
 * Play a .mod file
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <common/types.h>
#include <snd.h>
#ifndef WIN32
#include <netinet/in.h>
#else
u16 ntohs(u16 data) { return ((data >> 8) | (data << 8)) & 0xffff; }
#endif


#define MOD_MAGIC_MK 0x2e4b2e4d
#define MOD_MAGIC_FLT4 0x34544c46
#define MOD_MAGIC_6CHN 0x4e484336
#define MOD_MAGIC_8CHN 0x4e484338


/* on-disk structures */

struct instrument {
    char name[22];
    u16  length;
    u8   finetune;
    u8   volume;
    u16  loopstart;
    u16  looplength;
} PACKED;

struct mod_info {
    u8  song_length;
    u8  ciaa_speed; /* Amiga-only, not for PC use */
    u8  arrangement[128];
    u32 magic; /* magic identifier */
} PACKED;


/* in-memory structures */

struct channel {
    int number;
    int enabled;
    u32 current_position;
    int base_period;
    int target_period;
    u32 increment;
    u32 length;
    u8 volume;
    struct instrument *instrument;
    u8 *samples;
    u8 effect;
    u8 effect_args;
    int effect_data;
};

struct mod_file {
    char *song_name;
    struct instrument *instruments;
    struct mod_info *info;
    u8 *patterns;
    u8 *instrument_data[31];
} *modfile;

struct tracker {
    struct channel channels[8];
    int num_channels;
    int line_increment;
    int pattern_increment;
    int cur_position;
    u8 *cur_pattern;
    int cur_line;
    int ticks_left;
    int ticks_per_line;
    int beats_per_minute;
    int samples_per_tick;
} tracker;

struct display {
    int visible;
} display;

struct mod_file *parse_mod_file(u8 *data, int length)
{
    static struct mod_file file;
    int i;

    file.instruments = (void *)&data[20];
    file.info = (void *)&file.instruments[31];
    file.patterns = (void *)&file.info[1];

    /* Fix all 16-bit values in the file to be little-endian */
    for (i = 31; i; i--) {
	file.instruments[i-1].length = ntohs(file.instruments[i-1].length);
	file.instruments[i-1].loopstart = ntohs(file.instruments[i-1].loopstart);
	file.instruments[i-1].looplength = ntohs(file.instruments[i-1].looplength);
    }

    /* Find the start of the instrument data for each instrument */
    file.instrument_data[30] = &data[length - file.instruments[30].length * 2];
    for (i = 30; i; i--) {
	file.instrument_data[i-1] = file.instrument_data[i] - file.instruments[i-1].length * 2;
    }

    /* convert the instrument sample data from S8 to U8 format */
    for (i = file.instrument_data[0] - data; i < length; i++) {
	data[i] ^= 0x80;
    }
    
    return &file;
}


/* Mixer core */

/*
 * The mixer is run once per tick, and generates one tick's worth of output
 * for the soundcard. This is 44100 Hz / 50 Hz = 882 samples at the default
 * 125 beats per minute.
 */

u8 update_channel(struct channel *channel)
{
    u8 retval;

    if (!channel->enabled) return 0x80;

    retval = channel->samples[channel->current_position >> 15];
    retval = (retval * channel->volume) >> 6;

    channel->current_position += channel->increment;

    if (channel->current_position < channel->length) return retval;

    if (channel->instrument->looplength > 1) {
	channel->current_position -= channel->length;
	channel->current_position += (channel->instrument->loopstart << 15);
	channel->length = (channel->instrument->loopstart + channel->instrument->looplength) << 15;
    } else {
	channel->enabled = 0;
    }

    return retval;
}

void mix4(struct channel *channels)
{
    u8 buffer0[tracker.samples_per_tick];
    u8 buffer1[tracker.samples_per_tick];
    u8 buffer2[tracker.samples_per_tick];
    u8 buffer3[tracker.samples_per_tick];
    int i;

    for (i = 0; i < tracker.samples_per_tick; i++) {
	buffer0[i] = update_channel(&channels[0]);
	buffer1[i] = update_channel(&channels[1]);
	buffer2[i] = update_channel(&channels[2]);
	buffer3[i] = update_channel(&channels[3]);
    }

    snd_output_4_waves(tracker.samples_per_tick, buffer0, buffer1,
                       buffer2, buffer3);
}

void mix8(struct channel *channels)
{
    u8 buffer0[tracker.samples_per_tick];
    u8 buffer1[tracker.samples_per_tick];
    u8 buffer2[tracker.samples_per_tick];
    u8 buffer3[tracker.samples_per_tick];
    u8 buffer4[tracker.samples_per_tick];
    u8 buffer5[tracker.samples_per_tick];
    u8 buffer6[tracker.samples_per_tick];
    u8 buffer7[tracker.samples_per_tick];
    int i;

    for (i = 0; i < tracker.samples_per_tick; i++) {
	buffer0[i] = update_channel(&channels[0]);
	buffer1[i] = update_channel(&channels[1]);
	buffer2[i] = update_channel(&channels[2]);
	buffer3[i] = update_channel(&channels[3]);
	buffer4[i] = update_channel(&channels[4]);
	buffer5[i] = update_channel(&channels[5]);
	buffer6[i] = update_channel(&channels[6]);
	buffer7[i] = update_channel(&channels[7]);
    }

    snd_output_8_waves(tracker.samples_per_tick, buffer0, buffer1,
                       buffer2, buffer3, buffer4, buffer5, buffer6,
		       buffer7);
}


/* Tracker <-> mixer interface */

void tracker_update_pattern(void)
{
    tracker.cur_pattern = &modfile->patterns[modfile->info->arrangement[tracker.cur_position] * tracker.pattern_increment];

    if (!display.visible) return;

    printf("\033[1;0H\033[KPattern %d/%d[%d]", tracker.cur_position, modfile->info->song_length, modfile->info->arrangement[tracker.cur_position]);
}

void tracker_update_bpm(void)
{
    tracker.samples_per_tick = 110250 / tracker.beats_per_minute;
}

void set_channel_instrument(struct channel *channel, u8 instrument)
{
    if (!instrument) return;

    channel->instrument = &modfile->instruments[instrument - 1];
    channel->samples = modfile->instrument_data[instrument - 1];

    channel->volume = channel->instrument->volume;
    channel->length = channel->instrument->length << 16;

    channel->current_position = 0;
    channel->enabled = 1;

    if (!display.visible) return;

    printf("\033[%d;%dH%d: %-22.22s", 2 + (channel->number >> 1),
	   27 * (channel->number & 1), channel->number,
	   channel->instrument->name);
}

void set_channel_increment_from_period(struct channel *channel, u32 period)
{
    /*
     * The increment is speicified in terms of the period, the output
     * sample frequency (44100 Hz), and the PAL or NTSC Amiga clocks
     * (7093789.2 Hz for PAL, 7159090.5 Hz for NTSC).
     *
     * The formula given is rate = clock / (period * 2). We happen to
     * want something of the form increment = magic / period, and need
     * some suitable value of magic.
     *
     * clock / (period * 2) gives the sample output frequency in Hz.
     * output frequency / 44100 gives the increment between samples.
     *
     * subsituting out constants, we get:
     * increment = (clock / 88200) / period.
     *
     * Our NTSC clock is 7159090.5, so we have
     * increment = 7159090.5 / 88200 = 81.1688265306
     * We're in 17.15 fixed point, so multiply by 32768 = 2659740.1077547008.
     */

    channel->increment = 2659740 / period;
}

void set_channel_period(struct channel *channel, u32 period)
{
    if (!period) return;

    channel->current_position = 0;
    channel->enabled = 1;

    channel->base_period = period;
    channel->target_period = period;
    set_channel_increment_from_period(channel, period);

    /* we also disable effects here, just in case */
    channel->effect = 0;
    channel->effect_args = 0;
    channel->effect_data = 0;
}

void set_channel_effect(struct channel *channel, u8 effect, u8 args)
{
    if (!(effect || args)) return;

    channel->effect = 0;
    channel->effect_args = 0;
    channel->effect_data = 0;

    switch (effect) {
    case 0x0b:
	/* Position Jump */
	tracker.cur_line = -1;
	tracker.cur_position = args;
	tracker_update_pattern();
	break;

    case 0x0c:
	/* Set volume */
	if (args > 0x40) args = 0x40;
	channel->volume = args;
	break;

    case 0x0d:
	/* Pattern Break */
	tracker.cur_position++;
	tracker.cur_line = (((args >> 4) * 10) + (args & 0x0f)) - 1;
	tracker_update_pattern();
	break;

    case 0x0f:
	/* Set speed */
	if (args < 32) {
	    tracker.ticks_per_line = args;
	} else {
	    tracker.beats_per_minute = args;
	    tracker_update_bpm();
	}
	break;

    default:
	channel->effect = effect;
	channel->effect_args = args;
	break;
    }
}

void run_channel_effect(struct channel *channel)
{
    if (!(channel->effect || channel->effect_args)) return;

    switch (channel->effect) {
    case 0x00:
	switch(channel->effect_data) {
	case 0:
	    set_channel_increment_from_period(channel, channel->base_period);
	    break;

	case 1:
	    set_channel_increment_from_period(channel, channel->base_period + (channel->effect_args >> 4));
	    break;

	case 2:
	    set_channel_increment_from_period(channel, channel->base_period + (channel->effect_args & 15));
	    break;
	}

	channel->effect_data++;
	channel->effect_data %= 3;
	break;

    case 0x01:
	channel->base_period -= channel->effect_args;
	if (channel->base_period < 1) channel->base_period = 1;
	set_channel_increment_from_period(channel, channel->base_period);
	break;

    case 0x02:
	channel->base_period += channel->effect_args;
	set_channel_increment_from_period(channel, channel->base_period);
	break;

    case 0x03:
	if (channel->base_period > channel->target_period) {
	    channel->base_period -= channel->effect_args;
	    if (channel->base_period < channel->target_period) {
		channel->base_period = channel->target_period;
		channel->effect = 0;
		channel->effect_args = 0;
	    }
	} else {
	    channel->base_period += channel->effect_args;
	    if (channel->base_period > channel->target_period) {
		channel->base_period = channel->target_period;
		channel->effect = 0;
		channel->effect_args = 0;
	    }
	}
	set_channel_increment_from_period(channel, channel->base_period);
	break;

    case 0x0a:
	if (channel->effect_args < 0x10) {
	    channel->volume -= channel->effect_args;

	    if (channel->volume > 0x40) channel->volume = 0;
	} else {
	    channel->volume += channel->effect_args >> 4;

	    if (channel->volume > 0x40) channel->volume = 0x40;
	}
	break;

    default:
	break;
    }
}


/* Tracker core */

void parse_note(struct channel *channel, u8 *note)
{
    u8 instrument;
    u16 period;
    u8 effect;
    u8 effect_args;

    instrument  = note[0] & 0xf0;
    instrument |= note[2] >> 4;

    period  = note[0] << 8;
    period |= note[1];
    period &= 0x0fff;

    effect = note[2] & 0x0f;
    effect_args = note[3];

#ifdef DEBUG
    printf("%03x %02x %01x%02x", period, instrument, effect, effect_args);
#endif
    
    set_channel_instrument(channel, instrument);
    if (effect != 3) {
	set_channel_period(channel, period);
    } else if (period) {
	channel->target_period = period;
    }
    set_channel_effect(channel, effect, effect_args);
}

void parse_noteline(struct channel *channels, u8 *noteline)
{
    int channel;

    for (channel = 0; channel < tracker.num_channels; channel++) {
	parse_note(&channels[channel], &noteline[channel << 2]);
#ifdef DEBUG
	putc((channel == 3)? '\n': '|', stdout);
#endif
    }
}

const char *find_note_name(int period)
{
    const int periods[61] = {
	0x000,

	0x036, 0x039, 0x03c, 0x040, 0x043, 0x047,
	0x04c, 0x055, 0x05a, 0x05f, 0x065, 0x06b,

	0x071, 0x078, 0x07f, 0x087, 0x08f, 0x097,
	0x0a0, 0x0aa, 0x0b4, 0x0be, 0x0ca, 0x0d6,

	0x0e2, 0x0f0, 0x0fe, 0x10d, 0x11d, 0x12e,
	0x140, 0x153, 0x168, 0x17d, 0x194, 0x1ac,

	0x1c5, 0x1e0, 0x1fc, 0x21a, 0x23a, 0x25c,
	0x280, 0x2a6, 0x2d0, 0x2fa, 0x328, 0x358,

	0x386, 0x3c1, 0x3fa, 0x436, 0x477, 0x4bb,
	0x503, 0x54f, 0x5a0, 0x5f5, 0x650, 0x6b0,
    };

    const char *names[61] = {
	"---",

	"B-4", "A#4", "A-4", "G#4", "G-4", "F#4",
	"F-4", "E-4", "D#4", "D-4", "C#4", "C-4",

	"B-3", "A#3", "A-3", "G#3", "G-3", "F#3",
	"F-3", "E-3", "D#3", "D-3", "C#3", "C-3",

	"B-2", "A#2", "A-2", "G#2", "G-2", "F#2",
	"F-2", "E-2", "D#2", "D-2", "C#2", "C-2",

	"B-1", "A#1", "A-1", "G#1", "G-1", "F#1",
	"F-1", "E-1", "D#1", "D-1", "C#1", "C-1",

	"B-0", "A#0", "A-0", "G#0", "G-0", "F#0",
	"F-0", "E-0", "D#0", "D-0", "C#0", "C-0",
    };

    int i;
    static char buffer[4];

    for (i = 0; i < 61; i++) {
	if (periods[i] == period) return names[i];

	if (periods[i] > period) break;
    }

    sprintf(buffer, "%03x", period);

    return buffer;
}

void display_note_line(u8 *noteline)
{
    int channel;

    for (channel = 0; channel < tracker.num_channels; channel++) {
	u8 *note;
	u8 instrument;
	u16 period;
	u8 effect;
	u8 effect_args;
	char inst_buf[4]; /* Yes, one too many. Don't change it. */
	char effect_buf[4];

	note = &noteline[(channel << 2)];

	instrument  = note[0] & 0xf0;
	instrument |= note[2] >> 4;

	period  = note[0] << 8;
	period |= note[1];
	period &= 0x0fff;

	effect = note[2] & 0x0f;
	effect_args = note[3];

	if (instrument) {
	    sprintf(inst_buf, "%02x", instrument);
	} else {
	    *((int *)inst_buf) = 0x00002d2d;
	}

	if (effect || effect_args) {
	    sprintf(effect_buf, "%01x%02x", effect, effect_args);
	} else {
	    *((int *)effect_buf) = 0x002d2d2d;
	}

	printf("%s %s %s", find_note_name(period), inst_buf, effect_buf);

	putc((channel == (tracker.num_channels - 1))? '\n': '|', stdout);
    }
}

void display_update_pattern()
{
    int i;
    int cur_line;

    if (!display.visible) return;

    cur_line = tracker.cur_line;
    if (cur_line == -1) cur_line = 0;
    
    printf("\033[%d;0H", 2 + (tracker.num_channels >> 1));

    for (i = 0; i < 40; i++) {
	if (i == 20) {
	    printf("\033[1;44m");
	} else if (i == 21) {
	    printf("\033[1;0m");
	}

	if (((i + cur_line - 20) < 0x40) &&
	    ((i + cur_line - 20) >= 0)) {
	    display_note_line(&tracker.cur_pattern[((cur_line - 20) + i) * tracker.line_increment]);
	} else {
	    printf("\033[K\n");
	}
    }
}

void toplevel(void)
{
    int i;

    if ((modfile->info->magic == MOD_MAGIC_MK) ||
	(modfile->info->magic == MOD_MAGIC_FLT4)) {
	tracker.num_channels = 4;
    } else if (modfile->info->magic == MOD_MAGIC_6CHN) {
	tracker.num_channels = 6;
    } else if (modfile->info->magic == MOD_MAGIC_8CHN) {
	tracker.num_channels = 8;
    } else {
	fprintf(stderr, "unrecognized magic 0x%08lx.\n", modfile->info->magic);
    }

    tracker.line_increment = tracker.num_channels * 4;
    tracker.pattern_increment = tracker.line_increment * 0x40;

    tracker.cur_position = 0;
    tracker_update_pattern();

    for (i = 0; i < 8; i++) {
	tracker.channels[i].number = i;
	tracker.channels[i].enabled = 0;
    }

    tracker.ticks_per_line = 6;
    tracker.beats_per_minute = 125;

    tracker_update_bpm();
    
    tracker.cur_line = 0;
    tracker.ticks_left = 0;

    while(1) {
	if (!tracker.ticks_left--) {
	    display_update_pattern();
	    parse_noteline(tracker.channels, &tracker.cur_pattern[tracker.cur_line * tracker.line_increment]);
	    if (++tracker.cur_line == 0x40) {
		tracker.cur_line = 0;
		tracker.cur_position++;
		tracker_update_pattern();
	    }
	    tracker.ticks_left = tracker.ticks_per_line;
	}

	for (i = 0; i < tracker.num_channels; i++) {
	    run_channel_effect(&tracker.channels[i]);
	}

	if (tracker.num_channels == 4) {
	    mix4(tracker.channels);
	} else if (tracker.num_channels == 8) {
	    mix8(tracker.channels);
	} else {
	    fprintf(stderr, "unsupported number of channels %d.\n", tracker.num_channels);
	}
	
	if (tracker.cur_position >= modfile->info->song_length) break;
    }
}

int process_data(u8 *data, int length)
{
    display.visible = 1;

    modfile = parse_mod_file(data, length);
    snd_init();
    snd_open(44100);
    toplevel();
    snd_close();
    
    return 0;
}

#include <common/fileslurp.h>

/* EOF */
