aboutsummaryrefslogtreecommitdiff
path: root/src/midi_sequencer.hpp
blob: 370068be255c39d65905a7515e2549677ea0da6d (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
/*
 * BW_Midi_Sequencer - MIDI Sequencer for C++
 *
 * Copyright (c) 2015-2018 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 BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP
#define BISQUIT_AND_WOHLSTANDS_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>
        };
        //! Main type of event
        uint8_t type;
        //! Sub-type of the event
        uint8_t subtype;
        //! Targeted MIDI channel
        uint8_t channel;
        //! Is valid event
        uint8_t isValid;
        //! Reserved 5 bytes padding
        uint8_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;
        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
        {
            size_t ptr;
            //! Delay to next event in a track
            uint64_t delay;
            //! Last handled event type
            int     lastHandledEvent;
            char    padding2[4];
            //! MIDI Events queue position iterator
            MidiTrackQueue::iterator pos;
            TrackInfo(): ptr(0), delay(0), lastHandledEvent(0) {}
        };
        std::vector<TrackInfo> track;
        Position(): began(false), wait(0.0), absTimePosition(0.0), track()
        {}
    };

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

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

    /**
     * @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, int &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
    };

private:
    //! Music file format type. MIDI is default.
    FileFormat m_format;

    //! 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;

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

    //! Loop start time
    double m_loopStartTime;
    //! 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;
    //! Loop start has reached
    bool    m_loopStart;
    //! Loop end has reached, reset on handling
    bool    m_loopEnd;
    //! Are loop points invalid?
    bool    m_invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/

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

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

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

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

    /**
     * @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 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);
};

#endif /* BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP */