/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 "audio/softsynth/fmtowns_pc98/towns_audio.h"
#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"

#include "common/debug.h"
#include "common/endian.h"
#include "common/util.h"
#include "common/textconsole.h"
#include "backends/audiocd/audiocd.h"

class TownsAudio_WaveTable {
friend class TownsAudioInterfaceInternal;
friend class TownsAudio_PcmChannel;
public:
	TownsAudio_WaveTable();
	~TownsAudio_WaveTable();

private:
	void readHeader(const uint8 *buffer);
	void readData(const uint8 *buffer);
	void clear();

	char name[9];
	int32 id;
	uint32 size;
	uint32 loopStart;
	uint32 loopLen;
	uint16 rate;
	uint16 rateOffs;
	uint16 baseNote;
	int8 *data;
};

class TownsAudio_PcmChannel {
public:
	TownsAudio_PcmChannel();
	~TownsAudio_PcmChannel();

	void clear();

	void loadData(TownsAudio_WaveTable *w);
	void loadData(uint8 *buffer, uint32 size);

	int initInstrument(uint8 &note, TownsAudio_WaveTable *&tables, int numTables);
	void keyOn(uint8 note, uint8 velo, TownsAudio_WaveTable *w);
	void keyOff();

	void updateEnvelopeGenerator();

	void setInstrument(uint8 *instr);
	void setLevel(uint8 lvl);
	void setPitch(uint32 pt);
	void setBalance(uint8 blc);

	void updateOutput();
	int32 currentSampleLeft();
	int32 currentSampleRight();

	bool _keyPressed;
	bool _reserved;
	bool _activeKey;
	bool _activeEffect;
	bool _activeOutput;

private:
	void setupLoop(uint32 loopStart, uint32 loopLen);
	void setNote(uint8 note, TownsAudio_WaveTable *w, bool stepLimit = false);
	void setVelo(uint8 velo);

	void envAttack();
	void envDecay();
	void envSustain();
	void envRelease();

	uint8 *_curInstrument;

	uint8 _note;

	uint8 _velo;
	uint8 _level;
	uint8 _tl;

	uint8 _panLeft;
	uint8 _panRight;

	int8 *_data;

	uint32 _loopStart;
	uint32 _loopLen;
	uint32 _dataEnd;

	uint16 _stepNote;
	uint16 _stepPitch;
	uint16 _step;

	uint32 _pos;

	uint8 _envTotalLevel;
	uint8 _envAttackRate;
	uint8 _envDecayRate;
	uint8 _envSustainLevel;
	uint8 _envSustainRate;
	uint8 _envReleaseRate;
	int16 _envStep;
	int16 _envCurrentLevel;

	EnvelopeState _envState;

	int8 *_extData;

	static const uint16 _pcmPhase1[];
	static const uint16 _pcmPhase2[];
};

class TownsAudioInterfaceInternal : public TownsPC98_FmSynth {
private:
	TownsAudioInterfaceInternal(Audio::Mixer *mixer, TownsAudioInterface *owner, TownsAudioInterfacePluginDriver *driver);
public:
	~TownsAudioInterfaceInternal();

	static TownsAudioInterfaceInternal *addNewRef(Audio::Mixer *mixer, TownsAudioInterface *owner, TownsAudioInterfacePluginDriver *driver);
	static void releaseRef(TownsAudioInterface *owner);

	bool init();

	int callback(int command, ...);
	int processCommand(int command, va_list &args);

	void setMusicVolume(int volume);
	void setSoundEffectVolume(int volume);
	// Defines the channels used as sound effect channels for the purpose of ScummVM GUI volume control.
	// The first 6 bits are the 6 fm channels. The next 8 bits are pcm channels.
	void setSoundEffectChanMask(int mask);

private:
	bool assignPluginDriver(TownsAudioInterface *owner, TownsAudioInterfacePluginDriver *driver);
	void removePluginDriver(TownsAudioInterface *owner);

	void nextTickEx(int32 *buffer, uint32 bufferSize);

	void timerCallbackA();
	void timerCallbackB();

	typedef int (TownsAudioInterfaceInternal::*TownsAudioIntfCallback)(va_list &);
	const TownsAudioIntfCallback *_intfOpcodes;

	int intf_reset(va_list &args);
	int intf_keyOn(va_list &args);
	int intf_keyOff(va_list &args);
	int intf_setPanPos(va_list &args);
	int intf_setInstrument(va_list &args);
	int intf_loadInstrument(va_list &args);
	int intf_setPitch(va_list &args);
	int intf_setLevel(va_list &args);
	int intf_chanOff(va_list &args);
	int intf_writeReg(va_list &args);
	int intf_writeRegBuffer(va_list &args);
	int intf_readRegBuffer(va_list &args);
	int intf_setTimerA(va_list &args);
	int intf_setTimerB(va_list &args);
	int intf_enableTimerA(va_list &args);
	int intf_enableTimerB(va_list &args);
	int intf_loadSamples(va_list &args);
	int intf_reserveEffectChannels(va_list &args);
	int intf_loadWaveTable(va_list &args);
	int intf_unloadWaveTable(va_list &args);
	int intf_pcmPlayEffect(va_list &args);
	int intf_pcmChanOff(va_list &args);
	int intf_pcmEffectPlaying(va_list &args);
	int intf_pcmDisableAllChannels(va_list &args);
	int intf_fmKeyOn(va_list &args);
	int intf_fmKeyOff(va_list &args);
	int intf_fmSetPanPos(va_list &args);
	int intf_fmSetInstrument(va_list &args);
	int intf_fmLoadInstrument(va_list &args);
	int intf_fmSetPitch(va_list &args);
	int intf_fmSetLevel(va_list &args);
	int intf_fmReset(va_list &args);
	int intf_setOutputVolume(va_list &args);
	int intf_resetOutputVolume(va_list &args);
	int intf_getOutputVolume(va_list &args);
	int intf_setOutputMute(va_list &args);
	int intf_cdaToggle(va_list &args);
	int intf_getOutputVolume2(va_list &args);
	int intf_getOutputMute(va_list &args);
	int intf_pcmUpdateEnvelopeGenerator(va_list &args);

	int intf_notImpl(va_list &args);

	void fmReset();
	int fmKeyOn(int chan, int note, int velo);
	int fmKeyOff(int chan);
	int fmChanOff(int chan);
	int fmSetPanPos(int chan, int mode);
	int fmSetInstrument(int chan, int instrId);
	int fmLoadInstrument(int instrId, const uint8 *data);
	int fmSetPitch(int chan, int pitch);
	int fmSetLevel(int chan, int lvl);

	void bufferedWriteReg(uint8 part, uint8 regAddress, uint8 value);

	uint8 _fmChanPlaying;
	uint8 _fmChanNote[6];
	int16 _fmChanPitch[6];

	uint8 *_fmSaveReg[2];
	uint8 *_fmInstruments;

	void pcmReset();
	int pcmKeyOn(int chan, int note, int velo);
	int pcmKeyOff(int chan);
	int pcmChanOff(int chan);
	int pcmSetPanPos(int chan, int mode);
	int pcmSetInstrument(int chan, int instrId);
	int pcmLoadInstrument(int instrId, const uint8 *data);
	int pcmSetPitch(int chan, int pitch);
	int pcmSetLevel(int chan, int lvl);

	TownsAudio_PcmChannel *_pcmChan;

	uint8 _numReservedChannels;
	uint8 *_pcmInstruments;

	TownsAudio_WaveTable *_waveTables;
	uint8 _numWaveTables;
	uint32 _waveTablesTotalDataSize;

