/************************************************************************
   patchload.c  --  loads patches for playmidi package
   Some of this code was adapted from code written by Hannu Solovainen

   Copyright (C) 1994-1996 Nathan I. Laredo

   This program is modifiable/redistributable under the terms
   of the GNU General Public Licence.

   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.
   Send your comments and all your spare pocket change to
   laredo@gnu.ai.mit.edu (Nathan Laredo) or to PSC 1, BOX 709, 2401
   Kelly Drive, Lackland AFB, TX 78236-5128, USA.
 *************************************************************************/

#include "playmidi.h"
#ifdef linux
#include <linux/ultrasound.h>
#else
#include <machine/ultrasound.h>
#endif
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "gmvoices.h"

SEQ_USE_EXTBUF();
extern int play_gus, play_sb, play_ext, playing, verbose, force8bit;
extern int reverb, fmloaded[256], patchloaded[256];
extern int gus_dev, sb_dev, ext_dev, seqfd, wantopl3;
extern struct synth_info card_info[MAX_CARDS];
extern void close_show(int);

static int use8bit = 0;

struct pat_header {
    char magic[12];
    char version[10];
    char description[60];
    unsigned char instruments;
    char voices;
    char channels;
    unsigned short nr_waveforms;
    unsigned short master_volume;
    unsigned int data_size;
};

struct sample_header {
    char name[7];
    unsigned char fractions;
    int len;
    int loop_start;
    int loop_end;
    unsigned short base_freq;
    int low_note;
    int high_note;
    int base_note;
    short detune;
    unsigned char panning;

    unsigned char envelope_rate[6];
    unsigned char envelope_offset[6];

    unsigned char tremolo_sweep;
    unsigned char tremolo_rate;
    unsigned char tremolo_depth;

    unsigned char vibrato_sweep;
    unsigned char vibrato_rate;
    unsigned char vibrato_depth;

    char modes;

    short scale_frequency;
    unsigned short scale_factor;
};

struct patch_info *patch;
int spaceleft, totalspace;

void gus_reload_8_bit();

