/*
 *   Cargador/reproductor de instrumentos OPL3 para la MoonSound
 *   Basado en el sbiloader.c de Uros Bizjak
 *   (http://www.kss-loka.si/~uros/sbiload.html)
 *   Copyright (C) 2005  Avelino Herrera Morales
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */



#include <stdio.h>
#include <stdlib.h>
#include "files.h"
#include "ioport.h"


#define  DATA_LEN_2OP       16
#define  DATA_LEN_4OP       24
#define  FM_PATCH_OPL2      0x01
#define  FM_PATCH_OPL3      0x02
#define  SBI_FILE_TYPE_2OP  1
#define  SBI_FILE_TYPE_4OP  2

/* offsets para los parámetros dentro del fichero SBI */
#define  AM_VIB           0
#define  KSL_LEVEL        2
#define  ATTACK_DECAY     4
#define  SUSTAIN_RELEASE  6
#define  WAVE_SELECT      8

/* offset para cada instrumento SBI */
#define  CONNECTION  10
#define  OFFSET_4OP  11

/* offsets en sbi_header.name para las extensiones SBI */
#define  ECHO_DELAY     25
#define  ECHO_ATTEN     26
#define  CHORUS_SPREAD  27
#define  TRNSPS         28
#define  FIX_DUR        29
#define  MODES          30
#define  FIX_KEY        31

/* estructuras de cada instrumento SBI en el fichero */
/* cabecera */
typedef struct {
	char key[4];
	char name[32];
} sbi_header;

/* instrumento SBI */
typedef struct {
	sbi_header header;
	char data[DATA_LEN_4OP];
} sbi_inst;

/* operador */
typedef struct {
    unsigned char am_vib;
    unsigned char ksl_level;
    unsigned char attack_decay;
    unsigned char sustain_release;
    unsigned char wave_select;
} fm_op;

/* instrumento OPL2/OPL3 */
typedef struct {
    unsigned char type;         /* FM_PATCH_OPL2 ó FM_PATCH_OPL3 */
    fm_op op[4];
    unsigned char feedback_connection[2];
    unsigned char echo_delay;
    unsigned char echo_atten;
    unsigned char chorus_spread;
    unsigned char trnsps;
    unsigned char fix_dur;
    unsigned char modes;
    unsigned char fix_key;
} fm_inst;

/* moonsound */
#define  MS_FM_BASE      0xC4
#define  MS_WAVE_BASE    0x7E
#define  MS_STATUS_BASE  0xC4

#define  MS_FM_NUM_CHANNELS  18    /* número de canales con 2 operadores por canal */
#define  MS_FM_NUM_CELLS     36    /* número total de celdas operadoras */

/* máscaras para definir canales con 4 operadores */
#define  FOUR_OP_0_3    0x01
#define  FOUR_OP_1_4    0x02
#define  FOUR_OP_2_5    0x04
#define  FOUR_OP_9_12   0x10
#define  FOUR_OP_10_13  0x20
#define  FOUR_OP_11_14  0x40

/* registros FM de la Moonsound */
#define  MS_FM_REG1   MS_FM_BASE
#define  MS_FM_DATA1  (MS_FM_BASE + 1)
#define  MS_FM_REG2   (MS_FM_BASE + 2)
#define  MS_FM_DATA2  (MS_FM_BASE + 3)
sfr at MS_STATUS_BASE  MS_FM_STATUS_SFR;
sfr at MS_FM_REG1      MS_FM_REG1_SFR;

/* macro que se debe llamar cada vez que se escriba en un registro del OPL4 */
#define  MS_WAIT  while (MS_FM_STATUS_SFR & 0x01) {}

typedef struct {
	unsigned char mod_op;   /* celda operadora moduladora */
	unsigned char car_op;   /* celda operadora portadora */
} ms_fm_op_map;

/* este array indica, para cada uno de los 18 canales, qué pares de celdas operadoras utiliza */
ms_fm_op_map ms_fm_channel_op[MS_FM_NUM_CHANNELS] = {
	{0, 3}, {1, 4}, {2, 5}, {6, 9}, {7, 10}, {8, 11}, {12, 15}, {13, 16},
	{14, 17}, {18, 21}, {19, 22}, {20, 23}, {24, 27}, {25, 28}, {26, 29},
	{30, 33}, {31, 34}, {32, 35}
};

/* offset de las celdas operadoras para cada uno de los canales */
unsigned char ms_fm_op_offset[18] = {
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x08, 0x09, 0x0A,
	0x0B, 0x0C, 0x0D, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
};

#define  MAX_INSTRUMENTS  200
/* todo el conjunto de instrumentos */
fm_inst palette[MAX_INSTRUMENTS];

/* f-numbers para cada nota musical */
#define  NOTE_C      346
#define  NOTE_C_SUS  367
#define  NOTE_D      389
#define  NOTE_D_SUS  412
#define  NOTE_E      436
#define  NOTE_F      462
#define  NOTE_F_SUS  490
#define  NOTE_G      519
#define  NOTE_G_SUS  550
#define  NOTE_A      582
#define  NOTE_A_SUS  617
#define  NOTE_B      654



