|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include <fstream> |
| 4 | +#include <string> |
| 5 | +#include <vector> |
| 6 | + |
| 7 | +#include "Structs/Note.h" |
| 8 | + |
| 9 | +namespace RhythmGameUtilities |
| 10 | +{ |
| 11 | + |
| 12 | +template <typename T> T ByteSwap(T value, int length = 8) |
| 13 | +{ |
| 14 | + return (value >> length) | (value << length); |
| 15 | +} |
| 16 | + |
| 17 | +template <typename T> T ReadChunk(std::ifstream &file, int length = sizeof(T)) |
| 18 | +{ |
| 19 | + T chunk{}; |
| 20 | + file.read(reinterpret_cast<char *>(&chunk), length); |
| 21 | + return chunk; |
| 22 | +} |
| 23 | + |
| 24 | +std::string ReadString(std::ifstream &file, int length) |
| 25 | +{ |
| 26 | + std::string chunk(length, '\0'); |
| 27 | + file.read(&chunk[0], length); |
| 28 | + return file.gcount() == length ? chunk : ""; |
| 29 | +} |
| 30 | + |
| 31 | +uint32_t ReadVarLen(std::ifstream &file) |
| 32 | +{ |
| 33 | + uint32_t value = 0; |
| 34 | + uint8_t byte = 0; |
| 35 | + do |
| 36 | + { |
| 37 | + byte = ReadChunk<uint8_t>(file); |
| 38 | + value = (value << 7) | (byte & 0x7F); |
| 39 | + } while (byte & 0x80); |
| 40 | + return value; |
| 41 | +} |
| 42 | + |
| 43 | +auto STATUS_NOTE_EVENT = 0x90; |
| 44 | +auto STATUS_META_EVENT = 0xFF; |
| 45 | +auto TYPE_END_OF_TRACK = 0x2F; |
| 46 | + |
| 47 | +std::vector<Note> ReadMidiFile(const std::string &path) |
| 48 | +{ |
| 49 | + std::ifstream file(path, std::ios::binary); |
| 50 | + |
| 51 | + if (!file.is_open()) |
| 52 | + { |
| 53 | + throw std::runtime_error("Cannot open MIDI file"); |
| 54 | + } |
| 55 | + |
| 56 | + if (ReadString(file, 4) != "MThd") |
| 57 | + { |
| 58 | + throw std::runtime_error("Invalid MIDI file header"); |
| 59 | + } |
| 60 | + |
| 61 | + auto headerLength = ByteSwap(ReadChunk<uint32_t>(file)); |
| 62 | + auto format = ByteSwap(ReadChunk<uint16_t>(file)); |
| 63 | + auto tracks = ByteSwap(ReadChunk<uint16_t>(file)); |
| 64 | + auto resolution = ByteSwap(ReadChunk<uint16_t>(file)); |
| 65 | + |
| 66 | + std::vector<Note> notes; |
| 67 | + |
| 68 | + for (int t = 0; t < tracks; t += 1) |
| 69 | + { |
| 70 | + if (ReadString(file, 4) != "MTrk") |
| 71 | + { |
| 72 | + throw std::runtime_error("Invalid track header"); |
| 73 | + } |
| 74 | + |
| 75 | + auto trackLength = ByteSwap(ReadChunk<uint32_t>(file)); |
| 76 | + |
| 77 | + uint32_t absoluteTick = 0; |
| 78 | + |
| 79 | + while (true) |
| 80 | + { |
| 81 | + absoluteTick += ReadVarLen(file); |
| 82 | + |
| 83 | + auto status = ReadChunk<uint8_t>(file); |
| 84 | + |
| 85 | + if ((status & 0xF0) == STATUS_NOTE_EVENT) |
| 86 | + { |
| 87 | + Note note{.Position = static_cast<int>(absoluteTick), |
| 88 | + .HandPosition = ReadChunk<uint8_t>(file)}; |
| 89 | + |
| 90 | + auto velocity = ReadChunk<uint8_t>(file); |
| 91 | + |
| 92 | + notes.push_back(note); |
| 93 | + } |
| 94 | + else if (status == STATUS_META_EVENT) |
| 95 | + { |
| 96 | + auto type = ReadChunk<uint8_t>(file); |
| 97 | + |
| 98 | + auto length = ReadVarLen(file); |
| 99 | + |
| 100 | + file.seekg(length, std::ios::cur); |
| 101 | + |
| 102 | + if (type == TYPE_END_OF_TRACK) |
| 103 | + { |
| 104 | + break; |
| 105 | + } |
| 106 | + } |
| 107 | + else if ((status & 0xF0) == 0xC0 || (status & 0xF0) == 0xD0) |
| 108 | + { |
| 109 | + file.seekg(1, std::ios::cur); |
| 110 | + } |
| 111 | + else |
| 112 | + { |
| 113 | + file.seekg(2, std::ios::cur); |
| 114 | + } |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + return notes; |
| 119 | +} |
| 120 | + |
| 121 | +} // namespace RhythmGameUtilities |
0 commit comments