aboutsummaryrefslogtreecommitdiff
path: root/src/midi_sequencer.hpp
blob: e15812ccd3e84b682ba4d1e6f8101cbd1e84b307 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
/*
 * BW_Midi_Sequencer - MIDI Sequencer for C++
 *
 * Copyright (c) 2015-2025 Vitaly Novichkov <admin@wohlnet.ru>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#pragma once
#ifndef BW_MIDI_SEQUENCER_HHHHPPP
#define BW_MIDI_SEQUENCER_HHHHPPP

#include <list>
#include <vector>

#include "fraction.hpp"
#include "file_reader.hpp"
#include "midi_sequencer.h"

//! Helper for unused values
#define BW_MidiSequencer_UNUSED(x) (void)x;

class BW_MidiSequencer
{
    /**
     * @brief MIDI Event utility container
     */
    class MidiEvent
    {
    public:
        MidiEvent();
        /**
         * @brief Main MIDI event types
         */
        enum Types
        {
            //! Unknown event
            T_UNKNOWN       = 0x00,
            //! Note-Off event
            T_NOTEOFF       = 0x08,//size == 2
            //! Note-On event
            T_NOTEON        = 0x09,//size == 2
            //! Note After-Touch event
            T_NOTETOUCH     = 0x0A,//size == 2
            //! Controller change event
            T_CTRLCHANGE    = 0x0B,//size == 2
            //! Patch change event
            T_PATCHCHANGE   = 0x0C,//size == 1
            //! Channel After-Touch event
            T_CHANAFTTOUCH  = 0x0D,//size == 1
            //! Pitch-bend change event
            T_WHEEL         = 0x0E,//size == 2

            //! System Exclusive message, type 1
            T_SYSEX         = 0xF0,//size == len
            //! Sys Com Song Position Pntr [LSB, MSB]
            T_SYSCOMSPOSPTR = 0xF2,//size == 2
            //! Sys Com Song Select(Song #) [0-127]
            T_SYSCOMSNGSEL  = 0xF3,//size == 1
            //! System Exclusive message, type 2
            T_SYSEX2        = 0xF7,//size == len
            //! Special event
            T_SPECIAL       = 0xFF
        };
        /**
         * @brief Special MIDI event sub-types
         */
        enum SubTypes
        {
            //! Sequension number
            ST_SEQNUMBER    = 0x00,//size == 2
            //! Text label
            ST_TEXT         = 0x01,//size == len
            //! Copyright notice
            ST_COPYRIGHT    = 0x02,//size == len
            //! Sequence track title
            ST_SQTRKTITLE   = 0x03,//size == len
            //! Instrument title
            ST_INSTRTITLE   = 0x04,//size == len
            //! Lyrics text fragment
            ST_LYRICS       = 0x05,//size == len
            //! MIDI Marker
            ST_MARKER       = 0x06,//size == len
            //! Cue Point
            ST_CUEPOINT     = 0x07,//size == len
            //! [Non-Standard] Device Switch
            ST_DEVICESWITCH = 0x09,//size == len <CUSTOM>
            //! MIDI Channel prefix
            ST_MIDICHPREFIX = 0x20,//size == 1

            //! End of Track event
            ST_ENDTRACK     = 0x2F,//size == 0
            //! Tempo change event
            ST_TEMPOCHANGE  = 0x51,//size == 3
            //! SMPTE offset
            ST_SMPTEOFFSET  = 0x54,//size == 5
            //! Time signature
            ST_TIMESIGNATURE = 0x55, //size == 4
            //! Key signature
            ST_KEYSIGNATURE = 0x59,//size == 2
            //! Sequencer specs
            ST_SEQUENCERSPEC = 0x7F, //size == len

            /* Non-standard, internal ADLMIDI usage only */
            //! [Non-Standard] Loop Start point
            ST_LOOPSTART    = 0xE1,//size == 0 <CUSTOM>
            //! [Non-Standard] Loop End point
            ST_LOOPEND      = 0xE2,//size == 0 <CUSTOM>
            //! [Non-Standard] Raw OPL data
            ST_RAWOPL       = 0xE3,//size == 0 <CUSTOM>

            //! [Non-Standard] Loop Start point with support of multi-loops
            ST_LOOPSTACK_BEGIN = 0xE4,//size == 1 <CUSTOM>
            //! [Non-Standard] Loop End point with support of multi-loops
            ST_LOOPSTACK_END   = 0xE5,//size == 0 <CUSTOM>
            //! [Non-Standard] Loop End point with support of multi-loops
            ST_LOOPSTACK_BREAK = 0xE6,//size == 0 <CUSTOM>
            //! [Non-Standard] Callback Trigger
            ST_CALLBACK_TRIGGER = 0xE7,//size == 1 <CUSTOM>

            // Built-in hooks
            ST_SONG_BEGIN_HOOK    = 0x101
        };
        //! Main type of event
        uint_fast16_t type;
        //! Sub-type of the event
        uint_fast16_t subtype;
        //! Targeted MIDI channel
        uint_fast16_t channel;
        //! Is valid event
        uint_fast16_t isValid;
        //! Reserved 5 bytes padding
        uint_fast16_t __padding[4];
        //! Absolute tick position (Used for the tempo calculation only)
        uint64_t absPosition;
        //! Raw data of this event
        std::vector<uint8_t> data;
    };

    /**
     * @brief A track position event contains a chain of MIDI events until next delay value
     *
     * Created with purpose to sort events by type in the same position
     * (for example, to keep controllers always first than note on events or lower than note-off events)
     */
    class MidiTrackRow
    {
    public:
        MidiTrackRow();
        //! Clear MIDI row data
        void clear();
        //! Absolute time position in seconds
        double time;
        //! Delay to next event in ticks
        uint64_t delay;
        //! Absolute position in ticks
        uint64_t absPos;
        //! Delay to next event in seconds
        double timeDelay;
        //! List of MIDI events in the current row
        std::vector<MidiEvent> events;
        /**
         * @brief Sort events in this position
         * @param noteStates Buffer of currently pressed/released note keys in the track
         */
        void sortEvents(bool *noteStates = NULL);
    };

    /**
     * @brief Tempo change point entry. Used in the MIDI data building function only.
     */
    struct TempoChangePoint
    {
        uint64_t absPos;
        fraction<uint64_t> tempo;
    };
    //P.S. I declared it here instead of local in-function because C++98 can't process templates with locally-declared structures

    typedef std::list<MidiTrackRow> MidiTrackQueue;

    /**
     * @brief Song position context
     */
    struct Position
    {
        //! Was track began playing
        bool began;
        //! Reserved
        char __padding[7];
        //! Waiting time before next event in seconds
        double wait;
        //! Absolute time position on the track in seconds
        double absTimePosition;
        //! Track information
        struct TrackInfo
        {
            //! Delay to next event in a track
            uint64_t delay;
            //! Last handled event type
            int32_t lastHandledEvent;
            //! Reserved
            char    __padding2[4];
            //! MIDI Events queue position iterator
            MidiTrackQueue::iterator pos;

            TrackInfo() :
                delay(0),
                lastHandledEvent(0)
            {}
        };
        std::vector<TrackInfo> track;
        Position():
            began(false),
            wait(0.0),
            absTimePosition(0.0),
            track()
        {
            for(size_t i = 0; i < 7; ++i)
                __padding[i] = 0;
        }
    };

    //! MIDI Output interface context
    const BW_MidiRtInterface *m_interface;

    /**
     * @brief Prepare internal events storage for track data building
     * @param trackCount Count of tracks
     */
    void buildSmfSetupReset(size_t trackCount);

    /**
     * @brief Build MIDI track data from the raw track data storage
     * @return true if everything successfully processed, or false on any error
     */
    bool buildSmfTrackData(const std::vector<std::vector<uint8_t> > &trackData);

    /**
     * @brief Build the time line from off loaded events
     * @param tempos Pre-collected list of tempo events
     * @param loopStartTicks Global loop start tick (give zero if no global loop presented)
     * @param loopEndTicks Global loop end tick (give zero if no global loop presented)
     */
    void buildTimeLine(const std::vector<MidiEvent> &tempos,
                       uint64_t loopStartTicks = 0,
                       uint64_t loopEndTicks = 0);

    /**
     * @brief Parse one event from raw MIDI track stream
     * @param [_inout] ptr pointer to pointer to current position on the raw data track
     * @param [_in] end address to end of raw track data, needed to validate position and size
     * @param [_inout] status status of the track processing
     * @return Parsed MIDI event entry
     */
    MidiEvent parseEvent(const uint8_t **ptr, const uint8_t *end, int &status);

    /**
     * @brief Process MIDI events on the current tick moment
     * @param isSeek is a seeking process
     * @return returns false on reaching end of the song
     */
    bool processEvents(bool isSeek = false);

    /**
     * @brief Handle one event from the chain
     * @param tk MIDI track
     * @param evt MIDI event entry
     * @param status Recent event type, -1 returned when end of track event was handled.
     */
    void handleEvent(size_t tk, const MidiEvent &evt, int32_t &status);

