/* 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/>.
 *
 */

// TODO: Original Mac driver allows the interpreter to play notes. Channel
// is allocated via controller 0x4D. Find out if this is used. This would
// allow for music and sfx to be played simultaneously.

// FIXME: SQ3, LSL2 and HOYLE1 for Amiga don't seem to load any
// patches, even though patches are present. Later games do load
// patches, but include disabled patches with a 'd' appended to the
// filename, e.g. sound.010d. For SQ3, LSL2 and HOYLE1, we should
// probably disable patch loading. Maybe the original interpreter
// loads these disabled patches under some specific condition?

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

#include "common/file.h"
#include "common/memstream.h"
#include "common/mutex.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "audio/mods/paula.h"

namespace Sci {

class MidiPlayer_AmigaMac0 : public MidiPlayer {
public:
	enum {
		kVoices = 4,
		kBaseFreq = 60
	};

	MidiPlayer_AmigaMac0(SciVersion version, Audio::Mixer *mixer, Common::Mutex &mutex);
	virtual ~MidiPlayer_AmigaMac0();

	// MidiPlayer
	void close() override;
	void send(uint32 b) override;
	void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
	uint32 getBaseTempo() override { return (1000000 + kBaseFreq / 2) / kBaseFreq; }
	byte getPlayId() const override { return 0x40; }
	int getPolyphony() const override { return kVoices; }
	bool hasRhythmChannel() const override { return false; }
	void setVolume(byte volume) override;
	int getVolume() override;
	void playSwitch(bool play) override;
	void initTrack(SciSpan<const byte> &trackData) override;

protected:
	bool _playSwitch;
	uint _masterVolume;

	Audio::Mixer *_mixer;
	Audio::SoundHandle _mixerSoundHandle;
	Common::TimerManager::TimerProc _timerProc;
	void *_timerParam;
	bool _isOpen;

	void freeInstruments();
	void onTimer();

	struct Instrument {
		Instrument() :
			name(),
			loop(false),
			fixedNote(false),
			seg2Offset(0),
			seg3Offset(0),
			samples(nullptr),
			transpose(0),
			envelope() {}

		~Instrument() { delete[] samples; }

		char name[31];
		bool loop;
		bool fixedNote;
		uint32 seg2Offset;
		uint32 seg3Offset;
		const byte *samples;
		int16 transpose;

		struct Envelope {
			byte skip;
			int8 step;
			byte target;
		} envelope[4];
	};

	Common::Array<const Instrument *> _instruments;

	class Voice {
	public:
		Voice(MidiPlayer_AmigaMac0 &driver, byte id) :
			_patch(0),
			_note(-1),
			_velocity(0),
			_pitch(0),
			_instrument(nullptr),
			_loop(false),
			_envState(0),
			_envCntDown(0),
			_envCurVel(0),
			_volume(0),
			_id(id),
			_driver(driver) {}

		virtual ~Voice() {}

		virtual void noteOn(int8 note, int8 velocity) = 0;
		virtual void noteOff(int8 note) = 0;
		virtual void setPitchWheel(int16 pitch) {}

		virtual void stop() = 0;
		virtual void setEnvelopeVolume(byte volume) = 0;

		void processEnvelope();

		byte _patch;
		int8 _note;
		byte _velocity;
		uint16 _pitch;

		const Instrument *_instrument;
		bool _loop;

		byte _envState;
		byte _envCntDown;
		int8 _envCurVel;

		byte _volume;
		byte _id;

	private:
		MidiPlayer_AmigaMac0 &_driver;
	};

	Common::Array<Voice *> _voices;
	typedef Common::Array<Voice *>::const_iterator VoiceIt;

	Voice *_channels[MIDI_CHANNELS];

