/* WARNING - WARNING - WARNING
 * This code is not finished, doesn't compile, doesn't work.
 * I think I won't finish the code but try a different approach.
 * This code may be useful as a reference nontheless.
 */


/* ScummVM - Scumm Interpreter
 * Copyright (C) 2001-2003 The ScummVM project
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /cvsroot/scummvm/scummvm/scumm/player_v2.cpp,v 2.14 2003/06/07 09:48:10 hoenicke Exp $
 */

#include "stdafx.h"
#include "common/engine.h"
#include "sound/fmopl.h"
#include "player_adlib.h"
#include "scumm.h"

#define FREQ_HZ (473 / 4) // Don't change!

#define SPK_DECAY   0xfa00              /* Depends on sample rate */
#define PCJR_DECAY  0xf600              /* Depends on sample rate */

#define FIXP_SHIFT  16
#define MAX_OUTPUT 0x7fff

#define NG_PRESET 0x0f35        /* noise generator preset */
#define FB_WNOISE 0x12000       /* feedback for white noise */
#define FB_PNOISE 0x08000       /* feedback for periodic noise */

#define HIGH_NIBBLE(x) ((byte) (x) >> 4)
#define LOW_NIBBLE(x)  ((x) & 15)

struct modifier_xlat {
	int instr_offset;
	int opl_reg;
	int opl_regtype;
	int maxvalue;
	int reverse;
	int mask;
	int shift;
}

const struct modifier_xlat mod2adlib[7] = {
	{ INSTR_FREQ     , 0xa0, 2, 0xff,    0, 0xff, 0 },
	{ INSTR_FEEDBACK , 0xc0, 2, 0x07,    0, 0x0e, 1 },
	{ INSTR_LEVEL2   , 0x40, 1, 0x3f, 0x3f, 0x3f, 0 },
	{ INSTR_AMP2     , 0x20, 1, 0x0f,    0, 0x0f, 0 },
	{ INSTR_LEVEL    , 0x40, 0, 0x3f, 0x3f, 0x3f, 0 },
	{ INSTR_AMP      , 0x20, 0, 0x0f,    0, 0x0f, 0 },
	{ -1             ,    0, 0, 0x3f,    0,    0, 0 }
}

mod2param[7] {
	29, 27, 13, 16, 0, 3, -1
};
	

static const AdlibSetParams adlib_setparam_table[] = {
	{0x40, 0, 63, 63},  // level
	{0xE0, 2, 0, 0},    // unused
	{0x40, 6, 192, 0},  // level key scaling
	{0x20, 0, 15, 0},   // modulator frequency multiple
	{0x60, 4, 240, 15}, // attack rate
	{0x60, 0, 15, 15},  // decay rate
	{0x80, 4, 240, 15}, // sustain level
	{0x80, 0, 15, 15},  // release rate
	{0xE0, 0, 3, 0},    // waveform select
	{0x20, 7, 128, 0},  // amp mod
	{0x20, 6, 64, 0},   // vib
	{0x20, 5, 32, 0},   // eg typ
	{0x20, 4, 16, 0},   // ksr
	{0xC0, 0, 1, 0},    // decay alg
	{0xC0, 1, 14, 0}    // feedback
};



enum instr_offsets {
	INSTR_FREQ      = 1,
	INSTR_FEEDBACK  = 3,
	INSTR_AMP       = 4,
	INSTR_LEVEL     = 5,
	INSTR_ATTACK    = 6, 
	INSTR_SUSTAIN   = 7, 
	INSTR_WAVEFORM  = 8, 
	INSTR_AMP2      = 9,
	INSTR_LEVEL2    = 10,
	INSTR_ATTACK2   = 11,
	INSTR_SUSTAIN2  = 12,
	INSTR_WAVEFORM2 = 13
}

struct ModifierInfo {
	int offset;
	int remaining_time;
	int current_value;
	int delta;
	int change_time;
};

