/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef DISABLE_NES_APU

#include "engines/engine.h"
#include "scumm/players/player_nes.h"
#include "scumm/scumm.h"
#include "audio/mixer.h"

namespace Scumm {

static const byte channelMask[4] = {1, 2, 4, 8};

static const uint16 freqTable[64] = {
	0x07F0, 0x077E, 0x0712, 0x06AE, 0x064E, 0x05F3, 0x059E, 0x054D,
	0x0501, 0x04B9, 0x0475, 0x0435, 0x03F8, 0x03BF, 0x0389, 0x0357,
	0x0327, 0x02F9, 0x02CF, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A,
	0x01FC, 0x01DF, 0x01C4, 0x01AB, 0x0193, 0x017C, 0x0167, 0x0152,
	0x013F, 0x012D, 0x011C, 0x010C, 0x00FD, 0x00EE, 0x00E1, 0x00D4,
	0x00C8, 0x00BD, 0x00B2, 0x00A8, 0x009F, 0x0096, 0x008D, 0x0085,
	0x007E, 0x0076, 0x0070, 0x0069, 0x0063, 0x005E, 0x0058, 0x0053,
	0x004F, 0x004A, 0x0046, 0x0042, 0x003E, 0x003A, 0x0037, 0x0034
};

static const byte instChannel[16] = {
	0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 1, 3, 3, 3
};
static const byte startCmd[16] = {
	0x05, 0x03, 0x06, 0x08, 0x0B, 0x01, 0x01, 0x1A,
	0x16, 0x06, 0x04, 0x17, 0x02, 0x10, 0x0E, 0x0D
};
static const byte releaseCmd[16] = {
	0x0F, 0x00, 0x00, 0x09, 0x00, 0x14, 0x15, 0x00,
	0x00, 0x00, 0x1B, 0x1B, 0x0F, 0x0F, 0x0F, 0x0F
};
static const byte nextCmd[28] =	{
	0xFF, 0xFF, 0xFF, 0xFF, 0x17, 0xFF, 0x07, 0xFF,
	0xFF, 0x0A, 0x09, 0x0C, 0x00, 0x00, 0x00, 0x00,
	0x11, 0x12, 0x11, 0x03, 0xFF, 0xFF, 0x18, 0x00,
	0x19, 0x00, 0x00, 0x00
};
static const byte nextDelay[28] = {
	0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
	0x00, 0x05, 0x08, 0x03, 0x00, 0x00, 0x00, 0x00,
	0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00,
	0x03, 0x00, 0x00, 0x00
};

namespace APUe {

static const byte LengthCounts[32] = {
	0x0A,0xFE,
	0x14,0x02,
	0x28,0x04,
	0x50,0x06,
	0xA0,0x08,
	0x3C,0x0A,
	0x0E,0x0C,
	0x1A,0x0E,

	0x0C,0x10,
	0x18,0x12,
	0x30,0x14,
	0x60,0x16,
	0xC0,0x18,
	0x48,0x1A,
	0x10,0x1C,
	0x20,0x1E
};

class SoundGen {
protected:
	byte wavehold;
	uint32 freq;	// short
	uint32 CurD;

public:
	byte Timer;
	int32 Pos;
	uint32 Cycles;	// short

	inline byte GetTimer() const { return Timer; }
};

class Square : public SoundGen {
protected:
	byte volume, envelope, duty, swpspeed, swpdir, swpstep, swpenab;
	byte Vol;
	byte EnvCtr, Envelope, BendCtr;
	bool Enabled, ValidFreq, Active;
	bool EnvClk, SwpClk;

