/* 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 "sci/sci.h"

#include "common/file.h"
#include "common/system.h"
#include "common/textconsole.h"

#include "audio/softsynth/fmtowns_pc98/pc98_audio.h"

#include "sci/resource.h"
#include "sci/sound/drivers/mididriver.h"

/*	PC-98 sound driver for:
	Quest for Glory 1		SCI_VERSION_0_LATE		first driver type
	Police Quest 2			SCI_VERSION_0_LATE		second driver type
	Castle of Dr. Brain		SCI_VERSION_1_LATE		third driver type
	Space Quest IV			SCI_VERSION_1_LATE		third driver type
	King's Quest V			SCI_VERSION_1_LATE		latest driver type
*/

namespace Sci {

class MidiPart_PC9801;
class SoundChannel_PC9801 {
public:
	SoundChannel_PC9801(PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, int type, SciSpan<const uint8> instrumentData, bool &soundOn);
	virtual ~SoundChannel_PC9801();

	void noteOff();
	void noteOn(uint8 note, uint8 velo);

	virtual void processNoteEvent(uint8 note, bool soundOn);
	void update();
	void setVolume(uint8 volume);

	virtual void reset();
	virtual void toggleNoiseGenerator(bool) {}

	int getType() const { return _type; }

	uint8 _assign;
	uint8 _note;
	uint8 _sustain;
	uint16 _duration;

protected:
	enum ChannelStatusFlags {
		kChanVbrEnable = 0x01,
		kChanVbrRestartEnv = 0x02,
		kChanKeyOn = 0x04,
		kChanNgEnable = 0x08,
		kChanNgRestartEnv = 0x10,
		kChanNgDecrease = 0x20,
		kChanVbrMode = 0x40, 
		kChanVbrDecrease = 0x80
	};

	int recalculateFrequency(uint16 note, uint16 modifier, uint8 *destOctaveBlock, uint16 *destFrequency, uint8 *destVbrFrequencyModifier);
	uint8 getVolume();
	virtual void processSounds();
	void programChangeInit(SciSpan<const uint8> &data);
	void writeReg(uint8 part, uint8 reg, uint8 val);

	int8 _transpose;
	uint8 _vbrInitialDelay;
	uint8 _vbrEnvelopeTimer;
	uint8 _vbrEnvelopeSpeed;

	uint16 _frequencyCourse;
	uint8 _frequencyBlock;
	uint8 _frequencyNoteModifier;

	uint8 _vbrDepthIncr;
	uint8 _vbrDecrTime;
	uint8 _vbrDepthDecr;
	uint8 _vbrIncrTime;
	uint8 _vbrSensitivity;
	uint16 _vbrIncrStep;
	uint16 _vbrDecrStep;
	uint8 _vbrModulationTimer;
	uint16 _vbrFrequencyModifier;
	uint8 _vbrCur;		
	uint8 _flags;

	const uint16 *_noteFrequency;
	const uint16 *_noteFrequencyModifier;

	SciSpan<const uint8> _instrumentData;
	const SciVersion _version;

private:
	virtual void programChange(uint8 program) = 0;
	virtual bool prepareFrequencyAndVolume(bool updateVolume);
	virtual void sendSoundOnOff(bool soundOn) = 0;
	virtual void sendFrequency() = 0;
	virtual void sendVolume() = 0;	

	uint8 _velo;
	uint8 _volume;
	uint8 _program;
	bool &_soundOn;

	MidiPart_PC9801 **_parts;	
	PC98AudioCore *_pc98a;
	const int _type;
};

class SoundChannel_PC9801_FM4OP : public SoundChannel_PC9801 {
public:
	SoundChannel_PC9801_FM4OP(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, SciSpan<const uint8> instrumentData, uint8 patchSize, bool &soundOn);
	virtual ~SoundChannel_PC9801_FM4OP() {}

private:
	void programChange(uint8 program);
	void sendSoundOnOff(bool soundOn);
	void sendVolume();
	void sendFrequency();

	uint8 _operatorLevel[4];
	uint8 _carrier;
	uint8 _operatorFlags;

	const uint8 _regPrt;
	const uint8 _regOffs;
	const uint8 _patchSize;
};

class SoundChannel_PC9801_FM2OP : public SoundChannel_PC9801 {
public:
	SoundChannel_PC9801_FM2OP(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts,  SciVersion version, SciSpan<const uint8> instrumentData, uint8 patchSize, bool &soundOn);
	virtual ~SoundChannel_PC9801_FM2OP() {}
	
	void processNoteEvent(uint8 note, bool soundOn);
	void reset();

private:
	void programChange(uint8 program);
	bool prepareFrequencyAndVolume(bool updateVolume);
	void processSounds();
	void sendSoundOnOff(bool soundOn);
	void sendVolume();
	void sendFrequency();

	uint8 _operatorLevel[2];
	uint8 _operatorFrqIndex[2];
	uint16 _frequencyCourse2;
	uint8 _frequencyNoteModifier2;
	int16 _vbrFrequencyModifier2;
	uint16 _vbrIncrStep2;
	uint16 _vbrDecrStep2;
	uint8 _vbrCur2;
	static uint8 _activeOperators;

	const uint16 *_opFreqOffset;
	const uint8 _patchOffset;
	const uint8 _regPrt;
	const uint8 _regOffs;
	const uint8 _patchSize;
};

class SoundChannel_PC9801_SSG : public SoundChannel_PC9801 {
public:
	SoundChannel_PC9801_SSG(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, SciSpan<const uint8> instrumentData, uint8 patchOffset, uint8 patchSize, bool &soundOn);
	virtual ~SoundChannel_PC9801_SSG() {}
	void reset();
	void toggleNoiseGenerator(bool enable);

private:
	enum {
		kEnvSSG_silent = 0x00,
		kEnvSSG_decay = 0x01,
		kEnvSSG_sustain = 0x02,
		kEnvSSG_attack = 0x03,
		kEnvSSG_keyOn = 0x10	
	};

	void programChange(uint8 program);
	void processSounds();
	void sendSoundOnOff(bool soundOn);
	void sendVolume();
	void sendFrequency();
	void updateNg();
	void sendActiveChannelsStatus();

	uint8 _instrumentChanMask;
	uint8 _chanDisableMask;
	uint8 _chanEnableMask1;
	uint8 _chanEnableMask2;
	uint8 _currentLevel;
	uint8 _ngPhaseStep;
	uint8 _ngPhase;	
	uint8 _ngEnvelopeTimer;
	uint8 _ngSpeed;
	uint8 _ssgEnvelopeTimer;
	uint8 _ssgLevel;
	uint8 _ssgSpeed;
	uint8 _ssgEnvelopeState;
	bool _ccngEnabled;

	static uint8 _activeChannnelsStatus;
	SciSpan<const uint8> _selectedInstrument;

	const uint8 *_ngFreq;
	const uint8 _regOffs;
	const uint8 _patchOffset;
	const uint8 _patchSize;
};

class MidiPart_PC9801 {
public:
	MidiPart_PC9801(uint8 id, SoundChannel_PC9801 **channels, uint8 numChan, SciVersion version);
	~MidiPart_PC9801() {}

	void noteOff(uint8 note);
	void noteOn(uint8 note, uint8 velo);
	void controlChangeVolume(uint8 vol);
	void controlChangeSustain(uint8 sus);
	void controlChangePolyphony(uint8 numChan);
	void controlChangeNoiseGenerator(uint8 status);
	void controlChangeAllNotesOff();
	void programChange(uint8 prg);
	void pitchBend(int16 val);

	void addChannels(int num, int resetMissingChannels = -1, int channelType = -1);
	void dropChannels(int num);

	uint8 getCurrentProgram() const { return _program; }
	uint8 getCurrentVolume() const { return _volume; }
	uint16 getCurrentPitchBend() const { return _pitchBend; }
	uint8 getMissingChannels() const { return _chanMissing; }

private:
	int allocateChannel();
	void assignFreeChannels();

