VGM export: add "trailing ticks" option

issue #695
This commit is contained in:
tildearrow 2023-02-08 19:25:03 -05:00
parent 0a63399dcf
commit 2343cdecc5
5 changed files with 93 additions and 9 deletions

View File

@ -100,6 +100,7 @@ struct DivChannelState {
unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR; unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR;
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff;
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp; bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp;
bool wentThroughNote, goneThroughNote;
int midiNote, curMidiNote, midiPitch; int midiNote, curMidiNote, midiPitch;
size_t midiAge; size_t midiAge;
@ -152,6 +153,8 @@ struct DivChannelState {
wasShorthandPorta(false), wasShorthandPorta(false),
noteOnInhibit(false), noteOnInhibit(false),
resetArp(false), resetArp(false),
wentThroughNote(false),
goneThroughNote(false),
midiNote(-1), midiNote(-1),
curMidiNote(-1), curMidiNote(-1),
midiPitch(-1), midiPitch(-1),
@ -524,7 +527,12 @@ class DivEngine {
// specify system to build ROM for. // specify system to build ROM for.
SafeWriter* buildROM(int sys); SafeWriter* buildROM(int sys);
// dump to VGM. // dump to VGM.
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false); // set trailingTicks to:
// - 0 to add one tick of trailing
// - x to add x+1 ticks of trailing
// - -1 to auto-determine trailing
// - -2 to add a whole loop of trailing
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false, int trailingTicks=-1);
// dump to ZSM. // dump to ZSM.
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true); SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true);
// dump command stream. // dump command stream.

View File

@ -957,6 +957,8 @@ void DivEngine::processRow(int i, bool afterDelay) {
} }
} else if (!chan[i].noteOnInhibit) { } else if (!chan[i].noteOnInhibit) {
dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,chan[i].note,chan[i].volume>>8));
chan[i].goneThroughNote=true;
chan[i].wentThroughNote=true;
keyHit[i]=true; keyHit[i]=true;
} }
} }

View File