void gus_load(pgm)
int pgm;
{
    int i, j, patfd, offset;
    struct pat_header header;
    struct sample_header sample;
    char buf[256], name[256];
    struct stat info;

    if (pgm < 0) {
	use8bit = force8bit;
	GUS_NUMVOICES(gus_dev, (card_info[gus_dev].nr_voices = 32));
	SEQ_DUMPBUF();
	for (i = 0; i < 256; i++)
	    patchloaded[i] = 0;
	if (ioctl(seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1)
	    close_show(-1);
	spaceleft = gus_dev;
	ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &spaceleft);
	totalspace = spaceleft;
    }

    if (patchloaded[pgm] < 0)
	return;

    if (patchloaded[pgm] == 1)
	return;

    if (gmvoice[pgm] == NULL) {
	patchloaded[pgm] = -1;
	return;
    }
    sprintf(name, PATCH_PATH1 "/%s.pat", gmvoice[pgm]);

    if (stat(name, &info) == -1) {
	sprintf(name, PATCH_PATH2 "/%s.pat", gmvoice[pgm]);
	if (stat(name, &info) == -1)
	    return;
    }
    if ((patfd = open(name, O_RDONLY, 0)) == -1)
	return;
    if (spaceleft < info.st_size) {
	if (!use8bit)
	    gus_reload_8_bit();
	if (use8bit)
	    if (spaceleft < info.st_size / 2) {
		close(patfd);
		patchloaded[pgm] = -1;	/* no space for patch */
		return;
	    }
    }
    if (read(patfd, buf, 0xef) != 0xef) {
	close(patfd);
	return;
    }
    memcpy((char *) &header, buf, sizeof(header));

    if (strncmp(header.magic, "GF1PATCH110", 12)) {
	close(patfd);
	return;
    }
    if (strncmp(header.version, "ID#000002", 10)) {
	close(patfd);
	return;
    }
    header.nr_waveforms = *(unsigned short *) &buf[85];
    header.master_volume = *(unsigned short *) &buf[87];

    offset = 0xef;

    for (i = 0; i < header.nr_waveforms; i++) {

	if (lseek(patfd, offset, 0) == -1) {
	    close(patfd);
	    return;
	}
	if (read(patfd, &buf, sizeof(sample)) != sizeof(sample)) {
	    close(patfd);
	    return;
	}
	memcpy((char *) &sample, buf, sizeof(sample));

	/*
	 * Since some fields of the patch record are not 32bit aligned, we must
	 * handle them specially.
	 */
	sample.low_note = *(int *) &buf[22];
	sample.high_note = *(int *) &buf[26];
	sample.base_note = *(int *) &buf[30];
	sample.detune = *(short *) &buf[34];
	sample.panning = (unsigned char) buf[36];

	memcpy(sample.envelope_rate, &buf[37], 6);
	memcpy(sample.envelope_offset, &buf[43], 6);

	sample.tremolo_sweep = (unsigned char) buf[49];
	sample.tremolo_rate = (unsigned char) buf[50];
	sample.tremolo_depth = (unsigned char) buf[51];

	sample.vibrato_sweep = (unsigned char) buf[52];
	sample.vibrato_rate = (unsigned char) buf[53];
	sample.vibrato_depth = (unsigned char) buf[54];
	sample.modes = (unsigned char) buf[55];
	sample.scale_frequency = *(short *) &buf[56];
	sample.scale_factor = *(unsigned short *) &buf[58];

	offset = offset + 96;
	patch = (struct patch_info *) malloc(sizeof(*patch) + sample.len);

	if (patch == NULL) {
	    close(patfd);
	    return;
	}
	patch->key = GUS_PATCH;
	patch->device_no = gus_dev;
	patch->instr_no = pgm;
	patch->mode = sample.modes | WAVE_TREMOLO |
	    WAVE_VIBRATO | WAVE_SCALE;
	patch->len = (use8bit ? sample.len / 2 : sample.len);
	patch->loop_start =
	    (use8bit ? sample.loop_start / 2 : sample.loop_start);
	patch->loop_end = (use8bit ? sample.loop_end / 2 : sample.loop_end);
	patch->base_note = sample.base_note;
	patch->high_note = sample.high_note;
	patch->low_note = sample.low_note;
	patch->base_freq = sample.base_freq;
	patch->detuning = sample.detune;
	patch->panning = (sample.panning - 7) * 16;

	memcpy(patch->env_rate, sample.envelope_rate, 6);
	for (j = 0; j < 6; j++)	/* tone things down slightly */
	    patch->env_offset[j] =
		(736 * sample.envelope_offset[j] + 384) / 768;

	if (reverb)
	    if (pgm < 120)
		patch->env_rate[3] = (2 << 6) | (12 - (reverb >> 4));
	    else if (pgm > 127)
		patch->env_rate[1] = (3 << 6) | (63 - (reverb >> 1));

	patch->tremolo_sweep = sample.tremolo_sweep;
	patch->tremolo_rate = sample.tremolo_rate;
	patch->tremolo_depth = sample.tremolo_depth;

	patch->vibrato_sweep = sample.vibrato_sweep;
	patch->vibrato_rate = sample.vibrato_rate;
	patch->vibrato_depth = sample.vibrato_depth;

	patch->scale_frequency = sample.scale_frequency;
	patch->scale_factor = sample.scale_factor;

	patch->volume = header.master_volume;

	if (lseek(patfd, offset, 0) == -1) {
	    close(patfd);
	    return;
	}
	if (read(patfd, patch->data, sample.len) != sample.len) {
	    close(patfd);
	    return;
	}
	if (patch->mode & WAVE_16_BITS && use8bit) {
	    patch->mode &= ~WAVE_16_BITS;
	    /* cut out every other byte to make 8-bit data from 16-bit */
	    for (j = 0; j < patch->len; j++)
		patch->data[j] = patch->data[1 + j * 2];
	}
	SEQ_WRPATCH(patch, sizeof(*patch) + patch->len);
	free(patch);
	offset = offset + sample.len;
    }
    close(patfd);
    spaceleft = gus_dev;
    ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &spaceleft);
    patchloaded[pgm] = 1;
    return;
}