	void updateOutputVolume();
	void updateOutputVolumeInternal();
	uint8 _outputVolumeFlags;
	uint8 _outputLevel[16];
	uint8 _outputMute[16];
	bool _updateOutputVol;

	const float _baserate;
	uint32 _timerBase;
	uint32 _tickLength;
	uint32 _timer;

	uint16 _musicVolume;
	uint16 _sfxVolume;
	int _pcmSfxChanMask;

	TownsAudioInterfacePluginDriver *_drv;
	void *_drvOwner;
	bool _ready;

	static TownsAudioInterfaceInternal *_refInstance;
	static int _refCount;

	static const uint8 _chanFlags[];
	static const uint16 _frequency[];
	static const uint8 _carrier[];
	static const uint8 _fmDefaultInstrument[];
};

TownsAudioInterfaceInternal::TownsAudioInterfaceInternal(Audio::Mixer *mixer, TownsAudioInterface *owner, TownsAudioInterfacePluginDriver *driver) :
	TownsPC98_FmSynth(mixer, kTypeTowns),
	_fmInstruments(0), _pcmInstruments(0), _pcmChan(0), _waveTables(0), _waveTablesTotalDataSize(0),
	_baserate(55125.0f / (float)mixer->getOutputRate()), _tickLength(0), _timer(0), _drv(driver), _drvOwner(owner),
	_pcmSfxChanMask(0),	_musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume),
	_outputVolumeFlags(0), _fmChanPlaying(0),
	_numReservedChannels(0), _numWaveTables(0), _updateOutputVol(false), _ready(false) {

#define INTCB(x) &TownsAudioInterfaceInternal::intf_##x
	static const TownsAudioIntfCallback intfCb[] = {
		// 0
		INTCB(reset),
		INTCB(keyOn),
		INTCB(keyOff),
		INTCB(setPanPos),
		// 4
		INTCB(setInstrument),
		INTCB(loadInstrument),
		INTCB(notImpl),
		INTCB(setPitch),
		// 8
		INTCB(setLevel),
		INTCB(chanOff),
		INTCB(notImpl),
		INTCB(notImpl),
		// 12
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		// 16
		INTCB(notImpl),
		INTCB(writeReg),
		INTCB(notImpl),
		INTCB(writeRegBuffer),
		// 20
		INTCB(readRegBuffer),
		INTCB(setTimerA),
		INTCB(setTimerB),
		INTCB(enableTimerA),
		// 24
		INTCB(enableTimerB),
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		// 28
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		// 32
		INTCB(loadSamples),
		INTCB(reserveEffectChannels),
		INTCB(loadWaveTable),
		INTCB(unloadWaveTable),
		// 36
		INTCB(notImpl),
		INTCB(pcmPlayEffect),
		INTCB(notImpl),
		INTCB(pcmChanOff),
		// 40
		INTCB(pcmEffectPlaying),
		INTCB(pcmDisableAllChannels),
		INTCB(notImpl),
		INTCB(notImpl),
		// 44
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		// 48
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(fmKeyOn),
		INTCB(fmKeyOff),
		// 52
		INTCB(fmSetPanPos),
		INTCB(fmSetInstrument),
		INTCB(fmLoadInstrument),
		INTCB(notImpl),
		// 56
		INTCB(fmSetPitch),
		INTCB(fmSetLevel),
		INTCB(fmReset),
		INTCB(notImpl),
		// 60
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		// 64
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(setOutputVolume),
		// 68
		INTCB(resetOutputVolume),
		INTCB(getOutputVolume),
		INTCB(setOutputMute),
		INTCB(notImpl),
		// 72
		INTCB(notImpl),
		INTCB(cdaToggle),
		INTCB(getOutputVolume2),
		INTCB(getOutputMute),
		// 76
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		INTCB(notImpl),
		// 80
		INTCB(pcmUpdateEnvelopeGenerator),
		INTCB(notImpl)
	};
#undef INTCB

	_intfOpcodes = intfCb;

	memset(_fmSaveReg, 0, sizeof(_fmSaveReg));
	memset(_fmChanNote, 0, sizeof(_fmChanNote));
	memset(_fmChanPitch, 0, sizeof(_fmChanPitch));
	memset(_outputLevel, 0, sizeof(_outputLevel));
	memset(_outputMute, 0, sizeof(_outputMute));

	_timerBase = (uint32)(_baserate * 1000000.0f);
	_tickLength = 2 * _timerBase;
}

TownsAudioInterfaceInternal::~TownsAudioInterfaceInternal() {
	deinit();
	Common::StackLock lock(_mutex);
	_ready = false;

	delete[] _fmSaveReg[0];
	delete[] _fmSaveReg[1];
	delete[] _fmInstruments;
	delete[] _pcmInstruments;
	delete[] _waveTables;
	delete[] _pcmChan;
}

TownsAudioInterfaceInternal *TownsAudioInterfaceInternal::addNewRef(Audio::Mixer *mixer, TownsAudioInterface *owner, TownsAudioInterfacePluginDriver *driver) {
	_refCount++;
	if (_refCount == 1 && _refInstance == 0)
		_refInstance = new TownsAudioInterfaceInternal(mixer, owner, driver);
	else if (_refCount < 2 || _refInstance == 0)
		error("TownsAudioInterfaceInternal::addNewRef(): Internal reference management failure");
	else if (!_refInstance->assignPluginDriver(owner, driver))
		error("TownsAudioInterfaceInternal::addNewRef(): Plugin driver conflict");

	return _refInstance;
}

void TownsAudioInterfaceInternal::releaseRef(TownsAudioInterface *owner) {
	if (!_refCount)
		return;

	_refCount--;

	if (_refCount) {
		if (_refInstance)
			_refInstance->removePluginDriver(owner);
	} else {
		delete _refInstance;
		_refInstance = 0;
	}
}

bool TownsAudioInterfaceInternal::init() {
	if (_ready)
		return true;

	_fmSaveReg[0] = new uint8[256];
	_fmSaveReg[1] = new uint8[256];
	_fmInstruments = new uint8[128 * 48];
	_pcmInstruments = new uint8[32 * 128];
	_waveTables = new TownsAudio_WaveTable[128];
	_pcmChan = new TownsAudio_PcmChannel[8];

	_timer = 0;

	if (!TownsPC98_FmSynth::init())
		return false;


	setVolumeChannelMasks(-1, 0);

	_ready = true;
	callback(0);

	return true;
}

int TownsAudioInterfaceInternal::callback(int command, ...) {
	Common::StackLock lock(_mutex);
	if (!_ready)
		return 1;

	va_list args;
	va_start(args, command);

	int res = processCommand(command, args);

	va_end(args);
	return res;
}

int TownsAudioInterfaceInternal::processCommand(int command, va_list &args) {
	Common::StackLock lock(_mutex);
	if (!_ready)
		return 1;

	if (command < 0 || command > 81)
		return 4;
	
	int res = (this->*_intfOpcodes[command])(args);

	return res;
}

void TownsAudioInterfaceInternal::setMusicVolume(int volume) {
	Common::StackLock lock(_mutex);
	_musicVolume = CLIP<uint16>(volume, 0, Audio::Mixer::kMaxMixerVolume);
	setVolumeIntern(_musicVolume, _sfxVolume);
}

void TownsAudioInterfaceInternal::setSoundEffectVolume(int volume) {
	Common::StackLock lock(_mutex);
	_sfxVolume = CLIP<uint16>(volume, 0, Audio::Mixer::kMaxMixerVolume);
	setVolumeIntern(_musicVolume, _sfxVolume);
}

void TownsAudioInterfaceInternal::setSoundEffectChanMask(int mask) {
	Common::StackLock lock(_mutex);
	_pcmSfxChanMask = mask >> 6;
	mask &= 0x3f;
	setVolumeChannelMasks(~mask, mask);
}

