summaryrefslogtreecommitdiff
path: root/tools/mid2agb
diff options
context:
space:
mode:
Diffstat (limited to 'tools/mid2agb')
-rw-r--r--tools/mid2agb/.gitignore1
-rw-r--r--tools/mid2agb/LICENSE19
-rw-r--r--tools/mid2agb/Makefile18
-rw-r--r--tools/mid2agb/agb.cpp547
-rw-r--r--tools/mid2agb/agb.h33
-rw-r--r--tools/mid2agb/error.cpp36
-rw-r--r--tools/mid2agb/error.h26
-rw-r--r--tools/mid2agb/main.cpp233
-rw-r--r--tools/mid2agb/main.h39
-rw-r--r--tools/mid2agb/midi.cpp964
-rw-r--r--tools/mid2agb/midi.h87
-rw-r--r--tools/mid2agb/tables.cpp286
-rw-r--r--tools/mid2agb/tables.h29
13 files changed, 2318 insertions, 0 deletions
diff --git a/tools/mid2agb/.gitignore b/tools/mid2agb/.gitignore
new file mode 100644
index 0000000..0e7264c
--- /dev/null
+++ b/tools/mid2agb/.gitignore
@@ -0,0 +1 @@
+mid2agb
diff --git a/tools/mid2agb/LICENSE b/tools/mid2agb/LICENSE
new file mode 100644
index 0000000..534d153
--- /dev/null
+++ b/tools/mid2agb/LICENSE
@@ -0,0 +1,19 @@
+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.
diff --git a/tools/mid2agb/Makefile b/tools/mid2agb/Makefile
new file mode 100644
index 0000000..77f96db
--- /dev/null
+++ b/tools/mid2agb/Makefile
@@ -0,0 +1,18 @@
+CXX := g++
+
+CXXFLAGS := -std=c++11 -O2 -Wall -Wno-switch -Werror
+
+SRCS := agb.cpp error.cpp main.cpp midi.cpp tables.cpp
+
+HEADERS := agb.h error.h main.h midi.h tables.h
+
+.PHONY: all clean
+
+all: mid2agb
+ @:
+
+mid2agb: $(SRCS) $(HEADERS)
+ $(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
+
+clean:
+ $(RM) mid2agb mid2agb.exe
diff --git a/tools/mid2agb/agb.cpp b/tools/mid2agb/agb.cpp
new file mode 100644
index 0000000..d4d79f1
--- /dev/null
+++ b/tools/mid2agb/agb.cpp
@@ -0,0 +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 <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");
+}
diff --git a/tools/mid2agb/agb.h b/tools/mid2agb/agb.h
new file mode 100644
index 0000000..7eab3c1
--- /dev/null
+++ b/tools/mid2agb/agb.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef AGB_H
+#define AGB_H
+
+#include <vector>
+#include "midi.h"
+
+void PrintAgbHeader();
+void PrintAgbTrack(std::vector<Event>& events);
+void PrintAgbFooter();
+
+extern int g_agbTrack;
+
+#endif // AGB_H
diff --git a/tools/mid2agb/error.cpp b/tools/mid2agb/error.cpp
new file mode 100644
index 0000000..13e38ff
--- /dev/null
+++ b/tools/mid2agb/error.cpp
@@ -0,0 +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 <cstdio>
+#include <cstdlib>
+#include <cstdarg>
+
+// 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/error.h b/tools/mid2agb/error.h
new file mode 100644
index 0000000..da4f011
--- /dev/null
+++ b/tools/mid2agb/error.h
@@ -0,0 +1,26 @@
+// 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.
+
+#ifndef ERROR_H
+#define ERROR_H
+
+[[noreturn]] void RaiseError(const char* format, ...);
+
+#endif // ERROR_H
diff --git a/tools/mid2agb/main.cpp b/tools/mid2agb/main.cpp
new file mode 100644
index 0000000..ea2b294
--- /dev/null
+++ b/tools/mid2agb/main.cpp
@@ -0,0 +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 <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cctype>
+#include <cassert>
+#include <string>
+#include <set>
+#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/main.h b/tools/mid2agb/main.h
new file mode 100644
index 0000000..6e71e73
--- /dev/null
+++ b/tools/mid2agb/main.h
@@ -0,0 +1,39 @@
+// 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.
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <cstdio>
+#include <string>
+
+extern FILE* g_inputFile;
+extern FILE* g_outputFile;
+
+extern std::string g_asmLabel;
+extern int g_masterVolume;
+extern int g_voiceGroup;
+extern int g_priority;
+extern int g_reverb;
+extern int g_clocksPerBeat;
+extern bool g_exactGateTime;
+extern bool g_compressionEnabled;
+
+#endif // MAIN_H
diff --git a/tools/mid2agb/midi.cpp b/tools/mid2agb/midi.cpp
new file mode 100644
index 0000000..fa7d9ce
--- /dev/null
+++ b/tools/mid2agb/midi.cpp
@@ -0,0 +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 <cstdio>
+#include <cassert>
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <memory>
+#include "midi.h"
+#include "main.h"
+#include "error.h"
+#include "agb.h"
+#include "tables.h"
+
+enum class MidiEventCategory
+{
+ Control,
+ SysEx,
+ Meta,
+ Invalid,
+};
+
+MidiFormat g_midiFormat;
+std::int_fast32_t g_midiTrackCount;
+std::int16_t g_midiTimeDiv;
+
+int g_midiChan;
+std::int32_t g_initialWait;
+
+static long s_trackDataStart;
+static std::vector<Event> s_seqEvents;
+static std::vector<Event> 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<std::vector<Event>> MergeEvents()
+{
+ std::unique_ptr<std::vector<Event>> events(new std::vector<Event>());
+
+ 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<Event>& 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<std::vector<Event>> InsertTimingEvents(std::vector<Event>& inEvents)
+{
+ std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
+
+ 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<std::vector<Event>> SplitTime(std::vector<Event>& inEvents)
+{
+ std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
+
+ 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<std::vector<Event>> CreateTies(std::vector<Event>& inEvents)
+{
+ std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
+
+ 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<Event>& 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<Event>& 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<Event>& 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<Event>& 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<Event>& 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<std::vector<Event>> 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/midi.h b/tools/mid2agb/midi.h
new file mode 100644
index 0000000..434c8e8
--- /dev/null
+++ b/tools/mid2agb/midi.h
@@ -0,0 +1,87 @@
+// 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.
+
+#ifndef MIDI_H
+#define MIDI_H
+
+#include <cstdint>
+
+enum class MidiFormat
+{
+ SingleTrack,
+ MultiTrack
+};
+
+enum class EventType
+{
+ EndOfTie = 0x01,
+ Label = 0x11,
+ LoopEnd = 0x12,
+ LoopEndBegin = 0x13,
+ LoopBegin = 0x14,
+ OriginalTimeSignature = 0x15,
+ WholeNoteMark = 0x16,
+ Pattern = 0x17,
+ TimeSignature = 0x18,
+ Tempo = 0x19,
+ InstrumentChange = 0x21,
+ Controller = 0x22,
+ PitchBend = 0x23,
+ KeyShift = 0x31,
+ Note = 0x40,
+ TimeSplit = 0xFE,
+ EndOfTrack = 0xFF,
+};
+
+struct Event
+{
+ std::int32_t time;
+ EventType type;
+ std::uint8_t note;
+ std::uint8_t param1;
+ std::int32_t param2;
+
+ bool operator==(const Event& other)
+ {
+ return (time == other.time
+ && type == other.type
+ && note == other.note
+ && param1 == other.param1
+ && param2 == other.param2);
+ }
+
+ bool operator!=(const Event& other)
+ {
+ return !(*this == other);
+ }
+};
+
+void ReadMidiFileHeader();
+void ReadMidiTracks();
+
+extern int g_midiChan;
+extern std::int32_t g_initialWait;
+
+inline bool IsPatternBoundary(EventType type)
+{
+ return type == EventType::EndOfTrack || (int)type <= 0x17;
+}
+
+#endif // MIDI_H
diff --git a/tools/mid2agb/tables.cpp b/tools/mid2agb/tables.cpp
new file mode 100644
index 0000000..f254664
--- /dev/null
+++ b/tools/mid2agb/tables.cpp
@@ -0,0 +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",
+};
diff --git a/tools/mid2agb/tables.h b/tools/mid2agb/tables.h
new file mode 100644
index 0000000..da3c907
--- /dev/null
+++ b/tools/mid2agb/tables.h
@@ -0,0 +1,29 @@
+// 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.
+
+#ifndef TABLES_H
+#define TABLES_H
+
+extern const int g_noteDurationLUT[];
+extern const int g_noteVelocityLUT[];
+extern const char* g_noteTable[];
+extern const char* g_minusNoteTable[];
+
+#endif // TABLES_H