Merge pull request #1199 from mooinglemur/20230705-zsmsync

VERA, ZSM Export: Add EExx event as synchronization message, add sync message support in ZSM export
This commit is contained in:
tildearrow 2023-07-06 03:41:06 -05:00 committed by GitHub
commit 3d79827d55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 67 additions and 24 deletions

View file

@ -236,6 +236,8 @@ enum DivDispatchCmds {
DIV_CMD_NES_LINEAR_LENGTH,
DIV_CMD_EXTERNAL, // (value)
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_MAX

View file

@ -37,6 +37,7 @@ extern "C" {
#define rWritePCMRate(d) {regPool[65]=(d); pcm_write_rate(pcm,d);if (dumpWrites) addWrite(65,(d));}
#define rWritePCMData(d) {regPool[66]=(d); pcm_write_fifo(pcm,d);}
#define rWritePCMVol(d) rWritePCMCtrl((regPool[64]&(~0x8f))|((d)&15))
#define rWriteZSMSync(d) {if (dumpWrites) addWrite(68,(d));}
const char* regCheatSheetVERA[]={
"CHxFreq", "00+x*4",
@ -46,6 +47,7 @@ const char* regCheatSheetVERA[]={
"AUDIO_CTRL", "40",
"AUDIO_RATE", "41",
"AUDIO_DATA", "42",
"ZSM_SYNC", "44",
NULL
};
@ -414,6 +416,9 @@ int DivPlatformVERA::dispatch(DivCommand c) {
case DIV_CMD_MACRO_ON:
chan[c.chan].std.mask(c.value,false);
break;
case DIV_CMD_EXTERNAL:
rWriteZSMSync(c.value);
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;

View file

@ -51,7 +51,7 @@ class DivPlatformVERA: public DivDispatch {
Channel chan[17];
DivDispatchOscBuffer* oscBuf[17];
bool isMuted[17];
unsigned char regPool[67];
unsigned char regPool[69];
struct VERA_PSG* psg;
struct VERA_PCM* pcm;

View file

@ -236,6 +236,8 @@ const char* cmdName[]={
"NES_LINEAR_LENGTH",
"EXTERNAL",
"ALWAYS_SET_VOLUME"
};
@ -913,6 +915,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
//printf("\x1b[1;36m%d: extern command %d\x1b[m\n",i,effectVal);
extValue=effectVal;
extValuePresent=true;
dispatchCmd(DivCommand(DIV_CMD_EXTERNAL,effectVal));
break;
case 0xef: // global pitch
globalPitch+=(signed char)(effectVal-0x80);

View file

@ -118,9 +118,13 @@ void DivZSM::writePSG(unsigned char a, unsigned char v) {
// TODO: suppress writes to PSG voice that is not audible (volume=0)
// ^ Let's leave these alone, ZSMKit has a feature that can benefit
// from silent channels.
if (a>=67) {
logD ("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v);
if (a>=69) {
logD("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v);
return;
} else if (a==68) {
// Sync event
numWrites++;
return syncCache.push_back(v);
} else if (a>=64) {
return writePCM(a-64,v);
}
@ -259,7 +263,7 @@ SafeWriter* DivZSM::finish() {
}
void DivZSM::flushWrites() {
logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d pcmMeta=%d pcmCache=%d pcmData=%d",numWrites,ticks,ymwrites.size(),pcmMeta.size(),pcmCache.size(),pcmData.size());
logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d pcmMeta=%d pcmCache=%d pcmData=%d syncCache=%d",numWrites,ticks,ymwrites.size(),pcmMeta.size(),pcmCache.size(),pcmData.size(),syncCache.size());
if (numWrites==0) return;
flushTicks(); // only flush ticks if there are writes pending.
for (unsigned char i=0; i<64; i++) {
@ -287,43 +291,43 @@ void DivZSM::flushWrites() {
unsigned int pcmInst=0;
int pcmOff=0;
int pcmLen=0;
int extCmdLen=pcmMeta.size()*2;
int extCmd0Len=pcmMeta.size()*2;
if (pcmCache.size()) {
// collapse stereo data to mono if both channels are fully identical
// which cuts PCM data size in half for center-panned PCM events
if (pcmCtrlDCCache & 0x10) { // stereo bit is on
if (pcmCtrlDCCache&0x10) { // stereo bit is on
unsigned int e;
if (pcmCtrlDCCache & 0x20) { // 16-bit
if (pcmCtrlDCCache&0x20) { // 16-bit
// for 16-bit PCM data, the size must be a multiple of 4
if (pcmCache.size()%4==0) {
// check for identical L+R channels
for (e=0;e<pcmCache.size();e+=4) {
for (e=0; e<pcmCache.size(); e+=4) {
if (pcmCache[e]!=pcmCache[e+2] || pcmCache[e+1]!=pcmCache[e+3]) break;
}
if (e==pcmCache.size()) { // did not find a mismatch
// collapse the data to mono 16-bit
for (e=0;e<pcmCache.size()>>1;e+=2) {
for (e=0; e<pcmCache.size()>>1; e+=2) {
pcmCache[e]=pcmCache[e<<1];
pcmCache[e+1]=pcmCache[(e<<1)+1];
}
pcmCache.resize(pcmCache.size()>>1);
pcmCtrlDCCache &= ~0x10; // clear stereo bit
pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit
}
}
} else { // 8-bit
// for 8-bit PCM data, the size must be a multiple of 2
if (pcmCache.size()%2==0) {
// check for identical L+R channels
for (e=0;e<pcmCache.size();e+=2) {
for (e=0; e<pcmCache.size(); e+=2) {
if (pcmCache[e]!=pcmCache[e+1]) break;
}
if (e==pcmCache.size()) { // did not find a mismatch
// collapse the data to mono 8-bit
for (e=0;e<pcmCache.size()>>1;e++) {
for (e=0; e<pcmCache.size()>>1; e++) {
pcmCache[e]=pcmCache[e<<1];
}
pcmCache.resize(pcmCache.size()>>1);
pcmCtrlDCCache &= ~0x10; // clear stereo bit
pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit
}
}
}
@ -340,7 +344,7 @@ void DivZSM::flushWrites() {
pcmData.insert(pcmData.end(),pcmCache.begin(),pcmCache.end());
}
pcmCache.clear();
extCmdLen+=2;
extCmd0Len+=2;
// search for a matching PCM instrument definition
for (S_pcmInst& inst: pcmInsts) {
if (inst.offset==pcmOff && inst.length==pcmLen && inst.geometry==pcmCtrlDCCache)
@ -355,14 +359,14 @@ void DivZSM::flushWrites() {
pcmInsts.push_back(inst);
}
}
if (extCmdLen>63) { // this would be bad, but will almost certainly never happen
logE("ZSM: extCmd exceeded maximum length of 63: %d",extCmdLen);
extCmdLen=0;
if (extCmd0Len>63) { // this would be bad, but will almost certainly never happen
logE("ZSM: extCmd 0 exceeded maximum length of 63: %d",extCmd0Len);
extCmd0Len=0;
pcmMeta.clear();
}
if (extCmdLen) { // we have some PCM events to write
w->writeC(0x40);
w->writeC((unsigned char)extCmdLen); // the high two bits are guaranteed to be zero, meaning this is a PCM command
if (extCmd0Len) { // we have some PCM events to write
w->writeC(ZSM_EXT);
w->writeC(ZSM_EXT_PCM|(unsigned char)extCmd0Len);
for (DivRegWrite& write: pcmMeta) {
w->writeC(write.addr);
w->writeC(write.val);
@ -373,6 +377,18 @@ void DivZSM::flushWrites() {
w->writeC((unsigned char)pcmInst&0xff);
}
}
n=0;
while (n<(long)syncCache.size()) { // we have one or more sync events to write
int writes=syncCache.size()-n;
w->writeC(ZSM_EXT);
if (writes>ZSM_SYNC_MAX_WRITES) writes=ZSM_SYNC_MAX_WRITES;
w->writeC(ZSM_EXT_SYNC|(writes<<1));
for (; writes>0; writes--) {
w->writeC(0x00); // 0x00 = Arbitrary sync message
w->writeC(syncCache[n++]);
}
}
syncCache.clear();
numWrites=0;
}

View file

@ -30,9 +30,16 @@
#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 };
@ -52,6 +59,7 @@ class DivZSM {
std::vector<unsigned char> pcmData;
std::vector<unsigned char> pcmCache;
std::vector<S_pcmInst> pcmInsts;
std::vector<unsigned char> syncCache;
int loopOffset;
int numWrites;
int ticks;

View file

@ -151,7 +151,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
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 events on the loop lookahead
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);
}
}