public:
    /**
     * @brief MIDI marker entry
     */
    struct MIDI_MarkerEntry
    {
        //! Label
        std::string     label;
        //! Position time in seconds
        double          pos_time;
        //! Position time in MIDI ticks
        uint64_t        pos_ticks;
    };

    /**
     * @brief Container of one raw CMF instrument
     */
    struct CmfInstrument
    {
        //! Raw CMF instrument data
        uint8_t data[16];
    };

    /**
     * @brief The FileFormat enum
     */
    enum FileFormat
    {
        //! MIDI format
        Format_MIDI,
        //! CMF format
        Format_CMF,
        //! Id-Software Music File
        Format_IMF,
        //! EA-MUS format
        Format_RSXX,
        //! AIL's XMIDI format (act same as MIDI, but with exceptions)
        Format_XMIDI
    };

    /**
     * @brief Format of loop points implemented by CC events
     */
    enum LoopFormat
    {
        Loop_Default,
        Loop_RPGMaker = 1,
        Loop_EMIDI,
        Loop_HMI
    };

private:
    //! Music file format type. MIDI is default.
    FileFormat m_format;
    //! SMF format identifier.
    unsigned m_smfFormat;
    //! Loop points format
    LoopFormat m_loopFormat;

    //! Current position
    Position m_currentPosition;
    //! Track begin position
    Position m_trackBeginPosition;
    //! Loop start point
    Position m_loopBeginPosition;

    //! Is looping enabled or not
    bool    m_loopEnabled;
    //! Don't process loop: trigger hooks only if they are set
    bool    m_loopHooksOnly;

    //! Full song length in seconds
    double m_fullSongTimeLength;
    //! Delay after song playd before rejecting the output stream requests
    double m_postSongWaitDelay;

    //! Global loop start time
    double m_loopStartTime;
    //! Global loop end time
    double m_loopEndTime;

    //! Pre-processed track data storage
    std::vector<MidiTrackQueue > m_trackData;

    //! CMF instruments
    std::vector<CmfInstrument> m_cmfInstruments;

    //! Title of music
    std::string m_musTitle;
    //! Copyright notice of music
    std::string m_musCopyright;
    //! List of track titles
    std::vector<std::string> m_musTrackTitles;
    //! List of MIDI markers
    std::vector<MIDI_MarkerEntry> m_musMarkers;

    //! Time of one tick
    fraction<uint64_t> m_invDeltaTicks;
    //! Current tempo
    fraction<uint64_t> m_tempo;

    //! Tempo multiplier factor
    double  m_tempoMultiplier;
    //! Is song at end
    bool    m_atEnd;

    //! Set the number of loops limit. Lesser than 0 - loop infinite
    int     m_loopCount;

    //! The number of track of multi-track file (for exmaple, XMI) to load
    int     m_loadTrackNumber;

    //! The XMI-specific list of raw songs, converted into SMF format
    std::vector<std::vector<uint8_t > > m_rawSongsData;

    /**
     * @brief Loop stack entry
     */
    struct LoopStackEntry
    {
        LoopStackEntry() :
            infinity(false),
            loops(0),
            start(0),
            end(0)
        {}

        //! is infinite loop
        bool infinity;
        //! Count of loops left to break. <0 - infinite loop
        int loops;
        //! Start position snapshot to return back
        Position startPosition;
        //! Loop start tick
        uint64_t start;
        //! Loop end tick
        uint64_t end;
    };

    struct LoopState
    {
        //! Loop start has reached
        bool    caughtStart;
        //! Loop end has reached, reset on handling
        bool    caughtEnd;

        //! Loop start has reached
        bool    caughtStackStart;
        //! Loop next has reached, reset on handling
        bool    caughtStackEnd;
        //! Loop break has reached, reset on handling
        bool    caughtStackBreak;
        //! Skip next stack loop start event handling
        bool    skipStackStart;

        //! Are loop points invalid?
        bool    invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/

        //! Is look got temporarily broken because of post-end seek?
        bool    temporaryBroken;

        //! How much times the loop should start repeat? For example, if you want to loop song twice, set value 1
        int     loopsCount;

        //! how many loops left until finish the song
        int     loopsLeft;

        //! Stack of nested loops
        std::vector<LoopStackEntry> stack;
        //! Current level on the loop stack (<0 - out of loop, 0++ - the index in the loop stack)
        int                         stackLevel;

        //! Constructor to initialize member variables
        LoopState()
                : caughtStart(false), caughtEnd(false), caughtStackStart(false),
                caughtStackEnd(false), caughtStackBreak(false), skipStackStart(false),
                invalidLoop(false), temporaryBroken(false), loopsCount(-1), loopsLeft(0),
                stackLevel(-1)
                {}
        /**
         * @brief Reset loop state to initial
         */
        void reset()
        {
            caughtStart = false;
            caughtEnd = false;
            caughtStackStart = false;
            caughtStackEnd = false;
            caughtStackBreak = false;
            skipStackStart = false;
            loopsLeft = loopsCount;
        }

        void fullReset()
        {
            loopsCount = -1;
            reset();
            invalidLoop = false;
            temporaryBroken = false;
            stack.clear();
            stackLevel = -1;
        }

        bool isStackEnd()
        {
            if(caughtStackEnd && (stackLevel >= 0) && (stackLevel < static_cast<int>(stack.size())))
            {
                const LoopStackEntry &e = stack[static_cast<size_t>(stackLevel)];
                if(e.infinity || (!e.infinity && e.loops > 0))
                    return true;
            }
            return false;
        }

        void stackUp(int count = 1)
        {
            stackLevel += count;
        }

        void stackDown(int count = 1)
        {
            stackLevel -= count;
        }

        LoopStackEntry &getCurStack()
        {
            if((stackLevel >= 0) && (stackLevel < static_cast<int>(stack.size())))
                return stack[static_cast<size_t>(stackLevel)];
            if(stack.empty())
            {
                LoopStackEntry d;
                d.loops = 0;
                d.infinity = 0;
                d.start = 0;
                d.end = 0;
                stack.push_back(d);
            }
            return stack[0];
        }
    } m_loop;

    //! Whether the nth track has playback disabled
    std::vector<bool> m_trackDisable;
    //! Index of solo track, or max for disabled
    size_t m_trackSolo;
    //! MIDI channel disable (exception for extra port-prefix-based channels)
    bool m_channelDisable[16];

    /**
     * @brief Handler of callback trigger events
     * @param userData Pointer to user data (usually, context of something)
     * @param trigger Value of the event which triggered this callback.
     * @param track Identifier of the track which triggered this callback.
     */
    typedef void (*TriggerHandler)(void *userData, unsigned trigger, size_t track);

    //! Handler of callback trigger events
    TriggerHandler m_triggerHandler;
    //! User data of callback trigger events
    void *m_triggerUserData;

    //! File parsing errors string (adding into m_errorString on aborting of the process)
    std::string m_parsingErrorsString;
    //! Common error string
    std::string m_errorString;

    struct SequencerTime
    {
        //! Time buffer
        double   timeRest;
        //! Sample rate
        uint32_t sampleRate;
        //! Size of one frame in bytes
        uint32_t frameSize;
        //! Minimum possible delay, granuality
        double minDelay;
        //! Last delay
        double delay;

        void init()
        {
            sampleRate = 44100;
            frameSize = 2;
            reset();
        }

        void reset()
        {
            timeRest = 0.0;
            minDelay = 1.0 / static_cast<double>(sampleRate);
            delay = 0.0;
        }
    } m_time;

