summaryrefslogtreecommitdiff
path: root/plugins/sid/sidplay-libs/libsidplay/src/xsid/xsid.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/sid/sidplay-libs/libsidplay/src/xsid/xsid.cpp')
-rw-r--r--plugins/sid/sidplay-libs/libsidplay/src/xsid/xsid.cpp598
1 files changed, 598 insertions, 0 deletions
diff --git a/plugins/sid/sidplay-libs/libsidplay/src/xsid/xsid.cpp b/plugins/sid/sidplay-libs/libsidplay/src/xsid/xsid.cpp
new file mode 100644
index 00000000..29b5c909
--- /dev/null
+++ b/plugins/sid/sidplay-libs/libsidplay/src/xsid/xsid.cpp
@@ -0,0 +1,598 @@
+/***************************************************************************
+ xsid.cpp - Support for Playsids Extended
+ Registers
+ -------------------
+ begin : Tue Jun 20 2000
+ copyright : (C) 2000 by Simon White
+ email : s_a_white@email.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+/***************************************************************************
+ * $Log: xsid.cpp,v $
+ * Revision 1.20 2002/07/17 21:19:54 s_a_white
+ * Minor non sid sample mode fixes.
+ *
+ * Revision 1.19 2002/02/21 20:26:13 s_a_white
+ * Nolonger default to Galway Mode when Noise samples init incorrectly. Fixes
+ * VARIOUS/S-Z/Zyron/Bouncy_Balls.sid (HVSC).
+ *
+ * Revision 1.18 2002/02/17 16:34:39 s_a_white
+ * New reset interface
+ *
+ * Revision 1.17 2002/01/28 19:31:42 s_a_white
+ * PSID sample improvements.
+ *
+ * Revision 1.16 2001/10/02 18:03:03 s_a_white
+ * Support updated sidbuilder class interface.
+ *
+ * Revision 1.15 2001/09/17 18:36:41 s_a_white
+ * Changed object construction to prevent multiple resets.
+ *
+ * Revision 1.14 2001/07/14 12:59:53 s_a_white
+ * XSID effeciency increased. Now uses new component classes and event
+ * generation.
+ *
+ * Revision 1.13 2001/03/25 19:51:23 s_a_white
+ * Performance update.
+ *
+ * Revision 1.12 2001/03/19 23:40:19 s_a_white
+ * Removed repeat definition of state for debug mode.
+ *
+ * Revision 1.11 2001/03/09 22:27:13 s_a_white
+ * Speed optimisation update.
+ *
+ * Revision 1.10 2001/03/01 23:45:58 s_a_white
+ * Combined both through sid and non-through sid modes. Can be selected
+ * at runtime now.
+ *
+ * Revision 1.9 2001/02/21 21:46:34 s_a_white
+ * 0x1d = 0 now fixed. Limit checking on sid volume. This helps us determine
+ * even better what the sample offset should be (fixes Skate and Die).
+ *
+ * Revision 1.8 2001/02/07 21:02:30 s_a_white
+ * Supported for delaying samples for frame simulation. New alogarithm to
+ * better guess original tunes volume when playing samples.
+ *
+ * Revision 1.7 2000/12/12 22:51:01 s_a_white
+ * Bug Fix #122033.
+ *
+ ***************************************************************************/
+
+#include <string.h>
+#include <stdio.h>
+#include "sidendian.h"
+#include "xsid.h"
+
+
+// Convert from 4 bit resolution to 8 bits
+/* Rev 2.0.5 (saw) - Removed for a more non-linear equivalent
+ which better models the SIDS master volume register
+const int8_t XSID::sampleConvertTable[16] =
+{
+ '\x80', '\x91', '\xa2', '\xb3', '\xc4', '\xd5', '\xe6', '\xf7',
+ '\x08', '\x19', '\x2a', '\x3b', '\x4c', '\x5d', '\x6e', '\x7f'
+};
+*/
+const int8_t XSID::sampleConvertTable[16] =
+{
+ '\x80', '\x94', '\xa9', '\xbc', '\xce', '\xe1', '\xf2', '\x03',
+ '\x1b', '\x2a', '\x3b', '\x49', '\x58', '\x66', '\x73', '\x7f'
+};
+
+const char *XSID::credit =
+{
+ "xSID (Extended SID) Engine:\0"
+ "\tCopyright (C) 2000 Simon White <sidplay2@email.com>\0"
+};
+
+
+channel::channel (const char * const name, EventContext *context, XSID *xsid)
+:m_name(name),
+ m_context(*context),
+ m_xsid(*xsid),
+ sampleEvent(this),
+ galwayEvent(this)
+{
+ memset (reg, 0, sizeof (reg));
+ active = true;
+ reset ();
+}
+
+void channel::reset ()
+{
+ galVolume = 0; // This is left to free run until reset
+ mode = FM_NONE;
+ free ();
+}
+
+void channel::free ()
+{
+ active = false;
+ cycleCount = 0;
+ sampleLimit = 0;
+ // Set XSID to stopped state
+ reg[convertAddr (0x1d)] = 0;
+ silence ();
+}
+
+inline int8_t channel::output ()
+{
+ outputs++;
+ return sample;
+}
+
+void channel::checkForInit ()
+{ // Check to see mode of operation
+ // See xsid documentation
+ switch (reg[convertAddr (0x1d)])
+ {
+ case 0xFF:
+ case 0xFE:
+ case 0xFC:
+ sampleInit ();
+ break;
+ case 0xFD:
+ if (!active)
+ return;
+ free (); // Stop
+ // Calculate the sample offset
+ m_xsid.sampleOffsetCalc ();
+ break;
+ case 0x00:
+ break;
+ default:
+ galwayInit ();
+ }
+}
+
+void channel::sampleInit ()
+{
+ uint8_t *r;
+ if (active && (mode == FM_GALWAY))
+ return;
+
+#ifdef XSID_DEBUG
+ printf ("XSID [%s]: Sample Init\n", m_name);
+ if (active && (mode == FM_HUELS))
+ printf ("XSID [%s]: Stopping Playing Sample\n", m_name);
+#endif
+
+ // Check all important parameters are legal
+ r = &reg[convertAddr (0x1d)];
+ volShift = (uint_least8_t) (0 - (int8_t) r[0]) >> 1;
+ r[0] = 0;
+ // Use endian_16 as can't g
+ r = &reg[convertAddr (0x1e)];
+ address = endian_16 (r[1], r[0]);
+ r = &reg[convertAddr (0x3d)];
+ samEndAddr = endian_16 (r[1], r[0]);
+ if (samEndAddr <= address) return;
+ samScale = reg[convertAddr (0x5f)];
+ r = &reg[convertAddr (0x5d)];
+ samPeriod = endian_16 (r[1], r[0]) >> samScale;
+ if (!samPeriod)
+ { // Stop this channel
+ reg[convertAddr (0x1d)] = 0xfd;
+ checkForInit ();
+ return;
+ }
+
+ // Load the other parameters
+ samNibble = 0;
+ samRepeat = reg[convertAddr (0x3f)];
+ samOrder = reg[convertAddr (0x7d)];
+ r = &reg[convertAddr (0x7e)];
+ samRepeatAddr = endian_16 (r[1], r[0]);
+ cycleCount = samPeriod;
+
+ // Support Galway Samples, but that
+ // mode it setup only when as Galway
+ // Noise sequence begins
+ if (mode == FM_NONE)
+ mode = FM_HUELS;
+
+ active = true;
+ cycles = 0;
+ outputs = 0;
+
+ sampleLimit = 8 >> volShift;
+ sample = sampleCalculate ();
+
+ // Calculate the sample offset
+ m_xsid.sampleOffsetCalc ();
+
+#ifdef XSID_DEBUG
+# if XSID_DEBUG > 1
+ printf ("XSID [%s]: Sample Start Address: 0x%04x\n", m_name, address);
+ printf ("XSID [%s]: Sample End Address: 0x%04x\n", m_name, samEndAddr);
+ printf ("XSID [%s]: Sample Repeat Address: 0x%04x\n", m_name, samRepeatAddr);
+ printf ("XSID [%s]: Sample Period: %u\n", m_name, samPeriod);
+ printf ("XSID [%s]: Sample Repeat: %u\n", m_name, samRepeat);
+ printf ("XSID [%s]: Sample Order: %u\n", m_name, samOrder);
+# endif
+ printf ("XSID [%s]: Sample Start\n", m_name);
+#endif // XSID_DEBUG
+
+ // Schedule a sample update
+ m_context.schedule (&m_xsid, 0);
+ m_context.schedule (&sampleEvent, cycleCount);
+}
+
+void channel::sampleClock ()
+{
+ cycleCount = samPeriod;
+ if (address >= samEndAddr)
+ {
+ if (samRepeat != 0xFF)
+ {
+ if (samRepeat)
+ samRepeat--;
+ else
+ samRepeatAddr = address;
+ }
+
+ address = samRepeatAddr;
+ if (address >= samEndAddr)
+ { // The sequence has completed
+ uint8_t &status = reg[convertAddr (0x1d)];
+ if (!status)
+ status = 0xfd;
+ if (status != 0xfd)
+ active = false;
+#ifdef XSID_DEBUG
+ printf ("XSID [%s]: Sample Stop (%lu Cycles, %lu Outputs)\n",
+ m_name, cycles, outputs);
+ if (status != 0xfd)
+ printf ("XSID [%s]: Starting Delayed Sequence\n", m_name);
+#endif
+ checkForInit ();
+ return;
+ }
+ }
+
+ // We have reached the required sample
+ // So now we need to extract the right nibble
+ sample = sampleCalculate ();
+ cycles += cycleCount;
+ // Schedule a sample update
+ m_context.schedule (&sampleEvent, cycleCount);
+ m_context.schedule (&m_xsid, 0);
+}
+
+int8_t channel::sampleCalculate ()
+{
+ uint_least8_t tempSample = m_xsid.readMemByte (address);
+ if (samOrder == SO_LOWHIGH)
+ {
+ if (samScale == 0)
+ {
+ if (samNibble != 0)
+ tempSample >>= 4;
+ }
+ // AND 15 further below.
+ }
+ else // if (samOrder == SO_HIGHLOW)
+ {
+ if (samScale == 0)
+ {
+ if (samNibble == 0)
+ tempSample >>= 4;
+ }
+ else // if (samScale != 0)
+ tempSample >>= 4;
+ // AND 15 further below.
+ }
+
+ // Move to next address
+ address += samNibble;
+ samNibble ^= 1;
+ return (int8_t) ((tempSample & 0x0f) - 0x08) >> volShift;
+}
+
+void channel::galwayInit()
+{
+ uint8_t *r;
+ if (active)
+ return;
+
+#ifdef XSID_DEBUG
+ printf ("XSID [%s]: Galway Init\n", m_name);
+#endif
+
+ // Check all important parameters are legal
+ r = &reg[convertAddr (0x1d)];
+ galTones = r[0];
+ r[0] = 0;
+ galInitLength = reg[convertAddr (0x3d)];
+ if (!galInitLength) return;
+ galLoopWait = reg[convertAddr (0x3f)];
+ if (!galLoopWait) return;
+ galNullWait = reg[convertAddr (0x5d)];
+ if (!galNullWait) return;
+
+ // Load the other parameters
+ r = &reg[convertAddr(0x1e)];
+ address = endian_16 (r[1], r[0]);
+ volShift = reg[convertAddr (0x3e)] & 0x0f;
+ mode = FM_GALWAY;
+ active = true;
+ cycles = 0;
+ outputs = 0;
+
+ sampleLimit = 8;
+ sample = (int8_t) galVolume - 8;
+ galwayTonePeriod ();
+
+ // Calculate the sample offset
+ m_xsid.sampleOffsetCalc ();
+
+#ifdef XSID_DEBUG
+ printf ("XSID [%s]: Galway Start\n", m_name);
+#endif
+
+ // Schedule a sample update
+ m_context.schedule (&m_xsid, 0);
+ m_context.schedule (&galwayEvent, cycleCount);
+}
+
+void channel::galwayClock ()
+{
+ if (--galLength)
+ cycleCount = samPeriod;
+ else if (galTones == 0xff)
+ { // The sequence has completed
+ uint8_t &status = reg[convertAddr (0x1d)];
+ if (!status)
+ status = 0xfd;
+ if (status != 0xfd)
+ active = false;
+#ifdef XSID_DEBUG
+ printf ("XSID [%s]: Galway Stop (%lu Cycles, %lu Outputs)\n",
+ m_name, cycles, outputs);
+ if (status != 0xfd)
+ printf ("XSID [%s]: Starting Delayed Sequence\n", m_name);
+#endif
+ checkForInit ();
+ return;
+ }
+ else
+ galwayTonePeriod ();
+
+ // See Galway Example...
+ galVolume += volShift;
+ galVolume &= 0x0f;
+ sample = (int8_t) galVolume - 8;
+ cycles += cycleCount;
+ m_context.schedule (&galwayEvent, cycleCount);
+ m_context.schedule (&m_xsid, 0);
+}
+
+void channel::galwayTonePeriod ()
+{ // Calculate the number of cycles over which sample should last
+ galLength = galInitLength;
+ samPeriod = m_xsid.readMemByte (address + galTones);
+ samPeriod *= galLoopWait;
+ samPeriod += galNullWait;
+ cycleCount = samPeriod;
+#if XSID_DEBUG > 2
+ printf ("XSID [%s]: Galway Settings\n", m_name);
+ printf ("XSID [%s]: Length %u, LoopWait %u, NullWait %u\n",
+ m_name, galLength, galLoopWait, galNullWait);
+ printf ("XSID [%s]: Tones %u, Data %u\n",
+ m_name, galTones, m_xsid.readMemByte (address + galTones));
+#endif
+ galTones--;
+}
+
+void channel::silence ()
+{
+ sample = 0;
+ m_context.cancel (&sampleEvent);
+ m_context.cancel (&galwayEvent);
+ m_context.schedule (&m_xsid, 0);
+}
+
+
+XSID::XSID (EventContext *context)
+:sidemu(NULL),
+ Event("xSID"),
+ ch4("CH4", context, this),
+ ch5("CH5", context, this),
+ muted(false),
+ suppressed(false),
+ wasRunning(false)
+{
+ sidSamples (true);
+}
+
+void XSID::reset (uint8_t)
+{
+ ch4.reset ();
+ ch5.reset ();
+ suppressed = false;
+ wasRunning = false;
+}
+
+void XSID::event (void)
+{
+ if (ch4 || ch5)
+ {
+ setSidData0x18 ();
+ wasRunning = true;
+ }
+ else if (wasRunning)
+ {
+ recallSidData0x18 ();
+ wasRunning = false;
+ }
+}
+
+// Use Suppress to delay the samples and start them later
+// Effectivly allows running samples in a frame based mode.
+void XSID::suppress (bool enable)
+{
+ // @FIXME@: Mute Temporary Hack
+ suppressed = enable;
+ if (!suppressed)
+ { // Get the channels running
+#if XSID_DEBUG
+ printf ("XSID: Un-suppressing\n");
+#endif
+ ch4.checkForInit ();
+ ch5.checkForInit ();
+ }
+#if XSID_DEBUG
+ else
+ printf ("XSID: Suppressing\n");
+#endif
+}
+
+// By muting samples they will start and play the at the
+// appropriate time but no sound is produced. Un-muting
+// will cause sound output from the current play position.
+void XSID::mute (bool enable)
+{
+ if (!muted && enable && wasRunning)
+ recallSidData0x18 ();
+ muted = enable;
+}
+
+void XSID::write (uint_least16_t addr, uint8_t data)
+{
+ channel *ch;
+ uint8_t tempAddr;
+
+ // Make sure address is legal
+ if ((addr & 0xfe8c) ^ 0x000c)
+ return;
+
+ ch = &ch4;
+ if (addr & 0x0100)
+ ch = &ch5;
+
+ tempAddr = (uint8_t) addr;
+ ch->write (tempAddr, data);
+#if XSID_DEBUG > 1
+ printf ("XSID: Addr 0x%02x, Data 0x%02x\n", tempAddr, data);
+#endif
+
+ if (tempAddr == 0x1d)
+ {
+ if (suppressed)
+ {
+#if XSID_DEBUG
+ printf ("XSID: Initialise Suppressed\n");
+#endif
+ return;
+ }
+ ch->checkForInit ();
+ }
+}
+
+int8_t XSID::sampleOutput (void)
+{
+ int8_t sample;
+ sample = ch4.output ();
+ sample += ch5.output ();
+ // Automatically compensated for by C64 code
+ //return (sample >> 1);
+ return sample;
+}
+
+void XSID::setSidData0x18 (void)
+{
+ if (!_sidSamples || muted)
+ return;
+
+ uint8_t data = (sidData0x18 & 0xf0);
+ data |= ((sampleOffset + sampleOutput ()) & 0x0f);
+
+#ifdef XSID_DEBUG
+ if ((sampleOffset + sampleOutput ()) > 0x0f)
+ {
+ printf ("XSID: Sample Wrapped [offset %u, sample %d]\n",
+ sampleOffset, sampleOutput ());
+ }
+# if XSID_DEBUG > 1
+ printf ("XSID: Writing Sample to SID Volume [0x%02x]\n", data);
+# endif
+#endif // XSID_DEBUG
+
+ writeMemByte (data);
+}
+
+void XSID::recallSidData0x18 (void)
+{ // Rev 2.0.5 (saw) - Changed to recall volume differently depending on mode
+ // Normally after samples volume should be restored to half volume,
+ // however, Galway Tunes sound horrible and seem to require setting back to
+ // the original volume. Setting back to the original volume for normal
+ // samples can have nasty pulsing effects
+ if (ch4.isGalway ())
+ {
+ if (_sidSamples && !muted)
+ writeMemByte (sidData0x18);
+ }
+ else
+ setSidData0x18 ();
+}
+
+void XSID::sampleOffsetCalc (void)
+{
+ // Try to determine a sensible offset between voice
+ // and sample volumes.
+ uint_least8_t lower = ch4.limit () + ch5.limit ();
+ uint_least8_t upper;
+
+ // Both channels seem to be off. Keep current offset!
+ if (!lower)
+ return;
+
+ sampleOffset = sidData0x18 & 0x0f;
+
+ // Is possible to compensate for both channels
+ // set to 4 bits here, but should never happen.
+ if (lower > 8)
+ lower >>= 1;
+ upper = 0x0f - lower + 1;
+
+ // Check against limits
+ if (sampleOffset < lower)
+ sampleOffset = lower;
+ else if (sampleOffset > upper)
+ sampleOffset = upper;
+
+#ifdef XSID_DEBUG
+ printf ("XSID: Sample Offset %d based on channel(s) ", sampleOffset);
+ if (ch4)
+ printf ("4 ");
+ if (ch5)
+ printf ("5");
+ printf ("\n");
+#endif // XSID_DEBUG
+}
+
+bool XSID::storeSidData0x18 (uint8_t data)
+{
+ sidData0x18 = data;
+ if (ch4 || ch5)
+ { // Force volume to be changed at next clock
+ sampleOffsetCalc ();
+ if (_sidSamples)
+ {
+#if XSID_DEBUG
+ printf ("XSID: SID Volume Changed Externally (Corrected).\n");
+#endif
+ return true;
+ }
+ }
+ writeMemByte (sidData0x18);
+ return false;
+}