void gus_reload_8_bit()
{
    int i;

    if (ioctl(seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) {
	close_show(-1);
    }
    spaceleft = gus_dev;
    ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &spaceleft);
    totalspace = spaceleft;
    use8bit = 1;
    for (i = 0; i < 256; i++)
	if (patchloaded[i] > 0) {
	    patchloaded[i] = 0;
	    gus_load(i);
	}
}

void adjustfm(buf, key)
char *buf;
int key;
{
    unsigned char pan = ((rand() % 3) + 1) << 4;

    if (key == FM_PATCH) {
	buf[39] &= 0xc0;
	if (buf[46] & 1)
	    buf[38] &= 0xc0;
	buf[46] = (buf[46] & 0xcf) | pan;
	if (reverb) {
	    unsigned val;
	    val = buf[43] & 0x0f;
	    if (val > 0)
		val--;
	    buf[43] = (buf[43] & 0xf0) | val;
	}
    } else {
	int mode;
	if (buf[46] & 1)
	    mode = 2;
	else
	    mode = 0;
	if (buf[57] & 1)
	    mode++;
	buf[50] &= 0xc0;
	if (mode == 3)
	    buf[49] &= 0xc0;
	if (mode == 1)
	    buf[39] &= 0xc0;
	if (mode == 2 || mode == 3)
	    buf[38] &= 0xc0;
	buf[46] = (buf[46] & 0xcf) | pan;
	buf[57] = (buf[57] & 0xcf) | pan;
	if (mode == 1 && reverb) {
	    unsigned val;
	    val = buf[43] & 0x0f;
	    if (val > 0)
		val--;
	    buf[43] = (buf[43] & 0xf0) | val;
	    val = buf[54] & 0x0f;
	    if (val > 0)
		val--;
	    buf[54] = (buf[54] & 0xf0) | val;
	}
    }
}

void loadfm()
{
    int sbfd, i, n, voice_size, data_size;
    char buf[60];
    struct sbi_instrument instr;

    for (i = 0; i < 256; i++)
	fmloaded[i] = 0;
    srand(getpid());
    if (wantopl3) {
	voice_size = 60;
	sbfd = open(O3MELODIC, O_RDONLY, 0);
    } else {
	voice_size = 52;
	sbfd = open(SBMELODIC, O_RDONLY, 0);
    }
    if (sbfd == -1)
	close_show(-1);
    instr.device = sb_dev;

    for (i = 0; i < 128; i++) {
	if (read(sbfd, buf, voice_size) != voice_size)
	    close_show(-1);
	instr.channel = i;

	if (strncmp(buf, "4OP", 3) == 0) {
	    instr.key = OPL3_PATCH;
	    data_size = 22;
	} else {
	    instr.key = FM_PATCH;
	    data_size = 11;
	}

	fmloaded[i] = instr.key;

	adjustfm(buf, instr.key);
	for (n = 0; n < 32; n++)
	    instr.operators[n] = (n < data_size) ? buf[36 + n] : 0;

	SEQ_WRPATCH(&instr, sizeof(instr));
    }
    close(sbfd);

    if (wantopl3)
	sbfd = open(O3DRUMS, O_RDONLY, 0);
    else
	sbfd = open(SBDRUMS, O_RDONLY, 0);

    for (i = 128; i < 175; i++) {
	if (read(sbfd, buf, voice_size) != voice_size)
	    close_show(-1);
	instr.channel = i;

	if (strncmp(buf, "4OP", 3) == 0) {
	    instr.key = OPL3_PATCH;
	    data_size = 22;
	} else {
	    instr.key = FM_PATCH;
	    data_size = 11;
	}
	fmloaded[i] = instr.key;

	adjustfm(buf, instr.key);
	for (n = 0; n < 32; n++)
	    instr.operators[n] = (n < data_size) ? buf[n + 36] : 0;

	SEQ_WRPATCH(&instr, sizeof(instr));
    }
    close(sbfd);
}