	uint8 _id;
	uint8 _program;
	uint8 _volume;
	uint8 _sustain;
	uint16 _pitchBend;
	uint8 _outChan;
	uint8 _chanMissing;

	SoundChannel_PC9801 **_chan;
	const SciVersion _version;
	const int _numChan;
	const uint8 _noteRangeLow;
	const uint8 _noteRangeHigh;
};

class MidiDriver_PC9801 : public MidiDriver, public PC98AudioPluginDriver {
public:
	enum {
		MIDI_PROP_PLAYSWITCH = 1,
		MIDI_PROP_POLYPHONY = 2,
		MIDI_PROP_CHANNEL_ID = 3
	};

	MidiDriver_PC9801(Audio::Mixer *mixer, SciVersion version);
	~MidiDriver_PC9801();

	int open();
	bool isOpen() const { return _isOpen; }
	void close();

	void send(uint32 b);
	static void assignFreeChannels(int num);
	uint32 property(int prop, uint32 param);
	void initTrack(SciSpan<const byte> &header);

	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc);
	uint32 getBaseTempo();	
	MidiChannel *allocateChannel() { return 0; }
	MidiChannel *getPercussionChannel() { return 0; }

	void timerCallbackB();

private:
	bool loadInstruments(const SciSpan<const uint8> &data);
	void updateParser();
	void updateChannels();
	void reset();

	Common::TimerManager::TimerProc _timerProc;
	void *_timerProcPara;

	static MidiPart_PC9801 **_parts;
	SoundChannel_PC9801 **_chan;
	Common::SpanOwner<SciSpan<uint8> > _instrumentData;

	uint8 _masterVolume;
	bool _soundOn;

	uint8 _numChan;
	uint8 _ssgPatchOffset;
	uint8 _patchSize;

	uint8 _internalVersion;
	const uint8 _playID;
	const uint8 _channelMask1;
	uint8 _channelMask2;
	uint8 _polyphony;

	bool _isOpen;
	bool _ready;

	const uint16 _baseTempo;

	PC98AudioCore *_pc98a;
	const SciVersion _version;
};

class MidiPlayer_PC9801 : public MidiPlayer {
public:
	enum {
		MIDI_PROP_PLAYSWITCH = 1,
		MIDI_PROP_POLYPHONY = 2,
		MIDI_PROP_CHANNEL_ID = 3
	};

	MidiPlayer_PC9801(SciVersion version);
	~MidiPlayer_PC9801();

	bool hasRhythmChannel() const;
	byte getPlayId() const;
	int getPolyphony() const;
	void playSwitch(bool play);

	void initTrack(SciSpan<const byte> &trackData);
};

SoundChannel_PC9801::SoundChannel_PC9801(PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, int type, SciSpan<const uint8> instrumentData, bool &soundOn)
	: _pc98a(pc98a), _parts(parts), _version(version), _type(type), _instrumentData(instrumentData), _soundOn(soundOn), _assign(0xff), _note(0xff), _velo(0),
	_volume(0), _transpose(0), _sustain(0), _duration(0), _program(0xff), _vbrInitialDelay(0), _vbrEnvelopeTimer(0),
	_vbrEnvelopeSpeed(0), _vbrDepthIncr(0), _vbrDecrTime(0), _vbrDepthDecr(0), _vbrIncrTime(0), _vbrSensitivity(0),
	_vbrFrequencyModifier(0), _vbrIncrStep(0), _vbrDecrStep(0), _vbrModulationTimer(0), _flags(0), _vbrCur(0x80),
	_frequencyBlock(0), _frequencyCourse(0), _frequencyNoteModifier(0) {

	static const uint16 courseV0[] = { 0x269, 0x28D, 0x2B5, 0x2DE, 0x30A, 0x339, 0x368, 0x39D, 0x3D4, 0x40E, 0x44A, 0x48C };
	static const uint16 courseV1[] = { 0x26A, 0x28E, 0x2B5, 0x2DF, 0x30A, 0x339, 0x36A, 0x39E, 0x3D5, 0x40F, 0x44D, 0x48F };
	static const uint16 fine[] = { 0x24, 0x27, 0x2A, 0x2B, 0x2F, 0x31, 0x34, 0x37, 0x3A, 0x3E, 0x42, 0x45 };

	_noteFrequency = (version > SCI_VERSION_0_LATE) ? courseV1 : courseV0;
	_noteFrequencyModifier = fine;
}

SoundChannel_PC9801::~SoundChannel_PC9801() {
	_instrumentData.clear();
}

void SoundChannel_PC9801::noteOff() {
	if (_sustain)
		return;

	processNoteEvent(_note, false);

	_note = 0xFF;
	_duration = 0;
}

void SoundChannel_PC9801::noteOn(uint8 note, uint8 velo) {
	_duration = 0;

	if (_program != _parts[_assign]->getCurrentProgram() && _soundOn) {
		_program = _parts[_assign]->getCurrentProgram();
		programChange(_program);
	}

	if (_version > SCI_VERSION_0_LATE) {
		velo >>= 1;
		if (velo > 63)
			velo = 63;
		_velo = velo;
	}

	processNoteEvent(note, true);
}

void SoundChannel_PC9801::processNoteEvent(uint8 note, bool soundOn) {
	if (_note != note) {
		_note = note;
		_vbrEnvelopeTimer = _vbrInitialDelay;
		_vbrFrequencyModifier = 0;
		_vbrCur = 0x80;
		_flags |= kChanVbrRestartEnv;
	}

	if (!prepareFrequencyAndVolume(soundOn))
		soundOn = false;

	sendSoundOnOff(soundOn);
}

void SoundChannel_PC9801::update() {
	processSounds();
	if (_note != 0xFF)
		_duration++;
}

void SoundChannel_PC9801::setVolume(uint8 volume) {
	static const uint8 volumeTable[] = { 0x7F, 0x7F, 0x30, 0x30, 0x28, 0x28, 0x20, 0x20, 0x18, 0x18, 0x10, 0x10, 0x08, 0x08, 0x00, 0x00 };
	assert(volume < 16);
	_volume = volumeTable[volume];
}

void SoundChannel_PC9801::reset() {
	_assign = 0xFF;
	_note = 0xFF;
	_volume = 0;
}

int SoundChannel_PC9801::recalculateFrequency(uint16 note, uint16 modifier, uint8 *destOctaveBlock, uint16 *destFrequency, uint8 *destVbrFrequencyModifier) {
	note += _transpose;
	uint16 pb = _parts[_assign]->getCurrentPitchBend();

	if (pb == 0x2000) {
		pb = 0;
	} else {
		int dir = 1;
		if (pb > 0x2000) {
			pb -= 0x2000;
		} else if (pb < 0x2000) {
			pb = 0x2000 - pb;
			dir = -1;
		}

		uint8 noteDiff = pb / 684;
		note += (noteDiff * dir);

		pb %= 684;
		if (pb > 682)
			pb = 682;
		pb = ((pb * 6) >> 4);
		pb *= dir;
	}

	note += (modifier >> 8);
	pb += (modifier & 0xff);
	if ((pb >> 8) == 1) {
		pb &= 0xFF;
		note++;
	}

	if (_version == SCI_VERSION_0_LATE)
		note -= 12;

	if (note < 12 || note > 107)
		return -1;

	if (_version == SCI_VERSION_0_LATE && _type == 2) {
		uint16 rs = _noteFrequency[note - 12];
		if (destFrequency)
			*destFrequency = rs;
		return rs;
	}

	uint8 block = (note / 12) - 1;
	note %= 12;
	uint16 res =_noteFrequency[note];
	uint16 pitchVbrMultiplier = _noteFrequencyModifier[note];

	if (_type != 2)
		res |= (block << 11);
	else if (block == 0)
		return -1;

	if (pb)
		res += (((pb * pitchVbrMultiplier) & 0x0FF0) >> 8);

	if (res < 0x4000) {
		if (destFrequency)
			*destFrequency = res;
		if (destOctaveBlock)
			*destOctaveBlock = block;
		if (destVbrFrequencyModifier)
			*destVbrFrequencyModifier = pitchVbrMultiplier;
		return res;
	}

	return -1;
}

