furnace/src/engine/zsmOps.cpp
2023-01-19 19:18:40 -05:00

177 lines
4.7 KiB
C++

/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* 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;
SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
int VERA = -1;
int YM = -1;
int IGNORED = 0;
//loop = false;
// find indexes for YM and VERA. Ignore other systems.
for (int i=0; i<song.systemLen; i++) {
switch (song.system[i]) {
case DIV_SYSTEM_VERA:
if (VERA >= 0) { IGNORED++;break; }
VERA = i;
logD("VERA detected as chip id %d",i);
break;
case DIV_SYSTEM_YM2151:
if (YM >= 0) { IGNORED++;break; }
YM = i;
logD("YM detected as chip id %d",i);
break;
default:
IGNORED++;
logD("Ignoring chip %d systemID %d",i,song.system[i]);
}
}
if (VERA < 0 && YM < 0) {
logE("No supported systems for ZSM");
return NULL;
}
if (IGNORED > 0)
logW("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
stop();
repeatPattern=false;
setOrder(0);
BUSY_BEGIN_SOFT;
double origRate=got.rate;
got.rate=zsmrate & 0xffff;
// determine loop point
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
walkSong(loopOrder,loopRow,loopEnd);
logI("loop point: %d %d",loopOrder,loopRow);
warnings="";
DivZSM zsm;
zsm.init(zsmrate);
// reset the playback state
curOrder=0;
freelance=false;
playing=false;
extValuePresent=false;
remainingLoops=-1;
// Prepare to write song data
playSub(false);
//size_t tickCount=0;
bool done=false;
int loopPos=-1;
int writeCount=0;
int fracWait=0; // accumulates fractional ticks
if (VERA >= 0) disCont[VERA].dispatch->toggleRegisterDump(true);
if (YM >= 0) {
disCont[YM].dispatch->toggleRegisterDump(true);
// emit LFO initialization commands
zsm.writeYM(0x18,0); // freq = 0
zsm.writeYM(0x19,0x7F); // AMD = 7F
zsm.writeYM(0x19,0xFF); // PMD = 7F
// TODO: incorporate the Furnace meta-command for init data and filter
// out writes to otherwise-unused channels.
}
while (!done) {
if (loopPos==-1) {
if (loopOrder==curOrder && loopRow==curRow && ticks==1 && loop) {
loopPos=zsm.getoffset();
zsm.setLoopPoint();
}
}
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++) {
int i=0;
// dump YM writes first
if (j==0) {
if (YM < 0)
continue;
else
i=YM;
}
// dump VERA writes second
if (j==1) {
if (VERA < 0)
continue;
else {
i=VERA;
}
}
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
if (writes.size() > 0)
logD("zsmOps: Writing %d messages to chip %d",writes.size(), i);
for (DivRegWrite& write: writes) {
if (i==YM) zsm.writeYM(write.addr&0xff, write.val);
if (i==VERA) zsm.writePSG(write.addr&0xff, write.val);
writeCount++;
}
writes.clear();
}
// write wait
int totalWait=cycles>>MASTER_CLOCK_PREC;
fracWait += cycles & MASTER_CLOCK_MASK;
totalWait += fracWait>>MASTER_CLOCK_PREC;
fracWait &= MASTER_CLOCK_MASK;
if (totalWait>0) {
zsm.tick(totalWait);
//tickCount+=totalWait;
}
}
// end of song
// done - close out.
got.rate = origRate;
if (VERA >= 0) disCont[VERA].dispatch->toggleRegisterDump(false);
if (YM >= 0) disCont[YM].dispatch->toggleRegisterDump(false);
remainingLoops=-1;
playing=false;
freelance=false;
extValuePresent=false;
BUSY_END;
return zsm.finish();
}