	Common::Mutex &_mixMutex;
	Common::Mutex _timerMutex;
};

MidiPlayer_AmigaMac0::MidiPlayer_AmigaMac0(SciVersion version, Audio::Mixer *mixer, Common::Mutex &mutex) :
	MidiPlayer(version),
	_playSwitch(true),
	_masterVolume(15),
	_mixer(mixer),
	_mixerSoundHandle(),
	_timerProc(),
	_timerParam(nullptr),
	_isOpen(false),
	_channels(),
	_mixMutex(mutex) {}

MidiPlayer_AmigaMac0::~MidiPlayer_AmigaMac0() {
	close();
}

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

	_mixer->stopHandle(_mixerSoundHandle);

	for (uint ci = 0; ci < ARRAYSIZE(_channels); ++ci)
		_channels[ci] = nullptr;

	for (VoiceIt v = _voices.begin(); v != _voices.end(); ++v)
		delete *v;
	_voices.clear();

	freeInstruments();

	_isOpen = false;
}

void MidiPlayer_AmigaMac0::setVolume(byte volume) {
	Common::StackLock lock(_mixMutex);
	_masterVolume = CLIP<byte>(volume, 0, 15);
}

int MidiPlayer_AmigaMac0::getVolume() {
	Common::StackLock lock(_mixMutex);
	return _masterVolume;
}

void MidiPlayer_AmigaMac0::playSwitch(bool play) {
	Common::StackLock lock(_mixMutex);
	_playSwitch = play;
}

void MidiPlayer_AmigaMac0::initTrack(SciSpan<const byte>& header) {
	if (!_isOpen)
		return;

	uint8 readPos = 0;
	const uint8 caps = header.getInt8At(readPos++);

	// We only implement the MIDI functionality here, samples are
	// handled by the generic sample code
	if (caps != 0)
		return;

	Common::StackLock lock(_mixMutex);

	uint vi = 0;

	for (uint i = 0; i < 15; ++i) {
		readPos++;
		const uint8 flags = header.getInt8At(readPos++);

		if ((flags & getPlayId()) && (vi < kVoices))
			_channels[i] = _voices[vi++];
		else
			_channels[i] = nullptr;
	}

	_channels[15] = nullptr;

	for (VoiceIt it = _voices.begin(); it != _voices.end(); ++it) {
		Voice *voice = *it;
		voice->stop();
		voice->_note = -1;
		voice->_envState = 0;
		voice->_pitch = 0x2000;
	}
}

void MidiPlayer_AmigaMac0::freeInstruments() {
	for (Common::Array<const Instrument *>::iterator it = _instruments.begin(); it != _instruments.end(); ++it)
		delete *it;

	_instruments.clear();
}

void MidiPlayer_AmigaMac0::onTimer() {
	_mixMutex.unlock();
	_timerMutex.lock();

	if (_timerProc)
		(*_timerProc)(_timerParam);

	_timerMutex.unlock();
	_mixMutex.lock();

	for (VoiceIt it = _voices.begin(); it != _voices.end(); ++it)
		(*it)->processEnvelope();
}

void MidiPlayer_AmigaMac0::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
	Common::StackLock lock(_timerMutex);
	_timerProc = timerProc;
	_timerParam = timerParam;
}

void MidiPlayer_AmigaMac0::send(uint32 b) {
	Common::StackLock lock(_mixMutex);

	byte command = b & 0xf0;
	byte channel = b & 0xf;
	byte op1 = (b >> 8) & 0xff;
	byte op2 = (b >> 16) & 0xff;

	Voice *voice = _channels[channel];

	if (!voice)
		return;

	switch(command) {
	case 0x80:
		voice->noteOff(op1);
		break;
	case 0x90:
		voice->noteOn(op1, op2);
		break;
	case 0xb0:
		// Not in original driver
		if (op1 == 0x7b && voice->_note != -1 && voice->_envState < 4)
			voice->noteOff(voice->_note);
		break;
	case 0xc0:
		voice->_patch = op1;
		break;
	case 0xe0:
		voice->setPitchWheel((op2 << 7) | op1);
		break;
	}
}