uint8 SoundChannel_PC9801::getVolume() {
	static const uint8 volumeTable1[] = {
		0x00, 0x0c, 0x0d, 0x0e, 0x0f, 0x11, 0x12, 0x13, 0x14, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1d,
		0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2d, 0x2d, 0x2e,
		0x2f, 0x30, 0x31, 0x32, 0x32, 0x33, 0x34, 0x34, 0x35, 0x36,	0x36, 0x37, 0x38, 0x38, 0x39, 0x3a,
		0x3b, 0x3b,	0x3b, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e,	0x3e, 0x3f, 0x3f, 0x3f
	};

	static const uint8 volumeTable2[] = {
		0x00, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x61,
		0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x6f, 0x70,
		0x71, 0x72, 0x72, 0x73, 0x74, 0x74, 0x75, 0x76, 0x76, 0x77, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a,
		0x7b, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f
	};

	uint16 partVolume = ((_assign != 0xFF) ? _parts[_assign]->getCurrentVolume() : 0);
	if (_version == SCI_VERSION_0_LATE)
		return partVolume;

	partVolume += 1;
	uint16 velocity = volumeTable1[_velo] + 1;
	uint16 volume = _soundOn ? (partVolume * velocity) >> 6 : 0;
	volume = volumeTable2[volume] - _volume;

	return (volume < 0x7F ? volume : 0);
}

void SoundChannel_PC9801::processSounds() {
	if (!(_flags & kChanVbrEnable))
		return;

	if (_flags & kChanVbrRestartEnv) {
		if (--_vbrEnvelopeTimer)
			return;

		_vbrIncrStep = _vbrDepthIncr * _vbrSensitivity * _frequencyNoteModifier;
		_vbrDecrStep = _vbrDepthDecr * _vbrSensitivity * _frequencyNoteModifier;
		_vbrModulationTimer = ((_flags & kChanVbrMode) ? _vbrIncrTime : _vbrDecrTime) >> 1;
		_vbrCur = 0x80;

		_flags &= ~(kChanVbrDecrease | kChanVbrRestartEnv);
		if (_flags & kChanVbrMode)
			_flags |= kChanVbrDecrease;
	}

	uint16 t = _vbrEnvelopeTimer + _vbrEnvelopeSpeed;
	_vbrEnvelopeTimer = t & 0xFF;
	if (t & 0x100)
		return;

	if (!--_vbrModulationTimer) {
		_vbrModulationTimer = (_flags & kChanVbrDecrease) ? _vbrDecrTime : _vbrIncrTime;
		_flags ^= kChanVbrDecrease;
	}

	if (_flags & kChanVbrDecrease) {
		uint8 sL = _vbrDecrStep & 0xFF;
		uint8 sH = _vbrDecrStep >> 8;
		bool ovrflow = (sL > _vbrCur);
		_vbrCur -= sL;
		if (!ovrflow)
			return;
		_vbrFrequencyModifier -= (sH + 1);
	} else {
		uint8 sL = _vbrDecrStep & 0xFF;
		uint8 sH = _vbrDecrStep >> 8;
		bool ovrflow = (sL + _vbrCur > 255);
		_vbrCur += sL;
		if (!ovrflow)
			return;
		_vbrFrequencyModifier += (sH + 1);
	}

	sendFrequency();
}

void SoundChannel_PC9801::programChangeInit(SciSpan<const uint8> &data) {
	_transpose = (int8)(data[0] & 0xC0 && data[0] < 0xC0 ? data[0] ^ 0x80 : data[0]);
	_vbrInitialDelay = data[1];
	_vbrDepthIncr = data[2];
	_vbrDecrTime = data[3];
	_vbrDepthDecr = data[4];
	_vbrIncrTime = data[5];
	_vbrSensitivity = (data[6] & 3) + 1;
	_vbrEnvelopeSpeed = data[6] >> 3;
	_flags = (_flags & ~(kChanVbrEnable | kChanVbrMode)) | ((data[6] << 4) & 0x40);
	if (_vbrInitialDelay)
		_flags |= (kChanVbrEnable | kChanVbrRestartEnv);
}

void SoundChannel_PC9801::writeReg(uint8 part, uint8 reg, uint8 val) {
	_pc98a->writeReg(part, reg, val);
}

bool SoundChannel_PC9801::prepareFrequencyAndVolume(bool updateVolume) {
	if (recalculateFrequency(_note, 0, &_frequencyBlock, &_frequencyCourse, &_frequencyNoteModifier) == -1)
		return false;

	sendFrequency();
	if (updateVolume)
		sendVolume();

	return true;
}

SoundChannel_PC9801_FM4OP::SoundChannel_PC9801_FM4OP(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, SciSpan<const uint8> instrumentData, uint8 patchSize, bool &soundOn)
	: SoundChannel_PC9801(pc98a, parts, version, 0, instrumentData, soundOn), _carrier(0), _regPrt(id > 2 ? 1 : 0), _regOffs(id % 3),
	_operatorFlags((id > 2 ? 4 : 0) | (id % 3) | 0xF0), _patchSize(patchSize) {
	_operatorLevel[0] = _operatorLevel[1] = _operatorLevel[2] = _operatorLevel[3] = 0x7F;
}

void SoundChannel_PC9801_FM4OP::programChange(uint8 program) {
	static const uint8 carrier[] = { 0x10, 0x10, 0x10, 0x10, 0x30, 0x70, 0x70, 0xF0 };
	static const uint8 steps[] = { 0, 16, 8, 24 };
	SciSpan<const uint8> data = _instrumentData.subspan(program * _patchSize);

	if (_version == SCI_VERSION_1_LATE) {
		programChangeInit(data);
		uint8 pos = 8;
	
		for (uint8 i = 0x40 + _regOffs; i < 0x50 + _regOffs; i += 4)
			writeReg(_regPrt, i, 0xFF);
		for (uint8 i = 0x30 + _regOffs; i < 0x40 + _regOffs; i += 4)
			writeReg(_regPrt, i, data[pos++]);
		for (int i = 0; i < 4; ++i)
			_operatorLevel[i] = data[pos++];
		for (uint8 i = 0x50 + _regOffs; i < 0xA0 + _regOffs; i += 4)
			writeReg(_regPrt, i, data[pos++]);
		writeReg(_regPrt, 0xB0 + _regOffs, data[pos]);
		_carrier = carrier[data[pos] & 7];

	} else {
		uint8 pos = 11;
		uint8 opFlags = data[pos];
		uint8 fba = data[pos + 1] & 0x3F;
		_carrier = carrier[fba & 7];

		pos += 8;
		uint8 reg = 0x30 + _regOffs;
		for (int i = 0; i < 4; ++i)
			writeReg(_regPrt, reg + (i << 2), data[pos + steps[i & 3]] & 0x7F);

		pos -= 3;
		_operatorLevel[0] = (opFlags & 0x08) ? data[pos + steps[0]] : 0x7F;
		_operatorLevel[1] = (opFlags & 0x20) ? data[pos + steps[1]] : 0x7F;
		_operatorLevel[2] = (opFlags & 0x10) ? data[pos + steps[2]] : 0x7F;
		_operatorLevel[3] = (opFlags & 0x40) ? data[pos + steps[3]] : 0x7F;

		pos += 4;
		reg = 0x50 + _regOffs;
		for (int i = 0; i < 16; ++i)
			writeReg(_regPrt, reg + (i << 2), data[pos + steps[i & 3] + (i >> 2)]);

		if (fba >= 24)
			fba -= 24;
		else
			fba &= 7;

		writeReg(_regPrt, 0xB0 + _regOffs, fba);
		_operatorFlags = (_operatorFlags & 7) | (opFlags << 1);
	}
}