	void CheckActive();

public:
	void Reset();
	void Write(int Reg, byte Val);
	void Run();
	void QuarterFrame();
	void HalfFrame();
};

static const int8 Duties[4][8] = {
	{-4,+4,-4,-4,-4,-4,-4,-4},
	{-4,+4,+4,-4,-4,-4,-4,-4},
	{-4,+4,+4,+4,+4,-4,-4,-4},
	{+4,-4,-4,+4,+4,+4,+4,+4}
};

void Square::Reset() {
	memset(this, 0, sizeof(*this));
	Cycles = 1;
	EnvCtr = 1;
	BendCtr = 1;
}

void Square::CheckActive() {
	ValidFreq = (freq >= 0x8) && ((swpdir) || !((freq + (freq >> swpstep)) & 0x800));
	Active = Timer && ValidFreq;
	Pos = Active ? (Duties[duty][CurD] * Vol) : 0;
}

void Square::Write(int Reg, byte Val) {
	switch (Reg) {
	case 0:
		volume = Val & 0xF;
		envelope = Val & 0x10;
		wavehold = Val & 0x20;
		duty = (Val >> 6) & 0x3;
		Vol = envelope ? volume : Envelope;
		break;

	case 1:
		swpstep = Val & 0x07;
		swpdir = Val & 0x08;
		swpspeed = (Val >> 4) & 0x7;
		swpenab = Val & 0x80;
		SwpClk = true;
		break;

	case 2:
		freq &= 0x700;
		freq |= Val;
		break;

	case 3:
		freq &= 0xFF;
		freq |= (Val & 0x7) << 8;

		if (Enabled)
			Timer = LengthCounts[(Val >> 3) & 0x1F];

		CurD = 0;
		EnvClk = true;
		break;

	case 4:
		Enabled = (Val != 0);
		if (!Enabled)
			Timer = 0;
		break;

	default:
		break;
	}
	CheckActive();
}

void Square::Run() {
	Cycles = (freq + 1) << 1;
	CurD = (CurD + 1) & 0x7;

	if (Active)
		Pos = Duties[duty][CurD] * Vol;
}

void Square::QuarterFrame() {
	if (EnvClk) {
		EnvClk = false;
		Envelope = 0xF;
		EnvCtr = volume + 1;
	} else if (!--EnvCtr) {
		EnvCtr = volume + 1;

		if (Envelope)
			Envelope--;
		else
			Envelope = wavehold ? 0xF : 0x0;
	}

	Vol = envelope ? volume : Envelope;
	CheckActive();
}

void Square::HalfFrame() {
	if (!--BendCtr) {
		BendCtr = swpspeed + 1;

		if (swpenab && swpstep && ValidFreq) {
			int sweep = freq >> swpstep;
			// FIXME: Is -sweep or ~sweep correct???
			freq += swpdir ? -sweep : sweep;
		}
	}

	if (SwpClk) {
		SwpClk = false;
		BendCtr = swpspeed + 1;
	}

	if (Timer && !wavehold)
		Timer--;

	CheckActive();
}


class Triangle : public SoundGen {
protected:
	byte linear;
	byte LinCtr;
	bool Enabled, Active;
	bool LinClk;

	void CheckActive();

public:
	void Reset();
	void Write(int Reg, byte Val);
	void Run();
	void QuarterFrame();
	void HalfFrame();
};

static const int8 TriDuty[32] = {
	-8,-7,-6,-5,-4,-3,-2,-1,
	+0,+1,+2,+3,+4,+5,+6,+7,
	+7,+6,+5,+4,+3,+2,+1,+0,
	-1,-2,-3,-4,-5,-6,-7,-8
};

void Triangle::Reset() {
	memset(this, 0, sizeof(*this));
	Cycles = 1;
}

void Triangle::CheckActive() {
	Active = Timer && LinCtr;

	if (freq < 4)
		Pos = 0;	// beyond hearing range
	else
		Pos = TriDuty[CurD] * 8;
}

void Triangle::Write(int Reg, byte Val) {
	switch (Reg) {
	case 0:
		linear = Val & 0x7F;
		wavehold = (Val >> 7) & 0x1;
		break;

	case 2:
		freq &= 0x700;
		freq |= Val;
		break;

	case 3:
		freq &= 0xFF;
		freq |= (Val & 0x7) << 8;

		if (Enabled)
			Timer = LengthCounts[(Val >> 3) & 0x1F];

		LinClk = true;
		break;

	case 4:
		Enabled = (Val != 0);
		if (!Enabled)
			Timer = 0;
		break;

	default:
		break;
	}
	CheckActive();
}

void Triangle::Run() {
	Cycles = freq + 1;

	if (Active) {
		CurD++;
		CurD &= 0x1F;

		if (freq < 4)
			Pos = 0;	// beyond hearing range
		else
			Pos = TriDuty[CurD] * 8;
	}
}

void Triangle::QuarterFrame() {
	if (LinClk)
		LinCtr = linear;
	else if (LinCtr)
		LinCtr--;

	if (!wavehold)
		LinClk = false;

	CheckActive();
}

void Triangle::HalfFrame() {
	if (Timer && !wavehold)
		Timer--;

	CheckActive();
}

class Noise : public SoundGen {
protected:
	byte volume, envelope, datatype;
	byte Vol;
	byte EnvCtr, Envelope;
	bool Enabled;
	bool EnvClk;

