furnace/src/engine/zsmOps.cpp

209 lines
5.9 KiB
C++
Raw Normal View History

2022-05-26 05:24:21 +00:00
/**
* Furnace Tracker - multi-system chiptune tracker
2024-01-17 02:26:57 +00:00
* Copyright (C) 2021-2024 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="";
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;
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);
// 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
// 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
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) {
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.
// 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);
if (i==VERA) {
if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead
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;
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();
}