void SoundChannel_PC9801_FM4OP::sendSoundOnOff(bool soundOn) {
	_flags = soundOn ? (_flags | kChanKeyOn) : (_flags & ~kChanKeyOn);
	writeReg(0, 0x28, soundOn ? _operatorFlags : _operatorFlags & 7);
}

void SoundChannel_PC9801_FM4OP::sendVolume() {
	uint8 vol = getVolume();
	uint16 c = _carrier;

	for (uint8 i = 0; i < 4; ++i) {
		uint8 r = _operatorLevel[i];
		c += c;
		if (c & 0x100) {
			c &= 0xFF;
			if (_version == SCI_VERSION_1_LATE) {
				r = (((r ^ 0x7F) * vol) / 0x7F) * 2;
				r = ((r < 0x7F) ? 0x7F - r : 0) + 20;
				if (r > 0x7F)
					r = 0x7F;
			} else {
				r = 127 - ((127 - r) * vol / 128);
			}
		}		
		writeReg(_regPrt, 0x40 + (i << 2) + _regOffs, r);
	}
}

void SoundChannel_PC9801_FM4OP::sendFrequency() {
	uint16 freq = _frequencyCourse + _vbrFrequencyModifier;
	writeReg(_regPrt, 0xA4 + _regOffs, freq >> 8);
	writeReg(_regPrt, 0xA0 + _regOffs, freq & 0xFF);
}

uint8 SoundChannel_PC9801_FM2OP::_activeOperators = 0;

SoundChannel_PC9801_FM2OP::SoundChannel_PC9801_FM2OP(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, SciSpan<const uint8> instrumentData, uint8 patchSize, bool &soundOn)
	: SoundChannel_PC9801(pc98a, parts, version, 1, instrumentData, soundOn), _patchOffset(37), _patchSize(patchSize), _frequencyCourse2(0), _frequencyNoteModifier2(0),
	_vbrFrequencyModifier2(0), _vbrCur2(0x80), _vbrIncrStep2(0), _vbrDecrStep2(0), _regPrt(id > 3 ? 1 : 0), _regOffs(id & 1) {

	static const uint16 opFreqOffset[] = { 0x0000, 0x0600, 0x07CF, 0x0980 };

	_operatorLevel[0] = _operatorLevel[1] = 0x7F;
	_operatorFrqIndex[0] = _operatorFrqIndex[1] = 0;
	_opFreqOffset = opFreqOffset;
}

void SoundChannel_PC9801_FM2OP::processNoteEvent(uint8 note, bool soundOn) {
	if (_note != note) {
		_vbrCur2 = 0x80;
		_vbrFrequencyModifier2 = 0;
	}
	SoundChannel_PC9801::processNoteEvent(note, soundOn);
}

void SoundChannel_PC9801_FM2OP::reset() {
	SoundChannel_PC9801::reset();
	_activeOperators = 0;
}

void SoundChannel_PC9801_FM2OP::programChange(uint8 program) {
	SciSpan<const uint8> data = _instrumentData.subspan(program * _patchSize + _patchOffset);

	programChangeInit(data);
	uint8 pos = 7;
	
	for (uint8 i = 0x42 + (_regOffs << 2); i < 0x52 + (_regOffs << 2); i += 8)
		writeReg(_regPrt, i, 0xFF);
	for (uint8 i = 0x32 + (_regOffs << 2); i < 0x42 + (_regOffs << 2); i += 8)
		writeReg(_regPrt, i, data[pos++]);
	for (int i = 0; i < 2; ++i)
		_operatorLevel[i] = data[pos++];
	for (uint8 i = 0x52 + (_regOffs << 2); i < 0x72 + (_regOffs << 2); i += 8)
		writeReg(_regPrt, i, data[pos++]);
	_operatorFrqIndex[0] = data[pos] >> 6;
	_operatorFrqIndex[1] = data[pos + 1] >> 6;
	for (uint8 i = 0x72 + (_regOffs << 2); i < 0xA2 + (_regOffs << 2); i += 8)
		writeReg(_regPrt, i, data[pos++]);
}

bool SoundChannel_PC9801_FM2OP::prepareFrequencyAndVolume(bool updateVolume) {
	if (recalculateFrequency(_note, _opFreqOffset[_operatorFrqIndex[0]], 0, &_frequencyCourse, &_frequencyNoteModifier) == -1)
		return false;
	if (recalculateFrequency(_note, _opFreqOffset[_operatorFrqIndex[1]], 0, &_frequencyCourse2, &_frequencyNoteModifier2) == -1)
		return false;

	sendFrequency();
	if (updateVolume)
		sendVolume();

	return true;
}

void SoundChannel_PC9801_FM2OP::sendSoundOnOff(bool soundOn) {
	uint8 op = 0x30 << (_regOffs << 1);

	if (soundOn) {
		_flags |= kChanKeyOn;
		_activeOperators |= op;
	} else {
		_flags &= ~kChanKeyOn;
		_activeOperators &= ~op;
	}

	writeReg(0, 0x28, _activeOperators | (_regPrt << 2) | 2);
}

void SoundChannel_PC9801_FM2OP::sendVolume() {
	writeReg(_regPrt, 0x42 + (_regOffs << 2), _operatorLevel[0]);
	uint8 r = (((_operatorLevel[1] ^ 0x7F) * getVolume()) / 0x7F) * 2;
	r = ((r < 0x7F) ? 0x7F - r : 0) + 16;
	writeReg(_regPrt, 0x4A + (_regOffs << 2), r);
}

void SoundChannel_PC9801_FM2OP::processSounds() {
	if (!(_flags & kChanVbrEnable))
		return;

	if (_flags & kChanVbrRestartEnv) {
		if (--_vbrEnvelopeTimer)
			return;

		_vbrIncrStep = _vbrDepthIncr * _vbrSensitivity * _frequencyNoteModifier;
		_vbrIncrStep2 = _vbrDepthIncr * _vbrSensitivity * _frequencyNoteModifier2;
		_vbrDecrStep = _vbrDepthDecr * _vbrSensitivity * _frequencyNoteModifier;
		_vbrDecrStep2 = _vbrDepthDecr * _vbrSensitivity * _frequencyNoteModifier2;
		_vbrModulationTimer = ((_flags & kChanVbrMode) ? _vbrIncrTime : _vbrDecrTime) >> 1;
		_vbrCur = 0x80;

		_flags &= ~(kChanVbrDecrease | kChanVbrRestartEnv);
		if (_flags & kChanVbrMode)
			_flags |= kChanVbrDecrease;
	}

	uint16 t = _vbrEnvelopeTimer + _vbrEnvelopeSpeed;
	_vbrEnvelopeTimer = t & 0xFF;
	if (t & 0x100)
		return;

	if (!--_vbrModulationTimer) {
		_vbrModulationTimer = (_flags & kChanVbrDecrease) ? _vbrDecrTime : _vbrIncrTime;
		_flags ^= kChanVbrDecrease;
	}

	if (_flags & kChanVbrDecrease) {
		uint8 sL = _vbrDecrStep & 0xFF;
		uint8 sH = _vbrDecrStep >> 8;
		bool ovrflow = (sL > _vbrCur);
		_vbrCur -= sL;
		if (ovrflow)
			_vbrFrequencyModifier -= (sH + 1);
		sL = _vbrDecrStep2 & 0xFF;
		sH = _vbrDecrStep2 >> 8;
		ovrflow = (sL > _vbrCur2);
		_vbrCur2 -= sL;
		if (ovrflow)
			_vbrFrequencyModifier2 -= (sH + 1);
	} else {
		uint8 sL = _vbrDecrStep & 0xFF;
		uint8 sH = _vbrDecrStep >> 8;
		bool ovrflow = (sL + _vbrCur > 255);
		_vbrCur += sL;
		if (ovrflow)
			_vbrFrequencyModifier += (sH + 1);
		sL = _vbrDecrStep2 & 0xFF;
		sH = _vbrDecrStep2 >> 8;
		ovrflow = (sL + _vbrCur2 > 255);
		_vbrCur2 += sL;
		if (ovrflow)
			_vbrFrequencyModifier2 += (sH + 1);
	}

	sendFrequency();
}

