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

// Only compiled if Kyra is built-in or we're building for dynamic modules
#if !defined(AUDIO_MODS_MAXTRAX_H) && (defined(ENABLE_KYRA) || defined(DYNAMIC_MODULES))
#define AUDIO_MODS_MAXTRAX_H

// #define MAXTRAX_HAS_MODULATION
// #define MAXTRAX_HAS_MICROTONAL

#include "audio/mods/paula.h"

namespace Audio {

class MaxTrax : public Paula {
public:
	MaxTrax(int rate, bool stereo, uint16 vBlankFreq = 50, uint16 maxScores = 128);
	virtual ~MaxTrax();

	bool load(Common::SeekableReadStream &musicData, bool loadScores = true, bool loadSamples = true);
	bool playSong(int songIndex, bool loop = false);
	void advanceSong(int advance = 1);
	int playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide);
	void setVolume(const byte volume) { Common::StackLock lock(_mutex); _playerCtx.volume = volume; }
	void setTempo(const uint16 tempo);
	void stopMusic();
	/**
	 * Set a callback function for sync-events.
	 * @param callback Callback function, will be called synchronously, so DONT modify the player
	 *		directly in response
	 */
	void setSignalCallback(void (*callback) (int));

protected:
	void interrupt();

private:
	enum { kNumPatches = 64, kNumVoices = 4, kNumChannels = 16, kNumExtraChannels = 1 };
	enum { kPriorityScore, kPriorityNote, kPrioritySound };

#ifdef MAXTRAX_HAS_MICROTONAL
	int16	_microtonal[128];
#endif

	struct Event {
		uint16	startTime;
		uint16	stopTime;
		byte	command;
		byte	parameter;
	};

	const struct Score {
		const Event	*events;
		uint32	numEvents;
	} *_scores;

	int _numScores;

	struct {
		uint32	sineValue;
		uint16	vBlankFreq;
		int32	ticks;
		int32	tickUnit;
		uint16	frameUnit;

		uint16	maxScoreNum;
		uint16	tempo;
		uint16	tempoInitial;
		uint16	tempoStart;
		int16	tempoDelta;
		int32	tempoTime;
		int32	tempoTicks;

		byte	volume;

		bool	filterOn;
		bool	handleVolume;
		bool	musicLoop;

		int		scoreIndex;
		const Event	*nextEvent;
		int32	nextEventTime;

		void (*syncCallBack) (int);
		const Event	*repeatPoint[4];
		byte	repeatCount[4];
	} _playerCtx;

	struct Envelope {
		uint16	duration;
		uint16	volume;
	};

	struct Patch {
		const Envelope *attackPtr;
		//Envelope *releasePtr;
		uint16	attackLen;
		uint16	releaseLen;

		int16	tune;
		uint16	volume;

		// this was the SampleData struct in the assembler source
		const int8	*samplePtr;
		uint32	sampleTotalLen;
		uint32	sampleAttackLen;
		uint16	sampleOctaves;
	} _patch[kNumPatches];

	struct ChannelContext {
		const Patch	*patch;
		uint16	regParamNumber;

		uint16	modulation;
		uint16	modulationTime;

		int16	microtonal;

		uint16	portamentoTime;

		int16	pitchBend;
		int16	pitchReal;
		int8	pitchBendRange;

		uint8	volume;
//		uint8	voicesActive;

		enum {
			kFlagRightChannel = 1 << 0,
			kFlagPortamento = 1 << 1,
			kFlagDamper = 1 << 2,
			kFlagMono = 1 << 3,
			kFlagMicrotonal = 1 << 4,
			kFlagModVolume = 1 << 5
		};
		byte	flags;
		bool	isAltered;

		uint8	lastNote;
//		uint8	program;

	} _channelCtx[kNumChannels + kNumExtraChannels];

	struct VoiceContext {
		ChannelContext *channel;
		const Patch	*patch;
		const Envelope *envelope;
//		uint32	uinqueId;
		int32	preCalcNote;
		uint32	ticksLeft;
		int32	portaTicks;
		int32	incrVolume;
//		int32	periodOffset;
		uint16	envelopeLeft;
		uint16	noteVolume;
		uint16	baseVolume;
		uint16	lastPeriod;
		byte	baseNote;
		byte	endNote;
		byte	octave;
//		byte	number;
//		byte	link;
		enum {
			kStatusFree,
			kStatusHalt,
			kStatusDecay,
			kStatusRelease,
			kStatusSustain,
			kStatusAttack,
			kStatusStart
		};
		uint8	isBlocked;
		uint8	priority;
		byte	status;
		byte	lastVolume;
		byte	tieBreak;
		bool	hasDamper;
		bool	hasPortamento;
		byte	dmaOff;

		int32	stopEventTime;
	} _voiceCtx[kNumVoices];

	void controlCh(ChannelContext &channel, byte command, byte data);
	void freePatches();
	void freeScores();
	void resetChannel(ChannelContext &chan, bool rightChannel);
	void resetPlayer();

	int8 pickvoice(uint pick, int16 pri);
	uint16 calcNote(const VoiceContext &voice);
	int8 noteOn(ChannelContext &channel, byte note, uint16 volume, uint16 pri);
	void killVoice(byte num);

	static void outPutEvent(const Event &ev, int num = -1);
	static void outPutScore(const Score &sc, int num = -1);
};
} // End of namespace Audio

#endif // !defined(AUDIO_MODS_MAXTRAX_H)