void MidiPlayer_AmigaMac0::Voice::processEnvelope() {
	if (_envState == 0 || _envState == 3)
		return;

	if (_envState == 6) {
		stop();
		_envState = 0;
		return;
	}

	if (_envCntDown == 0) {
		const uint envIdx = (_envState > 3 ? _envState - 2 : _envState - 1);

		_envCntDown = _instrument->envelope[envIdx].skip;
		int8 velocity = _envCurVel;

		if (velocity <= 0) {
			stop();
			_envState = 0;
			return;
		}

		if (velocity > 63)
			velocity = 63;

		if (!_driver._playSwitch)
			velocity = 0;

		setEnvelopeVolume(velocity);

		const int8 step = _instrument->envelope[envIdx].step;
		if (step < 0) {
			_envCurVel -= step;
			if (_envCurVel > _instrument->envelope[envIdx].target) {
				_envCurVel = _instrument->envelope[envIdx].target;
				++_envState;
			}
		} else {
			_envCurVel -= step;
			if (_envCurVel < _instrument->envelope[envIdx].target) {
				_envCurVel = _instrument->envelope[envIdx].target;
				++_envState;
			}
		}
	}

	--_envCntDown;
}

class MidiPlayer_Mac0 : public Mixer_Mac<MidiPlayer_Mac0>, public MidiPlayer_AmigaMac0 {
public:
	MidiPlayer_Mac0(SciVersion version, Audio::Mixer *mixer, Mode mode);

	// MidiPlayer
	int open(ResourceManager *resMan) override;
	void setVolume(byte volume) override;

	// MidiDriver
	void close() override;

	// Mixer_Mac
	static int8 applyChannelVolume(byte volume, byte sample);
	void interrupt() { onTimer(); }
	void onChannelFinished(uint channel);

private:
	enum {
		kStepTableSize = 84
	};

	template <Mode mode>
	void generateSamples(int16 *buf, int len);

	struct MacInstrument : public Instrument {
		MacInstrument() :
			Instrument(),
			endOffset(0) {}

		uint32 endOffset;
	};

	class MacVoice : public Voice {
	public:
		MacVoice(MidiPlayer_Mac0 &driver, byte id) :
			Voice(driver, id),
			_macDriver(driver) {}

	private:
		void noteOn(int8 note, int8 velocity) override;
		void noteOff(int8 note) override;

		void stop() override;
		void setEnvelopeVolume(byte volume) override;

		void calcVoiceStep();

		MidiPlayer_Mac0 &_macDriver;
	};

	bool loadInstruments(Common::SeekableReadStream &patch);

	ufrac_t _stepTable[kStepTableSize];
};

MidiPlayer_Mac0::MidiPlayer_Mac0(SciVersion version, Audio::Mixer *mixer, Mixer_Mac<MidiPlayer_Mac0>::Mode mode) :
	Mixer_Mac<MidiPlayer_Mac0>(mode),
	MidiPlayer_AmigaMac0(version, mixer, _mutex) {

	for (uint i = 0; i < kStepTableSize; ++i)
		_stepTable[i] = round(0x2000 * pow(2.0, i / 12.0));
}

int MidiPlayer_Mac0::open(ResourceManager *resMan) {
	if (_isOpen)
		return MidiDriver::MERR_ALREADY_OPEN;

	Resource *patch = g_sci->getResMan()->findResource(ResourceId(kResourceTypePatch, 200), false);
	if (!patch) {
		warning("MidiPlayer_Mac0: Failed to open patch 200");
		return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
	}

	Common::MemoryReadStream stream(patch->toStream());
	if (!loadInstruments(stream)) {
		freeInstruments();
		return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
	}

	for (byte vi = 0; vi < kVoices; ++vi)
		_voices.push_back(new MacVoice(*this, vi));

	startMixer();
	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);

	_isOpen = true;

	return 0;
}

void MidiPlayer_Mac0::setVolume(byte volume) {
	MidiPlayer_AmigaMac0::setVolume(volume);
	setMixerVolume(volume / 2 + 1);
}