public:
    BW_MidiSequencer();
    virtual ~BW_MidiSequencer();

    /**
     * @brief Sets the RT interface
     * @param intrf Pre-Initialized interface structure (pointer will be taken)
     */
    void setInterface(const BW_MidiRtInterface *intrf);

    /**
     * @brief Runs ticking in a sync with audio streaming. Use this together with onPcmRender hook to easily play MIDI.
     * @param stream pointer to the output PCM stream
     * @param length length of the buffer in bytes
     * @return Count of recorded data in bytes
     */
    int playStream(uint8_t *stream, size_t length);

    /**
     * @brief Returns file format type of currently loaded file
     * @return File format type enumeration
     */
    FileFormat getFormat();

    /**
     * @brief Returns the number of tracks
     * @return Track count
     */
    size_t getTrackCount() const;

    /**
     * @brief Sets whether a track is playing
     * @param track Track identifier
     * @param enable Whether to enable track playback
     * @return true on success, false if there was no such track
     */
    bool setTrackEnabled(size_t track, bool enable);

    /**
     * @brief Disable/enable a channel is sounding
     * @param channel Channel number from 0 to 15
     * @param enable Enable the channel playback
     * @return true on success, false if there was no such channel
     */
    bool setChannelEnabled(size_t channel, bool enable);

    /**
     * @brief Enables or disables solo on a track
     * @param track Identifier of solo track, or max to disable
     */
    void setSoloTrack(size_t track);

    /**
     * @brief Set the song number of a multi-song file (such as XMI)
     * @param trackNumber Identifier of the song to load (or -1 to mix all songs as one song)
     */
    void setSongNum(int track);

    /**
     * @brief Retrive the number of songs in a currently opened file
     * @return Number of songs in the file. If 1 or less, means, the file has only one song inside.
     */
    int getSongsCount();

    /**
     * @brief Defines a handler for callback trigger events
     * @param handler Handler to invoke from the sequencer when triggered, or NULL.
     * @param userData Instance of the library
     */
    void setTriggerHandler(TriggerHandler handler, void *userData);

    /**
     * @brief Get the list of CMF instruments (CMF only)
     * @return Array of raw CMF instruments entries
     */
    const std::vector<CmfInstrument> getRawCmfInstruments();

    /**
     * @brief Get string that describes reason of error
     * @return Error string
     */
    const std::string &getErrorString();

    /**
     * @brief Check is loop enabled
     * @return true if loop enabled
     */
    bool getLoopEnabled();

    /**
     * @brief Switch loop on/off
     * @param enabled Enable loop
     */
    void setLoopEnabled(bool enabled);

    /**
     * @brief Get the number of loops set
     * @return number of loops or -1 if loop infinite
     */
    int getLoopsCount();

    /**
     * @brief How many times song should loop
     * @param loops count or -1 to loop infinite
     */
    void setLoopsCount(int loops);

    /**
     * @brief Switch loop hooks-only mode on/off
     * @param enabled Don't loop: trigger hooks only without loop
     */
    void setLoopHooksOnly(bool enabled);

    /**
     * @brief Get music title
     * @return music title string
     */
    const std::string &getMusicTitle();

    /**
     * @brief Get music copyright notice
     * @return music copyright notice string
     */
    const std::string &getMusicCopyright();

    /**
     * @brief Get list of track titles
     * @return array of track title strings
     */
    const std::vector<std::string> &getTrackTitles();

    /**
     * @brief Get list of MIDI markers
     * @return Array of MIDI marker structures
     */
    const std::vector<MIDI_MarkerEntry> &getMarkers();

    /**
     * @brief Is position of song at end
     * @return true if end of song was reached
     */
    bool positionAtEnd();

    /**
     * @brief Load MIDI file from path
     * @param filename Path to file to open
     * @return true if file successfully opened, false on any error
     */
    bool loadMIDI(const std::string &filename);

    /**
     * @brief Load MIDI file from a memory block
     * @param data Pointer to memory block with MIDI data
     * @param size Size of source memory block
     * @return true if file successfully opened, false on any error
     */
    bool loadMIDI(const void *data, size_t size);

    /**
     * @brief Load MIDI file by using FileAndMemReader interface
     * @param fr FileAndMemReader context with opened source file
     * @return true if file successfully opened, false on any error
     */
    bool loadMIDI(FileAndMemReader &fr);

    /**
     * @brief Periodic tick handler.
     * @param s seconds since last call
     * @param granularity don't expect intervals smaller than this, in seconds
     * @return desired number of seconds until next call
     */
    double Tick(double s, double granularity);

    /**
     * @brief Change current position to specified time position in seconds
     * @param granularity don't expect intervals smaller than this, in seconds
     * @param seconds Absolute time position in seconds
     * @return desired number of seconds until next call of Tick()
     */
    double seek(double seconds, const double granularity);

    /**
     * @brief Gives current time position in seconds
     * @return Current time position in seconds
     */
    double  tell();

    /**
     * @brief Gives time length of current song in seconds
     * @return Time length of current song in seconds
     */
    double  timeLength();

    /**
     * @brief Gives loop start time position in seconds
     * @return Loop start time position in seconds or -1 if song has no loop points
     */
    double  getLoopStart();

    /**
     * @brief Gives loop end time position in seconds
     * @return Loop end time position in seconds or -1 if song has no loop points
     */
    double  getLoopEnd();

    /**
     * @brief Return to begin of current song
     */
    void    rewind();

    /**
     * @brief Get current tempor multiplier value
     * @return
     */
    double getTempoMultiplier();

    /**
     * @brief Set tempo multiplier
     * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - slower
     */
    void   setTempo(double tempo);

