/* 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.
 *
 * $URL$
 * $Id$
 *
 */

#include "scumm/player_v2cms.h"
#include "scumm/scumm.h"
#include "audio/mididrv.h"
#include "audio/mixer.h"
#include "audio/softsynth/cms.h"

namespace Scumm {

#define PROCESS_ATTACK 1
#define PROCESS_RELEASE 2
#define PROCESS_SUSTAIN 3
#define PROCESS_DECAY 4
#define PROCESS_VIBRATO 5

#define CMS_RATE 22050

static const byte freqTable[] = {
	  3,  10,  17,  24,  31,  38,  45,  51,
	 58,  65,  71,  77,  83,  90,  96, 102,
	107, 113, 119, 125, 130, 136, 141, 146,
	151, 157, 162, 167, 172, 177, 181, 186,
	191, 195, 200, 204, 209, 213, 217, 221,
	226, 230, 234, 238, 242, 246, 249, 253
};

/*static const byte amplTable[] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	// 0 %
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	// 10 %
	0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10,	// 20 %
	0x10, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30,
	0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x20,	// 30%
	0x20, 0x20, 0x30, 0x30, 0x30, 0x30, 0x40, 0x40,
	0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x20, 0x20,	// 40 %
	0x30, 0x30, 0x40, 0x40, 0x40, 0x50, 0x50, 0x60,
	0x00, 0x00, 0x10, 0x10, 0x20, 0x20, 0x30, 0x30,	// 50%
	0x40, 0x40, 0x50, 0x50, 0x60, 0x60, 0x70, 0x70,
	0x00, 0x00, 0x10, 0x10, 0x20, 0x30, 0x30, 0x40,	// 60 %
	0x40, 0x50, 0x60, 0x60, 0x70, 0x70, 0x80, 0x90,
	0x00, 0x00, 0x10, 0x20, 0x20, 0x30, 0x40, 0x40,	// 70 %
	0x50, 0x60, 0x70, 0x70, 0x80, 0x90, 0x90, 0xA0,
	0x00, 0x00, 0x10, 0x20, 0x30, 0x40, 0x40, 0x50,	// 80 %
	0x60, 0x70, 0x80, 0x80, 0x90, 0xA0, 0xB0, 0xC0,
	0x00, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60,	// 90 %
	0x70, 0x80, 0x90, 0x90, 0xA0, 0xB0, 0xC0, 0xD0,
	0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,	// 100 %
	0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0
};*/

static const byte octaveTable[] = {
	0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03,
	0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07,
	0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B,
	0x01, 0x00, 0x01, 0x01, 0x01, 0x02, 0x01, 0x03,
	0x01, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x07,
	0x01, 0x08, 0x01, 0x09, 0x01, 0x0A, 0x01, 0x0B,
	0x02, 0x00, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03,
	0x02, 0x04, 0x02, 0x05, 0x02, 0x06, 0x02, 0x07,
	0x02, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x0B,
	0x03, 0x00, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03,
	0x03, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x07,
	0x03, 0x08, 0x03, 0x09, 0x03, 0x0A, 0x03, 0x0B,
	0x04, 0x00, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03,
	0x04, 0x04, 0x04, 0x05, 0x04, 0x06, 0x04, 0x07,
	0x04, 0x08, 0x04, 0x09, 0x04, 0x0A, 0x04, 0x0B,
	0x05, 0x00, 0x05, 0x01, 0x05, 0x02, 0x05, 0x03,
	0x05, 0x04, 0x05, 0x05, 0x05, 0x06, 0x05, 0x07,
	0x05, 0x08, 0x05, 0x09, 0x05, 0x0A, 0x05, 0x0B,
	0x06, 0x00, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03,
	0x06, 0x04, 0x06, 0x05, 0x06, 0x06, 0x06, 0x07,
	0x06, 0x08, 0x06, 0x09, 0x06, 0x0A, 0x06, 0x0B,
	0x07, 0x00, 0x07, 0x01, 0x07, 0x02, 0x07, 0x03,
	0x07, 0x04, 0x07, 0x05, 0x07, 0x06, 0x07, 0x07,
	0x07, 0x08, 0x07, 0x09, 0x07, 0x0A, 0x07, 0x0B,
	0x08, 0x00, 0x08, 0x01, 0x08, 0x02, 0x08, 0x03,
	0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x08, 0x07,
	0x08, 0x08, 0x08, 0x09, 0x08, 0x0A, 0x08, 0x0B,
	0x09, 0x00, 0x09, 0x01, 0x09, 0x02, 0x09, 0x03,
	0x09, 0x04, 0x09, 0x05, 0x09, 0x06, 0x09, 0x07,
	0x09, 0x08, 0x09, 0x09, 0x09, 0x0A, 0x09, 0x0B,
	0x0A, 0x00, 0x0A, 0x01, 0x0A, 0x02, 0x0A, 0x03,
	0x0A, 0x04, 0x0A, 0x05, 0x0A, 0x06, 0x0A, 0x07,
	0x0A, 0x08, 0x0A, 0x09, 0x0A, 0x0A, 0x0A, 0x0B
};

static const byte attackRate[] = {
	  0,   2,   4,   7,  14,  26,  48,  82,
	128, 144, 160, 176, 192, 208, 224, 255
};

static const byte decayRate[] = {
	  0,   1,   2,   3,   4,   6,  12,  24,
	 48,  96, 192, 215, 255, 255, 255, 255
};

static const byte sustainRate[] = {
	255, 180, 128,  96,  80,  64,  56,  48,
	 42,  36,  32,  28,  24,  20,  16,   0
};

static const byte releaseRate[] = {
	  0,   1,   2,   4,   6,   9,  14,  22,
	 36,  56,  80, 100, 120, 140, 160, 255
};

static const byte volumeTable[] = {
	0x00, 0x10, 0x10, 0x11, 0x11, 0x21, 0x22, 0x22,
	0x33, 0x44, 0x55, 0x66, 0x88, 0xAA, 0xCC, 0xFF
};

Player_V2CMS::Player_V2CMS(ScummEngine *scumm, Audio::Mixer *mixer)
	: Player_V2Base(scumm, mixer, true) {
	int i;

	setMusicVolume(255);

	memset(_cmsVoicesBase, 0, sizeof(Voice)*16);
	memset(_cmsVoices, 0, sizeof(Voice2)*8);
	memset(_cmsChips, 0, sizeof(MusicChip)*2);
	_midiDelay = _octaveMask = _looping = _tempo = 0;
	_midiData = _midiSongBegin = 0;
	_loadedMidiSong = 0;
	memset(_midiChannel, 0, sizeof(Voice2*)*16);
	memset(_midiChannelUse, 0, sizeof(byte)*16);

	_cmsVoices[0].amplitudeOutput = &(_cmsChips[0].ampl[0]);
	_cmsVoices[0].freqOutput = &(_cmsChips[0].freq[0]);
	_cmsVoices[0].octaveOutput = &(_cmsChips[0].octave[0]);
	_cmsVoices[1].amplitudeOutput = &(_cmsChips[0].ampl[1]);
	_cmsVoices[1].freqOutput = &(_cmsChips[0].freq[1]);
	_cmsVoices[1].octaveOutput = &(_cmsChips[0].octave[0]);
	_cmsVoices[2].amplitudeOutput = &(_cmsChips[0].ampl[2]);
	_cmsVoices[2].freqOutput = &(_cmsChips[0].freq[2]);
	_cmsVoices[2].octaveOutput = &(_cmsChips[0].octave[1]);
	_cmsVoices[3].amplitudeOutput = &(_cmsChips[0].ampl[3]);
	_cmsVoices[3].freqOutput = &(_cmsChips[0].freq[3]);
	_cmsVoices[3].octaveOutput = &(_cmsChips[0].octave[1]);
	_cmsVoices[4].amplitudeOutput = &(_cmsChips[1].ampl[0]);
	_cmsVoices[4].freqOutput = &(_cmsChips[1].freq[0]);
	_cmsVoices[4].octaveOutput = &(_cmsChips[1].octave[0]);
	_cmsVoices[5].amplitudeOutput = &(_cmsChips[1].ampl[1]);
	_cmsVoices[5].freqOutput = &(_cmsChips[1].freq[1]);
	_cmsVoices[5].octaveOutput = &(_cmsChips[1].octave[0]);
	_cmsVoices[6].amplitudeOutput = &(_cmsChips[1].ampl[2]);
	_cmsVoices[6].freqOutput = &(_cmsChips[1].freq[2]);
	_cmsVoices[6].octaveOutput = &(_cmsChips[1].octave[1]);
	_cmsVoices[7].amplitudeOutput = &(_cmsChips[1].ampl[3]);
	_cmsVoices[7].freqOutput = &(_cmsChips[1].freq[3]);
	_cmsVoices[7].octaveOutput = &(_cmsChips[1].octave[1]);

	// inits the CMS Emulator like in the original
	_cmsEmu = new CMSEmulator(_sampleRate);
	static const byte cmsInitData[13*2] = {
		0x1C, 0x02,
		0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00,
		0x14, 0x3F, 0x15, 0x00, 0x16, 0x00, 0x18, 0x00, 0x19, 0x00, 0x1C, 0x01
	};

	i = 0;
	for (int cmsPort = 0x220; i < 2; cmsPort += 2, ++i) {
		for (int off = 0; off < 13; ++off) {
			_cmsEmu->portWrite(cmsPort+1, cmsInitData[off*2]);
			_cmsEmu->portWrite(cmsPort, cmsInitData[off*2+1]);
		}
	}

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

Player_V2CMS::~Player_V2CMS() {
	Common::StackLock lock(_mutex);

	_mixer->stopHandle(_soundHandle);
	delete _cmsEmu;
}

void Player_V2CMS::setMusicVolume(int vol) {
}

void Player_V2CMS::stopAllSounds() {
	Common::StackLock lock(_mutex);

	for (int i = 0; i < 4; i++) {
		clear_channel(i);
	}
	_next_nr = _current_nr = 0;
	_next_data = _current_data = 0;
	_midiData = 0;
	_midiSongBegin = 0;
	_midiDelay = 0;
	offAllChannels();
}

void Player_V2CMS::stopSound(int nr) {
	Common::StackLock lock(_mutex);

	if (_next_nr == nr) {
		_next_nr = 0;
		_next_data = 0;
	}
	if (_current_nr == nr) {
		for (int i = 0; i < 4; i++) {
			clear_channel(i);
		}
		_current_nr = 0;
		_current_data = 0;
		chainNextSound();
	}
	if (_loadedMidiSong == nr) {
		_midiData = 0;
		_midiSongBegin = 0;
		_midiDelay = 0;
		offAllChannels();
	}
}

void Player_V2CMS::startSound(int nr) {
	Common::StackLock lock(_mutex);

	byte *data = _vm->getResourceAddress(rtSound, nr);
	assert(data);

	if (data[6] == 0x80) {
		loadMidiData(data, nr);
	} else {
		int cprio = _current_data ? *(_current_data + _header_len) : 0;
		int prio  = *(data + _header_len);
		int nprio = _next_data ? *(_next_data + _header_len) : 0;

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

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

			chainSound(nr, data);
			nr   = tnr;
			prio = tprio;
			data = tdata;
			restartable = data ? *(data + _header_len + 1) : 0;
		}

		if (!_current_nr) {
			nr = 0;
			_next_nr = 0;
			_next_data = 0;
		}

		if (nr != _current_nr
			&& restartable
			&& (!_next_nr
			|| nprio <= prio)) {

			_next_nr = nr;
			_next_data = data;
		}
	}
}

void Player_V2CMS::loadMidiData(byte *data, int sound) {
	memset(_midiChannelUse, 0, sizeof(byte)*16);

	_tempo = data[7];
	_looping = data[8];

	byte channels = data[14];
	byte curChannel = 0;
	byte *voice2 = data + 23;

	for (; channels != 0; ++curChannel, --channels, voice2 += 16) {
		if (*(data + 15 + curChannel)) {
			byte channel = *(data + 15 + curChannel) - 1;
			_midiChannelUse[channel] = 1;

			Voice *voiceDef = &_cmsVoicesBase[channel];

			byte attackDecay = voice2[10];
			voiceDef->attack = attackRate[attackDecay >> 4];
			voiceDef->decay = decayRate[attackDecay & 0x0F];
			byte sustainRelease = voice2[11];
			voiceDef->sustain = sustainRate[sustainRelease >> 4];
			voiceDef->release = releaseRate[sustainRelease & 0x0F];

			if (voice2[3] & 0x40) {
				voiceDef->vibrato = 0x0301;
				if (voice2[13] & 0x40) {
					voiceDef->vibrato = 0x0601;
				}
			} else {
				voiceDef->vibrato = 0;
			}

			if (voice2[8] & 0x80) {
				voiceDef->vibrato2 = 0x0506;
				if (voice2[13] & 0x80) {
					voiceDef->vibrato2 = 0x050C;
				}
			} else {
				voiceDef->vibrato2 = 0;
			}

			if ((voice2[8] & 0x0F) > 1) {
				voiceDef->octadd = 0x01;
			} else {
				voiceDef->octadd = 0x00;
			}
		}
	}

	for (int i = 0, channel = 0; i < 8; ++i, channel += 2) {
		_cmsVoices[i].chanNumber = 0xFF;
		_cmsVoices[i].curVolume = 0;
		_cmsVoices[i].nextVoice = 0;

		_midiChannel[channel] = 0;
	}

	_midiDelay = 0;
	memset(_cmsChips, 0, sizeof(MusicChip)*2);
	_midiData = data + 151;
	_midiSongBegin = _midiData + data[9];

	_loadedMidiSong = sound;
}

int Player_V2CMS::getSoundStatus(int nr) const {
	return _current_nr == nr || _next_nr == nr || _loadedMidiSong == nr;
}

void Player_V2CMS::processMidiData(uint ticks) {
	byte *currentData = _midiData;
	byte command = 0x00;
	int16 temp = 0;

	if (!_midiDelay) {
		while (true) {
			if ((command = *currentData++) == 0xFF) {
				if ((command = *currentData++) == 0x2F) {
					if (_looping == 0) {
						currentData = _midiData = _midiSongBegin;
						continue;
					}
					_midiData = _midiSongBegin = 0;
					offAllChannels();
					return;
				} else {
					if (command == 0x58) {
						currentData += 6;
					}
				}
			} else {
				_lastMidiCommand = command;
				if (command < 0x90) {
					clearNote(currentData);
				} else {
					playNote(currentData);
				}
			}

			temp = command = *currentData++;
			if (command & 0x80) {
				temp = (command & 0x7F) << 8;
				command = *currentData++;
				temp |= (command << 1);
				temp >>= 1;
			}
			temp >>= 1;
			int lastBit = temp & 1;
			temp >>= 1;
			temp += lastBit;

			if (temp)
				break;
		}
		_midiData = currentData;
		_midiDelay = temp;
	}

	_midiDelay -= ticks;
	if (_midiDelay < 0)
		_midiDelay = 0;

	return;
}

int Player_V2CMS::readBuffer(int16 *buffer, const int numSamples) {
	Common::StackLock lock(_mutex);

	uint step = 1;
	int len = numSamples / 2;

	// maybe this needs a complete rewrite
	do {
		if (_midiData) {
			--_clkFrequenz;
			if (!(_clkFrequenz & 0x01)) {
				playVoice();
			}

			_tempoSum += _tempo;
			// FIXME: _tempoSum is declared as char; on some systems char is unsigned.
			// E.g. on OS X. Hence the following check is always false.
			// Moral of the story: Use uint8, int8 or any of the other types provided by
			// ScummVM if you want to ensure signedness and number of available bits.
			if (_tempoSum < 0) {
				// this have to be called in the same rate as in the original (I think)
				processMidiData(1);
			}
		}

		if (!(_next_tick >> FIXP_SHIFT) && !_midiData) {
			_next_tick += _tick_len;
			nextTick();
			play();
		}

		step = len;
		if (step > (_next_tick >> FIXP_SHIFT))
			step = (_next_tick >> FIXP_SHIFT);
		_cmsEmu->readBuffer(buffer, step);
		buffer += 2 * step;
		_next_tick -= step << FIXP_SHIFT;
	} while (len -= step);

	return numSamples;
}

void Player_V2CMS::playVoice() {
	if (_outputTableReady) {
		playMusicChips(_cmsChips);
		_outputTableReady = 0;
	}

	_octaveMask = 0xF0;
	Voice2 *voice =0;
	for (int i = 0; i < 8; ++i) {
		voice = &_cmsVoices[i];
		_octaveMask = ~_octaveMask;

		if (voice->chanNumber != 0xFF) {
			processChannel(voice);
			continue;
		}

		if (!voice->curVolume) {
			*(voice->amplitudeOutput) = 0;
		}

		int volume = voice->curVolume - voice->releaseRate;
		voice->curVolume = volume;

		if (volume < 0) {
			volume = voice->curVolume = 0;
		}

		*(voice->amplitudeOutput) = ((volume >> 4) | (volume & 0xF0)) & voice->channel;
		++_outputTableReady;
	}
}

void Player_V2CMS::processChannel(Voice2 *channel) {
	++_outputTableReady;
	switch (channel->nextProcessState) {
	case PROCESS_RELEASE:
		processRelease(channel);
		break;

	case PROCESS_ATTACK:
		processAttack(channel);
		break;

	case PROCESS_DECAY:
		processDecay(channel);
		break;

	case PROCESS_SUSTAIN:
		processSustain(channel);
		break;

	case PROCESS_VIBRATO:
		processVibrato(channel);
		break;

	default:
		break;
	}
}

void Player_V2CMS::processRelease(Voice2 *channel) {
	channel->curVolume -= channel->releaseRate;
	if (channel->curVolume < 0)
		channel->curVolume = 0;
	processVibrato(channel);
}

void Player_V2CMS::processAttack(Voice2 *channel) {
	channel->curVolume += channel->attackRate;
	if (channel->curVolume >= 0) {
		if (channel->curVolume <= channel->maxAmpl)
			return processVibrato(channel);
	}
	channel->curVolume = channel->maxAmpl;
	channel->nextProcessState = PROCESS_DECAY;
	processVibrato(channel);
}

void Player_V2CMS::processDecay(Voice2 *channel) {
	channel->curVolume -= channel->decayRate;
	if (channel->curVolume >= 0) {
		if (channel->curVolume > channel->sustainRate)
			return processVibrato(channel);
	}
	channel->curVolume = channel->sustainRate;
	channel->nextProcessState = PROCESS_SUSTAIN;
	processVibrato(channel);
}

void Player_V2CMS::processSustain(Voice2 *channel) {
	if (channel->unkVibratoRate) {
		int volume = (int)channel->curVolume + (int)channel->unkRate;
		if (volume & 0xFF00) {
			volume = ((~volume) >> 8) & 0xFF;
		}
		channel->curVolume = volume;
		--(channel->unkCount);
		if (!channel->unkCount) {
			channel->unkRate = ~(channel->unkRate);
			channel->unkCount = (channel->unkVibratoDepth & 0x0F) << 1;
		}
	}
	processVibrato(channel);
}

void Player_V2CMS::processVibrato(Voice2 *channel) {
	if (channel->vibratoRate) {
		uint16 temp = channel->curFreq + channel->curVibratoRate;
		channel->curOctave += (temp & 0xFF00) >> 8;
		channel->curFreq = temp & 0xFF;
		--(channel->curVibratoUnk);
		if (!channel->curVibratoUnk) {
			channel->curVibratoRate = ~(channel->curVibratoRate);
			channel->curVibratoUnk = (channel->vibratoDepth & 0x0F) << 1;
		}
	}

	byte *output = channel->amplitudeOutput;
	*output = ((channel->curVolume >> 4) | (channel->curVolume & 0xF0)) & channel->channel;
	output = channel->freqOutput;
	*output = channel->curFreq;
	output = channel->octaveOutput;
	*output = ((((channel->curOctave >> 4) | (channel->curOctave & 0x0F)) & _octaveMask) | ((~_octaveMask) & *output));
}

void Player_V2CMS::offAllChannels() {
	warning("offAllChannels STUB");
/*
	// after using this sound can not be played anymore (since it would deinit the emulator)
	static const byte cmsOffData[10*2] = {
		0x1C, 0x02,
		0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00,
		0x14, 0x3F, 0x15, 0x00, 0x16, 0x00
	};

	for (int cmsPort = 0x220, i = 0; i < 2; cmsPort += 2, ++i) {
		for (int off = 0; off < 10; ++off) {
			_cmsEmu->portWrite(cmsPort+1, cmsOffData[off*2]);
			_cmsEmu->portWrite(cmsPort, cmsOffData[off*2+1]);
		}
	}*/
}

Player_V2CMS::Voice2 *Player_V2CMS::getFreeVoice() {
	Voice2 *curVoice = 0;
	Voice2 *selected = 0;
	uint8 volume = 0xFF;

	for (int i = 0; i < 8; ++i) {
		curVoice = &_cmsVoices[i];

		if (curVoice->chanNumber == 0xFF) {
			if (!curVoice->curVolume) {
				selected = curVoice;
				break;
			}

			if (curVoice->curVolume < volume) {
				selected = curVoice;
				volume = selected->curVolume;
			}
		}
	}

	if (selected) {
		selected->chanNumber = _lastMidiCommand & 0x0F;

		uint8 channel = selected->chanNumber;
		Voice2 *oldChannel = _midiChannel[channel];
		_midiChannel[channel] = selected;
		selected->nextVoice = oldChannel;
	}

	return selected;
}

void Player_V2CMS::playNote(byte *&data) {
	byte channel = _lastMidiCommand & 0x0F;
	if (_midiChannelUse[channel]) {
		Voice2 *freeVoice = getFreeVoice();
		if (freeVoice) {
			Voice *voice = &_cmsVoicesBase[freeVoice->chanNumber];
			freeVoice->attackRate = voice->attack;
			freeVoice->decayRate = voice->decay;
			freeVoice->sustainRate = voice->sustain;
			freeVoice->releaseRate = voice->release;
			freeVoice->octaveAdd = voice->octadd;
			freeVoice->vibratoRate = freeVoice->curVibratoRate = voice->vibrato;
			freeVoice->unkVibratoRate = freeVoice->unkRate = voice->vibrato2;
			freeVoice->maxAmpl = 0xFF;

			uint8 rate = freeVoice->attackRate;
			uint8 volume = freeVoice->curVolume >> 1;

			if (rate < volume)
				rate = volume;

			rate -= freeVoice->attackRate;
			freeVoice->curVolume = rate;
			freeVoice->playingNote = *data;
			int octave = octaveTable[(*data + 3) << 1] + freeVoice->octaveAdd - 3;
			if (octave < 0)
				octave = 0;
			if (octave > 7)
				octave = 7;
			if (!octave)
				++octave;
			freeVoice->curOctave = octave;
			freeVoice->curFreq = freqTable[volume << 2];
			freeVoice->curVolume = 0;
			freeVoice->nextProcessState = PROCESS_ATTACK;
			if (_lastMidiCommand & 1)
				freeVoice->channel = 0xF0;
			else
				freeVoice->channel = 0x0F;
		}
	}
	data += 2;
}

Player_V2CMS::Voice2 *Player_V2CMS::getPlayVoice(byte param) {
	byte channelNum = _lastMidiCommand & 0x0F;
	Voice2 *channel = _midiChannel[channelNum];

	if (channel) {
		Voice2 *backUp = 0;
		while (true) {
			if (channel->playingNote == param)
				break;

			backUp = channel;
			channel = channel->nextVoice;
			if (!channel)
				return 0;
		}

		Voice2 *backUp2 = channel->nextVoice;
		{
			Voice2 *temp = backUp;
			backUp = channel;
			channel = temp;
		}
		if (channel) {
			channel->nextVoice = backUp2;
		} else {
			_midiChannel[channelNum] = backUp2;
		}
		channel = backUp;
	}

	return channel;
}

void Player_V2CMS::clearNote(byte *&data) {
	Voice2 *voice = getPlayVoice(*data);
	if (voice) {
		voice->chanNumber = 0xFF;
		voice->nextVoice = 0;
		voice->nextProcessState = PROCESS_RELEASE;
	}
	data += 2;
}

void Player_V2CMS::play() {
	_octaveMask = 0xF0;
	channel_data *chan = &(_channels[0].d);

	static byte volumeReg[4] = { 0x00, 0x00, 0x00, 0x00 };
	static byte octaveReg[4] = { 0x66, 0x66, 0x66, 0x66 };
	static byte freqReg[4] = { 0xFF, 0xFF, 0xFF, 0xFF };

	static byte freqEnable = 0x3E;
	static byte noiseEnable = 0x01;
	static byte noiseGen = 0x02;
	for (int i = 1; i <= 4; ++i) {
		if (chan->time_left) {
			uint16 freq = chan->freq;

			if (i == 4) {
				if ((freq >> 8) & 0x40) {
					noiseGen = freq & 0xFF;
				} else {
					noiseGen = 3;
					freqReg[0] = freqReg[3];
					octaveReg[0] = (octaveReg[0] & 0xF0) | ((octaveReg[1] & 0xF0) >> 4);
				}
			} else {
				if (freq == 0) {
					freq = 0xFFC0;
				}

				int cmsOct = 2;
				int freqOct = 0x8000;

				while (true) {
					if (freq >= freqOct) {
						break;
					}
					freqOct >>= 1;
					++cmsOct;
					if (cmsOct == 8) {
						--cmsOct;
						freq = 1024;
						break;
					}
				}
				byte oct = cmsOct << 4;
				oct |= cmsOct;

				oct &= _octaveMask;
				oct |= ((~_octaveMask) & octaveReg[((i & 3) >> 1)]);
				octaveReg[((i & 3) >> 1)] = oct;

				freq >>= -(cmsOct-9);
				freqReg[(i&3)] = (-(freq-511)) & 0xFF;
			}
			volumeReg[i & 3] = volumeTable[chan->volume >> 12];
		} else {
			volumeReg[i & 3] = 0;
		}
		chan = &(_channels[i].d);
		_octaveMask ^= 0xFF;
	}

	// with the high nibble of the volumeReg value
	// the right channels amplitude is set
	// with the low value the left channels amplitude
	_cmsEmu->portWrite(0x221, 0);
	_cmsEmu->portWrite(0x220, volumeReg[0]);
	_cmsEmu->portWrite(0x221, 1);
	_cmsEmu->portWrite(0x220, volumeReg[1]);
	_cmsEmu->portWrite(0x221, 2);
	_cmsEmu->portWrite(0x220, volumeReg[2]);
	_cmsEmu->portWrite(0x221, 3);
	_cmsEmu->portWrite(0x220, volumeReg[3]);
	_cmsEmu->portWrite(0x221, 8);
	_cmsEmu->portWrite(0x220, freqReg[0]);
	_cmsEmu->portWrite(0x221, 9);
	_cmsEmu->portWrite(0x220, freqReg[1]);
	_cmsEmu->portWrite(0x221, 10);
	_cmsEmu->portWrite(0x220, freqReg[2]);
	_cmsEmu->portWrite(0x221, 11);
	_cmsEmu->portWrite(0x220, freqReg[3]);
	_cmsEmu->portWrite(0x221, 0x10);
	_cmsEmu->portWrite(0x220, octaveReg[0]);
	_cmsEmu->portWrite(0x221, 0x11);
	_cmsEmu->portWrite(0x220, octaveReg[1]);
	_cmsEmu->portWrite(0x221, 0x14);
	_cmsEmu->portWrite(0x220, freqEnable);
	_cmsEmu->portWrite(0x221, 0x15);
	_cmsEmu->portWrite(0x220, noiseEnable);
	_cmsEmu->portWrite(0x221, 0x16);
	_cmsEmu->portWrite(0x220, noiseGen);
}

void Player_V2CMS::playMusicChips(const MusicChip *table) {
	int cmsPort = 0x21E;

	do {
		cmsPort += 2;
		_cmsEmu->portWrite(cmsPort+1, 0);
		_cmsEmu->portWrite(cmsPort, table->ampl[0]);
		_cmsEmu->portWrite(cmsPort+1, 1);
		_cmsEmu->portWrite(cmsPort, table->ampl[1]);
		_cmsEmu->portWrite(cmsPort+1, 2);
		_cmsEmu->portWrite(cmsPort, table->ampl[2]);
		_cmsEmu->portWrite(cmsPort+1, 3);
		_cmsEmu->portWrite(cmsPort, table->ampl[3]);
		_cmsEmu->portWrite(cmsPort+1, 8);
		_cmsEmu->portWrite(cmsPort, table->freq[0]);
		_cmsEmu->portWrite(cmsPort+1, 9);
		_cmsEmu->portWrite(cmsPort, table->freq[1]);
		_cmsEmu->portWrite(cmsPort+1, 10);
		_cmsEmu->portWrite(cmsPort, table->freq[2]);
		_cmsEmu->portWrite(cmsPort+1, 11);
		_cmsEmu->portWrite(cmsPort, table->freq[3]);
		_cmsEmu->portWrite(cmsPort+1, 0x10);
		_cmsEmu->portWrite(cmsPort, table->octave[0]);
		_cmsEmu->portWrite(cmsPort+1, 0x11);
		_cmsEmu->portWrite(cmsPort, table->octave[1]);
		_cmsEmu->portWrite(cmsPort+1, 0x14);
		_cmsEmu->portWrite(cmsPort, 0x3F);
		_cmsEmu->portWrite(cmsPort+1, 0x15);
		_cmsEmu->portWrite(cmsPort, 0x00);
		++table;
	} while ((cmsPort & 2) == 0);
}

} // End of namespace Scumm
