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