bool TownsAudioInterfaceInternal::assignPluginDriver(TownsAudioInterface *owner, TownsAudioInterfacePluginDriver *driver) {
	Common::StackLock lock(_mutex);
	if (_refCount <= 1)
		return true;

	if (_drv) {
		if (driver && driver != _drv)
			return false;
	} else {
		_drv = driver;
		_drvOwner = owner;
	}

	return true;
}

void TownsAudioInterfaceInternal::removePluginDriver(TownsAudioInterface *owner) {
	Common::StackLock lock(_mutex);
	if (_drvOwner == owner)
		_drv = 0;
}

void TownsAudioInterfaceInternal::nextTickEx(int32 *buffer, uint32 bufferSize) {
	Common::StackLock lock(_mutex);
	if (!_ready)
		return;

	if (_updateOutputVol)
		updateOutputVolumeInternal();

	for (uint32 i = 0; i < bufferSize; i++) {
		_timer += _tickLength;
		while (_timer > 0x514767) {
			_timer -= 0x514767;

			for (int ii = 0; ii < 8; ii++)
				_pcmChan[ii].updateOutput();
		}

		int32 finOutL = 0;
		int32 finOutR = 0;

		for (int ii = 0; ii < 8; ii++) {
			if (_pcmChan[ii]._activeOutput) {
				int32 oL = _pcmChan[ii].currentSampleLeft();
				int32 oR = _pcmChan[ii].currentSampleRight();
				if ((1 << ii) & (~_pcmSfxChanMask)) {
					oL = (oR * _musicVolume) / Audio::Mixer::kMaxMixerVolume;
					oR = (oR * _musicVolume) / Audio::Mixer::kMaxMixerVolume;
				}
				if ((1 << ii) & _pcmSfxChanMask) {
					oL = (oL * _sfxVolume) / Audio::Mixer::kMaxMixerVolume;
					oR = (oR * _sfxVolume) / Audio::Mixer::kMaxMixerVolume;
				}
				finOutL += oL;
				finOutR += oR;

				if (!(_pcmChan[ii]._activeKey || _pcmChan[ii]._activeEffect))
					_pcmChan[ii]._activeOutput = false;
			}
		}

		buffer[i << 1] += finOutL;
		buffer[(i << 1) + 1] += finOutR;
	}
}

void TownsAudioInterfaceInternal::timerCallbackA() {
	Common::StackLock lock(_mutex);
	if (_drv && _ready)
		_drv->timerCallback(0);
}

void TownsAudioInterfaceInternal::timerCallbackB() {
	Common::StackLock lock(_mutex);
	if (_ready) {
		if (_drv)
			_drv->timerCallback(1);
		callback(80);
	}
}

int TownsAudioInterfaceInternal::intf_reset(va_list &args) {
	fmReset();
	pcmReset();
	callback(68);
	return 0;
}

int TownsAudioInterfaceInternal::intf_keyOn(va_list &args) {
	int chan = va_arg(args, int);
	int note = va_arg(args, int);
	int velo = va_arg(args, int);
	return (chan & 0x40) ? pcmKeyOn(chan, note, velo) : fmKeyOn(chan, note, velo);
}

int TownsAudioInterfaceInternal::intf_keyOff(va_list &args) {
	int chan = va_arg(args, int);
	return (chan & 0x40) ? pcmKeyOff(chan) : fmKeyOff(chan);
}

int TownsAudioInterfaceInternal::intf_setPanPos(va_list &args) {
	int chan = va_arg(args, int);
	int mode = va_arg(args, int);
	return (chan & 0x40) ? pcmSetPanPos(chan, mode) : fmSetPanPos(chan, mode);
}

int TownsAudioInterfaceInternal::intf_setInstrument(va_list &args) {
	int chan = va_arg(args, int);
	int instrId = va_arg(args, int);
	return (chan & 0x40) ? pcmSetInstrument(chan, instrId) : fmSetInstrument(chan, instrId);
}

int TownsAudioInterfaceInternal::intf_loadInstrument(va_list &args) {
	int chanType = va_arg(args, int);
	int instrId = va_arg(args, int);
	uint8 *instrData = va_arg(args, uint8 *);
	return (chanType & 0x40) ? pcmLoadInstrument(instrId, instrData) : fmLoadInstrument(instrId, instrData);
}

int TownsAudioInterfaceInternal::intf_setPitch(va_list &args) {
	int chan = va_arg(args, int);
	int16 pitch = (int16)(va_arg(args, int) & 0xffff);
	return (chan & 0x40) ? pcmSetPitch(chan, pitch) : fmSetPitch(chan, pitch);
}

int TownsAudioInterfaceInternal::intf_setLevel(va_list &args) {
	int chan = va_arg(args, int);
	int lvl = va_arg(args, int);
	return (chan & 0x40) ? pcmSetLevel(chan, lvl) : fmSetLevel(chan, lvl);
}

int TownsAudioInterfaceInternal::intf_chanOff(va_list &args) {
	int chan = va_arg(args, int);
	return (chan & 0x40) ? pcmChanOff(chan) : fmChanOff(chan);
}

int TownsAudioInterfaceInternal::intf_writeReg(va_list &args) {
	int part = va_arg(args, int) ? 1 : 0;
	int reg = va_arg(args, int);
	int val = va_arg(args, int);
	if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xb6))
		return 3;

	bufferedWriteReg(part, reg, val);
	return 0;
}

int TownsAudioInterfaceInternal::intf_writeRegBuffer(va_list &args) {
	int part = va_arg(args, int) ? 1 : 0;
	int reg = va_arg(args, int);
	int val = va_arg(args, int);

	if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xef))
		return 3;

	_fmSaveReg[part][reg] = val;
	return 0;
}

int TownsAudioInterfaceInternal::intf_readRegBuffer(va_list &args) {
	int part = va_arg(args, int) ? 1 : 0;
	int reg = va_arg(args, int);
	uint8 *dst = va_arg(args, uint8 *);
	*dst = 0;

	if ((!part && reg < 0x20) || (part && reg < 0x30) || (reg > 0xef))
		return 3;

	*dst = _fmSaveReg[part][reg];
	return 0;
}

int TownsAudioInterfaceInternal::intf_setTimerA(va_list &args) {
	int enable = va_arg(args, int);
	int tempo = va_arg(args, int);

	if (enable) {
		bufferedWriteReg(0, 0x25, tempo & 3);
		bufferedWriteReg(0, 0x24, (tempo >> 2) & 0xff);
		bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x05);
	} else {
		bufferedWriteReg(0, 0x27, (_fmSaveReg[0][0x27] & 0xfa) | 0x10);
	}

	return 0;
}

int TownsAudioInterfaceInternal::intf_setTimerB(va_list &args) {
	int enable = va_arg(args, int);
	int tempo = va_arg(args, int);

	if (enable) {
		bufferedWriteReg(0, 0x26, tempo & 0xff);
		bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x0A);
	} else {
		bufferedWriteReg(0, 0x27, (_fmSaveReg[0][0x27] & 0xf5) | 0x20);
	}

	return 0;
}

int TownsAudioInterfaceInternal::intf_enableTimerA(va_list &args) {
	bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x15);
	return 0;
}

int TownsAudioInterfaceInternal::intf_enableTimerB(va_list &args) {
	bufferedWriteReg(0, 0x27, _fmSaveReg[0][0x27] | 0x2a);
	return 0;
}

