import from github
This commit is contained in:
1
tools/mid2agb/.gitignore
vendored
Normal file
1
tools/mid2agb/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
mid2agb
|
19
tools/mid2agb/LICENSE
Normal file
19
tools/mid2agb/LICENSE
Normal 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.
|
24
tools/mid2agb/Makefile
Normal file
24
tools/mid2agb/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
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
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE := .exe
|
||||
else
|
||||
EXE :=
|
||||
endif
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: mid2agb$(EXE)
|
||||
@:
|
||||
|
||||
mid2agb$(EXE): $(SRCS) $(HEADERS)
|
||||
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
$(RM) mid2agb mid2agb.exe
|
547
tools/mid2agb/agb.cpp
Normal file
547
tools/mid2agb/agb.cpp
Normal file
@ -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");
|
||||
}
|
33
tools/mid2agb/agb.h
Normal file
33
tools/mid2agb/agb.h
Normal 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
36
tools/mid2agb/error.cpp
Normal 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
26
tools/mid2agb/error.h
Normal 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
|
233
tools/mid2agb/main.cpp
Normal file
233
tools/mid2agb/main.cpp
Normal file
@ -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;
|
||||
}
|
39
tools/mid2agb/main.h
Normal file
39
tools/mid2agb/main.h
Normal 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
|
964
tools/mid2agb/midi.cpp
Normal file
964
tools/mid2agb/midi.cpp
Normal file
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
tools/mid2agb/midi.h
Normal file
87
tools/mid2agb/midi.h
Normal 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
286
tools/mid2agb/tables.cpp
Normal 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
29
tools/mid2agb/tables.h
Normal 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
|
Reference in New Issue
Block a user