	void CheckActive();

public:
	void Reset();
	void Write(int Reg, byte Val);
	void Run();
	void QuarterFrame();
	void HalfFrame();
};

static const uint32 NoiseFreq[16] = {
	0x004,0x008,0x010,0x020,0x040,0x060,0x080,0x0A0,
	0x0CA,0x0FE,0x17C,0x1FC,0x2FA,0x3F8,0x7F2,0xFE4
};

void Noise::Reset() {
	memset(this, 0, sizeof(*this));
	CurD = 1;
	Cycles = 1;
	EnvCtr = 1;

}

void Noise::Write(int Reg, byte Val) {
	switch (Reg) {
	case 0:
		volume = Val & 0x0F;
		envelope = Val & 0x10;
		wavehold = Val & 0x20;
		Vol = envelope ? volume : Envelope;

		if (Timer)
			Pos = ((CurD & 0x4000) ? -2 : 2) * Vol;
		break;

	case 2:
		freq = Val & 0xF;
		datatype = Val & 0x80;
		break;

	case 3:
		if (Enabled)
			Timer = LengthCounts[(Val >> 3) & 0x1F];

		EnvClk = true;
		break;

	case 4:
		Enabled = (Val != 0);
		if (!Enabled)
			Timer = 0;
		break;

	default:
		break;
	}
}

void Noise::Run() {
	Cycles = NoiseFreq[freq];	/* no + 1 here */

	if (datatype)
		CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 8)) & 0x1);
	else
		CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 13)) & 0x1);

	if (Timer)
		Pos = ((CurD & 0x4000) ? -2 : 2) * Vol;
}

void Noise::QuarterFrame() {
	if (EnvClk) {
		EnvClk = false;
		Envelope = 0xF;
		EnvCtr = volume + 1;
	} else if (!--EnvCtr) {
		EnvCtr = volume + 1;

		if (Envelope)
			Envelope--;
		else
			Envelope = wavehold ? 0xF : 0x0;
	}

	Vol = envelope ? volume : Envelope;

	if (Timer)
		Pos = ((CurD & 0x4000) ? -2 : 2) * Vol;
}

void Noise::HalfFrame() {
	if (Timer && !wavehold)
		Timer--;
}

class APU {
protected:
	int	BufPos;
	int	SampleRate;

	Square _square0;
	Square _square1;
	Triangle _triangle;
	Noise _noise;

	struct {
		uint32 Cycles;
		int Num;
	} Frame;

public:
	APU(int rate) : SampleRate(rate) {
		Reset();
	}

	void WriteReg(int Addr, byte Val);
	byte Read4015();
	void Reset ();
	int16 GetSample();
};

void APU::WriteReg(int Addr, byte Val) {
	switch (Addr) {
	case 0x000:	_square0.Write(0,Val);	break;
	case 0x001:	_square0.Write(1,Val);	break;
	case 0x002:	_square0.Write(2,Val);	break;
	case 0x003:	_square0.Write(3,Val);	break;
	case 0x004:	_square1.Write(0,Val);	break;
	case 0x005:	_square1.Write(1,Val);	break;
	case 0x006:	_square1.Write(2,Val);	break;
	case 0x007:	_square1.Write(3,Val);	break;
	case 0x008:	_triangle.Write(0,Val);	break;
	case 0x009:	_triangle.Write(1,Val);	break;
	case 0x00A:	_triangle.Write(2,Val);	break;
	case 0x00B:	_triangle.Write(3,Val);	break;
	case 0x00C:	_noise.Write(0,Val);	break;
	case 0x00D:	_noise.Write(1,Val);	break;
	case 0x00E:	_noise.Write(2,Val);	break;
	case 0x00F:	_noise.Write(3,Val);	break;
	case 0x015:	_square0.Write(4,Val & 0x1);
				_square1.Write(4,Val & 0x2);
				_triangle.Write(4,Val & 0x4);
				_noise.Write(4,Val & 0x8);
		break;
	default:
		break;
	}
}

