2022-05-26 05:24:21 +00:00
|
|
|
/**
|
|
|
|
* Furnace Tracker - multi-system chiptune tracker
|
2023-01-20 00:18:40 +00:00
|
|
|
* Copyright (C) 2021-2023 tildearrow and contributors
|
2022-05-26 05:24:21 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "engine.h"
|
|
|
|
#include "../ta-log.h"
|
|
|
|
#include "../utfutils.h"
|
|
|
|
#include "song.h"
|
|
|
|
#include "zsm.h"
|
|
|
|
|
|
|
|
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
|
|
|
constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0;
|
|
|
|
|
2023-08-11 20:03:37 +00:00
|
|
|
SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop, bool optimize) {
|
2023-06-06 04:24:34 +00:00
|
|
|
int VERA=-1;
|
|
|
|
int YM=-1;
|
|
|
|
int IGNORED=0;
|
2022-05-26 05:24:21 +00:00
|
|
|
|
|
|
|
// find indexes for YM and VERA. Ignore other systems.
|
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
2022-09-24 07:31:10 +00:00
|
|
|
switch (song.system[i]) {
|
|
|
|
case DIV_SYSTEM_VERA:
|
2023-06-06 04:24:34 +00:00
|
|
|
if (VERA>=0) {
|
|
|
|
IGNORED++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
VERA=i;
|
2022-09-24 07:31:10 +00:00
|
|
|
logD("VERA detected as chip id %d",i);
|
|
|
|
break;
|
|
|
|
case DIV_SYSTEM_YM2151:
|
2023-06-06 04:24:34 +00:00
|
|
|
if (YM>=0) {
|
|
|
|
IGNORED++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
YM=i;
|
2022-09-24 07:31:10 +00:00
|
|
|
logD("YM detected as chip id %d",i);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
IGNORED++;
|
2023-07-29 18:09:35 +00:00
|
|
|
logD("Ignoring chip %d systemID %d",i,(int)song.system[i]);
|
2023-06-06 04:24:34 +00:00
|
|
|
break;
|
2022-09-24 07:31:10 +00:00
|
|
|
}
|
2022-05-26 05:24:21 +00:00
|
|
|
}
|
2023-06-06 04:24:34 +00:00
|
|
|
if (VERA<0 && YM<0) {
|
|
|
|
logE("No supported systems for ZSM");
|
|
|
|
return NULL;
|
2022-05-26 05:24:21 +00:00
|
|
|
}
|
2023-06-06 04:24:34 +00:00
|
|
|
if (IGNORED>0) {
|
2022-05-26 05:24:21 +00:00
|
|
|
logW("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
|
2023-06-06 04:24:34 +00:00
|
|
|
}
|
2022-05-26 05:24:21 +00:00
|
|
|
|
|
|
|
stop();
|
|
|
|
repeatPattern=false;
|
|
|
|
setOrder(0);
|
|
|
|
BUSY_BEGIN_SOFT;
|
|
|
|
|
|
|
|
double origRate=got.rate;
|
2023-06-06 04:24:34 +00:00
|
|
|
got.rate=zsmrate&0xffff;
|
2022-05-26 05:24:21 +00:00
|
|
|
|
|
|
|
// determine loop point
|
|
|
|
int loopOrder=0;
|
|
|
|
int loopRow=0;
|
|
|
|
int loopEnd=0;
|
|
|
|
walkSong(loopOrder,loopRow,loopEnd);
|
|
|
|
logI("loop point: %d %d",loopOrder,loopRow);
|
|
|
|
warnings="";
|
|
|
|
|
2022-09-24 04:23:03 +00:00
|
|
|
DivZSM zsm;
|
2022-05-26 05:24:21 +00:00
|
|
|
zsm.init(zsmrate);
|
|
|
|
|
|
|
|
// reset the playback state
|
|
|
|
curOrder=0;
|
|
|
|
freelance=false;
|
|
|
|
playing=false;
|
|
|
|
extValuePresent=false;
|
|
|
|
remainingLoops=-1;
|
|
|
|
|
|
|
|
// Prepare to write song data
|
|
|
|
playSub(false);
|
2022-09-25 03:58:44 +00:00
|
|
|
//size_t tickCount=0;
|
2022-05-26 05:24:21 +00:00
|
|
|
bool done=false;
|
2023-07-28 08:36:38 +00:00
|
|
|
bool loopNow=false;
|
2022-05-26 05:24:21 +00:00
|
|
|
int loopPos=-1;
|
|
|
|
int fracWait=0; // accumulates fractional ticks
|
2023-06-06 04:24:34 +00:00
|
|
|
if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(true);
|
|
|
|
if (YM>=0) {
|
2022-05-26 05:24:21 +00:00
|
|
|
disCont[YM].dispatch->toggleRegisterDump(true);
|
2022-11-10 18:40:17 +00:00
|
|
|
// emit LFO initialization commands
|
2023-06-06 04:24:34 +00:00
|
|
|
zsm.writeYM(0x18,0); // freq=0
|
|
|
|
zsm.writeYM(0x19,0x7F); // AMD =7F
|
|
|
|
zsm.writeYM(0x19,0xFF); // PMD =7F
|
2022-11-10 18:40:17 +00:00
|
|
|
// TODO: incorporate the Furnace meta-command for init data and filter
|
|
|
|
// out writes to otherwise-unused channels.
|
2022-05-26 05:24:21 +00:00
|
|
|
}
|
2023-08-11 08:02:18 +00:00
|
|
|
// Indicate the song's tuning as a sync meta-event
|
|
|
|
// specified in terms of how many 1/256th semitones
|
|
|
|
// the song is offset from standard A-440 tuning.
|
|
|
|
// This is mainly to benefit visualizations in players
|
|
|
|
// for non-standard tunings so that they can avoid
|
|
|
|
// displaying the entire song held in pitch bend.
|
|
|
|
// Tunings offsets that exceed a half semitone
|
|
|
|
// will simply be represented in a different key
|
2023-08-11 08:31:53 +00:00
|
|
|
// by nature of overflowing the signed char value
|
2023-08-11 08:30:45 +00:00
|
|
|
signed char tuningoffset=(signed char)(round(3072*(log(song.tuning/440.0)/log(2))))&0xff;
|
2023-08-11 08:02:18 +00:00
|
|
|
zsm.writeSync(0x01,tuningoffset);
|
2023-08-11 20:03:37 +00:00
|
|
|
// Set optimize flag, which mainly buffers PSG writes
|
|
|
|
// whenever the channel is silent
|
|
|
|
zsm.setOptimize(optimize);
|
2022-05-26 05:24:21 +00:00
|
|
|
|
|
|
|
while (!done) {
|
|
|
|
if (loopPos==-1) {
|
2023-07-28 08:36:38 +00:00
|
|
|
if (loopOrder==curOrder && loopRow==curRow && loop)
|
|
|
|
loopNow=true;
|
|
|
|
if (loopNow) {
|
|
|
|
// If Virtual Tempo is in use, our exact loop point
|
2023-07-28 08:51:22 +00:00
|
|
|
// might be skipped due to quantization error.
|
2023-07-28 08:36:38 +00:00
|
|
|
// If this happens, the tick immediately following is our loop point.
|
|
|
|
if (ticks==1 || !(loopOrder==curOrder && loopRow==curRow)) {
|
|
|
|
loopPos=zsm.getoffset();
|
|
|
|
zsm.setLoopPoint();
|
|
|
|
loopNow=false;
|
|
|
|
}
|
2022-05-26 05:24:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nextTick() || !playing) {
|
|
|
|
done=true;
|
|
|
|
if (!loop) {
|
|
|
|
for (int i=0; i<song.systemLen; i++) {
|
|
|
|
disCont[i].dispatch->getRegisterWrites().clear();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!playing) {
|
|
|
|
loopPos=-1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// get register dumps
|
|
|
|
for (int j=0; j<2; j++) {
|
2022-09-24 07:31:10 +00:00
|
|
|
int i=0;
|
2022-05-26 05:24:21 +00:00
|
|
|
// dump YM writes first
|
2022-09-24 07:31:10 +00:00
|
|
|
if (j==0) {
|
2023-06-06 04:24:34 +00:00
|
|
|
if (YM<0) {
|
2022-09-24 07:31:10 +00:00
|
|
|
continue;
|
2023-06-06 04:24:34 +00:00
|
|
|
} else {
|
2022-09-24 07:31:10 +00:00
|
|
|
i=YM;
|
2023-06-06 04:24:34 +00:00
|
|
|
}
|
2022-09-24 07:31:10 +00:00
|
|
|
}
|
2022-05-26 05:24:21 +00:00
|
|
|
// dump VERA writes second
|
2022-09-24 07:31:10 +00:00
|
|
|
if (j==1) {
|
2023-06-06 04:24:34 +00:00
|
|
|
if (VERA<0) {
|
2022-09-24 07:31:10 +00:00
|
|
|
continue;
|
2023-06-06 04:24:34 +00:00
|
|
|
} else {
|
2022-05-26 05:24:21 +00:00
|
|
|
i=VERA;
|
2022-09-24 07:31:10 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-26 05:24:21 +00:00
|
|
|
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
|
2023-06-06 04:24:34 +00:00
|
|
|
if (writes.size()>0)
|
|
|
|
logD("zsmOps: Writing %d messages to chip %d",writes.size(),i);
|
2022-05-26 05:24:21 +00:00
|
|
|
for (DivRegWrite& write: writes) {
|
2023-06-06 04:24:34 +00:00
|
|
|
if (i==YM) zsm.writeYM(write.addr&0xff,write.val);
|
2023-07-04 03:24:49 +00:00
|
|
|
if (i==VERA) {
|
2023-07-05 22:07:44 +00:00
|
|
|
if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead
|
2023-07-04 03:24:49 +00:00
|
|
|
zsm.writePSG(write.addr&0xff,write.val);
|
|
|
|
}
|
2022-05-26 05:24:21 +00:00
|
|
|
}
|
|
|
|
writes.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
// write wait
|
|
|
|
int totalWait=cycles>>MASTER_CLOCK_PREC;
|
2023-06-06 04:24:34 +00:00
|
|
|
fracWait+=cycles&MASTER_CLOCK_MASK;
|
|
|
|
totalWait+=fracWait>>MASTER_CLOCK_PREC;
|
|
|
|
fracWait&=MASTER_CLOCK_MASK;
|
2023-07-04 03:24:49 +00:00
|
|
|
if (totalWait>0 && !done) {
|
2022-05-26 05:24:21 +00:00
|
|
|
zsm.tick(totalWait);
|
2022-09-25 03:58:44 +00:00
|
|
|
//tickCount+=totalWait;
|
2022-05-26 05:24:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// end of song
|
|
|
|
|
|
|
|
// done - close out.
|
2023-06-06 04:24:34 +00:00
|
|
|
got.rate=origRate;
|
|
|
|
if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(false);
|
|
|
|
if (YM>=0) disCont[YM].dispatch->toggleRegisterDump(false);
|
2022-05-26 05:24:21 +00:00
|
|
|
|
|
|
|
remainingLoops=-1;
|
|
|
|
playing=false;
|
|
|
|
freelance=false;
|
|
|
|
extValuePresent=false;
|
|
|
|
|
|
|
|
BUSY_END;
|
|
|
|
return zsm.finish();
|
|
|
|
}
|