Skip to content

Commit

Permalink
Fixed countTicks and section naming bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
sauraen committed Jul 1, 2021
1 parent db4328c commit 3475a56
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 74 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Made with the Juce 6 C++ framework under the GPL3 license

Click [the Wiki tab](https://github.com/sauraen/seq64/wiki) for articles on SEQ64 compilation and use.

## .com? .mus? What is all this? Where do I load a ROM?

You don't load a ROM in SEQ64 V2.0. `.com`, `.aseq`, and `.m64` are all the same thing (Music Macro Language binary). `.mus` is the text-based assembly language version of this same data. The rest of this README explains in more detail. If you are using an old-school toolchain which doesn't split the ROM into its constituent files, but where you just edit the whole ROM in one tool after another, use SEQ64 V1.

## Background

Certain Nintendo 64 games made by Nintendo's internal studios, including Super Mario 64, Zelda, and several others, use a music sequence format called Music Macro Language (formerly referred to as `Audioseq` after the name of the file containing the sequences in the OoT Debug ROM). This format can be thought of as a cross between MIDI and a scripting language such as Python: it contains instructions for things like playing notes, pitch bend, and selecting instruments, and also programming instructions like branches, loops, calls, memory I/O, and some basic variable manipulation. In games which use this format, not only are MIDI-like music sequences stored in this format, but the first sequence in each game handles sound effects. This sequence is a huge "program" written in this language which listens for messages from the game engine and then plays miniature sequences for each of these sound effects.
Expand Down
4 changes: 2 additions & 2 deletions Source/SeqEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,7 @@ void SeqEditor::buttonClicked (juce::Button* buttonThatWasClicked)
if(!abi.isValid()) return;
if(!checkSeqPresence(false)) return;
File f = File::getSpecialLocation(File::userHomeDirectory); //TODO SEQ64::readFolderProperty("romfolder");
FileChooser box("Load .com/.aseq", f, "*.com;*.aseq", true);
FileChooser box("Load .com/.aseq", f, "*.com;*.aseq;*.m64;*.bin;*.seq", true);
if(!box.browseForFileToOpen()) return;
f = box.getResult();
seq.reset(new SeqFile(abi));
Expand All @@ -956,7 +956,7 @@ void SeqEditor::buttonClicked (juce::Button* buttonThatWasClicked)
//[UserButtonCode_btnExportCom] -- add your button handler code here..
if(!checkSeqPresence(true)) return;
File savelocation = File::getSpecialLocation(File::userHomeDirectory); //SEQ64::readFolderProperty("comfolder");
FileChooser box("Save .com/.aseq", savelocation, "*.com;*.aseq", true);
FileChooser box("Save .com/.aseq", savelocation, "*.com;*.aseq;*.m64;*.bin;*.seq", true);
if(!box.browseForFileToSave(true)) return;
savelocation = box.getResult();
startSeqOperation(".com/.aseq export", &SeqFile::exportCom, savelocation);
Expand Down
188 changes: 117 additions & 71 deletions Source/SeqFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Identifier SeqFile::idSectionName("sectionname");
Identifier SeqFile::idOldSectionIdx("oldsectionidx");
Identifier SeqFile::idSecDone("secdone");
Identifier SeqFile::idTicks("ticks");
Identifier SeqFile::idLastDelay("lastdelay");
Identifier SeqFile::idLabelName("labelname");
Identifier SeqFile::idLabelNameAuto("labelnameauto");
Identifier SeqFile::idSrcCmdRef("srccmdref");
Expand Down Expand Up @@ -267,8 +268,7 @@ void SeqFile::getCommandRange(ValueTree command, String meaning, int &range_min,
int SeqFile::getLargestCommandRange(int stype, String action, String meaning){
ValueTree test, param, param2;
ValueTree possibleCmdsList("possiblecmdslist");
bool flag;
int i, range, maxrange = 0;
int i, maxrange = 0;
for(i=0; i<abi.getNumChildren(); i++){
test = abi.getChild(i);
if(!isCommandValidIn(test, stype)) continue;
Expand Down Expand Up @@ -597,7 +597,7 @@ void SeqFile::prefSetBool(ValueTree midiopts, Identifier opt, String value, Stri
if(truthy) midiopts.setProperty(opt, true, nullptr);
else if(falsey) midiopts.setProperty(opt, false, nullptr);
else{
dbgmsg(".pref: Invalid value " + prefline);
dbgmsg(".pref: Invalid bool value: " + prefline);
importresult |= 1;
}
}
Expand All @@ -606,18 +606,18 @@ void SeqFile::prefSetInt(ValueTree midiopts, Identifier opt, int max, String val
if(isInt(value) && v >= 0 && v <= max){
midiopts.setProperty(opt, v, nullptr);
}else{
dbgmsg(".pref: Invalid " + opt + " value " + value);
dbgmsg(".pref: Invalid " + opt + " value " + value + ": " + prefline);
importresult |= 1;
}
}
void SeqFile::prefSetHex(ValueTree midiopts, Identifier opt, int max, String value, String prefline){
if(value.startsWithIgnoreCase("0x")) value = value.substring(2);
else if(value.startsWith("$")) value = value.substring(1);
int v = value.getHexValue32();
if(isHex(value) && v >= 0 && v <= 255){
if(isHex(value) && v >= 0 && v <= max){
midiopts.setProperty(opt, v, nullptr);
}else{
dbgmsg(".pref: Invalid " + opt + " value " + value);
dbgmsg(".pref: Invalid " + opt + " value " + value + ": " + prefline);
importresult |= 1;
}
}
Expand Down Expand Up @@ -1654,7 +1654,6 @@ void SeqFile::optimize(ValueTree midiopts){
int numCmdsDelete;
//
ValueTree want, want2, param;
int i;
int cctype, cclast, ccdir;
if(useLoops){
dbgmsg("\nLooking for data to loop");
Expand Down Expand Up @@ -3479,11 +3478,12 @@ int SeqFile::importMus(File musfile){
bool endSection = false, dontAdvanceLine = false;
if(ln >= lines.size()){
if(section.hasProperty(idQuestionableSection)){
line->Info("Ran into end of sequence while parsing unused code, "
dbgmsg("Ran into end of sequence while parsing unused code, "
"calling that the end of the unused code");
}else{
line->Warning("Ran into end of sequence while parsing normal code, "
dbgmsg("Ran into end of sequence while parsing normal code, "
"normally this is an error but in some technical sequences it's OK");
importresult |= 1;
}
dontAdvanceLine = true;
endSection = true;
Expand Down Expand Up @@ -3898,20 +3898,47 @@ String SeqFile::getSecNamePrefix(int dialect, ValueTree section){
return name;
}

void SeqFile::registerExistingLabelName(ValueTree section){
String name = section.getProperty(idLabelName).toString();
if(alllabelnames.contains(name)){
dbgmsg("Duplicate existing label name " + name + "!");
importresult |= 2;
}else{
alllabelnames.add(name);
}
}

void SeqFile::setLabelNameAuto(ValueTree target, String name, String extra){
if(extra.isEmpty()){
dbgmsg("Internal error in setLabelNameAuto!");
importresult |= 2;
return;
}
while(name.isEmpty() || alllabelnames.contains(name)){
String name2 = name + extra;
//dbgmsg("Duplicate generated name " + name + ", changing to " + name2);
name = name2;
}
alllabelnames.add(name);
target.setProperty(idLabelName, name, nullptr);
target.setProperty(idLabelNameAuto, true, nullptr);
}

void SeqFile::nameSections(int dialect){
StringArray allsecnames;
//Main section
ValueTree section = structure.getChild(0);
if(!section.hasProperty(idLabelName) || section.hasProperty(idLabelNameAuto)){
String name = dialect >= 2 ? "_" + seqname : "_start";
allsecnames.addIfNotAlreadyThere(name);
section.setProperty(idLabelName, name, nullptr);
section.setProperty(idLabelNameAuto, true, nullptr);
if(section.hasProperty(idLabelName) && !section.hasProperty(idLabelNameAuto)){
registerExistingLabelName(section);
}else{
setLabelNameAuto(section, dialect >= 2 ? "_" + seqname : "_start");
}
//Rest of sections
for(int sec=1; sec<structure.getNumChildren(); sec++){
section = structure.getChild(sec);
if(section.hasProperty(idLabelName) && !section.hasProperty(idLabelNameAuto)) continue;
if(section.hasProperty(idLabelName) && !section.hasProperty(idLabelNameAuto)){
registerExistingLabelName(section);
continue;
}
int stype = section.getProperty(idSType);
String name = getSecNamePrefix(dialect, section);
if(stype == 1 || stype == 2){
Expand Down Expand Up @@ -3941,15 +3968,10 @@ void SeqFile::nameSections(int dialect){
name += "_ldstbl" + String(sec);
}
}else if(section.hasProperty(idSrcCmdRef)){
name += "_" + String(dialect >= 2 ? "pat" : "call") + section.getProperty(idSrcCmdRef).toString();
name += "_" + String(dialect >= 2 ? "pat" : "call")
+ section.getProperty(idSrcCmdRef).toString();
}
if(name.isEmpty() || allsecnames.contains(name)){
//dbgmsg("Name clash for section " + String(sec) + " \"" + name + "\"");
name += "_" + String(sec);
}
allsecnames.addIfNotAlreadyThere(name);
section.setProperty(idLabelName, name, nullptr);
section.setProperty(idLabelNameAuto, true, nullptr);
setLabelNameAuto(section, name, "_" + String(sec));
}
}

Expand All @@ -3970,62 +3992,87 @@ void SeqFile::nameTargetCommands(int dialect){
ValueTree section = structure.getChild(sec);
for(int cmd=0; cmd<section.getNumChildren(); ++cmd){
ValueTree command = section.getChild(cmd);
if(command.hasProperty(idLabelName) && !command.hasProperty(idLabelNameAuto)) continue;
if(command.hasProperty(idLabelName) && !command.hasProperty(idLabelNameAuto)){
registerExistingLabelName(command);
continue;
}
if(command.hasProperty(idTSection)){
//In seq header, first command of each tsection
command.setProperty(idLabelName, getSecNamePrefix(dialect, command), nullptr);
setLabelNameAuto(command, getSecNamePrefix(dialect, command));
}else if(targethashes.contains(command.getProperty(idHash))){
//Normal target command
command.setProperty(idLabelName, section.getProperty(idLabelName).toString() + "_" + String(cmd), nullptr);
}else{
continue;
setLabelNameAuto(command,
section.getProperty(idLabelName).toString() + "_" + String(cmd));
}
command.setProperty(idLabelNameAuto, true, nullptr);
}
}
}

int SeqFile::countTicks(ValueTree sec){
if(!sec.isValid()){
dbgmsg("Invalid section in countTicks!");
importresult |= 2;
return 0;
}
if(sec.hasProperty(idTicks)){
return sec.getProperty(idTicks);
}
int stype = sec.getProperty(idSType);
if(stype >= 3) return 0;
sec.setProperty(idTicks, -1, nullptr);
int ticks = 0;
int lastnotedelay = -1;
for(int i=0; i<sec.getNumChildren(); ++i){
ValueTree command = sec.getChild(i);
String action = command.getProperty(idAction).toString();
ValueTree delayparam = command.getChildWithProperty(idMeaning, "Delay");
if(delayparam.isValid()){
int delay = delayparam.getProperty(idValue);
ticks += delay;
if(action == "Note") lastnotedelay = delay;
}else if(action == "Note"){
if(stype != 2 || lastnotedelay < 0){
dbgmsg("Internal error in countTicks()!");
importresult |= 2;
return 0;
}
ticks += lastnotedelay;
int SeqFile::countTicks(ValueTree sec, int starthash, int *lastdelay){
do{
if(!sec.isValid()){
dbgmsg("countTicks(): invalid section!");
break;
}
if(action == "Call"){
int t = countTicks(structure.getChild((int)command.getProperty(idTargetSection)));
//TODO this isn't always right, can call into middle of section
if(t >= 0){
if(starthash == 0 && sec.hasProperty(idTicks)){
if(lastdelay != nullptr) *lastdelay = sec.getProperty(idLastDelay);
return sec.getProperty(idTicks);
}
int stype = sec.getProperty(idSType);
if(stype >= 3) return 0;
int ticks = 0;
int lastnotedelay = -1;
int i=0;
if(starthash != 0){
ValueTree command = sec.getChildWithProperty(idHash, starthash);
if(!command.isValid()){
dbgmsg("countTicks(): target hash not found! Sec "
+ sec.getProperty(idLabelName).toString() + " hash "
+ String(starthash));
break;
}
i = sec.indexOf(command);
}
bool error = false;
for(; i<sec.getNumChildren() && !error; ++i){
ValueTree command = sec.getChild(i);
String action = command.getProperty(idAction).toString();
ValueTree delayparam = command.getChildWithProperty(idMeaning, "Delay");
if(delayparam.isValid()){
int delay = delayparam.getProperty(idValue);
ticks += delay;
if(action == "Note") lastnotedelay = delay;
}else if(action == "Note"){
if(stype != 2){
dbgmsg("countTicks(): note in stype" + String(stype) + "!");
error = true; break;
}else if(lastnotedelay < 0){
dbgmsg("countTicks(): note uses last delay but last delay is undefined! Sec "
+ sec.getProperty(idName).toString() + " " + sec.getProperty(idLabelName).toString()
+ " cmd " + String(i));
error = true; break;
}
ticks += lastnotedelay;
}
if(action == "Call"){
int ld = -1;
ValueTree subsec = structure.getChild((int)command.getProperty(idTargetSection));
int sh = command.getProperty(idTargetHash, 0);
int t = countTicks(subsec, sh, &ld);
if(ld < 0) { error = true; break; }
ticks += t;
lastnotedelay = -1;
lastnotedelay = ld;
}
}
}
sec.setProperty(idTicks, ticks, nullptr);
return ticks;
if(error) break;
if(starthash == 0) sec.setProperty(idTicks, ticks, nullptr);
sec.setProperty(idLastDelay, lastnotedelay, nullptr);
if(lastdelay != nullptr) *lastdelay = lastnotedelay;
return ticks;
}while(false);
if(lastdelay != nullptr) *lastdelay = -1;
importresult |= 1;
return 0;
}

String SeqFile::getCommandMusLine(int sec, ValueTree section, ValueTree command,
Expand Down Expand Up @@ -4214,6 +4261,7 @@ int SeqFile::exportMus(File musfile, int dialect){
}
}
}
alllabelnames.clear();
nameSections(dialect);
nameTargetCommands(dialect);
//Write data
Expand All @@ -4240,7 +4288,6 @@ int SeqFile::exportMus(File musfile, int dialect){
}
int tsecnum = -1;
int sectiongroup = -1;
int last_stype = -2;
for(int sec=0; sec<structure.getNumChildren(); ++sec){
ValueTree section = structure.getChild(sec);
if(section.getType().toString() == "align"){
Expand Down Expand Up @@ -4403,14 +4450,13 @@ int SeqFile::exportMus(File musfile, int dialect){
if(lblctr != 0) out += "\n";
}else{
//Seq header, channel, or layer command
int secticks = countTicks(section);
int secticks = countTicks(section, 0, nullptr);
for(int cmd=0; cmd<section.getNumChildren(); ++cmd){
out += getCommandMusLine(sec, section, section.getChild(cmd),
dialect, stype, secticks);
}
}
out += "\n";
last_stype = stype;
}
if(tsecnames_generated){
tsecnames.clear();
Expand Down Expand Up @@ -4453,7 +4499,7 @@ ValueTree SeqFile::getCommand(const Array<uint8_t> &data, uint32_t address, int
ValueTree param, desc;
String action, meaning, datasrc;
int i, len, paramlen, paramindex, cmdoffset, paramvalue, datalen;
uint8_t c, d;
uint8_t c;
uint32_t a = address;
//
len = 1;
Expand Down
6 changes: 5 additions & 1 deletion Source/SeqFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,16 @@ class SeqFile{
bool getSectionAndCmd(ValueTree command, int &s, int &c);

//For exportMus
StringArray alllabelnames;
void assignTSection(ValueTree sec, int tsecnum);
int assignAllTSections();
void generateTSecNames(int num_tsections, int dialect);
String getSecNamePrefix(int dialect, ValueTree section);
void registerExistingLabelName(ValueTree section);
void setLabelNameAuto(ValueTree target, String name, String extra = "_");
void nameSections(int dialect);
void nameTargetCommands(int dialect);
int countTicks(ValueTree sec);
int countTicks(ValueTree sec, int starthash, int *lastdelay);
String getCommandMusLine(int sec, ValueTree section, ValueTree command,
int dialect, int stype, int secticks);
int findDynTableIndex(int sec);
Expand Down Expand Up @@ -259,6 +262,7 @@ class SeqFile{
static Identifier idOldSectionIdx;
static Identifier idSecDone;
static Identifier idTicks;
static Identifier idLastDelay;
static Identifier idLabelName;
static Identifier idLabelNameAuto;
static Identifier idSrcCmdRef;
Expand Down

0 comments on commit 3475a56

Please sign in to comment.