byte APU::Read4015() {
	byte result =
		(( _square0.GetTimer()) ? 0x01 : 0) |
		(( _square1.GetTimer()) ? 0x02 : 0) |
		((_triangle.GetTimer()) ? 0x04 : 0) |
		((   _noise.GetTimer()) ? 0x08 : 0);
	return result;
}

void APU::Reset () {
	BufPos = 0;

	_square0.Reset();
	_square1.Reset();
	_triangle.Reset();
	_noise.Reset();

	Frame.Num = 0;
	Frame.Cycles = 1;
}

template<class T>
int step(T &obj, int sampcycles, uint frame_Cycles, int frame_Num) {
	int samppos = 0;
	while (sampcycles) {
		// Compute the maximal amount we can step ahead before triggering
		// an action (i.e. compute the minimum of sampcycles, frame_Cycles
		// and obj.Cycles).
		uint max_step = sampcycles;
		if (max_step > frame_Cycles)
			max_step = frame_Cycles;
		if (max_step > obj.Cycles)
			max_step = obj.Cycles;

		// During all but the last of these steps, we just add the value of obj.Pos
		// to samppos -- so we can to that all at once with a simple multiplication:
		samppos += obj.Pos * (max_step - 1);

		// Now step ahead...
		sampcycles -= max_step;
		frame_Cycles -= max_step;
		obj.Cycles -= max_step;

		if (!frame_Cycles) {
			frame_Cycles = 7457;

			if (frame_Num < 4) {
				obj.QuarterFrame();

				if (frame_Num & 1)
					frame_Cycles++;
				else
					obj.HalfFrame();

				frame_Num++;
			} else
				frame_Num = 0;
		}

		if (!obj.Cycles)
			obj.Run();

		samppos += obj.Pos;
	}

	return samppos;
}

int16 APU::GetSample() {
	int samppos = 0;

	const int sampcycles = 1+(1789773-BufPos-1)/SampleRate;
	BufPos = BufPos + sampcycles * SampleRate - 1789773;

	samppos += step( _square0, sampcycles, Frame.Cycles, Frame.Num);
	samppos += step( _square1, sampcycles, Frame.Cycles, Frame.Num);
	samppos += step(_triangle, sampcycles, Frame.Cycles, Frame.Num);
	samppos += step(   _noise, sampcycles, Frame.Cycles, Frame.Num);

	uint tmp = sampcycles;
	while (tmp >= Frame.Cycles) {
		tmp -= Frame.Cycles;
		Frame.Cycles = 7457;

		if (Frame.Num < 4) {
			if (Frame.Num & 1)
				Frame.Cycles++;
			Frame.Num++;
		} else
			Frame.Num = 0;
	}

	Frame.Cycles -= tmp;

	return (samppos << 6) / sampcycles;
}

} // End of namespace APUe

Player_NES::Player_NES(ScummEngine *scumm, Audio::Mixer *mixer) {
	int i;
	_vm = scumm;
	_mixer = mixer;
	_sampleRate = _mixer->getOutputRate();
	_apu = new APUe::APU(_sampleRate);

	_samples_per_frame = _sampleRate / 60;
	_current_sample = 0;

	for (i = 0; i < NUMSLOTS; i++) {
		_slot[i].id = -1;
		_slot[i].framesleft = 0;
		_slot[i].type = 0;
		_slot[i].offset = 0;
		_slot[i].data = nullptr;
	}

	for (i = 0; i < NUMCHANS; i++) {
		_mchan[i].command = 0;
		_mchan[i].framedelay = 0;
		_mchan[i].pitch = 0;
		_mchan[i].volume = 0;
		_mchan[i].voldelta = 0;
		_mchan[i].envflags = 0;
		_mchan[i].cmdlock = 0;
	}
	isSFXplaying = wasSFXplaying = false;

	auxData1 = auxData2 = nullptr;
	numNotes = 0;

	APU_writeControl(0);

	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}

