From 8006e40e26abead1d5c1e0836e729d4464308958 Mon Sep 17 00:00:00 2001 From: MooingLemur Date: Fri, 11 Aug 2023 01:02:18 -0700 Subject: [PATCH 1/4] 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 e0fd2c02d..682e63b46 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 04bf68b91..660ccd14e 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 ff497693f..d9d4f2620 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 92b8aecb8..124847446 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) { From 245fe1c092d94d56c31b408373543c887a3b6d9e Mon Sep 17 00:00:00 2001 From: MooingLemur Date: Fri, 11 Aug 2023 01:19:25 -0700 Subject: [PATCH 2/4] fix cast --- src/engine/zsm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/zsm.cpp b/src/engine/zsm.cpp index 660ccd14e..96d39a4a0 100644 --- a/src/engine/zsm.cpp +++ b/src/engine/zsm.cpp @@ -398,9 +398,9 @@ void DivZSM::flushWrites() { 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)); + w->writeC((unsigned char)(ZSM_EXT_SYNC|(ZSM_SYNC_MAX_WRITES<<1))); } else { - w->writeC((unsigned char)ZSM_EXT_SYNC|((syncCache.size()-n)<<1)); + w->writeC((unsigned char)(ZSM_EXT_SYNC|((syncCache.size()-n)<<1))); } } n++; From 2365321d46540dd9ec2a4307b1a38b33304368fe Mon Sep 17 00:00:00 2001 From: MooingLemur Date: Fri, 11 Aug 2023 01:30:45 -0700 Subject: [PATCH 3/4] make clear the tuningoffset is okay to overflow --- src/engine/zsmOps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/zsmOps.cpp b/src/engine/zsmOps.cpp index 124847446..c85186253 100644 --- a/src/engine/zsmOps.cpp +++ b/src/engine/zsmOps.cpp @@ -115,7 +115,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { // 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))); + signed char tuningoffset=(signed char)(round(3072*(log(song.tuning/440.0)/log(2))))&0xff; zsm.writeSync(0x01,tuningoffset); while (!done) { From 94383fae631ff533154ebc7207343d8bcb6fdacb Mon Sep 17 00:00:00 2001 From: MooingLemur Date: Fri, 11 Aug 2023 01:31:53 -0700 Subject: [PATCH 4/4] clarify comment --- src/engine/zsmOps.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/zsmOps.cpp b/src/engine/zsmOps.cpp index c85186253..6b360976b 100644 --- a/src/engine/zsmOps.cpp +++ b/src/engine/zsmOps.cpp @@ -115,6 +115,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) { // 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);