/* funciones relacionadas con la parte FM de la Moonsound */
char ms_detect(void) {
    if (MS_FM_REG1_SFR == 0xFF)
        return 0;
    return 1;
}

void ms_fm1_write(unsigned char reg, unsigned char data) {
	out(MS_FM_REG1, reg);
	MS_WAIT;
	out(MS_FM_DATA1, data);
	MS_WAIT;
}

void ms_fm2_write(unsigned char reg, unsigned char data) {
	out(MS_FM_REG2, reg);
	MS_WAIT;
	out(MS_FM_DATA2, data);
	MS_WAIT;
}

void ms_init(unsigned char four_op_mask, unsigned char deep_tremolo,
             unsigned char deep_vibrato) {
	ms_fm1_write(0x01, 0x20);
	/* habilita opl3 y opl4 */
	ms_fm2_write(0x05, 0x03);
	/* configura el modo de 4 operadores */
	ms_fm2_write(0x04, four_op_mask & 0x3F);
	/* deshabilita el modo de ritmo y configura la profundidad del trémolo y del vibrato */
	ms_fm1_write(0xBD, (deep_tremolo << 7) | (deep_vibrato << 6));
}

void ms_fm_set_op_patch(unsigned char cell, fm_op *op) {
	unsigned char o = 0;

	if (cell > 17) {
		cell -= 18;
		o = 2;
	}
	/* am-vib */
	out(MS_FM_REG1 + o, 0x20 + ms_fm_op_offset[cell]);
	MS_WAIT;
	out(MS_FM_DATA1 + o, op->am_vib);
	MS_WAIT;
	/* ksl-level */
	out(MS_FM_REG1 + o, 0x40 + ms_fm_op_offset[cell]);
	MS_WAIT;
	out(MS_FM_DATA1 + o, op->ksl_level);
	MS_WAIT;
	/* attack-decay */
	out(MS_FM_REG1  + o, 0x60 + ms_fm_op_offset[cell]);
	MS_WAIT;
	out(MS_FM_DATA1 + o, op->attack_decay);
	MS_WAIT;
	/* sustain-release */
	out(MS_FM_REG1 + o, 0x80 + ms_fm_op_offset[cell]);
	MS_WAIT;
	out(MS_FM_DATA1 + o, op->sustain_release);
	MS_WAIT;
	/* waveselect */
	out(MS_FM_REG1 + o, 0xE0 + ms_fm_op_offset[cell]);
	MS_WAIT;
	out(MS_FM_DATA1 + o, op->wave_select);
	MS_WAIT;
}

void ms_fm_set_channel(unsigned char channel, unsigned char feedback_connection) {
	unsigned char o = 0;

	if (channel > 8) {
		channel -= 9;
		o = 2;
	}
	out(MS_FM_REG1 + o, 0xC0 + channel);
	MS_WAIT;
	out(MS_FM_DATA1 + o, 0xF0 | feedback_connection);
	MS_WAIT;
}

void ms_fm_start_channel(unsigned char channel, unsigned int f_number,
                         unsigned char block) {
	unsigned char o = 0;

	if (channel > 8) {
		channel -= 9;
		o = 2;
	}
	out(MS_FM_REG1 + o, 0xA0 + channel);
	MS_WAIT;
	out(MS_FM_DATA1 + o, f_number & 0x00FF);
	MS_WAIT;
	out(MS_FM_REG1 + o, 0xB0 + channel);
	MS_WAIT;
	out(MS_FM_DATA1 + o, 0x20 | ((block & 0x07) << 2) | (f_number >> 8));
	MS_WAIT;
}

void ms_fm_stop_channel(unsigned char channel) {
	unsigned char o = 0;

	if (channel > 8) {
		channel -= 9;
		o = 2;
	}
	out(MS_FM_REG1 + o, 0xB0 + channel);
	MS_WAIT;
	out(MS_FM_DATA1 + o, 0x00);
	MS_WAIT;
}

void ms_fm_load_2op_inst(unsigned char channel, fm_inst *fmi) {
	unsigned char car = ms_fm_channel_op[channel].car_op;
	unsigned char mod = ms_fm_channel_op[channel].mod_op;

	ms_fm_stop_channel(channel);
	ms_fm_set_op_patch(mod, &(fmi->op[0]));
	ms_fm_set_op_patch(car, &(fmi->op[1]));
	ms_fm_set_channel(channel, fmi->feedback_connection[0]);
}


/* funciones relacionadas con la carga de ficheros SBI */
int memcmp(char *v1, char *v2, int n) {
	int i;

	for (i = 0; i < n; i++) {
		if (v1[i] > v2[i])
			return 1;
		else if (v1[i] < v2[i])
			return -1;
	}
	return 0;
}

