/* REminiscence - Flashback interpreter
 * Copyright (C) 2005-2007 Gregory Montoir
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "mixer.h"
#include "sfx_player.h"


SfxPlayer::SfxPlayer(Mixer *mixer)
	: _mod(0), _playing(false), _mix(mixer) {
}

void SfxPlayer::play(uint8 num) {
	debug(DBG_SFX, "SfxPlayer::play(%d)", num);
	if (!_playing) {
		if (num >= 68 && num <= 75) {
			static const Module *modTable[] = {
				&_module68, &_module68, &_module70, &_module70,
				&_module72, &_module73, &_module74, &_module75
			};
			_mod = modTable[num - 68];
			_curOrder = 0;
			_numOrders = READ_BE_UINT16(_mod->moduleData);
			_orderDelay = 0;
			_modData = _mod->moduleData + 0x22;
			memset(_samples, 0, sizeof(_samples));
			_samplesLeft = 0;
			_mix->setPremixHook(mixCallback, this);
			_playing = true;
		}
	}
}

void SfxPlayer::stop() {
	if (_playing) {
		_mix->setPremixHook(0, 0);
		_playing = false;
	}
}

void SfxPlayer::playSample(int channel, const uint8 *sampleData, uint16 period) {
	assert(channel < NUM_CHANNELS);
	SampleInfo *si = &_samples[channel];
	si->len = READ_BE_UINT16(sampleData); sampleData += 2;
	si->vol = READ_BE_UINT16(sampleData); sampleData += 2;
	si->loopPos = READ_BE_UINT16(sampleData); sampleData += 2;
	si->loopLen = READ_BE_UINT16(sampleData); sampleData += 2;
	si->freq = PAULA_FREQ / period;
	si->pos = 0;
	si->data = sampleData;
}

void SfxPlayer::handleTick() {
	if (!_playing) {
		return;
	}
	if (_orderDelay != 0) {
		--_orderDelay;
		// check for end of song
		if (_orderDelay == 0 && _modData == 0) {
			_playing = false;
		}
	} else {
		_orderDelay = READ_BE_UINT16(_mod->moduleData + 2);
		debug(DBG_SFX, "curOrder=%d/%d _orderDelay=%d\n", _curOrder, _numOrders, _orderDelay);
		int16 period = 0;
		for (int ch = 0; ch < 3; ++ch) {
			const uint8 *sampleData = 0;
			uint8 b = *_modData++;
			if (b != 0) {
				--b;
				assert(b < 5);
				period = READ_BE_UINT16(_mod->moduleData + 4 + b * 2);
				sampleData = _mod->sampleData[b];
			}
			b = *_modData++;
			if (b != 0) {
				int16 per = period + (b - 1);
				if (per >= 0 && per < 40) {
					per = _periodTable[per];
				} else if (per == -3) {
					per = 0xA0;
				} else {
					per = 0x71;
				}
				playSample(ch, sampleData, per);
			}
		}
		++_curOrder;
		if (_curOrder >= _numOrders) {
			debug(DBG_SFX, "End of song");
			_orderDelay += 20;
			_modData = 0;
		}
	}
}

inline void addclamp(int16& a, int b) {
	int add = a + b;
	if (add < -32767) {
		add = -32767;
	} else if (add > 32767) {
		add = 32767;
	}
	a = add;
}

void SfxPlayer::mixSamples(int16 *buf, int samplesLen) {
	for (int i = 0; i < NUM_CHANNELS; ++i) {
		SampleInfo *si = &_samples[i];
		if (si->data) {
			int16 *mixbuf = buf;
			int len = si->len << FRAC_BITS;
			int loopLen = si->loopLen << FRAC_BITS;
			int loopPos = si->loopPos << FRAC_BITS;
			int deltaPos = (si->freq << FRAC_BITS) / _mix->getSampleRate();
			int curLen = samplesLen;
			int pos = si->pos;
			while (curLen != 0) {
				int count;
				if (loopLen > (2 << FRAC_BITS)) {
					assert(si->loopPos + si->loopLen <= si->len);
					if (pos >= loopPos + loopLen) {
						pos -= loopLen;
					}
					count = MIN(curLen, (loopPos + loopLen - pos - 1) / deltaPos + 1);
					curLen -= count;
				} else {
					if (pos >= len) {
						count = 0;
					} else {
						count = MIN(curLen, (len - pos - 1) / deltaPos + 1);
					}
					curLen = 0;
				}
				while (count--) {
					int out = resample3Pt(si, pos, deltaPos, FRAC_BITS);
					addclamp(*mixbuf++, out * Mixer::MIX_AMPLIFICATIION * si->vol / 64);
					pos += deltaPos;
				}
			}
			si->pos = pos;
     	}
	}
}

bool SfxPlayer::mix(int16 *buf, int len) {
	if (_playing) {
		memset(buf, 0, len*2);
		const int samplesPerTick = _mix->getSampleRate() / 50;
		while (len != 0) {
			if (_samplesLeft == 0) {
				handleTick();
				_samplesLeft = samplesPerTick;
			}
			int count = _samplesLeft;
			if (count > len) {
				count = len;
			}
			_samplesLeft -= count;
			len -= count;
			mixSamples(buf, count);
			buf += count;
		}
	}
	return _playing;
}

bool SfxPlayer::mixCallback(void *param, int16 *buf, int len) {
	return ((SfxPlayer *)param)->mix(buf, len);
}