@ -952,7 +952,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
chipVol.push_back((_id)|(0x80000100)|(((unsigned int)_vol)<<16)); \ chipVol.push_back((_id)|(0x80000100)|(((unsigned int)_vol)<<16)); \
} }
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints, bool directStream) { SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints, bool directStream, int trailingTicks) {
if (version<0x150) { if (version<0x150) {
lastError="VGM version is too low"; lastError="VGM version is too low";
return NULL; return NULL;
@ -1037,7 +1037,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
int chipAccounting=0; int chipAccounting=0;
int loopPos=-1; int loopPos=-1;
int loopTick=-1; int loopTickSong=-1;
int songTick=0;
unsigned int sampleOff8[256]; unsigned int sampleOff8[256];
unsigned int sampleOffSegaPCM[256]; unsigned int sampleOffSegaPCM[256];
@ -1063,6 +1064,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
std::vector<DivDelayedWrite> delayedWrites[DIV_MAX_CHIPS]; std::vector<DivDelayedWrite> delayedWrites[DIV_MAX_CHIPS];
std::vector<std::pair<int,DivDelayedWrite>> sortedWrites; std::vector<std::pair<int,DivDelayedWrite>> sortedWrites;
std::vector<size_t> tickPos; std::vector<size_t> tickPos;
std::vector<int> tickSample;
bool trailing=false;
bool beenOneLoopAlready=false;
int countDown=MAX(0,trailingTicks)+1;
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
loopTimer[i]=0; loopTimer[i]=0;
@ -2103,11 +2109,14 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
playSub(false); playSub(false);
size_t tickCount=0; size_t tickCount=0;
bool writeLoop=false; bool writeLoop=false;
bool alreadyWroteLoop=false;
int ord=-1; int ord=-1;
int exportChans=0; int exportChans=0;
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
if (!willExport[dispatchOfChan[i]]) continue; if (!willExport[dispatchOfChan[i]]) continue;
exportChans++; exportChans++;
chan[i].wentThroughNote=false;
chan[i].goneThroughNote=false;
} }
while (!done) { while (!done) {
if (loopPos==-1) { if (loopPos==-1) {
@ -2115,8 +2124,45 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
writeLoop=true; writeLoop=true;
} }
} }
songTick++;
tickPos.push_back(w->tell()); tickPos.push_back(w->tell());
if (nextTick(false,true) || !playing) { tickSample.push_back(tickCount);
if (nextTick(false,true)) {
if (trailing) beenOneLoopAlready=true;
trailing=true;
for (int i=0; i<chans; i++) {
if (!willExport[dispatchOfChan[i]]) continue;
chan[i].wentThroughNote=false;
}
}
if (trailing) {
switch (trailingTicks) {
case -1: { // automatic
bool stillHaveTo=false;
for (int i=0; i<chans; i++) {
if (!willExport[dispatchOfChan[i]]) continue;
if (!chan[i].goneThroughNote) continue;
if (!chan[i].wentThroughNote) {
stillHaveTo=true;
break;
}
}
if (!stillHaveTo) countDown=0;
break;
}
case -2: // one loop
break;
default: // custom
countDown--;
break;
}
if (song.loopModality!=2) countDown=0;
if (countDown>0 && !beenOneLoopAlready) {
loopTickSong++;
}
}
if (countDown<=0 || !playing || beenOneLoopAlready) {
done=true; done=true;
if (!loop) { if (!loop) {
for (int i=0; i<song.systemLen; i++) { for (int i=0; i<song.systemLen; i++) {
@ -2275,10 +2321,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
} }
tickCount+=totalWait; tickCount+=totalWait;
} }
if (writeLoop) { if (writeLoop && !alreadyWroteLoop) {
writeLoop=false; writeLoop=false;
alreadyWroteLoop=true;
loopPos=w->tell(); loopPos=w->tell();
loopTick=tickCount; loopTickSong=songTick;
} }
} }
// end of song // end of song
@ -2333,9 +2380,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
if (loopPos==-1) { if (loopPos==-1) {
w->writeI(0); w->writeI(0);
w->writeI(0); w->writeI(0);
} else if (loopTickSong<0 || loopTickSong>(int)tickPos.size()) {
logW("loopTickSong out of range! %d>%d",loopTickSong,(int)tickPos.size());
w->writeI(0);
w->writeI(0);
} else { } else {
w->writeI(loopPos-0x1c); int realLoopTick=tickSample[loopTickSong];
w->writeI(tickCount-loopTick); int realLoopPos=tickPos[loopTickSong];
logI("tickCount-realLoopTick: %d. realLoopPos: %d",tickCount-realLoopTick,realLoopPos);
w->writeI(realLoopPos-0x1c);
w->writeI(tickCount-realLoopTick);
} }
} else { } else {
w->writeI(0); w->writeI(0);

View File

@ -3530,6 +3530,24 @@ bool FurnaceGUI::loop() {
ImGui::EndCombo(); ImGui::EndCombo();
} }
ImGui::Checkbox("loop",&vgmExportLoop); ImGui::Checkbox("loop",&vgmExportLoop);
if (vgmExportLoop && e->song.loopModality==2) {
ImGui::Text("trailing ticks:");
if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) {
vgmExportTrailingTicks=-1;
}
if (ImGui::RadioButton("one loop",vgmExportTrailingTicks==-2)) {
vgmExportTrailingTicks=-2;
}
if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) {
vgmExportTrailingTicks=0;
}
if (vgmExportTrailingTicks>=0) {
ImGui::SameLine();
if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) {
if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0;
}
}
}
ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints);
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip( ImGui::SetTooltip(
@ -4463,7 +4481,7 @@ bool FurnaceGUI::loop() {
break; break;
} }
case GUI_FILE_EXPORT_VGM: { case GUI_FILE_EXPORT_VGM: {
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints,vgmExportDirectStream); SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints,vgmExportDirectStream,vgmExportTrailingTicks);
if (w!=NULL) { if (w!=NULL) {
FILE* f=ps_fopen(copyOfName.c_str(),"wb"); FILE* f=ps_fopen(copyOfName.c_str(),"wb");
if (f!=NULL) { if (f!=NULL) {
@ -5837,6 +5855,7 @@ FurnaceGUI::FurnaceGUI():
snesFilterHex(false), snesFilterHex(false),
mobileEdit(false), mobileEdit(false),
vgmExportVersion(0x171), vgmExportVersion(0x171),
vgmExportTrailingTicks(-1),
drawHalt(10), drawHalt(10),
zsmExportTickRate(60), zsmExportTickRate(60),
macroPointSize(16), macroPointSize(16),

View File

@ -1118,6 +1118,7 @@ class FurnaceGUI {
bool mobileEdit; bool mobileEdit;
bool willExport[DIV_MAX_CHIPS]; bool willExport[DIV_MAX_CHIPS];
int vgmExportVersion; int vgmExportVersion;
int vgmExportTrailingTicks;
int drawHalt; int drawHalt;
int zsmExportTickRate; int zsmExportTickRate;
int macroPointSize; int macroPointSize;