/* 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 AUDIO_MIDIDRV_MS_H
#define AUDIO_MIDIDRV_MS_H

#include "common/mutex.h"

#include "audio/mididrv.h"

/**
 * Abstract base class for MIDI drivers supporting multiple simultaneous
 * sources of MIDI data.
 *
 * These drivers support the following features:
 * 
 * - Multiple MIDI sources
 *   If the game plays multiple streams of MIDI data at the same time, each
 *   stream can be marked with a source number. When a source has finished
 *   playing, it must be deinitialized to release any resources allocated to
 *   it. This is done automatically when an End Of Track MIDI meta event is
 *   received, or manually by calling deinitSource.
 *   Using source numbers enables the following features:
 *	 - Music/SFX volume
 *	   Using setSourceType a MIDI source can be designated as music or sound
 *	   effects. The driver will then apply the appropriate user volume setting
 *	   to the MIDI channel volume. This setting sticks after deinitializing a
 *	   source, so if you use the same source numbers for the same types of MIDI
 *	   data, you don't need to set the source type repeatedly. The default setup
 *	   is music for source 0 and SFX for sources 1 and higher.
 *	 - Source volume
 *	   If the game changes the volume of the MIDI playback, you can use
 *	   setSourceVolume to set the volume level for a source. The driver will
 *	   then adjust the current MIDI channel volume and any received MIDI volume
 *	   controller messages. Use setSourceNeutralVolume to set the neutral volume
 *	   for a source (MIDI volume is not changed when source volume is at this
 *	   level; if it is lower or higher, MIDI volume is reduced or increased).
 *	 - Volume fading
 *	   If the game needs to gradually change the volume of the MIDI playback
 *	   (typically for a fade-out), you can use the startFade function. You can
 *	   check the status of the fade using isFading, and abort a fade using
 *	   abortFade. An active fade is automatically aborted when the fading source
 *	   is deinitialized.
 *	   The fading functionality uses the source volume, so you should not set
 *	   this while a fade is active. After the fade the source volume will remain
 *	   at the target level, so if you perform f.e. a fade-out, the source volume
 *	   will remain at 0. If you want to start playback again using this source,
 *	   use setSourceVolume to set the correct playback volume.
 *	   Note that when you stop MIDI playback, notes will not be immediately
 *	   silent but will gradually die out ("release"). So if you fade out a
 *	   source, stop playback, and immediately reset the source volume, the
 *	   note release will be audible. It is recommended to wait about 0.5s
 *	   before resetting the source volume.
 * 
 * - User volume settings
 *   The driver can scale the MIDI channel volume using the user specified
 *   volume settings. Just call syncSoundSettings when the user has changed the
 *   volume settings. Set the USER_VOLUME_SCALING property to true to enable
 *   this functionality.
 *
 * A driver extending this class must implement the following functions:
 * - send(source, data): process a MIDI event for a specific source.
 * - stopAllNotes(source, channel): stop all active notes for a source and/or
 *   MIDI channel (called when a source is deinitialized).
 * - applySourceVolume(source): set the current source volume on active notes
 *   and/or MIDI channels.
 */
class MidiDriver_Multisource : public MidiDriver {
public:
	/**
	 * The maximum number of sources supported. This can be increased if
	 * necessary, but this will consume more memory and processing time.
	 */
	static const uint8 MAXIMUM_SOURCES = 10;
	/**
	 * The default neutral volume level for a source. If the source volume is
	 * set to this level, the volume levels in the MIDI data are used directly;
	 * if source volume is lower or higher, output volume is decreased or
	 * increased, respectively. Use @see setSourceNeutralVolume to change the
	 * default neutral volume.
	 */
	static const uint16 DEFAULT_SOURCE_NEUTRAL_VOLUME = 255;

protected:
	// Timeout between updates of the channel volume for fades (25ms)
	static const uint16 FADING_DELAY = 25 * 1000;

public:
	/**
	 * The type of audio produced by a MIDI source (music or sound effects).
	 */
	enum SourceType {
		/**
		 * Source type not specified (generally treated as music).
		 */
		SOURCE_TYPE_UNDEFINED,
		/**
		 * Source produces music.
		 */
		SOURCE_TYPE_MUSIC,
		/**
		 * Source produces sound effects.
		 */
		SOURCE_TYPE_SFX
	};

