// Copyright(c) 2016 YamaArashi // // 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. // https://github.com/jpmac26/gba-mus-ripper #include #include #include #include #include #include #include "midi.h" #include "main.h" #include "error.h" #include "agb.h" #include "tables.h" enum class MidiEventCategory { Control, SysEx, Meta, Invalid, }; MidiFormat g_midiFormat; std::int_fast32_t g_midiTrackCount; std::int16_t g_midiTimeDiv; int g_midiChan; std::int32_t g_initialWait; void Seek(long offset) { if (std::fseek(g_inputFile, offset, SEEK_SET) != 0) RaiseError("failed to seek to %x", offset); } void Skip(long offset) { if (std::fseek(g_inputFile, offset, SEEK_CUR) != 0) RaiseError("failed to skip %l bytes", offset); } std::string ReadSignature() { char signature[4]; if (std::fread(signature, 4, 1, g_inputFile) != 1) RaiseError("failed to read signature"); return std::string(signature, 4); } std::uint32_t ReadInt8() { int c = std::fgetc(g_inputFile); if (c < 0) RaiseError("unexpected EOF"); return c; } std::uint32_t ReadInt16() { std::uint32_t val = 0; val |= ReadInt8() << 8; val |= ReadInt8(); return val; } std::uint32_t ReadInt24() { std::uint32_t val = 0; val |= ReadInt8() << 16; val |= ReadInt8() << 8; val |= ReadInt8(); return val; } std::uint32_t ReadInt32() { std::uint32_t val = 0; val |= ReadInt8() << 24; val |= ReadInt8() << 16; val |= ReadInt8() << 8; val |= ReadInt8(); return val; } std::uint32_t ReadVLQ() { std::uint32_t val = 0; std::uint32_t c; do { c = ReadInt8(); val <<= 7; val |= (c & 0x7F); } while (c & 0x80); return val; } size_t lendian_fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) { if (size != 4) { /* Warn and exit */ } int x = 1; if (*((char*)&x) == 1) { /* Little endian machine, use fwrite directly */ return fwrite(ptr, size, nmemb, stream); } else { /* Big endian machine, pre-process first */ unsigned char* buffer = (unsigned char*)ptr; for (size_t i = 0; i < nmemb; i++) { unsigned char a = buffer[4 * i]; unsigned char b = buffer[4 * i + 1]; buffer[4 * i] = buffer[4 * i + 3]; buffer[4 * i + 1] = buffer[4 * i + 2]; buffer[4 * i + 2] = b; buffer[4 * i + 3] = a; } return fwrite(ptr, size, nmemb, stream); } } void WriteInt32(int value) { union { char bytes[4]; long ul; } data; data.ul = value; fwrite(&data.bytes[3], 1, 1, g_outputFile); fwrite(&data.bytes[2], 1, 1, g_outputFile); fwrite(&data.bytes[1], 1, 1, g_outputFile); fwrite(&data.bytes[0], 1, 1, g_outputFile); } void WriteInt16(short value) { union { char bytes[2]; short us; } data; data.us = value; fwrite(&data.bytes[1], 1, 1, g_outputFile); fwrite(&data.bytes[0], 1, 1, g_outputFile); } void WriteInt8(char value) { fwrite(&value, 1, 1, g_outputFile); } void writeDeltaTime(std::vector& data, long value) { char bytes[4] = { 0 }; if ((unsigned long)value >= (1 << 28)) { // TODO error number too large value = 0x0FFFffff; } bytes[0] = (char)(((long)value >> 21) & 0x7f); // most significant 7 bits bytes[1] = (char)(((long)value >> 14) & 0x7f); bytes[2] = (char)(((long)value >> 7) & 0x7f); bytes[3] = (char)(((long)value) & 0x7f); // least significant 7 bits int start = 0; while ((start < 4) && (bytes[start] == 0)) start++; for (int i = start; i < 3; i++) { bytes[i] = bytes[i] | 0x80; data.push_back(bytes[i]); } data.push_back(bytes[3]); } void writeInt32(std::vector& data, int value) { union { char bytes[4]; int ul; } onion; onion.ul = value; data.push_back(onion.bytes[3]); data.push_back(onion.bytes[2]); data.push_back(onion.bytes[1]); data.push_back(onion.bytes[0]); } void writeInt24(std::vector& data, int value) { union { char bytes[4]; int ul; } onion; onion.ul = value; data.push_back(onion.bytes[2]); data.push_back(onion.bytes[1]); data.push_back(onion.bytes[0]); } void WriteTrack(int channelId, const std::vector& events) { std::vector data; // TODO reuse same data vector data.reserve(0x1000); data.clear(); // int previousTime = 0; int currentTime = 0; bool DEBUG = g_verbose && false; for (const auto& event : events) { if (DEBUG) { std::printf("TIME: %d\n", event.time); } writeDeltaTime(data, event.time - currentTime); currentTime = event.time; switch (event.type) { case EventType::InstrumentChange: if (DEBUG) { std::printf("InstrumentChange\n"); } data.push_back(0xC0 + channelId); // type data.push_back(event.param1); // instrument break; case EventType::Controller: if (DEBUG) { std::printf("Controller\n"); } data.push_back(0xB0 + channelId); // type data.push_back(event.param1); // controller index data.push_back(event.param2); // value break; case EventType::Note: if (DEBUG) { std::printf("Note\n"); } data.push_back(0x90 + channelId); // type data.push_back(event.note); // note data.push_back(event.param1); // velocity break; case EventType::NoteOff: if (DEBUG) { std::printf("NoteOff\n"); } // Wait and note_end data.push_back(0x80 + channelId); // type data.push_back(event.note); // note data.push_back(0); // Ignored velocity break; case EventType::TimeSignature: { if (DEBUG) { std::printf("TimeSignature\n"); } // One time meta event at the beginning of the meta track. data.push_back(0xff); // meta data.push_back(88); // TIME_SIGNATURE writeDeltaTime(data, 4); // m_length data.push_back(event.param1); // numerator data.push_back(event.param2); // denominator data.push_back(24 * g_clocksPerBeat); // clocksPerClick data.push_back(8); // 32ndPer4th break; } case EventType::Tempo: if (DEBUG) { std::printf("Tempo\n"); } data.push_back(0xff); // meta data.push_back(81); // META_TEMPO writeDeltaTime(data, 3); // only valid tempo size is 3 writeInt24(data, event.param2); // usecPerQuarterNote break; case EventType::Extended: if (DEBUG) { std::printf("Extended\n"); } if (event.note == 0x1d) { // for some reason the first one is in a meta change? data.push_back(0xb0); } data.push_back(0x1e); // extended param change data.push_back(event.param1); writeDeltaTime(data, 0); // Next midi event data.push_back(0x1d); // TODO why twice 0x1d? event.note); data.push_back(event.param2); break; case EventType::PitchBend: if (DEBUG) { std::printf("PitchBend\n"); } data.push_back(0xe0 + channelId); // petch bend data.push_back(0); // lsb data.push_back(event.param2); break; case EventType::EndOfTrack: if (DEBUG) { std::printf("EndOfTrack\n"); } // End of track data.push_back(0xff); // meta data.push_back(47); // META_END_OF_TRACK writeDeltaTime(data, 0); // length break; case EventType::LoopBegin: if (DEBUG) { std::printf("LoopBegin\n"); } data.push_back(0xff); // meta data.push_back(0x6); // META_MARKER data.push_back(0x1); // length data.push_back(0x5B); // [ break; case EventType::LoopEnd: if (DEBUG) { std::printf("LoopEnd\n"); } data.push_back(0xff); // meta data.push_back(0x6); // META_MARKER data.push_back(0x1); // length data.push_back(0x5D); // ] break; } } // MidiTrack header std::fprintf(g_outputFile, "MTrk"); // length of track data WriteInt32(data.size()); fwrite(data.data(), data.size(), 1, g_outputFile); } void WriteMidiFile() { // MidiHeader std::fprintf(g_outputFile, "MThd"); // u32 length of header data = 6 WriteInt32(6); // u16 format = 1 WriteInt16(1); // u16 numberTracks WriteInt16(trackEvents.size() + 1); // u16 midiTimeDiv (24 in example) WriteInt16(g_midiTimeDiv); // Add end of track to meta events Event event; event.type = EventType::EndOfTrack; // EndOfTrack event needs to be at the last meta event (can be earlier than the end of other tracks) for // bgmVaatiMotif to work event.time = metaEvents.back().time; metaEvents.push_back(event); // Track for meta events WriteTrack(-1, metaEvents); int i = 0; for (const auto& events : trackEvents) { WriteTrack(i, events); i++; } }