int TownsAudioInterfaceInternal::intf_loadSamples(va_list &args) {
	uint32 dest = va_arg(args, uint32);
	int size = va_arg(args, int);
	uint8 *src = va_arg(args, uint8*);

	if (dest >= 65536 || size == 0 || size > 65536)
		return 3;
	if (size + dest > 65536)
		// EOB II FM-TOWNS tries to load more than 65536 bytes of wave sounds for the outro sequence.
		// This means that some sfx would not play. Since we don't really need the memory limit,
		// I have commented out the error return and added a debug message instead.
		debugN(9, "FM-TOWNS AUDIO: exceeding wave memory size by %d bytes", size + dest - 65536);
		// return 5;

	int dwIndex = _numWaveTables - 1;
	for (uint32 t = _waveTablesTotalDataSize; dwIndex && (dest < t); dwIndex--)
		t -= _waveTables[dwIndex].size;

	TownsAudio_WaveTable *s = &_waveTables[dwIndex];
	_waveTablesTotalDataSize -= s->size;
	s->size = size;
	s->readData(src);
	_waveTablesTotalDataSize += s->size;

	return 0;
}

int TownsAudioInterfaceInternal::intf_reserveEffectChannels(va_list &args) {
	int numChan = va_arg(args, int);
	if (numChan > 8)
		return 3;
	if ((numChan << 13) + _waveTablesTotalDataSize > 65536)
		return 5;

	if (numChan == _numReservedChannels)
		return 0;

	if (numChan < _numReservedChannels) {
		int c = 8 - _numReservedChannels;
		for (int i = numChan; i; i--)
			_pcmChan[c--]._activeEffect = false;
	} else {
		int c = 7 - _numReservedChannels;
		for (int i = numChan - _numReservedChannels; i; i--) {
			_pcmChan[c]._keyPressed = false;
			_pcmChan[c--]._activeKey = false;
		}
	}

	_numReservedChannels = numChan;
	for (int i = 0; i < 8; i++)
		_pcmChan[i]._reserved = i >= (8 - _numReservedChannels) ? true : false;

	return 0;
}

int TownsAudioInterfaceInternal::intf_loadWaveTable(va_list &args) {
	uint8 *data = va_arg(args, uint8 *);
	if (_numWaveTables > 127)
		return 3;

	TownsAudio_WaveTable w;
	w.readHeader(data);
	if (!w.size)
		return 6;

	if (_waveTablesTotalDataSize + w.size > 65504)
		return 5;

	callback(41);

	for (int i = 0; i < _numWaveTables; i++) {
		if (_waveTables[i].id == w.id)
			return 10;
	}

	TownsAudio_WaveTable *s = &_waveTables[_numWaveTables++];
	s->readHeader(data);

	_waveTablesTotalDataSize += s->size;
	int res = callback(32, _waveTablesTotalDataSize, s->size, data + 32);
	if (res) {
		_waveTablesTotalDataSize -= s->size;
		_numWaveTables--;
	}

	return res;
}

int TownsAudioInterfaceInternal::intf_unloadWaveTable(va_list &args) {
	int id = va_arg(args, int);
	callback(41);

	if (id == -1) {
		for (int i = 0; i < 128; i++)
			_waveTables[i].clear();
		_numWaveTables = 0;
		_waveTablesTotalDataSize = 0;
	} else {
		if (_waveTables) {
			for (int i = 0; i < _numWaveTables; i++) {
				if (_waveTables[i].id == id) {
					_numWaveTables--;
					_waveTablesTotalDataSize -= _waveTables[i].size;
					_waveTables[i].clear();
					for (; i < _numWaveTables; i++)
						_waveTables[i] = _waveTables[i + 1];
					return 0;
				}
			}
			return 9;
		}
	}

	return 0;
}

int TownsAudioInterfaceInternal::intf_pcmPlayEffect(va_list &args) {
	int chan = va_arg(args, int);
	int note = va_arg(args, int);
	int velo = va_arg(args, int);
	uint8 *data = va_arg(args, uint8 *);

	if (chan < 0x40 || chan > 0x47)
		return 1;

	if (note & 0x80 || velo & 0x80)
		return 3;

	chan -= 0x40;

	if (!_pcmChan[chan]._reserved)
		return 7;

	if (_pcmChan[chan]._activeEffect)
		return 2;

	TownsAudio_WaveTable w;
	w.readHeader(data);

	if (w.size < (w.loopStart + w.loopLen))
		return 13;

	if (!w.size)
		return 6;

	TownsAudio_PcmChannel *p = &_pcmChan[chan];

	p->loadData(data + 32, w.size);
	p->keyOn(note, velo, &w);

	return 0;
}

int TownsAudioInterfaceInternal::intf_pcmChanOff(va_list &args) {
	int chan = va_arg(args, int);
	pcmChanOff(chan);
	return 0;
}

int TownsAudioInterfaceInternal::intf_pcmEffectPlaying(va_list &args) {
	int chan = va_arg(args, int);
	if (chan < 0x40 || chan > 0x47)
		return 1;
	chan -= 0x40;
	return _pcmChan[chan]._activeEffect ? 1 : 0;
}

int TownsAudioInterfaceInternal::intf_pcmDisableAllChannels(va_list &args) {
	for (int i = 0; i < 8; ++i)
		_pcmChan[i]._activeOutput = false;
	return 0;
}

int TownsAudioInterfaceInternal::intf_fmKeyOn(va_list &args) {
	int chan = va_arg(args, int);
	int note = va_arg(args, int);
	int velo = va_arg(args, int);
	return fmKeyOn(chan, note, velo);
}

int TownsAudioInterfaceInternal::intf_fmKeyOff(va_list &args) {
	int chan = va_arg(args, int);
	return fmKeyOff(chan);
}

int TownsAudioInterfaceInternal::intf_fmSetPanPos(va_list &args) {
	int chan = va_arg(args, int);
	int mode = va_arg(args, int);
	return fmSetPanPos(chan, mode);
}

int TownsAudioInterfaceInternal::intf_fmSetInstrument(va_list &args) {
	int chan = va_arg(args, int);
	int instrId = va_arg(args, int);
	return fmSetInstrument(chan, instrId);
}

int TownsAudioInterfaceInternal::intf_fmLoadInstrument(va_list &args) {
	int instrId = va_arg(args, int);
	uint8 *instrData = va_arg(args, uint8 *);
	return fmLoadInstrument(instrId, instrData);
}

int TownsAudioInterfaceInternal::intf_fmSetPitch(va_list &args) {
	int chan = va_arg(args, int);
	uint16 freq = va_arg(args, int) & 0xffff;
	return fmSetPitch(chan, freq);
}

int TownsAudioInterfaceInternal::intf_fmSetLevel(va_list &args) {
	int chan = va_arg(args, int);
	int lvl = va_arg(args, int);
	return fmSetLevel(chan, lvl);
}

int TownsAudioInterfaceInternal::intf_fmReset(va_list &args) {
	fmReset();
	return 0;
}

int TownsAudioInterfaceInternal::intf_setOutputVolume(va_list &args) {
	int chanType = va_arg(args, int);
	int left = va_arg(args, int);
	int right = va_arg(args, int);

	if (left & 0xff80 || right & 0xff80)
		return 3;

	static const uint8 flags[] = { 0x0C, 0x30, 0x40, 0x80 };

	uint8 chan = (chanType & 0x40) ? 8 : 12;

	chanType &= 3;
	left = (left & 0x7e) >> 1;
	right = (right & 0x7e) >> 1;

	if (chan == 12)
		_outputVolumeFlags |= flags[chanType];
	else
		_outputVolumeFlags &= ~flags[chanType];

	if (chanType > 1) {
		_outputLevel[chan + chanType] = left;
		_outputMute[chan + chanType] = 0;
	} else {
		if (chanType == 0)
			chan -= 8;
		_outputLevel[chan] = left;
		_outputLevel[chan + 1] = right;
		_outputMute[chan] = _outputMute[chan + 1] = 0;
	}

	updateOutputVolume();

	return 0;
}

