Add prototype for music extraction

This commit is contained in:
octorock 2021-09-16 18:31:51 +02:00
parent 15956402ea
commit bc858317ec
24 changed files with 5948 additions and 10748 deletions

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

1
tools/agb2mid/.gitignore vendored Executable file
View File

@ -0,0 +1 @@
agb2mid

19
tools/agb2mid/LICENSE Executable file
View File

@ -0,0 +1,19 @@
Copyright (c) 2016 YamaArashi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

18
tools/agb2mid/Makefile Executable file
View File

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

871
tools/agb2mid/agb.cpp Executable file
View File

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

36
tools/agb2mid/agb.h Executable file
View File

@ -0,0 +1,36 @@
// Copyright(c) 2016 YamaArashi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#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

36
tools/agb2mid/error.cpp Executable file
View File

@ -0,0 +1,36 @@
// Copyright(c) 2016 YamaArashi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
// Reports an error diagnostic and terminates the program.
[[noreturn]] void RaiseError(const char* format, ...)
{
const int bufferSize = 1024;
char buffer[bufferSize];
std::va_list args;
va_start(args, format);
std::vsnprintf(buffer, bufferSize, format, args);
std::fprintf(stderr, "error: %s\n", buffer);
va_end(args);
std::exit(1);
}

26
tools/agb2mid/error.h Executable file
View File

@ -0,0 +1,26 @@
// Copyright(c) 2016 YamaArashi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef ERROR_H
#define ERROR_H
[[noreturn]] void RaiseError(const char* format, ...);
#endif // ERROR_H

327
tools/agb2mid/main.cpp Executable file
View File

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

51
tools/agb2mid/main.h Executable file
View File

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

382
tools/agb2mid/midi.cpp Executable file
View File

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

90
tools/agb2mid/midi.h Executable file
View File

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

342
tools/agb2mid/tables.cpp Executable file
View File

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

29
tools/agb2mid/tables.h Executable file
View File

@ -0,0 +1,29 @@
// Copyright(c) 2016 YamaArashi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef TABLES_H
#define TABLES_H
extern const int g_noteDurationLUT[97];
extern const int g_noteVelocityLUT[];
extern const char* g_noteTable[];
extern const char* g_minusNoteTable[];
extern const int g_noteDurationInverseLUT[];
#endif // TABLES_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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