struct ChannelInfo {
	int    mode;
	int    sound_nr;
	byte * chunk_ptr;
	InstrumentParams instrument;
};

const static int channel2opl[18] = {
	0,  3,
	1,  4,
	2,  5,
	8,  11,
	9,  12,
	10, 13,
	16, 19,
	17, 20,
	18, 21
};

////////////////////////////////////////
//
// V2 PC-Speaker MIDI driver
//
////////////////////////////////////////


Player_Adlib::Player_Adlib(Scumm *scumm) : _scumm(scumm) {
	int i;
	
	// This simulates the pc speaker sound, which is driven
	// by the 8253 (square wave generator) and a low-band filter.
	
	_system = scumm->_system;
	_sample_rate = _system->property(OSystem::PROP_GET_SAMPLE_RATE, 0);
	_mutex = _system->create_mutex();

	_header_len = (scumm->_features & GF_OLD_BUNDLE) ? 4 : 6;

	// Allocate Channels
	channels = new ChannelInfo[9];
	modinfos = new ModifierInfo[9*2];

	// Initialize sound queue 
	current_nr = next_nr = 0;
	current_data = next_data = 0;
	
	// Initialize channel code
	for (i = 0; i < 4; ++i)
		clear_channel(i);

	_next_tick = 0;
	_tick_len = (_sample_rate << FIXP_SHIFT) / FREQ_HZ;

	// Initialize V3 music timer
	_music_timer_ctr = _music_timer = 0;
	_ticks_per_music_timer = 65535;

	// Initialize square generator
	_level = 0;

	_RNG = NG_PRESET;

	set_pcjr(true);
	set_master_volume(255);

	scumm->_mixer->setupPremix(this, premix_proc);
}

Player_Adlib::~Player_Adlib() {
	mutex_up();
	// Detach the premix callback handler
	_scumm->_mixer->setupPremix (0, 0);
	mutex_down();
	_system->delete_mutex (_mutex);
	delete (channels);
	delete (modinfos);
}

void Player_Adlib::set_pcjr (bool pcjr) {
	mutex_up();
	_pcjr = pcjr;

	if (_pcjr) {
		_decay = PCJR_DECAY;
		_update_step = (_sample_rate << FIXP_SHIFT) / (111860 * 2);
		_freqs_table = pcjr_freq_table;
	} else {
		_decay = SPK_DECAY;
		_update_step = (_sample_rate << FIXP_SHIFT) / (1193000 * 2);
		_freqs_table = spk_freq_table;
	}

	/* adapt _decay to sample rate.  It must be squared when
	 * sample rate doubles.
	 */
	int i;
	for (i = 0; (_sample_rate << i) < 30000; i++)
		_decay = _decay * _decay / 65536;


	_timer_output = 0;
	for (i = 0; i < 4; i++)
		_timer_count[i] = 0;

	if (current_data)
		restartSound();
	mutex_down();
}

void Player_Adlib::set_master_volume (int vol) {
	if (vol > 255)
		vol = 255;

	/* scale to int16, FIXME: find best value */
	double out = vol * 128 / 3;
	
    /* build volume table (2dB per step) */
	for (int i = 0; i < 15; i++) {
		/* limit volume to avoid clipping */
		if (out > 0x7fff)
			_volumetable[i] = 0x7fff;
		else
			_volumetable[i] = (int) out;

		out /= 1.258925412;         /* = 10 ^ (2/20) = 2dB */
	}
	_volumetable[15] = 0;
}

void Player_Adlib::chainSound(int nr, byte *data) {
	int offset = _header_len + (_pcjr ? 10 : 2);

	current_nr = nr;
	current_data = data;

	for (int i = 0; i < 4; i++) {
		clear_channel(i);

		channels[i].d.music_script_nr = nr;
		if (data) {
			channels[i].d.next_cmd = READ_LE_UINT16(data+offset+2*i);
			if (channels[i].d.next_cmd)
				channels[i].d.time_left = 1;
		}
	}
	_music_timer = 0;
}