Player_NES::~Player_NES() {
	_mixer->stopHandle(_soundHandle);
	delete _apu;
}

void Player_NES::setMusicVolume (int vol) {
	_maxvol = vol;
}

int Player_NES::readBuffer(int16 *buffer, const int numSamples) {
	for (int n = 0; n < numSamples; n++) {
		buffer[n] = _apu->GetSample() * _maxvol / 255;
		_current_sample++;

		if (_current_sample == _samples_per_frame) {
			_current_sample = 0;
			sound_play();
		}
	}
	return numSamples;
}
void Player_NES::stopAllSounds() {
	for (int i = 0; i < NUMSLOTS; i++) {
		_slot[i].framesleft = 0;
		_slot[i].type = 0;
		_slot[i].id = -1;
	}

	isSFXplaying = 0;
	checkSilenceChannels(0);
}

void Player_NES::stopSound(int nr) {
	if (nr == -1)
		return;

	for (int i = 0; i < NUMSLOTS; i++) {
		if (_slot[i].id != nr)
			continue;

		isSFXplaying = 0;
		_slot[i].framesleft = 0;
		_slot[i].type = 0;
		_slot[i].id = -1;
		checkSilenceChannels(i);
	}
}

void Player_NES::startSound(int nr) {
	byte *data = _vm->getResourceAddress(rtSound, nr) + 2;
	assert(data);

	int soundType = data[1];
	int chan = data[0];

	if (chan == 4) {
		if (_slot[2].framesleft)
			return;
		chan = 0;
	}

	if (soundType < _slot[chan].type)
		return;

	_slot[chan].type = soundType;
	_slot[chan].id = nr;
	_slot[chan].data = data;
	_slot[chan].offset = 2;
	_slot[chan].framesleft = 1;
	checkSilenceChannels(chan);
	if (chan == 2) {
		numNotes = _slot[chan].data[2];
		auxData1 = _slot[chan].data + 3;
		auxData2 = auxData1 + numNotes;
		_slot[chan].data = auxData2 + numNotes;
		_slot[chan].offset = 0;

		for (int i = 0; i < NUMCHANS; i++)
			_mchan[i].cmdlock = 0;
	}
}

void Player_NES::checkSilenceChannels(int chan) {
	for (chan--; chan >= 0; chan--) {
		if (_slot[chan].framesleft)
			return;
	}
	APU_writeControl(0);
}

void Player_NES::sound_play() {
	if (_slot[0].framesleft)
		playSFX(0);
	else if (_slot[1].framesleft)
		playSFX(1);

	playMusic();
}

void Player_NES::playSFX (int nr) {
	if (--_slot[nr].framesleft)
		return;

	while (1) {
		int a = _slot[nr].data[_slot[nr].offset++];
		if (a < 16) {
			a >>= 2;
			APU_writeControl(APU_readStatus() | channelMask[a]);
			isSFXplaying = true;
			APU_writeChannel(a, 0, _slot[nr].data[_slot[nr].offset++]);
			APU_writeChannel(a, 1, _slot[nr].data[_slot[nr].offset++]);
			APU_writeChannel(a, 2, _slot[nr].data[_slot[nr].offset++]);
			APU_writeChannel(a, 3, _slot[nr].data[_slot[nr].offset++]);
		} else if (a == 0xFE) {
			_slot[nr].offset = 2;
		} else if (a == 0xFF) {
			_slot[nr].id = -1;
			_slot[nr].type = 0;
			isSFXplaying = false;
			APU_writeControl(0);

			if (!nr && _slot[1].framesleft) {
				_slot[1].framesleft = 1;
				isSFXplaying = true;
			}
			return;
		} else {
			_slot[nr].framesleft = _slot[nr].data[_slot[nr].offset++];
			return;
		}
	}
}

