|
35 | 35 | #include <CoreMIDI/CoreMIDI.h> |
36 | 36 | #include <CoreFoundation/CoreFoundation.h> |
37 | 37 | #include <mach/mach_time.h> |
| 38 | +#include <CoreAudio/HostTime.h> |
38 | 39 | #include <pthread.h> |
39 | 40 | #include <unistd.h> // For usleep |
40 | 41 |
|
|
96 | 97 | // Timing |
97 | 98 | int Tempo; |
98 | 99 | int Division; |
99 | | - uint64_t StartTime; |
100 | | - |
101 | | - double SampleRate = 44100; // Assuming a default sample rate |
102 | | - double SamplesPerTick; |
103 | | - double NextTickIn; |
| 100 | + MIDITimeStamp CurrentEventHostTime; // This will track the host time of the current event being processed. |
| 101 | + double HostUnitsPerTick; // Conversion factor: Host Time Units per MIDI Tick. |
104 | 102 | MidiHeader *Events; // Linked list of MIDI headers |
105 | 103 | uint32_t Position; // Current position in the MidiHeader buffer |
106 | 104 |
|
|
127 | 125 | , isOpen(false) |
128 | 126 | , Tempo(500000) // Default: 120 BPM (500,000 µs per quarter note) |
129 | 127 | , Division(96) // Default PPQN |
130 | | - , StartTime(0) |
131 | | - , SamplesPerTick(0.0) |
132 | | - , NextTickIn(0.0) |
| 128 | + , CurrentEventHostTime(0) |
133 | 129 | , Events(nullptr) |
134 | 130 | , Position(0) |
135 | 131 | { |
|
304 | 300 |
|
305 | 301 | void CoreMIDIDevice::CalcTickRate() |
306 | 302 | { |
307 | | - SamplesPerTick = (double)Tempo * SampleRate / (1000000.0 * Division); |
| 303 | + // Tempo is in microseconds per quarter note. Division is PPQN. |
| 304 | + // Host time units per second = AudioGetHostClockFrequency() |
| 305 | + // Microseconds per second = 1,000,000 |
| 306 | + // Microseconds per tick = Tempo / Division |
| 307 | + // Host time units per tick = (Microseconds per tick) * (Host time units per second / Microseconds per second) |
| 308 | + Float64 hostClockFrequency = AudioGetHostClockFrequency(); |
| 309 | + HostUnitsPerTick = ((double)Tempo / (double)Division) * (hostClockFrequency / 1000000.0); |
308 | 310 | } |
309 | 311 |
|
310 | 312 | //========================================================================== |
|
475 | 477 |
|
476 | 478 | void CoreMIDIDevice::InitPlayback() |
477 | 479 | { |
478 | | - StartTime = mach_absolute_time(); |
479 | | - NextTickIn = 0; |
| 480 | + CurrentEventHostTime = AudioGetCurrentHostTime(); // Initialize with current host time |
480 | 481 | Position = 0; |
481 | 482 | Events = nullptr; |
482 | 483 | CalcTickRate(); |
|
494 | 495 | { |
495 | 496 | if (Events == nullptr && Callback) |
496 | 497 | { |
| 498 | + // All events in the current MidiHeader processed, request next buffer |
497 | 499 | Callback(CallbackData); |
498 | 500 | } |
499 | 501 |
|
500 | 502 | if (Events == nullptr) |
501 | 503 | { |
| 504 | + // No events available to process. |
502 | 505 | return 0; |
503 | 506 | } |
504 | 507 |
|
505 | | - uint64_t time = mach_absolute_time(); |
| 508 | + // Read the delta time (first 4 bytes of the event) |
| 509 | + uint32_t *event_ptr = (uint32_t *)(Events->lpData + Position); |
| 510 | + uint32_t midi_delta_ticks = event_ptr[0]; // Assuming delta time is the first uint32_t |
506 | 511 |
|
507 | | - while (Events != nullptr && NextTickIn <= 0) |
508 | | - { |
509 | | - uint32_t *event = (uint32_t *)(Events->lpData + Position); |
510 | | - uint32_t len; |
| 512 | + // Advance CurrentEventHostTime based on delta ticks. |
| 513 | + // This timestamp will be used for the current event. |
| 514 | + CurrentEventHostTime += (MIDITimeStamp)(midi_delta_ticks * HostUnitsPerTick); |
511 | 515 |
|
512 | | - if (event[2] < 0x80000000) // Short message |
513 | | - { |
514 | | - len = 12; |
515 | | - } |
516 | | - else |
517 | | - { |
518 | | - len = 12 + ((MEVENT_EVENTPARM(event[2]) + 3) & ~3); |
519 | | - } |
| 516 | + uint32_t len; // Length of the current MIDI event in bytes |
| 517 | + uint32_t midi_event_type_param = event_ptr[2]; // This is the actual MIDI event or meta-event info |
520 | 518 |
|
521 | | - if (MEVENT_EVENTTYPE(event[2]) == MEVENT_TEMPO) |
522 | | - { |
523 | | - SetTempo(MEVENT_EVENTPARM(event[2])); |
524 | | - } |
525 | | - else if (MEVENT_EVENTTYPE(event[2]) == MEVENT_LONGMSG) |
| 519 | + if (midi_event_type_param < 0x80000000) // Short message (midi_event_type_param is the combined status/data bytes) |
| 520 | + { |
| 521 | + len = 12; // 4 bytes delta time, 4 bytes reserved, 4 bytes MIDI message (up to 3 bytes + padding) |
| 522 | + } |
| 523 | + else // Long message or meta-event (midi_event_type_param holds type and parameter length) |
| 524 | + { |
| 525 | + len = 12 + ((MEVENT_EVENTPARM(midi_event_type_param) + 3) & ~3); |
| 526 | + } |
| 527 | + |
| 528 | + if (MEVENT_EVENTTYPE(midi_event_type_param) == MEVENT_TEMPO) |
| 529 | + { |
| 530 | + // Tempo change event, update our internal calculation for future events |
| 531 | + SetTempo(MEVENT_EVENTPARM(midi_event_type_param)); |
| 532 | + } |
| 533 | + else if (MEVENT_EVENTTYPE(midi_event_type_param) == MEVENT_LONGMSG) |
| 534 | + { |
| 535 | + // Long MIDI message (SysEx, etc.), data starts after event_ptr[3] |
| 536 | + SendMIDIData((uint8_t *)&event_ptr[3], MEVENT_EVENTPARM(midi_event_type_param), CurrentEventHostTime); |
| 537 | + } |
| 538 | + else if (MEVENT_EVENTTYPE(midi_event_type_param) == 0) // Short MIDI message (note on/off, control change, etc.) |
| 539 | + { |
| 540 | + // midi_event_type_param contains the 1, 2, or 3 byte MIDI message |
| 541 | + uint8_t msg[3] = { (uint8_t)(midi_event_type_param & 0xff), |
| 542 | + (uint8_t)((midi_event_type_param >> 8) & 0xff), |
| 543 | + (uint8_t)((midi_event_type_param >> 16) & 0xff) }; |
| 544 | + int msgLen = 0; |
| 545 | + if (msg[0] >= 0xF0) // System messages |
526 | 546 | { |
527 | | - SendMIDIData((uint8_t *)&event[3], MEVENT_EVENTPARM(event[2]), time); |
| 547 | + if (msg[0] == 0xF0 || msg[0] == 0xF7) msgLen = 1; // Start/Stop/Continue/Timing/Active Sensing/Reset (1 byte) |
| 548 | + else if (msg[0] == 0xF1 || msg[0] == 0xF3) msgLen = 2; // Time Code Quarter Frame, Song Select (2 bytes) |
| 549 | + else if (msg[0] == 0xF2) msgLen = 3; // Song Position Pointer (3 bytes) |
| 550 | + else msgLen = 1; // Default to 1 for other unknown system messages |
528 | 551 | } |
529 | | - else if (MEVENT_EVENTTYPE(event[2]) == 0) |
| 552 | + else if (msg[0] >= 0xC0 && msg[0] < 0xE0) // Program Change or Channel Aftertouch (2 bytes) |
530 | 553 | { |
531 | | - uint8_t msg[3] = { (uint8_t)(event[2] & 0xff), (uint8_t)((event[2] >> 8) & 0xff), (uint8_t)((event[2] >> 16) & 0xff) }; |
532 | | - int msgLen = (msg[0] >= 0xC0 && msg[0] < 0xE0) ? 2 : 3; |
533 | | - SendMIDIData(msg, msgLen, time); |
| 554 | + msgLen = 2; |
534 | 555 | } |
535 | | - |
536 | | - Position += len; |
537 | | - if (Position >= Events->dwBytesRecorded) |
| 556 | + else // Note On/Off, Poly Aftertouch, Control Change, Pitch Bend (3 bytes) |
538 | 557 | { |
539 | | - Events = Events->lpNext; |
540 | | - Position = 0; |
541 | | - if (Events == nullptr && Callback) |
542 | | - { |
543 | | - Callback(CallbackData); |
544 | | - } |
| 558 | + msgLen = 3; |
545 | 559 | } |
| 560 | + SendMIDIData(msg, msgLen, CurrentEventHostTime); |
| 561 | + } |
| 562 | + // Other MEVENT_EVENTTYPE values (e.g., MEVENT_NOTEON, MEVENT_NOTEOFF etc. from WinMIDI) |
| 563 | + // are not directly used here; the raw MIDI message is parsed from event_ptr[2] |
546 | 564 |
|
547 | | - if (Events != nullptr) |
548 | | - { |
549 | | - NextTickIn += SamplesPerTick * (*(uint32_t *)(Events->lpData + Position)); |
550 | | - } |
| 565 | + Position += len; |
| 566 | + if (Position >= Events->dwBytesRecorded) |
| 567 | + { |
| 568 | + // Current MidiHeader buffer exhausted, move to the next one |
| 569 | + Events = Events->lpNext; |
| 570 | + Position = 0; |
551 | 571 | } |
| 572 | + |
| 573 | + // Indicate that an event was processed and potentially more are available in the current tick. |
| 574 | + // The PlayerLoop will decide when to call PlayTick again. |
552 | 575 | return 1; |
553 | 576 | } |
554 | 577 |
|
|
578 | 601 | { |
579 | 602 | while (!ExitRequested) |
580 | 603 | { |
581 | | - PlayTick(); |
582 | | - usleep(10000); // Sleep for 10ms |
| 604 | + // Process all available events and schedule them with CoreMIDI |
| 605 | + while (Events != nullptr && !Paused && !ExitRequested) |
| 606 | + { |
| 607 | + // PlayTick returns 1 if an event was processed. |
| 608 | + // It will continue to be called until Events becomes nullptr. |
| 609 | + PlayTick(); |
| 610 | + } |
| 611 | + |
| 612 | + // After processing all currently available events, or if paused/exit requested, |
| 613 | + // wait for new data, unpause, or exit signal. |
| 614 | + std::unique_lock<std::mutex> lock(EventMutex); |
| 615 | + EventCV.wait(lock, [&]{ |
| 616 | + return Paused || ExitRequested || Events != nullptr; // Wake up if paused, exit requested, or new events available |
| 617 | + }); |
| 618 | + |
| 619 | + // If paused, just wait until unpaused or exit requested |
| 620 | + while (Paused && !ExitRequested) |
| 621 | + { |
| 622 | + EventCV.wait(lock); |
| 623 | + } |
583 | 624 | } |
584 | 625 | } |
585 | 626 |
|
|
0 commit comments