void Player_Adlib::chainNextSound() {
	if (next_nr) {
		chainSound(next_nr, next_data);
		next_nr = 0;
		next_data = 0;
	}
}

void Player_Adlib::stopAllSounds() {
	mutex_up();
	for (int i = 0; i < 4; i++) {
		clear_channel(i);
	}
	next_nr = current_nr = 0;
	next_data = current_data = 0;
	mutex_down();
}

void Player_Adlib::stopSound(int nr) {
	mutex_up();
	if (next_nr == nr) {
		next_nr = 0;
		next_data = 0;
	}
	if (current_nr == nr) {
		for (int i = 0; i < 4; i++) {
			clear_channel(i);
		}
		current_nr = 0;
		current_data = 0;
		chainNextSound();
	}
	mutex_down();
}

void Player_Adlib::startSound(int nr, byte *data) {
	mutex_up();

	int cprio = current_data ? *(current_data + _header_len) : 0;
	int prio  = *(data + _header_len);
	int nprio = next_data ? *(next_data + _header_len) : 0;

	int restartable = *(data + _header_len + 1);

	if (!current_nr || cprio <= prio) {
		int tnr = current_nr;
		int tprio = cprio;
		byte *tdata  = current_data;

		chainSound(nr, data);
		nr   = tnr;
		prio = tprio;
		data = tdata;
		restartable = data ? *(data + _header_len + 1) : 0;
	}
	
	if (!current_nr) {
		nr = 0;
		next_nr = 0;
		next_data = 0;
	}
	
	if (nr != current_nr
	    && restartable
	    && (!next_nr
		|| nprio <= prio)) {

		next_nr = nr;
		next_data = data;
	}

	mutex_down();
}

void Player_Adlib::restartSound() {
	if (*(current_data + _header_len + 1)) {
		/* current sound is restartable */
		chainSound(current_nr, current_data);
	} else {
		chainNextSound();
	}
}

int Player_Adlib::getSoundStatus(int nr) {
	return current_nr == nr || next_nr == nr;
}


void Player_Adlib::premix_proc(void *param, int16 *buf, uint len) {
	((Player_Adlib *) param)->do_mix(buf, len);
}

void Player_Adlib::clear_channel(int i) {
	ChannelInfo *channel = &channels[i];
	channel->d.time_left  = 0;
	channel->d.next_cmd   = 0;
	channel->d.base_freq  = 0;
	channel->d.freq_delta = 0;
	channel->d.freq = 0;
	channel->d.volume = 0;
	channel->d.volume_delta = 0;
	channel->d.inter_note_pause = 0;
	channel->d.transpose = 0;
	channel->d.hull_curve = 0;
	channel->d.hull_offset = 0;
	channel->d.hull_counter = 0;
	channel->d.freqmod_table = 0;
	channel->d.freqmod_offset = 0;
	channel->d.freqmod_incr = 0;
	channel->d.freqmod_multiplier = 0;
	channel->d.freqmod_modulo = 0;
}

int Player_Adlib::getMusicTimer() {
	if (_scumm->_version == 3)
		return _music_timer;
	else
		return channels[0].d.music_timer;
}


void Player_Adlib::adlib_write(byte port, byte value) {
	if (_adlib_reg_cache[port] == value)
		return;
	_adlib_reg_cache[port] = value;

	YM3812Write(0, 0, port);
	YM3812Write(0, 1, value);
}

void Player_Adlib::set_alasw(int channel, byte* ptr) {
	adlib_write(0x20 + channel, ptr[0]);
	adlib_write(0x40 + channel, ptr[1]);
	adlib_write(0x60 + channel, ptr[2]);
	adlib_write(0x80 + channel, ptr[3]);
	adlib_write(0xe0 + channel, ptr[4]);
}

