tmc/tools/agb2mid/agb.cpp

729 lines
26 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.
#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <vector>
#include <cmath>
#include <iostream>
#include "agb.h"
#include "main.h"
#include "midi.h"
#include "tables.h"
//////// ------
// TODO move functions to correct files
std::uint32_t ReadInt8();
std::uint32_t lReadInt32() {
std::uint32_t val = 0;
val |= ReadInt8();
val |= ReadInt8() << 8;
val |= ReadInt8() << 16;
val |= ReadInt8() << 24;
return val;
}
void Seek(long offset);
std::vector<std::uint32_t> trackPointers;
const unsigned int REDUCE_POINTERS = 0x8000000;
extern int g_fileStartOffset; // TODO move to header
void ReadAgbSong() {
Seek(g_fileStartOffset);
int numTracks = ReadInt8();
int numBlocks = ReadInt8();
int priority = ReadInt8();
int reverb = ReadInt8();
int sound = lReadInt32();
for (int i = 0; i < numTracks; i++) {
trackPointers.push_back(lReadInt32());
if (g_verbose)
std::printf("Track: %X\n", trackPointers.back());
}
if (g_verbose) {
std::printf("numTracks %d\n", numTracks);
std::printf("numBlocks %d\n", numBlocks);
std::printf("priority %d\n", priority);
std::printf("reverb %d\n", reverb);
std::printf("sound %X\n", sound);
}
}
const int FINE = 0xb1;
const int GOTO = 0xb2;
const int PATT = 0xb3;
const int PEND = 0xb4;
const int TEMPO = 0xbb;
const int KEYSH = 0xBC;
const int VOICE = 0xbd;
const int VOL = 0xbe;
const int PAN = 0xbf;
const int BEND = 0xc0;
const int BENDR = 0xc1;
const int LFOS = 0xc2;
const int MOD = 0xc4;
const int TUNE = 0xc8;
const int XCMD = 0xcd;
const int EOT = 0xce;
const int TIE = 0xcf;
const int MODT = 0xc5;
const int LFODL = 0xc3;
std::vector<std::vector<Event>> trackEvents;
std::vector<Event> metaEvents;
int convertTime(int time) {
return ((float)time * g_midiTimeDiv) / (24 * g_clocksPerBeat);
}
extern void Skip(long offset);
std::uint32_t GetCurrentPtr() {
return std::ftell(g_inputFile);
}
std::uint32_t PtrToFileAddr(std::uint32_t ptr) {
return ptr - REDUCE_POINTERS;
}
void insertAtCorrectTimeFromEnd(std::vector<Event>& events, const Event& event) {
for (auto it = events.rbegin(); it != events.rend(); it++) {
if ((*it).time <= event.time) {
events.insert(it.base(), event);
return;
}
}
events.insert(events.begin(), event);
}
void insertAtCorrectTimeFromStart(std::vector<Event>& events, const Event& event) {
for (auto it = events.begin(); it != events.end(); it++) {
if ((*it).time >= event.time) {
events.insert(it, event);
return;
}
}
events.push_back(event);
}
void ReadAgbTracks() {
size_t count = 0;
int addedPadding = 8;
// TODO configurable???
g_midiTimeDiv = 24;
// Add initial time signature (only one irrespective of how many tracks there are)
// TODO TimeSignature events in original midi were converted by changing waits? (InsertTimingEvents?)
Event event;
event.type = EventType::TimeSignature;
event.param1 = g_nominator;
event.param2 = g_denominatorExp;
metaEvents.push_back(event);
for (uint32_t trackPointer : trackPointers) {
std::vector<Event> events;
count++;
if (g_verbose)
std::printf("\n\ntrackPointer: %X -> %X %ld\n", trackPointer, trackPointer - REDUCE_POINTERS, count);
// Search for loop in this trac
bool hasLoop = false;
std::uint32_t loopAddress = 0;
bool foundLoop = false;
int loopStartTime = 0;
unsigned int trackEnd =
(trackPointers.size() > count
? trackPointers[count]
: (g_fileStartOffset + REDUCE_POINTERS)); // Use offset to header as end for last track
if (g_verbose)
std::printf("End of track: %X\n", trackEnd);
Seek(trackEnd - REDUCE_POINTERS);
// search for a few bytes whether there is a loop end
for (int i = 5; i < 10 + addedPadding; i++) {
if (trackEnd - i < trackPointer) {
// Ignore GOTOs from the previous track.
continue;
}
Seek(trackEnd - REDUCE_POINTERS - i);
if (ReadInt8() == GOTO) {
if (g_verbose)
std::printf("Has loop: %d\n", i);
loopAddress = lReadInt32() - REDUCE_POINTERS;
if (loopAddress > 0x1000000) {
// The 0xB1 was probably part of the pointer or something.
continue;
}
hasLoop = true;
if (g_verbose) {
std::printf("Addr: %X\n", GetCurrentPtr());
std::printf("Addr: %X\n", loopAddress);
}
break;
}
}
if (g_verbose)
std::printf("Has loop: %d, %X\n", hasLoop, loopAddress);
// Read all track events
Seek(trackPointer - REDUCE_POINTERS);
int type = ReadInt8();
// VOL?
if (type == VOL) {
int val = ReadInt8();
if (g_verbose)
std::printf("ignore vol: %X\n", val);
type = ReadInt8();
}
int keyShift = 0;
// WAIT?
// KEYSH
if (type == KEYSH) {
keyShift = ReadInt8();
if (g_verbose)
std::printf("KEYSH: %d\n", keyShift);
// TODO initial shift is always 0? Or is this set as the whole key shift?
} else {
std::printf("Error: initial type %X not implemented\n", type);
return;
}
std::vector<Event> currentlyPlayingNotes;
bool endOfTrack = false;
int lastCommand = -1;
int lastNote = -1;
int lastVelocity = -1;
int currentTime = 0;
int loopEndTime = -1;
int patternDuration = 0;
type = ReadInt8();
// Did we jump into a pattern
bool inPattern = false;
int returnPtr = 0;
while (!endOfTrack) {
if (hasLoop && !foundLoop && GetCurrentPtr() - 1 == loopAddress) {
if (g_verbose)
std::printf("<<<< inserted loop start\n");
// foundLoop = true;
loopStartTime = currentTime;
Event event;
event.time = loopStartTime;
event.type = EventType::LoopBegin;
events.push_back(event);
}
if (g_verbose)
std::printf("%X|@%d\t ", GetCurrentPtr() - 1, currentTime);
if (type < 0x80) {
// Repeat last command
Skip(-1);
type = lastCommand;
if (g_verbose)
std::printf("(repeat cmd) ");
} else {
if (type > 0xb4 && type != 0xbb) { // type is no repeatable command (not WAIT, GOTO, TEMPO etc.)
lastCommand = type;
}
}
switch (type) {
case TEMPO: {
int val = ReadInt8();
if (g_verbose)
std::printf("TEMPO: %d\n", val * 2);
Event event;
event.time = currentTime;
event.type = EventType::Tempo;
event.param2 = 60000000 / (val * 2);
metaEvents.push_back(event);
break;
}
case VOICE: {
int val = ReadInt8();
if (g_verbose)
std::printf("VOICE: %d\n", val);
Event event;
event.time = currentTime;
event.type = EventType::InstrumentChange;
event.param1 = val;
events.push_back(event);
break;
}
case VOL: {
const int MVL = 128;
const int MXV = 0x7F;
int val = ReadInt8();
if (g_verbose)
std::printf("VOL: %d\n", val);
Event event;
event.time = currentTime;
event.type = EventType::Controller;
event.param1 = 0x7;
event.param2 = std::ceil((float)val * MXV / MVL);
events.push_back(event);
break;
}
case PAN: {
int val = ReadInt8();
if (g_verbose)
std::printf("PAN: %X\n", val);
event.time = currentTime;
event.type = EventType::Controller;
event.param1 = 0xa;
event.param2 = val;
events.push_back(event);
break;
}
case LFOS: {
int val = ReadInt8();
if (g_verbose)
std::printf("LFOS: %X\n", val);
event.time = currentTime;
event.type = EventType::Controller;
event.param1 = 0x15;
event.param2 = val;
events.push_back(event);
break;
}
case MOD: {
int val = ReadInt8();
if (g_verbose)
std::printf("MOD: %X\n", val);
event.time = currentTime;
event.type = EventType::Controller;
event.param1 = 0x1;
event.param2 = val;
events.push_back(event);
break;
}
case PEND: {
if (inPattern) {
if (g_verbose) {
std::printf("P--END\n");
std::printf(">>>>>> Returning to %X (duration: %d)\n", returnPtr, patternDuration);
}
inPattern = false;
Seek(returnPtr);
} else {
if (g_verbose)
std::printf("PEND\n");
}
break;
}
case PATT: {
std::uint32_t val = lReadInt32();
if (g_verbose) {
std::printf("PATT\n");
std::printf(">>>>>> Jumping to: %X\n", val);
}
patternDuration = 0;
returnPtr = GetCurrentPtr();
inPattern = true;
Seek(PtrToFileAddr(val));
break;
}
case GOTO: {
int val = lReadInt32();
if (g_verbose)
std::printf("GOTO: %X\n", val);
// Already handled
loopEndTime = currentTime;
break;
}
case EOT: {
bool lastNoteUsed = false;
int note = ReadInt8();
if (note < 0x80) { // a valid note
type = ReadInt8();
} else {
type = note;
note = lastNote;
lastNoteUsed = true;
}
char noteBuf[16];
if (note >= 24)
std::snprintf(noteBuf, sizeof(noteBuf), g_noteTable[note % 12], note / 12 - 2);
else
std::snprintf(noteBuf, sizeof(noteBuf), g_minusNoteTable[note % 12], note / -12 + 2);
if (lastNoteUsed) {
if (g_verbose)
std::printf("END Tie: (%s) \n", noteBuf);
} else {
if (g_verbose)
std::printf("END Tie: %s\n", noteBuf);
}
Event event;
event.time = currentTime;
event.type = EventType::NoteOff;
event.note = note + keyShift;
events.push_back(event);
continue; // Next type was already read
}
case TIE: {
int note = ReadInt8();
int velocity = 0;
bool lastNoteUsed = false;
bool lastVelocityUsed = false;
if (note < 0x80) { // a valid note
lastNote = note;
velocity = ReadInt8();
if (velocity < 0x80) { // a valid velocity
lastVelocity = velocity;
type = ReadInt8();
} else {
type = velocity;
velocity = lastVelocity;
lastVelocityUsed = true;
}
} else {
type = note;
note = lastNote;
velocity = lastVelocity;
lastNoteUsed = true;
lastVelocityUsed = true;
}
char noteBuf[16];
if (note >= 24)
std::snprintf(noteBuf, sizeof(noteBuf), g_noteTable[note % 12], note / 12 - 2);
else
std::snprintf(noteBuf, sizeof(noteBuf), g_minusNoteTable[note % 12], note / -12 + 2);
if (lastNoteUsed) {
if (lastVelocityUsed) {
if (g_verbose)
std::printf("TIE: (%s) (v%d)\n", noteBuf, velocity);
} else {
if (g_verbose)
std::printf("TIE: (%s) v%d\n", noteBuf, velocity);
}
} else {
if (lastVelocityUsed) {
if (g_verbose)
std::printf("TIE: %s(v%d)\n", noteBuf, velocity);
} else {
if (g_verbose)
std::printf("TIE: %sv%d\n", noteBuf, velocity);
}
}
Event event;
event.time = currentTime;
event.type = EventType::Note;
event.note = note + keyShift;
event.param1 = velocity;
events.push_back(event);
continue; // Next type was already read
}
case BEND: {
int val = ReadInt8();
if (g_verbose)
std::printf("BEND %X\n", val);
Event event;
event.time = currentTime;
event.type = EventType::PitchBend;
event.param2 = val;
events.push_back(event);
break;
}
case BENDR: {
int val = ReadInt8();
if (g_verbose)
std::printf("BENDR %X\n", val);
event.time = currentTime;
event.type = EventType::Controller;
event.param1 = 0x14;
event.param2 = val;
events.push_back(event);
break;
}
case TUNE: {
int val = ReadInt8();
if (g_verbose)
std::printf("TUNE %X\n", val);
event.time = currentTime;
event.type = EventType::Controller;
event.param1 = 0x18;
event.param2 = val;
events.push_back(event);
break;
}
case MODT: {
int val = ReadInt8();
if (g_verbose)
std::printf("MODT %X\n", val);
event.time = currentTime;
event.type = EventType::Controller;
event.param1 = 0x16;
event.param2 = val;
events.push_back(event);
break;
}
case LFODL: {
int val = ReadInt8();
if (g_verbose)
std::printf("LFODL %X\n", val);
event.time = currentTime;
event.type = EventType::Controller;
event.param1 = 0x1A;
event.param2 = val;
events.push_back(event);
break;
}
case XCMD: {
// TODO does XCMD always have four params or can xIECV or xIECL stand alone?
int c1 = ReadInt8();
int v1 = ReadInt8();
int c2 = ReadInt8();
int v2 = ReadInt8();
if (g_verbose)
std::printf("XCMD %X %X %X %X\n", c1, v1, c2, v2);
Event event;
event.time = currentTime;
event.type = EventType::Extended;
event.note = 0x1d;
event.param1 = c1;
event.param2 = v1;
events.push_back(event);
event.note = 0x1f;
event.param1 = c2;
event.param2 = v2;
events.push_back(event);
break;
}
case KEYSH: {
int val = ReadInt8();
if (g_verbose)
std::printf("KEYSH: %d\n", val);
keyShift = val;
break;
}
case FINE: {
if (g_verbose)
std::printf("--- FINE ---\n");
endOfTrack = true;
break;
}
default:
if (type >= 0x80 && type <= 0x80 + 48) {
const int lenTbl[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 28, 30, 32, 36, 40, 42, 44, 48, 52,
54, 56, 60, 64, 66, 68, 72, 76, 78, 80, 84, 88, 90, 92, 96 };
int wait = lenTbl[type - 0x80];
if (g_verbose)
std::printf("WAIT: %d\n", wait);
patternDuration += wait;
if (!currentlyPlayingNotes.empty()) {
for (auto it = currentlyPlayingNotes.begin(); it != currentlyPlayingNotes.end();) {
Event& note = *it; // Modify the note in the list if we don't remove it now.
note.time -= wait;
if (note.time <= 0) {
note.time += currentTime + wait;
insertAtCorrectTimeFromEnd(events, note);
it = currentlyPlayingNotes.erase(it);
} else {
++it;
}
}
}
currentTime += wait;
} else if (type >= 0xD0 && type <= 0xD0 + 47) {
// Handle note with a fixed length
int noteType = type;
int note = ReadInt8();
int velocity = 0;
int length = 0;
bool usedLastNote = false;
bool usedLastVelocity = false;
if (note < 0x80) { // a valid note
lastNote = note;
velocity = ReadInt8();
if (velocity < 0x80) { // a valid velocity
lastVelocity = velocity;
length = ReadInt8();
if (length < 0x80) { // a valid length
type = ReadInt8();
} else {
type = length;
length = 0;
}
} else {
type = velocity;
velocity = lastVelocity;
usedLastVelocity = true;
}
} else {
type = note;
note = lastNote;
velocity = lastVelocity;
usedLastNote = true;
usedLastVelocity = true;
}
Event event;
event.time = currentTime;
event.type = EventType::Note;
event.note = note + keyShift;
event.param1 = velocity;
// Length table for notes and rests // TODO move to tables.cpp
const int lenTbl[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 28, 30, 32, 36, 40, 42, 44, 48, 52,
54, 56, 60, 64, 66, 68, 72, 76, 78, 80, 84, 88, 90, 92, 96 };
int wait = lenTbl[noteType - 0xd0 + 1] + length;
// DEBUG
if (g_verbose) {
char noteBuf[16];
if (note >= 24)
std::snprintf(noteBuf, sizeof(noteBuf), g_noteTable[note % 12], note / 12 - 2);
else
std::snprintf(noteBuf, sizeof(noteBuf), g_minusNoteTable[note % 12], note / -12 + 2);
std::printf("NOTE: %d ", wait - length);
if (usedLastNote) {
std::printf("(%s) ", noteBuf);
} else {
std::printf("%s", noteBuf);
}
if (usedLastVelocity) {
std::printf("(v%03u)", velocity);
} else {
std::printf("v%03u", velocity);
}
if (length != 0) {
std::printf(" +l:%d", length);
}
std::printf("\n");
}
events.push_back(event);
event.type = EventType::NoteOff;
event.time = wait;
currentlyPlayingNotes.push_back(event);
continue; // Next type was already read
} else {
std::printf("ERROR: Unhandled type %X\n", type);
return;
}
break;
}
// TODO notes, waits,
type = ReadInt8();
}
if (currentlyPlayingNotes.size() > 0) {
if (g_verbose) {
std::printf("Need to finish %lu notes.\n", currentlyPlayingNotes.size());
std::cin.ignore();
}
int latestTime = currentTime;
for (auto& note : currentlyPlayingNotes) {
note.time += currentTime;
if (note.time > latestTime) {
latestTime = note.time;
}
insertAtCorrectTimeFromEnd(events, note);
}
if (g_verbose)
std::printf("Found notes from %d up to %d.\n", currentTime, latestTime);
currentTime = latestTime;
if (g_verbose)
std::cin.ignore();
}
if (hasLoop) {
Event event;
event.type = EventType::LoopEnd;
event.time = loopEndTime;
insertAtCorrectTimeFromEnd(events, event);
}
Event event;
event.type = EventType::EndOfTrack;
event.time = currentTime;
events.push_back(event);
if (g_verbose)
std::printf("END OF TRACK: %d\n", currentTime);
trackEvents.push_back(events);
}
// Insert manual time signature change event
for (const auto& change : timeSignatureChanges) {
bool inserted = false;
event.type = EventType::TimeSignature;
event.time = change.time;
event.param1 = change.nominator;
event.param2 = change.denominator;
for (size_t i = 0; i < metaEvents.size() - 1; i++) {
int prevTime = metaEvents[i].time;
int nextTime = metaEvents[i + 1].time;
if (event.time > prevTime && event.time <= nextTime) {
metaEvents.insert(metaEvents.begin() + i + 1, event);
inserted = true;
break;
}
}
if (!inserted) {
metaEvents.push_back(event);
}
}
}