void memset(char *v, char value, int size) {
	int i;

	for (i = 0; i < size; i++)
		v[i] = value;
}

char load_sbi(char fd, unsigned char file_type, fm_inst *fmi) {
	int prog;
	unsigned char i;
	sbi_inst sbi_instrument;
	char type;
	int data_size = (file_type == SBI_FILE_TYPE_4OP) ? DATA_LEN_4OP : DATA_LEN_2OP;

	printf("Loading SBI...\n\r");
	for (prog = 0; prog < MAX_INSTRUMENTS; prog++) {
		/* leemos la cabecera de cada instrumento */
		if (read(fd, &(sbi_instrument.header), sizeof(sbi_header)) < sizeof(sbi_header))
			return 0;
		if (!memcmp(sbi_instrument.header.key, "SBI\032", 4) || !memcmp(sbi_instrument.header.key, "2OP\032", 4))
			type = FM_PATCH_OPL2;
		else if (!memcmp(sbi_instrument.header.key, "4OP\032", 4))
			type = FM_PATCH_OPL3;
		else
			return 0;
		printf("%03d - '%s'\n\r", prog, sbi_instrument.header.name);
		/* leemos los datos */
		if (read(fd, sbi_instrument.data, data_size) < data_size)
			return 0;
		memset((char *)fmi, 0, sizeof(fm_inst));
		fmi->type = type;
		for (i = 0; i < 2; i++) {
			fmi->op[i].am_vib = sbi_instrument.data[AM_VIB + i];
			fmi->op[i].ksl_level = sbi_instrument.data[KSL_LEVEL + i];
			fmi->op[i].attack_decay = sbi_instrument.data[ATTACK_DECAY + i];
			fmi->op[i].sustain_release = sbi_instrument.data[SUSTAIN_RELEASE + i];
			fmi->op[i].wave_select = sbi_instrument.data[WAVE_SELECT + i];
		}
		fmi->feedback_connection[0] = sbi_instrument.data[CONNECTION];
		if (type == FM_PATCH_OPL3) {
			for (i = 0; i < 2; i++) {
				fmi->op[i + 2].am_vib = sbi_instrument.data[OFFSET_4OP + AM_VIB + i];
				fmi->op[i + 2].ksl_level = sbi_instrument.data[OFFSET_4OP + KSL_LEVEL + i];
				fmi->op[i + 2].attack_decay = sbi_instrument.data[OFFSET_4OP + ATTACK_DECAY + i];
				fmi->op[i + 2].sustain_release = sbi_instrument.data[OFFSET_4OP + SUSTAIN_RELEASE + i];
				fmi->op[i + 2].wave_select = sbi_instrument.data[OFFSET_4OP + WAVE_SELECT + i];
			}
			fmi->feedback_connection[1] = sbi_instrument.data[OFFSET_4OP + CONNECTION];
		}
		fmi++;
	}
	return 1;
}

void show_fm_inst(fm_inst *fmi) {
	unsigned char i, n, m;

	printf("Instrument:\n\r");
	if (fmi->type == FM_PATCH_OPL2) {
		printf("    FM_PATCH_OPL2\n\r");
		n = 2;
		m = 1;
	}
	else {
		printf("    FM_PATCH_OPL3\n\r");
		n = 4;
		m = 2;
	}
	for (i = 0; i < n; i++) {
		printf("Op %d\n\r", i);
		printf("    am_vib = %02X\n\r", fmi->op[i].am_vib);
		printf("    ksl_level = %02X\n\r", fmi->op[i].ksl_level);
		printf("    attack_decay = %02X\n\r", fmi->op[i].attack_decay);
		printf("    sustain_release = %02X\n\r", fmi->op[i].sustain_release);
		printf("    wave_select = %02X\n\r", fmi->op[i].wave_select);
	}
	for (i = 0; i < m; i++)
		printf("feedback_connection %d = %02X\n\r", i, fmi->feedback_connection[i]);
}


int main(void) {
	char fd;

	/* detectamos la MoonSound */
	if (!ms_detect()) {
		printf("MoonSound not detected! :-(. Exiting...\n\r");
		return -1;
	}
	printf("MoonSound detected! :-)\n\r");
	/* la inicializamos */
	ms_init(0, 0, 0);
	/* abrimos el fichero con los instrumentos General MIDI 1 de 2 operadores */
	fd = open("STD.SB", O_RDONLY);
	if (fd <= 0) {
		printf("Error opening STD.SB\n\r");
		return -1;
	}
	load_sbi(fd, SBI_FILE_TYPE_2OP, palette);
	/* tocamos una nota del órgane (instrumento 19 (General Midi 20)) */
	printf("\n\rPlaying Acoustic Piano...\n\r");
	ms_fm_load_2op_inst(0, &(palette[19]));
	ms_fm_start_channel(0, NOTE_A, 4);
	show_fm_inst(&(palette[0]));
	return 0;
}