void Player_Adlib::set_instrument(int channel, byte* ptr) {
	adlib_write(0xc0 + channel, *(ptr + INSTR_FEEDBACK));

	set_alasw(channel2opl[2 * channel + 0], ptr + INSTR_AMP);
	set_alasw(channel2opl[2 * channel + 1], ptr + INSTR_AMP2);

	adlib_write(0xa0 + channel, *(ptr + INSTR_FREQ));
	adlib_write(0xb0 + channel, *(ptr + INSTR_FREQ+1) & 0xdf);
}

int Player_Adlib::get_value(int modparam, int reg_val) {
	if (modparam == 6)
		return 0;

	if (!reg_val) {
		if (mod2adlib[modparam].opl_regtype == 2) {
			oplchan = channel;
		} else {
			oplchan = channel2opl[2 * channel + 
								  mod2adlib[modparam].opl_regtype];
		}
		
		reg_val = _adlib_reg_cache[mod2adlib[modparam].opl_reg + oplchan];
	}
	return (reg_val & mod2adlib[modparam].mask) >> shift;
}

void Player_Adlib::set_value(int modparam, int value) {
	if (modparam == 6)
		return;

	if (mod2adlib[modparam].opl_regtype == 2) {
		oplchan = channel;
	} else {
		oplchan = channel2opl[2 * channel + mod2adlib[modparam].opl_regtype];
	}
	
	int port = mod2adlib[modparam].opl_reg + oplchan;
	adlib_write(port, 
				(_adlib_reg_cache[port] & ~mod2adlib[modparam].mask)
				| (value << mod2adlib[modparam]. shift));
}

int Player_Adlib::next_chunk2_cmd(int channel, int subchunk) {
	byte *ptr = channels[channel].chunk_ptr + 1 + 5 * subchunk;
	ModifierInfo *modinfo = modinfos[2 * channel + subchunk]

	int chunk2_ctr = ++modinfo->chunk2_ctr;
	if (chunk2_ctr ==  4)
		return 1;

	int modparam = ptr[0] & 7;
	modinfo->modparam = modparam;
	if (chunk2_ctr == 0)
		cl = channels[channel].instrument_definition[modparam];
	else
		cl = 0;

	int src_value = get_value(modparam, cl);
	int change_time;
	int dest_value;
	if (mod2adlib[modparam].reverse)
		src_value = mod2adlib[modparam].reverse - src_value;

	if (chunk2_ctr <= 2) {
		byte value = ptr[1 + chunk2_ctr];
		change_time = decode_time[HIGH_NIBBLE(value)];

		if (chunk2_ctr == 2) {
			if (ptr[0] & 0x40)
				change_time = _scumm->_rnd->getRandomNumber(change_time);

			modinfo->remaining_time = change_time;
			return 0;
		}

		dest_value = modparam_maxval[modparam] * LOW_NIBBLE(value) / 15;
	} else {
		change_time = decode_time[LOW_NIBBLE(ptr[3])];
		dest_value  = 0;
	}
	modinfo->delta = dest_value - src_value;
	modinfo->change_time = change_time;
	modinfo->remaining_time = change_time;
	modinfo->current_value = src_value * delta;
	return 0;
}

int Player_Adlib::modify_value(ModifierInfo *modinfo) {
}

void Player_Adlib::init_chunk2(int channel, int subchunk) {
	byte *ptr = channels[channel].chunk_ptr + 1 + 5 * subchunk;
	channels[channel].chunk2_ctr[subchunk] = -1;
	next_chunk2_cmd(channel, subchunk);
	channels[channel].chunk2_repeattime = 0;
	if (ptr[0] & 0x20) {
		channels[channel].chunk2_repeattime = 
			HIGH_NIBBLE(ptr[4]) * 118 + LOW_NIBBLE(ptr[4]) * 8;
	}
}

void Player_Adlib::restart_note(int channel) {
	/* toggle the mute bit ??? */
	int old_value = _adlib_reg_cache[0xb0 + channel];
	adlib_write(0xb0 + channel, old_value & 0xdf);
	adlib_write(0xb0 + channel, old_value | 0x20);
}