int TownsAudioInterfaceInternal::intf_resetOutputVolume(va_list &args) {
	memset(_outputLevel, 0, sizeof(_outputLevel));
	_outputVolumeFlags = 0;
	updateOutputVolume();
	return 0;
}

int TownsAudioInterfaceInternal::intf_getOutputVolume(va_list &args) {
	int chanType = va_arg(args, int);
	int *left = va_arg(args, int*);
	int *right = va_arg(args, int*);

	uint8 chan = (chanType & 0x40) ? 8 : 12;
	chanType &= 3;

	if (chanType > 1) {
		*left = _outputLevel[chan + chanType] & 0x3f;
	} else {
		if (chanType == 0)
			chan -= 8;
		*left = _outputLevel[chan] & 0x3f;
		*right = _outputLevel[chan + 1] & 0x3f;
	}

	return 0;
}

int TownsAudioInterfaceInternal::intf_setOutputMute(va_list &args) {
	int flags = va_arg(args, int);
	_outputVolumeFlags = flags;
	uint8 mute = flags & 3;
	uint8 f = flags & 0xff;

	memset(_outputMute, 1, 8);
	if (mute & 2)
		memset(&_outputMute[12], 1, 4);
	if (mute & 1)
		memset(&_outputMute[8], 1, 4);

	_outputMute[(f < 0x80) ? 11 : 15] = 0;
	f += f;
	_outputMute[(f < 0x80) ? 10 : 14] = 0;
	f += f;
	_outputMute[(f < 0x80) ? 8 : 12] = 0;
	f += f;
	_outputMute[(f < 0x80) ? 9 : 13] = 0;
	f += f;
	_outputMute[(f < 0x80) ? 0 : 4] = 0;
	f += f;
	_outputMute[(f < 0x80) ? 1 : 5] = 0;
	f += f;

	updateOutputVolume();
	return 0;
}

int TownsAudioInterfaceInternal::intf_cdaToggle(va_list &args) {
	//int mode = va_arg(args, int);
	//_unkMask = mode ? 0x7f : 0x3f;
	return 0;
}

int TownsAudioInterfaceInternal::intf_getOutputVolume2(va_list &args) {
	return 0;
}

int TownsAudioInterfaceInternal::intf_getOutputMute (va_list &args) {
	return 0;
}

int TownsAudioInterfaceInternal::intf_pcmUpdateEnvelopeGenerator(va_list &args) {
	for (int i = 0; i < 8; i++)
		_pcmChan[i].updateEnvelopeGenerator();
	return 0;
}

int TownsAudioInterfaceInternal::intf_notImpl(va_list &args) {
	return 4;
}

void TownsAudioInterfaceInternal::fmReset() {
	TownsPC98_FmSynth::reset();

	_fmChanPlaying = 0;
	memset(_fmChanNote, 0, sizeof(_fmChanNote));
	memset(_fmChanPitch, 0, sizeof(_fmChanPitch));

	memset(_fmSaveReg[0], 0, 240);
	memset(&_fmSaveReg[0][240], 0x7f, 16);
	memset(_fmSaveReg[1], 0, 256);
	memset(&_fmSaveReg[1][240], 0x7f, 16);
	_fmSaveReg[0][243] = _fmSaveReg[0][247] = _fmSaveReg[0][251] = _fmSaveReg[0][255] = _fmSaveReg[1][243] = _fmSaveReg[1][247] = _fmSaveReg[1][251] = _fmSaveReg[1][255] = 0xff;

	for (int i = 0; i < 128; i++)
		fmLoadInstrument(i, _fmDefaultInstrument);

	bufferedWriteReg(0, 0x21, 0);
	bufferedWriteReg(0, 0x2C, 0x80);
	bufferedWriteReg(0, 0x2B, 0);
	bufferedWriteReg(0, 0x27, 0x30);

	for (int i = 0; i < 6; i++) {
		fmKeyOff(i);
		fmSetInstrument(i, 0);
		fmSetLevel(i, 127);
	}
}

int TownsAudioInterfaceInternal::fmKeyOn(int chan, int note, int velo) {
	if (chan > 5)
		return 1;
	if (note < 12 || note > 107 || (velo & 0x80))
		return 3;
	if (_fmChanPlaying & _chanFlags[chan])
		return 2;

	_fmChanPlaying |= _chanFlags[chan];
	note -= 12;

	_fmChanNote[chan] = note;
	int16 pitch = _fmChanPitch[chan];

	uint8 part = chan > 2 ? 1 : 0;
	if (chan > 2)
		chan -= 3;

	int frq = 0;
	uint8 bl = 0;

	if (note) {
		frq = _frequency[(note - 1) % 12];
		bl = (note - 1) / 12;
	} else {
		frq = 616;
	}

	frq += pitch;

	if (frq < 616) {
		if (!bl) {
			frq = 616;
		} else {
			frq += 616;
			--bl;
		}
	} else if (frq > 1232) {
		if (bl == 7) {
			frq = 15500;
		} else {
			frq -= 616;
			++bl;
		}
	}

	frq |= (bl << 11);

	bufferedWriteReg(part, chan + 0xa4, (frq >> 8) & 0xff);
	bufferedWriteReg(part, chan + 0xa0, frq & 0xff);

	velo = (velo >> 2) + 96;
	uint16 c = _carrier[_fmSaveReg[part][0xb0 + chan] & 7];
	_fmSaveReg[part][0xe0 + chan] = velo;

	for (uint8 reg = 0x40 + chan; reg < 0x50; reg += 4) {
		c += c;
		if (c & 0x100) {
			c &= 0xff;
			bufferedWriteReg(part, reg, (((((((_fmSaveReg[part][0x80 + reg] ^ 0x7f) * velo) >> 7) + 1) * _fmSaveReg[part][0xd0 + chan]) >> 7) + 1) ^ 0x7f);
		}
	}

	uint8 v = chan;
	if (part)
		v |= 4;

	for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4)
		writeReg(part, reg, _fmSaveReg[part][reg] | 0x0f);

	writeReg(0, 0x28, v);

	for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4)
		writeReg(part, reg, _fmSaveReg[part][reg]);

	bufferedWriteReg(0, 0x28, v | 0xf0);

	return 0;
}

int TownsAudioInterfaceInternal::fmKeyOff(int chan) {
	if (chan > 5)
		return 1;
	_fmChanPlaying &= ~_chanFlags[chan];
	if (chan > 2)
		chan++;
	bufferedWriteReg(0, 0x28, chan);
	return 0;
}

int TownsAudioInterfaceInternal::fmChanOff(int chan) {
	if (chan > 5)
		return 1;
	_fmChanPlaying &= ~_chanFlags[chan];

	uint8 part = chan > 2 ? 1 : 0;
	if (chan > 2)
		chan -= 3;

	for (uint8 reg = 0x80 + chan; reg < 0x90; reg += 4)
		writeReg(part, reg, _fmSaveReg[part][reg] | 0x0f);

	if (part)
		chan += 4;
	writeReg(0, 0x28, chan);
	return 0;
}

int TownsAudioInterfaceInternal::fmSetPanPos(int chan, int value) {
	if (chan > 5)
		return 1;

	uint8 part = chan > 2 ? 1 : 0;
	if (chan > 2)
		chan -= 3;

	if (value > 0x40)
		value = 0x40;
	else if (value < 0x40)
		value = 0x80;
	else
		value = 0xC0;

	bufferedWriteReg(part, 0xb4 + chan, (_fmSaveReg[part][0xb4 + chan] & 0x3f) | value);
	return 0;
}