void SoundChannel_PC9801_FM2OP::sendFrequency() {
	uint16 freq = _frequencyCourse + _vbrFrequencyModifier;
	writeReg(_regPrt, 0xAD - _regOffs, freq >> 8);
	writeReg(_regPrt, 0xA9 - _regOffs, freq & 0xFF);
	freq = _frequencyCourse2 + _vbrFrequencyModifier2;
	writeReg(_regPrt, 0xAE - (_regOffs << 3), freq >> 8);
	writeReg(_regPrt, 0xAA - (_regOffs << 3), freq & 0xFF);
}

uint8 SoundChannel_PC9801_SSG::_activeChannnelsStatus = 0x3F;

SoundChannel_PC9801_SSG::SoundChannel_PC9801_SSG(uint8 id, PC98AudioCore *pc98a, MidiPart_PC9801 **parts, SciVersion version, SciSpan<const uint8> instrumentData, uint8 patchOffset, uint8 patchSize, bool &soundOn)
	: SoundChannel_PC9801(pc98a, parts, version, 2, instrumentData, soundOn), _patchOffset(patchOffset), _patchSize(patchSize), _regOffs(id & 3),
	_instrumentChanMask(0x3F), _ngPhaseStep(0),	_currentLevel(0), _ssgEnvelopeState(kEnvSSG_silent), _ngEnvelopeTimer(0),
	_ngSpeed(0), _ngPhase(0), _ssgEnvelopeTimer(0), _ssgLevel(0), _ssgSpeed(0), _ccngEnabled(false) {
	
	static const uint16 courseV0[] = {
		0xfcf, 0xef1, 0xe16, 0xd59, 0xc87, 0xbcd, 0xb27, 0xa93, 0x9ee, 0x96a, 0x8da, 0x865, 0x778, 0x70d, 0x6a6, 0x643,
		0x5ec, 0x599, 0x545, 0x4f7, 0x4b5, 0x46d, 0x432, 0x3f4, 0x3c5, 0x386, 0x353, 0x325, 0x2f6, 0x2cc, 0x2a3, 0x27d,
		0x25a, 0x238, 0x218, 0x1fa, 0x1dd, 0x1c3, 0x1a9, 0x192, 0x17b, 0x166, 0x152, 0x13f, 0x12d, 0x11c, 0x10c, 0x0fd,
		0x0ef, 0x0e1, 0x0d5, 0x0c9, 0x0bd, 0x0b3, 0x0a9, 0x09f, 0x096, 0x08e, 0x086, 0x07e, 0x077, 0x071, 0x06a, 0x064,
		0x05f, 0x059, 0x054, 0x050, 0x04b, 0x047, 0x043, 0x03f, 0x03c, 0x038, 0x035, 0x032, 0x02f, 0x02d, 0x02a, 0x028,
		0x026, 0x023, 0x021, 0x020, 0x01e, 0x01c, 0x01b, 0x019, 0x018, 0x016, 0x015, 0x014, 0x013, 0x012, 0x011, 0x010
	};

	static const uint16 courseV1[] = { 0x82D, 0x8A9, 0x92D, 0x9B6, 0xA4D, 0xAEA, 0xB90, 0xC40, 0xCFA, 0xDC0, 0xE90, 0xF6F };
	static const uint16 fine[] = { 0x7C, 0x84, 0x8B, 0x94, 0x9D, 0xA6, 0xB0, 0xBA, 0xC5, 0xD0, 0xDE, 0xEB };
	static const uint8 noiseFrq[] = { 0x00, 0x01, 0x04, 0x07, 0x0A, 0x0D, 0x10, 0x13, 0x16, 0x19, 0x1C, 0x1F };
	static const uint8 disableMask[] = { 0xF6, 0xED, 0xDB };
	static const uint8 enableMask1[] = { 0xBE, 0xBD, 0xBB };
	static const uint8 enableMask2[] = { 0xB7, 0xAF, 0x9F };

	_noteFrequency = (version > SCI_VERSION_0_LATE) ? courseV1 : courseV0;
	_noteFrequencyModifier = fine;
	_ngFreq = noiseFrq;
	_chanDisableMask = disableMask[id & 3];
	_chanEnableMask1 = enableMask1[id & 3];
	_chanEnableMask2 = enableMask2[id & 3];
}

void SoundChannel_PC9801_SSG::reset() {
	SoundChannel_PC9801::reset();
	_activeChannnelsStatus = (_version == SCI_VERSION_1_LATE) ? 0x3F : 0xFF;
	sendActiveChannelsStatus();
}

void SoundChannel_PC9801_SSG::toggleNoiseGenerator(bool enable) {
	_ccngEnabled = enable;
}

void SoundChannel_PC9801_SSG::programChange(uint8 program) {
	_selectedInstrument = _instrumentData.subspan(program * _patchSize + _patchOffset);

	if (_version == SCI_VERSION_1_LATE) {
		programChangeInit(_selectedInstrument);

		_flags &= ~kChanNgEnable;
		if (_selectedInstrument[7])
			_flags |= (kChanNgEnable | kChanNgRestartEnv);
		_instrumentChanMask = _selectedInstrument[22];
		_activeChannnelsStatus = (_activeChannnelsStatus & _chanDisableMask) | (~_chanDisableMask & (_instrumentChanMask & 0x3F));
		if (!(_instrumentChanMask & 8)) {
			_ngPhaseStep = _selectedInstrument[21] & 0x1F;
			updateNg();
		}

		sendActiveChannelsStatus();

	} else {
		writeReg(0, 13, _selectedInstrument[0]);
		writeReg(0,  6, _selectedInstrument[1]);
		writeReg(0, 11, _selectedInstrument[2]);
		writeReg(0, 12, _selectedInstrument[3]);
	}

	_currentLevel = 0;
	_ssgEnvelopeState = kEnvSSG_silent;
}

void SoundChannel_PC9801_SSG::processSounds() {
	if (_ssgEnvelopeState == kEnvSSG_silent)
		return;

	SoundChannel_PC9801::processSounds();

	if (!(_instrumentChanMask & 0x38) && (_flags & kChanNgEnable)) {
		bool cont = false;
		if (_flags & kChanNgRestartEnv) {
			if (--_ngEnvelopeTimer) {
				cont = true;
			} else {
				uint8 flg = _selectedInstrument[10] & 4;
				_ngSpeed = ((flg ? _selectedInstrument[9] : _selectedInstrument[8]) & 0x1F) >> 1;
				_flags = (_flags & ~(kChanNgDecrease | kChanNgRestartEnv)) | (flg << 3);
			}
		}
		if (!cont) {
			uint16 s = _ngEnvelopeTimer + (_selectedInstrument[10] >> 3);
			_ngEnvelopeTimer = s & 0xFF;
			if (s & 0x100)
				cont = true;
			if (!cont) {
				if (--_ngSpeed) {
					if (_flags & kChanNgDecrease) {
						_ngSpeed = _selectedInstrument[8] & 0x1F;
						_flags &= ~kChanNgDecrease;
					} else {
						_ngSpeed = _selectedInstrument[9] & 0x1F;
						_flags |= kChanNgDecrease;
					}
				}

				if (_flags & kChanNgDecrease) {
					_ngPhase -= (_selectedInstrument[9] >> 5) * ((_selectedInstrument[10] & 3) + 1);
				} else {
					_ngPhase += (_selectedInstrument[8] >> 5) * ((_selectedInstrument[10] & 3) + 1);
				}
				updateNg();
			}
		}
	}

	uint16 s = _ssgEnvelopeTimer + (_selectedInstrument[21] & 0x60);
	_ssgEnvelopeTimer = s & 0xFF;
	if ((s & 0x100) || !(_ssgEnvelopeState & 0x0F))
		return;

	uint8 vol = _currentLevel;
	if (_ssgLevel & 1)
		vol = (vol > (_ssgLevel & ~1)) ? vol - (_ssgLevel & ~1) : 0;
	else
		vol = (vol + _ssgLevel > 255) ? 255 : vol + _ssgLevel;

	if (!--_ssgSpeed) {
		if (--_ssgEnvelopeState == (kEnvSSG_keyOn | kEnvSSG_sustain)) {
			_ssgLevel = _selectedInstrument[13];
			_ssgSpeed = _selectedInstrument[14];
			vol = (uint8)CLIP<int16>(vol + (int8)(_selectedInstrument[19] & 0xF0), 0, 255);
		}
		if (_ssgEnvelopeState == (kEnvSSG_keyOn | kEnvSSG_decay)) {
			_ssgLevel = _selectedInstrument[15];
			_ssgSpeed = _selectedInstrument[16];
			vol = (uint8)CLIP<int16>(vol + (int8)((_selectedInstrument[20] & 0x0F) << 4), 0, 255);
		}
	}
	
	if (_ssgEnvelopeState == kEnvSSG_silent)
		vol = 0;

	if (_currentLevel != vol) {
		_currentLevel = vol;
		sendVolume();
	}
}

