//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Nov 26 14:12:01 PST 1999
// Last Modified: Mon Jan 18 20:54:04 PST 2021 Added readSmf().
// Filename:      midifile/include/MidiFile.h
// Website:       http://midifile.sapp.org
// Syntax:        C++11
// vim:           ts=3 noexpandtab
//
// Description:   A class that can read/write Standard MIDI files.
//                MIDI data is stored by track in an array.
//

#ifndef _MIDIFILE_H_INCLUDED
#define _MIDIFILE_H_INCLUDED

#include "MidiEventList.h"

#include <fstream>
#include <istream>
#include <string>
#include <vector>


namespace smf {

enum {
    TRACK_STATE_SPLIT  = 0, // Tracks are separated into separate vector postions.
    TRACK_STATE_JOINED = 1  // Tracks are merged into a single vector position,
};                          // like a Type-0 MIDI file, but reversible.

enum {
    TIME_STATE_DELTA    = 0, // MidiMessage::ticks are in delta time format (like MIDI file).
    TIME_STATE_ABSOLUTE = 1  // MidiMessage::ticks are in absolute time format (0=start time).
};

class _TickTime {
	public:
		int    tick;
		double seconds;
};


class MidiFile {
	public:
		               MidiFile                    (void);
		               MidiFile                    (const std::string& filename);
		               MidiFile                    (std::istream& input);
		               MidiFile                    (const MidiFile& other);
		               MidiFile                    (MidiFile&& other);

		              ~MidiFile                    ();

		MidiFile&      operator=                   (const MidiFile& other);
		MidiFile&      operator=                   (MidiFile&& other);

		// Reading/writing functions:

		// Auto-detected SMF or ASCII-encoded SMF (decoded with Binasc class):
		bool           read                        (const std::string& filename);
		bool           read                        (std::istream& instream);
		bool           readBase64                  (const std::string& base64data);
		bool           readBase64                  (std::istream& instream);

		// Only allow Standard MIDI File input:
		bool           readSmf                     (const std::string& filename);
		bool           readSmf                     (std::istream& instream);

		bool           write                       (const std::string& filename);
		bool           write                       (std::ostream& out);
		bool           writeBase64                 (const std::string& out, int width = 0);
		bool           writeBase64                 (std::ostream& out, int width = 0);
		std::string    getBase64                   (int width = 0);
		bool           writeHex                    (const std::string& filename, int width = 25);
		bool           writeHex                    (std::ostream& out, int width = 25);
		bool           writeBinasc                 (const std::string& filename);
		bool           writeBinasc                 (std::ostream& out);
		bool           writeBinascWithComments     (const std::string& filename);
		bool           writeBinascWithComments     (std::ostream& out);
		bool           status                      (void) const;

		// track-related functions:
		const MidiEventList& operator[]            (int aTrack) const;
		MidiEventList&   operator[]                (int aTrack);
		int              getTrackCount             (void) const;
		int              getNumTracks              (void) const;
		int              size                      (void) const;
		void             removeEmpties             (void);

		// tick-related functions:
		void             makeDeltaTicks            (void);
		void             setDeltaTicks             (void) { makeDeltaTicks(); }
		void             deltaTicks                (void);
		void             makeAbsoluteTicks         (void);
		void             setAbsoluteTicks          (void) { makeAbsoluteTicks(); }
		void             absoluteTicks             (void);
		int              getTickState              (void) const;
		bool             isDeltaTicks              (void) const;
		bool             isAbsoluteTicks           (void) const;

		// join/split track functionality:
		void             joinTracks                (void);
		void             splitTracks               (void);
		void             splitTracksByChannel      (void);
		int              getTrackState             (void) const;
		int              hasJoinedTracks           (void) const;
		int              hasSplitTracks            (void) const;
		int              getSplitTrack             (int track, int index) const;
		int              getSplitTrack             (int index) const;

		// track sorting funcionality:
		void             sortTrack                  (int track) { sortTrackNoteOnsBeforeOffs(track); }
		void             sortTrackNoteOnsBeforeOffs (int track);
		void             sortTrackNoteOffsBeforeOns (int track);
		void             sortTracks                 (void) { sortTracksNoteOnsBeforeOffs(); }
		void             sortTracksNoteOnsBeforeOffs(void);
		void             sortTracksNoteOffsBeforeOns(void);
		void             markSequence               (void);
		void             markSequence               (int track, int sequence = 1);
		void             clearSequence              (void);
		void             clearSequence              (int track);