void Player_Adlib::next_command2(int channel) {
	byte *ptr = channels[channel].chunk_ptr + 1;
	for (subchunk = 0; subchunk < 2; subchunk++, ptr += 5) {
		ModifierInfo *modinfo = &modinfos[2 * channel + subchunk];
		if (!(ptr[0] & 0x80))
			continue;

		if (channels[channel].chunk2_ctr[subchunk] != 2) {
			modinfo->current_value += change_time;
			value = modinfo->current_value / delta;
			if (mod2adlib[modparam].reverse)
				value = mod2adlib[modparam].reverse - value;
			set_value(modparam, value);
		}
		if (--modinfo->remaining_time)
			goto l3f12;
		if (!next_chunk2_cmd(channel, subchunk))
			goto l3f12;

		if ((ptr[0] & 0x08)) {
			if ((ptr[0] & 0x10))
				restart_note(channel);
			channels[channel].chunk2_ctr[subchunk] = -1;
			next_chunk2_cmd(channel, subchunk);

	l3f12:
			if ((ptr[0] & 0x20)
				&& !--channels[channel].chunk2_repeattime) {
				
				channels[channel].chunk_ptr += 11;
				channels[channel].mode = 1;
			}
		} else {
			channels[channel].chunk_ptr += 11;
			channels[channel].mode = 1;
		}
	}
}

void Player_Adlib::find_next_chunk(int channel) {
	for(;;) {
		byte header = *channels[channel].chunk_ptr;
		switch (header) {
		case 1:
			channels[channel].instrument.freq = 
				channels[channel].chunk_ptr + INSTR_FREQ;
			channels[channel].instrument.feedback = 
				channels[channel].chunk_ptr + INSTR_FEEDBACK;
			channels[channel].instrument.level2 = 
				channels[channel].chunk_ptr + INSTR_LEVEL2;
			channels[channel].instrument.amp2 = 
				channels[channel].chunk_ptr + INSTR_AMP2;
			channels[channel].instrument.level = 
				channels[channel].chunk_ptr + INSTR_LEVEL;
			channels[channel].instrument.amp = 
				channels[channel].chunk_ptr + INSTR_AMP;
			channels[channel].instrument.xyz = 0;
			set_instrument(channel, channels[channel].chunk_ptr);
			channels[channel].chunk_ptr += 15;
			break;

		case 2:
			channels[channel].mode = 2;
			restart_note(channel);
			init_chunk2(channel, 0);
			init_chunk2(channel, 1);
			return;

		case 0x80:
			channels[channel].chunk_ptr += channels[channel].loop_offset;
			break;
				
		default:
			clear_freq(channel);
			channels[channel].mode = 0;
			if (channel >= 6) {
				channel = 6;
			} else {
				if (channel >= 3)
					channel = 3;
				else
					channel = 0;
			}
			if (!channels[channel].mode 
				&& !channels[channel+1].mode
				&& !channels[channel+1].mode) {
				channels[channel].sound_nr = 0;
				inactivate_sound(channel);
			}
			return;
		}
	}
}

void Player_Adlib::next_command(int channel) {
	if (channels[channel].mode == 1)
		find_next_chunk(channel);
	else
		next_command2(channel);
}

void Player_Adlib::on_timer() {
	int i;
	if (--_tick_172 != 0)
		return;
	_tick_172 = 4;
    for (i = 0; i < 9; i++) {
		if (channels[i].mode != 0) {
			next_command(i);
		}
	}
}

void Player_Adlib::do_mix (int16 *data, int len) {
	mutex_up();
	int step;

	do {
		step = len;
		if (step > _next_tick)
			step = _next_tick;
		YM3812UpdateOne(0, data, step);

		_next_tick -= step << FIXP_SHIFT;

		if (!(_next_tick >> FIXP_SHIFT)) {
			on_timer();
			_next_tick += _tick_len;
		}
		data += step;
		len -= step;
	} while (len);
	mutex_down();
}