void SoundChannel_PC9801_SSG::sendSoundOnOff(bool soundOn) {
	if (_version == SCI_VERSION_1_LATE && soundOn && !(_ssgEnvelopeState & kEnvSSG_keyOn)) {
		_currentLevel = _selectedInstrument[19] << 4;
		_ssgEnvelopeState = (kEnvSSG_keyOn | kEnvSSG_attack);
		_ssgLevel = _selectedInstrument[11];
		_ssgSpeed = _selectedInstrument[12];
		_ssgEnvelopeTimer = 1;
		if (!(_selectedInstrument[22] & 0x38)) {
			if (_selectedInstrument[21] & 0x80)
				_ngPhaseStep = _ngFreq[_note % 12];
			_ngEnvelopeTimer = _selectedInstrument[7];
			_ngPhase = 0;
			updateNg();
			_flags |= kChanNgRestartEnv;
		}
	} else if (_version == SCI_VERSION_1_LATE && !soundOn) {
		int16 l = _currentLevel + (int8)(_selectedInstrument[20] & 0xF0);
		_currentLevel = (uint8)CLIP<int16>(l, 0, 255);
		_ssgEnvelopeState = kEnvSSG_decay;
		_ssgLevel = _selectedInstrument[17];
		_ssgSpeed = _selectedInstrument[18];
		_note = 0xFF;
	} else if (_version == SCI_VERSION_0_LATE && soundOn) {
		_activeChannnelsStatus &= _chanEnableMask1;
		if (_ccngEnabled)
			_activeChannnelsStatus &= _chanEnableMask2;
		_currentLevel = 1;
		sendActiveChannelsStatus();
		writeReg(0, 13, _selectedInstrument[0]);
	} else if (_version == SCI_VERSION_0_LATE) {
		_activeChannnelsStatus |= ~_chanEnableMask1;
		if (_ccngEnabled)
			_activeChannnelsStatus |= ~_chanEnableMask2;
		_currentLevel = 0;
		_note = 0xFF;
		sendActiveChannelsStatus();
	}
	sendVolume();
}

void SoundChannel_PC9801_SSG::sendVolume() {
	uint8 v1 = getVolume();
	uint16 r = 0;
	
	if (_version == SCI_VERSION_1_LATE) {
		uint8 vol = v1 + (((v1 >> 1) + v1) >> 2);

		if (vol > 0x7F)
			vol = 0x7F;
		vol = (vol >> 3) & 0x0F;

		r = _currentLevel & 0xF0;
		for (uint8 i = 0; i < 4; ++i) {
			r += r;
			if (r & 0x100)
				r = (r + vol) & 0xFF;
		}
		
		r = (r + 15) >> 4;

	} else {
		r = _currentLevel * (0x10 | v1 >> 3);
	}

	writeReg(0, 8 + _regOffs, r);
}

void SoundChannel_PC9801_SSG::sendFrequency() {
	uint16 freq = _frequencyCourse;

	if (_version > SCI_VERSION_0_LATE) {
		freq = (uint16)(freq + _vbrFrequencyModifier) >> (8 - _frequencyBlock);
		if (!freq)
			return;
		freq = 62400 / freq;
	}

	writeReg(0, _regOffs << 1, freq & 0xFF);
	writeReg(0, (_regOffs << 1) + 1, freq >> 8);
}

void SoundChannel_PC9801_SSG::updateNg() {
	int p = _ngPhase + _ngPhaseStep;
	if (p > 255)
		p = 0;
	if (p > 31)
		p = 31;
	_ngPhase = p & 0xFF;
	writeReg(0, 6, _ngPhase);
}

void SoundChannel_PC9801_SSG::sendActiveChannelsStatus() {
	writeReg(0, 7, (_activeChannnelsStatus & 0x3F) | 0x80);
}

MidiPart_PC9801::MidiPart_PC9801(uint8 id, SoundChannel_PC9801 **channels, uint8 numChan, SciVersion version) : _chan(channels), _id(id), _version(version), _numChan(numChan),
	_program(0), _volume(0x3f), _sustain(0), _chanMissing(0), _pitchBend(0x2000), _outChan(0), _noteRangeLow(version > SCI_VERSION_0_LATE ? 12 : 24), _noteRangeHigh(version > SCI_VERSION_0_LATE ? 107 : 119) {
}

void MidiPart_PC9801::noteOff(uint8 note) {
	for (int i = 0; i < _numChan; ++i) {
		if (_chan[i]->_assign != _id || _chan[i]->_note != note)
			continue;
		if (_sustain && _version > SCI_VERSION_0_LATE)
			_chan[i]->_sustain = 1;
		else
			_chan[i]->noteOff();
		return;
	}
}

void MidiPart_PC9801::noteOn(uint8 note, uint8 velo) {
	if (note < _noteRangeLow || note > _noteRangeHigh)
		return;

	if (velo == 0) {
		noteOff(note);
		return;
	}

	velo >>= 1;

	for (int i = 0; i < _numChan; ++i) {
		if (_chan[i]->_assign != _id || _chan[i]->_note != note)
			continue;
		_chan[i]->_sustain = 0;
		_chan[i]->noteOff();
		_chan[i]->noteOn(note, velo);
		return;
	}

	int chan = allocateChannel();
	if (chan != -1)
		_chan[chan]->noteOn(note, velo);
}

void MidiPart_PC9801::controlChangeVolume(uint8 vol) {
	_volume = (_version < SCI_VERSION_1_LATE) ? vol : CLIP(vol >> 1, 0, 0x3f);
	for (int i = 0; i < _numChan; ++i) {
		if (_chan[i]->_assign == _id && _chan[i]->_note != 0xff)
			_chan[i]->processNoteEvent(_chan[i]->_note, true);
	}
}

void MidiPart_PC9801::controlChangeSustain(uint8 sus) {
	if (_version < SCI_VERSION_1_LATE)
		return;

	_sustain = sus;
	if (_sustain)
		return;

	for (int i = 0; i < _numChan; ++i) {
		if (_chan[i]->_assign == _id && _chan[i]->_sustain) {
			_chan[i]->_sustain = 0;
			_chan[i]->noteOff();
		}
	}
}

void MidiPart_PC9801::controlChangePolyphony(uint8 numChan) {
	if (_version < SCI_VERSION_1_LATE)
		return;

	uint8 numAssigned = 0;
	for (int i = 0; i < _numChan; ++i) {
		if (_chan[i]->_assign == _id)
			numAssigned++;
	}

	numAssigned += _chanMissing;
	if (numAssigned < numChan) {
		addChannels(numChan - numAssigned);
	} else if (numAssigned > numChan) {
		dropChannels(numAssigned - numChan);
		assignFreeChannels();
	}
}

void MidiPart_PC9801::controlChangeNoiseGenerator(uint8 enable) {
	if (_version > SCI_VERSION_0_LATE)
		return;

	for (int i = 0; i < _numChan; ++i) {
		if (_chan[i]->_assign == _id)
			_chan[i]->toggleNoiseGenerator(enable);
	}
}

