adding tools from pokeruby

This commit is contained in:
xenonnsmb
2017-12-03 19:55:01 -06:00
parent eeaa59d837
commit e649e3d248
88 changed files with 10976 additions and 0 deletions

1
tools/mid2agb/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
mid2agb

19
tools/mid2agb/LICENSE Normal file
View File

@@ -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.

15
tools/mid2agb/Makefile Normal file
View File

@@ -0,0 +1,15 @@
CXX := g++
CXXFLAGS := -std=c++11 -O2 -s -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: clean
mid2agb: $(SRCS) $(HEADERS)
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
clean:
$(RM) mid2agb mid2agb.exe

462
tools/mid2agb/agb.cpp Normal file
View File

@@ -0,0 +1,462 @@
// 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;
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 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:
// TODO: memacc
break;
case 0x0D:
// TODO: memacc var
break;
case 0x0E:
// TODO: memacc var
break;
case 0x0F:
// TODO: memacc var
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);
PrintWait(g_initialWait);
PrintByte("KEYSH , %s_key%+d", g_asmLabel.c_str(), 0);
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());
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");
}

33
tools/mid2agb/agb.h Normal file
View File

@@ -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

36
tools/mid2agb/error.cpp Normal file
View File

@@ -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);
}

26
tools/mid2agb/error.h Normal file
View File

@@ -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

230
tools/mid2agb/main.cpp Normal file
View File

@@ -0,0 +1,230 @@
// 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 -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 "";
}
struct Option
{
char letter = 0;
const char *arg = NULL;
};
static Option ParseOption(int& index, const int argc, char** argv)
{
static std::set<char> optionsWithArg = { 'L', 'V', 'G', 'P', 'R' };
static std::set<char> optionsWithoutArg = { 'X', 'E', 'N' };
assert(index >= 0 && index < argc);
const char *opt = argv[index];
assert(opt[0] == '-');
assert(std::strlen(opt) == 2);
char letter = std::toupper(opt[1]);
bool isOption = false;
bool hasArg = false;
if (optionsWithArg.count(letter) != 0)
{
isOption = true;
hasArg = true;
}
else if (optionsWithoutArg.count(letter) != 0)
{
isOption = true;
}
if (!isOption)
PrintUsage();
Option retVal;
retVal.letter = letter;
if (hasArg)
{
index++;
if (index >= argc)
RaiseError("missing argument for \"-%c\"", letter);
retVal.arg = argv[index];
}
return retVal;
}
int main(int argc, char** argv)
{
std::string inputFilename;
std::string outputFilename;
for (int i = 1; i < argc; i++)
{
if (argv[i][0] == '-' && std::strlen(argv[i]) == 2)
{
Option option = ParseOption(i, argc, argv);
switch (option.letter)
{
case 'E':
g_exactGateTime = true;
break;
case 'G':
g_voiceGroup = std::stoi(option.arg);
break;
case 'L':
g_asmLabel = option.arg;
break;
case 'N':
g_compressionEnabled = false;
break;
case 'P':
g_priority = std::stoi(option.arg);
break;
case 'R':
g_reverb = std::stoi(option.arg);
break;
case 'V':
g_masterVolume = std::stoi(option.arg);
break;
case 'X':
g_clocksPerBeat = 2;
break;
}
}
else
{
switch (i)
{
case 1:
inputFilename = argv[i];
break;
case 2:
outputFilename = argv[i];
break;
default:
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 = StripExtension(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;
}

39
tools/mid2agb/main.h Normal file
View File

@@ -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

942
tools/mid2agb/midi.cpp Normal file
View File

@@ -0,0 +1,942 @@
// 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;
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;
}
void SkipEventData()
{
Skip(ReadVLQ());
}
void DetermineEventCategory(MidiEventCategory& category, int& typeChan, int& size)
{
typeChan = ReadInt8();
if (typeChan == 0xFF)
{
category = MidiEventCategory::Meta;
size = 0;
}
else if (typeChan >= 0xF0)
{
category = MidiEventCategory::SysEx;
size = 0;
}
else if (typeChan >= 0x80)
{
category = MidiEventCategory::Control;
switch (typeChan >> 4)
{
case 0xC:
case 0xD:
size = 1;
break;
default:
size = 2;
break;
}
}
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)
{
long startPos = ftell(g_inputFile);
event.param2 = 0;
while (!CheckNoteEnd(event))
;
Seek(startPos);
}
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 = 0x80u;
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)
{
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++;
}
}
}
}

87
tools/mid2agb/midi.h Normal file
View File

@@ -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

286
tools/mid2agb/tables.cpp Normal file
View File

@@ -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",
};

29
tools/mid2agb/tables.h Normal file
View File

@@ -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