void MidiPlayer_Mac0::close() {
	MidiPlayer_AmigaMac0::close();
	stopMixer();
}

int8 MidiPlayer_Mac0::applyChannelVolume(byte volume, byte sample) {
	int8 signedSample = sample - 0x80;

	if (volume == 0)
		return 0;

	if (volume == 63)
		return signedSample;

	if (signedSample >= 0)
		return (signedSample * volume + 32) / 64;
	else
		return ~((~signedSample * volume + 32) / 64);
}

void MidiPlayer_Mac0::onChannelFinished(uint channel) {
	_voices[channel]->_envState = 0;
}

void MidiPlayer_Mac0::MacVoice::stop() {
	_macDriver.resetChannel(_id);
}

void MidiPlayer_Mac0::MacVoice::calcVoiceStep() {
	int8 note = _note;

	if (_instrument->fixedNote)
		note = 72;

	int16 index = note + _instrument->transpose;

	index -= 24;

	while (index < 0)
		index += 12;

	while (index >= kStepTableSize)
		index -= 12;

	_macDriver.setChannelStep(_id, _macDriver._stepTable[index]);
}

void MidiPlayer_Mac0::MacVoice::setEnvelopeVolume(byte volume) {
	if (_macDriver._masterVolume == 0 || !_macDriver._playSwitch)
		volume = 0;
	_macDriver.setChannelVolume(_id, volume * _volume >> 6);
}

void MidiPlayer_Mac0::MacVoice::noteOn(int8 note, int8 velocity) {
	if (velocity == 0) {
		noteOff(note);
		return;
	}

	stop();
	_envState = 0;

	if (!_macDriver._instruments[_patch]) // Not in original driver
		return;

	_instrument = _macDriver._instruments[_patch];

	_velocity = velocity;
	_volume = velocity >> 1;
	_envCurVel = 64;
	_envCntDown = 0;
	_loop = _instrument->loop;
	_note = note;

	calcVoiceStep();

	const MacInstrument *ins = static_cast<const MacInstrument *>(_instrument);

	if (_loop) {
		_envState = 1;
		_macDriver.setChannelData(_id, ins->samples, 0, ins->seg3Offset, ins->seg3Offset - ins->seg2Offset);
	} else {
		_macDriver.setChannelData(_id, ins->samples, 0, ins->endOffset);
	}

	setEnvelopeVolume(63);
}

void MidiPlayer_Mac0::MacVoice::noteOff(int8 note) {
	if (_note == note) {
		if (_envState != 0) {
			_envState = 4;
			_envCntDown = 0;
		}
		// Original driver doesn't reset note anywhere that I could find,
		// but this seems like a good place to do that
		_note = -1;
	}
}