void MidiPart_PC9801::controlChangeAllNotesOff() {
	for (int i = 0; i < _numChan; ++i) {
		if (_chan[i]->_assign == _id && _chan[i]->_note != 0xff)
			_chan[i]->noteOff();
	}
}

void MidiPart_PC9801::programChange(uint8 prg) {
	_program = prg;
}

void MidiPart_PC9801::pitchBend(int16 val) {
	if (_version < SCI_VERSION_1_LATE)
		return;

	_pitchBend = val;
	for (int i = 0; i < _numChan; ++i) {
		if (_chan[i]->_assign == _id && _chan[i]->_note != 0xff)
			_chan[i]->processNoteEvent(_chan[i]->_note, true);
	}
}


void MidiPart_PC9801::addChannels(int num, int resetMissingChannels, int channelType) {
	if (resetMissingChannels != -1)
		_chanMissing = resetMissingChannels;

	for (int i = 0; i < _numChan; ++i) {
		if (_chan[i]->_assign != 0xFF || (channelType != -1 && _chan[i]->getType() != channelType))
			continue;

		_chan[i]->_assign = _id;

#ifdef DEBUG_REMAP
		debug("===== MidiDriver_PC9801: hardware channel %d is assigned to device channel %d =====", i, _id);
#endif
		if (_chan[i]->_note != 0xff)
			_chan[i]->noteOff();

		if (!--num)
			break;
	}
	_chanMissing += num;
}

void MidiPart_PC9801::dropChannels(int num) {
	if (_chanMissing == num) {
		_chanMissing = 0;
		return;
	} else if (_chanMissing > num) {
		_chanMissing -= num;
		return;
	}

	num -= _chanMissing;
	_chanMissing = 0;

	for (int i = 0; i < _numChan; i++) {
		if (_chan[i]->_assign != _id || _chan[i]->_note != 0xff)
			continue;
		_chan[i]->_assign = 0xff;

#ifdef DEBUG_REMAP
		debug("===== MidiDriver_PC9801: hardware channel %d is dropped from device channel %d =====", i, _id);
#endif

		if (!--num)
			return;
	}

	for (int i = 0; i < _numChan; i++) {
		if (_chan[i]->_assign != _id)
			continue;
		_chan[i]->_sustain = 0;
		_chan[i]->noteOff();
		_chan[i]->_assign = 0xff;
		if (!--num)
			return;
	}
}

int MidiPart_PC9801::allocateChannel() {
	int chan = _outChan;
	int ovrChan = 0;
	int ld = 0;
	bool found = false;

	for (bool loop = true; loop; ) {
		if (++chan == _numChan)
			chan = 0;

		if (chan == _outChan)
			loop = false;

		if (_id == _chan[chan]->_assign) {
			if (_chan[chan]->_note == 0xff || _version < SCI_VERSION_1_LATE) {
				found = true;
				break;
			}

			if (_chan[chan]->_duration >= ld) {
				ld = _chan[chan]->_duration;
				ovrChan = chan;
			}
		}
	}

	if (!found) {
		if (!ld)
			return -1;
		chan = ovrChan;
		_chan[chan]->_sustain = 0;
		_chan[chan]->noteOff();
	}

	_outChan = chan;
	return chan;
}

void MidiPart_PC9801::assignFreeChannels() {
	uint8 freeChan = 0;
	for (int i = 0; i < _numChan; i++) {
		if (_chan[i]->_assign == 0xff)
			freeChan++;
	}

	if (!freeChan)
		return;

	MidiDriver_PC9801::assignFreeChannels(freeChan);
}

MidiPart_PC9801 **MidiDriver_PC9801::_parts = 0;

MidiDriver_PC9801::MidiDriver_PC9801(Audio::Mixer *mixer, SciVersion version)
	: _version(version), _pc98a(0), _chan(0), _numChan(6), _internalVersion(0xFF), _ssgPatchOffset(0xFF), _patchSize(0),
	_timerProc(0), _timerProcPara(0), _baseTempo(10080), _ready(false), _isOpen(false), _masterVolume(0x0f) ,_soundOn(true), _playID(0),
	_polyphony(9), _channelMask1(0x10), _channelMask2(0x02) {
	_pc98a =
#ifdef SCI_PC98_AUDIO_EXTENDED
		new PC98AudioCore(mixer, this, kType86);
#else
		new PC98AudioCore(mixer, this, kType26);
#endif
}

MidiDriver_PC9801::~MidiDriver_PC9801() {
	_ready = false;
	close();
	delete _pc98a;
}

int MidiDriver_PC9801::open() {
	if (_isOpen)
		return MERR_ALREADY_OPEN;

	if (!_ready) {
		if (!_pc98a->init())
			return MERR_CANNOT_CONNECT;
		_pc98a->setSoundEffectChanMask(0);
		_pc98a->ssgSetVolume(205);
		_ready = true;
	}

	ResourceManager *resMan = g_sci->getResMan();
	if (!loadInstruments(*resMan->findResource(ResourceId(kResourceTypePatch, _version < SCI_VERSION_1_LATE ? 2 : 8), false)))
		return MERR_CANNOT_CONNECT;

	if (_version == SCI_VERSION_0_LATE && _channelMask2 == 0x00) {
		_internalVersion = 0;
		_polyphony = 3;
	} else if (_version == SCI_VERSION_0_LATE && _channelMask2 == 0x02) {
		_internalVersion = 1;
		_polyphony = 6;
	} else if (_patchSize == 60 && _ssgPatchOffset == 37)
		_internalVersion = 2;
	else if (_patchSize == 81 && _ssgPatchOffset == 58)
		_internalVersion = 3;	
	else
		return MERR_CANNOT_CONNECT;

	if (_internalVersion == 3)
		_numChan++;

	int config = _internalVersion;

#ifdef	SCI_PC98_AUDIO_EXTENDED
	_numChan = 9;
	config = 4;
#endif

	static const int channelConfig[6][11] = {
		{  0,  0,  0,  2,  2,  2, -1, -1, -1, -1, -1 },
		{  0,  0,  0,  2,  2,  2, -1, -1, -1, -1, -1 },
		{  0,  0,  0,  2,  2,  2, -1, -1, -1, -1, -1 },
		{  0,  0,  1,  1,  2,  2,  2, -1, -1, -1, -1 },
		{  0,  0,  0,  0,  0,  0,  2,  2,  2, -1, -1 }
	};

	_parts = new MidiPart_PC9801*[16];
	_chan = new SoundChannel_PC9801*[_numChan];

	int numSSG = 0;
	for (int i = 0; i < _numChan; ++i) {
		if (channelConfig[config][i] == 0)
			_chan[i] = new SoundChannel_PC9801_FM4OP(i, _pc98a, _parts, _version, *_instrumentData, _patchSize, _soundOn);
		else if (channelConfig[config][i] == 1)
			_chan[i] = new SoundChannel_PC9801_FM2OP(i, _pc98a, _parts, _version, *_instrumentData, _patchSize, _soundOn);
		else if (channelConfig[config][i] == 2)
			_chan[i] = new SoundChannel_PC9801_SSG(numSSG++, _pc98a, _parts, _version, *_instrumentData, _ssgPatchOffset, _patchSize, _soundOn);
		else
			_chan[i] = 0;
	}

	for (int i = 0; i < 16; ++i)
		_parts[i] = new MidiPart_PC9801(i, _chan, _numChan, _version);

	_isOpen = true;

	reset();

	return 0;
}

void MidiDriver_PC9801::close() {
	if (!_isOpen)
		return;

	bool ready = _ready;
	_isOpen = _ready = false;

	PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex();

	if (_parts) {
		for (int i = 0; i < 16; ++i) {
			delete _parts[i];
			_parts[i] = 0;
		}
		delete[] _parts;
		_parts = 0;
	}

	if (_chan) {
		for (int i = 0; i < _numChan; ++i) {
			delete _chan[i];
			_chan[i] = 0;
		}
		delete[] _chan;
		_chan = 0;
	}

	_instrumentData.clear();

	_ready = ready;
}