	/**
	 * Specifies what happens to the volume when a fade is aborted.
	 */
	enum FadeAbortType {
		/**
		 * The volume is set to the fade's end volume level.
		 */
		FADE_ABORT_TYPE_END_VOLUME,
		/**
		 * The volume remains at the current level.
		 */
		FADE_ABORT_TYPE_CURRENT_VOLUME,
		/**
		 * The volume is reset to the fade's start volume level.
		 */
		FADE_ABORT_TYPE_START_VOLUME
	};

	/**
	 * The controllers and parameters for which a default value can be set
	 * using setControllerDefault.
	 */
	enum ControllerDefaultType {
		CONTROLLER_DEFAULT_PROGRAM,
		CONTROLLER_DEFAULT_INSTRUMENT_BANK,
		CONTROLLER_DEFAULT_DRUMKIT,
		CONTROLLER_DEFAULT_CHANNEL_PRESSURE,
		CONTROLLER_DEFAULT_PITCH_BEND,
		CONTROLLER_DEFAULT_MODULATION,
		CONTROLLER_DEFAULT_VOLUME,
		CONTROLLER_DEFAULT_PANNING,
		CONTROLLER_DEFAULT_EXPRESSION,
		CONTROLLER_DEFAULT_SUSTAIN,
		CONTROLLER_DEFAULT_RPN,
		CONTROLLER_DEFAULT_PITCH_BEND_SENSITIVITY
	};

protected:
	// This stores data about a specific source of MIDI data.
	struct MidiSource {
		// Whether this source sends music or SFX MIDI data.
		SourceType type;
		// The source volume (relative volume for this source as defined by the
		// game). Default is the default neutral value (255).
		uint16 volume;
		// The source volume level at which no scaling is performed (volume as
		// defined in the MIDI data is used directly). Volume values below this
		// decrease volume, values above increase volume (up to the maximum MIDI
		// channel volume). Set this to match the volume values used by the game
		// engine to avoid having to convert them. Default value is 255; minimum
		// value is 1.
		uint16 neutralVolume;
		// The volume level at which the fade started.
		uint16 fadeStartVolume;
		// The target volume level for the fade.
		uint16 fadeEndVolume;
		// How much time (microseconds) has passed since the start of the fade.
		int32 fadePassedTime;
		// The total duration of the fade (microseconds).
		int32 fadeDuration;

		MidiSource();
	};

	// Stores the default values that should be set for each controller.
	// -1 means no explicit default should be set for that controller.
	struct ControllerDefaults {
		int8 program[16];
		int8 instrumentBank;
		int8 drumkit;

		int8 channelPressure;
		int16 pitchBend;

		int8 modulation;
		int8 volume;
		int8 panning;
		int8 expression;
		int8 sustain;
		int16 rpn;

		int8 pitchBendSensitivity;

		ControllerDefaults();
	};

public:
	MidiDriver_Multisource();

	// MidiDriver functions
	using MidiDriver_BASE::send;
	void send(uint32 b) override;
	void send(int8 source, uint32 b) override = 0;

	uint32 property(int prop, uint32 param) override;

	/**
	 * Deinitializes a source. This will abort active fades and stop any active
	 * notes.
	 *
	 * @param source The source to deinitialize.
	 */
	virtual void deinitSource(uint8 source);
	/**
	 * Sets the type for all sources (music or SFX).
	 *
	 * @param type The new type for all sources.
	 */
	void setSourceType(SourceType type);
	/**
	 * Sets the type for a specific sources (music or SFX).
	 *
	 * @param source The source for which the type should be set.
	 * @param type The new type for the specified source.
	 */
	void setSourceType(uint8 source, SourceType type);
	/**
	 * Sets the source volume for all sources.
	 *
	 * @param volume The new source volume for all sources.
	 */
	void setSourceVolume(uint16 volume);
	/**
	 * Sets the volume for this source. The volume values in the MIDI data sent
	 * by this source will be scaled by the source volume.
	 *
	 * @param source The source for which the source volume should be set.
	 * @param volume The new source volume for the specified source.
	 */
	void setSourceVolume(uint8 source, uint16 volume);
	/**
	 * Resets the source volume for all sources to each source's neutral volume.
	 */
	void resetSourceVolume();
	/**
	 * Resets the volume for this source to its neutral volume.
	 */
	void resetSourceVolume(uint8 source);
	/**
	 * Sets the neutral volume for all sources. See the source-specific
	 * setSourceNeutralVolume function for details.
	 * 
	 * @param volume The new neutral volume for all sources.
	 */
	void setSourceNeutralVolume(uint16 volume);
	/**
	 * Sets the neutral volume for this source. If the source volume is at this
	 * level, the volume values in the MIDI data sent by this source will not
	 * be changed. At source volumes below or above this value, the MIDI volume
	 * values will be decreased or increased accordingly.
	 *
	 * @param source The source for which the neutral volume should be set.
	 * @param volume The new neutral volume for the specified source.
	 */
	void setSourceNeutralVolume(uint8 source, uint16 volume);