bool MidiPlayer_Mac0::loadInstruments(Common::SeekableReadStream &patch) {
	char name[33];

	if (patch.read(name, 8) < 8 || strncmp(name, "X1iUo123", 8) != 0) {
		warning("MidiPlayer_Mac0: Incorrect ID string in patch bank");
		return false;
	}

	if (patch.read(name, 32) < 32) {
		warning("MidiPlayer_Mac0: Error reading patch bank");
		return false;
	}
	name[32] = 0;

	debugC(kDebugLevelSound, "Bank: '%s'", name);

	_instruments.resize(128);

	for (byte i = 0; i < 128; i++) {
		patch.seek(40 + i * 4);
		uint32 offset = patch.readUint32BE();

		if (offset == 0) {
			_instruments[i] = 0;
			continue;
		}

		patch.seek(offset);

		MacInstrument *instrument = new MacInstrument();
		_instruments[i] = instrument;

		patch.readUint16BE(); // index

		const uint16 flags = patch.readUint16BE();
		instrument->loop = flags & 1;
		instrument->fixedNote = !(flags & 2);

		instrument->seg2Offset = patch.readUint32BE();
		instrument->seg3Offset = patch.readUint32BE();
		instrument->endOffset = patch.readUint32BE();

		instrument->transpose = patch.readUint16BE();

		for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
			instrument->envelope[stage].skip = patch.readByte();

		for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
			instrument->envelope[stage].step = patch.readByte();

		// In the original, it uses the stage 0 step as the stage 3 target,
		// but we (most likely) don't have to replicate this bug.
		for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
			instrument->envelope[stage].target = patch.readByte();

		patch.read(instrument->name, 30);
		instrument->name[30] = 0;

		debugC(kDebugLevelSound, "\tInstrument[%d]: '%s'", i, instrument->name);
		debugC(kDebugLevelSound, "\t\tSegment offsets: %d, %d, %d", instrument->seg2Offset, instrument->seg3Offset, instrument->endOffset);
		debugC(kDebugLevelSound, "\t\tTranspose = %d, Fixed note = %d, Loop = %d", instrument->transpose, instrument->fixedNote, instrument->loop);
		debugC(kDebugLevelSound, "\t\tEnvelope:");
		for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
			debugC(kDebugLevelSound, "\t\t\tStage %d: skip %d, step %d, target %d", stage, instrument->envelope[stage].skip, instrument->envelope[stage].step, instrument->envelope[stage].target);

		uint32 sampleSize = (instrument->loop ? instrument->seg3Offset : instrument->endOffset) + 1111;
		byte *samples = new byte[sampleSize];
		patch.read(samples, sampleSize);
		instrument->samples = samples;
	}

	return true;
}

class MidiPlayer_Amiga0 : public Audio::Paula, public MidiPlayer_AmigaMac0 {
public:
	MidiPlayer_Amiga0(SciVersion version, Audio::Mixer *mixer);

	// MidiPlayer
	int open(ResourceManager *resMan) override;

	// MidiDriver
	void close() override;

	// Audio::Paula
	void interrupt() override { onTimer(); }

private:
	struct AmigaInstrument : public Instrument {
		AmigaInstrument() :
			Instrument(),
			seg1Size(0),
			seg2Size(0),
			seg3Size(0) {}

		int16 seg1Size;
		int16 seg2Size;
		int16 seg3Size;
	};

	class AmigaVoice : public Voice {
	public:
		AmigaVoice(MidiPlayer_Amiga0 &driver, uint id) :
			Voice(driver, id),
			_amigaDriver(driver) {}

	private:
		void noteOn(int8 note, int8 velocity) override;
		void noteOff(int8 note) override;
		void setPitchWheel(int16 pitch) override;

		void stop() override;
		void setEnvelopeVolume(byte volume) override;

		void calcVoiceStep();

		MidiPlayer_Amiga0 &_amigaDriver;
	};

	uint _defaultInstrument;
	bool _isEarlyDriver;

	bool loadInstruments(Common::SeekableReadStream &patch);

	uint16 _periodTable[333];
};

MidiPlayer_Amiga0::MidiPlayer_Amiga0(SciVersion version, Audio::Mixer *mixer) :
	Audio::Paula(true, mixer->getOutputRate(), mixer->getOutputRate() / kBaseFreq),
	MidiPlayer_AmigaMac0(version, mixer, _mutex),
	_defaultInstrument(0),
	_isEarlyDriver(false) {

	// These values are close, but not identical to the original
	for (int i = 0; i < ARRAYSIZE(_periodTable); ++i)
		_periodTable[i] = 3579545 / 20000.0 / pow(2.0, (i - 308) / 48.0);
}

void MidiPlayer_Amiga0::AmigaVoice::setEnvelopeVolume(byte volume) {
	// Early games ignore note velocity for envelope-enabled notes
	if (_amigaDriver._isEarlyDriver)
		_amigaDriver.setChannelVolume(_id, volume * _amigaDriver._masterVolume >> 4);
	else
		_amigaDriver.setChannelVolume(_id, (volume * _amigaDriver._masterVolume >> 4) * _volume >> 6);
}