void MidiDriver_PC9801::send(uint32 b) {
	if (!_isOpen)
		return;

	byte para2 = (b >> 16) & 0xFF;
	byte para1 = (b >> 8) & 0xFF;
	byte cmd = b & 0xF0;

	MidiPart_PC9801 *part = _parts[b & 0x0F];

	switch (cmd) {
	case 0x80:
		part->noteOff(para1);
		break;
	case 0x90:
		part->noteOn(para1, para2);
		break;
	case 0xb0:
		switch (para1) {
		case 7:
			part->controlChangeVolume(para2);
			break;
		case 64:
			part->controlChangeSustain(para2);
			break;
		case SCI_MIDI_SET_POLYPHONY:
			part->controlChangePolyphony(para2);
			break;
		case 76:
			// This event (from the SCI0 driver) is parsing related and can't be handled here. Lets's see if this ever comes up.
			warning("MidiDriver_PC9801: Midi Control Change '0x%2x' not implemented", para1);
			break;
		case 81:
			part->controlChangeNoiseGenerator(para2);
			break;
		case 96:
			// This event (from the SCI0 driver) is parsing related. It is handled in MidiParser_SCI::processEvent().
			break;
		case SCI_MIDI_CHANNEL_NOTES_OFF:
			part->controlChangeAllNotesOff();
			break;
		default:
			break;
		}
		break;
	case 0xc0:
		part->programChange(para1);
		break;
	case 0xe0:
		part->pitchBend(para1 | (para2 << 7));
		break;
	default:
		break;
	}
}

void MidiDriver_PC9801::assignFreeChannels(int num) {
	assert(_parts);
	for (int i = 0; i < 16; ++i) {
		uint8 missing = _parts[i]->getMissingChannels();
		if (!missing)
			continue;
		if (missing < num) {
			num -= missing;
			_parts[i]->addChannels(missing, 0);
		} else {
			_parts[i]->addChannels(num, missing - num);
			return;
		}
	}
}

uint32 MidiDriver_PC9801::property(int prop, uint32 param) {
	if (!_isOpen)
		return 0;

	switch(prop) {
	case MIDI_PROP_MASTER_VOLUME:
		if (param != 0xffff && param != _masterVolume && param < 16) {
			_masterVolume = param;
			for (int i = 0; i < _numChan; ++i) {
				_chan[i]->setVolume(_masterVolume);
				if (_chan[i]->_note != 0xFF)
					_chan[i]->processNoteEvent(_chan[i]->_note, true);
			}
		}
		return _masterVolume;
	case MIDI_PROP_PLAYSWITCH:
		_soundOn = param;
		break;
	case MIDI_PROP_POLYPHONY:
		return _polyphony;
	case MIDI_PROP_CHANNEL_ID:
		return _version < SCI_VERSION_1_LATE ? (_channelMask1 | _channelMask2) : _playID;
	default:
		break;
	}
	return 0;
}

void MidiDriver_PC9801::initTrack(SciSpan<const byte> &header) {
	if (!_isOpen || _version > SCI_VERSION_0_LATE)
		return;

	for (int i = 0; i < _numChan; ++i)
		_chan[i]->reset();

	uint8 readPos = 0;
	uint8 caps = header.getInt8At(readPos++);
	int numChan = (caps == 2) ? 15 : 16;
	if (caps != 0 && caps != 2)
		return;

	for (int i = 0; i < numChan; ++i) {
		_parts[i]->controlChangeVolume(103);
			
		uint8 num = (_internalVersion == 1) ? (header.getInt8At(readPos) & 0x7F) : 1;
		readPos++;
		uint8 flags = header.getInt8At(readPos);
		readPos++;

		if (flags & _channelMask1 && num)
			_parts[i]->addChannels(num, -1, 0);

		if (flags & _channelMask2 && num)
			_parts[i]->addChannels(num, -1, 2);

		if (_internalVersion == 0)
			_parts[i]->programChange(10);
	}
}

void MidiDriver_PC9801::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
	_timerProc = timer_proc;
	_timerProcPara = timer_param;
}

uint32 MidiDriver_PC9801::getBaseTempo() {
	return _baseTempo;
}

void MidiDriver_PC9801::timerCallbackB() {
	if (!_isOpen)
		return;
	updateParser();
	updateChannels();
}

bool MidiDriver_PC9801::loadInstruments(const SciSpan<const uint8> &data) {
	if (!data)
		return false;

	SciSpan<const uint8> src = data;
	_instrumentData.clear();

	if (_version == SCI_VERSION_0_LATE) {
		_ssgPatchOffset = 48;
		_patchSize = 52;

		_instrumentData->allocate(96 * _patchSize);
		SciSpan<uint8> dst = *_instrumentData;

		for (bool load = true; load; ) {
			for (int i = 0; i < 48; ++i) {
				src.subspan(0, _patchSize).copyDataTo(dst);
				src += 64;
				dst += _patchSize;
			}
			uint16 id = (src.byteSize() >= 2) ? src.getInt16BEAt(0) : 0;
			if (id == 0xABCD || id == 0xCDAB) {
				src += 2;
				_channelMask2 = 0x00;
			} else {
				load = false;
			}
		}
	} else if (_version == SCI_VERSION_1_LATE) {
		_instrumentData->allocateFromSpan(++src);
		_patchSize = (data.byteSize() - 1) / 96;
		_ssgPatchOffset = (_patchSize == 81) ? 58 : 37;
	}

	return (_instrumentData->byteSize() && _patchSize && _ssgPatchOffset != 0xFF);
}

void MidiDriver_PC9801::updateParser() {
	if (_timerProc)
		_timerProc(_timerProcPara);
}

void MidiDriver_PC9801::updateChannels() {
	for (int i = 0; i < _numChan; ++i)
		_chan[i]->update();
}

void MidiDriver_PC9801::reset() {
	if (!_ready)
		return;

	for (int i = 0; i < 3; ++i) {
		_pc98a->writeReg(0, 0x28, i);
		_pc98a->writeReg(0, i, 0);
		_pc98a->writeReg(0, 8 + i, 0);
	}

	uint8 flag = 0;
#ifndef SCI_PC98_AUDIO_EXTENDED
	if (_internalVersion == 3) {
		_pc98a->writeReg(0, 0xB2, 0x04);
		flag = 0x40;
	}
#endif
	
	_pc98a->writeReg(0, 0x27, 0x38);
	_pc98a->writeReg(0, 0x27, 0x3a | flag);

	if (!_isOpen)
		return;

	for (int i = 0; i < _numChan; ++i)
		_chan[i]->reset();	
}

MidiPlayer_PC9801::MidiPlayer_PC9801(SciVersion version) : MidiPlayer(version) {
	_driver = new MidiDriver_PC9801(g_system->getMixer(), version);
}

MidiPlayer_PC9801::~MidiPlayer_PC9801() {
	delete _driver;
}

bool MidiPlayer_PC9801::hasRhythmChannel() const {
	return false;
}

byte MidiPlayer_PC9801::getPlayId() const {
	return _driver ? _driver->property(MIDI_PROP_CHANNEL_ID, 0) : 0;
}

int MidiPlayer_PC9801::getPolyphony() const {
	return _driver ? _driver->property(MIDI_PROP_POLYPHONY, 0) : 0;
}

void MidiPlayer_PC9801::playSwitch(bool play) {
	if (_driver)
		_driver->property(MIDI_PROP_PLAYSWITCH, play ? 1 : 0);
}

void MidiPlayer_PC9801::initTrack(SciSpan<const byte> &trackData) {
	if (_driver)
		static_cast<MidiDriver_PC9801*>(_driver)->initTrack(trackData);
}

MidiPlayer *MidiPlayer_PC9801_create(SciVersion _soundVersion) {
	return new MidiPlayer_PC9801(_soundVersion);
}

} // End of namespace Sci

