mirror of https://github.com/zeldaret/tmc.git
Add prototype for music extraction
This commit is contained in:
parent
15956402ea
commit
bc858317ec
10
Makefile
10
Makefile
|
|
@ -106,7 +106,7 @@ infoshell = $(foreach line, $(shell $1 | sed "s/ /__SPACE__/g"), $(info $(subst
|
|||
|
||||
# Build tools when building the rom
|
||||
# Disable dependency scanning for clean/tidy/tools
|
||||
ifeq (,$(filter-out all compare,$(MAKECMDGOALS)))
|
||||
ifeq (,$(filter-out all compare target,$(MAKECMDGOALS)))
|
||||
$(call infoshell, $(MAKE) tools)
|
||||
else
|
||||
NODEP := 1
|
||||
|
|
@ -221,7 +221,7 @@ endif
|
|||
|
||||
$(C_BUILDDIR)/%.o : $(C_SUBDIR)/%.c $$(c_dep)
|
||||
@$(CPP) $(CPPFLAGS) $< -o $(C_BUILDDIR)/$*.i
|
||||
$(PREPROC) $(C_BUILDDIR)/$*.i charmap.txt | $(CC1) $(CFLAGS) -o $(C_BUILDDIR)/$*.s
|
||||
$(PREPROC) $(BUILD_NAME) $(C_BUILDDIR)/$*.i charmap.txt | $(CC1) $(CFLAGS) -o $(C_BUILDDIR)/$*.s
|
||||
@echo -e "\t.text\n\t.align\t2, 0 @ Don't pad with nop\n" >> $(C_BUILDDIR)/$*.s
|
||||
$(AS) $(ASFLAGS) -o $@ $(C_BUILDDIR)/$*.s
|
||||
|
||||
|
|
@ -232,16 +232,16 @@ $(ASM_BUILDDIR)/%.o: asm_dep = $(shell $(SCANINC) -I . $(ASM_SUBDIR)/$*.s)
|
|||
endif
|
||||
|
||||
$(ASM_BUILDDIR)/%.o: $(ASM_SUBDIR)/%.s $$(asm_dep)
|
||||
$(PREPROC) $< | $(AS) $(ASFLAGS) -o $@
|
||||
$(PREPROC) $(BUILD_NAME) $< | $(AS) $(ASFLAGS) -o $@
|
||||
|
||||
ifeq ($(NODEP),1)
|
||||
$(DATA_ASM_BUILDDIR)/%.o: data_dep :=
|
||||
else
|
||||
$(DATA_ASM_BUILDDIR)/%.o: data_dep = $(shell $(SCANINC) -I . $(DATA_ASM_SUBDIR)/$*.s)
|
||||
$(DATA_ASM_BUILDDIR)/%.o: data_dep = $(shell $(SCANINC) -I . -I $(ASSET_SUBDIR) -I $(ASSET_BUILDDIR) $(DATA_ASM_SUBDIR)/$*.s)
|
||||
endif
|
||||
|
||||
$(DATA_ASM_BUILDDIR)/%.o: $(DATA_ASM_SUBDIR)/%.s $$(data_dep)
|
||||
$(PREPROC) $< charmap.txt | $(CPP) -I include -nostdinc -undef -Wno-unicode - | $(AS) $(ASFLAGS) -o $@
|
||||
$(PREPROC) $(BUILD_NAME) $< charmap.txt | $(CPP) -I include -nostdinc -undef -Wno-unicode - | $(AS) $(ASFLAGS) -o $@
|
||||
|
||||
$(SONG_BUILDDIR)/%.o: $(SONG_SUBDIR)/%.s
|
||||
$(AS) $(ASFLAGS) -I sound -o $@ $<
|
||||
|
|
|
|||
5670
assets.yaml
5670
assets.yaml
File diff suppressed because it is too large
Load Diff
8627
data/sounds.s
8627
data/sounds.s
File diff suppressed because it is too large
Load Diff
|
|
@ -243,7 +243,7 @@ extern const SongHeader bgmCastleMotif;
|
|||
extern const SongHeader bgmElementGet;
|
||||
extern const SongHeader bgmFairyFountain;
|
||||
extern const SongHeader bgmFileSelect;
|
||||
extern const SongHeader bgmIntorCutscene;
|
||||
extern const SongHeader bgmIntroCutscene;
|
||||
extern const SongHeader bgmCredits;
|
||||
extern const SongHeader bgmGameover;
|
||||
extern const SongHeader bgmSavingZelda;
|
||||
|
|
@ -829,7 +829,7 @@ const Song gSongTable[] = {
|
|||
[BGM_ELEMENT_GET] = { &bgmElementGet, MUSIC_PLAYER_BGM, MUSIC_PLAYER_BGM },
|
||||
[BGM_FAIRY_FOUNTAIN] = { &bgmFairyFountain, MUSIC_PLAYER_BGM, MUSIC_PLAYER_BGM },
|
||||
[BGM_FILE_SELECT] = { &bgmFileSelect, MUSIC_PLAYER_BGM, MUSIC_PLAYER_BGM },
|
||||
[BGM_INTRO_CUTSCENE] = { &bgmIntorCutscene, MUSIC_PLAYER_BGM, MUSIC_PLAYER_BGM },
|
||||
[BGM_INTRO_CUTSCENE] = { &bgmIntroCutscene, MUSIC_PLAYER_BGM, MUSIC_PLAYER_BGM },
|
||||
[BGM_CREDITS] = { &bgmCredits, MUSIC_PLAYER_BGM, MUSIC_PLAYER_BGM },
|
||||
[BGM_GAMEOVER] = { &bgmGameover, MUSIC_PLAYER_BGM, MUSIC_PLAYER_BGM },
|
||||
[BGM_SAVING_ZELDA] = { &bgmSavingZelda, MUSIC_PLAYER_BGM, MUSIC_PLAYER_BGM },
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
agb2mid
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
CXX := g++
|
||||
|
||||
CXXFLAGS := -std=c++11 -O2 -Wall -Wno-switch -Werror -g
|
||||
|
||||
SRCS := agb.cpp error.cpp main.cpp midi.cpp tables.cpp
|
||||
|
||||
HEADERS := agb.h error.h main.h midi.h tables.h
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: agb2mid
|
||||
@:
|
||||
|
||||
agb2mid: $(SRCS) $(HEADERS)
|
||||
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
$(RM) agb2mid agb2mid.exe
|
||||
|
|
@ -0,0 +1,871 @@
|
|||
// 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 <cmath>
|
||||
#include <iostream>
|
||||
#include "agb.h"
|
||||
#include "main.h"
|
||||
#include "midi.h"
|
||||
#include "tables.h"
|
||||
//////// ------
|
||||
|
||||
// TODO move functions to correct files
|
||||
std::uint32_t ReadInt8();
|
||||
std::uint32_t lReadInt32() {
|
||||
std::uint32_t val = 0;
|
||||
val |= ReadInt8();
|
||||
val |= ReadInt8() << 8;
|
||||
val |= ReadInt8() << 16;
|
||||
val |= ReadInt8() << 24;
|
||||
return val;
|
||||
}
|
||||
void Seek(long offset);
|
||||
|
||||
std::vector<std::uint32_t> trackPointers;
|
||||
|
||||
/*const int FILE_OFFSET = 0x10000;
|
||||
// const int START_OFFSET = 0x1c;
|
||||
const int START_OFFSET = 0x2DE4;
|
||||
const int REDUCE_POINTERS = 0;*/
|
||||
|
||||
const int FILE_OFFSET = 0;
|
||||
const int START_OFFSET = 0xDE38F0;
|
||||
const unsigned int REDUCE_POINTERS = 0x8000000;
|
||||
|
||||
extern int g_fileStartOffset; // TODO move to header
|
||||
|
||||
void ReadAgbSong() {
|
||||
|
||||
Seek(FILE_OFFSET + g_fileStartOffset);
|
||||
|
||||
int numTracks = ReadInt8();
|
||||
int numBlocks = ReadInt8();
|
||||
int priority = ReadInt8();
|
||||
int reverb = ReadInt8();
|
||||
int sound = lReadInt32();
|
||||
for (int i = 0; i < numTracks; i++) {
|
||||
trackPointers.push_back(lReadInt32());
|
||||
if (g_verbose)
|
||||
std::printf("Track: %X\n", trackPointers.back());
|
||||
}
|
||||
if (g_verbose) {
|
||||
std::printf("numTracks %d\n", numTracks);
|
||||
std::printf("numBlocks %d\n", numBlocks);
|
||||
std::printf("priority %d\n", priority);
|
||||
std::printf("reverb %d\n", reverb);
|
||||
std::printf("sound %X\n", sound);
|
||||
}
|
||||
}
|
||||
const int FINE = 0xb1;
|
||||
const int GOTO = 0xb2;
|
||||
const int PATT = 0xb3;
|
||||
const int PEND = 0xb4;
|
||||
const int TEMPO = 0xbb;
|
||||
const int KEYSH = 0xBC;
|
||||
const int VOICE = 0xbd;
|
||||
const int VOL = 0xbe;
|
||||
const int PAN = 0xbf;
|
||||
const int BEND = 0xc0;
|
||||
const int BENDR = 0xc1;
|
||||
const int LFOS = 0xc2;
|
||||
const int MOD = 0xc4;
|
||||
const int TUNE = 0xc8;
|
||||
const int XCMD = 0xcd;
|
||||
const int EOT = 0xce;
|
||||
const int TIE = 0xcf;
|
||||
const int MODT = 0xc5;
|
||||
const int LFODL = 0xc3;
|
||||
|
||||
std::vector<std::vector<Event>> trackEvents;
|
||||
std::vector<Event> metaEvents;
|
||||
|
||||
int convertTime(int time) {
|
||||
// event.time = (24 * g_clocksPerBeat * event.time) / g_midiTimeDiv;
|
||||
return ((float)time * g_midiTimeDiv) / (24 * g_clocksPerBeat);
|
||||
}
|
||||
|
||||
extern void Skip(long offset);
|
||||
|
||||
std::uint32_t GetCurrentPtr() {
|
||||
return std::ftell(g_inputFile);
|
||||
}
|
||||
|
||||
std::uint32_t PtrToFileAddr(std::uint32_t ptr) {
|
||||
return ptr + FILE_OFFSET - REDUCE_POINTERS;
|
||||
}
|
||||
|
||||
void insertAtCorrectTimeFromEnd(std::vector<Event>& events, const Event& event) {
|
||||
// std::printf("time: %d\n", event.time);
|
||||
for (auto it = events.rbegin(); it != events.rend(); it++) {
|
||||
if ((*it).time <= event.time) {
|
||||
events.insert(it.base(), event);
|
||||
|
||||
/* for (const auto& event : events) {
|
||||
std::printf("e: %X t: %d\n", (int)event.type, event.time);
|
||||
}
|
||||
std::cin.ignore();*/
|
||||
return;
|
||||
}
|
||||
}
|
||||
events.insert(events.begin(), event);
|
||||
}
|
||||
|
||||
void insertAtCorrectTimeFromStart(std::vector<Event>& events, const Event& event) {
|
||||
// std::printf("time: %d\n", event.time);
|
||||
for (auto it = events.begin(); it != events.end(); it++) {
|
||||
if ((*it).time >= event.time) {
|
||||
events.insert(it, event);
|
||||
|
||||
/* for (const auto& event : events) {
|
||||
std::printf("e: %X t: %d\n", (int)event.type, event.time);
|
||||
}
|
||||
std::cin.ignore();*/
|
||||
return;
|
||||
}
|
||||
}
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
void ReadAgbTracks() {
|
||||
size_t count = 0;
|
||||
|
||||
|
||||
|
||||
int addedPadding = 0;
|
||||
|
||||
// TODO configurable???
|
||||
g_midiTimeDiv = 24;
|
||||
|
||||
// Add initial time signature (only one irrespective of how many tracks there are)
|
||||
// TODO TimeSignature events in original midi were converted by changing waits? (InsertTimingEvents?)
|
||||
Event event;
|
||||
event.type = EventType::TimeSignature;
|
||||
event.param1 = g_nominator;
|
||||
event.param2 = g_denominatorExp;
|
||||
metaEvents.push_back(event);
|
||||
|
||||
for (uint32_t trackPointer : trackPointers) {
|
||||
|
||||
std::vector<Event> events;
|
||||
count ++;
|
||||
if (g_verbose)
|
||||
std::printf("\n\ntrackPointer: %X -> %X %ld\n", trackPointer, trackPointer - REDUCE_POINTERS, count);
|
||||
|
||||
|
||||
// Search for loop in this trac
|
||||
bool hasLoop = false;
|
||||
std::uint32_t loopAddress = 0;
|
||||
bool foundLoop = false;
|
||||
int loopStartTime = 0;
|
||||
|
||||
unsigned int trackEnd =
|
||||
(trackPointers.size() > count ? trackPointers[count] : (g_fileStartOffset + REDUCE_POINTERS)) - 10 -
|
||||
addedPadding; // Use offset to header as end for last track
|
||||
if (g_verbose)
|
||||
std::printf("End of track: %X\n", trackEnd);
|
||||
Seek(FILE_OFFSET + trackEnd - REDUCE_POINTERS);
|
||||
|
||||
// search for a few bytes whether there is a loop end
|
||||
for (int i = 0; i < 5 + addedPadding; i++) {
|
||||
if (ReadInt8() == GOTO) {
|
||||
if (g_verbose)
|
||||
std::printf("Has loop: %d\n", i);
|
||||
hasLoop = true;
|
||||
loopAddress = lReadInt32() + FILE_OFFSET - REDUCE_POINTERS;
|
||||
if (g_verbose) {
|
||||
std::printf("Addr: %X\n", GetCurrentPtr());
|
||||
std::printf("Addr: %X\n", loopAddress);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (g_verbose)
|
||||
std::printf("Has loop: %d, %X\n", hasLoop, loopAddress);
|
||||
|
||||
|
||||
|
||||
|
||||
// Read all track events
|
||||
|
||||
Seek(FILE_OFFSET + trackPointer - REDUCE_POINTERS);
|
||||
|
||||
int type = ReadInt8();
|
||||
// std::printf("type: %X\n", type);
|
||||
|
||||
// VOL?
|
||||
if (type == VOL) {
|
||||
// TODO extract to function and reuse below
|
||||
|
||||
int val = ReadInt8();
|
||||
|
||||
if (g_verbose)
|
||||
std::printf("ignore vol: %X\n", val);
|
||||
|
||||
// TODO if this is a change to 127*mvl/mxv, there was no event in midi
|
||||
/*std::printf("VOL: %X\n", val);
|
||||
Event event;
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = 0x7;
|
||||
event.param2 = std::round((float)val * MXV / MVL); // TODO inverse "%u*%s_mvl/mxv", event.param2,
|
||||
g_asmLabel.c_str()); events.push_back(event);*/
|
||||
type = ReadInt8();
|
||||
}
|
||||
|
||||
int keyShift = 0;
|
||||
|
||||
// WAIT?
|
||||
// KEYSH
|
||||
if (type == KEYSH) {
|
||||
keyShift = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("KEYSH: %d\n", keyShift);
|
||||
// TODO initial shift is always 0? Or is this set as the whole key shift?
|
||||
|
||||
} else {
|
||||
std::printf("Error: initial type %X not implemented\n", type);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Event> currentlyPlayingNotes;
|
||||
|
||||
bool endOfTrack = false;
|
||||
int lastCommand = -1;
|
||||
int lastNote = -1;
|
||||
int lastVelocity = -1;
|
||||
int currentTime = 0;
|
||||
int loopEndTime = -1;
|
||||
|
||||
int patternDuration = 0;
|
||||
|
||||
type = ReadInt8();
|
||||
|
||||
// Did we jump into a pattern
|
||||
bool inPattern = false;
|
||||
int returnPtr = 0;
|
||||
|
||||
while (!endOfTrack) {
|
||||
// std::printf("type: %X\n", type);
|
||||
if (g_verbose)
|
||||
std::printf("%X|@%d\t ", GetCurrentPtr() - 1, currentTime);
|
||||
if (type < 0x80) {
|
||||
// Repeat last command
|
||||
Skip(-1);
|
||||
type = lastCommand;
|
||||
// std::printf("%X ", type);
|
||||
// std::printf("Repeat last command: %X\n", lastCommand);
|
||||
if (g_verbose)
|
||||
std::printf("(repeat cmd) ");
|
||||
} else {
|
||||
if (type > 0xb4 && type != 0xbb) { // type is no repeatable command (not WAIT, GOTO, TEMPO etc.)
|
||||
lastCommand = type;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case TEMPO: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("TEMPO: %d\n", val * 2);
|
||||
|
||||
Event event;
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Tempo;
|
||||
// 60000000 / event.param2
|
||||
event.param2 = 60000000 / (val * 2);
|
||||
metaEvents.push_back(event);
|
||||
|
||||
break;
|
||||
}
|
||||
case VOICE: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("VOICE: %d\n", val);
|
||||
Event event;
|
||||
event.time = currentTime;
|
||||
event.type = EventType::InstrumentChange;
|
||||
event.param1 = val;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case VOL: {
|
||||
// const int MVL = 90; // TODO configurable per song?
|
||||
const int MVL = 128;
|
||||
|
||||
const int MXV = 0x7F;
|
||||
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("VOL: %d\n", val);
|
||||
Event event;
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = 0x7;
|
||||
event.param2 = std::ceil((float)val * MXV /
|
||||
MVL); // TODO inverse "%u*%s_mvl/mxv", event.param2, g_asmLabel.c_str());
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case PAN: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("PAN: %X\n", val);
|
||||
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = 0xa;
|
||||
event.param2 = val;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case LFOS: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("LFOS: %X\n", val);
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = 0x15;
|
||||
event.param2 = val;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case MOD: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("MOD: %X\n", val);
|
||||
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = 0x1;
|
||||
event.param2 = val;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case PEND: {
|
||||
if (inPattern) {
|
||||
if (g_verbose) {
|
||||
std::printf("P--END\n");
|
||||
std::printf(">>>>>> Returning to %X (duration: %d)\n", returnPtr, patternDuration);
|
||||
}
|
||||
inPattern = false;
|
||||
Seek(returnPtr);
|
||||
} else {
|
||||
if (g_verbose)
|
||||
std::printf("PEND\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PATT: {
|
||||
std::uint32_t val = lReadInt32();
|
||||
if (g_verbose) {
|
||||
std::printf("PATT\n");
|
||||
std::printf(">>>>>> Jumping to: %X\n", val);
|
||||
}
|
||||
patternDuration = 0;
|
||||
returnPtr = GetCurrentPtr();
|
||||
inPattern = true;
|
||||
|
||||
Seek(PtrToFileAddr(val));
|
||||
break;
|
||||
}
|
||||
case GOTO: {
|
||||
int val = lReadInt32();
|
||||
if (g_verbose)
|
||||
std::printf("GOTO: %X\n", val);
|
||||
|
||||
// Already handled
|
||||
loopEndTime = currentTime;
|
||||
|
||||
break;
|
||||
}
|
||||
case EOT: {
|
||||
bool lastNoteUsed = false;
|
||||
int note = ReadInt8();
|
||||
if (note < 0x80) { // a valid note
|
||||
type = ReadInt8();
|
||||
} else {
|
||||
type = note;
|
||||
note = lastNote;
|
||||
lastNoteUsed = true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (lastNoteUsed) {
|
||||
if (g_verbose)
|
||||
std::printf("END Tie: (%s) \n", noteBuf);
|
||||
} else {
|
||||
if (g_verbose)
|
||||
std::printf("END Tie: %s\n", noteBuf);
|
||||
}
|
||||
|
||||
Event event;
|
||||
event.time = currentTime;
|
||||
event.type = EventType::NoteOff;
|
||||
event.note = note + keyShift;
|
||||
events.push_back(event);
|
||||
continue; // Next type was already read
|
||||
}
|
||||
case TIE: {
|
||||
int note = ReadInt8();
|
||||
int velocity = 0;
|
||||
bool lastNoteUsed = false;
|
||||
bool lastVelocityUsed = false;
|
||||
if (note < 0x80) { // a valid note
|
||||
lastNote = note;
|
||||
velocity = ReadInt8();
|
||||
if (velocity < 0x80) { // a valid velocity
|
||||
lastVelocity = velocity;
|
||||
type = ReadInt8();
|
||||
} else {
|
||||
type = velocity;
|
||||
velocity = lastVelocity;
|
||||
lastVelocityUsed = true;
|
||||
}
|
||||
} else {
|
||||
type = note;
|
||||
note = lastNote;
|
||||
lastNoteUsed = true;
|
||||
lastVelocityUsed = true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (lastNoteUsed) {
|
||||
if (lastVelocityUsed) {
|
||||
if (g_verbose)
|
||||
std::printf("TIE: (%s) (v%d)\n", noteBuf, velocity);
|
||||
} else {
|
||||
if (g_verbose)
|
||||
std::printf("TIE: (%s) v%d\n", noteBuf, velocity);
|
||||
}
|
||||
} else {
|
||||
if (lastVelocityUsed) {
|
||||
if (g_verbose)
|
||||
std::printf("TIE: %s(v%d)\n", noteBuf, velocity);
|
||||
} else {
|
||||
if (g_verbose)
|
||||
std::printf("TIE: %sv%d\n", noteBuf, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
Event event;
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Note;
|
||||
event.note = note + keyShift;
|
||||
event.param1 = velocity;
|
||||
events.push_back(event);
|
||||
continue; // Next type was already read
|
||||
}
|
||||
case BEND: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("BEND %X\n", val);
|
||||
Event event;
|
||||
event.time = currentTime;
|
||||
event.type = EventType::PitchBend;
|
||||
event.param2 = val;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case BENDR: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("BENDR %X\n", val);
|
||||
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = 0x14;
|
||||
event.param2 = val;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case TUNE: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("TUNE %X\n", val);
|
||||
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = 0x18;
|
||||
event.param2 = val;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case MODT: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("MODT %X\n", val);
|
||||
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = 0x16;
|
||||
event.param2 = val;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case LFODL: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("LFODL %X\n", val);
|
||||
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = 0x1A;
|
||||
event.param2 = val;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case XCMD: {
|
||||
// TODO does XCMD always have four params or can xIECV or xIECL stand alone?
|
||||
int c1 = ReadInt8();
|
||||
int v1 = ReadInt8();
|
||||
int c2 = ReadInt8();
|
||||
int v2 = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("XCMD %X %X %X %X\n", c1, v1, c2, v2);
|
||||
|
||||
Event event;
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Extended;
|
||||
event.note = 0x1d;
|
||||
event.param1 = c1;
|
||||
event.param2 = v1;
|
||||
events.push_back(event);
|
||||
event.note = 0x1f;
|
||||
event.param1 = c2;
|
||||
event.param2 = v2;
|
||||
events.push_back(event);
|
||||
break;
|
||||
}
|
||||
case KEYSH: {
|
||||
int val = ReadInt8();
|
||||
if (g_verbose)
|
||||
std::printf("KEYSH: %d\n", val);
|
||||
keyShift = val;
|
||||
break;
|
||||
}
|
||||
case FINE: {
|
||||
if (g_verbose)
|
||||
std::printf("--- FINE ---\n");
|
||||
endOfTrack = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (type >= 0x80 && type <= 0x80 + 48) {
|
||||
|
||||
const int lenTbl[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24, 28, 30, 32, 36, 40, 42, 44, 48, 52,
|
||||
54, 56, 60, 64, 66, 68, 72, 76, 78, 80, 84, 88, 90, 92, 96 };
|
||||
|
||||
int wait = lenTbl[type - 0x80];
|
||||
if (g_verbose)
|
||||
std::printf("WAIT: %d\n", wait);
|
||||
|
||||
patternDuration += wait;
|
||||
|
||||
if (!currentlyPlayingNotes.empty()) {
|
||||
|
||||
// std::printf("Testing... %d\n", (int)currentlyPlayingNotes.size());
|
||||
for (auto it = currentlyPlayingNotes.begin(); it != currentlyPlayingNotes.end();) {
|
||||
Event& note = *it; // Modify the note in the list if we don't remove it now.
|
||||
note.time -= wait;
|
||||
if (note.time <= 0) {
|
||||
|
||||
/*if (note.time < 0) {
|
||||
std::printf("<0: %d\n", note.time);
|
||||
std::cin.ignore();
|
||||
}*/
|
||||
// note.time += currentTime; // Set to the global time the note was finished.
|
||||
// TODO negative=
|
||||
note.time += currentTime + wait;
|
||||
|
||||
// std::printf("Note off at %d\n", note.time);
|
||||
insertAtCorrectTimeFromEnd(events, note);
|
||||
|
||||
it = currentlyPlayingNotes.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
// // TODO handle multiple notes playing at the same time on the same track?
|
||||
// if (wait >= currentlyPlayingNotes[0].time) {
|
||||
// Event event = currentlyPlayingNotes[0];
|
||||
// std::printf("Finished playing note\n");
|
||||
// event.time = currentTime;
|
||||
// events.push_back(event);
|
||||
// currentlyPlayingNotes.pop_back();
|
||||
// } else {
|
||||
// currentlyPlayingNotes[0].time -= wait;
|
||||
// }
|
||||
}
|
||||
// std::printf("Done... %d\n", (int)currentlyPlayingNotes.size());
|
||||
}
|
||||
currentTime += wait;
|
||||
|
||||
/* if (wait > 0) {
|
||||
Event event;
|
||||
event.type = EventType::Wait;
|
||||
event.time = currentTime+convertTime(wait);
|
||||
events.push_back(event);
|
||||
}*/
|
||||
|
||||
} else if (type >= 0xD0 && type <= 0xD0 + 47) {
|
||||
|
||||
// Handle note with a fixed length
|
||||
int noteType = type;
|
||||
int note = ReadInt8();
|
||||
int velocity = 0;
|
||||
int length = 0;
|
||||
|
||||
bool usedLastNote = false;
|
||||
bool usedLastVelocity = false;
|
||||
if (note < 0x80) { // a valid note
|
||||
lastNote = note;
|
||||
velocity = ReadInt8();
|
||||
if (velocity < 0x80) { // a valid velocity
|
||||
// std::printf("Explicit velocity%d\n", velocity);
|
||||
lastVelocity = velocity;
|
||||
length = ReadInt8();
|
||||
if (length < 0x80) { // a valid length
|
||||
type = ReadInt8();
|
||||
} else {
|
||||
type = length;
|
||||
length = 0;
|
||||
}
|
||||
} else {
|
||||
// std::printf("Reusing last vel %d\n", lastVelocity);
|
||||
type = velocity;
|
||||
velocity = lastVelocity;
|
||||
usedLastVelocity = true;
|
||||
}
|
||||
} else {
|
||||
type = note;
|
||||
note = lastNote;
|
||||
velocity = lastVelocity;
|
||||
usedLastNote = true;
|
||||
usedLastVelocity = true;
|
||||
}
|
||||
|
||||
Event event;
|
||||
event.time = currentTime;
|
||||
event.type = EventType::Note;
|
||||
event.note = note + keyShift;
|
||||
event.param1 = velocity;
|
||||
// Length table for notes and rests // TODO move to tables.cpp
|
||||
const int lenTbl[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24, 28, 30, 32, 36, 40, 42, 44, 48, 52,
|
||||
54, 56, 60, 64, 66, 68, 72, 76, 78, 80, 84, 88, 90, 92, 96 };
|
||||
|
||||
int wait = lenTbl[noteType - 0xd0 + 1] + length;
|
||||
|
||||
// DEBUG
|
||||
if (g_verbose) {
|
||||
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);
|
||||
|
||||
std::printf("NOTE: %d ", wait - length);
|
||||
if (usedLastNote) {
|
||||
std::printf("(%s) ", noteBuf);
|
||||
} else {
|
||||
std::printf("%s", noteBuf);
|
||||
}
|
||||
if (usedLastVelocity) {
|
||||
std::printf("(v%03u)", velocity);
|
||||
} else {
|
||||
std::printf("v%03u", velocity);
|
||||
}
|
||||
|
||||
if (length != 0) {
|
||||
std::printf(" +l:%d", length);
|
||||
}
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
// std::printf("Wait before convert: %d\n", wait);
|
||||
// std::printf("Wait after convert: %d\n", event.param2);
|
||||
events.push_back(event);
|
||||
|
||||
event.type = EventType::NoteOff;
|
||||
event.time = wait;
|
||||
// std::printf("Should be off at %d...\n", currentTime+wait);
|
||||
// std::printf("Note length: %d\n", wait);
|
||||
// std::cin.ignore();
|
||||
currentlyPlayingNotes.push_back(event);
|
||||
// event.param2 = wait;
|
||||
|
||||
/* int duration = wait; // event.param2;
|
||||
|
||||
if (!g_exactGateTime) { // && duration < 96)
|
||||
// TODO inverse lut
|
||||
std::printf("asdf\n");
|
||||
for (size_t i = 0; i < 97; i++) {
|
||||
if (g_noteDurationLUT[i] == duration) {
|
||||
duration = i;
|
||||
std::printf("test%d\n", (int)i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (duration == 1)
|
||||
duration = 0;
|
||||
|
||||
// event.param2 = (duration * g_midiTimeDiv) /(24 * g_clocksPerBeat);
|
||||
event.param2 = g_noteDurationInverseLUT[duration];
|
||||
std::printf("Wait after convert: %d\n", event.param2);*/
|
||||
|
||||
/*
|
||||
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;
|
||||
*/
|
||||
|
||||
continue; // Next type was already read
|
||||
|
||||
} else {
|
||||
std::printf("ERROR: Unhandled type %X\n", type);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasLoop && !foundLoop && GetCurrentPtr() == loopAddress) {
|
||||
if (g_verbose)
|
||||
std::printf("<<<< inserted loop start\n");
|
||||
//foundLoop = true;
|
||||
loopStartTime = currentTime;
|
||||
Event event;
|
||||
event.time = loopStartTime;
|
||||
event.type = EventType::LoopBegin;
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
// TODO notes, waits,
|
||||
type = ReadInt8();
|
||||
}
|
||||
|
||||
if (currentlyPlayingNotes.size() > 0) {
|
||||
if (g_verbose) {
|
||||
std::printf("Need to finish %lu notes.\n", currentlyPlayingNotes.size());
|
||||
std::cin.ignore();
|
||||
}
|
||||
|
||||
int latestTime = currentTime;
|
||||
for (auto& note : currentlyPlayingNotes) {
|
||||
note.time += currentTime;
|
||||
if (note.time > latestTime) {
|
||||
latestTime = note.time;
|
||||
}
|
||||
insertAtCorrectTimeFromEnd(events, note);
|
||||
}
|
||||
if (g_verbose)
|
||||
std::printf("Found notes from %d up to %d.\n", currentTime, latestTime);
|
||||
currentTime = latestTime;
|
||||
if (g_verbose)
|
||||
std::cin.ignore();
|
||||
}
|
||||
|
||||
/*if (foundLoop) {
|
||||
// Place the loop begin event as the last event for that time on the meta events track.
|
||||
// TODO this does not help as TEMPO events are not placed on the meta track?
|
||||
|
||||
Event event;
|
||||
event.time = loopStartTime;
|
||||
event.type = EventType::LoopBegin;
|
||||
insertAtCorrectTimeFromEnd(events, event);
|
||||
std::printf("--- Inserting loop at %d\n", loopStartTime);
|
||||
}*/
|
||||
|
||||
if (hasLoop) {
|
||||
Event event;
|
||||
event.type = EventType::LoopEnd;
|
||||
event.time = loopEndTime;
|
||||
// Place loop end at the end of the track that has the latest end.
|
||||
/*for (const auto& events : trackEvents) {
|
||||
int trackEnd = events.back().time;
|
||||
if (trackEnd > event.time) {
|
||||
event.time = trackEnd;
|
||||
}
|
||||
}*/
|
||||
// std::printf("####### loop End %d\n", event.time);
|
||||
//events.push_back(event);
|
||||
insertAtCorrectTimeFromEnd(events, event);
|
||||
}
|
||||
|
||||
Event event;
|
||||
event.type = EventType::EndOfTrack;
|
||||
event.time = currentTime;
|
||||
events.push_back(event);
|
||||
if (g_verbose)
|
||||
std::printf("END OF TRACK: %d\n", currentTime);
|
||||
|
||||
trackEvents.push_back(events);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Insert manual time signature change event
|
||||
for (const auto& change : timeSignatureChanges) {
|
||||
bool inserted = false;
|
||||
event.type = EventType::TimeSignature;
|
||||
event.time = change.time;
|
||||
event.param1 = change.nominator;
|
||||
event.param2 = change.denominator;
|
||||
|
||||
for (size_t i = 0; i < metaEvents.size() - 1; i++) {
|
||||
int prevTime = metaEvents[i].time;
|
||||
int nextTime = metaEvents[i + 1].time;
|
||||
//std::printf("%d < %d <= %d?\n", prevTime, event.time, nextTime);
|
||||
if (event.time > prevTime && event.time <= nextTime) {
|
||||
//std::printf("axd%d\n", (int)metaEvents.size());
|
||||
metaEvents.insert(metaEvents.begin() + i + 1, event);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//std::printf("asd %d\n", (int)metaEvents.size());
|
||||
if (!inserted) {
|
||||
metaEvents.push_back(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
#ifndef AGB_H
|
||||
#define AGB_H
|
||||
|
||||
#include <vector>
|
||||
#include "midi.h"
|
||||
|
||||
void ReadAgbSong();
|
||||
void ReadAgbTracks();
|
||||
|
||||
extern int g_agbTrack;
|
||||
extern std::vector<std::vector<Event>> trackEvents;
|
||||
extern std::vector<Event> metaEvents;
|
||||
|
||||
void insertAtCorrectTimeFromStart(std::vector<Event>& events, const Event& event);
|
||||
|
||||
#endif // AGB_H
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
// 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 <vector>
|
||||
#include "main.h"
|
||||
#include "error.h"
|
||||
#include "midi.h"
|
||||
#include "agb.h"
|
||||
|
||||
FILE* g_inputFile = nullptr;
|
||||
FILE* g_outputFile = nullptr;
|
||||
|
||||
int g_fileStartOffset = 0;
|
||||
|
||||
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;
|
||||
int g_nominator = 4;
|
||||
int g_denominatorExp = 2;
|
||||
bool g_verbose = false;
|
||||
std::vector<TimeSignatureChange> timeSignatureChanges;
|
||||
|
||||
|
||||
|
||||
[[noreturn]] static void PrintUsage()
|
||||
{
|
||||
std::printf(
|
||||
"Usage: AGB2MID baserom start_offset input_file output_file [options]\n"
|
||||
"\n"
|
||||
" baserom path to the baserom\n"
|
||||
" start_offset offset to the sound header\n"
|
||||
" input_file filename(.s) for AGB file\n"
|
||||
" output_file filename(.mid) of MIDI 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"
|
||||
" -n??? midi time nominator\n"
|
||||
" -d??? midi time denominator\n"
|
||||
" -t ? ? ? time signature change\n"
|
||||
" -v verbose\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 baseromPath;
|
||||
std::string startOffset;
|
||||
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 (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;
|
||||
case 'n':
|
||||
arg = GetArgument(argc, argv, i);
|
||||
if (arg == nullptr)
|
||||
PrintUsage();
|
||||
g_nominator = std::stoi(arg);
|
||||
break;
|
||||
case 'd':
|
||||
{
|
||||
arg = GetArgument(argc, argv, i);
|
||||
if (arg == nullptr)
|
||||
PrintUsage();
|
||||
int denominator = std::stoi(arg);
|
||||
if (denominator == 1) {
|
||||
g_denominatorExp = 0;
|
||||
} else if (denominator == 2) {
|
||||
g_denominatorExp = 1;
|
||||
} else if (denominator == 4) {
|
||||
g_denominatorExp = 2;
|
||||
} else if (denominator == 8) {
|
||||
g_denominatorExp = 3;
|
||||
} else if (denominator == 16) {
|
||||
g_denominatorExp = 4;
|
||||
} else if (denominator == 32) {
|
||||
g_denominatorExp = 5;
|
||||
} else {
|
||||
std::printf("ERROR: denominator %d\n", denominator);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 't':
|
||||
{
|
||||
int nominator = std::stoi(argv[i+1]);
|
||||
int denominator = std::stoi(argv[i+2]);
|
||||
if (denominator == 1) {
|
||||
denominator = 0;
|
||||
} else if (denominator == 2) {
|
||||
denominator = 1;
|
||||
} else if (denominator == 4) {
|
||||
denominator = 2;
|
||||
} else if (denominator == 8) {
|
||||
denominator = 3;
|
||||
} else if (denominator == 16) {
|
||||
denominator = 4;
|
||||
} else if (denominator == 32) {
|
||||
denominator = 5;
|
||||
} else {
|
||||
std::printf("ERROR: denominator %d\n", denominator);
|
||||
exit(1);
|
||||
}
|
||||
int time = std::stoi(argv[i+3]);
|
||||
i += 3;
|
||||
TimeSignatureChange change;
|
||||
change.nominator = nominator;
|
||||
change.denominator = denominator;
|
||||
change.time = time;
|
||||
|
||||
// TODO sort by time?
|
||||
timeSignatureChanges.push_back(change);
|
||||
break;
|
||||
}
|
||||
case 'v': {
|
||||
g_verbose = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
PrintUsage();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (baseromPath.empty())
|
||||
baseromPath = argv[i];
|
||||
else if (startOffset.empty())
|
||||
startOffset = argv[i];
|
||||
else if (inputFilename.empty())
|
||||
inputFilename = argv[i];
|
||||
else if (outputFilename.empty())
|
||||
outputFilename = argv[i];
|
||||
else
|
||||
PrintUsage();
|
||||
}
|
||||
}
|
||||
|
||||
if (inputFilename.empty())
|
||||
PrintUsage();
|
||||
|
||||
/*if (GetExtension(inputFilename) != "out") // TODO unify with baserom parameter?
|
||||
RaiseError("input filename extension is not \"out\"");
|
||||
*/
|
||||
if (outputFilename.empty())
|
||||
outputFilename = StripExtension(inputFilename) + ".mid";
|
||||
|
||||
if (GetExtension(outputFilename) != "mid")
|
||||
RaiseError("output filename extension is not \"mid\"");
|
||||
|
||||
|
||||
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());
|
||||
|
||||
g_fileStartOffset = std::stoul(startOffset, nullptr, 16);
|
||||
|
||||
|
||||
ReadAgbSong();
|
||||
ReadAgbTracks();
|
||||
WriteMidiFile();
|
||||
|
||||
/*ReadMidiFileHeader();
|
||||
PrintAgbHeader();
|
||||
ReadMidiTracks();
|
||||
PrintAgbFooter();*/
|
||||
|
||||
std::fclose(g_inputFile);
|
||||
std::fclose(g_outputFile);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// 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;
|
||||
extern int g_nominator;
|
||||
extern int g_denominatorExp;
|
||||
extern int g_timeSignatureChange;
|
||||
extern bool g_nonmatching;
|
||||
extern bool g_verbose;
|
||||
|
||||
struct TimeSignatureChange {
|
||||
int nominator;
|
||||
int denominator;
|
||||
int time;
|
||||
};
|
||||
extern std::vector<TimeSignatureChange> timeSignatureChanges;
|
||||
|
||||
#endif // MAIN_H
|
||||
|
|
@ -0,0 +1,382 @@
|
|||
// 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.
|
||||
|
||||
// https://github.com/jpmac26/gba-mus-ripper
|
||||
|
||||
#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;
|
||||
|
||||
void Seek(long offset) {
|
||||
if (std::fseek(g_inputFile, offset, SEEK_SET) != 0)
|
||||
RaiseError("failed to seek to %x", 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;
|
||||
}
|
||||
|
||||
size_t lendian_fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) {
|
||||
if (size != 4) {
|
||||
/* Warn and exit */
|
||||
}
|
||||
|
||||
int x = 1;
|
||||
|
||||
if (*((char*)&x) == 1) {
|
||||
/* Little endian machine, use fwrite directly */
|
||||
return fwrite(ptr, size, nmemb, stream);
|
||||
} else {
|
||||
/* Big endian machine, pre-process first */
|
||||
|
||||
unsigned char* buffer = (unsigned char*)ptr;
|
||||
|
||||
for (size_t i = 0; i < nmemb; i++) {
|
||||
unsigned char a = buffer[4 * i];
|
||||
unsigned char b = buffer[4 * i + 1];
|
||||
|
||||
buffer[4 * i] = buffer[4 * i + 3];
|
||||
buffer[4 * i + 1] = buffer[4 * i + 2];
|
||||
buffer[4 * i + 2] = b;
|
||||
buffer[4 * i + 3] = a;
|
||||
}
|
||||
|
||||
return fwrite(ptr, size, nmemb, stream);
|
||||
}
|
||||
}
|
||||
void WriteInt32(int value) {
|
||||
union {
|
||||
char bytes[4];
|
||||
long ul;
|
||||
} data;
|
||||
data.ul = value;
|
||||
fwrite(&data.bytes[3], 1, 1, g_outputFile);
|
||||
fwrite(&data.bytes[2], 1, 1, g_outputFile);
|
||||
fwrite(&data.bytes[1], 1, 1, g_outputFile);
|
||||
fwrite(&data.bytes[0], 1, 1, g_outputFile);
|
||||
}
|
||||
void WriteInt16(short value) {
|
||||
union {
|
||||
char bytes[2];
|
||||
short us;
|
||||
} data;
|
||||
data.us = value;
|
||||
fwrite(&data.bytes[1], 1, 1, g_outputFile);
|
||||
fwrite(&data.bytes[0], 1, 1, g_outputFile);
|
||||
}
|
||||
void WriteInt8(char value) {
|
||||
fwrite(&value, 1, 1, g_outputFile);
|
||||
}
|
||||
|
||||
void writeDeltaTime(std::vector<char>& data, long value) {
|
||||
char bytes[4] = { 0 };
|
||||
|
||||
if ((unsigned long)value >= (1 << 28)) {
|
||||
// TODO error number too large
|
||||
value = 0x0FFFffff;
|
||||
}
|
||||
|
||||
bytes[0] = (char)(((long)value >> 21) & 0x7f); // most significant 7 bits
|
||||
bytes[1] = (char)(((long)value >> 14) & 0x7f);
|
||||
bytes[2] = (char)(((long)value >> 7) & 0x7f);
|
||||
bytes[3] = (char)(((long)value) & 0x7f); // least significant 7 bits
|
||||
|
||||
int start = 0;
|
||||
while ((start < 4) && (bytes[start] == 0))
|
||||
start++;
|
||||
|
||||
for (int i = start; i < 3; i++) {
|
||||
bytes[i] = bytes[i] | 0x80;
|
||||
data.push_back(bytes[i]);
|
||||
}
|
||||
data.push_back(bytes[3]);
|
||||
}
|
||||
|
||||
void writeInt32(std::vector<char>& data, int value) {
|
||||
union {
|
||||
char bytes[4];
|
||||
int ul;
|
||||
} onion;
|
||||
onion.ul = value;
|
||||
data.push_back(onion.bytes[3]);
|
||||
data.push_back(onion.bytes[2]);
|
||||
data.push_back(onion.bytes[1]);
|
||||
data.push_back(onion.bytes[0]);
|
||||
}
|
||||
|
||||
void writeInt24(std::vector<char>& data, int value) {
|
||||
union {
|
||||
char bytes[4];
|
||||
int ul;
|
||||
} onion;
|
||||
onion.ul = value;
|
||||
data.push_back(onion.bytes[2]);
|
||||
data.push_back(onion.bytes[1]);
|
||||
data.push_back(onion.bytes[0]);
|
||||
}
|
||||
|
||||
void WriteTrack(int channelId, const std::vector<Event>& events) {
|
||||
std::vector<char> data; // TODO reuse same data vector
|
||||
data.reserve(0x1000);
|
||||
data.clear();
|
||||
|
||||
// int previousTime = 0;
|
||||
|
||||
int currentTime = 0;
|
||||
|
||||
bool DEBUG = g_verbose && false;
|
||||
|
||||
for (const auto& event : events) {
|
||||
|
||||
if (DEBUG) { std::printf("TIME: %d\n", event.time); }
|
||||
|
||||
|
||||
/* if (event.type == EventType::Wait) { // Handle special wait event
|
||||
waiting += event.time;
|
||||
continue;
|
||||
}*/
|
||||
|
||||
|
||||
// TODO can remove type parameter if it is the same type as before?
|
||||
|
||||
// Delta time to previous event
|
||||
// std::printf("delta Time: %d\n", event.time);
|
||||
// TODO make sure currentTime <= event.time!!
|
||||
writeDeltaTime(data, event.time - currentTime); // - previousTime);
|
||||
currentTime = event.time;
|
||||
// previousTime = event.time;
|
||||
|
||||
switch (event.type) {
|
||||
case EventType::InstrumentChange:
|
||||
if (DEBUG) { std::printf("InstrumentChange\n");}
|
||||
data.push_back(0xC0 + channelId); // type
|
||||
data.push_back(event.param1); // instrument
|
||||
break;
|
||||
case EventType::Controller:
|
||||
if (DEBUG) { std::printf("Controller\n");}
|
||||
/*if (event.param1 == 7 && !firstVol) {
|
||||
firstVol = true; // hack TODO
|
||||
} else {*/
|
||||
data.push_back(0xB0 + channelId); // type
|
||||
//}
|
||||
data.push_back(event.param1); // controller index
|
||||
data.push_back(event.param2); // value
|
||||
break;
|
||||
case EventType::Note:
|
||||
if (DEBUG) { std::printf("Note\n");}
|
||||
data.push_back(0x90 + channelId); // type
|
||||
data.push_back(event.note); // note
|
||||
data.push_back(event.param1); // velocity
|
||||
break;
|
||||
case EventType::NoteOff:
|
||||
if (DEBUG) { std::printf("NoteOff\n");}
|
||||
// Wait and note_end
|
||||
data.push_back(0x80 + channelId); // type
|
||||
data.push_back(event.note); // note
|
||||
data.push_back(0); // Ignored velocity
|
||||
break;
|
||||
case EventType::TimeSignature:
|
||||
{
|
||||
if (DEBUG) { std::printf("TimeSignature\n");}
|
||||
// One time meta event at the beginning of the meta track.
|
||||
data.push_back(0xff); // meta
|
||||
data.push_back(88); // TIME_SIGNATURE
|
||||
writeDeltaTime(data, 4); // m_length
|
||||
data.push_back(event.param1); // numerator
|
||||
data.push_back(event.param2); // denominator
|
||||
data.push_back(24 * g_clocksPerBeat); // clocksPerClick
|
||||
data.push_back(8); // 32ndPer4th
|
||||
break;
|
||||
}
|
||||
case EventType::Tempo:
|
||||
if (DEBUG) { std::printf("Tempo\n");}
|
||||
data.push_back(0xff); // meta
|
||||
data.push_back(81); // META_TEMPO
|
||||
writeDeltaTime(data, 3); // only valid tempo size is 3
|
||||
writeInt24(data, event.param2); // usecPerQuarterNote
|
||||
break;
|
||||
case EventType::Extended:
|
||||
if (DEBUG) { std::printf("Extended\n");}
|
||||
if (event.note == 0x1d) {
|
||||
// for some reason the first one is in a meta change?
|
||||
data.push_back(0xb0);
|
||||
}
|
||||
data.push_back(0x1e); // extended param change
|
||||
data.push_back(event.param1);
|
||||
|
||||
writeDeltaTime(data, 0); // Next midi event
|
||||
data.push_back(0x1d); // TODO why twice 0x1d? event.note);
|
||||
data.push_back(event.param2);
|
||||
break;
|
||||
case EventType::PitchBend:
|
||||
if (DEBUG) { std::printf("PitchBend\n");}
|
||||
data.push_back(0xe0 + channelId); // petch bend
|
||||
data.push_back(0); // lsb
|
||||
data.push_back(event.param2);
|
||||
break;
|
||||
case EventType::EndOfTrack:
|
||||
if (DEBUG) { std::printf("EndOfTrack\n");}
|
||||
// End of track
|
||||
data.push_back(0xff); // meta
|
||||
data.push_back(47); // META_END_OF_TRACK
|
||||
writeDeltaTime(data, 0); // length
|
||||
break;
|
||||
case EventType::LoopBegin:
|
||||
if (DEBUG) { std::printf("LoopBegin\n");}
|
||||
data.push_back(0xff); // meta
|
||||
data.push_back(0x6); // META_MARKER
|
||||
data.push_back(0x1); // length
|
||||
data.push_back(0x5B); // [
|
||||
break;
|
||||
case EventType::LoopEnd:
|
||||
if (DEBUG) { std::printf("LoopEnd\n");}
|
||||
data.push_back(0xff); // meta
|
||||
data.push_back(0x6); // META_MARKER
|
||||
data.push_back(0x1); // length
|
||||
data.push_back(0x5D); // ]
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MidiTrack header
|
||||
std::fprintf(g_outputFile, "MTrk");
|
||||
// length of track data
|
||||
WriteInt32(data.size());
|
||||
|
||||
fwrite(data.data(), data.size(), 1, g_outputFile);
|
||||
}
|
||||
|
||||
void WriteMidiFile() {
|
||||
// MidiHeader
|
||||
std::fprintf(g_outputFile, "MThd");
|
||||
// u32 length of header data = 6
|
||||
WriteInt32(6);
|
||||
// u16 format = 1
|
||||
WriteInt16(1);
|
||||
// u16 numberTracks
|
||||
WriteInt16(trackEvents.size() + 1);
|
||||
// u16 midiTimeDiv (24 in example)
|
||||
WriteInt16(g_midiTimeDiv);
|
||||
|
||||
|
||||
// Add end of track to meta events
|
||||
Event event;
|
||||
event.type = EventType::EndOfTrack;
|
||||
/*event.time = 0;
|
||||
// Place loop end at the end of the track that has the latest end.
|
||||
for (const auto& events:trackEvents) {
|
||||
int trackEnd = events.back().time;
|
||||
if (trackEnd > event.time) {
|
||||
event.time = trackEnd;
|
||||
}
|
||||
}*/
|
||||
// EndOfTrack event needs to be at the last meta event (can be earlier than the end of other tracks) for bgmVaatiMotif to work
|
||||
event.time = metaEvents.back().time;
|
||||
metaEvents.push_back(event);
|
||||
|
||||
// Track for meta events
|
||||
WriteTrack(-1, metaEvents);
|
||||
|
||||
int i = 0;
|
||||
for (const auto& events : trackEvents) {
|
||||
WriteTrack(i, events);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// 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,
|
||||
Extended,
|
||||
Wait,
|
||||
NoteOff,
|
||||
};
|
||||
|
||||
struct Event
|
||||
{
|
||||
std::int32_t time = 0;
|
||||
EventType type;
|
||||
std::uint8_t note = 0;
|
||||
std::uint8_t param1 = 0;
|
||||
std::int32_t param2 = 0;
|
||||
|
||||
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 WriteMidiFile();
|
||||
|
||||
extern int g_midiChan;
|
||||
extern std::int32_t g_initialWait;
|
||||
|
||||
inline bool IsPatternBoundary(EventType type)
|
||||
{
|
||||
return type == EventType::EndOfTrack || (int)type <= 0x17;
|
||||
}
|
||||
extern std::int16_t g_midiTimeDiv;
|
||||
|
||||
#endif // MIDI_H
|
||||
|
|
@ -0,0 +1,342 @@
|
|||
// 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",
|
||||
};
|
||||
|
||||
const int g_noteIdLUT[] = {
|
||||
0, //
|
||||
};
|
||||
|
||||
const int g_noteDurationInverseLUT[] = {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
28,
|
||||
30,
|
||||
32,
|
||||
36,
|
||||
40,
|
||||
42,
|
||||
44,
|
||||
48,
|
||||
52,
|
||||
54,
|
||||
56,
|
||||
60,
|
||||
64,
|
||||
66,
|
||||
68,
|
||||
72,
|
||||
76,
|
||||
78,
|
||||
80,
|
||||
84,
|
||||
88,
|
||||
90,
|
||||
92,
|
||||
96,
|
||||
};
|
||||
|
|
@ -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[97];
|
||||
extern const int g_noteVelocityLUT[];
|
||||
extern const char* g_noteTable[];
|
||||
extern const char* g_minusNoteTable[];
|
||||
extern const int g_noteDurationInverseLUT[];
|
||||
#endif // TABLES_H
|
||||
|
|
@ -3,11 +3,13 @@ import os
|
|||
import sys
|
||||
import subprocess
|
||||
import yaml
|
||||
from distutils.util import strtobool
|
||||
import json
|
||||
|
||||
verbose = False
|
||||
|
||||
def extract_assets(variant, assets_folder):
|
||||
print(f'Extract assets from {variant}.')
|
||||
print(f'Extract assets from {variant}.', flush=True)
|
||||
map = {
|
||||
'USA': 'baserom.gba',
|
||||
'EU': 'baserom_eu.gba',
|
||||
|
|
@ -24,10 +26,21 @@ def extract_assets(variant, assets_folder):
|
|||
baserom_path = map[variant]
|
||||
with open(baserom_path, 'rb') as file:
|
||||
baserom = bytearray(file.read())
|
||||
|
||||
config_modified = os.path.getmtime('assets.yaml')
|
||||
json_modified = os.path.getmtime('assets.json')
|
||||
if json_modified < config_modified:
|
||||
print('Convert yaml to json...', flush=True)
|
||||
subprocess.check_call('cat assets.yaml | yq . > assets.json', shell=True)
|
||||
|
||||
with open('assets.yaml') as file:
|
||||
with open('assets.json') as file:
|
||||
current_offset = 0
|
||||
assets = yaml.safe_load(file)
|
||||
#print('Parsing yaml...', flush=True)
|
||||
#assets = yaml.safe_load(file)
|
||||
#print('done', flush=True)
|
||||
print('Parsing json...', flush=True)
|
||||
assets = json.load(file)
|
||||
print('done', flush=True)
|
||||
for asset in assets:
|
||||
if 'offsets' in asset: # Offset definition
|
||||
if variant in asset['offsets']:
|
||||
|
|
@ -40,13 +53,26 @@ def extract_assets(variant, assets_folder):
|
|||
continue
|
||||
|
||||
path = os.path.join(assets_folder, asset['path'])
|
||||
|
||||
extract_file = False
|
||||
|
||||
if os.path.isfile(path):
|
||||
if verbose:
|
||||
print(f'{path} already extracted.')
|
||||
file_modified = os.path.getmtime(path)
|
||||
if file_modified < config_modified:
|
||||
if verbose:
|
||||
print(f'{path} was created before the config was modified.')
|
||||
extract_file = True
|
||||
# TODO Extract when source file (depends on type) was modified after target file
|
||||
#print(f'{file_modified} {config_modified}')
|
||||
else:
|
||||
if verbose:
|
||||
print(f'Extracting {path}...')
|
||||
print(f'{path} does not yet exist.')
|
||||
extract_file = True
|
||||
|
||||
|
||||
if extract_file:
|
||||
if verbose:
|
||||
print(f'Extracting {path}...')
|
||||
start = 0
|
||||
if 'start' in asset:
|
||||
# Apply offset to the start of the USA variant
|
||||
|
|
@ -116,6 +142,8 @@ def extract_midi(path, baserom_path, start, options):
|
|||
common_params = []
|
||||
agb2mid_params = []
|
||||
|
||||
exactGateTime = True # Set exactGateTime by default
|
||||
|
||||
for key in options:
|
||||
if key == 'group' or key == 'G':
|
||||
common_params.append('-G')
|
||||
|
|
@ -134,13 +162,32 @@ def extract_midi(path, baserom_path, start, options):
|
|||
agb2mid_params.append(str(options[key]))
|
||||
elif key == 'timeChanges':
|
||||
changes = options['timeChanges']
|
||||
agb2mid_params.append('-t')
|
||||
agb2mid_params.append(str(changes['nominator']))
|
||||
agb2mid_params.append(str(changes['denominator']))
|
||||
agb2mid_params.append(str(changes['time']))
|
||||
if isinstance(changes, list):
|
||||
# Multiple time changes
|
||||
for change in changes:
|
||||
agb2mid_params.append('-t')
|
||||
agb2mid_params.append(str(change['nominator']))
|
||||
agb2mid_params.append(str(change['denominator']))
|
||||
agb2mid_params.append(str(change['time']))
|
||||
else:
|
||||
agb2mid_params.append('-t')
|
||||
agb2mid_params.append(str(changes['nominator']))
|
||||
agb2mid_params.append(str(changes['denominator']))
|
||||
agb2mid_params.append(str(changes['time']))
|
||||
elif key == 'exactGateTime':
|
||||
if options[key] == 1:
|
||||
exactGateTime = True
|
||||
elif options[key] == 0:
|
||||
exactGateTime = False
|
||||
else:
|
||||
exactGateTime = strtobool(options[key])
|
||||
else:
|
||||
common_params.append('-'+key)
|
||||
common_params.append(str(options[key]))
|
||||
|
||||
if exactGateTime:
|
||||
common_params.append('-E')
|
||||
|
||||
# To midi
|
||||
subprocess.check_call([os.path.join('tools', 'agb2mid', 'agb2mid'), baserom_path, hex(start), baserom_path, base+'.mid'] + common_params + agb2mid_params)
|
||||
# To assembly (TODO only do in build step, not if only extracting)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ static int s_memaccParam2;
|
|||
|
||||
void PrintAgbHeader()
|
||||
{
|
||||
std::fprintf(g_outputFile, "\t.include \"MPlayDef.s\"\n\n");
|
||||
std::fprintf(g_outputFile, "\t.include \"sound/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);
|
||||
|
||||
|
|
@ -245,7 +245,7 @@ void PrintEndOfTieOp(const Event& event)
|
|||
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);
|
||||
std::fprintf(g_outputFile, "%s_%u_B%u::\n", g_asmLabel.c_str(), g_agbTrack, s_blockNum);
|
||||
PrintWait(event.time);
|
||||
ResetTrackVars();
|
||||
}
|
||||
|
|
@ -376,7 +376,7 @@ void PrintControllerOp(const Event& event)
|
|||
PrintWait(event.time);
|
||||
break;
|
||||
case 0x11:
|
||||
std::fprintf(g_outputFile, "%s_%u_L%u:\n", g_asmLabel.c_str(), g_agbTrack, event.param2);
|
||||
std::fprintf(g_outputFile, "%s_%u_L%u::\n", g_asmLabel.c_str(), g_agbTrack, event.param2);
|
||||
PrintWait(event.time);
|
||||
ResetTrackVars();
|
||||
break;
|
||||
|
|
@ -417,7 +417,7 @@ void PrintControllerOp(const Event& event)
|
|||
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);
|
||||
std::fprintf(g_outputFile, "%s_%u::\n", g_asmLabel.c_str(), g_agbTrack);
|
||||
|
||||
int wholeNoteCount = 0;
|
||||
int loopEndBlockNum = 0;
|
||||
|
|
@ -487,7 +487,7 @@ void PrintAgbTrack(std::vector<Event>& events)
|
|||
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));
|
||||
std::fprintf(g_outputFile, "%s_%u_%03lu::\n", g_asmLabel.c_str(), g_agbTrack, (unsigned long)(event.param2 & 0x7FFFFFFF));
|
||||
ResetTrackVars();
|
||||
s_inPattern = true;
|
||||
}
|
||||
|
|
@ -543,5 +543,5 @@ void PrintAgbFooter()
|
|||
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");
|
||||
// std::fprintf(g_outputFile, "\n\t.end\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -523,15 +523,36 @@ bool ReadTrackEvent(Event& event)
|
|||
if (category == MidiEventCategory::Meta)
|
||||
{
|
||||
int metaEventType = ReadInt8();
|
||||
SkipEventData();
|
||||
|
||||
if (metaEventType == 0x2F)
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
else if (metaEventType == 0x2F)
|
||||
{
|
||||
SkipEventData();
|
||||
event.type = EventType::EndOfTrack;
|
||||
event.param1 = 0;
|
||||
event.param2 = 0;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SkipEventData();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -651,6 +672,16 @@ void ConvertTimes(std::vector<Event>& events)
|
|||
}
|
||||
}
|
||||
|
||||
void insertAtCorrectTimeFromEnd(const std::unique_ptr<std::vector<Event>>& events, const Event& event) {
|
||||
for (auto it = events->rbegin(); it != events->rend(); it++) {
|
||||
if (it->time <= event.time) {
|
||||
events->insert(it.base(), event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
events->insert(events->begin(), event);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<Event>> InsertTimingEvents(std::vector<Event>& inEvents)
|
||||
{
|
||||
std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
|
||||
|
|
@ -739,17 +770,19 @@ std::unique_ptr<std::vector<Event>> CreateTies(std::vector<Event>& inEvents)
|
|||
{
|
||||
Event tieEvent = event;
|
||||
tieEvent.param2 = -1;
|
||||
outEvents->push_back(tieEvent);
|
||||
insertAtCorrectTimeFromEnd(outEvents, tieEvent);
|
||||
|
||||
Event eotEvent = {};
|
||||
eotEvent.time = event.time + event.param2;
|
||||
eotEvent.type = EventType::EndOfTie;
|
||||
eotEvent.note = event.note;
|
||||
outEvents->push_back(eotEvent);
|
||||
// directly insert at the correct position, so it does not need to be sorted later.
|
||||
// TODO rather keep eotEvent in queue until it's time is reached?
|
||||
insertAtCorrectTimeFromEnd(outEvents, eotEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
outEvents->push_back(event);
|
||||
insertAtCorrectTimeFromEnd(outEvents, event);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -948,7 +981,6 @@ void ReadMidiTracks()
|
|||
ConvertTimes(*events);
|
||||
events = InsertTimingEvents(*events);
|
||||
events = CreateTies(*events);
|
||||
std::stable_sort(events->begin(), events->end(), EventCompare);
|
||||
events = SplitTime(*events);
|
||||
CalculateWaits(*events);
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,15 @@ enum class EventType
|
|||
{
|
||||
EndOfTie = 0x01,
|
||||
Label = 0x11,
|
||||
LoopEnd = 0x12,
|
||||
LoopEnd = 0x38,// To place it last if at the same time as other meta events, but before notes on the same frame
|
||||
LoopEndBegin = 0x13,
|
||||
LoopBegin = 0x14,
|
||||
LoopBegin = 0x24,
|
||||
// Original: 0x14
|
||||
// TODO sfx1AA wants a LoopBegin before a Volume Change -> < 0x22
|
||||
// bgmFestivalApproach wants a LoopBegin after a Tempo -> >0x19 -> 0x20
|
||||
// sfxSparkles wants a LoopBegin after a VOL --> NOT POSSIBLE
|
||||
// bgmCuccoMinigame as well
|
||||
// To place it last if at the same time as other meta events, but before notes on the same frame
|
||||
OriginalTimeSignature = 0x15,
|
||||
WholeNoteMark = 0x16,
|
||||
Pattern = 0x17,
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ AsmFile::AsmFile(std::string filename) : m_filename(filename)
|
|||
FILE *fp = std::fopen(filename.c_str(), "rb");
|
||||
|
||||
if (fp == NULL) {
|
||||
// TODO pass current build assets path to preproc
|
||||
fp = std::fopen(("build/tmc_eu/assets/" + filename).c_str(), "rb");
|
||||
// The include might be an asset.
|
||||
fp = std::fopen(("build/" + g_buildName + "/assets/" + filename).c_str(), "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
|
||||
|
|
|
|||
|
|
@ -24,8 +24,10 @@
|
|||
#include "asm_file.h"
|
||||
#include "c_file.h"
|
||||
#include "charmap.h"
|
||||
#include <algorithm>
|
||||
|
||||
Charmap* g_charmap;
|
||||
std::string g_buildName;
|
||||
|
||||
void PrintAsmBytes(unsigned char *s, int length)
|
||||
{
|
||||
|
|
@ -132,25 +134,26 @@ char* GetFileExtension(char* filename)
|
|||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 3 && argc != 2)
|
||||
if (argc != 4 && argc != 3)
|
||||
{
|
||||
std::fprintf(stderr, "Usage: %s SRC_FILE CHARMAP_FILE", argv[0]);
|
||||
std::fprintf(stderr, "Usage: %s BUILD_NAME SRC_FILE CHARMAP_FILE", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_charmap = new Charmap(argc == 3 ? argv[2] : "");
|
||||
g_buildName = std::string(argv[1]);
|
||||
g_charmap = new Charmap(argc == 4 ? argv[3] : "");
|
||||
|
||||
char* extension = GetFileExtension(argv[1]);
|
||||
char* extension = GetFileExtension(argv[2]);
|
||||
|
||||
if (!extension)
|
||||
FATAL_ERROR("\"%s\" has no file extension.\n", argv[1]);
|
||||
FATAL_ERROR("\"%s\" has no file extension.\n", argv[2]);
|
||||
|
||||
if ((extension[0] == 's') && extension[1] == 0)
|
||||
PreprocAsmFile(argv[1]);
|
||||
PreprocAsmFile(argv[2]);
|
||||
else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0)
|
||||
PreprocCFile(argv[1]);
|
||||
PreprocCFile(argv[2]);
|
||||
else
|
||||
FATAL_ERROR("\"%s\" has an unknown file extension of \"%s\".\n", argv[1], extension);
|
||||
FATAL_ERROR("\"%s\" has an unknown file extension of \"%s\".\n", argv[2], extension);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,5 +50,6 @@ const int kMaxStringLength = 1024;
|
|||
const unsigned long kMaxCharmapSequenceLength = 16;
|
||||
|
||||
extern Charmap* g_charmap;
|
||||
extern std::string g_buildName;
|
||||
|
||||
#endif // PREPROC_H
|
||||
|
|
|
|||
Loading…
Reference in New Issue