int MidiPlayer_Amiga0::open(ResourceManager *resMan) {
	if (_isOpen)
		return MidiDriver::MERR_ALREADY_OPEN;

	_isEarlyDriver = g_sci->getGameId() == GID_LSL2 || g_sci->getGameId() == GID_SQ3;

	Common::File file;

	if (!file.open("bank.001")) {
		warning("MidiPlayer_Amiga0: Failed to open bank.001");
		return false;
	}

	if (!loadInstruments(file)) {
		freeInstruments();
		return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
	}

	for (byte vi = 0; vi < NUM_VOICES; ++vi)
		_voices.push_back(new AmigaVoice(*this, vi));

	startPaula();
	// Enable reverse stereo to counteract Audio::Paula's reverse stereo
	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO, false, true);
	_isOpen = true;

	return 0;
}

void MidiPlayer_Amiga0::close() {
	MidiPlayer_AmigaMac0::close();
	clearVoices();
	stopPaula();
}

void MidiPlayer_Amiga0::AmigaVoice::stop() {
	_amigaDriver.clearVoice(_id);
}

void MidiPlayer_Amiga0::AmigaVoice::calcVoiceStep() {
	int8 note = _note;

	if (_instrument->fixedNote)
		note = 101;

	int16 index = (note + _instrument->transpose) * 4;

	if (_pitch >= 0x2000)
		index += (_pitch - 0x2000) / 171;
	else
		index -= (0x2000 - _pitch) / 171;

	// For very high notes, the original driver reads out of bounds
	// (see e.g. SQ3 intro sequence). We compute the period for
	// these notes. The original hardware would not be able to
	// handle these very low periods, but Audio::Paula doesn't
	// seem to mind.

	while (index < 96)
		index += 48;

	index -= 96;

	while (index >= ARRAYSIZE(_amigaDriver._periodTable))
		index -= 48;

	_amigaDriver.setChannelPeriod(_id, _amigaDriver._periodTable[index]);
}

void MidiPlayer_Amiga0::AmigaVoice::noteOn(int8 note, int8 velocity) {
	if (velocity == 0) {
		noteOff(note);
		return;
	}

	_instrument = _amigaDriver._instruments[_patch];

	// Default to the first instrument in the bank
	if (!_instrument)
		_instrument = _amigaDriver._instruments[_amigaDriver._defaultInstrument];

	_velocity = velocity;
	_volume = velocity >> 1;
	_loop = _instrument->loop;
	_note = note;

	stop();
	_envState = 0;

	calcVoiceStep();

	const AmigaInstrument *ins = static_cast<const AmigaInstrument *>(_instrument);

	const int8 *seg1 = (const int8 *)ins->samples;
	const int8 *seg2 = seg1;
	int16 seg1Size = ins->seg1Size;
	seg2 += ins->seg2Offset & 0xfffe;
	int16 seg2Size = ins->seg2Size;

	if (!_loop) {
		seg1Size = seg1Size + seg2Size + ins->seg3Size;
		seg2 = nullptr;
		seg2Size = 0;
	}

	if (ins->envelope[0].skip != 0 && _loop) {
		_envCurVel = _volume;
		_envCntDown = 0;
		_envState = 1;
	}

	_amigaDriver.setChannelData(_id, seg1, seg2, seg1Size * 2, seg2Size * 2);
	if (_amigaDriver._playSwitch)
		_amigaDriver.setChannelVolume(_id, _amigaDriver._masterVolume * _volume >> 4);
}

void MidiPlayer_Amiga0::AmigaVoice::noteOff(int8 note) {
	if (_note == note) {
		if (_envState != 0) {
			_envCurVel = _instrument->envelope[1].target;
			_envState = 4;
		}
		// Original driver doesn't reset note anywhere that I could find,
		// but this seems like a good place to do that
		_note = -1;
	}
}

