From 8237e29a164211eb2ec4cd161eb4183cc1947fee Mon Sep 17 00:00:00 2001 From: Cheng Hann Gan Date: Thu, 9 Sep 2021 19:22:48 -0400 Subject: Defined more in-dungeon structs and enums (#53) * Defined DungeonEntity * Rename EntityType enums * Revert EntityType rename * Defined more in-dungeon structs and enums * Added more dungeon global structs/enums * Prefixed dungeonGlobalData with g * Fixed compile errors * Removed some CRLFs * Fixed compile after merge * Revert Makefile * Rename DungeonEntityData.entityType Co-authored-by: Seth Barberee * Renamed symbols per PR comments Co-authored-by: Cheng Hann Gan Co-authored-by: Seth Barberee --- tools/mid2agb/.gitignore | 2 +- tools/mid2agb/agb.cpp | 1094 +++++++++++++------------- tools/mid2agb/error.cpp | 72 +- tools/mid2agb/main.cpp | 466 +++++------ tools/mid2agb/midi.cpp | 1928 +++++++++++++++++++++++----------------------- tools/mid2agb/tables.cpp | 572 +++++++------- 6 files changed, 2067 insertions(+), 2067 deletions(-) (limited to 'tools/mid2agb') diff --git a/tools/mid2agb/.gitignore b/tools/mid2agb/.gitignore index e5cd12e..0e7264c 100644 --- a/tools/mid2agb/.gitignore +++ b/tools/mid2agb/.gitignore @@ -1 +1 @@ -mid2agb +mid2agb diff --git a/tools/mid2agb/agb.cpp b/tools/mid2agb/agb.cpp index 9c9d030..d4d79f1 100644 --- a/tools/mid2agb/agb.cpp +++ b/tools/mid2agb/agb.cpp @@ -1,547 +1,547 @@ -// 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 -#include -#include -#include -#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& 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"); -} +// 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 +#include +#include +#include +#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& 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"); +} diff --git a/tools/mid2agb/error.cpp b/tools/mid2agb/error.cpp index c4a1184..13e38ff 100644 --- a/tools/mid2agb/error.cpp +++ b/tools/mid2agb/error.cpp @@ -1,36 +1,36 @@ -// 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 -#include -#include - -// Reports an error diagnostic and terminates the program. -[[noreturn]] void RaiseError(const char* format, ...) -{ - const int bufferSize = 1024; - char buffer[bufferSize]; - std::va_list args; - va_start(args, format); - std::vsnprintf(buffer, bufferSize, format, args); - std::fprintf(stderr, "error: %s\n", buffer); - va_end(args); - std::exit(1); -} +// 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 +#include +#include + +// Reports an error diagnostic and terminates the program. +[[noreturn]] void RaiseError(const char* format, ...) +{ + const int bufferSize = 1024; + char buffer[bufferSize]; + std::va_list args; + va_start(args, format); + std::vsnprintf(buffer, bufferSize, format, args); + std::fprintf(stderr, "error: %s\n", buffer); + va_end(args); + std::exit(1); +} diff --git a/tools/mid2agb/main.cpp b/tools/mid2agb/main.cpp index d1f1960..ea2b294 100644 --- a/tools/mid2agb/main.cpp +++ b/tools/mid2agb/main.cpp @@ -1,233 +1,233 @@ -// 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 -#include -#include -#include -#include -#include -#include -#include "main.h" -#include "error.h" -#include "midi.h" -#include "agb.h" - -FILE* g_inputFile = nullptr; -FILE* g_outputFile = nullptr; - -std::string g_asmLabel; -int g_masterVolume = 127; -int g_voiceGroup = 0; -int g_priority = 0; -int g_reverb = -1; -int g_clocksPerBeat = 1; -bool g_exactGateTime = false; -bool g_compressionEnabled = true; - -[[noreturn]] static void PrintUsage() -{ - std::printf( - "Usage: MID2AGB name [options]\n" - "\n" - " input_file filename(.mid) of MIDI file\n" - " output_file filename(.s) for AGB file (default:input_file)\n" - "\n" - "options -L??? label for assembler (default:output_file)\n" - " -V??? master volume (default:127)\n" - " -G??? voice group number (default:0)\n" - " -P??? priority (default:0)\n" - " -R??? reverb (default:off)\n" - " -X 48 clocks/beat (default:24 clocks/beat)\n" - " -E exact gate-time\n" - " -N no compression\n" - ); - std::exit(1); -} - -static std::string StripExtension(std::string s) -{ - std::size_t pos = s.find_last_of('.'); - - if (pos > 0 && pos != std::string::npos) - { - s = s.substr(0, pos); - } - - return s; -} - -static std::string GetExtension(std::string s) -{ - std::size_t pos = s.find_last_of('.'); - - if (pos > 0 && pos != std::string::npos) - { - return s.substr(pos + 1); - } - - return ""; -} - -static std::string BaseName(std::string s) -{ - std::size_t posAfterSlash = s.find_last_of("/\\"); - - if (posAfterSlash == std::string::npos) - posAfterSlash = 0; - else - posAfterSlash++; - - std::size_t dotPos = s.find_first_of('.', posAfterSlash); - if (dotPos > posAfterSlash && dotPos != std::string::npos) - s = s.substr(posAfterSlash, dotPos - posAfterSlash); - - return s; -} - -static const char *GetArgument(int argc, char **argv, int& index) -{ - assert(index >= 0 && index < argc); - - const char *option = argv[index]; - - assert(option != nullptr); - assert(option[0] == '-'); - - // If there is text following the letter, return that. - if (std::strlen(option) >= 3) - return option + 2; - - // Otherwise, try to get the next arg. - if (index + 1 < argc) - { - index++; - return argv[index]; - } - else - { - return nullptr; - } -} - -int main(int argc, char** argv) -{ - std::string inputFilename; - std::string outputFilename; - - for (int i = 1; i < argc; i++) - { - const char *option = argv[i]; - - if (option[0] == '-' && option[1] != '\0') - { - const char *arg; - - switch (std::toupper(option[1])) - { - case 'E': - g_exactGateTime = true; - break; - case 'G': - arg = GetArgument(argc, argv, i); - if (arg == nullptr) - PrintUsage(); - g_voiceGroup = std::stoi(arg); - break; - case 'L': - arg = GetArgument(argc, argv, i); - if (arg == nullptr) - PrintUsage(); - g_asmLabel = arg; - break; - case 'N': - g_compressionEnabled = false; - break; - case 'P': - arg = GetArgument(argc, argv, i); - if (arg == nullptr) - PrintUsage(); - g_priority = std::stoi(arg); - break; - case 'R': - arg = GetArgument(argc, argv, i); - if (arg == nullptr) - PrintUsage(); - g_reverb = std::stoi(arg); - break; - case 'V': - arg = GetArgument(argc, argv, i); - if (arg == nullptr) - PrintUsage(); - g_masterVolume = std::stoi(arg); - break; - case 'X': - g_clocksPerBeat = 2; - break; - default: - PrintUsage(); - } - } - else - { - if (inputFilename.empty()) - inputFilename = argv[i]; - else if (outputFilename.empty()) - outputFilename = argv[i]; - else - PrintUsage(); - } - } - - if (inputFilename.empty()) - PrintUsage(); - - if (GetExtension(inputFilename) != "mid") - RaiseError("input filename extension is not \"mid\""); - - if (outputFilename.empty()) - outputFilename = StripExtension(inputFilename) + ".s"; - - if (GetExtension(outputFilename) != "s") - RaiseError("output filename extension is not \"s\""); - - if (g_asmLabel.empty()) - g_asmLabel = BaseName(outputFilename); - - g_inputFile = std::fopen(inputFilename.c_str(), "rb"); - - if (g_inputFile == nullptr) - RaiseError("failed to open \"%s\" for reading", inputFilename.c_str()); - - g_outputFile = std::fopen(outputFilename.c_str(), "w"); - - if (g_outputFile == nullptr) - RaiseError("failed to open \"%s\" for writing", outputFilename.c_str()); - - ReadMidiFileHeader(); - PrintAgbHeader(); - ReadMidiTracks(); - PrintAgbFooter(); - - std::fclose(g_inputFile); - std::fclose(g_outputFile); - - return 0; -} +// 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 +#include +#include +#include +#include +#include +#include +#include "main.h" +#include "error.h" +#include "midi.h" +#include "agb.h" + +FILE* g_inputFile = nullptr; +FILE* g_outputFile = nullptr; + +std::string g_asmLabel; +int g_masterVolume = 127; +int g_voiceGroup = 0; +int g_priority = 0; +int g_reverb = -1; +int g_clocksPerBeat = 1; +bool g_exactGateTime = false; +bool g_compressionEnabled = true; + +[[noreturn]] static void PrintUsage() +{ + std::printf( + "Usage: MID2AGB name [options]\n" + "\n" + " input_file filename(.mid) of MIDI file\n" + " output_file filename(.s) for AGB file (default:input_file)\n" + "\n" + "options -L??? label for assembler (default:output_file)\n" + " -V??? master volume (default:127)\n" + " -G??? voice group number (default:0)\n" + " -P??? priority (default:0)\n" + " -R??? reverb (default:off)\n" + " -X 48 clocks/beat (default:24 clocks/beat)\n" + " -E exact gate-time\n" + " -N no compression\n" + ); + std::exit(1); +} + +static std::string StripExtension(std::string s) +{ + std::size_t pos = s.find_last_of('.'); + + if (pos > 0 && pos != std::string::npos) + { + s = s.substr(0, pos); + } + + return s; +} + +static std::string GetExtension(std::string s) +{ + std::size_t pos = s.find_last_of('.'); + + if (pos > 0 && pos != std::string::npos) + { + return s.substr(pos + 1); + } + + return ""; +} + +static std::string BaseName(std::string s) +{ + std::size_t posAfterSlash = s.find_last_of("/\\"); + + if (posAfterSlash == std::string::npos) + posAfterSlash = 0; + else + posAfterSlash++; + + std::size_t dotPos = s.find_first_of('.', posAfterSlash); + if (dotPos > posAfterSlash && dotPos != std::string::npos) + s = s.substr(posAfterSlash, dotPos - posAfterSlash); + + return s; +} + +static const char *GetArgument(int argc, char **argv, int& index) +{ + assert(index >= 0 && index < argc); + + const char *option = argv[index]; + + assert(option != nullptr); + assert(option[0] == '-'); + + // If there is text following the letter, return that. + if (std::strlen(option) >= 3) + return option + 2; + + // Otherwise, try to get the next arg. + if (index + 1 < argc) + { + index++; + return argv[index]; + } + else + { + return nullptr; + } +} + +int main(int argc, char** argv) +{ + std::string inputFilename; + std::string outputFilename; + + for (int i = 1; i < argc; i++) + { + const char *option = argv[i]; + + if (option[0] == '-' && option[1] != '\0') + { + const char *arg; + + switch (std::toupper(option[1])) + { + case 'E': + g_exactGateTime = true; + break; + case 'G': + arg = GetArgument(argc, argv, i); + if (arg == nullptr) + PrintUsage(); + g_voiceGroup = std::stoi(arg); + break; + case 'L': + arg = GetArgument(argc, argv, i); + if (arg == nullptr) + PrintUsage(); + g_asmLabel = arg; + break; + case 'N': + g_compressionEnabled = false; + break; + case 'P': + arg = GetArgument(argc, argv, i); + if (arg == nullptr) + PrintUsage(); + g_priority = std::stoi(arg); + break; + case 'R': + arg = GetArgument(argc, argv, i); + if (arg == nullptr) + PrintUsage(); + g_reverb = std::stoi(arg); + break; + case 'V': + arg = GetArgument(argc, argv, i); + if (arg == nullptr) + PrintUsage(); + g_masterVolume = std::stoi(arg); + break; + case 'X': + g_clocksPerBeat = 2; + break; + default: + PrintUsage(); + } + } + else + { + if (inputFilename.empty()) + inputFilename = argv[i]; + else if (outputFilename.empty()) + outputFilename = argv[i]; + else + PrintUsage(); + } + } + + if (inputFilename.empty()) + PrintUsage(); + + if (GetExtension(inputFilename) != "mid") + RaiseError("input filename extension is not \"mid\""); + + if (outputFilename.empty()) + outputFilename = StripExtension(inputFilename) + ".s"; + + if (GetExtension(outputFilename) != "s") + RaiseError("output filename extension is not \"s\""); + + if (g_asmLabel.empty()) + g_asmLabel = BaseName(outputFilename); + + g_inputFile = std::fopen(inputFilename.c_str(), "rb"); + + if (g_inputFile == nullptr) + RaiseError("failed to open \"%s\" for reading", inputFilename.c_str()); + + g_outputFile = std::fopen(outputFilename.c_str(), "w"); + + if (g_outputFile == nullptr) + RaiseError("failed to open \"%s\" for writing", outputFilename.c_str()); + + ReadMidiFileHeader(); + PrintAgbHeader(); + ReadMidiTracks(); + PrintAgbFooter(); + + std::fclose(g_inputFile); + std::fclose(g_outputFile); + + return 0; +} diff --git a/tools/mid2agb/midi.cpp b/tools/mid2agb/midi.cpp index 2adbd52..fa7d9ce 100644 --- a/tools/mid2agb/midi.cpp +++ b/tools/mid2agb/midi.cpp @@ -1,964 +1,964 @@ -// 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 -#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; - -static long s_trackDataStart; -static std::vector s_seqEvents; -static std::vector s_trackEvents; -static std::int32_t s_absoluteTime; -static int s_blockCount = 0; -static int s_minNote; -static int s_maxNote; -static int s_runningStatus; - -void Seek(long offset) -{ - if (std::fseek(g_inputFile, offset, SEEK_SET) != 0) - RaiseError("failed to seek to %l", 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; -} - -void ReadMidiFileHeader() -{ - Seek(0); - - if (ReadSignature() != "MThd") - RaiseError("MIDI file header signature didn't match \"MThd\""); - - std::uint32_t headerLength = ReadInt32(); - - if (headerLength != 6) - RaiseError("MIDI file header length isn't 6"); - - std::uint16_t midiFormat = ReadInt16(); - - if (midiFormat >= 2) - RaiseError("unsupported MIDI format (%u)", midiFormat); - - g_midiFormat = (MidiFormat)midiFormat; - g_midiTrackCount = ReadInt16(); - g_midiTimeDiv = ReadInt16(); - - if (g_midiTimeDiv < 0) - RaiseError("unsupported MIDI time division (%d)", g_midiTimeDiv); -} - -long ReadMidiTrackHeader(long offset) -{ - Seek(offset); - - if (ReadSignature() != "MTrk") - RaiseError("MIDI track header signature didn't match \"MTrk\""); - - long size = ReadInt32(); - - s_trackDataStart = std::ftell(g_inputFile); - - return size + 8; -} - -void StartTrack() -{ - Seek(s_trackDataStart); - s_absoluteTime = 0; - s_runningStatus = 0; -} - -void SkipEventData() -{ - Skip(ReadVLQ()); -} - -void DetermineEventCategory(MidiEventCategory& category, int& typeChan, int& size) -{ - typeChan = ReadInt8(); - - if (typeChan < 0x80) - { - // If data byte was found, use the running status. - ungetc(typeChan, g_inputFile); - typeChan = s_runningStatus; - } - - if (typeChan == 0xFF) - { - category = MidiEventCategory::Meta; - size = 0; - s_runningStatus = 0; - } - else if (typeChan >= 0xF0) - { - category = MidiEventCategory::SysEx; - size = 0; - s_runningStatus = 0; - } - else if (typeChan >= 0x80) - { - category = MidiEventCategory::Control; - - switch (typeChan >> 4) - { - case 0xC: - case 0xD: - size = 1; - break; - default: - size = 2; - break; - } - s_runningStatus = typeChan; - } - else - { - category = MidiEventCategory::Invalid; - } -} - -void MakeBlockEvent(Event& event, EventType type) -{ - event.type = type; - event.param1 = s_blockCount++; - event.param2 = 0; -} - -std::string ReadEventText() -{ - char buffer[2]; - std::uint32_t length = ReadVLQ(); - - if (length <= 2) - { - if (fread(buffer, length, 1, g_inputFile) != 1) - RaiseError("failed to read event text"); - } - else - { - Skip(length); - length = 0; - } - - return std::string(buffer, length); -} - -bool ReadSeqEvent(Event& event) -{ - s_absoluteTime += ReadVLQ(); - event.time = s_absoluteTime; - - MidiEventCategory category; - int typeChan; - int size; - - DetermineEventCategory(category, typeChan, size); - - if (category == MidiEventCategory::Control) - { - Skip(size); - return false; - } - - if (category == MidiEventCategory::SysEx) - { - SkipEventData(); - return false; - } - - if (category == MidiEventCategory::Invalid) - RaiseError("invalid event"); - - // meta event - int metaEventType = ReadInt8(); - - if (metaEventType >= 1 && metaEventType <= 7) - { - // text event - std::string text = ReadEventText(); - - if (text == "[") - MakeBlockEvent(event, EventType::LoopBegin); - else if (text == "][") - MakeBlockEvent(event, EventType::LoopEndBegin); - else if (text == "]") - MakeBlockEvent(event, EventType::LoopEnd); - else if (text == ":") - MakeBlockEvent(event, EventType::Label); - else - return false; - } - else - { - switch (metaEventType) - { - case 0x2F: // end of track - SkipEventData(); - event.type = EventType::EndOfTrack; - event.param1 = 0; - event.param2 = 0; - break; - case 0x51: // tempo - if (ReadVLQ() != 3) - RaiseError("invalid tempo size"); - - event.type = EventType::Tempo; - event.param1 = 0; - event.param2 = ReadInt24(); - break; - case 0x58: // time signature - { - if (ReadVLQ() != 4) - RaiseError("invalid time signature size"); - - int numerator = ReadInt8(); - int denominatorExponent = ReadInt8(); - - if (denominatorExponent >= 16) - RaiseError("invalid time signature denominator"); - - Skip(2); // ignore other values - - int clockTicks = 96 * numerator * g_clocksPerBeat; - int denominator = 1 << denominatorExponent; - int timeSig = clockTicks / denominator; - - if (timeSig <= 0 || timeSig >= 0x10000) - RaiseError("invalid time signature"); - - event.type = EventType::TimeSignature; - event.param1 = 0; - event.param2 = timeSig; - break; - } - default: - SkipEventData(); - return false; - } - } - - return true; -} - -void ReadSeqEvents() -{ - StartTrack(); - - for (;;) - { - Event event = {}; - - if (ReadSeqEvent(event)) - { - s_seqEvents.push_back(event); - - if (event.type == EventType::EndOfTrack) - return; - } - } -} - -bool CheckNoteEnd(Event& event) -{ - event.param2 += ReadVLQ(); - - MidiEventCategory category; - int typeChan; - int size; - - DetermineEventCategory(category, typeChan, size); - - if (category == MidiEventCategory::Control) - { - int chan = typeChan & 0xF; - - if (chan != g_midiChan) - { - Skip(size); - return false; - } - - switch (typeChan & 0xF0) - { - case 0x80: // note off - { - int note = ReadInt8(); - ReadInt8(); // ignore velocity - if (note == event.note) - return true; - break; - } - case 0x90: // note on - { - int note = ReadInt8(); - int velocity = ReadInt8(); - if (velocity == 0 && note == event.note) - return true; - break; - } - default: - Skip(size); - break; - } - - return false; - } - - if (category == MidiEventCategory::SysEx) - { - SkipEventData(); - return false; - } - - if (category == MidiEventCategory::Meta) - { - int metaEventType = ReadInt8(); - SkipEventData(); - - if (metaEventType == 0x2F) - RaiseError("note doesn't end"); - - return false; - } - - RaiseError("invalid event"); -} - -void FindNoteEnd(Event& event) -{ - // Save the current file position and running status - // which get modified by CheckNoteEnd. - long startPos = ftell(g_inputFile); - int savedRunningStatus = s_runningStatus; - - event.param2 = 0; - - while (!CheckNoteEnd(event)) - ; - - Seek(startPos); - s_runningStatus = savedRunningStatus; -} - -bool ReadTrackEvent(Event& event) -{ - s_absoluteTime += ReadVLQ(); - event.time = s_absoluteTime; - - MidiEventCategory category; - int typeChan; - int size; - - DetermineEventCategory(category, typeChan, size); - - if (category == MidiEventCategory::Control) - { - int chan = typeChan & 0xF; - - if (chan != g_midiChan) - { - Skip(size); - return false; - } - - switch (typeChan & 0xF0) - { - case 0x90: // note on - { - int note = ReadInt8(); - int velocity = ReadInt8(); - - if (velocity != 0) - { - event.type = EventType::Note; - event.note = note; - event.param1 = velocity; - FindNoteEnd(event); - if (event.param2 > 0) - { - if (note < s_minNote) - s_minNote = note; - if (note > s_maxNote) - s_maxNote = note; - } - } - break; - } - case 0xB0: // controller event - event.type = EventType::Controller; - event.param1 = ReadInt8(); // controller index - event.param2 = ReadInt8(); // value - break; - case 0xC0: // instrument change - event.type = EventType::InstrumentChange; - event.param1 = ReadInt8(); // instrument - event.param2 = 0; - break; - case 0xE0: // pitch bend - event.type = EventType::PitchBend; - event.param1 = ReadInt8(); - event.param2 = ReadInt8(); - break; - default: - Skip(size); - return false; - } - - return true; - } - - if (category == MidiEventCategory::SysEx) - { - SkipEventData(); - return false; - } - - if (category == MidiEventCategory::Meta) - { - int metaEventType = ReadInt8(); - SkipEventData(); - - if (metaEventType == 0x2F) - { - event.type = EventType::EndOfTrack; - event.param1 = 0; - event.param2 = 0; - return true; - } - - return false; - } - - RaiseError("invalid event"); -} - -void ReadTrackEvents() -{ - StartTrack(); - - s_trackEvents.clear(); - - s_minNote = 0xFF; - s_maxNote = 0; - - for (;;) - { - Event event = {}; - - if (ReadTrackEvent(event)) - { - s_trackEvents.push_back(event); - - if (event.type == EventType::EndOfTrack) - return; - } - } -} - -bool EventCompare(const Event& event1, const Event& event2) -{ - if (event1.time < event2.time) - return true; - - if (event1.time > event2.time) - return false; - - unsigned event1Type = (unsigned)event1.type; - unsigned event2Type = (unsigned)event2.type; - - if (event1.type == EventType::Note) - event1Type += event1.note; - - if (event2.type == EventType::Note) - event2Type += event2.note; - - if (event1Type < event2Type) - return true; - - if (event1Type > event2Type) - return false; - - if (event1.type == EventType::EndOfTie) - { - if (event1.note < event2.note) - return true; - - if (event1.note > event2.note) - return false; - } - - return false; -} - -std::unique_ptr> MergeEvents() -{ - std::unique_ptr> events(new std::vector()); - - unsigned trackEventPos = 0; - unsigned seqEventPos = 0; - - while (s_trackEvents[trackEventPos].type != EventType::EndOfTrack - && s_seqEvents[seqEventPos].type != EventType::EndOfTrack) - { - if (EventCompare(s_trackEvents[trackEventPos], s_seqEvents[seqEventPos])) - events->push_back(s_trackEvents[trackEventPos++]); - else - events->push_back(s_seqEvents[seqEventPos++]); - } - - while (s_trackEvents[trackEventPos].type != EventType::EndOfTrack) - events->push_back(s_trackEvents[trackEventPos++]); - - while (s_seqEvents[seqEventPos].type != EventType::EndOfTrack) - events->push_back(s_seqEvents[seqEventPos++]); - - // Push the EndOfTrack event with the larger time. - if (EventCompare(s_trackEvents[trackEventPos], s_seqEvents[seqEventPos])) - events->push_back(s_seqEvents[seqEventPos]); - else - events->push_back(s_trackEvents[trackEventPos]); - - return events; -} - -void ConvertTimes(std::vector& events) -{ - for (Event& event : events) - { - event.time = (24 * g_clocksPerBeat * event.time) / g_midiTimeDiv; - - if (event.type == EventType::Note) - { - event.param1 = g_noteVelocityLUT[event.param1]; - - std::uint32_t duration = (24 * g_clocksPerBeat * event.param2) / g_midiTimeDiv; - - if (duration == 0) - duration = 1; - - if (!g_exactGateTime && duration < 96) - duration = g_noteDurationLUT[duration]; - - event.param2 = duration; - } - } -} - -std::unique_ptr> InsertTimingEvents(std::vector& inEvents) -{ - std::unique_ptr> outEvents(new std::vector()); - - Event timingEvent = {}; - timingEvent.time = 0; - timingEvent.type = EventType::TimeSignature; - timingEvent.param2 = 96 * g_clocksPerBeat; - - for (const Event& event : inEvents) - { - while (EventCompare(timingEvent, event)) - { - outEvents->push_back(timingEvent); - timingEvent.time += timingEvent.param2; - } - - if (event.type == EventType::TimeSignature) - { - if (g_agbTrack == 1 && event.param2 != timingEvent.param2) - { - Event originalTimingEvent = event; - originalTimingEvent.type = EventType::OriginalTimeSignature; - outEvents->push_back(originalTimingEvent); - } - timingEvent.param2 = event.param2; - timingEvent.time = event.time + timingEvent.param2; - } - - outEvents->push_back(event); - } - - return outEvents; -} - -std::unique_ptr> SplitTime(std::vector& inEvents) -{ - std::unique_ptr> outEvents(new std::vector()); - - std::int32_t time = 0; - - for (const Event& event : inEvents) - { - std::int32_t diff = event.time - time; - - if (diff > 96) - { - int wholeNoteCount = (diff - 1) / 96; - diff -= 96 * wholeNoteCount; - - for (int i = 0; i < wholeNoteCount; i++) - { - time += 96; - Event timeSplitEvent = {}; - timeSplitEvent.time = time; - timeSplitEvent.type = EventType::TimeSplit; - outEvents->push_back(timeSplitEvent); - } - } - - std::int32_t lutValue = g_noteDurationLUT[diff]; - - if (lutValue != diff) - { - Event timeSplitEvent = {}; - timeSplitEvent.time = time + lutValue; - timeSplitEvent.type = EventType::TimeSplit; - outEvents->push_back(timeSplitEvent); - } - - time = event.time; - - outEvents->push_back(event); - } - - return outEvents; -} - -std::unique_ptr> CreateTies(std::vector& inEvents) -{ - std::unique_ptr> outEvents(new std::vector()); - - for (const Event& event : inEvents) - { - if (event.type == EventType::Note && event.param2 > 96) - { - Event tieEvent = event; - tieEvent.param2 = -1; - outEvents->push_back(tieEvent); - - Event eotEvent = {}; - eotEvent.time = event.time + event.param2; - eotEvent.type = EventType::EndOfTie; - eotEvent.note = event.note; - outEvents->push_back(eotEvent); - } - else - { - outEvents->push_back(event); - } - } - - return outEvents; -} - -void CalculateWaits(std::vector& events) -{ - g_initialWait = events[0].time; - int wholeNoteCount = 0; - - for (unsigned i = 0; i < events.size() && events[i].type != EventType::EndOfTrack; i++) - { - events[i].time = events[i + 1].time - events[i].time; - - if (events[i].type == EventType::TimeSignature) - { - events[i].type = EventType::WholeNoteMark; - events[i].param2 = wholeNoteCount++; - } - } -} - -int CalculateCompressionScore(std::vector& events, int index) -{ - int score = 0; - std::uint8_t lastParam1 = events[index].param1; - std::uint8_t lastVelocity = 0x80u; - EventType lastType = events[index].type; - std::int32_t lastDuration = 0x80000000; - std::uint8_t lastNote = 0x40u; - - if (events[index].time > 0) - score++; - - for (int i = index + 1; !IsPatternBoundary(events[i].type); i++) - { - if (events[i].type == EventType::Note) - { - int val = 0; - - if (events[i].note != lastNote) - { - val++; - lastNote = events[i].note; - } - - if (events[i].param1 != lastVelocity) - { - val++; - lastVelocity = events[i].param1; - } - - std::int32_t duration = events[i].param2; - - if (g_noteDurationLUT[duration] != lastDuration) - { - val++; - lastDuration = g_noteDurationLUT[duration]; - } - - if (duration != lastDuration) - val++; - - if (val == 0) - val = 1; - - score += val; - } - else - { - lastDuration = 0x80000000; - - if (events[i].type == lastType) - { - if ((lastType != EventType::Controller && (int)lastType != 0x25 && lastType != EventType::EndOfTie) || events[i].param1 == lastParam1) - { - score++; - } - else - { - score += 2; - } - } - else - { - score += 2; - } - } - - lastParam1 = events[i].param1; - lastType = events[i].type; - - if (events[i].time) - score++; - } - - return score; -} - -bool IsCompressionMatch(std::vector& events, int index1, int index2) -{ - if (events[index1].type != events[index2].type || - events[index1].note != events[index2].note || - events[index1].param1 != events[index2].param1 || - events[index1].time != events[index2].time) - return false; - - index1++; - index2++; - - do - { - if (events[index1] != events[index2]) - return false; - - index1++; - index2++; - } while (!IsPatternBoundary(events[index1].type)); - - return IsPatternBoundary(events[index2].type); -} - -void CompressWholeNote(std::vector& events, int index) -{ - for (int j = index + 1; events[j].type != EventType::EndOfTrack; j++) - { - while (events[j].type != EventType::WholeNoteMark) - { - j++; - - if (events[j].type == EventType::EndOfTrack) - return; - } - - if (IsCompressionMatch(events, index, j)) - { - events[j].type = EventType::Pattern; - events[j].param2 = events[index].param2 & 0x7FFFFFFF; - events[index].param2 |= 0x80000000; - } - } -} - -void Compress(std::vector& events) -{ - for (int i = 0; events[i].type != EventType::EndOfTrack; i++) - { - while (events[i].type != EventType::WholeNoteMark) - { - i++; - - if (events[i].type == EventType::EndOfTrack) - return; - } - - if (CalculateCompressionScore(events, i) >= 6) - { - CompressWholeNote(events, i); - } - } -} - -void ReadMidiTracks() -{ - long trackHeaderStart = 14; - - ReadMidiTrackHeader(trackHeaderStart); - ReadSeqEvents(); - - g_agbTrack = 1; - - for (int midiTrack = 0; midiTrack < g_midiTrackCount; midiTrack++) - { - trackHeaderStart += ReadMidiTrackHeader(trackHeaderStart); - - for (g_midiChan = 0; g_midiChan < 16; g_midiChan++) - { - ReadTrackEvents(); - - if (s_minNote != 0xFF) - { -#ifdef DEBUG - printf("Track%d = Midi-Ch.%d\n", g_agbTrack, g_midiChan + 1); -#endif - - std::unique_ptr> events(MergeEvents()); - - // We don't need TEMPO in anything but track 1. - if (g_agbTrack == 1) - { - auto it = std::remove_if(s_seqEvents.begin(), s_seqEvents.end(), [](const Event& event) { return event.type == EventType::Tempo; }); - s_seqEvents.erase(it, s_seqEvents.end()); - } - - ConvertTimes(*events); - events = InsertTimingEvents(*events); - events = CreateTies(*events); - std::stable_sort(events->begin(), events->end(), EventCompare); - events = SplitTime(*events); - CalculateWaits(*events); - - if (g_compressionEnabled) - Compress(*events); - - PrintAgbTrack(*events); - - g_agbTrack++; - } - } - } -} +// 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 +#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; + +static long s_trackDataStart; +static std::vector s_seqEvents; +static std::vector s_trackEvents; +static std::int32_t s_absoluteTime; +static int s_blockCount = 0; +static int s_minNote; +static int s_maxNote; +static int s_runningStatus; + +void Seek(long offset) +{ + if (std::fseek(g_inputFile, offset, SEEK_SET) != 0) + RaiseError("failed to seek to %l", 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; +} + +void ReadMidiFileHeader() +{ + Seek(0); + + if (ReadSignature() != "MThd") + RaiseError("MIDI file header signature didn't match \"MThd\""); + + std::uint32_t headerLength = ReadInt32(); + + if (headerLength != 6) + RaiseError("MIDI file header length isn't 6"); + + std::uint16_t midiFormat = ReadInt16(); + + if (midiFormat >= 2) + RaiseError("unsupported MIDI format (%u)", midiFormat); + + g_midiFormat = (MidiFormat)midiFormat; + g_midiTrackCount = ReadInt16(); + g_midiTimeDiv = ReadInt16(); + + if (g_midiTimeDiv < 0) + RaiseError("unsupported MIDI time division (%d)", g_midiTimeDiv); +} + +long ReadMidiTrackHeader(long offset) +{ + Seek(offset); + + if (ReadSignature() != "MTrk") + RaiseError("MIDI track header signature didn't match \"MTrk\""); + + long size = ReadInt32(); + + s_trackDataStart = std::ftell(g_inputFile); + + return size + 8; +} + +void StartTrack() +{ + Seek(s_trackDataStart); + s_absoluteTime = 0; + s_runningStatus = 0; +} + +void SkipEventData() +{ + Skip(ReadVLQ()); +} + +void DetermineEventCategory(MidiEventCategory& category, int& typeChan, int& size) +{ + typeChan = ReadInt8(); + + if (typeChan < 0x80) + { + // If data byte was found, use the running status. + ungetc(typeChan, g_inputFile); + typeChan = s_runningStatus; + } + + if (typeChan == 0xFF) + { + category = MidiEventCategory::Meta; + size = 0; + s_runningStatus = 0; + } + else if (typeChan >= 0xF0) + { + category = MidiEventCategory::SysEx; + size = 0; + s_runningStatus = 0; + } + else if (typeChan >= 0x80) + { + category = MidiEventCategory::Control; + + switch (typeChan >> 4) + { + case 0xC: + case 0xD: + size = 1; + break; + default: + size = 2; + break; + } + s_runningStatus = typeChan; + } + else + { + category = MidiEventCategory::Invalid; + } +} + +void MakeBlockEvent(Event& event, EventType type) +{ + event.type = type; + event.param1 = s_blockCount++; + event.param2 = 0; +} + +std::string ReadEventText() +{ + char buffer[2]; + std::uint32_t length = ReadVLQ(); + + if (length <= 2) + { + if (fread(buffer, length, 1, g_inputFile) != 1) + RaiseError("failed to read event text"); + } + else + { + Skip(length); + length = 0; + } + + return std::string(buffer, length); +} + +bool ReadSeqEvent(Event& event) +{ + s_absoluteTime += ReadVLQ(); + event.time = s_absoluteTime; + + MidiEventCategory category; + int typeChan; + int size; + + DetermineEventCategory(category, typeChan, size); + + if (category == MidiEventCategory::Control) + { + Skip(size); + return false; + } + + if (category == MidiEventCategory::SysEx) + { + SkipEventData(); + return false; + } + + if (category == MidiEventCategory::Invalid) + RaiseError("invalid event"); + + // meta event + int metaEventType = ReadInt8(); + + if (metaEventType >= 1 && metaEventType <= 7) + { + // text event + std::string text = ReadEventText(); + + if (text == "[") + MakeBlockEvent(event, EventType::LoopBegin); + else if (text == "][") + MakeBlockEvent(event, EventType::LoopEndBegin); + else if (text == "]") + MakeBlockEvent(event, EventType::LoopEnd); + else if (text == ":") + MakeBlockEvent(event, EventType::Label); + else + return false; + } + else + { + switch (metaEventType) + { + case 0x2F: // end of track + SkipEventData(); + event.type = EventType::EndOfTrack; + event.param1 = 0; + event.param2 = 0; + break; + case 0x51: // tempo + if (ReadVLQ() != 3) + RaiseError("invalid tempo size"); + + event.type = EventType::Tempo; + event.param1 = 0; + event.param2 = ReadInt24(); + break; + case 0x58: // time signature + { + if (ReadVLQ() != 4) + RaiseError("invalid time signature size"); + + int numerator = ReadInt8(); + int denominatorExponent = ReadInt8(); + + if (denominatorExponent >= 16) + RaiseError("invalid time signature denominator"); + + Skip(2); // ignore other values + + int clockTicks = 96 * numerator * g_clocksPerBeat; + int denominator = 1 << denominatorExponent; + int timeSig = clockTicks / denominator; + + if (timeSig <= 0 || timeSig >= 0x10000) + RaiseError("invalid time signature"); + + event.type = EventType::TimeSignature; + event.param1 = 0; + event.param2 = timeSig; + break; + } + default: + SkipEventData(); + return false; + } + } + + return true; +} + +void ReadSeqEvents() +{ + StartTrack(); + + for (;;) + { + Event event = {}; + + if (ReadSeqEvent(event)) + { + s_seqEvents.push_back(event); + + if (event.type == EventType::EndOfTrack) + return; + } + } +} + +bool CheckNoteEnd(Event& event) +{ + event.param2 += ReadVLQ(); + + MidiEventCategory category; + int typeChan; + int size; + + DetermineEventCategory(category, typeChan, size); + + if (category == MidiEventCategory::Control) + { + int chan = typeChan & 0xF; + + if (chan != g_midiChan) + { + Skip(size); + return false; + } + + switch (typeChan & 0xF0) + { + case 0x80: // note off + { + int note = ReadInt8(); + ReadInt8(); // ignore velocity + if (note == event.note) + return true; + break; + } + case 0x90: // note on + { + int note = ReadInt8(); + int velocity = ReadInt8(); + if (velocity == 0 && note == event.note) + return true; + break; + } + default: + Skip(size); + break; + } + + return false; + } + + if (category == MidiEventCategory::SysEx) + { + SkipEventData(); + return false; + } + + if (category == MidiEventCategory::Meta) + { + int metaEventType = ReadInt8(); + SkipEventData(); + + if (metaEventType == 0x2F) + RaiseError("note doesn't end"); + + return false; + } + + RaiseError("invalid event"); +} + +void FindNoteEnd(Event& event) +{ + // Save the current file position and running status + // which get modified by CheckNoteEnd. + long startPos = ftell(g_inputFile); + int savedRunningStatus = s_runningStatus; + + event.param2 = 0; + + while (!CheckNoteEnd(event)) + ; + + Seek(startPos); + s_runningStatus = savedRunningStatus; +} + +bool ReadTrackEvent(Event& event) +{ + s_absoluteTime += ReadVLQ(); + event.time = s_absoluteTime; + + MidiEventCategory category; + int typeChan; + int size; + + DetermineEventCategory(category, typeChan, size); + + if (category == MidiEventCategory::Control) + { + int chan = typeChan & 0xF; + + if (chan != g_midiChan) + { + Skip(size); + return false; + } + + switch (typeChan & 0xF0) + { + case 0x90: // note on + { + int note = ReadInt8(); + int velocity = ReadInt8(); + + if (velocity != 0) + { + event.type = EventType::Note; + event.note = note; + event.param1 = velocity; + FindNoteEnd(event); + if (event.param2 > 0) + { + if (note < s_minNote) + s_minNote = note; + if (note > s_maxNote) + s_maxNote = note; + } + } + break; + } + case 0xB0: // controller event + event.type = EventType::Controller; + event.param1 = ReadInt8(); // controller index + event.param2 = ReadInt8(); // value + break; + case 0xC0: // instrument change + event.type = EventType::InstrumentChange; + event.param1 = ReadInt8(); // instrument + event.param2 = 0; + break; + case 0xE0: // pitch bend + event.type = EventType::PitchBend; + event.param1 = ReadInt8(); + event.param2 = ReadInt8(); + break; + default: + Skip(size); + return false; + } + + return true; + } + + if (category == MidiEventCategory::SysEx) + { + SkipEventData(); + return false; + } + + if (category == MidiEventCategory::Meta) + { + int metaEventType = ReadInt8(); + SkipEventData(); + + if (metaEventType == 0x2F) + { + event.type = EventType::EndOfTrack; + event.param1 = 0; + event.param2 = 0; + return true; + } + + return false; + } + + RaiseError("invalid event"); +} + +void ReadTrackEvents() +{ + StartTrack(); + + s_trackEvents.clear(); + + s_minNote = 0xFF; + s_maxNote = 0; + + for (;;) + { + Event event = {}; + + if (ReadTrackEvent(event)) + { + s_trackEvents.push_back(event); + + if (event.type == EventType::EndOfTrack) + return; + } + } +} + +bool EventCompare(const Event& event1, const Event& event2) +{ + if (event1.time < event2.time) + return true; + + if (event1.time > event2.time) + return false; + + unsigned event1Type = (unsigned)event1.type; + unsigned event2Type = (unsigned)event2.type; + + if (event1.type == EventType::Note) + event1Type += event1.note; + + if (event2.type == EventType::Note) + event2Type += event2.note; + + if (event1Type < event2Type) + return true; + + if (event1Type > event2Type) + return false; + + if (event1.type == EventType::EndOfTie) + { + if (event1.note < event2.note) + return true; + + if (event1.note > event2.note) + return false; + } + + return false; +} + +std::unique_ptr> MergeEvents() +{ + std::unique_ptr> events(new std::vector()); + + unsigned trackEventPos = 0; + unsigned seqEventPos = 0; + + while (s_trackEvents[trackEventPos].type != EventType::EndOfTrack + && s_seqEvents[seqEventPos].type != EventType::EndOfTrack) + { + if (EventCompare(s_trackEvents[trackEventPos], s_seqEvents[seqEventPos])) + events->push_back(s_trackEvents[trackEventPos++]); + else + events->push_back(s_seqEvents[seqEventPos++]); + } + + while (s_trackEvents[trackEventPos].type != EventType::EndOfTrack) + events->push_back(s_trackEvents[trackEventPos++]); + + while (s_seqEvents[seqEventPos].type != EventType::EndOfTrack) + events->push_back(s_seqEvents[seqEventPos++]); + + // Push the EndOfTrack event with the larger time. + if (EventCompare(s_trackEvents[trackEventPos], s_seqEvents[seqEventPos])) + events->push_back(s_seqEvents[seqEventPos]); + else + events->push_back(s_trackEvents[trackEventPos]); + + return events; +} + +void ConvertTimes(std::vector& events) +{ + for (Event& event : events) + { + event.time = (24 * g_clocksPerBeat * event.time) / g_midiTimeDiv; + + if (event.type == EventType::Note) + { + event.param1 = g_noteVelocityLUT[event.param1]; + + std::uint32_t duration = (24 * g_clocksPerBeat * event.param2) / g_midiTimeDiv; + + if (duration == 0) + duration = 1; + + if (!g_exactGateTime && duration < 96) + duration = g_noteDurationLUT[duration]; + + event.param2 = duration; + } + } +} + +std::unique_ptr> InsertTimingEvents(std::vector& inEvents) +{ + std::unique_ptr> outEvents(new std::vector()); + + Event timingEvent = {}; + timingEvent.time = 0; + timingEvent.type = EventType::TimeSignature; + timingEvent.param2 = 96 * g_clocksPerBeat; + + for (const Event& event : inEvents) + { + while (EventCompare(timingEvent, event)) + { + outEvents->push_back(timingEvent); + timingEvent.time += timingEvent.param2; + } + + if (event.type == EventType::TimeSignature) + { + if (g_agbTrack == 1 && event.param2 != timingEvent.param2) + { + Event originalTimingEvent = event; + originalTimingEvent.type = EventType::OriginalTimeSignature; + outEvents->push_back(originalTimingEvent); + } + timingEvent.param2 = event.param2; + timingEvent.time = event.time + timingEvent.param2; + } + + outEvents->push_back(event); + } + + return outEvents; +} + +std::unique_ptr> SplitTime(std::vector& inEvents) +{ + std::unique_ptr> outEvents(new std::vector()); + + std::int32_t time = 0; + + for (const Event& event : inEvents) + { + std::int32_t diff = event.time - time; + + if (diff > 96) + { + int wholeNoteCount = (diff - 1) / 96; + diff -= 96 * wholeNoteCount; + + for (int i = 0; i < wholeNoteCount; i++) + { + time += 96; + Event timeSplitEvent = {}; + timeSplitEvent.time = time; + timeSplitEvent.type = EventType::TimeSplit; + outEvents->push_back(timeSplitEvent); + } + } + + std::int32_t lutValue = g_noteDurationLUT[diff]; + + if (lutValue != diff) + { + Event timeSplitEvent = {}; + timeSplitEvent.time = time + lutValue; + timeSplitEvent.type = EventType::TimeSplit; + outEvents->push_back(timeSplitEvent); + } + + time = event.time; + + outEvents->push_back(event); + } + + return outEvents; +} + +std::unique_ptr> CreateTies(std::vector& inEvents) +{ + std::unique_ptr> outEvents(new std::vector()); + + for (const Event& event : inEvents) + { + if (event.type == EventType::Note && event.param2 > 96) + { + Event tieEvent = event; + tieEvent.param2 = -1; + outEvents->push_back(tieEvent); + + Event eotEvent = {}; + eotEvent.time = event.time + event.param2; + eotEvent.type = EventType::EndOfTie; + eotEvent.note = event.note; + outEvents->push_back(eotEvent); + } + else + { + outEvents->push_back(event); + } + } + + return outEvents; +} + +void CalculateWaits(std::vector& events) +{ + g_initialWait = events[0].time; + int wholeNoteCount = 0; + + for (unsigned i = 0; i < events.size() && events[i].type != EventType::EndOfTrack; i++) + { + events[i].time = events[i + 1].time - events[i].time; + + if (events[i].type == EventType::TimeSignature) + { + events[i].type = EventType::WholeNoteMark; + events[i].param2 = wholeNoteCount++; + } + } +} + +int CalculateCompressionScore(std::vector& events, int index) +{ + int score = 0; + std::uint8_t lastParam1 = events[index].param1; + std::uint8_t lastVelocity = 0x80u; + EventType lastType = events[index].type; + std::int32_t lastDuration = 0x80000000; + std::uint8_t lastNote = 0x40u; + + if (events[index].time > 0) + score++; + + for (int i = index + 1; !IsPatternBoundary(events[i].type); i++) + { + if (events[i].type == EventType::Note) + { + int val = 0; + + if (events[i].note != lastNote) + { + val++; + lastNote = events[i].note; + } + + if (events[i].param1 != lastVelocity) + { + val++; + lastVelocity = events[i].param1; + } + + std::int32_t duration = events[i].param2; + + if (g_noteDurationLUT[duration] != lastDuration) + { + val++; + lastDuration = g_noteDurationLUT[duration]; + } + + if (duration != lastDuration) + val++; + + if (val == 0) + val = 1; + + score += val; + } + else + { + lastDuration = 0x80000000; + + if (events[i].type == lastType) + { + if ((lastType != EventType::Controller && (int)lastType != 0x25 && lastType != EventType::EndOfTie) || events[i].param1 == lastParam1) + { + score++; + } + else + { + score += 2; + } + } + else + { + score += 2; + } + } + + lastParam1 = events[i].param1; + lastType = events[i].type; + + if (events[i].time) + score++; + } + + return score; +} + +bool IsCompressionMatch(std::vector& events, int index1, int index2) +{ + if (events[index1].type != events[index2].type || + events[index1].note != events[index2].note || + events[index1].param1 != events[index2].param1 || + events[index1].time != events[index2].time) + return false; + + index1++; + index2++; + + do + { + if (events[index1] != events[index2]) + return false; + + index1++; + index2++; + } while (!IsPatternBoundary(events[index1].type)); + + return IsPatternBoundary(events[index2].type); +} + +void CompressWholeNote(std::vector& events, int index) +{ + for (int j = index + 1; events[j].type != EventType::EndOfTrack; j++) + { + while (events[j].type != EventType::WholeNoteMark) + { + j++; + + if (events[j].type == EventType::EndOfTrack) + return; + } + + if (IsCompressionMatch(events, index, j)) + { + events[j].type = EventType::Pattern; + events[j].param2 = events[index].param2 & 0x7FFFFFFF; + events[index].param2 |= 0x80000000; + } + } +} + +void Compress(std::vector& events) +{ + for (int i = 0; events[i].type != EventType::EndOfTrack; i++) + { + while (events[i].type != EventType::WholeNoteMark) + { + i++; + + if (events[i].type == EventType::EndOfTrack) + return; + } + + if (CalculateCompressionScore(events, i) >= 6) + { + CompressWholeNote(events, i); + } + } +} + +void ReadMidiTracks() +{ + long trackHeaderStart = 14; + + ReadMidiTrackHeader(trackHeaderStart); + ReadSeqEvents(); + + g_agbTrack = 1; + + for (int midiTrack = 0; midiTrack < g_midiTrackCount; midiTrack++) + { + trackHeaderStart += ReadMidiTrackHeader(trackHeaderStart); + + for (g_midiChan = 0; g_midiChan < 16; g_midiChan++) + { + ReadTrackEvents(); + + if (s_minNote != 0xFF) + { +#ifdef DEBUG + printf("Track%d = Midi-Ch.%d\n", g_agbTrack, g_midiChan + 1); +#endif + + std::unique_ptr> events(MergeEvents()); + + // We don't need TEMPO in anything but track 1. + if (g_agbTrack == 1) + { + auto it = std::remove_if(s_seqEvents.begin(), s_seqEvents.end(), [](const Event& event) { return event.type == EventType::Tempo; }); + s_seqEvents.erase(it, s_seqEvents.end()); + } + + ConvertTimes(*events); + events = InsertTimingEvents(*events); + events = CreateTies(*events); + std::stable_sort(events->begin(), events->end(), EventCompare); + events = SplitTime(*events); + CalculateWaits(*events); + + if (g_compressionEnabled) + Compress(*events); + + PrintAgbTrack(*events); + + g_agbTrack++; + } + } + } +} diff --git a/tools/mid2agb/tables.cpp b/tools/mid2agb/tables.cpp index 025dcd2..f254664 100644 --- a/tools/mid2agb/tables.cpp +++ b/tools/mid2agb/tables.cpp @@ -1,286 +1,286 @@ -// 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 "tables.h" - -const int g_noteDurationLUT[] = -{ - 0, // 0 - 1, // 1 - 2, // 2 - 3, // 3 - 4, // 4 - 5, // 5 - 6, // 6 - 7, // 7 - 8, // 8 - 9, // 9 - 10, // 10 - 11, // 11 - 12, // 12 - 13, // 13 - 14, // 14 - 15, // 15 - 16, // 16 - 17, // 17 - 18, // 18 - 19, // 19 - 20, // 20 - 21, // 21 - 22, // 22 - 23, // 23 - 24, // 24 - 24, // 25 - 24, // 26 - 24, // 27 - 28, // 28 - 28, // 29 - 30, // 30 - 30, // 31 - 32, // 32 - 32, // 33 - 32, // 34 - 32, // 35 - 36, // 36 - 36, // 37 - 36, // 38 - 36, // 39 - 40, // 40 - 40, // 41 - 42, // 42 - 42, // 43 - 44, // 44 - 44, // 45 - 44, // 46 - 44, // 47 - 48, // 48 - 48, // 49 - 48, // 50 - 48, // 51 - 52, // 52 - 52, // 53 - 54, // 54 - 54, // 55 - 56, // 56 - 56, // 57 - 56, // 58 - 56, // 59 - 60, // 60 - 60, // 61 - 60, // 62 - 60, // 63 - 64, // 64 - 64, // 65 - 66, // 66 - 66, // 67 - 68, // 68 - 68, // 69 - 68, // 70 - 68, // 71 - 72, // 72 - 72, // 73 - 72, // 74 - 72, // 75 - 76, // 76 - 76, // 77 - 78, // 78 - 78, // 79 - 80, // 80 - 80, // 81 - 80, // 82 - 80, // 83 - 84, // 84 - 84, // 85 - 84, // 86 - 84, // 87 - 88, // 88 - 88, // 89 - 90, // 90 - 90, // 91 - 92, // 92 - 92, // 93 - 92, // 94 - 92, // 95 - 96, // 96 -}; - -const int g_noteVelocityLUT[] = -{ - 0, // 0 - 4, // 1 - 4, // 2 - 4, // 3 - 4, // 4 - 8, // 5 - 8, // 6 - 8, // 7 - 8, // 8 - 12, // 9 - 12, // 10 - 12, // 11 - 12, // 12 - 16, // 13 - 16, // 14 - 16, // 15 - 16, // 16 - 20, // 17 - 20, // 18 - 20, // 19 - 20, // 20 - 24, // 21 - 24, // 22 - 24, // 23 - 24, // 24 - 28, // 25 - 28, // 26 - 28, // 27 - 28, // 28 - 32, // 29 - 32, // 30 - 32, // 31 - 32, // 32 - 36, // 33 - 36, // 34 - 36, // 35 - 36, // 36 - 40, // 37 - 40, // 38 - 40, // 39 - 40, // 40 - 44, // 41 - 44, // 42 - 44, // 43 - 44, // 44 - 48, // 45 - 48, // 46 - 48, // 47 - 48, // 48 - 52, // 49 - 52, // 50 - 52, // 51 - 52, // 52 - 56, // 53 - 56, // 54 - 56, // 55 - 56, // 56 - 60, // 57 - 60, // 58 - 60, // 59 - 60, // 60 - 64, // 61 - 64, // 62 - 64, // 63 - 64, // 64 - 68, // 65 - 68, // 66 - 68, // 67 - 68, // 68 - 72, // 69 - 72, // 70 - 72, // 71 - 72, // 72 - 76, // 73 - 76, // 74 - 76, // 75 - 76, // 76 - 80, // 77 - 80, // 78 - 80, // 79 - 80, // 80 - 84, // 81 - 84, // 82 - 84, // 83 - 84, // 84 - 88, // 85 - 88, // 86 - 88, // 87 - 88, // 88 - 92, // 89 - 92, // 90 - 92, // 91 - 92, // 92 - 96, // 93 - 96, // 94 - 96, // 95 - 96, // 96 - 100, // 97 - 100, // 98 - 100, // 99 - 100, // 100 - 104, // 101 - 104, // 102 - 104, // 103 - 104, // 104 - 108, // 105 - 108, // 106 - 108, // 107 - 108, // 108 - 112, // 109 - 112, // 110 - 112, // 111 - 112, // 112 - 116, // 113 - 116, // 114 - 116, // 115 - 116, // 116 - 120, // 117 - 120, // 118 - 120, // 119 - 120, // 120 - 124, // 121 - 124, // 122 - 124, // 123 - 124, // 124 - 127, // 125 - 127, // 126 - 127, // 127 -}; - -const char* g_noteTable[] = -{ - "Cn%01u ", - "Cs%01u ", - "Dn%01u ", - "Ds%01u ", - "En%01u ", - "Fn%01u ", - "Fs%01u ", - "Gn%01u ", - "Gs%01u ", - "An%01u ", - "As%01u ", - "Bn%01u ", -}; - -const char* g_minusNoteTable[] = -{ - "CnM%01u", - "CsM%01u", - "DnM%01u", - "DsM%01u", - "EnM%01u", - "FnM%01u", - "FsM%01u", - "GnM%01u", - "GsM%01u", - "AnM%01u", - "AsM%01u", - "BnM%01u", -}; +// 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 "tables.h" + +const int g_noteDurationLUT[] = +{ + 0, // 0 + 1, // 1 + 2, // 2 + 3, // 3 + 4, // 4 + 5, // 5 + 6, // 6 + 7, // 7 + 8, // 8 + 9, // 9 + 10, // 10 + 11, // 11 + 12, // 12 + 13, // 13 + 14, // 14 + 15, // 15 + 16, // 16 + 17, // 17 + 18, // 18 + 19, // 19 + 20, // 20 + 21, // 21 + 22, // 22 + 23, // 23 + 24, // 24 + 24, // 25 + 24, // 26 + 24, // 27 + 28, // 28 + 28, // 29 + 30, // 30 + 30, // 31 + 32, // 32 + 32, // 33 + 32, // 34 + 32, // 35 + 36, // 36 + 36, // 37 + 36, // 38 + 36, // 39 + 40, // 40 + 40, // 41 + 42, // 42 + 42, // 43 + 44, // 44 + 44, // 45 + 44, // 46 + 44, // 47 + 48, // 48 + 48, // 49 + 48, // 50 + 48, // 51 + 52, // 52 + 52, // 53 + 54, // 54 + 54, // 55 + 56, // 56 + 56, // 57 + 56, // 58 + 56, // 59 + 60, // 60 + 60, // 61 + 60, // 62 + 60, // 63 + 64, // 64 + 64, // 65 + 66, // 66 + 66, // 67 + 68, // 68 + 68, // 69 + 68, // 70 + 68, // 71 + 72, // 72 + 72, // 73 + 72, // 74 + 72, // 75 + 76, // 76 + 76, // 77 + 78, // 78 + 78, // 79 + 80, // 80 + 80, // 81 + 80, // 82 + 80, // 83 + 84, // 84 + 84, // 85 + 84, // 86 + 84, // 87 + 88, // 88 + 88, // 89 + 90, // 90 + 90, // 91 + 92, // 92 + 92, // 93 + 92, // 94 + 92, // 95 + 96, // 96 +}; + +const int g_noteVelocityLUT[] = +{ + 0, // 0 + 4, // 1 + 4, // 2 + 4, // 3 + 4, // 4 + 8, // 5 + 8, // 6 + 8, // 7 + 8, // 8 + 12, // 9 + 12, // 10 + 12, // 11 + 12, // 12 + 16, // 13 + 16, // 14 + 16, // 15 + 16, // 16 + 20, // 17 + 20, // 18 + 20, // 19 + 20, // 20 + 24, // 21 + 24, // 22 + 24, // 23 + 24, // 24 + 28, // 25 + 28, // 26 + 28, // 27 + 28, // 28 + 32, // 29 + 32, // 30 + 32, // 31 + 32, // 32 + 36, // 33 + 36, // 34 + 36, // 35 + 36, // 36 + 40, // 37 + 40, // 38 + 40, // 39 + 40, // 40 + 44, // 41 + 44, // 42 + 44, // 43 + 44, // 44 + 48, // 45 + 48, // 46 + 48, // 47 + 48, // 48 + 52, // 49 + 52, // 50 + 52, // 51 + 52, // 52 + 56, // 53 + 56, // 54 + 56, // 55 + 56, // 56 + 60, // 57 + 60, // 58 + 60, // 59 + 60, // 60 + 64, // 61 + 64, // 62 + 64, // 63 + 64, // 64 + 68, // 65 + 68, // 66 + 68, // 67 + 68, // 68 + 72, // 69 + 72, // 70 + 72, // 71 + 72, // 72 + 76, // 73 + 76, // 74 + 76, // 75 + 76, // 76 + 80, // 77 + 80, // 78 + 80, // 79 + 80, // 80 + 84, // 81 + 84, // 82 + 84, // 83 + 84, // 84 + 88, // 85 + 88, // 86 + 88, // 87 + 88, // 88 + 92, // 89 + 92, // 90 + 92, // 91 + 92, // 92 + 96, // 93 + 96, // 94 + 96, // 95 + 96, // 96 + 100, // 97 + 100, // 98 + 100, // 99 + 100, // 100 + 104, // 101 + 104, // 102 + 104, // 103 + 104, // 104 + 108, // 105 + 108, // 106 + 108, // 107 + 108, // 108 + 112, // 109 + 112, // 110 + 112, // 111 + 112, // 112 + 116, // 113 + 116, // 114 + 116, // 115 + 116, // 116 + 120, // 117 + 120, // 118 + 120, // 119 + 120, // 120 + 124, // 121 + 124, // 122 + 124, // 123 + 124, // 124 + 127, // 125 + 127, // 126 + 127, // 127 +}; + +const char* g_noteTable[] = +{ + "Cn%01u ", + "Cs%01u ", + "Dn%01u ", + "Ds%01u ", + "En%01u ", + "Fn%01u ", + "Fs%01u ", + "Gn%01u ", + "Gs%01u ", + "An%01u ", + "As%01u ", + "Bn%01u ", +}; + +const char* g_minusNoteTable[] = +{ + "CnM%01u", + "CsM%01u", + "DnM%01u", + "DsM%01u", + "EnM%01u", + "FnM%01u", + "FsM%01u", + "GnM%01u", + "GsM%01u", + "AnM%01u", + "AsM%01u", + "BnM%01u", +}; -- cgit v1.2.3