	/**
	 * Starts a fade for all sources.
	 * See the source-specific startFade function for more information.
	 * 
	 * @param duration The fade duration in milliseconds
	 * @param targetVolume The volume at the end of the fade
	 */
	void startFade(uint16 duration, uint16 targetVolume);
	/**
	 * Starts a fade for a source. This will linearly increase or decrease the
	 * volume of the MIDI channels used by the source to the specified target
	 * value over the specified length of time.
	 *
	 * @param source The source to fade
	 * @param duration The fade duration in milliseconds
	 * @param targetVolume The volume at the end of the fade
	 */
	void startFade(uint8 source, uint16 duration, uint16 targetVolume);
	/**
	 * Aborts any active fades for all sources.
	 * See the source-specific abortFade function for more information.
	 * 
	 * @param abortType How to set the volume when aborting the fade (default:
	 * set to the target fade volume).
	 */
	void abortFade(FadeAbortType abortType = FADE_ABORT_TYPE_END_VOLUME);
	/**
	 * Aborts an active fade for a source. Depending on the abort type, the
	 * volume will remain at the current value or be set to the start or end
	 * volume. If there is no active fade for the specified source, this
	 * function does nothing.
	 *
	 * @param source The source that should have its fade aborted
	 * @param abortType How to set the volume when aborting the fade (default:
	 * set to the target fade volume).
	 */
	void abortFade(uint8 source, FadeAbortType abortType = FADE_ABORT_TYPE_END_VOLUME);
	/**
	 * Check if any source has an active fade.
	 *
	 * @return True if any source has an active fade.
	 */
	bool isFading();
	/**
	 * Check if the specified source has an active fade.
	 *
	 * @return True if the specified source has an active fade.
	 */
	bool isFading(uint8 source);

	/**
	 * Specify a controller which should be reset to its General MIDI default
	 * value when a new track is started. See the overload for more details.
	 * 
	 * @param type The controller which should be reset.
	 */
	void setControllerDefault(ControllerDefaultType type);
	/**
	 * Specify a default value for a controller which should be set when a new
	 * track is started. Use this if a game uses a MIDI controller, but does
	 * not consistently set it to a value at the start of every track, causing
	 * incorrect playback. Do not use this if a game depends on controller
	 * values carrying over to the next track for correct playback.
	 *
	 * This functionality will not work if the fallback MIDI source -1 is used.
	 * It is also necessary to call deinitSource whenever playback of a track
	 * is stopped, as this sets up the contoller reset.
	 *
	 * Use the setControllerDefault(ControllerDefaultType) overload if the
	 * General MIDI default value for the controller should be used.
	 * 
	 * @param type The controller which should be reset.
	 * @param value The default value which should be set.
	 */
	void setControllerDefault(ControllerDefaultType type, int16 value);
	/**
	 * Specify a default value for a controller which should be set when a new
	 * track is started. This expects an array of values, each of which will
	 * be used as the default for the corresponding MIDI channel.
	 *
	 * This is currently only supported for program.
	 *
	 * See setControllerDefault for more details.
	 *
	 * @param type The controller which should be reset.
	 * @param values The default values which should be set. Must be a 16 value
	 * array.
	 */
	void setControllerDefaults(ControllerDefaultType type, int16 *values);
	/**
	 * Clears a previously set default value for the specified controller.
	 * 
	 * @param type The controller for which the default value should be cleared.
	 */
	void clearControllerDefault(ControllerDefaultType type);

