From 8006e40e26abead1d5c1e0836e729d4464308958 Mon Sep 17 00:00:00 2001 From: MooingLemur Date: Fri, 11 Aug 2023 01:02:18 -0700 Subject: [PATCH] ZSM: include song tuning in export --- papers/zsm-format.md | 3 ++- src/engine/zsm.cpp | 25 ++++++++++++++++--------- src/engine/zsm.h | 3 ++- src/engine/zsmOps.cpp | 10 ++++++++++ 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/papers/zsm-format.md b/papers/zsm-format.md index e0fd2c02..682e63b4 100644 --- a/papers/zsm-format.md +++ b/papers/zsm-format.md @@ -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` diff --git a/src/engine/zsm.cpp b/src/engine/zsm.cpp index 04bf68b9..660ccd14 100644 --- a/src/engine/zsm.cpp +++ b/src/engine/zsm.cpp @@ -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; diff --git a/src/engine/zsm.h b/src/engine/zsm.h index ff497693..d9d4f262 100644 --- a/src/engine/zsm.h +++ b/src/engine/zsm.h @@ -63,7 +63,7 @@ class DivZSM { std::vector pcmData; std::vector pcmCache; std::vector pcmInsts; - std::vector syncCache; + std::vector 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(); diff --git a/src/engine/zsmOps.cpp b/src/engine/zsmOps.cpp index 92b8aecb..12484744 100644 --- a/src/engine/zsmOps.cpp +++ b/src/engine/zsmOps.cpp @@ -107,6 +107,16 @@ 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 + signed char tuningoffset=(signed char)round(3072*(log(song.tuning/440.0)/log(2))); + zsm.writeSync(0x01,tuningoffset); while (!done) { if (loopPos==-1) {