/** * encoding: UTF-8 * This file is part of reSID, a MOS6581 SID emulator engine. * Copyright (C) 2004 Dag Lem <resid@nimrod.no> * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * @author Ken Händel * */ package resid; import resid.ISIDDefs.chip_model; import resid.ISIDDefs.sampling_method; public class SID { /** * This is a patch against ReSID * engine in libsidplay2-2.1.1 to make its sound closer to 6581R4 * chip.<BR> * * http://bel.fi/~alankila/c64-sw/ * * <P> * <B>Good simulation of strong lead and bass distortion.</B> Tel's leads, * Jeff's bass, Mechanicus's extreme distortion: it all seems to reproduce * quite nicely. * * <P> * <B>Good simulation of advanced effects peculiar to 6581</B>, especially * on Jeff's songs that target 6581R4. * * <P> * <B>Parameters are now in sidplay2.ini file.</B> No recompile is * necessary when testing alternative parameters. * * <P> * <B>Bugs in some songs, especially related to lowpass filter output.</B> * The middle part of Spaceman Salutes Commodore and intro for Snake Disco * are incorrectly rendered. (ReSID's linear filter is totally wrong with * them too.) Vendetta probably gets too much edge in the bass, too. It * seems that some kind of additional low-pass filtering needs to happen * during so far unknown filter conditions. * <P> * <UL> * <LI> 8.12.2007 Separate parameters for band-pass (high) and low-pass * (low) distortion. This doubles the number of tunables, but seems like a * necessary step to open experimentation on this front. * <LI> 6.12.2007 New distortion algorithm. After I scratched the whole * model of substracting filter states from each other. The new model simply * calculates distortion by mixing in the filter state terms and input in * linear combination. The following rules seem to apply: * <OL> * <LI> Vlp term must be about 0.9. More than that, and Jeff's bass sounds * lose their edge. Too little, and pretty much everything distorts wrong. * <LI> Vbp term be something negative like -0.5 or -1 or Vendetta and * Meanwhile The Planet doesn't work. * <LI> Vhp must be small or the distortion loses its "edge". * </OL> * * The new model has one important property above the previous one: it only * distorts the other half-wave, the same which can be seen distorted in SID * samplings, too. Therefore it makes for more complicated distorted sounds, * and better replicates the effects in f.e. Mechanicus. * <LI> 5.12.2007 Nearly doubled the impact of the bp-hp term. This improves * Elite and Shades quite a bit, without seeming to negatively impact other * songs. * <LI> 4.12.2007 Made tunables out of the rest of the distortion * parameters, save from one that I just added: low-pass filtering for my * crude w0_eff term. This seems to reduce the artifacts on Robocop 3, and * generally doesn't seem to harm the songs. * <LI> 3.12.2007 A new distortion algorithm. A lot of time has passed. I * made some improvements that force more distortion out of the model. I * feel that this reinstates some of my LEADDIST effects and should be * closer to correct model. I'm reasonably happy about how it turned out. * This is definitely the best all-around emulation to date. * * I also made the distortion tunable via config file, so the thing is sane * and doesn't trash 8580 any more. * <LI> 18.4.2007 Oops, a few months passed as I was busy at work. * * I've got rid of the LEADDIST effect, simply accepted that things will not * work with it. I hope to be able to replace it with another trick up my * sleeve, but so far the new songs and patch probably constitute a step * backwards. Maybe. * <LI> 13.2.2007 Defeat with the masterful TBB songs such as 0_N_0 and * Meanwhile The Planet. The 0_N_0 makes a lot of snapping because in a real * chip the distortion terms are "filtered" by the other filter components, * while mine are filtered by a static ad-hoc estimate. So, the snapping * here comes from the LEADDIST term and it doesn't make the right kind of * effect. * * Meanwhile The Planet makes masterfully precise use of the filter, and * there isn't enough distortion for ReSID rendition to be palatable. Thanks * to Grue for digging these songs up. Also fixed invalid sidplay2.ini * configuration file (was not using 6581R4 curve). This, too, was spotted * by Grue. * * I suspect Snake Disco might be fixed by accounting for the loss of * intensity of sound with the Vbp op-amp distortion. I made some * measurements last night and discovered that level of lp and hp outputs * dropped dramatically when bp was driven to distortion. I observed loss of * up to 20 dB. I probably have to make many more measurements to allow * mapping an estimate for this behaviour. * <LI> 12.2.2007 I added a minor tweak, adding an estimate of the resonance * term in the op-amp loading measurement. I also made the bass distortion * slightly more prominent, although it still isn't so harsh as the real * chip produces. * <LI> 11.2.2007 I decided the price for fixing RoboCop 3 on 10.2. changes * was too high to pay. It put a few dB of distortion back and migrated the * distortion back to later time. Other changes in frequency drift * implementation however helped a lot even with RoboCop 3, although I do * think it still distorts too much. I think overall I'm now better off. * * I also moved the distortion term computation into the filter clocking * function, so that if output is being requested at some different rate * than the filter is clocked, the lowpass filtering would still be computed * similarly. This bit me because I had this stupid OptimiseLevel * PerformanceHacks accidentally enabled. * <LI> 10.2.2007 I made the distortion term to start earlier but also more * delicate. (Reduced distortion by 6 dB.) This fixed RoboCop_3, SYS4096 and * improved locutox's cover of Purple Shades. (Not included on sample set.) * I also set bass level to -4.4 dB, because it comes about 1.5 dB louder * than treble or bandpass. This is in full contrast to comments in ReSID * that claim no level difference exists. Strange, but I can see it plain as * day on FFT. * <LI> 3.2.2007 - 9.2.2007 Creation and tuning the model... * </UL> * @author Antti Lankila (alankila@bel.fi) */ public static boolean ANTTI_LANKILA_PATCH = true; /** * Read/Write State. * * @author Ken Händel * */ public class State { public char sid_register[] = new char[0x20]; public int /* reg8 */bus_value; public int /* cycle_count */bus_value_ttl; public int /* reg24 */accumulator[] = new int[3]; public int /* reg24 */shift_register[] = new int[3]; public int /* reg16 */rate_counter[] = new int[3]; public int /* reg16 */rate_counter_period[] = new int[3]; public int /* reg16 */exponential_counter[] = new int[3]; public int /* reg16 */exponential_counter_period[] = new int[3]; int /* reg8 */envelope_counter[] = new int[3]; public EnvelopeGenerator.State envelope_state[] = new EnvelopeGenerator.State[3]; public boolean hold_zero[] = new boolean[3]; /** * Constructor. */ public State() { int i; for (i = 0; i < 0x20; i++) { sid_register[i] = 0; } bus_value = 0; bus_value_ttl = 0; for (i = 0; i < 3; i++) { accumulator[i] = 0; shift_register[i] = 0x7ffff8; rate_counter[i] = 0; rate_counter_period[i] = 9; exponential_counter[i] = 0; exponential_counter_period[i] = 1; envelope_counter[i] = 0; envelope_state[i] = EnvelopeGenerator.State.RELEASE; hold_zero[i] = true; } } } protected Voice voice[] = new Voice[] { new Voice(), new Voice(), new Voice() }; public Filter filter = new Filter(); protected ExternalFilter extfilt = new ExternalFilter(); protected Potentiometer potx = new Potentiometer(); protected Potentiometer poty = new Potentiometer(); protected int /* reg8 */bus_value; protected int /* cycle_count */bus_value_ttl; protected double clock_frequency; /** * External audio input. */ protected int ext_in; /** * Resampling constants. The error in interpolated lookup is bounded by * 1.234/L^2, while the error in non-interpolated lookup is bounded by * 0.7854/L + 0.4113/L^2, see * http://www-ccrma.stanford.edu/~jos/resample/Choice_Table_Size.html For a * resolution of 16 bits this yields L >= 285 and L >= 51473, respectively. */ protected static final int FIR_N = 125; /** * Resampling constants. The error in interpolated lookup is bounded by * 1.234/L^2, while the error in non-interpolated lookup is bounded by * 0.7854/L + 0.4113/L^2, see * http://www-ccrma.stanford.edu/~jos/resample/Choice_Table_Size.html For a * resolution of 16 bits this yields L >= 285 and L >= 51473, respectively. */ protected static final int FIR_RES_INTERPOLATE = 285; /** * Resampling constants. The error in interpolated lookup is bounded by * 1.234/L^2, while the error in non-interpolated lookup is bounded by * 0.7854/L + 0.4113/L^2, see * http://www-ccrma.stanford.edu/~jos/resample/Choice_Table_Size.html For a * resolution of 16 bits this yields L >= 285 and L >= 51473, respectively. */ protected static final int FIR_RES_FAST = 51473; /** * Resampling constants. The error in interpolated lookup is bounded by * 1.234/L^2, while the error in non-interpolated lookup is bounded by * 0.7854/L + 0.4113/L^2, see * http://www-ccrma.stanford.edu/~jos/resample/Choice_Table_Size.html For a * resolution of 16 bits this yields L >= 285 and L >= 51473, respectively. */ protected static final int FIR_SHIFT = 15; /** * Resampling constants. The error in interpolated lookup is bounded by * 1.234/L^2, while the error in non-interpolated lookup is bounded by * 0.7854/L + 0.4113/L^2, see * http://www-ccrma.stanford.edu/~jos/resample/Choice_Table_Size.html For a * resolution of 16 bits this yields L >= 285 and L >= 51473, respectively. */ protected static final int RINGSIZE = 16384; /** * Fixpoint constants (16.16 bits). */ protected static final int FIXP_SHIFT = 16; /** * Fixpoint constants (16.16 bits). */ protected static final int FIXP_MASK = 0xffff; /** * Sampling variables. */ protected sampling_method sampling; /** * Sampling variables. */ protected int /* cycle_count */cycles_per_sample; /** * Sampling variables. */ protected int /* cycle_count */sample_offset; /** * Sampling variables. */ protected int sample_index; /** * Sampling variables. */ protected short sample_prev; /** * Sampling variables. */ protected int fir_N; /** * Sampling variables. */ protected int fir_RES; /** * Ring buffer with overflow for contiguous storage of RINGSIZE samples. */ protected short sample[]; /** * FIR_RES filter tables (FIR_N*FIR_RES). */ protected short fir[]; /** * Constructor. */ public SID() { // Initialize pointers. sample = null; fir = null; 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, sampling_method.SAMPLE_FAST, 44100, -1, 0.97); bus_value = 0; bus_value_ttl = 0; ext_in = 0; } /** * Set chip model. * * @param model */ public void set_chip_model(chip_model model) { for (int i = 0; i < 3; i++) { voice[i].set_chip_model(model); } filter.set_chip_model(model); extfilt.set_chip_model(model); } public void set_distortion_properties(int Lt, int Ls, int Ll, int Lb, int Lh, int Ht, int Hs, int Hl, int Hb, int Hh) { filter .set_distortion_properties(Lt, Ls, Ll, Lb, Lh, Ht, Hs, Hl, Hb, Hh); } /** * SID reset. */ public void reset() { for (int i = 0; i < 3; i++) { voice[i].reset(); } filter.reset(); extfilt.reset(); bus_value = 0; bus_value_ttl = 0; } /** * 16-bit input (EXT IN). Write 16-bit sample to audio input. NB! The caller * is responsible for keeping the value within 16 bits. Note that to mix in * an external audio signal, the signal should be resampled to 1MHz first to * avoid sampling noise. * * @param sample */ public void input(int sample) { // Voice outputs are 20 bits. Scale up to match three voices in order // to facilitate simulation of the MOS8580 "digi boost" hardware hack. ext_in = (sample << 4) * 3; } /** * 16-bit output (AUDIO OUT). Read sample from audio output. Both 16-bit and * n-bit output is provided. * * @return */ public int output() { final int range = 1 << 16; final 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; } /** * n-bit output. * * @param bits * @return */ public int output(int bits) { final int range = 1 << bits; final 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. * <P> * 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. * * @param offset * @return */ public int /* reg8 */read(int /* reg8 */offset) { switch (offset) { case 0x19: return potx.readPOT(); case 0x1a: return poty.readPOT(); case 0x1b: return voice[2].wave.readOSC(); case 0x1c: return voice[2].envelope.readENV(); default: return bus_value; } } /** * Write registers. * * @param offset * @param value */ public void write(int /* reg8 */offset, int /* 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; } } /** * SID voice muting. * * @param channel * @param enable */ public void mute(int /* reg8 */channel, boolean enable) { // Only have 3 voices! if (channel >= 3) return; voice[channel].mute(enable); } /** * Read state. * * @return */ public State read_state() { State state = new State(); int i, j; for (i = 0, j = 0; i < 3; i++, j += 7) { WaveformGenerator wave = voice[i].wave; EnvelopeGenerator envelope = voice[i].envelope; state.sid_register[j + 0] = (char) (wave.freq & 0xff); state.sid_register[j + 1] = (char) (wave.freq >> 8); state.sid_register[j + 2] = (char) (wave.pw & 0xff); state.sid_register[j + 3] = (char) (wave.pw >> 8); state.sid_register[j + 4] = (char) ((wave.waveform << 4) | ((wave.test != 0) ? 0x08 : 0) | ((wave.ring_mod != 0) ? 0x04 : 0) | ((wave.sync != 0) ? 0x02 : 0) | ((envelope.gate != 0) ? 0x01 : 0)); state.sid_register[j + 5] = (char) ((envelope.attack << 4) | envelope.decay); state.sid_register[j + 6] = (char) ((envelope.sustain << 4) | envelope.release); } state.sid_register[j++] = (char) (filter.fc & 0x007); state.sid_register[j++] = (char) (filter.fc >> 3); state.sid_register[j++] = (char) ((filter.res << 4) | filter.filt); state.sid_register[j++] = (char) (((filter.voice3off != 0) ? 0x80 : 0) | (filter.hp_bp_lp << 4) | filter.vol); // These registers are superfluous, but included for completeness. for (; j < 0x1d; j++) { state.sid_register[j] = (char) (read(j)); } for (; j < 0x20; j++) { state.sid_register[j] = 0; } state.bus_value = bus_value; state.bus_value_ttl = bus_value_ttl; for (i = 0; i < 3; i++) { state.accumulator[i] = voice[i].wave.accumulator; state.shift_register[i] = voice[i].wave.shift_register; state.rate_counter[i] = voice[i].envelope.rate_counter; state.rate_counter_period[i] = voice[i].envelope.rate_period; state.exponential_counter[i] = voice[i].envelope.exponential_counter; state.exponential_counter_period[i] = voice[i].envelope.exponential_counter_period; state.envelope_counter[i] = voice[i].envelope.envelope_counter; state.envelope_state[i] = voice[i].envelope.state; state.hold_zero[i] = voice[i].envelope.hold_zero; } return state; } /** * Write state. * * @param state */ public void write_state(final State state) { int i; for (i = 0; i <= 0x18; i++) { write(i, state.sid_register[i]); } bus_value = state.bus_value; bus_value_ttl = state.bus_value_ttl; for (i = 0; i < 3; i++) { voice[i].wave.accumulator = state.accumulator[i]; voice[i].wave.shift_register = state.shift_register[i]; voice[i].envelope.rate_counter = state.rate_counter[i]; voice[i].envelope.rate_period = state.rate_counter_period[i]; voice[i].envelope.exponential_counter = state.exponential_counter[i]; voice[i].envelope.exponential_counter_period = state.exponential_counter_period[i]; voice[i].envelope.envelope_counter = state.envelope_counter[i]; voice[i].envelope.state = state.envelope_state[i]; voice[i].envelope.hold_zero = state.hold_zero[i]; } } /** * Enable filter. * * @param enable */ public void enable_filter(boolean enable) { filter.enable_filter(enable); } /** * Enable external filter. * * @param enable */ public void enable_external_filter(boolean enable) { extfilt.enable_filter(enable); } /** * I0() computes the 0th order modified Bessel function of the first kind. * This function is originally from resample-1.5/filterkit.c by J. O. Smith. * * @param x * @return */ protected double I0(double x) { // Max error acceptable in I0. final double I0e = 1e-6; double sum, u, halfx, temp; int n; sum = u = n = 1; halfx = x / 2.0; do { temp = halfx / n++; u *= temp * temp; sum += u; } while (u >= I0e * sum); return sum; } /** * Setting of SID sampling parameters. * <P> * 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. * <P> * 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. * <P> * The end of passband frequency is also limited: pass_freq <= * 0.9*sample_freq/2 * <P> * 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. * * @param clock_freq * @param method * @param sample_freq * @param pass_freq * @param filter_scale * @return */ public boolean set_sampling_parameters(double clock_freq, sampling_method method, double sample_freq, double pass_freq, double filter_scale) { // Check resampling constraints. if (method == sampling_method.SAMPLE_RESAMPLE_INTERPOLATE || method == sampling_method.SAMPLE_RESAMPLE_FAST) { // Check whether the sample ring buffer would overfill. if (FIR_N * clock_freq / sample_freq >= RINGSIZE) { return false; } } // 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; sampling = method; cycles_per_sample = (int /* cycle_count */) (clock_freq / sample_freq * (1 << FIXP_SHIFT) + 0.5); sample_offset = 0; sample_prev = 0; // FIR initialization is only necessary for resampling. if (method != sampling_method.SAMPLE_RESAMPLE_INTERPOLATE && method != sampling_method.SAMPLE_RESAMPLE_FAST) { sample = null; fir = null; return true; } final double pi = 3.1415926535897932385; // 16 bits -> -96dB stopband attenuation. final double A = -20 * Math.log10(1.0 / (1 << 16)); // A fraction of the bandwidth is allocated to the transition band, double dw = (1 - 2 * pass_freq / sample_freq) * pi; // The cutoff frequency is midway through the transition band. double wc = (2 * pass_freq / sample_freq + 1) * pi / 2; // For calculation of beta and N see the reference for the kaiserord // function in the MATLAB Signal Processing Toolbox: // http://www.mathworks.com/access/helpdesk/help/toolbox/signal/kaiserord.html final double beta = 0.1102 * (A - 8.7); final double I0beta = I0(beta); // The filter order will maximally be 124 with the current constraints. // N >= (96.33 - 7.95)/(2.285*0.1*pi) -> N >= 123 // The filter order is equal to the number of zero crossings, i.e. // it should be an even number (sinc is symmetric about x = 0). int N = (int) ((A - 7.95) / (2.285 * dw) + 0.5); N += N & 1; double f_samples_per_cycle = sample_freq / clock_freq; double f_cycles_per_sample = clock_freq / sample_freq; // The filter length is equal to the filter order + 1. // The filter length must be an odd number (sinc is symmetric about x = // 0). fir_N = (int) (N * f_cycles_per_sample) + 1; fir_N |= 1; // We clamp the filter table resolution to 2^n, making the fixpoint // sample_offset a whole multiple of the filter table resolution. int res = method == sampling_method.SAMPLE_RESAMPLE_INTERPOLATE ? FIR_RES_INTERPOLATE : FIR_RES_FAST; int n = (int) Math.ceil(Math.log(res / f_cycles_per_sample) / Math.log((double) 2)); fir_RES = 1 << n; // Allocate memory for FIR tables. fir = null; fir = new short[fir_N * fir_RES]; // Calculate fir_RES FIR tables for linear interpolation. for (int i = 0; i < fir_RES; i++) { int fir_offset = i * fir_N + fir_N / 2; double j_offset = (double) (i) / fir_RES; // Calculate FIR table. This is the sinc function, weighted by the // Kaiser window. for (int j = -fir_N / 2; j <= fir_N / 2; j++) { double jx = j - j_offset; double wt = wc * jx / f_cycles_per_sample; double temp = jx / (fir_N / 2); double Kaiser = Math.abs(temp) <= 1 ? I0(beta * Math.sqrt(1 - temp * temp)) / I0beta : 0; double sincwt = Math.abs(wt) >= 1e-6 ? Math.sin(wt) / wt : 1; double val = (1 << FIR_SHIFT) * filter_scale * f_samples_per_cycle * wc / pi * sincwt * Kaiser; fir[fir_offset + j] = (short) (val + 0.5); } } // Allocate sample buffer. if ((sample == null)) { sample = new short[RINGSIZE * 2]; } // Clear sample buffer. for (int j = 0; j < RINGSIZE * 2; j++) { sample[j] = 0; } sample_index = 0; return true; } /** * Adjustment of SID sampling frequency. * <P> * In some applications, e.g. a C64 emulator, it can be desirable to * synchronize sound with a timer source. This is supported by adjustment of * the SID sampling frequency. * <P> * NB! Adjustment of the sampling frequency may lead to noticeable shifts in * frequency, and should only be used for interactive applications. Note * also that any adjustment of the sampling frequency will change the * characteristics of the resampling filter, since the filter is not * rebuilt. * * @param sample_freq */ public void adjust_sampling_frequency(double sample_freq) { cycles_per_sample = (int /* cycle_count */) (clock_frequency / sample_freq * (1 << FIXP_SHIFT) + 0.5); } // ---------------------------------------------------------------------------- // Return array of default spline interpolation points to map FC to // filter cutoff frequency. // ---------------------------------------------------------------------------- public class FCPoints { public int[] /* fc_point */[] points; public int count; public FCPoints() { } } public void fc_default(FCPoints fcp) { filter.fc_default(fcp); } /** * Return FC spline plotter object. * * @return */ public PointPlotter fc_plotter() { return filter.fc_plotter(); } /** * SID clocking - 1 cycle. */ public void clock() { int i; // Age bus value. 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(); } // Clock oscillators. for (i = 0; i < 3; i++) { voice[i].wave.clock(); } // Synchronize oscillators. for (i = 0; i < 3; i++) { voice[i].wave.synchronize(); } // Clock filter. filter.clock(voice[0].output(), voice[1].output(), voice[2].output(), ext_in); // Clock external filter. extfilt.clock(filter.output()); } /** * SID clocking - delta_t cycles. * * @param delta_t */ public void clock(int /* 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. int /* cycle_count */delta_t_osc = delta_t; while (delta_t_osc != 0) { int /* 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 != 0) && (wave.freq != 0))) { continue; } int /* reg16 */freq = wave.freq; int /* reg24 */accumulator = wave.accumulator; // Clock on MSB off if MSB is on, clock on MSB on if MSB is off. int /* reg24 */delta_accumulator = ((accumulator & 0x800000) != 0 ? 0x1000000 : 0x800000) - accumulator; int /* cycle_count */delta_t_next = (delta_accumulator / freq); if ((delta_accumulator % freq) != 0) { ++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(), ext_in); // Clock external filter. extfilt.clock(delta_t, filter.output()); } public class CycleCount { public CycleCount(int delta_t2) { delta_t = delta_t2; } public int /* cycle_count */delta_t; } /** * SID clocking with audio sampling. Fixpoint arithmetics is used. * <P> * The example below shows how to clock the SID a specified amount of cycles * while producing audio output: * * <pre> * while (delta_t) { * bufindex += sid.clock(delta_t, buf + bufindex, buflength - bufindex); * write(dsp, buf, bufindex * 2); * bufindex = 0; * } * </pre> * * @return */ public int clock(CycleCount delta_t, short buf[], int n, int interleave) { switch (sampling) { default: case SAMPLE_FAST: return clock_fast(delta_t, buf, n, interleave); case SAMPLE_INTERPOLATE: return clock_interpolate(delta_t, buf, n, interleave); case SAMPLE_RESAMPLE_INTERPOLATE: return clock_resample_interpolate(delta_t, buf, n, interleave); case SAMPLE_RESAMPLE_FAST: return clock_resample_fast(delta_t, buf, n, interleave); } } /** * SID clocking with audio sampling - delta clocking picking nearest sample. * * @return */ protected int clock_fast(CycleCount delta_t, short buf[], int n, int interleave) { int s = 0; for (;;) { int /* cycle_count */next_sample_offset = sample_offset + cycles_per_sample + (1 << (FIXP_SHIFT - 1)); int /* cycle_count */delta_t_sample = next_sample_offset >> FIXP_SHIFT; if (delta_t_sample > delta_t.delta_t) { break; } if (s >= n) { return s; } clock(delta_t_sample); delta_t.delta_t -= delta_t_sample; sample_offset = (next_sample_offset & FIXP_MASK) - (1 << (FIXP_SHIFT - 1)); buf[s++ * interleave] = (short) output(); } clock(delta_t.delta_t); sample_offset -= delta_t.delta_t << FIXP_SHIFT; delta_t.delta_t = 0; return s; } /** * SID clocking with audio sampling - cycle based with linear sample * interpolation. * <P> * Here the chip is clocked every cycle. This yields higher quality sound * since the samples are linearly interpolated, and since the external * filter attenuates frequencies above 16kHz, thus reducing sampling noise. * * @return */ protected int clock_interpolate(CycleCount delta_t, short buf[], int n, int interleave) { int s = 0; int i; for (;;) { int /* cycle_count */next_sample_offset = sample_offset + cycles_per_sample; int /* cycle_count */delta_t_sample = next_sample_offset >> FIXP_SHIFT; if (delta_t_sample > delta_t.delta_t) { break; } if (s >= n) { return s; } for (i = 0; i < delta_t_sample - 1; i++) { clock(); } if (i < delta_t_sample) { sample_prev = (short) output(); clock(); } delta_t.delta_t -= delta_t_sample; sample_offset = next_sample_offset & FIXP_MASK; short sample_now = (short) output(); buf[s++ * interleave] = (short) (sample_prev + (sample_offset * (sample_now - sample_prev) >> FIXP_SHIFT)); sample_prev = sample_now; } for (i = 0; i < delta_t.delta_t - 1; i++) { clock(); } if (i < delta_t.delta_t) { sample_prev = (short) output(); clock(); } sample_offset -= delta_t.delta_t << FIXP_SHIFT; delta_t.delta_t = 0; return s; } /** * SID clocking with audio sampling - cycle based with audio resampling. * <P> * This is the theoretically correct (and computationally intensive) audio * sample generation. The samples are generated by resampling to the * specified sampling frequency. The work rate is inversely proportional to * the percentage of the bandwidth allocated to the filter transition band. * <P> * This implementation is based on the paper "A Flexible Sampling-Rate * Conversion Method", by J. O. Smith and P. Gosset, or rather on the * expanded tutorial on the "Digital Audio Resampling Home Page": * http://www-ccrma.stanford.edu/~jos/resample/ * <P> * By building shifted FIR tables with samples according to the sampling * frequency, this implementation dramatically reduces the computational * effort in the filter convolutions, without any loss of accuracy. The * filter convolutions are also vectorizable on current hardware. * <P> * Further possible optimizations are: * An equiripple filter design could * yield a lower filter order, see * http://www.mwrf.com/Articles/ArticleID/7229/7229.html * The Convolution * Theorem could be used to bring the complexity of convolution down from * O(n*n) to O(n*log(n)) using the Fast Fourier Transform, see * http://en.wikipedia.org/wiki/Convolution_theorem * Simply resampling in * two steps can also yield computational savings, since the transition band * will be wider in the first step and the required filter order is thus * lower in this step. Laurent Ganier has found the optimal intermediate * sampling frequency to be (via derivation of sum of two steps): 2 * * pass_freq + sqrt [ 2 * pass_freq * orig_sample_freq * (dest_sample_freq - * 2 * pass_freq) / dest_sample_freq ] * <P> * * @return */ protected int clock_resample_interpolate(CycleCount delta_t, short buf[], int n, int interleave) { int s = 0; for (;;) { int /* cycle_count */next_sample_offset = sample_offset + cycles_per_sample; int /* cycle_count */delta_t_sample = next_sample_offset >> FIXP_SHIFT; if (delta_t_sample > delta_t.delta_t) { break; } if (s >= n) { return s; } for (int i = 0; i < delta_t_sample; i++) { clock(); sample[sample_index] = sample[sample_index + RINGSIZE] = (short) output(); ++sample_index; sample_index &= 0x3fff; } delta_t.delta_t -= delta_t_sample; sample_offset = next_sample_offset & FIXP_MASK; int fir_offset = sample_offset * fir_RES >> FIXP_SHIFT; int fir_offset_rmd = sample_offset * fir_RES & FIXP_MASK; int fir_start = (fir_offset * fir_N); int sample_start = (sample_index - fir_N + RINGSIZE); // Convolution with filter impulse response. int v1 = 0; for (int j = 0; j < fir_N; j++) { v1 += sample[sample_start + j] * fir[fir_start + j]; } // Use next FIR table, wrap around to first FIR table using // previous sample. if (++fir_offset == fir_RES) { fir_offset = 0; --sample_start; } fir_start = (fir_offset * fir_N); // Convolution with filter impulse response. int v2 = 0; for (int j = 0; j < fir_N; j++) { v2 += sample[sample_start + j] * fir[fir_start + j]; } // Linear interpolation. // fir_offset_rmd is equal for all samples, it can thus be // factorized out: // sum(v1 + rmd*(v2 - v1)) = sum(v1) + rmd*(sum(v2) - sum(v1)) int v = v1 + (fir_offset_rmd * (v2 - v1) >> FIXP_SHIFT); v >>= FIR_SHIFT; // Saturated arithmetics to guard against 16 bit sample overflow. final int half = 1 << 15; if (v >= half) { v = half - 1; } else if (v < -half) { v = -half; } buf[s++ * interleave] = (short) v; } for (int i = 0; i < delta_t.delta_t; i++) { clock(); sample[sample_index] = sample[sample_index + RINGSIZE] = (short) output(); ++sample_index; sample_index &= 0x3fff; } sample_offset -= delta_t.delta_t << FIXP_SHIFT; delta_t.delta_t = 0; return s; } /** * SID clocking with audio sampling - cycle based with audio resampling. * * @return */ protected int clock_resample_fast(CycleCount delta_t, short buf[], int n, int interleave) { int s = 0; for (;;) { int /* cycle_count */next_sample_offset = sample_offset + cycles_per_sample; int /* cycle_count */delta_t_sample = next_sample_offset >> FIXP_SHIFT; if (delta_t_sample > delta_t.delta_t) { break; } if (s >= n) { return s; } for (int i = 0; i < delta_t_sample; i++) { clock(); sample[sample_index] = sample[sample_index + RINGSIZE] = (short) output(); ++sample_index; sample_index &= 0x3fff; } delta_t.delta_t -= delta_t_sample; sample_offset = next_sample_offset & FIXP_MASK; int fir_offset = sample_offset * fir_RES >> FIXP_SHIFT; int fir_start = (fir_offset * fir_N); int sample_start = (sample_index - fir_N + RINGSIZE); // Convolution with filter impulse response. int v = 0; for (int j = 0; j < fir_N; j++) { v += sample[sample_start + j] * fir[fir_start + j]; } v >>= FIR_SHIFT; // Saturated arithmetics to guard against 16 bit sample overflow. final int half = 1 << 15; if (v >= half) { v = half - 1; } else if (v < -half) { v = -half; } buf[s++ * interleave] = (short) v; } for (int i = 0; i < delta_t.delta_t; i++) { clock(); sample[sample_index] = sample[sample_index + RINGSIZE] = (short) output(); ++sample_index; sample_index &= 0x3fff; } sample_offset -= delta_t.delta_t << FIXP_SHIFT; delta_t.delta_t = 0; return s; } }