116 size_t trackCount = 1)
118 if ((
format != 0 &&
format != 1) || ppqn == 0 || ppqn > 0x7fffu)
120 if (trackCount == 0 || trackCount >
kMaxTracks)
122 if (
format == 0 && trackCount != 1)
125 std::vector<MidiTrack> replacement(trackCount);
126 std::vector<TrackAuthoringState> replacementStates(trackCount);
127 format_ =
static_cast<int>(
format);
129 tracks_ = std::move(replacement);
130 validationTotals_ = {};
131 trackAuthoringStates_ = std::move(replacementStates);
141 if (format_ != 1 || tracks_.size() >=
kMaxTracks)
143 trackAuthoringStates_.emplace_back();
146 tracks_.emplace_back();
150 trackAuthoringStates_.pop_back();
153 return tracks_.size() - 1;
164 uint8_t status, uint8_t data1,
170 event.status = status;
173 return appendEvent(track, std::move(event));
185 std::span<const uint8_t> payload)
187 if (!preflightPayloadEvent(track, delta, f0OrF7, 0, payload))
192 event.payload.assign(payload.begin(), payload.end());
193 return appendEvent(track, std::move(event));
197 [[nodiscard]]
bool addMetaEvent(
size_t track, uint32_t delta, uint8_t type,
198 std::span<const uint8_t> payload = {})
206 event.metaType = type;
207 event.payload.assign(payload.begin(), payload.end());
208 return appendEvent(track, std::move(event));
217 validationTotals_ = {};
218 trackAuthoringStates_.clear();
225 [[nodiscard]]
bool read(
const std::filesystem::path& path)
229 std::ifstream input(path, std::ios::binary | std::ios::ate);
230 if (!input.is_open())
232 const std::streampos end = input.tellg();
233 if (end <= std::streampos(0))
235 const auto fileSize =
static_cast<uint64_t
>(end);
237 || fileSize >
static_cast<uint64_t
>(std::numeric_limits<size_t>::max()))
240 std::vector<uint8_t> bytes(
static_cast<size_t>(fileSize));
241 input.seekg(0, std::ios::beg);
242 input.read(
reinterpret_cast<char*
>(bytes.data()),
243 static_cast<std::streamsize
>(bytes.size()));
244 if (input.gcount() !=
static_cast<std::streamsize
>(bytes.size()))
248 if (!parseFile(bytes, parsed))
250 *
this = std::move(parsed);
260 [[nodiscard]]
bool write(
const std::filesystem::path& path)
const
262 ValidationTotals totals;
263 if (format_ != 0 && format_ != 1)
265 if (!validateDocument(format_, ppqn_, tracks_,
false,
false, totals))
268 uint64_t implicitEotCount = 0;
270 if (track.events.empty() || !isEot(track.events.back()))
272 if (implicitEotCount >
kMaxEvents - totals.events)
275 std::vector<uint8_t> output;
277 appendTag(output,
"MThd");
278 appendBE32(output, 6);
279 appendBE16(output,
static_cast<uint16_t
>(format_));
280 appendBE16(output,
static_cast<uint16_t
>(tracks_.size()));
281 appendBE16(output, ppqn_);
285 std::vector<uint8_t> data;
286 for (
const MidiEvent& event : track.events)
292 data.push_back(event.
status);
293 data.push_back(event.
data1);
294 if (channelDataCount(event.
status) == 2)
295 data.push_back(event.
data2);
300 ? uint8_t { 0xf0 } : uint8_t { 0xf7 });
301 appendVlq(data,
static_cast<uint32_t
>(event.
payload.size()));
302 data.insert(data.end(), event.
payload.begin(), event.
payload.end());
305 data.push_back(0xff);
307 appendVlq(data,
static_cast<uint32_t
>(event.
payload.size()));
308 data.insert(data.end(), event.
payload.begin(), event.
payload.end());
313 if (track.events.empty() || !isEot(track.events.back()))
315 data.push_back(0x00);
316 data.push_back(0xff);
317 data.push_back(0x2f);
318 data.push_back(0x00);
322 || data.size() > std::numeric_limits<uint32_t>::max())
327 appendTag(output,
"MTrk");
328 appendBE32(output,
static_cast<uint32_t
>(data.size()));
329 output.insert(output.end(), data.begin(), data.end());
332 std::ofstream destination(path, std::ios::binary | std::ios::trunc);
333 if (!destination.is_open())
335 destination.write(
reinterpret_cast<const char*
>(output.data()),
336 static_cast<std::streamsize
>(output.size()));
337 return destination.good();
341 [[nodiscard]]
int format() const noexcept {
return format_; }
354 [[nodiscard]]
const std::vector<MidiTrack>&
tracks() const noexcept
366 [[nodiscard]] std::optional<std::vector<MidiTempoChange>>
369 const std::optional<size_t> source = tempoTrack(track);
373 std::vector<MidiTempoChange> result;
374 result.push_back({ 0, 500000 });
375 uint64_t absoluteTick = 0;
376 for (
const MidiEvent& event : tracks_[*source].events)
378 if (absoluteTick > std::numeric_limits<uint64_t>::max() -
event.deltaTicks)
380 absoluteTick +=
event.deltaTicks;
382 if (!getTempo(event, tempo))
384 if (result.back().tick == absoluteTick)
385 result.back().microsecondsPerQuarter = tempo;
387 result.push_back({ absoluteTick, tempo });
396 [[nodiscard]] std::optional<uint64_t>
400 uint64_t remainder = 0;
401 if (!integrateTicks(tick, track, whole, remainder))
410 [[nodiscard]] std::optional<double>
414 uint64_t remainder = 0;
415 if (!integrateTicks(tick, track, whole, remainder))
417 return secondsFromRationalMicroseconds(whole, remainder, ppqn_);
421 struct ValidationTotals
424 uint64_t payloadBytes = 0;
427 struct TrackAuthoringState
429 uint64_t absoluteTick = 0;
430 bool sysexOpen =
false;
437 ByteCursor(
const std::vector<uint8_t>& bytes,
size_t begin,
size_t end)
438 : bytes_(bytes), pos_(begin), end_(end)
442 [[nodiscard]]
size_t remaining() const noexcept {
return end_ - pos_; }
443 [[nodiscard]]
size_t position() const noexcept {
return pos_; }
445 bool readU8(uint8_t& value)
noexcept
447 if (pos_ >= end_)
return false;
448 value = bytes_[pos_++];
452 bool readBE16(uint16_t& value)
noexcept
454 uint8_t a = 0, b = 0;
455 if (!readU8(a) || !readU8(b))
return false;
456 value =
static_cast<uint16_t
>((
static_cast<uint16_t
>(a) << 8)
457 |
static_cast<uint16_t
>(b));
461 bool readBE32(uint32_t& value)
noexcept
463 uint8_t a = 0, b = 0, c = 0, d = 0;
464 if (!readU8(a) || !readU8(b) || !readU8(c) || !readU8(d))
466 value = (
static_cast<uint32_t
>(a) << 24)
467 | (
static_cast<uint32_t
>(b) << 16)
468 | (
static_cast<uint32_t
>(c) << 8)
469 |
static_cast<uint32_t
>(d);
473 bool readTag(
char tag[4])
noexcept
475 if (remaining() < 4)
return false;
476 for (
size_t i = 0; i < 4; ++i)
477 tag[i] =
static_cast<char>(bytes_[pos_++]);
481 bool readSpan(
size_t length,
const uint8_t*& data)
noexcept
483 if (length > remaining())
return false;
484 data = bytes_.data() + pos_;
489 bool skip(
size_t length)
noexcept
491 const uint8_t* ignored =
nullptr;
492 return readSpan(length, ignored);
496 const std::vector<uint8_t>& bytes_;
503 std::vector<MidiTrack> tracks_;
504 ValidationTotals validationTotals_;
505 std::vector<TrackAuthoringState> trackAuthoringStates_;
507 static bool sameTag(
const char tag[4],
const char (&expected)[5])
noexcept
509 return std::memcmp(tag, expected, 4) == 0;
512 static int channelDataCount(uint8_t status)
noexcept
514 if (status < 0x80 || status > 0xef)
516 const uint8_t high = status & 0xf0u;
517 return (high == 0xc0u || high == 0xd0u) ? 1 : 2;
520 static bool isEot(
const MidiEvent& event)
noexcept
525 static bool payloadEndsF7(
const MidiEvent& event)
noexcept
527 return !
event.payload.empty() &&
event.payload.back() == 0xf7;
530 static bool validTempoPayload(
const MidiEvent& event)
noexcept
535 return event.payload[0] != 0 ||
event.payload[1] != 0
536 ||
event.payload[2] != 0;
539 static bool validateEventShape(
const MidiEvent& event)
noexcept
547 const int count = channelDataCount(event.
status);
548 if (count == 0 || event.
data1 >= 0x80 || event.
data2 >= 0x80)
550 if (count == 1 && event.
data2 != 0)
552 return event.metaType == 0 &&
event.payload.empty();
556 return event.status == 0 &&
event.data1 == 0 &&
event.data2 == 0
557 &&
event.metaType == 0;
560 || event.
metaType >= 0x80 || !validTempoPayload(event))
562 return event.metaType != 0x2f ||
event.payload.empty();
567 static bool advanceTrackAuthoringState(
568 const MidiEvent& event, TrackAuthoringState& state)
noexcept
571 || state.absoluteTick
572 > std::numeric_limits<uint64_t>::max() - event.
deltaTicks)
574 state.absoluteTick +=
event.deltaTicks;
579 return !state.sysexOpen;
589 state.sysexOpen = !payloadEndsF7(event);
593 state.sysexOpen = !payloadEndsF7(event);
599 static bool validateTrack(
const MidiTrack& track,
bool requireEot,
601 ValidationTotals& totals,
602 TrackAuthoringState* authoringState =
nullptr) noexcept
604 TrackAuthoringState state;
605 for (
const MidiEvent& event : track.events)
607 if (!validateEventShape(event))
615 totals.payloadBytes +=
event.payload.size();
616 if (!advanceTrackAuthoringState(event, state))
619 if ((!allowOpenSysEx && state.sysexOpen)
620 || (requireEot && !state.sawEot))
622 if (authoringState !=
nullptr)
623 *authoringState = state;
627 static bool validateDocument(
int format, uint16_t ppqn,
628 const std::vector<MidiTrack>&
tracks,
631 ValidationTotals& totals,
632 std::span<TrackAuthoringState> authoringStates = {})
noexcept
634 if (format < 0 || format > 2 || ppqn == 0 || ppqn > 0x7fff)
640 if (!authoringStates.empty() && authoringStates.size() !=
tracks.size())
643 for (
size_t index = 0; index <
tracks.size(); ++index)
645 TrackAuthoringState* state = authoringStates.empty()
646 ? nullptr : &authoringStates[index];
647 if (!validateTrack(
tracks[index], requireEot, allowOpenSysEx,
654 [[nodiscard]]
bool appendEvent(
size_t track, MidiEvent event)
656 if (format_ != 0 && format_ != 1)
658 if (track >= tracks_.size()
659 || trackAuthoringStates_.size() != tracks_.size()
660 || !validateEventShape(event))
664 || validationTotals_.payloadBytes
668 TrackAuthoringState nextState = trackAuthoringStates_[track];
669 if (!advanceTrackAuthoringState(event, nextState))
671 const size_t payloadBytes =
event.payload.size();
672 tracks_[track].events.push_back(std::move(event));
673 ++validationTotals_.events;
674 validationTotals_.payloadBytes += payloadBytes;
675 trackAuthoringStates_[track] = nextState;
679 [[nodiscard]]
bool preflightPayloadEvent(
680 size_t track, uint32_t delta,
MidiEventKind kind, uint8_t metaType,
681 std::span<const uint8_t> payload)
const noexcept
683 if ((format_ != 0 && format_ != 1) || track >= tracks_.size()
692 if (metaType >= 0x80 || (metaType == 0x2f && !payload.empty()))
694 if (metaType == 0x51 && payload.size() == 3
695 && payload[0] == 0 && payload[1] == 0 && payload[2] == 0)
699 if (trackAuthoringStates_.size() != tracks_.size()
701 || validationTotals_.payloadBytes
705 const TrackAuthoringState& state = trackAuthoringStates_[track];
707 || state.absoluteTick > std::numeric_limits<uint64_t>::max() - delta)
710 return !state.sysexOpen;
713 return !state.sysexOpen;
716 static bool readVlq(ByteCursor& cursor, uint32_t& value)
noexcept
719 for (
unsigned count = 0; count < 4; ++count)
722 if (!cursor.readU8(
byte))
724 if (count == 0 && (
byte & 0x80u) != 0 && (
byte & 0x7fu) == 0)
726 value =
static_cast<uint32_t
>((value << 7) | (
byte & 0x7fu));
727 if ((
byte & 0x80u) == 0)
733 static bool copyPayload(ByteCursor& cursor, uint32_t length,
734 ValidationTotals& totals,
735 std::vector<uint8_t>& payload)
737 if (length > cursor.remaining()
740 const uint8_t* data =
nullptr;
741 if (!cursor.readSpan(length, data))
743 payload.assign(data, data + length);
744 totals.payloadBytes += length;
748 static bool parseTrack(
const std::vector<uint8_t>& bytes,
size_t begin,
749 size_t end, ValidationTotals& totals,
752 ByteCursor cursor(bytes, begin, end);
753 uint8_t runningStatus = 0;
754 bool sysexOpen =
false;
756 uint64_t absoluteTick = 0;
758 while (cursor.remaining() > 0)
763 if (!readVlq(cursor, event.deltaTicks))
765 if (absoluteTick > std::numeric_limits<uint64_t>::max() - event.deltaTicks)
767 absoluteTick +=
event.deltaTicks;
770 if (!cursor.readU8(first))
774 if (runningStatus == 0 || sysexOpen)
777 event.status = runningStatus;
779 if (channelDataCount(runningStatus) == 2)
781 if (!cursor.readU8(event.data2) || event.data2 >= 0x80)
785 else if (first >= 0x80 && first <= 0xef)
790 event.status = first;
791 runningStatus = first;
792 if (!cursor.readU8(event.data1) || event.data1 >= 0x80)
794 if (channelDataCount(first) == 2)
796 if (!cursor.readU8(event.data2) || event.data2 >= 0x80)
800 else if (first == 0xf0 || first == 0xf7)
806 if (!readVlq(cursor, length)
807 || !copyPayload(cursor, length, totals, event.payload))
811 if (sysexOpen)
return false;
812 sysexOpen = !payloadEndsF7(event);
816 sysexOpen = !payloadEndsF7(event);
819 else if (first == 0xff)
825 if (!cursor.readU8(event.metaType) || event.metaType >= 0x80)
828 if (!readVlq(cursor, length)
829 || !copyPayload(cursor, length, totals, event.payload)
830 || !validTempoPayload(event))
832 if (event.metaType == 0x2f)
834 if (!event.payload.empty())
return false;
843 if (!validateEventShape(event))
846 result.events.push_back(std::move(event));
848 return sawEot && !sysexOpen;
851 static bool parseFile(
const std::vector<uint8_t>& bytes, MidiFile& result)
855 ByteCursor cursor(bytes, 0, bytes.size());
857 uint32_t headerLength = 0;
858 if (!cursor.readTag(tag) || !sameTag(tag,
"MThd")
859 || !cursor.readBE32(headerLength) || headerLength < 6
860 || headerLength > cursor.remaining())
863 uint16_t
format = 0, trackCount = 0, division = 0;
864 if (!cursor.readBE16(
format) || !cursor.readBE16(trackCount)
865 || !cursor.readBE16(division))
867 if (headerLength > 6 && !cursor.skip(headerLength - 6))
870 || (
format == 0 && trackCount != 1)
871 || division == 0 || (division & 0x8000u) != 0)
874 std::vector<MidiTrack>
tracks;
875 tracks.reserve(trackCount);
876 ValidationTotals totals;
877 while (cursor.remaining() > 0)
879 if (cursor.remaining() < 8)
882 if (!cursor.readTag(tag) || !cursor.readBE32(length)
883 || length > cursor.remaining())
885 const size_t begin = cursor.position();
886 const size_t end = begin +
static_cast<size_t>(length);
888 if (sameTag(tag,
"MTrk"))
893 if (!parseTrack(bytes, begin, end, totals, track))
895 tracks.push_back(std::move(track));
902 if (!cursor.skip(length))
905 if (
tracks.size() != trackCount)
908 ValidationTotals verified;
909 std::vector<TrackAuthoringState> authoringStates(
tracks.size());
910 if (!validateDocument(
format, division,
tracks,
true,
false, verified,
914 result.ppqn_ = division;
915 result.tracks_ = std::move(
tracks);
916 result.validationTotals_ = verified;
917 result.trackAuthoringStates_ = std::move(authoringStates);
921 static void appendTag(std::vector<uint8_t>& bytes,
const char (&tag)[5])
923 bytes.insert(bytes.end(), tag, tag + 4);
926 static void appendBE16(std::vector<uint8_t>& bytes, uint16_t value)
928 bytes.push_back(
static_cast<uint8_t
>(value >> 8));
929 bytes.push_back(
static_cast<uint8_t
>(value));
932 static void appendBE32(std::vector<uint8_t>& bytes, uint32_t value)
934 bytes.push_back(
static_cast<uint8_t
>(value >> 24));
935 bytes.push_back(
static_cast<uint8_t
>(value >> 16));
936 bytes.push_back(
static_cast<uint8_t
>(value >> 8));
937 bytes.push_back(
static_cast<uint8_t
>(value));
940 static void appendVlq(std::vector<uint8_t>& bytes, uint32_t value)
942 uint8_t encoded[4] = {};
944 encoded[3] =
static_cast<uint8_t
>(value & 0x7fu);
945 while ((value >>= 7) != 0)
946 encoded[3 - count++] =
static_cast<uint8_t
>((value & 0x7fu) | 0x80u);
947 bytes.insert(bytes.end(), encoded + (4 - count), encoded + 4);
950 [[nodiscard]] std::optional<size_t> tempoTrack(
size_t track)
const noexcept
952 if (format_ < 0 || format_ > 2 || ppqn_ == 0 || track >= tracks_.size())
954 return format_ == 2 ? track :
size_t { 0 };
957 static bool getTempo(
const MidiEvent& event, uint32_t& tempo)
noexcept
960 || event.payload.size() != 3)
962 tempo = (
static_cast<uint32_t
>(
event.payload[0]) << 16)
963 | (
static_cast<uint32_t
>(
event.payload[1]) << 8)
964 |
static_cast<uint32_t
>(
event.payload[2]);
968 static bool addInterval(uint64_t ticks, uint32_t tempo, uint16_t ppqn,
969 uint64_t& whole, uint64_t& remainder)
noexcept
971 const uint64_t quotient = ticks / ppqn;
972 const uint64_t tickRemainder = ticks % ppqn;
974 && quotient > std::numeric_limits<uint64_t>::max() / tempo)
976 const uint64_t wholePart = quotient * tempo;
977 if (whole > std::numeric_limits<uint64_t>::max() - wholePart)
981 const uint64_t fractional = tickRemainder * tempo + remainder;
982 const uint64_t carry = fractional / ppqn;
983 remainder = fractional % ppqn;
984 if (whole > std::numeric_limits<uint64_t>::max() - carry)
990 static double secondsFromRationalMicroseconds(
991 uint64_t wholeMicroseconds, uint64_t remainder,
992 uint16_t ppqn)
noexcept
994 constexpr uint64_t microsecondsPerSecond = 1000000;
995 const uint64_t wholeSeconds = wholeMicroseconds / microsecondsPerSecond;
996 const uint64_t fractionNumerator =
997 (wholeMicroseconds % microsecondsPerSecond) * ppqn + remainder;
998 const uint64_t fractionDenominator = microsecondsPerSecond * ppqn;
999 if (fractionNumerator == 0)
1000 return static_cast<double>(wholeSeconds);
1001 if (wholeSeconds == 0)
1002 return static_cast<double>(fractionNumerator)
1003 /
static_cast<double>(fractionDenominator);
1005 unsigned exponent = 0;
1006 for (uint64_t value = wholeSeconds; value > 1; value >>= 1)
1008 const unsigned fractionalBits = 52u - exponent;
1010 uint64_t residual = fractionNumerator;
1011 for (
unsigned bit = 0; bit < fractionalBits; ++bit)
1015 if (residual >= fractionDenominator)
1017 residual -= fractionDenominator;
1021 const uint64_t twiceResidual = residual * 2;
1022 if (twiceResidual > fractionDenominator
1023 || (twiceResidual == fractionDenominator && (units & 1u) != 0))
1025 const uint64_t scale = uint64_t { 1 } << fractionalBits;
1026 return static_cast<double>(wholeSeconds)
1027 +
static_cast<double>(units) /
static_cast<double>(scale);
1030 bool integrateTicks(uint64_t targetTick,
size_t track, uint64_t& whole,
1031 uint64_t& remainder)
const noexcept
1033 const std::optional<size_t> source = tempoTrack(track);
1038 uint64_t eventTick = 0;
1039 uint64_t integratedTick = 0;
1040 uint32_t tempo = 500000;
1042 for (
const MidiEvent& event : tracks_[*source].events)
1044 if (eventTick > std::numeric_limits<uint64_t>::max() - event.deltaTicks)
1046 eventTick +=
event.deltaTicks;
1047 uint32_t nextTempo = 0;
1048 if (!getTempo(event, nextTempo))
1050 if (eventTick > targetTick)
1052 if (!addInterval(eventTick - integratedTick, tempo, ppqn_, whole, remainder))
1054 integratedTick = eventTick;
1057 return addInterval(targetTick - integratedTick, tempo, ppqn_, whole, remainder);