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 */
|