void MidiPlayer_Amiga0::AmigaVoice::setPitchWheel(int16 pitch) {
	if (_amigaDriver._isEarlyDriver)
		return;

	_pitch = pitch;

	if (_note != -1)
		calcVoiceStep();
}

bool MidiPlayer_Amiga0::loadInstruments(Common::SeekableReadStream &patch) {
	char name[31];

	if (patch.read(name, 8) < 8 || strncmp(name, "X0iUo123", 8) != 0) {
		warning("MidiPlayer_Amiga0: Incorrect ID string in patch bank");
		return false;
	}

	if (patch.read(name, 30) < 30) {
		warning("MidiPlayer_Amiga0: Error reading patch bank");
		return false;
	}
	name[30] = 0;

	debugC(kDebugLevelSound, "Bank: '%s'", name);

	_instruments.resize(128);

	const uint16 instrumentCount = patch.readUint16BE();

	if (instrumentCount == 0) {
		warning("MidiPlayer_Amiga0: No instruments found in patch bank");
		return false;
	}

	for (uint i = 0; i < instrumentCount; ++i) {
		AmigaInstrument *instrument = new AmigaInstrument();

		const uint16 patchIdx = patch.readUint16BE();
		_instruments[patchIdx] = instrument;

		if (i == 0)
			_defaultInstrument = patchIdx;

		patch.read(instrument->name, 30);
		instrument->name[30] = 0;
		const uint16 flags = patch.readUint16BE();
		instrument->loop = flags & 1;
		instrument->fixedNote = !(flags & 2);
		instrument->transpose = patch.readSByte();
		instrument->seg1Size = patch.readSint16BE();
		instrument->seg2Offset = patch.readUint32BE();
		instrument->seg2Size = patch.readSint16BE();
		instrument->seg3Offset = patch.readUint32BE();
		instrument->seg3Size = patch.readSint16BE();

		// There's some envelope-related bugs here in the original, these were not replicated
		for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
			instrument->envelope[stage].skip = patch.readByte();

		for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
			instrument->envelope[stage].step = patch.readByte();

		for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
			instrument->envelope[stage].target = patch.readByte();

		int32 sampleSize = instrument->seg1Size + instrument->seg2Size + instrument->seg3Size;
		sampleSize <<= 1;
		byte *samples = new byte[sampleSize];
		patch.read(samples, sampleSize);
		instrument->samples = samples;

		if (patch.eos() || patch.err()) {
			warning("MidiPlayer_Amiga0: Error reading patch bank");
			return false;
		}

		debugC(kDebugLevelSound, "\tInstrument[%d]: '%s'", patchIdx, instrument->name);
		debugC(kDebugLevelSound, "\t\tSegment 1: offset 0, size %d", instrument->seg1Size * 2);
		debugC(kDebugLevelSound, "\t\tSegment 2: offset %d, size %d", instrument->seg2Offset, instrument->seg2Size * 2);
		debugC(kDebugLevelSound, "\t\tSegment 3: offset %d, size %d", instrument->seg3Offset, instrument->seg3Size * 2);
		debugC(kDebugLevelSound, "\t\tTranspose = %d, Fixed note = %d, Loop = %d", instrument->transpose, instrument->fixedNote, instrument->loop);
		debugC(kDebugLevelSound, "\t\tEnvelope:");
		for (uint stage = 0; stage < ARRAYSIZE(instrument->envelope); ++stage)
			debugC(kDebugLevelSound, "\t\t\tStage %d: skip %d, step %d, target %d", stage, instrument->envelope[stage].skip, instrument->envelope[stage].step, instrument->envelope[stage].target);
	}

	return true;
}

MidiPlayer *MidiPlayer_AmigaMac0_create(SciVersion version, Common::Platform platform) {
	if (platform == Common::kPlatformMacintosh)
		return new MidiPlayer_Mac0(version, g_system->getMixer(), MidiPlayer_Mac0::kModeHq);
	else
		return new MidiPlayer_Amiga0(version, g_system->getMixer());
}

} // End of namespace Sci