		// track manipulation functionality:
		int              addTrack                  (void);
		int              addTrack                  (int count);
		int              addTracks                 (int count);
		void             deleteTrack               (int aTrack);
		void             mergeTracks               (int aTrack1, int aTrack2);
		int              getTrackCountAsType1      (void);

		// ticks-per-quarter related functions:
		void             setMillisecondTicks       (void);
		int              getTicksPerQuarterNote    (void) const;
		int              getTPQ                    (void) const;
		void             setTicksPerQuarterNote    (int ticks);
		void             setTPQ                    (int ticks);

		// physical-time analysis functions:
		void             doTimeAnalysis            (void);
		double           getTimeInSeconds          (int aTrack, int anIndex);
		double           getTimeInSeconds          (int tickvalue);
		double           getAbsoluteTickTime       (double starttime);
		int              getFileDurationInTicks    (void);
		double           getFileDurationInQuarters (void);
		double           getFileDurationInSeconds  (void);

		// note-analysis functions:
		int              linkNotePairsFIFO         (void);
		int              linkNotePairsLIFO         (void);
		int              linkNotePairs             (void) { return linkNotePairsFIFO(); }
		int              linkEventPairs            (void);
		void             clearLinks                (void);

		// filename functions:
		void             setFilename               (const std::string& aname);
		const char*      getFilename               (void) const;

		// event functionality:
		MidiEvent*       addEvent                  (int aTrack, int aTick,
		                                            std::vector<uchar>& midiData);
		MidiEvent*       addEvent                  (MidiEvent& mfevent);
		MidiEvent*       addEvent                  (int aTrack, MidiEvent& mfevent);
		MidiEvent&       getEvent                  (int aTrack, int anIndex);
		const MidiEvent& getEvent                  (int aTrack, int anIndex) const;
		int              getEventCount             (int aTrack) const;
		int              getNumEvents              (int aTrack) const;
		void             allocateEvents            (int track, int aSize);
		void             erase                     (void);
		void             clear                     (void);
		void             clear_no_deallocate       (void);

		// MIDI message adding convenience functions:
		MidiEvent*        addNoteOn               (int aTrack, int aTick,
		                                           int aChannel, int key,
		                                           int vel);
		MidiEvent*        addNoteOff              (int aTrack, int aTick,
		                                           int aChannel, int key,
		                                           int vel);
		MidiEvent*        addNoteOff              (int aTrack, int aTick,
		                                           int aChannel, int key);
		MidiEvent*        addController           (int aTrack, int aTick,
		                                           int aChannel, int num,
		                                           int value);
		MidiEvent*        addPatchChange          (int aTrack, int aTick,
		                                           int aChannel, int patchnum);
		MidiEvent*        addTimbre               (int aTrack, int aTick,
		                                           int aChannel, int patchnum);
		MidiEvent*        addPitchBend            (int aTrack, int aTick,
		                                           int aChannel, double amount);

		// RPN settings:
		void              setPitchBendRange       (int aTrack, int aTick,
		                                           int aChannel, double range);

		// Controller message adding convenience functions:
		MidiEvent*        addSustain              (int aTrack, int aTick,
		                                           int aChannel, int value);
		MidiEvent*        addSustainPedal         (int aTrack, int aTick,
		                                           int aChannel, int value);
		MidiEvent*        addSustainOn            (int aTrack, int aTick,
		                                           int aChannel);
		MidiEvent*        addSustainPedalOn       (int aTrack, int aTick,
		                                           int aChannel);
		MidiEvent*        addSustainOff           (int aTrack, int aTick,
		                                           int aChannel);
		MidiEvent*        addSustainPedalOff      (int aTrack, int aTick,
		                                           int aChannel);

		// Meta-event adding convenience functions:
		MidiEvent*         addMetaEvent           (int aTrack, int aTick,
		                                           int aType,
		                                           std::vector<uchar>& metaData);
		MidiEvent*         addMetaEvent           (int aTrack, int aTick,
		                                           int aType,
		                                           const std::string& metaData);
		MidiEvent*         addText                (int aTrack, int aTick,
		                                           const std::string& text);
		MidiEvent*         addCopyright           (int aTrack, int aTick,
		                                           const std::string& text);
		MidiEvent*         addTrackName           (int aTrack, int aTick,
		                                           const std::string& name);
		MidiEvent*         addInstrumentName      (int aTrack, int aTick,
		                                           const std::string& name);
		MidiEvent*         addLyric               (int aTrack, int aTick,
		                                           const std::string& text);
		MidiEvent*         addMarker              (int aTrack, int aTick,
		                                           const std::string& text);
		MidiEvent*         addCue                 (int aTrack, int aTick,
		                                           const std::string& text);
		MidiEvent*         addTempo               (int aTrack, int aTick,
		                                           double aTempo);
		MidiEvent*         addKeySignature        (int aTrack, int aTick,
		                                           int fifths, bool mode = 0);
		MidiEvent*         addTimeSignature       (int aTrack, int aTick,
		                                           int top, int bottom,
		                                           int clocksPerClick = 24,
		                                           int num32dsPerQuarter = 8);
		MidiEvent*         addCompoundTimeSignature(int aTrack, int aTick,
		                                           int top, int bottom,
		                                           int clocksPerClick = 36,
		                                           int num32dsPerQuarter = 8);

