mirror of https://github.com/zeldaret/tmc.git
375 lines
11 KiB
C++
Executable File
375 lines
11 KiB
C++
Executable File
// 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 <cstdio>
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#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<char>& 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<char>& 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<char>& 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<Event>& events) {
|
|
std::vector<char> 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++;
|
|
}
|
|
} |