int TownsAudioInterfaceInternal::fmSetInstrument(int chan, int instrId) {
	if (chan > 5)
		return 1;
	if (instrId > 127)
		return 3;

	uint8 part = chan > 2 ? 1 : 0;
	if (chan > 2)
		chan -= 3;

	uint8 *src = &_fmInstruments[instrId * 48 + 8];

	uint16 c = _carrier[src[24] & 7];
	uint8 reg = 0x30 + chan;

	for (; reg < 0x40; reg += 4)
		bufferedWriteReg(part, reg, *src++);

	for (; reg < 0x50; reg += 4) {
		uint8 v = *src++;
		_fmSaveReg[part][0x80 + reg] = _fmSaveReg[part][reg] = v;
		c += c;
		if (c & 0x100) {
			c &= 0xff;
			v = 127;
		}
		writeReg(part, reg, v);
	}

	for (; reg < 0x90; reg += 4)
		bufferedWriteReg(part, reg, *src++);

	reg += 0x20;
	bufferedWriteReg(part, reg, *src++);

	uint8 v = *src++;
	reg += 4;
	if (v < 64)
		v |= (_fmSaveReg[part][reg] & 0xc0);
	bufferedWriteReg(part, reg, v);

	return 0;
}

int TownsAudioInterfaceInternal::fmLoadInstrument(int instrId, const uint8 *data) {
	if (instrId > 127)
		return 3;
	assert(data);
	memcpy(&_fmInstruments[instrId * 48], data, 48);
	return 0;
}

int TownsAudioInterfaceInternal::fmSetPitch(int chan, int pitch) {
	if (chan > 5)
		return 1;

	uint8 bl = _fmChanNote[chan];
	int frq = 0;

	if (pitch < 0) {
		if (bl) {
			if (pitch < -8008)
				pitch = -8008;
			pitch *= -1;
			pitch /= 13;
			frq = _frequency[(bl - 1) % 12] - pitch;
			bl = (bl - 1) / 12;
			_fmChanPitch[chan] = -pitch;

			if (frq < 616) {
				if (bl) {
					frq += 616;
					bl--;
				} else {
					frq = 616;
					bl = 0;
				}
			}
		} else {
			frq = 616;
			bl = 0;
		}

	} else if (pitch > 0) {
		if (bl < 96) {
			if (pitch > 8008)
				pitch = 8008;
			pitch /= 13;

			if (bl) {
				frq = _frequency[(bl - 1) % 12] + pitch;
				bl = (bl - 1) / 12;
			} else {
				frq = 616;
				bl = 0;
			}

			_fmChanPitch[chan] = pitch;

			if (frq > 1232) {
				if (bl < 7) {
					frq -= 616;
					bl++;
				} else {
					frq = 1164;
					bl = 7;
				}
			} else {
				if (bl >= 7 && frq > 1164)
					frq = 1164;
			}

		} else {
			frq = 1164;
			bl = 7;
		}
	} else {
		_fmChanPitch[chan] = 0;
		if (bl) {
			frq = _frequency[(bl - 1) % 12];
			bl = (bl - 1) / 12;
		} else {
			frq = 616;
			bl = 0;
		}
	}

	uint8 part = chan > 2 ? 1 : 0;
	if (chan > 2)
		chan -= 3;

	frq |= (bl << 11);

	bufferedWriteReg(part, chan + 0xa4, (frq >> 8));
	bufferedWriteReg(part, chan + 0xa0, (frq & 0xff));

	return 0;
}

int TownsAudioInterfaceInternal::fmSetLevel(int chan, int lvl) {
	if (chan > 5)
		return 1;
	if (lvl > 127)
		return 3;

	uint8 part = chan > 2 ? 1 : 0;
	if (chan > 2)
		chan -= 3;

	uint16 c = _carrier[_fmSaveReg[part][0xb0 + chan] & 7];
	_fmSaveReg[part][0xd0 + chan] = lvl;

	for (uint8 reg = 0x40 + chan; reg < 0x50; reg += 4) {
		c += c;
		if (c & 0x100) {
			c &= 0xff;
			bufferedWriteReg(part, reg, (((((((_fmSaveReg[part][0x80 + reg] ^ 0x7f) * lvl) >> 7) + 1) * _fmSaveReg[part][0xe0 + chan]) >> 7) + 1) ^ 0x7f);
		}
	}
	return 0;
}

void TownsAudioInterfaceInternal::bufferedWriteReg(uint8 part, uint8 regAddress, uint8 value) {
	_fmSaveReg[part][regAddress] = value;
	writeReg(part, regAddress, value);
}

void TownsAudioInterfaceInternal::pcmReset() {
	_numReservedChannels = 0;

	for (int i = 0; i < 8; i++)
		_pcmChan[i].clear();

	memset(_pcmInstruments, 0, 128 * 32);
	static uint8 name[] = { 0x4E, 0x6F, 0x20, 0x44, 0x61, 0x74, 0x61, 0x21 };
	for (int i = 0; i < 32; i++)
		memcpy(_pcmInstruments + i * 128, name, 8);

	for (int i = 0; i < 128; i++)
		_waveTables[i].clear();
	_numWaveTables = 0;
	_waveTablesTotalDataSize = 0;

	for (int i = 0x40; i < 0x48; i++) {
		pcmSetInstrument(i, 0);
		pcmSetLevel(i, 127);
	}
}

int TownsAudioInterfaceInternal::pcmKeyOn(int chan, int note, int velo) {
	if (chan < 0x40 || chan > 0x47)
		return 1;

	if (note & 0x80 || velo & 0x80)
		return 3;

	chan -= 0x40;
	uint8 noteT = note;
	TownsAudio_PcmChannel *p = &_pcmChan[chan];

	if (p->_reserved || p->_keyPressed)
		return 2;

	TownsAudio_WaveTable *w = _waveTables;
	int res = p->initInstrument(noteT, w, _numWaveTables);
	if (res)
		return res;

	p->loadData(w);
	p->keyOn(noteT, velo, w);

	return 0;
}

int TownsAudioInterfaceInternal::pcmKeyOff(int chan) {
	if (chan < 0x40 || chan > 0x47)
		return 1;

	chan -= 0x40;
	_pcmChan[chan].keyOff();
	return 0;
}

int TownsAudioInterfaceInternal::pcmChanOff(int chan) {
	if (chan < 0x40 || chan > 0x47)
		return 1;

	chan -= 0x40;
	_pcmChan[chan]._keyPressed = _pcmChan[chan]._activeEffect = _pcmChan[chan]._activeKey = _pcmChan[chan]._activeOutput = false;

	return 0;
}

int TownsAudioInterfaceInternal::pcmSetPanPos(int chan, int mode) {
	if (chan > 0x47)
		return 1;
	if (mode & 0x80)
		return 3;

	chan -= 0x40;
	uint8 blc = 0x77;

	if (mode > 64) {
		mode -= 64;
		blc = ((blc ^ (mode >> 3)) + (mode << 4)) & 0xff;
	} else if (mode < 64) {
		mode = (mode >> 3) ^ 7;
		blc = ((119 + mode) ^ (mode << 4)) & 0xff;
	}

	_pcmChan[chan].setBalance(blc);

	return 0;
}

int TownsAudioInterfaceInternal::pcmSetInstrument(int chan, int instrId) {
	if (chan > 0x47)
		return 1;
	if (instrId > 31)
		return 3;
	chan -= 0x40;
	_pcmChan[chan].setInstrument(&_pcmInstruments[instrId * 128]);

	return 0;
}

