Merge branch 'tildearrow:master' into SID3
This commit is contained in:
commit
c26fa0c1f6
19 changed files with 549 additions and 484 deletions
|
|
@ -669,6 +669,7 @@ class DivEngine {
|
|||
friend class DivROMExport;
|
||||
friend class DivExportAmigaValidation;
|
||||
friend class DivExportTiuna;
|
||||
friend class DivExportZSM;
|
||||
|
||||
public:
|
||||
DivSong song;
|
||||
|
|
@ -721,8 +722,6 @@ class DivEngine {
|
|||
// - -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.
|
||||
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true, bool optimize=true);
|
||||
// dump to TIunA.
|
||||
SafeWriter* saveTiuna(const bool* sysToExport, const char* baseLabel, int firstBankSize, int otherBankSize);
|
||||
// dump command stream.
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "export/amigaValidation.h"
|
||||
#include "export/tiuna.h"
|
||||
#include "export/zsm.h"
|
||||
|
||||
DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
|
||||
DivROMExport* exporter=NULL;
|
||||
|
|
@ -31,6 +32,9 @@ DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
|
|||
case DIV_ROM_TIUNA:
|
||||
exporter=new DivExportTiuna;
|
||||
break;
|
||||
case DIV_ROM_ZSM:
|
||||
exporter=new DivExportZSM;
|
||||
break;
|
||||
default:
|
||||
exporter=new DivROMExport;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -18,9 +18,77 @@
|
|||
*/
|
||||
|
||||
#include "zsm.h"
|
||||
#include "../engine.h"
|
||||
#include "../ta-log.h"
|
||||
#include "../utfutils.h"
|
||||
#include "song.h"
|
||||
#include <fmt/printf.h>
|
||||
|
||||
/// DivZSM definitions
|
||||
|
||||
#define ZSM_HEADER_SIZE 16
|
||||
#define ZSM_VERSION 1
|
||||
#define ZSM_YM_CMD 0x40
|
||||
#define ZSM_DELAY_CMD 0x80
|
||||
#define ZSM_YM_MAX_WRITES 63
|
||||
#define ZSM_SYNC_MAX_WRITES 31
|
||||
#define ZSM_DELAY_MAX 127
|
||||
#define ZSM_EOF ZSM_DELAY_CMD
|
||||
|
||||
#define ZSM_EXT ZSM_YM_CMD
|
||||
#define ZSM_EXT_PCM 0x00
|
||||
#define ZSM_EXT_CHIP 0x40
|
||||
#define ZSM_EXT_SYNC 0x80
|
||||
#define ZSM_EXT_CUSTOM 0xC0
|
||||
|
||||
enum YM_STATE { ym_PREV, ym_NEW, ym_STATES };
|
||||
enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES };
|
||||
|
||||
class DivZSM {
|
||||
private:
|
||||
struct S_pcmInst {
|
||||
int geometry;
|
||||
unsigned int offset, length, loopPoint;
|
||||
bool isLooped;
|
||||
};
|
||||
SafeWriter* w;
|
||||
int ymState[ym_STATES][256];
|
||||
int psgState[psg_STATES][64];
|
||||
int pcmRateCache;
|
||||
int pcmCtrlRVCache;
|
||||
int pcmCtrlDCCache;
|
||||
unsigned int pcmLoopPointCache;
|
||||
bool pcmIsLooped;
|
||||
std::vector<DivRegWrite> ymwrites;
|
||||
std::vector<DivRegWrite> pcmMeta;
|
||||
std::vector<unsigned char> pcmData;
|
||||
std::vector<unsigned char> pcmCache;
|
||||
std::vector<S_pcmInst> pcmInsts;
|
||||
std::vector<DivRegWrite> syncCache;
|
||||
int loopOffset;
|
||||
int numWrites;
|
||||
int ticks;
|
||||
int tickRate;
|
||||
int ymMask;
|
||||
int psgMask;
|
||||
bool optimize;
|
||||
public:
|
||||
DivZSM();
|
||||
~DivZSM();
|
||||
void init(unsigned int rate = 60);
|
||||
int getoffset();
|
||||
void writeYM(unsigned char a, unsigned char v);
|
||||
void writePSG(unsigned char a, unsigned char v);
|
||||
void writePCM(unsigned char a, unsigned char v);
|
||||
void writeSync(unsigned char a, unsigned char v);
|
||||
void setOptimize(bool o);
|
||||
void tick(int numticks = 1);
|
||||
void setLoopPoint();
|
||||
SafeWriter* finish();
|
||||
private:
|
||||
void flushWrites();
|
||||
void flushTicks();
|
||||
};
|
||||
|
||||
/// DivZSM implementation
|
||||
|
||||
DivZSM::DivZSM() {
|
||||
w=NULL;
|
||||
|
|
@ -447,3 +515,240 @@ void DivZSM::flushTicks() {
|
|||
}
|
||||
ticks=0;
|
||||
}
|
||||
|
||||
/// ZSM export
|
||||
|
||||
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
||||
constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0;
|
||||
|
||||
void DivExportZSM::run() {
|
||||
// settings
|
||||
unsigned int zsmrate=conf.getInt("zsmrate",60);
|
||||
bool loop=conf.getBool("loop",true);
|
||||
bool optimize=conf.getBool("optimize",true);
|
||||
|
||||
// system IDs
|
||||
int VERA=-1;
|
||||
int YM=-1;
|
||||
int IGNORED=0;
|
||||
|
||||
// find indexes for YM and VERA. Ignore other systems.
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
switch (e->song.system[i]) {
|
||||
case DIV_SYSTEM_VERA:
|
||||
if (VERA>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
VERA=i;
|
||||
logAppendf("VERA detected as chip id %d",i);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2151:
|
||||
if (YM>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
YM=i;
|
||||
logAppendf("YM detected as chip id %d",i);
|
||||
break;
|
||||
default:
|
||||
IGNORED++;
|
||||
logAppendf("Ignoring chip %d systemID %d",i,(int)e->song.system[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (VERA<0 && YM<0) {
|
||||
logAppend("ERROR: No supported systems for ZSM");
|
||||
failed=true;
|
||||
running=false;
|
||||
return;
|
||||
}
|
||||
if (IGNORED>0) {
|
||||
logAppendf("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
|
||||
}
|
||||
|
||||
DivZSM zsm;
|
||||
|
||||
e->stop();
|
||||
e->repeatPattern=false;
|
||||
e->setOrder(0);
|
||||
e->synchronizedSoft([&]() {
|
||||
double origRate=e->got.rate;
|
||||
e->got.rate=zsmrate&0xffff;
|
||||
|
||||
// determine loop point
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
logAppendf("loop point: %d %d",loopOrder,loopRow);
|
||||
|
||||
zsm.init(zsmrate);
|
||||
|
||||
// reset the playback state
|
||||
e->curOrder=0;
|
||||
e->freelance=false;
|
||||
e->playing=false;
|
||||
e->extValuePresent=false;
|
||||
e->remainingLoops=-1;
|
||||
|
||||
// Prepare to write song data
|
||||
e->playSub(false);
|
||||
//size_t tickCount=0;
|
||||
bool done=false;
|
||||
bool loopNow=false;
|
||||
int loopPos=-1;
|
||||
int fracWait=0; // accumulates fractional ticks
|
||||
if (VERA>=0) e->disCont[VERA].dispatch->toggleRegisterDump(true);
|
||||
if (YM>=0) {
|
||||
e->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.
|
||||
}
|
||||
// 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
|
||||
// by nature of overflowing the signed char value
|
||||
signed char tuningoffset=(signed char)(round(3072*(log(e->song.tuning/440.0)/log(2))))&0xff;
|
||||
zsm.writeSync(0x01,tuningoffset);
|
||||
// Set optimize flag, which mainly buffers PSG writes
|
||||
// whenever the channel is silent
|
||||
zsm.setOptimize(optimize);
|
||||
|
||||
while (!done) {
|
||||
if (loopPos==-1) {
|
||||
if (loopOrder==e->curOrder && loopRow==e->curRow && loop)
|
||||
loopNow=true;
|
||||
if (loopNow) {
|
||||
// If Virtual Tempo is in use, our exact loop point
|
||||
// might be skipped due to quantization error.
|
||||
// If this happens, the tick immediately following is our loop point.
|
||||
if (e->ticks==1 || !(loopOrder==e->curOrder && loopRow==e->curRow)) {
|
||||
loopPos=zsm.getoffset();
|
||||
zsm.setLoopPoint();
|
||||
loopNow=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e->nextTick() || !e->playing) {
|
||||
done=true;
|
||||
if (!loop) {
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
e->disCont[i].dispatch->getRegisterWrites().clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!e->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=e->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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
writes.clear();
|
||||
}
|
||||
|
||||
// write wait
|
||||
int totalWait=e->cycles>>MASTER_CLOCK_PREC;
|
||||
fracWait+=e->cycles&MASTER_CLOCK_MASK;
|
||||
totalWait+=fracWait>>MASTER_CLOCK_PREC;
|
||||
fracWait&=MASTER_CLOCK_MASK;
|
||||
if (totalWait>0 && !done) {
|
||||
zsm.tick(totalWait);
|
||||
//tickCount+=totalWait;
|
||||
}
|
||||
}
|
||||
// end of song
|
||||
|
||||
// done - close out.
|
||||
e->got.rate=origRate;
|
||||
if (VERA>=0) e->disCont[VERA].dispatch->toggleRegisterDump(false);
|
||||
if (YM>=0) e->disCont[YM].dispatch->toggleRegisterDump(false);
|
||||
|
||||
e->remainingLoops=-1;
|
||||
e->playing=false;
|
||||
e->freelance=false;
|
||||
e->extValuePresent=false;
|
||||
});
|
||||
|
||||
progress[0].amount=1.0f;
|
||||
|
||||
logAppend("finished!");
|
||||
|
||||
output.push_back(DivROMExportOutput("out.zsm",zsm.finish()));
|
||||
running=false;
|
||||
}
|
||||
|
||||
/// DivExpottZSM - FRONTEND
|
||||
|
||||
bool DivExportZSM::go(DivEngine* eng) {
|
||||
progress[0].name="Generate";
|
||||
progress[0].amount=0.0f;
|
||||
|
||||
e=eng;
|
||||
running=true;
|
||||
failed=false;
|
||||
mustAbort=false;
|
||||
exportThread=new std::thread(&DivExportZSM::run,this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivExportZSM::wait() {
|
||||
if (exportThread!=NULL) {
|
||||
exportThread->join();
|
||||
delete exportThread;
|
||||
}
|
||||
}
|
||||
|
||||
void DivExportZSM::abort() {
|
||||
mustAbort=true;
|
||||
wait();
|
||||
}
|
||||
|
||||
bool DivExportZSM::isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
bool DivExportZSM::hasFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
DivROMExportProgress DivExportZSM::getProgress(int index) {
|
||||
if (index<0 || index>1) return progress[1];
|
||||
return progress[index];
|
||||
}
|
||||
38
src/engine/export/zsm.h
Normal file
38
src/engine/export/zsm.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 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 "../export.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
class DivExportZSM: public DivROMExport {
|
||||
DivEngine* e;
|
||||
std::thread* exportThread;
|
||||
DivROMExportProgress progress[2];
|
||||
bool running, failed, mustAbort;
|
||||
void run();
|
||||
public:
|
||||
bool go(DivEngine* e);
|
||||
bool isRunning();
|
||||
bool hasFailed();
|
||||
void abort();
|
||||
void wait();
|
||||
DivROMExportProgress getProgress(int index=0);
|
||||
~DivExportZSM() {}
|
||||
};
|
||||
|
|
@ -401,8 +401,8 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
|
|||
sample->loop=false;
|
||||
}
|
||||
|
||||
if (sample->centerRate<4000) sample->centerRate=4000;
|
||||
if (sample->centerRate>64000) sample->centerRate=64000;
|
||||
if (sample->centerRate<100) sample->centerRate=100;
|
||||
if (sample->centerRate>384000) sample->centerRate=384000;
|
||||
sfWrap.doClose();
|
||||
BUSY_END;
|
||||
ret.push_back(sample);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "sfWrapper.h"
|
||||
#include "../fileutils.h"
|
||||
#include "../ta-log.h"
|
||||
#include "sndfile.h"
|
||||
|
||||
sf_count_t _vioGetSize(void* user) {
|
||||
|
|
@ -80,6 +81,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
vio.seek=_vioSeek;
|
||||
vio.tell=_vioTell;
|
||||
vio.write=_vioWrite;
|
||||
logV("SFWrapper: opening %s",path);
|
||||
|
||||
const char* modeC="rb";
|
||||
if (mode==SFM_WRITE) {
|
||||
|
|
@ -91,10 +93,12 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
|
||||
f=ps_fopen(path,modeC);
|
||||
if (f==NULL) {
|
||||
logE("SFWrapper: failed to open (%s)",strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_END)==-1) {
|
||||
logE("SFWrapper: failed to seek to end (%s)",strerror(errno));
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
return NULL;
|
||||
|
|
@ -102,6 +106,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
|
||||
len=ftell(f);
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
logE("SFWrapper: failed to tell (%s)",strerror(errno));
|
||||
len=0;
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
|
|
@ -109,6 +114,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
}
|
||||
|
||||
if (fseek(f,0,SEEK_SET)==-1) {
|
||||
logE("SFWrapper: failed to seek to beginning (%s)",strerror(errno));
|
||||
len=0;
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
|
|
@ -117,5 +123,8 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
|
||||
sf=sf_open_virtual(&vio,mode,sfinfo,this);
|
||||
if (sf!=NULL) fileMode=mode;
|
||||
if (sf==NULL) {
|
||||
logE("SFWrapper: WHY IS IT NULL?!");
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 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.
|
||||
*/
|
||||
|
||||
#ifndef _ZSM_H
|
||||
#define _ZSM_H
|
||||
|
||||
//#include "engine.h"
|
||||
#include "safeWriter.h"
|
||||
#include "dispatch.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ZSM_HEADER_SIZE 16
|
||||
#define ZSM_VERSION 1
|
||||
#define ZSM_YM_CMD 0x40
|
||||
#define ZSM_DELAY_CMD 0x80
|
||||
#define ZSM_YM_MAX_WRITES 63
|
||||
#define ZSM_SYNC_MAX_WRITES 31
|
||||
#define ZSM_DELAY_MAX 127
|
||||
#define ZSM_EOF ZSM_DELAY_CMD
|
||||
|
||||
#define ZSM_EXT ZSM_YM_CMD
|
||||
#define ZSM_EXT_PCM 0x00
|
||||
#define ZSM_EXT_CHIP 0x40
|
||||
#define ZSM_EXT_SYNC 0x80
|
||||
#define ZSM_EXT_CUSTOM 0xC0
|
||||
|
||||
enum YM_STATE { ym_PREV, ym_NEW, ym_STATES };
|
||||
enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES };
|
||||
|
||||
class DivZSM {
|
||||
private:
|
||||
struct S_pcmInst {
|
||||
int geometry;
|
||||
unsigned int offset, length, loopPoint;
|
||||
bool isLooped;
|
||||
};
|
||||
SafeWriter* w;
|
||||
int ymState[ym_STATES][256];
|
||||
int psgState[psg_STATES][64];
|
||||
int pcmRateCache;
|
||||
int pcmCtrlRVCache;
|
||||
int pcmCtrlDCCache;
|
||||
unsigned int pcmLoopPointCache;
|
||||
bool pcmIsLooped;
|
||||
std::vector<DivRegWrite> ymwrites;
|
||||
std::vector<DivRegWrite> pcmMeta;
|
||||
std::vector<unsigned char> pcmData;
|
||||
std::vector<unsigned char> pcmCache;
|
||||
std::vector<S_pcmInst> pcmInsts;
|
||||
std::vector<DivRegWrite> syncCache;
|
||||
int loopOffset;
|
||||
int numWrites;
|
||||
int ticks;
|
||||
int tickRate;
|
||||
int ymMask;
|
||||
int psgMask;
|
||||
bool optimize;
|
||||
public:
|
||||
DivZSM();
|
||||
~DivZSM();
|
||||
void init(unsigned int rate = 60);
|
||||
int getoffset();
|
||||
void writeYM(unsigned char a, unsigned char v);
|
||||
void writePSG(unsigned char a, unsigned char v);
|
||||
void writePCM(unsigned char a, unsigned char v);
|
||||
void writeSync(unsigned char a, unsigned char v);
|
||||
void setOptimize(bool o);
|
||||
void tick(int numticks = 1);
|
||||
void setLoopPoint();
|
||||
SafeWriter* finish();
|
||||
private:
|
||||
void flushWrites();
|
||||
void flushTicks();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 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, bool optimize) {
|
||||
int VERA=-1;
|
||||
int YM=-1;
|
||||
int IGNORED=0;
|
||||
|
||||
// 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,(int)song.system[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
bool loopNow=false;
|
||||
int loopPos=-1;
|
||||
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.
|
||||
}
|
||||
// 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
|
||||
// by nature of overflowing the signed char value
|
||||
signed char tuningoffset=(signed char)(round(3072*(log(song.tuning/440.0)/log(2))))&0xff;
|
||||
zsm.writeSync(0x01,tuningoffset);
|
||||
// Set optimize flag, which mainly buffers PSG writes
|
||||
// whenever the channel is silent
|
||||
zsm.setOptimize(optimize);
|
||||
|
||||
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
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
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 && !done) {
|
||||
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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue