1 /*
2  * BW_Midi_Sequencer - MIDI Sequencer for C++
3  *
4  * Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included
14  * in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  */
24 
25 #pragma once
26 #ifndef BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP
27 #define BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP
28 
29 #include <list>
30 #include <vector>
31 
32 #include "fraction.hpp"
33 #include "file_reader.hpp"
34 #include "midi_sequencer.h"
35 
36 //! Helper for unused values
37 #define BW_MidiSequencer_UNUSED(x) (void)x;
38 
39 class BW_MidiSequencer
40 {
41     /**
42      * @brief MIDI Event utility container
43      */
44     class MidiEvent
45     {
46     public:
47         MidiEvent();
48         /**
49          * @brief Main MIDI event types
50          */
51         enum Types
52         {
53             //! Unknown event
54             T_UNKNOWN       = 0x00,
55             //! Note-Off event
56             T_NOTEOFF       = 0x08,//size == 2
57             //! Note-On event
58             T_NOTEON        = 0x09,//size == 2
59             //! Note After-Touch event
60             T_NOTETOUCH     = 0x0A,//size == 2
61             //! Controller change event
62             T_CTRLCHANGE    = 0x0B,//size == 2
63             //! Patch change event
64             T_PATCHCHANGE   = 0x0C,//size == 1
65             //! Channel After-Touch event
66             T_CHANAFTTOUCH  = 0x0D,//size == 1
67             //! Pitch-bend change event
68             T_WHEEL         = 0x0E,//size == 2
69 
70             //! System Exclusive message, type 1
71             T_SYSEX         = 0xF0,//size == len
72             //! Sys Com Song Position Pntr [LSB, MSB]
73             T_SYSCOMSPOSPTR = 0xF2,//size == 2
74             //! Sys Com Song Select(Song #) [0-127]
75             T_SYSCOMSNGSEL  = 0xF3,//size == 1
76             //! System Exclusive message, type 2
77             T_SYSEX2        = 0xF7,//size == len
78             //! Special event
79             T_SPECIAL       = 0xFF
80         };
81         /**
82          * @brief Special MIDI event sub-types
83          */
84         enum SubTypes
85         {
86             //! Sequension number
87             ST_SEQNUMBER    = 0x00,//size == 2
88             //! Text label
89             ST_TEXT         = 0x01,//size == len
90             //! Copyright notice
91             ST_COPYRIGHT    = 0x02,//size == len
92             //! Sequence track title
93             ST_SQTRKTITLE   = 0x03,//size == len
94             //! Instrument title
95             ST_INSTRTITLE   = 0x04,//size == len
96             //! Lyrics text fragment
97             ST_LYRICS       = 0x05,//size == len
98             //! MIDI Marker
99             ST_MARKER       = 0x06,//size == len
100             //! Cue Point
101             ST_CUEPOINT     = 0x07,//size == len
102             //! [Non-Standard] Device Switch
103             ST_DEVICESWITCH = 0x09,//size == len <CUSTOM>
104             //! MIDI Channel prefix
105             ST_MIDICHPREFIX = 0x20,//size == 1
106 
107             //! End of Track event
108             ST_ENDTRACK     = 0x2F,//size == 0
109             //! Tempo change event
110             ST_TEMPOCHANGE  = 0x51,//size == 3
111             //! SMPTE offset
112             ST_SMPTEOFFSET  = 0x54,//size == 5
113             //! Time signature
114             ST_TIMESIGNATURE = 0x55, //size == 4
115             //! Key signature
116             ST_KEYSIGNATURE = 0x59,//size == 2
117             //! Sequencer specs
118             ST_SEQUENCERSPEC = 0x7F, //size == len
119 
120             /* Non-standard, internal ADLMIDI usage only */
121             //! [Non-Standard] Loop Start point
122             ST_LOOPSTART    = 0xE1,//size == 0 <CUSTOM>
123             //! [Non-Standard] Loop End point
124             ST_LOOPEND      = 0xE2,//size == 0 <CUSTOM>
125             //! [Non-Standard] Raw OPL data
126             ST_RAWOPL       = 0xE3,//size == 0 <CUSTOM>
127 
128             //! [Non-Standard] Loop Start point with support of multi-loops
129             ST_LOOPSTACK_BEGIN = 0xE4,//size == 1 <CUSTOM>
130             //! [Non-Standard] Loop End point with support of multi-loops
131             ST_LOOPSTACK_END   = 0xE5,//size == 0 <CUSTOM>
132             //! [Non-Standard] Loop End point with support of multi-loops
133             ST_LOOPSTACK_BREAK = 0xE6,//size == 0 <CUSTOM>
134         };
135         //! Main type of event
136         uint8_t type;
137         //! Sub-type of the event
138         uint8_t subtype;
139         //! Targeted MIDI channel
140         uint8_t channel;
141         //! Is valid event
142         uint8_t isValid;
143         //! Reserved 5 bytes padding
144         uint8_t __padding[4];
145         //! Absolute tick position (Used for the tempo calculation only)
146         uint64_t absPosition;
147         //! Raw data of this event
148         std::vector<uint8_t> data;
149     };
150 
151     /**
152      * @brief A track position event contains a chain of MIDI events until next delay value
153      *
154      * Created with purpose to sort events by type in the same position
155      * (for example, to keep controllers always first than note on events or lower than note-off events)
156      */
157     class MidiTrackRow
158     {
159     public:
160         MidiTrackRow();
161         //! Clear MIDI row data
162         void clear();
163         //! Absolute time position in seconds
164         double time;
165         //! Delay to next event in ticks
166         uint64_t delay;
167         //! Absolute position in ticks
168         uint64_t absPos;
169         //! Delay to next event in seconds
170         double timeDelay;
171         //! List of MIDI events in the current row
172         std::vector<MidiEvent> events;
173         /**
174          * @brief Sort events in this position
175          * @param noteStates Buffer of currently pressed/released note keys in the track
176          */
177         void sortEvents(bool *noteStates = NULL);
178     };
179 
180     /**
181      * @brief Tempo change point entry. Used in the MIDI data building function only.
182      */
183     struct TempoChangePoint
184     {
185         uint64_t absPos;
186         fraction<uint64_t> tempo;
187     };
188     //P.S. I declared it here instead of local in-function because C++98 can't process templates with locally-declared structures
189 
190     typedef std::list<MidiTrackRow> MidiTrackQueue;
191 
192     /**
193      * @brief Song position context
194      */
195     struct Position
196     {
197         //! Was track began playing
198         bool began;
199         //! Reserved
200         char __padding[7];
201         //! Waiting time before next event in seconds
202         double wait;
203         //! Absolute time position on the track in seconds
204         double absTimePosition;
205         //! Track information
206         struct TrackInfo
207         {
208             //! Delay to next event in a track
209             uint64_t delay;
210             //! Last handled event type
211             int32_t lastHandledEvent;
212             //! Reserved
213             char    __padding2[4];
214             //! MIDI Events queue position iterator
215             MidiTrackQueue::iterator pos;
216 
TrackInfoBW_MidiSequencer::Position::TrackInfo217             TrackInfo() :
218                 delay(0),
219                 lastHandledEvent(0)
220             {}
221         };
222         std::vector<TrackInfo> track;
PositionBW_MidiSequencer::Position223         Position(): began(false), wait(0.0), absTimePosition(0.0), track()
224         {}
225     };
226 
227     //! MIDI Output interface context
228     const BW_MidiRtInterface *m_interface;
229 
230     /**
231      * @brief Build MIDI track data from the raw track data storage
232      * @return true if everything successfully processed, or false on any error
233      */
234     bool buildTrackData(const std::vector<std::vector<uint8_t> > &trackData);
235 
236     /**
237      * @brief Parse one event from raw MIDI track stream
238      * @param [_inout] ptr pointer to pointer to current position on the raw data track
239      * @param [_in] end address to end of raw track data, needed to validate position and size
240      * @param [_inout] status status of the track processing
241      * @return Parsed MIDI event entry
242      */
243     MidiEvent parseEvent(const uint8_t **ptr, const uint8_t *end, int &status);
244 
245     /**
246      * @brief Process MIDI events on the current tick moment
247      * @param isSeek is a seeking process
248      * @return returns false on reaching end of the song
249      */
250     bool processEvents(bool isSeek = false);
251 
252     /**
253      * @brief Handle one event from the chain
254      * @param tk MIDI track
255      * @param evt MIDI event entry
256      * @param status Recent event type, -1 returned when end of track event was handled.
257      */
258     void handleEvent(size_t tk, const MidiEvent &evt, int32_t &status);
259 
260 public:
261 
262     void (*MidiCallback)(void) = NULL;
263 
264     /**
265      * @brief MIDI marker entry
266      */
267     struct MIDI_MarkerEntry
268     {
269         //! Label
270         std::string     label;
271         //! Position time in seconds
272         double          pos_time;
273         //! Position time in MIDI ticks
274         uint64_t        pos_ticks;
275     };
276 
277     /**
278      * @brief Container of one raw CMF instrument
279      */
280     struct CmfInstrument
281     {
282         //! Raw CMF instrument data
283         uint8_t data[16];
284     };
285 
286     /**
287      * @brief The FileFormat enum
288      */
289     enum FileFormat
290     {
291         //! MIDI format
292         Format_MIDI,
293         //! CMF format
294         Format_CMF,
295         //! Id-Software Music File
296         Format_IMF,
297         //! EA-MUS format
298         Format_RSXX,
299         //! AIL's XMIDI format (act same as MIDI, but with exceptions)
300         Format_XMIDI
301     };
302 
303 private:
304     //! Music file format type. MIDI is default.
305     FileFormat m_format;
306     //! SMF format identifier.
307     unsigned m_smfFormat;
308 
309     //! Current position
310     Position m_currentPosition;
311     //! Track begin position
312     Position m_trackBeginPosition;
313     //! Loop start point
314     Position m_loopBeginPosition;
315 
316     //! Is looping enabled or not
317     bool    m_loopEnabled;
318 
319     //! Full song length in seconds
320     double m_fullSongTimeLength;
321     //! Delay after song playd before rejecting the output stream requests
322     double m_postSongWaitDelay;
323 
324     //! Global loop start time
325     double m_loopStartTime;
326     //! Global loop end time
327     double m_loopEndTime;
328 
329     //! Pre-processed track data storage
330     std::vector<MidiTrackQueue > m_trackData;
331 
332     //! CMF instruments
333     std::vector<CmfInstrument> m_cmfInstruments;
334 
335     //! Title of music
336     std::string m_musTitle;
337     //! Copyright notice of music
338     std::string m_musCopyright;
339     //! List of track titles
340     std::vector<std::string> m_musTrackTitles;
341     //! List of MIDI markers
342     std::vector<MIDI_MarkerEntry> m_musMarkers;
343 
344     //! Time of one tick
345     fraction<uint64_t> m_invDeltaTicks;
346     //! Current tempo
347     fraction<uint64_t> m_tempo;
348 
349     //! Tempo multiplier factor
350     double  m_tempoMultiplier;
351     //! Is song at end
352     bool    m_atEnd;
353 
354     /**
355      * @brief Loop stack entry
356      */
357     struct LoopStackEntry
358     {
359         //! is infinite loop
360         bool infinity;
361         //! Count of loops left to break. <0 - infinite loop
362         int loops;
363         //! Start position snapshot to return back
364         Position startPosition;
365         //! Loop start tick
366         uint64_t start;
367         //! Loop end tick
368         uint64_t end;
369     };
370 
371     struct LoopState
372     {
373         //! Loop start has reached
374         bool    caughtStart;
375         //! Loop end has reached, reset on handling
376         bool    caughtEnd;
377 
378         //! Loop start has reached
379         bool    caughtStackStart;
380         //! Loop next has reached, reset on handling
381         bool    caughtStackEnd;
382         //! Loop break has reached, reset on handling
383         bool    caughtStackBreak;
384         //! Skip next stack loop start event handling
385         bool    skipStackStart;
386 
387         //! Are loop points invalid?
388         bool    invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/
389 
390         //! Stack of nested loops
391         std::vector<LoopStackEntry> stack;
392         //! Current level on the loop stack (<0 - out of loop, 0++ - the index in the loop stack)
393         int                         stackLevel;
394 
395         /**
396          * @brief Reset loop state to initial
397          */
resetBW_MidiSequencer::LoopState398         void reset()
399         {
400             caughtStart = false;
401             caughtEnd = false;
402             caughtStackStart = false;
403             caughtStackEnd = false;
404             caughtStackBreak = false;
405             skipStackStart = false;
406         }
407 
fullResetBW_MidiSequencer::LoopState408         void fullReset()
409         {
410             reset();
411             invalidLoop = false;
412             stack.clear();
413             stackLevel = -1;
414         }
415 
isStackEndBW_MidiSequencer::LoopState416         bool isStackEnd()
417         {
418             if(caughtStackEnd && (stackLevel >= 0) && (stackLevel < static_cast<int>(stack.size())))
419             {
420                 const LoopStackEntry &e = stack[stackLevel];
421                 if(e.infinity || (!e.infinity && e.loops > 0))
422                     return true;
423             }
424             return false;
425         }
426 
stackUpBW_MidiSequencer::LoopState427         void stackUp(int count = 1)
428         {
429             stackLevel += count;
430         }
431 
stackDownBW_MidiSequencer::LoopState432         void stackDown(int count = 1)
433         {
434             stackLevel -= count;
435         }
436 
getCurStackBW_MidiSequencer::LoopState437         LoopStackEntry &getCurStack()
438         {
439             if((stackLevel >= 0) && (stackLevel < static_cast<int>(stack.size())))
440                 return stack[stackLevel];
441             if(stack.empty())
442             {
443                 LoopStackEntry d;
444                 d.loops = 0;
445                 d.infinity = 0;
446                 d.start = 0;
447                 d.end = 0;
448                 stack.push_back(d);
449             }
450             return stack[0];
451         }
452     } m_loop;
453 
454     //! Whether the nth track has playback disabled
455     std::vector<bool> m_trackDisable;
456     //! Index of solo track, or max for disabled
457     size_t m_trackSolo;
458 
459     //! File parsing errors string (adding into m_errorString on aborting of the process)
460     std::string m_parsingErrorsString;
461     //! Common error string
462     std::string m_errorString;
463 
464 public:
465     BW_MidiSequencer();
466     virtual ~BW_MidiSequencer();
467 
468     /**
469      * @brief Sets the RT interface
470      * @param intrf Pre-Initialized interface structure (pointer will be taken)
471      */
472     void setInterface(const BW_MidiRtInterface *intrf);
473 
474     /**
475      * @brief Returns file format type of currently loaded file
476      * @return File format type enumeration
477      */
478     FileFormat getFormat();
479 
480     /**
481      * @brief Returns the number of tracks
482      * @return Track count
483      */
484     size_t getTrackCount() const;
485 
486     /**
487      * @brief Sets whether a track is playing
488      * @param track Track identifier
489      * @param enable Whether to enable track playback
490      * @return true on success, false if there was no such track
491      */
492     bool setTrackEnabled(size_t track, bool enable);
493 
494     /**
495      * @brief Enables or disables solo on a track
496      * @param track Identifier of solo track, or max to disable
497      */
498     void setSoloTrack(size_t track);
499 
500     /**
501      * @brief Get the list of CMF instruments (CMF only)
502      * @return Array of raw CMF instruments entries
503      */
504     const std::vector<CmfInstrument> getRawCmfInstruments();
505 
506     /**
507      * @brief Get string that describes reason of error
508      * @return Error string
509      */
510     const std::string &getErrorString();
511 
512     /**
513      * @brief Check is loop enabled
514      * @return true if loop enabled
515      */
516     bool getLoopEnabled();
517 
518     /**
519      * @brief Switch loop on/off
520      * @param enabled Enable loop
521      */
522     void setLoopEnabled(bool enabled);
523 
524     /**
525      * @brief Get music title
526      * @return music title string
527      */
528     const std::string &getMusicTitle();
529 
530     /**
531      * @brief Get music copyright notice
532      * @return music copyright notice string
533      */
534     const std::string &getMusicCopyright();
535 
536     /**
537      * @brief Get list of track titles
538      * @return array of track title strings
539      */
540     const std::vector<std::string> &getTrackTitles();
541 
542     /**
543      * @brief Get list of MIDI markers
544      * @return Array of MIDI marker structures
545      */
546     const std::vector<MIDI_MarkerEntry> &getMarkers();
547 
548     /**
549      * @brief Is position of song at end
550      * @return true if end of song was reached
551      */
552     bool positionAtEnd();
553 
554     /**
555      * @brief Load MIDI file from path
556      * @param filename Path to file to open
557      * @return true if file successfully opened, false on any error
558      */
559     bool loadMIDI(const std::string &filename);
560 
561     /**
562      * @brief Load MIDI file from a memory block
563      * @param data Pointer to memory block with MIDI data
564      * @param size Size of source memory block
565      * @return true if file successfully opened, false on any error
566      */
567     bool loadMIDI(const void *data, size_t size);
568 
569     /**
570      * @brief Load MIDI file by using FileAndMemReader interface
571      * @param fr FileAndMemReader context with opened source file
572      * @return true if file successfully opened, false on any error
573      */
574     bool loadMIDI(FileAndMemReader &fr);
575 
576     /**
577      * @brief Periodic tick handler.
578      * @param s seconds since last call
579      * @param granularity don't expect intervals smaller than this, in seconds
580      * @return desired number of seconds until next call
581      */
582     double Tick(double s, double granularity);
583 
584     /**
585      * @brief Change current position to specified time position in seconds
586      * @param granularity don't expect intervals smaller than this, in seconds
587      * @param seconds Absolute time position in seconds
588      * @return desired number of seconds until next call of Tick()
589      */
590     double seek(double seconds, const double granularity);
591 
592     /**
593      * @brief Gives current time position in seconds
594      * @return Current time position in seconds
595      */
596     double  tell();
597 
598     /**
599      * @brief Gives time length of current song in seconds
600      * @return Time length of current song in seconds
601      */
602     double  timeLength();
603 
604     /**
605      * @brief Gives loop start time position in seconds
606      * @return Loop start time position in seconds or -1 if song has no loop points
607      */
608     double  getLoopStart();
609 
610     /**
611      * @brief Gives loop end time position in seconds
612      * @return Loop end time position in seconds or -1 if song has no loop points
613      */
614     double  getLoopEnd();
615 
616     /**
617      * @brief Return to begin of current song
618      */
619     void    rewind();
620 
621     /**
622      * @brief Get current tempor multiplier value
623      * @return
624      */
625     double getTempoMultiplier();
626 
627     /**
628      * @brief Set tempo multiplier
629      * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - slower
630      */
631     void   setTempo(double tempo);
632 };
633 
634 #endif /* BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP */
635