	/**
	 * Sets an instrument map for arbitrarily remapping instruments in the MIDI
	 * data. The map should consist of 128 bytes, with the index representing
	 * the instrument number in the MIDI data, and the value being the
	 * instrument which should be substituted.
	 * This instrument mapping is applied before MT-32 to GM or GM to MT-32
	 * instrument mapping.
	 * Call this method with nullptr as parameter to clear a previously set
	 * instrument remapping.
	 * 
	 * @param instrumentRemapping The instrument map that should be used for
	 * remapping, or nullptr to disable remapping.
	 */
	void setInstrumentRemapping(const byte *instrumentRemapping);

	/**
	 * Applies the user volume settings to the MIDI driver. MIDI channel
	 * volumes will be scaled using the user volume.
	 * This function must be called by the engine when the user has changed the
	 * volume settings.
	 */
	void syncSoundSettings();

	using MidiDriver::stopAllNotes;
	/**
	 * Stops all active notes (including sustained notes) for the specified
	 * source and MIDI channel. For both source and channel the value 0xFF can
	 * be specified, in which case active notes will be stopped for all sources
	 * and/or MIDI channels.
	 * 
	 * @param source The source for which all notes should be stopped, or all
	 * sources if 0xFF is specified.
	 * @param channel The MIDI channel on which all notes should be stopped, or
	 * all channels if 0xFF is specified.
	 */
	virtual void stopAllNotes(uint8 source, uint8 channel) = 0;

	/**
	 * Sets a callback which will be called whenever the driver's timer
	 * callback is called by the underlying emulator or hardware driver. The
	 * callback will only be called when the driver is open. Use
	 * @see getBaseTempo to get the delay between each callback invocation.
	 * 
	 * @param timer_param A parameter that will be passed to the callback
	 * function. Optional.
	 * @param timer_proc The function that should be called.
	 */
	void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override {
		_timer_param = timer_param;
		_timer_proc = timer_proc;
	}

protected:
	/**
	 * Applies the current source volume to the active notes and/or MIDI
	 * channels of the specified source. 0xFF can be specified to apply the
	 * source volume for all sources.
	 *
	 * @param source The source for which the source volume should be applied,
	 * or all sources if 0xFF is specified.
	 */
	virtual void applySourceVolume(uint8 source) = 0;
	/**
	 * Processes active fades and sets new volume values if necessary.
	 */
	void updateFading();
	/**
	 * Runs the MIDI driver's timer related functionality. Will update volume
	 * fades and calls the timer callback if necessary.
	 */
	virtual void onTimer();

	// MIDI source data
	MidiSource _sources[MAXIMUM_SOURCES];

	// Default values for each controller
	ControllerDefaults _controllerDefaults;

	// Map for arbitrary instrument remapping.
	const byte *_instrumentRemapping;

	// True if the driver should scale MIDI channel volume to the user
	// specified volume settings.
	bool _userVolumeScaling;

	// User volume settings
	uint16 _userMusicVolume;
	uint16 _userSfxVolume;
	bool _userMute;

	Common::Mutex _fadingMutex; // For operations on fades

	// The number of microseconds to wait before the next fading step.
	uint16 _fadeDelay;

	// The number of microseconds between timer callback invocations.
	uint32 _timerRate;

	// External timer callback
	void *_timer_param;
	Common::TimerManager::TimerProc _timer_proc;
};

class MidiDriver_NULL_Multisource : public MidiDriver_Multisource {
public:
	int open() override;
	bool isOpen() const override { return true; }
	void close() override;
	uint32 getBaseTempo() override { return 10000; }
	MidiChannel *allocateChannel() override { return 0; }
	MidiChannel *getPercussionChannel() override { return 0; }

	using MidiDriver_Multisource::send;
	void send(int8 source, uint32 b) override { }
	using MidiDriver_Multisource::stopAllNotes;
	void stopAllNotes(uint8 source, uint8 channel) override { }

	static void timerCallback(void *data);

protected:
	void applySourceVolume(uint8 source) override { }
};

#endif