int TownsAudioInterfaceInternal::pcmLoadInstrument(int instrId, const uint8 *data) {
	if (instrId > 31)
		return 3;
	assert(data);
	memcpy(&_pcmInstruments[instrId * 128], data, 128);
	return 0;
}

int TownsAudioInterfaceInternal::pcmSetPitch(int chan, int pitch) {
	if (chan > 0x47)
		return 1;

	if (pitch < -8192 || pitch > 8191)
		return 3;

	chan -= 0x40;
	TownsAudio_PcmChannel *p = &_pcmChan[chan];

	uint32 pts = 0x4000;

	if (pitch < 0)
		pts = (0x20000000 / (-pitch + 0x2001)) >> 2;
	else if (pitch > 0)
		pts = (((pitch + 0x2001) << 16) / 0x2000) >> 2;

	p->setPitch(pts);

	return 0;
}

int TownsAudioInterfaceInternal::pcmSetLevel(int chan, int lvl) {
	if (chan > 0x47)
		return 1;

	if (lvl & 0x80)
		return 3;

	chan -= 0x40;
	_pcmChan[chan].setLevel(lvl);

	return 0;
}

void TownsAudioInterfaceInternal::updateOutputVolume() {
	// Avoid calls to g_system->getAudioCDManager() functions from the main thread
	// since this can cause mutex lockups.
	_updateOutputVol = true;
}

void TownsAudioInterfaceInternal::updateOutputVolumeInternal() {
	if (!_ready)
		return;

	// FM Towns seems to support volumes of 0 - 63 for each channel.
	// We recalculate sane values for our 0 to 255 volume range and
	// balance values for our -128 to 127 volume range

	// CD-AUDIO
	uint32 maxVol = MAX(_outputLevel[12] * (_outputMute[12] ^ 1), _outputLevel[13] * (_outputMute[13] ^ 1));

	int volume = (int)(((float)(maxVol * 255) / 63.0f));
	int balance = maxVol ? (int)( ( ((int)_outputLevel[13] * (_outputMute[13] ^ 1) - _outputLevel[12] * (_outputMute[12] ^ 1)) * 127) / (float)maxVol) : 0;

	g_system->getAudioCDManager()->setVolume(volume);
	g_system->getAudioCDManager()->setBalance(balance);

	_updateOutputVol = false;
}

TownsAudioInterfaceInternal *TownsAudioInterfaceInternal::_refInstance = 0;

int TownsAudioInterfaceInternal::_refCount = 0;

const uint8 TownsAudioInterfaceInternal::_chanFlags[] = {
	0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
};

const uint16 TownsAudioInterfaceInternal::_frequency[] = {
	0x028C, 0x02B4, 0x02DC, 0x030A, 0x0338, 0x0368, 0x039C, 0x03D4, 0x040E, 0x044A, 0x048C, 0x04D0
};

const uint8 TownsAudioInterfaceInternal::_carrier[] = {
	0x10, 0x10, 0x10, 0x10, 0x30, 0x70, 0x70, 0xF0
};

