diff options
Diffstat (limited to 'audio/softsynth/sid.cpp')
-rw-r--r-- | audio/softsynth/sid.cpp | 1456 |
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 |