void Player_NES::playMusic() {
	if (!_slot[2].framesleft)
		return;

	if (wasSFXplaying && !isSFXplaying)
		for (int x = 1; x >= 0; x--)
			if (_mchan[x].cmdlock) {
				_mchan[x].command = _mchan[x].cmdlock;
				_mchan[x].framedelay = 1;
			}

	wasSFXplaying = isSFXplaying;
	if (!--_slot[2].framesleft) {
top:
		int b = _slot[2].data[_slot[2].offset++];
		if (b == 0xFF) {
			_slot[2].id = -1;
			_slot[2].type = 0;
			b = 0;
		} else if (b == 0xFE) {
			_slot[2].offset = 0;
			goto top;
		} else {
			if (b < numNotes) {
				int inst = auxData1[b];
				int ch = instChannel[inst];
				_mchan[ch].pitch = auxData2[b];
				_mchan[ch].cmdlock = startCmd[inst];
				_mchan[ch].command = startCmd[inst];
				_mchan[ch].framedelay = 1;
				goto top;
			}
			b -= numNotes;
			if (b < 16) {
				int inst = b;
				int ch = instChannel[inst];
				_mchan[ch].cmdlock = 0;
				_mchan[ch].command = releaseCmd[inst];
				_mchan[ch].framedelay = 1;
				goto top;
			}
			b -= 16;
		}
		_slot[2].framesleft = b;
	}

	for (int x = NUMCHANS - 1; x >= 0; x--) {
		if (_slot[0].framesleft || _slot[1].framesleft) {
			_mchan[x].volume = 0;
			_mchan[x].framedelay = 0;
			continue;
		}

		if (_mchan[x].framedelay && !--_mchan[x].framedelay) {
			switch (_mchan[x].command) {
			case 0x00:
			case 0x13:
				_mchan[x].voldelta = -10;
				break;

			case 0x01:
			case 0x03:
			case 0x08:
			case 0x16:
				_mchan[x].envflags = 0x30;
				_mchan[x].volume = 0x6F;
				_mchan[x].voldelta = 0;

				APU_writeChannel(x, 0, 0x00);
				APU_writeChannel(x, 1, 0x7F);
				APU_writeControl(APU_readStatus() | channelMask[x]);
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);

				chainCommand(x);
				break;

			case 0x02:
				_mchan[x].envflags = 0xB0;
				_mchan[x].volume = 0x6F;
				_mchan[x].voldelta = 0;

				APU_writeChannel(x, 0, 0x00);
				APU_writeChannel(x, 1, 0x84);
				APU_writeControl(APU_readStatus() | channelMask[x]);
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);

				chainCommand(x);
				break;

			case 0x04:
				_mchan[x].envflags = 0x80;
				_mchan[x].volume = 0x6F;
				_mchan[x].voldelta = 0;

				APU_writeChannel(x, 0, 0x00);
				APU_writeChannel(x, 1, 0x7F);
				APU_writeControl(APU_readStatus() | channelMask[x]);
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);

				chainCommand(x);
				break;

			case 0x05:
				_mchan[x].envflags = 0xF0;
				_mchan[x].volume = 0x6F;
				_mchan[x].voldelta = -15;

				APU_writeChannel(x, 1, 0x7F);
				APU_writeControl(APU_readStatus() | channelMask[x]);
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);

				chainCommand(x);
				break;

			case 0x06:
				_mchan[x].pitch += 0x18;
				_mchan[x].envflags = 0x80;
				_mchan[x].volume = 0x6F;
				_mchan[x].voldelta = 0;

				APU_writeChannel(x, 0, 0x00);
				APU_writeChannel(x, 1, 0x7F);
				APU_writeControl(APU_readStatus() | channelMask[x]);
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);

				chainCommand(x);
				break;

			case 0x07:
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch - 0x0C] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch - 0x0C] >> 8);

				chainCommand(x);
				break;

			case 0x09:
				_mchan[x].voldelta = -2;

				APU_writeChannel(x, 1, 0x7F);
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);

				chainCommand(x);
				break;

			case 0x0A:
				APU_writeChannel(x, 1, 0x86);
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);

				chainCommand(x);
				break;

			case 0x0B:	case 0x1A:
				_mchan[x].envflags = 0x70;
				_mchan[x].volume = 0x6F;
				_mchan[x].voldelta = 0;

				APU_writeChannel(x, 0, 0x00);
				APU_writeChannel(x, 1, 0x7F);
				APU_writeControl(APU_readStatus() | channelMask[x]);
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);

				chainCommand(x);
				break;

			case 0x0C:
				_mchan[x].envflags = 0xB0;

				chainCommand(x);
				break;

			case 0x0D:
				_mchan[x].envflags = 0x30;
				_mchan[x].volume = 0x5F;
				_mchan[x].voldelta = -22;

				APU_writeChannel(x, 0, 0x00);
				APU_writeControl(APU_readStatus() | channelMask[x]);
				APU_writeChannel(x, 2, _mchan[x].pitch & 0xF);
				APU_writeChannel(x, 3, 0xFF);

				chainCommand(x);
				break;

			case 0x0E:
			case 0x10:
				_mchan[x].envflags = 0x30;
				_mchan[x].volume = 0x5F;
				_mchan[x].voldelta = -6;

				APU_writeChannel(x, 0, 0x00);
				APU_writeControl(APU_readStatus() | channelMask[x]);
				APU_writeChannel(x, 2, _mchan[x].pitch & 0xF);
				APU_writeChannel(x, 3, 0xFF);

				chainCommand(x);
				break;

			case 0x0F:
				chainCommand(x);
				break;

			case 0x11:
				APU_writeChannel(x, 2, _mchan[x].pitch & 0xF);
				APU_writeChannel(x, 3, 0xFF);

				chainCommand(x);
				break;

			case 0x12:
				APU_writeChannel(x, 2, (_mchan[x].pitch + 3) & 0xF);
				APU_writeChannel(x, 3, 0xFF);

				chainCommand(x);
				break;

			case 0x14:
				_mchan[x].voldelta = -12;

				APU_writeChannel(x, 1, 0x8C);

				chainCommand(x);
				break;

			case 0x15:
				_mchan[x].voldelta = -12;

				APU_writeChannel(x, 1, 0x84);

				chainCommand(x);
				break;

			case 0x17:
				_mchan[x].pitch += 0x0C;
				_mchan[x].envflags = 0x80;
				_mchan[x].volume = 0x6F;
				_mchan[x].voldelta = 0;

				APU_writeChannel(x, 0, 0x00);
				APU_writeChannel(x, 1, 0x7F);
				APU_writeControl(APU_readStatus() | channelMask[x]);
				APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF);
				APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8);

				chainCommand(x);
				break;

			case 0x18:
				_mchan[x].envflags = 0x70;

				chainCommand(x);
				break;

			case 0x19:
				_mchan[x].envflags = 0xB0;

				chainCommand(x);
				break;

			case 0x1B:
				_mchan[x].envflags = 0x00;
				_mchan[x].voldelta = -10;
				break;

			default:
				break;
			}
		}

		_mchan[x].volume += _mchan[x].voldelta;
		_mchan[x].volume = CLIP(_mchan[x].volume, 0, MAXVOLUME);

		APU_writeChannel(x, 0, (_mchan[x].volume >> 3) | _mchan[x].envflags);
	}
}

void Player_NES::chainCommand(int c) {
	int i = _mchan[c].command;
	_mchan[c].command = nextCmd[i];
	_mchan[c].framedelay = nextDelay[i];
}

int Player_NES::getSoundStatus(int nr) const {
	for (int i = 0; i < NUMSLOTS; i++)
		if (_slot[i].id == nr)
			return 1;
	return 0;
}

void Player_NES::APU_writeChannel(int chan, int offset, byte value) {
	_apu->WriteReg(0x000 + 4 * chan + offset, value);
}
void Player_NES::APU_writeControl(byte value) {
	_apu->WriteReg(0x015, value);
}
byte Player_NES::APU_readStatus() {
	return _apu->Read4015();
}

} // End of namespace Scumm

#endif // DISABLE_NES_APU
