Merge pull request #1347 from mooinglemur/20230811-zsmtuning

ZSM: include song tuning in export
This commit is contained in:
tildearrow 2023-08-11 03:32:19 -05:00 committed by GitHub
commit 0073357cb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 31 additions and 11 deletions

View file

@ -162,11 +162,12 @@ There are currently no supported expansion HW IDs assigned.
The purpose of this channel is to provide for music synchronization cues that applications may use to perform operations in sync with the music (such as when the Goombas jump in New Super Mario Bros in time with the BOP! BOP! notes in the music). It is intended for the reference player to provide a sync channel callback, passing the data bytes to the callback function, and then to proceed with playback.
The synchronization format currently defines this one event type:
The synchronization format currently defines these event types:
Event Type|Description|Message Format
--|--|--
`0x00`|Generic sync message|`xx` (any value from `0x00`-`0xff`)
`0x01`|Song tuning|a signed byte indicating an offset from A-440 tuning represented in 256ths of a semitone
An example of an EXTCMD containing one sync event might look as follows: `0x40 0x82 0x00 0x05`

View file

@ -116,6 +116,10 @@ void DivZSM::writeYM(unsigned char a, unsigned char v) {
}
}
void DivZSM::writeSync(unsigned char a, unsigned char v) {
return syncCache.push_back(DivRegWrite(a,v));
}
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
@ -126,7 +130,7 @@ void DivZSM::writePSG(unsigned char a, unsigned char v) {
} else if (a==68) {
// Sync event
numWrites++;
return syncCache.push_back(v);
return writeSync(0x00,v);
} else if (a>=64) {
return writePCM(a-64,v);
}
@ -390,15 +394,18 @@ void DivZSM::flushWrites() {
}
}
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++]);
for (DivRegWrite& write: syncCache) {
if (n%ZSM_SYNC_MAX_WRITES==0) {
w->writeC(ZSM_EXT);
if (syncCache.size()-n>ZSM_SYNC_MAX_WRITES) {
w->writeC((unsigned char)(ZSM_EXT_SYNC|(ZSM_SYNC_MAX_WRITES<<1)));
} else {
w->writeC((unsigned char)(ZSM_EXT_SYNC|((syncCache.size()-n)<<1)));
}
}
n++;
w->writeC(write.addr);
w->writeC(write.val);
}
syncCache.clear();
numWrites=0;

View file

@ -63,7 +63,7 @@ class DivZSM {
std::vector<unsigned char> pcmData;
std::vector<unsigned char> pcmCache;
std::vector<S_pcmInst> pcmInsts;
std::vector<unsigned char> syncCache;
std::vector<DivRegWrite> syncCache;
int loopOffset;
int numWrites;
int ticks;
@ -78,6 +78,7 @@ class DivZSM {
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 tick(int numticks = 1);
void setLoopPoint();
SafeWriter* finish();

View file

@ -107,6 +107,17 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
// 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);
while (!done) {
if (loopPos==-1) {