const uint8 TownsAudioInterfaceInternal::_fmDefaultInstrument[] = {
	0x45, 0x4C, 0x45, 0x50, 0x49, 0x41, 0x4E, 0x4F, 0x01, 0x0A, 0x02, 0x01,
	0x1E, 0x32, 0x05, 0x00, 0x9C, 0xDC, 0x9C, 0xDC, 0x07, 0x03, 0x14, 0x08,
	0x00, 0x03, 0x05, 0x05, 0x55, 0x45, 0x27, 0xA7, 0x04, 0xC0, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

TownsAudio_PcmChannel::TownsAudio_PcmChannel() {
	_extData = 0;
	clear();
}

TownsAudio_PcmChannel::~TownsAudio_PcmChannel() {
	clear();
}

void TownsAudio_PcmChannel::clear() {
	_curInstrument = 0;
	_note = _tl = _level = _velo = 0;

	_data = 0;
	_dataEnd = 0;
	_loopLen = 0;

	_pos = 0;
	_loopStart = 0;

	_step = 0;
	_stepNote = 0x4000;
	_stepPitch = 0x4000;

	_panLeft = _panRight = 7;

	_envTotalLevel = _envAttackRate = _envDecayRate = _envSustainLevel = _envSustainRate = _envReleaseRate = 0;
	_envStep = _envCurrentLevel = 0;

	_envState = kEnvReady;

	_activeKey = _activeEffect = _activeOutput = _keyPressed = _reserved = false;

	delete[] _extData;
	_extData = 0;
}

void TownsAudio_PcmChannel::loadData(TownsAudio_WaveTable *w) {
	_data = w->data;
	_dataEnd = w->size << 11;
	_pos = 0;
}

void TownsAudio_PcmChannel::loadData(uint8 *buffer, uint32 size) {
	delete[] _extData;
	_extData = new int8[size];
	int8 *src = (int8 *)buffer;
	int8 *dst = _extData;
	for (uint32 i = 0; i < size; i++)
		*dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++;

	_data = _extData;
	_dataEnd = size << 11;
	_pos = 0;
}

int TownsAudio_PcmChannel::initInstrument(uint8 &note, TownsAudio_WaveTable *&tables, int numTables) {
	int i = 0;
	for (; i < 8; i++) {
		if (note <= _curInstrument[16 + 2 * i])
			break;
	}

	if (i == 8)
		return 8;

	uint8 *d = &_curInstrument[(i << 3) + 64];
	_envTotalLevel = d[0];
	_envAttackRate = d[1];
	_envDecayRate = d[2];
	_envSustainLevel = d[3];
	_envSustainRate = d[4];
	_envReleaseRate = d[5];
	_envStep = 0;
	note += d[6];

	int32 id = (int32)READ_LE_UINT32(&_curInstrument[i * 4 + 32]);

	for (i = 0; i < numTables; i++) {
		if (id == tables[i].id)
			break;
	}

	if (i == numTables)
		return 9;

	tables = &tables[i];
	return 0;
}

void TownsAudio_PcmChannel::keyOn(uint8 note, uint8 velo, TownsAudio_WaveTable *w) {
	setupLoop(w->loopLen ? w->loopStart : w->size, w->loopLen);
	setNote(note, w, _reserved);
	setVelo(velo);

	if (_reserved)
		_activeEffect = true;
	else
		_keyPressed = _activeKey = true;

	_activeOutput = true;
}

void TownsAudio_PcmChannel::keyOff() {
	_keyPressed = false;
	envRelease();
}

void TownsAudio_PcmChannel::updateEnvelopeGenerator() {
	if (!_envCurrentLevel) {
		_activeKey = false;
		_envState = kEnvReady;
	}

	if (!_activeKey)
		return;

	switch (_envState) {
	case kEnvAttacking:
		if (((_envCurrentLevel + _envStep) >> 8) > _envTotalLevel) {
			envDecay();
			return;
		} else {
			_envCurrentLevel += _envStep;
		}
		break;

	case kEnvDecaying:
		if (((_envCurrentLevel - _envStep) >> 8) < _envSustainLevel) {
			envSustain();
			return;
		} else {
			_envCurrentLevel -= _envStep;
		}
		break;

	case kEnvSustaining:
	case kEnvReleasing:
		_envCurrentLevel -= _envStep;
		if (_envCurrentLevel <= 0)
			_envCurrentLevel = 0;
		break;

	default:
		break;
	}
	_tl = (_envCurrentLevel >> 8) << 1;
}

void TownsAudio_PcmChannel::setInstrument(uint8 *instr) {
	_curInstrument = instr;
}

void TownsAudio_PcmChannel::setLevel(uint8 lvl) {
	if (_reserved) {
		_velo = lvl;
		_tl = lvl << 1;
	} else {
		int32 t = _envStep * lvl;
		if (_level)
			t /= _level;
		_envStep = t;
		t = _envCurrentLevel * lvl;
		if (_level)
			t /= _level;
		_envCurrentLevel = t;
		_level = lvl;
		_tl = _envCurrentLevel >> 8;
	}
}

void TownsAudio_PcmChannel::setPitch(uint32 pt) {
	_stepPitch = pt & 0xffff;
	_step = (_stepNote * _stepPitch) >> 14;

//	if (_pcmChanUnkFlag & _chanFlags[chan])
//		 unk[chan] = (((p->step * 1000) << 11) / 98) / 20833;

	/*else*/
	if (_activeEffect && (_step > 2048))
		_step = 2048;
}

void TownsAudio_PcmChannel::setBalance(uint8 blc) {
	_panLeft = blc & 0x0f;
	_panRight = blc >> 4;
}

void TownsAudio_PcmChannel::updateOutput() {
	if (_activeKey || _activeEffect) {
		_pos += _step;

		if (_pos >= _dataEnd) {
			if (_loopStart != _dataEnd) {
				_pos = _loopStart;
				_dataEnd = _loopLen;
			} else {
				_pos = 0;
				_activeKey = _activeEffect = false;
			}
		}
	}
}

int32 TownsAudio_PcmChannel::currentSampleLeft() {
	return (_activeOutput && _panLeft) ? (((_data[_pos >> 11] * _tl) * _panLeft) >> 3) : 0;
}

int32 TownsAudio_PcmChannel::currentSampleRight() {
	return (_activeOutput && _panRight) ? (((_data[_pos >> 11] * _tl) * _panRight) >> 3) : 0;
}

void TownsAudio_PcmChannel::setupLoop(uint32 loopStart, uint32 loopLen) {
	_loopStart = loopStart << 11;
	_loopLen = loopLen << 11;
}

void TownsAudio_PcmChannel::setNote(uint8 note, TownsAudio_WaveTable *w, bool stepLimit) {
	_note = note;
	int8 diff = _note - w->baseNote;
	uint16 r = w->rate + w->rateOffs;
	uint16 bl = 0;
	uint32 s = 0;

	if (diff < 0) {
		diff *= -1;
		bl = diff % 12;
		diff /= 12;
		s = (r >> diff);
		if (bl)
			s = (s * _pcmPhase2[bl]) >> 16;

	} else if (diff > 0) {
		bl = diff % 12;
		diff /= 12;
		s = (r << diff);
		if (bl)
			s += ((s * _pcmPhase1[bl]) >> 16);

	} else {
		s = r;
	}

	_stepNote = s & 0xffff;
	_step = (s * _stepPitch) >> 14;

	if (stepLimit && _step > 2048)
		_step = 2048;
}

void TownsAudio_PcmChannel::setVelo(uint8 velo) {
	if (_reserved) {
		_velo = velo;
		_tl = velo << 1;
	} else {
		_velo = velo;
		uint32 lvl = _level * _velo;
		_envTotalLevel = ((_envTotalLevel * lvl) >> 14) & 0xff;
		_envSustainLevel = ((_envSustainLevel * lvl) >> 14) & 0xff;
		envAttack();
		_tl = (_envCurrentLevel >> 8) << 1;
	}
}

void TownsAudio_PcmChannel::envAttack() {
	_envState = kEnvAttacking;
	int16 t = _envTotalLevel << 8;
	if (_envAttackRate == 127) {
		_envCurrentLevel = _envStep = 0;
	} else if (_envAttackRate) {
		_envStep = t / _envAttackRate;
		_envCurrentLevel = 1;
	} else {
		_envCurrentLevel = t;
		envDecay();
	}
}

void TownsAudio_PcmChannel::envDecay() {
	_envState = kEnvDecaying;
	int16 t = _envTotalLevel - _envSustainLevel;
	if (t < 0 || _envDecayRate == 127) {
		_envStep = 0;
	} else if (_envDecayRate) {
		_envStep = (t << 8) / _envDecayRate;
	} else {
		_envCurrentLevel = _envSustainLevel << 8;
		envSustain();
	}
}

void TownsAudio_PcmChannel::envSustain() {
	_envState = kEnvSustaining;
	if (_envSustainLevel && _envSustainRate)
		_envStep = (_envSustainRate == 127) ? 0 : (_envCurrentLevel / _envSustainRate) >> 1;
	else
		_envStep = _envCurrentLevel = 1;
}

void TownsAudio_PcmChannel::envRelease() {
	_envState = kEnvReleasing;
	if (_envReleaseRate == 127)
		_envStep = 0;
	else if (_envReleaseRate)
		_envStep = _envCurrentLevel / _envReleaseRate;
	else
		_envStep = _envCurrentLevel = 1;
}

const uint16 TownsAudio_PcmChannel::_pcmPhase1[] =  {
	0x879B, 0x0F37, 0x1F58, 0x306E, 0x4288, 0x55B6, 0x6A08, 0x7F8F, 0x965E, 0xAE88, 0xC882, 0xE341
};

const uint16 TownsAudio_PcmChannel::_pcmPhase2[] =  {
	0xFEFE, 0xF1A0, 0xE411, 0xD744, 0xCB2F, 0xBFC7, 0xB504, 0xAAE2, 0xA144, 0x9827, 0x8FAC
};

TownsAudio_WaveTable::TownsAudio_WaveTable() {
	data = 0;
	clear();
}

TownsAudio_WaveTable::~TownsAudio_WaveTable() {
	clear();
}

void TownsAudio_WaveTable::readHeader(const uint8 *buffer) {
	memcpy(name, buffer, 8);
	name[8] = 0;
	id = READ_LE_UINT32(&buffer[8]);
	size = READ_LE_UINT32(&buffer[12]);
	loopStart = READ_LE_UINT32(&buffer[16]);
	loopLen = READ_LE_UINT32(&buffer[20]);
	rate = READ_LE_UINT16(&buffer[24]);
	rateOffs = READ_LE_UINT16(&buffer[26]);
	baseNote = READ_LE_UINT32(&buffer[28]);
}

void TownsAudio_WaveTable::readData(const uint8 *buffer) {
	if (!size)
		return;

	delete[] data;
	data = new int8[size];

	const int8 *src = (const int8 *)buffer;
	int8 *dst = data;
	for (uint32 i = 0; i < size; i++)
		*dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++;
}

void TownsAudio_WaveTable::clear() {
	name[0] = name[8] = 0;
	id = -1;
	size = 0;
	loopStart = 0;
	loopLen = 0;
	rate = 0;
	rateOffs = 0;
	baseNote = 0;
	delete[] data;
	data = 0;
}

TownsAudioInterface::TownsAudioInterface(Audio::Mixer *mixer, TownsAudioInterfacePluginDriver *driver) {
	_intf = TownsAudioInterfaceInternal::addNewRef(mixer, this, driver);
}

TownsAudioInterface::~TownsAudioInterface() {
	TownsAudioInterfaceInternal::releaseRef(this);
	_intf = 0;
}

bool TownsAudioInterface::init() {
	return _intf->init();
}

TownsAudioInterface::ErrorCode TownsAudioInterface::callback(int command, ...) {
	va_list args;
	va_start(args, command);

	int res = _intf->processCommand(command, args);

	va_end(args);
	return (TownsAudioInterface::ErrorCode)res;
}

void TownsAudioInterface::setMusicVolume(int volume) {
	_intf->setMusicVolume(volume);
}

void TownsAudioInterface::setSoundEffectVolume(int volume) {
	_intf->setSoundEffectVolume(volume);
}

void TownsAudioInterface::setSoundEffectChanMask(int mask) {
	_intf->setSoundEffectChanMask(mask);
}
