aboutsummaryrefslogtreecommitdiff
path: root/audio/softsynth/sid.cpp
diff options
context:
space:
mode:
authorMax Horn2011-02-09 01:09:01 +0000
committerMax Horn2011-02-09 01:09:01 +0000
commit42ab839dd6c8a1570b232101eb97f4e54de57935 (patch)
tree3b763d8913a87482b793e0348c88b9a5f40eecc9 /audio/softsynth/sid.cpp
parent386203a3d6ce1abf457c9110d695408ec5f01b85 (diff)
downloadscummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.tar.gz
scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.tar.bz2
scummvm-rg350-42ab839dd6c8a1570b232101eb97f4e54de57935.zip
AUDIO: Rename sound/ dir to audio/
svn-id: r55850
Diffstat (limited to 'audio/softsynth/sid.cpp')
-rw-r--r--audio/softsynth/sid.cpp1456
1 files changed, 1456 insertions, 0 deletions
diff --git a/audio/softsynth/sid.cpp b/audio/softsynth/sid.cpp
new file mode 100644
index 0000000000..241766fa0a
--- /dev/null
+++ b/audio/softsynth/sid.cpp
@@ -0,0 +1,1456 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/*
+ * This file is based on reSID, a MOS6581 SID emulator engine.
+ * Copyright (C) 2004 Dag Lem <resid@nimrod.no>
+ */
+
+#ifndef DISABLE_SID
+
+#include "sid.h"
+#include "audio/null.h"
+#include <math.h>
+
+namespace Resid {
+
+// Fixpoint constants (16.16 bits).
+const int SID::FIXP_SHIFT = 16;
+const int SID::FIXP_MASK = 0xffff;
+
+/*
+ * WaveformGenerator
+ */
+
+WaveformGenerator::WaveformGenerator() {
+ sync_source = this;
+
+ reset();
+}
+
+void WaveformGenerator::set_sync_source(WaveformGenerator* source) {
+ sync_source = source;
+ source->sync_dest = this;
+}
+
+void WaveformGenerator::writeFREQ_LO(reg8 freq_lo) {
+ freq = (freq & 0xff00) | (freq_lo & 0x00ff);
+}
+
+void WaveformGenerator::writeFREQ_HI(reg8 freq_hi) {
+ freq = ((freq_hi << 8) & 0xff00) | (freq & 0x00ff);
+}
+
+void WaveformGenerator::writePW_LO(reg8 pw_lo) {
+ pw = (pw & 0xf00) | (pw_lo & 0x0ff);
+}
+
+void WaveformGenerator::writePW_HI(reg8 pw_hi) {
+ pw = ((pw_hi << 8) & 0xf00) | (pw & 0x0ff);
+}
+
+void WaveformGenerator::writeCONTROL_REG(reg8 control) {
+ waveform = (control >> 4) & 0x0f;
+ ring_mod = control & 0x04;
+ sync = control & 0x02;
+
+ reg8 test_next = control & 0x08;
+
+ // Test bit set.
+ if (test_next) {
+ accumulator = 0;
+ shift_register = 0;
+ }
+ // Test bit cleared.
+ else if (test) {
+ shift_register = 0x7ffff8;
+ }
+
+ test = test_next;
+
+ // The gate bit is handled by the EnvelopeGenerator.
+}
+
+reg8 WaveformGenerator::readOSC() {
+ return output() >> 4;
+}
+
+void WaveformGenerator::reset() {
+ accumulator = 0;
+ shift_register = 0x7ffff8;
+ freq = 0;
+ pw = 0;
+
+ test = 0;
+ ring_mod = 0;
+ sync = 0;
+
+ msb_rising = false;
+}
+
+RESID_INLINE void WaveformGenerator::clock(cycle_count delta_t) {
+ // No operation if test bit is set.
+ if (test) {
+ return;
+ }
+
+ reg24 accumulator_prev = accumulator;
+
+ // Calculate new accumulator value;
+ reg24 delta_accumulator = delta_t*freq;
+ accumulator += delta_accumulator;
+ accumulator &= 0xffffff;
+
+ // Check whether the MSB is set high. This is used for synchronization.
+ msb_rising = !(accumulator_prev & 0x800000) && (accumulator & 0x800000);
+
+ // Shift noise register once for each time accumulator bit 19 is set high.
+ // Bit 19 is set high each time 2^20 (0x100000) is added to the accumulator.
+ reg24 shift_period = 0x100000;
+
+ while (delta_accumulator) {
+ if (delta_accumulator < shift_period) {
+ shift_period = delta_accumulator;
+ // Determine whether bit 19 is set on the last period.
+ // NB! Requires two's complement integer.
+ if (shift_period <= 0x080000) {
+ // Check for flip from 0 to 1.
+ if (((accumulator - shift_period) & 0x080000) || !(accumulator & 0x080000))
+ {
+ break;
+ }
+ }
+ else {
+ // Check for flip from 0 (to 1 or via 1 to 0) or from 1 via 0 to 1.
+ if (((accumulator - shift_period) & 0x080000) && !(accumulator & 0x080000))
+ {
+ break;
+ }
+ }
+ }
+
+ // Shift the noise/random register.
+ // NB! The shift is actually delayed 2 cycles, this is not modeled.
+ reg24 bit0 = ((shift_register >> 22) ^ (shift_register >> 17)) & 0x1;
+ shift_register <<= 1;
+ shift_register &= 0x7fffff;
+ shift_register |= bit0;
+
+ delta_accumulator -= shift_period;
+ }
+}
+
+
+/**
+ * Synchronize oscillators.
+ * This must be done after all the oscillators have been clock()'ed since the
+ * oscillators operate in parallel.
+ * Note that the oscillators must be clocked exactly on the cycle when the
+ * MSB is set high for hard sync to operate correctly. See SID::clock().
+ */
+RESID_INLINE void WaveformGenerator::synchronize() {
+ // A special case occurs when a sync source is synced itself on the same
+ // cycle as when its MSB is set high. In this case the destination will
+ // not be synced. This has been verified by sampling OSC3.
+ if (msb_rising && sync_dest->sync && !(sync && sync_source->msb_rising)) {
+ sync_dest->accumulator = 0;
+ }
+}
+
+
+/*
+ * Output functions
+ */
+
+// No waveform: Zero output.
+RESID_INLINE reg12 WaveformGenerator::output____() {
+ return 0x000;
+}
+
+// Triangle:
+RESID_INLINE reg12 WaveformGenerator::output___T() {
+ reg24 msb = (ring_mod ? accumulator ^ sync_source->accumulator : accumulator)
+ & 0x800000;
+ return ((msb ? ~accumulator : accumulator) >> 11) & 0xfff;
+}
+
+// Sawtooth:
+RESID_INLINE reg12 WaveformGenerator::output__S_() {
+ return accumulator >> 12;
+}
+
+// Pulse:
+RESID_INLINE reg12 WaveformGenerator::output_P__() {
+ return (test || (accumulator >> 12) >= pw) ? 0xfff : 0x000;
+}
+
+// Noise:
+RESID_INLINE reg12 WaveformGenerator::outputN___() {
+ return
+ ((shift_register & 0x400000) >> 11) |
+ ((shift_register & 0x100000) >> 10) |
+ ((shift_register & 0x010000) >> 7) |
+ ((shift_register & 0x002000) >> 5) |
+ ((shift_register & 0x000800) >> 4) |
+ ((shift_register & 0x000080) >> 1) |
+ ((shift_register & 0x000010) << 1) |
+ ((shift_register & 0x000004) << 2);
+}
+
+// Combined waveforms:
+
+RESID_INLINE reg12 WaveformGenerator::output__ST() {
+ return wave6581__ST[output__S_()] << 4;
+}
+
+RESID_INLINE reg12 WaveformGenerator::output_P_T() {
+ return (wave6581_P_T[output___T() >> 1] << 4) & output_P__();
+}
+
+RESID_INLINE reg12 WaveformGenerator::output_PS_() {
+ return (wave6581_PS_[output__S_()] << 4) & output_P__();
+}
+
+RESID_INLINE reg12 WaveformGenerator::output_PST() {
+ return (wave6581_PST[output__S_()] << 4) & output_P__();
+}
+
+// Combined waveforms including noise:
+
+RESID_INLINE reg12 WaveformGenerator::outputN__T() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputN_S_() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputN_ST() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputNP__() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputNP_T() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputNPS_() {
+ return 0;
+}
+
+RESID_INLINE reg12 WaveformGenerator::outputNPST() {
+ return 0;
+}
+
+/**
+ * Select one of 16 possible combinations of waveforms.
+ */
+RESID_INLINE reg12 WaveformGenerator::output() {
+ // It may seem cleaner to use an array of member functions to return
+ // waveform output; however a switch with inline functions is faster.
+
+ switch (waveform) {
+ default:
+ case 0x0:
+ return output____();
+ case 0x1:
+ return output___T();
+ case 0x2:
+ return output__S_();
+ case 0x3:
+ return output__ST();
+ case 0x4:
+ return output_P__();
+ case 0x5:
+ return output_P_T();
+ case 0x6:
+ return output_PS_();
+ case 0x7:
+ return output_PST();
+ case 0x8:
+ return outputN___();
+ case 0x9:
+ return outputN__T();
+ case 0xa:
+ return outputN_S_();
+ case 0xb:
+ return outputN_ST();
+ case 0xc:
+ return outputNP__();
+ case 0xd:
+ return outputNP_T();
+ case 0xe:
+ return outputNPS_();
+ case 0xf:
+ return outputNPST();
+ }
+}
+
+/*
+ * Our objective is to construct a smooth interpolating single-valued function
+ * y = f(x).
+ * Our approach is to approximate the properties of Catmull-Rom splines for
+ * piecewice cubic polynomials.
+ */
+
+/**
+ * Calculation of coefficients.
+ */
+inline void cubic_coefficients(double x1, double y1, double x2, double y2,
+ double k1, double k2,
+ double& a, double& b, double& c, double& d)
+{
+ double dx = x2 - x1, dy = y2 - y1;
+
+ a = ((k1 + k2) - 2*dy/dx)/(dx*dx);
+ b = ((k2 - k1)/dx - 3*(x1 + x2)*a)/2;
+ c = k1 - (3*x1*a + 2*b)*x1;
+ d = y1 - ((x1*a + b)*x1 + c)*x1;
+}
+
+/**
+ * Evaluation of cubic polynomial by forward differencing.
+ */
+template<class PointPlotter>
+inline void interpolate_segment(double x1, double y1, double x2, double y2,
+ double k1, double k2,
+ PointPlotter plot, double res)
+{
+ double a, b, c, d;
+ cubic_coefficients(x1, y1, x2, y2, k1, k2, a, b, c, d);
+
+ double y = ((a*x1 + b)*x1 + c)*x1 + d;
+ double dy = (3*a*(x1 + res) + 2*b)*x1*res + ((a*res + b)*res + c)*res;
+ double d2y = (6*a*(x1 + res) + 2*b)*res*res;
+ double d3y = 6*a*res*res*res;
+
+ // Calculate each point.
+ for (double x = x1; x <= x2; x += res) {
+ plot(x, y);
+ y += dy; dy += d2y; d2y += d3y;
+ }
+}
+
+template<class PointIter>
+inline double x(PointIter p) {
+ return (*p)[0];
+}
+
+template<class PointIter>
+inline double y(PointIter p) {
+ return (*p)[1];
+}
+
+/**
+ * Evaluation of complete interpolating function.
+ * Note that since each curve segment is controlled by four points, the
+ * end points will not be interpolated. If extra control points are not
+ * desirable, the end points can simply be repeated to ensure interpolation.
+ * Note also that points of non-differentiability and discontinuity can be
+ * introduced by repeating points.
+ */
+template<class PointIter, class PointPlotter>
+inline void interpolate(PointIter p0, PointIter pn, PointPlotter plot, double res) {
+ double k1, k2;
+
+ // Set up points for first curve segment.
+ PointIter p1 = p0; ++p1;
+ PointIter p2 = p1; ++p2;
+ PointIter p3 = p2; ++p3;
+
+ // Draw each curve segment.
+ for (; p2 != pn; ++p0, ++p1, ++p2, ++p3) {
+ // p1 and p2 equal; single point.
+ if (x(p1) == x(p2)) {
+ continue;
+ }
+ // Both end points repeated; straight line.
+ if (x(p0) == x(p1) && x(p2) == x(p3)) {
+ k1 = k2 = (y(p2) - y(p1))/(x(p2) - x(p1));
+ }
+ // p0 and p1 equal; use f''(x1) = 0.
+ else if (x(p0) == x(p1)) {
+ k2 = (y(p3) - y(p1))/(x(p3) - x(p1));
+ k1 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k2)/2;
+ }
+ // p2 and p3 equal; use f''(x2) = 0.
+ else if (x(p2) == x(p3)) {
+ k1 = (y(p2) - y(p0))/(x(p2) - x(p0));
+ k2 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k1)/2;
+ }
+ // Normal curve.
+ else {
+ k1 = (y(p2) - y(p0))/(x(p2) - x(p0));
+ k2 = (y(p3) - y(p1))/(x(p3) - x(p1));
+ }
+
+ interpolate_segment(x(p1), y(p1), x(p2), y(p2), k1, k2, plot, res);
+ }
+}
+
+/**
+ * Class for plotting integers into an array.
+ */
+template<class F>
+class PointPlotter {
+protected:
+ F* f;
+
+public:
+ PointPlotter(F* arr) : f(arr) {
+ }
+
+ void operator ()(double x, double y) {
+ // Clamp negative values to zero.
+ if (y < 0) {
+ y = 0;
+ }
+
+ f[F(x)] = F(y);
+ }
+};
+
+fc_point Filter::f0_points_6581[] = {
+ // FC f FCHI FCLO
+ // ----------------------------
+ { 0, 220 }, // 0x00 - repeated end point
+ { 0, 220 }, // 0x00
+ { 128, 230 }, // 0x10
+ { 256, 250 }, // 0x20
+ { 384, 300 }, // 0x30
+ { 512, 420 }, // 0x40
+ { 640, 780 }, // 0x50
+ { 768, 1600 }, // 0x60
+ { 832, 2300 }, // 0x68
+ { 896, 3200 }, // 0x70
+ { 960, 4300 }, // 0x78
+ { 992, 5000 }, // 0x7c
+ { 1008, 5400 }, // 0x7e
+ { 1016, 5700 }, // 0x7f
+ { 1023, 6000 }, // 0x7f 0x07
+ { 1023, 6000 }, // 0x7f 0x07 - discontinuity
+ { 1024, 4600 }, // 0x80 -
+ { 1024, 4600 }, // 0x80
+ { 1032, 4800 }, // 0x81
+ { 1056, 5300 }, // 0x84
+ { 1088, 6000 }, // 0x88
+ { 1120, 6600 }, // 0x8c
+ { 1152, 7200 }, // 0x90
+ { 1280, 9500 }, // 0xa0
+ { 1408, 12000 }, // 0xb0
+ { 1536, 14500 }, // 0xc0
+ { 1664, 16000 }, // 0xd0
+ { 1792, 17100 }, // 0xe0
+ { 1920, 17700 }, // 0xf0
+ { 2047, 18000 }, // 0xff 0x07
+ { 2047, 18000 } // 0xff 0x07 - repeated end point
+};
+
+
+/*
+ * Filter
+ */
+
+Filter::Filter() {
+ fc = 0;
+
+ res = 0;
+
+ filt = 0;
+
+ voice3off = 0;
+
+ hp_bp_lp = 0;
+
+ vol = 0;
+
+ // State of filter.
+ Vhp = 0;
+ Vbp = 0;
+ Vlp = 0;
+ Vnf = 0;
+
+ enable_filter(true);
+
+ // Create mappings from FC to cutoff frequency.
+ interpolate(f0_points_6581, f0_points_6581
+ + sizeof(f0_points_6581)/sizeof(*f0_points_6581) - 1,
+ PointPlotter<sound_sample>(f0_6581), 1.0);
+
+ mixer_DC = (-0xfff*0xff/18) >> 7;
+
+ f0 = f0_6581;
+ f0_points = f0_points_6581;
+ f0_count = sizeof(f0_points_6581)/sizeof(*f0_points_6581);
+
+ set_w0();
+ set_Q();
+}
+
+void Filter::enable_filter(bool enable) {
+ enabled = enable;
+}
+
+void Filter::reset(){
+ fc = 0;
+
+ res = 0;
+
+ filt = 0;
+
+ voice3off = 0;
+
+ hp_bp_lp = 0;
+
+ vol = 0;
+
+ // State of filter.
+ Vhp = 0;
+ Vbp = 0;
+ Vlp = 0;
+ Vnf = 0;
+
+ set_w0();
+ set_Q();
+}
+
+void Filter::writeFC_LO(reg8 fc_lo) {
+ fc = (fc & 0x7f8) | (fc_lo & 0x007);
+ set_w0();
+}
+
+void Filter::writeFC_HI(reg8 fc_hi) {
+ fc = ((fc_hi << 3) & 0x7f8) | (fc & 0x007);
+ set_w0();
+}
+
+void Filter::writeRES_FILT(reg8 res_filt) {
+ res = (res_filt >> 4) & 0x0f;
+ set_Q();
+
+ filt = res_filt & 0x0f;
+}
+
+void Filter::writeMODE_VOL(reg8 mode_vol) {
+ voice3off = mode_vol & 0x80;
+
+ hp_bp_lp = (mode_vol >> 4) & 0x07;
+
+ vol = mode_vol & 0x0f;
+}
+
+// Set filter cutoff frequency.
+void Filter::set_w0() {
+ const double pi = 3.1415926535897932385;
+
+ // Multiply with 1.048576 to facilitate division by 1 000 000 by right-
+ // shifting 20 times (2 ^ 20 = 1048576).
+ w0 = static_cast<sound_sample>(2*pi*f0[fc]*1.048576);
+
+ // Limit f0 to 16kHz to keep 1 cycle filter stable.
+ const sound_sample w0_max_1 = static_cast<sound_sample>(2*pi*16000*1.048576);
+ w0_ceil_1 = w0 <= w0_max_1 ? w0 : w0_max_1;
+
+ // Limit f0 to 4kHz to keep delta_t cycle filter stable.
+ const sound_sample w0_max_dt = static_cast<sound_sample>(2*pi*4000*1.048576);
+ w0_ceil_dt = w0 <= w0_max_dt ? w0 : w0_max_dt;
+}
+
+// Set filter resonance.
+void Filter::set_Q() {
+ // Q is controlled linearly by res. Q has approximate range [0.707, 1.7].
+ // As resonance is increased, the filter must be clocked more often to keep
+ // stable.
+
+ // The coefficient 1024 is dispensed of later by right-shifting 10 times
+ // (2 ^ 10 = 1024).
+ _1024_div_Q = static_cast<sound_sample>(1024.0/(0.707 + 1.0*res/0x0f));
+}
+
+RESID_INLINE void Filter::clock(cycle_count delta_t,
+ sound_sample voice1,
+ sound_sample voice2,
+ sound_sample voice3)
+{
+ // Scale each voice down from 20 to 13 bits.
+ voice1 >>= 7;
+ voice2 >>= 7;
+
+ // NB! Voice 3 is not silenced by voice3off if it is routed through
+ // the filter.
+ if (voice3off && !(filt & 0x04)) {
+ voice3 = 0;
+ }
+ else {
+ voice3 >>= 7;
+ }
+
+ // Enable filter on/off.
+ // This is not really part of SID, but is useful for testing.
+ // On slow CPUs it may be necessary to bypass the filter to lower the CPU
+ // load.
+ if (!enabled) {
+ Vnf = voice1 + voice2 + voice3;
+ Vhp = Vbp = Vlp = 0;
+ return;
+ }
+
+ // Route voices into or around filter.
+ // The code below is expanded to a switch for faster execution.
+ // (filt1 ? Vi : Vnf) += voice1;
+ // (filt2 ? Vi : Vnf) += voice2;
+ // (filt3 ? Vi : Vnf) += voice3;
+
+ sound_sample Vi;
+
+ switch (filt) {
+ default:
+ case 0x0:
+ Vi = 0;
+ Vnf = voice1 + voice2 + voice3;
+ break;
+ case 0x1:
+ Vi = voice1;
+ Vnf = voice2 + voice3;
+ break;
+ case 0x2:
+ Vi = voice2;
+ Vnf = voice1 + voice3;
+ break;
+ case 0x3:
+ Vi = voice1 + voice2;
+ Vnf = voice3;
+ break;
+ case 0x4:
+ Vi = voice3;
+ Vnf = voice1 + voice2;
+ break;
+ case 0x5:
+ Vi = voice1 + voice3;
+ Vnf = voice2;
+ break;
+ case 0x6:
+ Vi = voice2 + voice3;
+ Vnf = voice1;
+ break;
+ case 0x7:
+ Vi = voice1 + voice2 + voice3;
+ Vnf = 0;
+ break;
+ case 0x8:
+ Vi = 0;
+ Vnf = voice1 + voice2 + voice3;
+ break;
+ case 0x9:
+ Vi = voice1;
+ Vnf = voice2 + voice3;
+ break;
+ case 0xa:
+ Vi = voice2;
+ Vnf = voice1 + voice3;
+ break;
+ case 0xb:
+ Vi = voice1 + voice2;
+ Vnf = voice3;
+ break;
+ case 0xc:
+ Vi = voice3;
+ Vnf = voice1 + voice2;
+ break;
+ case 0xd:
+ Vi = voice1 + voice3;
+ Vnf = voice2;
+ break;
+ case 0xe:
+ Vi = voice2 + voice3;
+ Vnf = voice1;
+ break;
+ case 0xf:
+ Vi = voice1 + voice2 + voice3;
+ Vnf = 0;
+ break;
+ }
+
+ // Maximum delta cycles for the filter to work satisfactorily under current
+ // cutoff frequency and resonance constraints is approximately 8.
+ cycle_count delta_t_flt = 8;
+
+ while (delta_t) {
+ if (delta_t < delta_t_flt) {
+ delta_t_flt = delta_t;
+ }
+
+ // delta_t is converted to seconds given a 1MHz clock by dividing
+ // with 1 000 000. This is done in two operations to avoid integer
+ // multiplication overflow.
+
+ // Calculate filter outputs.
+ // Vhp = Vbp/Q - Vlp - Vi;
+ // dVbp = -w0*Vhp*dt;
+ // dVlp = -w0*Vbp*dt;
+ sound_sample w0_delta_t = w0_ceil_dt*delta_t_flt >> 6;
+
+ sound_sample dVbp = (w0_delta_t*Vhp >> 14);
+ sound_sample dVlp = (w0_delta_t*Vbp >> 14);
+ Vbp -= dVbp;
+ Vlp -= dVlp;
+ Vhp = (Vbp*_1024_div_Q >> 10) - Vlp - Vi;
+
+ delta_t -= delta_t_flt;
+ }
+}
+
+RESID_INLINE sound_sample Filter::output() {
+ // This is handy for testing.
+ if (!enabled) {
+ return (Vnf + mixer_DC)*static_cast<sound_sample>(vol);
+ }
+
+ // Mix highpass, bandpass, and lowpass outputs. The sum is not
+ // weighted, this can be confirmed by sampling sound output for
+ // e.g. bandpass, lowpass, and bandpass+lowpass from a SID chip.
+
+ // The code below is expanded to a switch for faster execution.
+ // if (hp) Vf += Vhp;
+ // if (bp) Vf += Vbp;
+ // if (lp) Vf += Vlp;
+
+ sound_sample Vf;
+
+ switch (hp_bp_lp) {
+ default:
+ case 0x0:
+ Vf = 0;
+ break;
+ case 0x1:
+ Vf = Vlp;
+ break;
+ case 0x2:
+ Vf = Vbp;
+ break;
+ case 0x3:
+ Vf = Vlp + Vbp;
+ break;
+ case 0x4:
+ Vf = Vhp;
+ break;
+ case 0x5:
+ Vf = Vlp + Vhp;
+ break;
+ case 0x6:
+ Vf = Vbp + Vhp;
+ break;
+ case 0x7:
+ Vf = Vlp + Vbp + Vhp;
+ break;
+ }
+
+ // Sum non-filtered and filtered output.
+ // Multiply the sum with volume.
+ return (Vnf + Vf + mixer_DC)*static_cast<sound_sample>(vol);
+}
+
+
+/*
+ * EnvelopeGenerator
+ */
+
+EnvelopeGenerator::EnvelopeGenerator() {
+ reset();
+}
+
+void EnvelopeGenerator::reset() {
+ envelope_counter = 0;
+
+ attack = 0;
+ decay = 0;
+ sustain = 0;
+ release = 0;
+
+ gate = 0;
+
+ rate_counter = 0;
+ exponential_counter = 0;
+ exponential_counter_period = 1;
+
+ state = RELEASE;
+ rate_period = rate_counter_period[release];
+ hold_zero = true;
+}
+
+reg16 EnvelopeGenerator::rate_counter_period[] = {
+ 9, // 2ms*1.0MHz/256 = 7.81
+ 32, // 8ms*1.0MHz/256 = 31.25
+ 63, // 16ms*1.0MHz/256 = 62.50
+ 95, // 24ms*1.0MHz/256 = 93.75
+ 149, // 38ms*1.0MHz/256 = 148.44
+ 220, // 56ms*1.0MHz/256 = 218.75
+ 267, // 68ms*1.0MHz/256 = 265.63
+ 313, // 80ms*1.0MHz/256 = 312.50
+ 392, // 100ms*1.0MHz/256 = 390.63
+ 977, // 250ms*1.0MHz/256 = 976.56
+ 1954, // 500ms*1.0MHz/256 = 1953.13
+ 3126, // 800ms*1.0MHz/256 = 3125.00
+ 3907, // 1 s*1.0MHz/256 = 3906.25
+ 11720, // 3 s*1.0MHz/256 = 11718.75
+ 19532, // 5 s*1.0MHz/256 = 19531.25
+ 31251 // 8 s*1.0MHz/256 = 31250.00
+};
+
+
+reg8 EnvelopeGenerator::sustain_level[] = {
+ 0x00,
+ 0x11,
+ 0x22,
+ 0x33,
+ 0x44,
+ 0x55,
+ 0x66,
+ 0x77,
+ 0x88,
+ 0x99,
+ 0xaa,
+ 0xbb,
+ 0xcc,
+ 0xdd,
+ 0xee,
+ 0xff,
+};
+
+void EnvelopeGenerator::writeCONTROL_REG(reg8 control) {
+ reg8 gate_next = control & 0x01;
+
+ // The rate counter is never reset, thus there will be a delay before the
+ // envelope counter starts counting up (attack) or down (release).
+
+ // Gate bit on: Start attack, decay, sustain.
+ if (!gate && gate_next) {
+ state = ATTACK;
+ rate_period = rate_counter_period[attack];
+
+ // Switching to attack state unlocks the zero freeze.
+ hold_zero = false;
+ }
+ // Gate bit off: Start release.
+ else if (gate && !gate_next) {
+ state = RELEASE;
+ rate_period = rate_counter_period[release];
+ }
+
+ gate = gate_next;
+}
+
+void EnvelopeGenerator::writeATTACK_DECAY(reg8 attack_decay) {
+ attack = (attack_decay >> 4) & 0x0f;
+ decay = attack_decay & 0x0f;
+ if (state == ATTACK) {
+ rate_period = rate_counter_period[attack];
+ }
+ else if (state == DECAY_SUSTAIN) {
+ rate_period = rate_counter_period[decay];
+ }
+}
+
+void EnvelopeGenerator::writeSUSTAIN_RELEASE(reg8 sustain_release) {
+ sustain = (sustain_release >> 4) & 0x0f;
+ release = sustain_release & 0x0f;
+ if (state == RELEASE) {
+ rate_period = rate_counter_period[release];
+ }
+}
+
+reg8 EnvelopeGenerator::readENV() {
+ return output();
+}
+
+RESID_INLINE void EnvelopeGenerator::clock(cycle_count delta_t) {
+ // Check for ADSR delay bug.
+ // If the rate counter comparison value is set below the current value of the
+ // rate counter, the counter will continue counting up until it wraps around
+ // to zero at 2^15 = 0x8000, and then count rate_period - 1 before the
+ // envelope can finally be stepped.
+ // This has been verified by sampling ENV3.
+ //
+
+ // NB! This requires two's complement integer.
+ int rate_step = rate_period - rate_counter;
+ if (rate_step <= 0) {
+ rate_step += 0x7fff;
+ }
+
+ while (delta_t) {
+ if (delta_t < rate_step) {
+ rate_counter += delta_t;
+ if (rate_counter & 0x8000) {
+ ++rate_counter &= 0x7fff;
+ }
+ return;
+ }
+
+ rate_counter = 0;
+ delta_t -= rate_step;
+
+ // The first envelope step in the attack state also resets the exponential
+ // counter. This has been verified by sampling ENV3.
+ //
+ if (state == ATTACK || ++exponential_counter == exponential_counter_period)
+ {
+ exponential_counter = 0;
+
+ // Check whether the envelope counter is frozen at zero.
+ if (hold_zero) {
+ rate_step = rate_period;
+ continue;
+ }
+
+ switch (state) {
+ case ATTACK:
+ // The envelope counter can flip from 0xff to 0x00 by changing state to
+ // release, then to attack. The envelope counter is then frozen at
+ // zero; to unlock this situation the state must be changed to release,
+ // then to attack. This has been verified by sampling ENV3.
+ //
+ ++envelope_counter &= 0xff;
+ if (envelope_counter == 0xff) {
+ state = DECAY_SUSTAIN;
+ rate_period = rate_counter_period[decay];
+ }
+ break;
+ case DECAY_SUSTAIN:
+ if (envelope_counter != sustain_level[sustain]) {
+ --envelope_counter;
+ }
+ break;
+ case RELEASE:
+ // The envelope counter can flip from 0x00 to 0xff by changing state to
+ // attack, then to release. The envelope counter will then continue
+ // counting down in the release state.
+ // This has been verified by sampling ENV3.
+ // NB! The operation below requires two's complement integer.
+ //
+ --envelope_counter &= 0xff;
+ break;
+ }
+
+ // Check for change of exponential counter period.
+ switch (envelope_counter) {
+ case 0xff:
+ exponential_counter_period = 1;
+ break;
+ case 0x5d:
+ exponential_counter_period = 2;
+ break;
+ case 0x36:
+ exponential_counter_period = 4;
+ break;
+ case 0x1a:
+ exponential_counter_period = 8;
+ break;
+ case 0x0e:
+ exponential_counter_period = 16;
+ break;
+ case 0x06:
+ exponential_counter_period = 30;
+ break;
+ case 0x00:
+ exponential_counter_period = 1;
+
+ // When the envelope counter is changed to zero, it is frozen at zero.
+ // This has been verified by sampling ENV3.
+ hold_zero = true;
+ break;
+ }
+ }
+
+ rate_step = rate_period;
+ }
+}
+
+RESID_INLINE reg8 EnvelopeGenerator::output() {
+ return envelope_counter;
+}
+
+
+/*
+ * ExternalFilter
+ */
+
+ExternalFilter::ExternalFilter() {
+ reset();
+ enable_filter(true);
+ set_sampling_parameter(15915.6);
+ mixer_DC = ((((0x800 - 0x380) + 0x800)*0xff*3 - 0xfff*0xff/18) >> 7)*0x0f;
+}
+
+void ExternalFilter::enable_filter(bool enable) {
+ enabled = enable;
+}
+
+void ExternalFilter::set_sampling_parameter(double pass_freq) {
+ static const double pi = 3.1415926535897932385;
+
+ w0hp = 105;
+ w0lp = (sound_sample) (pass_freq * (2.0 * pi * 1.048576));
+ if (w0lp > 104858)
+ w0lp = 104858;
+}
+
+void ExternalFilter::reset() {
+ // State of filter.
+ Vlp = 0;
+ Vhp = 0;
+ Vo = 0;
+}
+
+RESID_INLINE void ExternalFilter::clock(cycle_count delta_t, sound_sample Vi) {
+ // This is handy for testing.
+ if (!enabled) {
+ // Remove maximum DC level since there is no filter to do it.
+ Vlp = Vhp = 0;
+ Vo = Vi - mixer_DC;
+ return;
+ }
+
+ // Maximum delta cycles for the external filter to work satisfactorily
+ // is approximately 8.
+ cycle_count delta_t_flt = 8;
+
+ while (delta_t) {
+ if (delta_t < delta_t_flt) {
+ delta_t_flt = delta_t;
+ }
+
+ // delta_t is converted to seconds given a 1MHz clock by dividing
+ // with 1 000 000.
+
+ // Calculate filter outputs.
+ // Vo = Vlp - Vhp;
+ // Vlp = Vlp + w0lp*(Vi - Vlp)*delta_t;
+ // Vhp = Vhp + w0hp*(Vlp - Vhp)*delta_t;
+
+ sound_sample dVlp = (w0lp*delta_t_flt >> 8)*(Vi - Vlp) >> 12;
+ sound_sample dVhp = w0hp*delta_t_flt*(Vlp - Vhp) >> 20;
+ Vo = Vlp - Vhp;
+ Vlp += dVlp;
+ Vhp += dVhp;
+
+ delta_t -= delta_t_flt;
+ }
+}
+
+RESID_INLINE sound_sample ExternalFilter::output() {
+ return Vo;
+}
+
+
+/*
+ * Voice
+ */
+
+Voice::Voice() {
+ wave_zero = 0x380;
+ voice_DC = 0x800*0xff;
+}
+
+void Voice::set_sync_source(Voice* source) {
+ wave.set_sync_source(&source->wave);
+}
+
+void Voice::writeCONTROL_REG(reg8 control) {
+ wave.writeCONTROL_REG(control);
+ envelope.writeCONTROL_REG(control);
+}
+
+void Voice::reset() {
+ wave.reset();
+ envelope.reset();
+}
+
+
+/*
+ * SID
+ */
+
+SID::SID() {
+ voice[0].set_sync_source(&voice[2]);
+ voice[1].set_sync_source(&voice[0]);
+ voice[2].set_sync_source(&voice[1]);
+
+ set_sampling_parameters(985248, 44100);
+
+ bus_value = 0;
+ bus_value_ttl = 0;
+}
+
+SID::~SID() {}
+
+void SID::reset() {
+ for (int i = 0; i < 3; i++) {
+ voice[i].reset();
+ }
+ filter.reset();
+ extfilt.reset();
+
+ bus_value = 0;
+ bus_value_ttl = 0;
+}
+
+int SID::output() {
+ const int range = 1 << 16;
+ const int half = range >> 1;
+ int sample = extfilt.output()/((4095*255 >> 7)*3*15*2/range);
+ if (sample >= half) {
+ return half - 1;
+ }
+ if (sample < -half) {
+ return -half;
+ }
+ return sample;
+}
+
+
+/**
+ * Read registers.
+ *
+ * Reading a write only register returns the last byte written to any SID
+ * register. The individual bits in this value start to fade down towards
+ * zero after a few cycles. All bits reach zero within approximately
+ * $2000 - $4000 cycles.
+ * It has been claimed that this fading happens in an orderly fashion, however
+ * sampling of write only registers reveals that this is not the case.
+ * NB! This is not correctly modeled.
+ * The actual use of write only registers has largely been made in the belief
+ * that all SID registers are readable. To support this belief the read
+ * would have to be done immediately after a write to the same register
+ * (remember that an intermediate write to another register would yield that
+ * value instead). With this in mind we return the last value written to
+ * any SID register for $2000 cycles without modeling the bit fading.
+ */
+reg8 SID::read(reg8 offset) {
+ switch (offset) {
+ case 0x19:
+ case 0x1a:
+ return 0; //readPOT();
+ case 0x1b:
+ return voice[2].wave.readOSC();
+ case 0x1c:
+ return voice[2].envelope.readENV();
+ default:
+ return bus_value;
+ }
+}
+
+void SID::write(reg8 offset, reg8 value) {
+ bus_value = value;
+ bus_value_ttl = 0x2000;
+
+ switch (offset) {
+ case 0x00:
+ voice[0].wave.writeFREQ_LO(value);
+ break;
+ case 0x01:
+ voice[0].wave.writeFREQ_HI(value);
+ break;
+ case 0x02:
+ voice[0].wave.writePW_LO(value);
+ break;
+ case 0x03:
+ voice[0].wave.writePW_HI(value);
+ break;
+ case 0x04:
+ voice[0].writeCONTROL_REG(value);
+ break;
+ case 0x05:
+ voice[0].envelope.writeATTACK_DECAY(value);
+ break;
+ case 0x06:
+ voice[0].envelope.writeSUSTAIN_RELEASE(value);
+ break;
+ case 0x07:
+ voice[1].wave.writeFREQ_LO(value);
+ break;
+ case 0x08:
+ voice[1].wave.writeFREQ_HI(value);
+ break;
+ case 0x09:
+ voice[1].wave.writePW_LO(value);
+ break;
+ case 0x0a:
+ voice[1].wave.writePW_HI(value);
+ break;
+ case 0x0b:
+ voice[1].writeCONTROL_REG(value);
+ break;
+ case 0x0c:
+ voice[1].envelope.writeATTACK_DECAY(value);
+ break;
+ case 0x0d:
+ voice[1].envelope.writeSUSTAIN_RELEASE(value);
+ break;
+ case 0x0e:
+ voice[2].wave.writeFREQ_LO(value);
+ break;
+ case 0x0f:
+ voice[2].wave.writeFREQ_HI(value);
+ break;
+ case 0x10:
+ voice[2].wave.writePW_LO(value);
+ break;
+ case 0x11:
+ voice[2].wave.writePW_HI(value);
+ break;
+ case 0x12:
+ voice[2].writeCONTROL_REG(value);
+ break;
+ case 0x13:
+ voice[2].envelope.writeATTACK_DECAY(value);
+ break;
+ case 0x14:
+ voice[2].envelope.writeSUSTAIN_RELEASE(value);
+ break;
+ case 0x15:
+ filter.writeFC_LO(value);
+ break;
+ case 0x16:
+ filter.writeFC_HI(value);
+ break;
+ case 0x17:
+ filter.writeRES_FILT(value);
+ break;
+ case 0x18:
+ filter.writeMODE_VOL(value);
+ break;
+ default:
+ break;
+ }
+}
+
+void SID::enable_filter(bool enable) {
+ filter.enable_filter(enable);
+}
+
+void SID::enable_external_filter(bool enable) {
+ extfilt.enable_filter(enable);
+}
+
+
+/**
+ * Setting of SID sampling parameters.
+ *
+ * Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64.
+ * The default end of passband frequency is pass_freq = 0.9*sample_freq/2
+ * for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample
+ * frequencies.
+ *
+ * For resampling, the ratio between the clock frequency and the sample
+ * frequency is limited as follows:
+ * 125*clock_freq/sample_freq < 16384
+ * E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not
+ * be set lower than ~ 8kHz. A lower sample frequency would make the
+ * resampling code overfill its 16k sample ring buffer.
+ *
+ * The end of passband frequency is also limited:
+ * pass_freq <= 0.9*sample_freq/2
+ *
+ * E.g. for a 44.1kHz sampling rate the end of passband frequency is limited
+ * to slightly below 20kHz. This constraint ensures that the FIR table is
+ * not overfilled.
+ */
+bool SID::set_sampling_parameters(double clock_freq,
+ double sample_freq, double pass_freq,
+ double filter_scale)
+{
+ // The default passband limit is 0.9*sample_freq/2 for sample
+ // frequencies below ~ 44.1kHz, and 20kHz for higher sample frequencies.
+ if (pass_freq < 0) {
+ pass_freq = 20000;
+ if (2*pass_freq/sample_freq >= 0.9) {
+ pass_freq = 0.9*sample_freq/2;
+ }
+ }
+ // Check whether the FIR table would overfill.
+ else if (pass_freq > 0.9*sample_freq/2) {
+ return false;
+ }
+
+ // The filter scaling is only included to avoid clipping, so keep
+ // it sane.
+ if (filter_scale < 0.9 || filter_scale > 1.0) {
+ return false;
+ }
+
+ // Set the external filter to the pass freq
+ extfilt.set_sampling_parameter (pass_freq);
+ clock_frequency = clock_freq;
+
+ cycles_per_sample =
+ cycle_count(clock_freq/sample_freq*(1 << FIXP_SHIFT) + 0.5);
+
+ sample_offset = 0;
+ sample_prev = 0;
+
+ return true;
+}
+
+void SID::clock(cycle_count delta_t) {
+ int i;
+
+ if (delta_t <= 0) {
+ return;
+ }
+
+ // Age bus value.
+ bus_value_ttl -= delta_t;
+ if (bus_value_ttl <= 0) {
+ bus_value = 0;
+ bus_value_ttl = 0;
+ }
+
+ // Clock amplitude modulators.
+ for (i = 0; i < 3; i++) {
+ voice[i].envelope.clock(delta_t);
+ }
+
+ // Clock and synchronize oscillators.
+ // Loop until we reach the current cycle.
+ cycle_count delta_t_osc = delta_t;
+ while (delta_t_osc) {
+ cycle_count delta_t_min = delta_t_osc;
+
+ // Find minimum number of cycles to an oscillator accumulator MSB toggle.
+ // We have to clock on each MSB on / MSB off for hard sync to operate
+ // correctly.
+ for (i = 0; i < 3; i++) {
+ WaveformGenerator& wave = voice[i].wave;
+
+ // It is only necessary to clock on the MSB of an oscillator that is
+ // a sync source and has freq != 0.
+ if (!(wave.sync_dest->sync && wave.freq)) {
+ continue;
+ }
+
+ reg16 freq = wave.freq;
+ reg24 accumulator = wave.accumulator;
+
+ // Clock on MSB off if MSB is on, clock on MSB on if MSB is off.
+ reg24 delta_accumulator =
+ (accumulator & 0x800000 ? 0x1000000 : 0x800000) - accumulator;
+
+ cycle_count delta_t_next = delta_accumulator/freq;
+ if (delta_accumulator%freq) {
+ ++delta_t_next;
+ }
+
+ if (delta_t_next < delta_t_min) {
+ delta_t_min = delta_t_next;
+ }
+ }
+
+ // Clock oscillators.
+ for (i = 0; i < 3; i++) {
+ voice[i].wave.clock(delta_t_min);
+ }
+
+ // Synchronize oscillators.
+ for (i = 0; i < 3; i++) {
+ voice[i].wave.synchronize();
+ }
+
+ delta_t_osc -= delta_t_min;
+ }
+
+ // Clock filter.
+ filter.clock(delta_t,
+ voice[0].output(), voice[1].output(), voice[2].output());
+
+ // Clock external filter.
+ extfilt.clock(delta_t, filter.output());
+}
+
+
+/**
+ * SID clocking with audio sampling.
+ * Fixpoint arithmetics is used.
+ */
+int SID::clock(cycle_count& delta_t, short* buf, int n, int interleave) {
+ int s = 0;
+
+ for (;;) {
+ cycle_count next_sample_offset = sample_offset + cycles_per_sample + (1 << (FIXP_SHIFT - 1));
+ cycle_count delta_t_sample = next_sample_offset >> FIXP_SHIFT;
+ if (delta_t_sample > delta_t) {
+ break;
+ }
+ if (s >= n) {
+ return s;
+ }
+ clock(delta_t_sample);
+ delta_t -= delta_t_sample;
+ sample_offset = (next_sample_offset & FIXP_MASK) - (1 << (FIXP_SHIFT - 1));
+ buf[s++*interleave] = output();
+ }
+
+ clock(delta_t);
+ sample_offset -= delta_t << FIXP_SHIFT;
+ delta_t = 0;
+ return s;
+}
+
+}
+
+// Plugin interface
+// (This can only create a null driver since C64 audio support is not part of the
+// midi driver architecture. But we need the plugin for the options menu in the launcher
+// and for MidiDriver::detectDevice() which is more or less used by all engines.)
+
+class C64MusicPlugin : public NullMusicPlugin {
+public:
+ const char *getName() const {
+ return _s("C64 Audio Emulator");
+ }
+
+ const char *getId() const {
+ return "C64";
+ }
+
+ MusicDevices getDevices() const;
+};
+
+MusicDevices C64MusicPlugin::getDevices() const {
+ MusicDevices devices;
+ devices.push_back(MusicDevice(this, "", MT_C64));
+ return devices;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(C64)
+ //REGISTER_PLUGIN_DYNAMIC(C64, PLUGIN_TYPE_MUSIC, C64MusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(C64, PLUGIN_TYPE_MUSIC, C64MusicPlugin);
+//#endif
+
+#endif