		uchar              readByte               (std::istream& input);

		// static functions:
		static ushort        readLittleEndian2Bytes  (std::istream& input);
		static ulong         readLittleEndian4Bytes  (std::istream& input);
		static std::ostream& writeLittleEndianUShort (std::ostream& out,
		                                              ushort value);
		static std::ostream& writeBigEndianUShort    (std::ostream& out,
		                                              ushort value);
		static std::ostream& writeLittleEndianShort  (std::ostream& out,
		                                              short value);
		static std::ostream& writeBigEndianShort     (std::ostream& out,
		                                              short value);
		static std::ostream& writeLittleEndianULong  (std::ostream& out,
		                                              ulong value);
		static std::ostream& writeBigEndianULong     (std::ostream& out,
		                                              ulong value);
		static std::ostream& writeLittleEndianLong   (std::ostream& out,
		                                              long value);
		static std::ostream& writeBigEndianLong      (std::ostream& out,
		                                              long value);
		static std::ostream& writeLittleEndianFloat  (std::ostream& out,
		                                              float value);
		static std::ostream& writeBigEndianFloat     (std::ostream& out,
		                                              float value);
		static std::ostream& writeLittleEndianDouble (std::ostream& out,
		                                              double value);
		static std::ostream& writeBigEndianDouble    (std::ostream& out,
		                                              double value);
		static std::string   getGMInstrumentName     (int patchIndex);

	protected:
		// m_events == Lists of MidiEvents for each MIDI file track.
		std::vector<MidiEventList*> m_events;

		// m_ticksPerQuarterNote == A value for the MIDI file header
		// which represents the number of ticks in a quarter note
		// that are used as units for the delta times for MIDI events
		// in MIDI file track data.
		int m_ticksPerQuarterNote = 120;

		// m_theTrackState == state variable for whether the tracks
		// are joined or split.
		int m_theTrackState = TRACK_STATE_SPLIT;

		// m_theTimeState == state variable for whether the MidiEvent::tick
		// variable contain absolute ticks since the start of the file's
		// time, or delta ticks since the last MIDI event in the track.
		int m_theTimeState = TIME_STATE_ABSOLUTE;

		// m_readFileName == the filename of the last file read into
		// the object.
		std::string m_readFileName;

		// m_timemapvalid ==
		bool m_timemapvalid = false;

		// m_timemap ==
		std::vector<_TickTime> m_timemap;

		// m_rwstatus == True if last read was successful, false if a problem.
		bool m_rwstatus = true;

		// m_linkedEventQ == True if link analysis has been done.
		bool m_linkedEventsQ = false;

	private:
		int         extractMidiData                 (std::istream& inputfile,
		                                             std::vector<uchar>& array,
		                                             uchar& runningCommand);
		ulong       readVLValue                     (std::istream& inputfile);
		ulong       unpackVLV                       (uchar a = 0, uchar b = 0,
		                                             uchar c = 0, uchar d = 0,
		                                             uchar e = 0);
		void        writeVLValue                    (long aValue,
		                                             std::vector<uchar>& data);
		int         makeVLV                         (uchar *buffer, int number);
		static int  ticksearch                      (const void* A, const void* B);
		static int  secondsearch                    (const void* A, const void* B);
		void        buildTimeMap                    (void);
		double      linearTickInterpolationAtSecond (double seconds);
		double      linearSecondInterpolationAtTick (int ticktime);
		std::string base64Encode                    (const std::string &input);
		std::string base64Decode                    (const std::string &input);

		static const std::string encodeLookup;
		static const std::vector<int> decodeLookup;
		static const char *GMinstrument[128];
};

} // end of namespace smf

std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile);

#endif /* _MIDIFILE_H_INCLUDED */