private:
    /**
     * @brief Load file as Id-software-Music-File (Wolfenstein)
     * @param fr Context with opened file
     * @return true on successful load
     */
    bool parseIMF(FileAndMemReader &fr);

    /**
     * @brief Load file as EA MUS
     * @param fr Context with opened file
     * @return true on successful load
     */
    bool parseRSXX(FileAndMemReader &fr);

    /**
     * @brief Load file as Creative Music Format
     * @param fr Context with opened file
     * @return true on successful load
     */
    bool parseCMF(FileAndMemReader &fr);

    /**
     * @brief Load file as GMD/MUS files (ScummVM)
     * @param fr Context with opened file
     * @return true on successful load
     */
    bool parseGMF(FileAndMemReader &fr);

    /**
     * @brief Load file as Standard MIDI file
     * @param fr Context with opened file
     * @return true on successful load
     */
    bool parseSMF(FileAndMemReader &fr);

    /**
     * @brief Load file as RIFF MIDI
     * @param fr Context with opened file
     * @return true on successful load
     */
    bool parseRMI(FileAndMemReader &fr);

#ifndef BWMIDI_DISABLE_MUS_SUPPORT
    /**
     * @brief Load file as DMX MUS file (Doom)
     * @param fr Context with opened file
     * @return true on successful load
     */
    bool parseMUS(FileAndMemReader &fr);
#endif

#ifndef BWMIDI_DISABLE_XMI_SUPPORT
    /**
     * @brief Load file as AIL eXtended MIdi
     * @param fr Context with opened file
     * @return true on successful load
     */
    bool parseXMI(FileAndMemReader &fr);
#endif

};

#endif /* BW_MIDI_SEQUENCER_HHHHPPP */