fireemblem8u/tools/mid2agb/agb.cpp
2022-01-04 22:31:44 +08:00

548 lines
15 KiB
C++

// 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 "agb.h"
#include "main.h"
#include "midi.h"
#include "tables.h"
int g_agbTrack;
static std::string s_lastOpName;
static int s_blockNum;
static bool s_keepLastOpName;
static int s_lastNote;
static int s_lastVelocity;
static bool s_noteChanged;
static bool s_velocityChanged;
static bool s_inPattern;
static int s_extendedCommand;
static int s_memaccOp;
static int s_memaccParam1;
static int s_memaccParam2;
void PrintAgbHeader()
{
std::fprintf(g_outputFile, "\t.include \"MPlayDef.s\"\n\n");
std::fprintf(g_outputFile, "\t.equ\t%s_grp, voicegroup%03u\n", g_asmLabel.c_str(), g_voiceGroup);
std::fprintf(g_outputFile, "\t.equ\t%s_pri, %u\n", g_asmLabel.c_str(), g_priority);
if (g_reverb >= 0)
std::fprintf(g_outputFile, "\t.equ\t%s_rev, reverb_set+%u\n", g_asmLabel.c_str(), g_reverb);
else
std::fprintf(g_outputFile, "\t.equ\t%s_rev, 0\n", g_asmLabel.c_str());
std::fprintf(g_outputFile, "\t.equ\t%s_mvl, %u\n", g_asmLabel.c_str(), g_masterVolume);
std::fprintf(g_outputFile, "\t.equ\t%s_key, %u\n", g_asmLabel.c_str(), 0);
std::fprintf(g_outputFile, "\t.equ\t%s_tbs, %u\n", g_asmLabel.c_str(), g_clocksPerBeat);
std::fprintf(g_outputFile, "\t.equ\t%s_exg, %u\n", g_asmLabel.c_str(), g_exactGateTime);
std::fprintf(g_outputFile, "\t.equ\t%s_cmp, %u\n", g_asmLabel.c_str(), g_compressionEnabled);
std::fprintf(g_outputFile, "\n\t.section .rodata\n");
std::fprintf(g_outputFile, "\t.global\t%s\n", g_asmLabel.c_str());
std::fprintf(g_outputFile, "\t.align\t2\n");
}
void ResetTrackVars()
{
s_lastVelocity = -1;
s_lastNote = -1;
s_velocityChanged = false;
s_noteChanged = false;
s_keepLastOpName = false;
s_lastOpName = "";
s_inPattern = false;
}
void PrintWait(int wait)
{
if (wait > 0)
{
std::fprintf(g_outputFile, "\t.byte\tW%02d\n", wait);
s_velocityChanged = true;
s_noteChanged = true;
s_keepLastOpName = true;
}
}
void PrintOp(int wait, std::string name, const char *format, ...)
{
std::va_list args;
va_start(args, format);
std::fprintf(g_outputFile, "\t.byte\t\t");
if (format != nullptr)
{
if (!g_compressionEnabled || s_lastOpName != name)
{
std::fprintf(g_outputFile, "%s, ", name.c_str());
s_lastOpName = name;
}
else
{
std::fprintf(g_outputFile, " ");
}
std::vfprintf(g_outputFile, format, args);
}
else
{
std::fputs(name.c_str(), g_outputFile);
s_lastOpName = name;
}
std::fprintf(g_outputFile, "\n");
va_end(args);
PrintWait(wait);
}
void PrintByte(const char *format, ...)
{
std::va_list args;
va_start(args, format);
std::fprintf(g_outputFile, "\t.byte\t");
std::vfprintf(g_outputFile, format, args);
std::fprintf(g_outputFile, "\n");
s_velocityChanged = true;
s_noteChanged = true;
s_keepLastOpName = true;
va_end(args);
}
void PrintWord(const char *format, ...)
{
std::va_list args;
va_start(args, format);
std::fprintf(g_outputFile, "\t .word\t");
std::vfprintf(g_outputFile, format, args);
std::fprintf(g_outputFile, "\n");
va_end(args);
}
void PrintNote(const Event& event)
{
int note = event.note;
int velocity = g_noteVelocityLUT[event.param1];
int duration = -1;
if (event.param2 != -1)
duration = g_noteDurationLUT[event.param2];
int gateTimeParam = 0;
if (g_exactGateTime && duration != -1)
gateTimeParam = event.param2 - duration;
char gtpBuf[16];
if (gateTimeParam > 0)
std::snprintf(gtpBuf, sizeof(gtpBuf), ", gtp%u", gateTimeParam);
else
gtpBuf[0] = 0;
char opName[16];
if (duration == -1)
std::strcpy(opName, "TIE ");
else
std::snprintf(opName, sizeof(opName), "N%02u ", duration);
bool noteChanged = true;
bool velocityChanged = true;
if (g_compressionEnabled)
{
noteChanged = (note != s_lastNote);
velocityChanged = (velocity != s_lastVelocity);
}
if (s_keepLastOpName)
s_keepLastOpName = false;
else
s_lastOpName = "";
if (noteChanged || velocityChanged || (gateTimeParam > 0))
{
s_lastNote = note;
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);
char velocityBuf[16];
if (velocityChanged || (gateTimeParam > 0))
{
s_lastVelocity = velocity;
std::snprintf(velocityBuf, sizeof(velocityBuf), ", v%03u", velocity);
}
else
{
velocityBuf[0] = 0;
}
PrintOp(event.time, opName, "%s%s%s", noteBuf, velocityBuf, gtpBuf);
}
else
{
PrintOp(event.time, opName, 0);
}
s_noteChanged = noteChanged;
s_velocityChanged = velocityChanged;
}
void PrintEndOfTieOp(const Event& event)
{
int note = event.note;
bool noteChanged = (note != s_lastNote);
if (!noteChanged || !s_noteChanged)
s_lastOpName = "";
if (!noteChanged && g_compressionEnabled)
{
PrintOp(event.time, "EOT ", nullptr);
}
else
{
s_lastNote = note;
if (note >= 24)
PrintOp(event.time, "EOT ", g_noteTable[note % 12], note / 12 - 2);
else
PrintOp(event.time, "EOT ", g_minusNoteTable[note % 12], note / -12 + 2);
}
s_noteChanged = noteChanged;
}
void PrintSeqLoopLabel(const Event& event)
{
s_blockNum = event.param1 + 1;
std::fprintf(g_outputFile, "%s_%u_B%u:\n", g_asmLabel.c_str(), g_agbTrack, s_blockNum);
PrintWait(event.time);
ResetTrackVars();
}
void PrintMemAcc(const Event& event)
{
switch (s_memaccOp)
{
case 0x00:
PrintByte("MEMACC, mem_set, 0x%02X, %u", s_memaccParam1, event.param2);
break;
case 0x01:
PrintByte("MEMACC, mem_add, 0x%02X, %u", s_memaccParam1, event.param2);
break;
case 0x02:
PrintByte("MEMACC, mem_sub, 0x%02X, %u", s_memaccParam1, event.param2);
break;
case 0x03:
PrintByte("MEMACC, mem_mem_set, 0x%02X, 0x%02X", s_memaccParam1, event.param2);
break;
case 0x04:
PrintByte("MEMACC, mem_mem_add, 0x%02X, 0x%02X", s_memaccParam1, event.param2);
break;
case 0x05:
PrintByte("MEMACC, mem_mem_sub, 0x%02X, 0x%02X", s_memaccParam1, event.param2);
break;
// TODO: everything else
case 0x06:
break;
case 0x07:
break;
case 0x08:
break;
case 0x09:
break;
case 0x0A:
break;
case 0x0B:
break;
case 0x0C:
break;
case 0x0D:
break;
case 0x0E:
break;
case 0x0F:
break;
case 0x10:
break;
case 0x11:
break;
case 0x46:
break;
case 0x47:
break;
case 0x48:
break;
case 0x49:
break;
case 0x4A:
break;
case 0x4B:
break;
case 0x4C:
break;
case 0x4D:
break;
case 0x4E:
break;
case 0x4F:
break;
case 0x50:
break;
case 0x51:
break;
default:
break;
}
PrintWait(event.time);
}
void PrintExtendedOp(const Event& event)
{
// TODO: support for other extended commands
switch (s_extendedCommand)
{
case 0x08:
PrintOp(event.time, "XCMD ", "xIECV , %u", event.param2);
break;
case 0x09:
PrintOp(event.time, "XCMD ", "xIECL , %u", event.param2);
break;
default:
PrintWait(event.time);
break;
}
}
void PrintControllerOp(const Event& event)
{
switch (event.param1)
{
case 0x01:
PrintOp(event.time, "MOD ", "%u", event.param2);
break;
case 0x07:
PrintOp(event.time, "VOL ", "%u*%s_mvl/mxv", event.param2, g_asmLabel.c_str());
break;
case 0x0A:
PrintOp(event.time, "PAN ", "c_v%+d", event.param2 - 64);
break;
case 0x0C:
case 0x10:
PrintMemAcc(event);
break;
case 0x0D:
s_memaccOp = event.param2;
PrintWait(event.time);
break;
case 0x0E:
s_memaccParam1 = event.param2;
PrintWait(event.time);
break;
case 0x0F:
s_memaccParam2 = event.param2;
PrintWait(event.time);
break;
case 0x11:
std::fprintf(g_outputFile, "%s_%u_L%u:\n", g_asmLabel.c_str(), g_agbTrack, event.param2);
PrintWait(event.time);
ResetTrackVars();
break;
case 0x14:
PrintOp(event.time, "BENDR ", "%u", event.param2);
break;
case 0x15:
PrintOp(event.time, "LFOS ", "%u", event.param2);
break;
case 0x16:
PrintOp(event.time, "MODT ", "%u", event.param2);
break;
case 0x18:
PrintOp(event.time, "TUNE ", "c_v%+d", event.param2 - 64);
break;
case 0x1A:
PrintOp(event.time, "LFODL ", "%u", event.param2);
break;
case 0x1D:
case 0x1F:
PrintExtendedOp(event);
break;
case 0x1E:
s_extendedCommand = event.param2;
// TODO: loop op
break;
case 0x21:
case 0x27:
PrintByte("PRIO , %u", event.param2);
PrintWait(event.time);
break;
default:
PrintWait(event.time);
break;
}
}
void PrintAgbTrack(std::vector<Event>& events)
{
std::fprintf(g_outputFile, "\n@**************** Track %u (Midi-Chn.%u) ****************@\n\n", g_agbTrack, g_midiChan + 1);
std::fprintf(g_outputFile, "%s_%u:\n", g_asmLabel.c_str(), g_agbTrack);
int wholeNoteCount = 0;
int loopEndBlockNum = 0;
ResetTrackVars();
bool foundVolBeforeNote = false;
for (const Event& event : events)
{
if (event.type == EventType::Note)
break;
if (event.type == EventType::Controller && event.param1 == 0x07)
{
foundVolBeforeNote = true;
break;
}
}
if (!foundVolBeforeNote)
PrintByte("\tVOL , 127*%s_mvl/mxv", g_asmLabel.c_str());
PrintWait(g_initialWait);
PrintByte("KEYSH , %s_key%+d", g_asmLabel.c_str(), 0);
for (unsigned i = 0; events[i].type != EventType::EndOfTrack; i++)
{
const Event& event = events[i];
if (IsPatternBoundary(event.type))
{
if (s_inPattern)
PrintByte("PEND");
s_inPattern = false;
}
if (event.type == EventType::WholeNoteMark || event.type == EventType::Pattern)
std::fprintf(g_outputFile, "@ %03d ----------------------------------------\n", wholeNoteCount++);
switch (event.type)
{
case EventType::Note:
PrintNote(event);
break;
case EventType::EndOfTie:
PrintEndOfTieOp(event);
break;
case EventType::Label:
PrintSeqLoopLabel(event);
break;
case EventType::LoopEnd:
PrintByte("GOTO");
PrintWord("%s_%u_B%u", g_asmLabel.c_str(), g_agbTrack, loopEndBlockNum);
PrintSeqLoopLabel(event);
break;
case EventType::LoopEndBegin:
PrintByte("GOTO");
PrintWord("%s_%u_B%u", g_asmLabel.c_str(), g_agbTrack, loopEndBlockNum);
PrintSeqLoopLabel(event);
loopEndBlockNum = s_blockNum;
break;
case EventType::LoopBegin:
PrintSeqLoopLabel(event);
loopEndBlockNum = s_blockNum;
break;
case EventType::WholeNoteMark:
if (event.param2 & 0x80000000)
{
std::fprintf(g_outputFile, "%s_%u_%03lu:\n", g_asmLabel.c_str(), g_agbTrack, (unsigned long)(event.param2 & 0x7FFFFFFF));
ResetTrackVars();
s_inPattern = true;
}
PrintWait(event.time);
break;
case EventType::Pattern:
PrintByte("PATT");
PrintWord("%s_%u_%03lu", g_asmLabel.c_str(), g_agbTrack, event.param2);
while (!IsPatternBoundary(events[i + 1].type))
i++;
ResetTrackVars();
break;
case EventType::Tempo:
PrintByte("TEMPO , %u*%s_tbs/2", 60000000 / event.param2, g_asmLabel.c_str());
PrintWait(event.time);
break;
case EventType::InstrumentChange:
PrintOp(event.time, "VOICE ", "%u", event.param1);
break;
case EventType::PitchBend:
PrintOp(event.time, "BEND ", "c_v%+d", event.param2 - 64);
break;
case EventType::Controller:
PrintControllerOp(event);
break;
default:
PrintWait(event.time);
break;
}
}
PrintByte("FINE");
}
void PrintAgbFooter()
{
int trackCount = g_agbTrack - 1;
std::fprintf(g_outputFile, "\n@******************************************************@\n");
std::fprintf(g_outputFile, "\t.align\t2\n");
std::fprintf(g_outputFile, "\n%s:\n", g_asmLabel.c_str());
std::fprintf(g_outputFile, "\t.byte\t%u\t@ NumTrks\n", trackCount);
std::fprintf(g_outputFile, "\t.byte\t%u\t@ NumBlks\n", 0);
std::fprintf(g_outputFile, "\t.byte\t%s_pri\t@ Priority\n", g_asmLabel.c_str());
std::fprintf(g_outputFile, "\t.byte\t%s_rev\t@ Reverb.\n", g_asmLabel.c_str());
std::fprintf(g_outputFile, "\n");
std::fprintf(g_outputFile, "\t.word\t%s_grp\n", g_asmLabel.c_str());
std::fprintf(g_outputFile, "\n");
// track pointers
for (int i = 1; i <= trackCount; i++)
std::fprintf(g_outputFile, "\t.word\t%s_%u\n", g_asmLabel.c_str(), i);
std::fprintf(g_outputFile, "\n\t.end\n");
}