/** * This file is a part of JaC64 - a Java C64 Emulator * Main Developer: Joakim Eriksson (JaC64.com Dreamfabric.com) * Contact: joakime@sics.se * Web: http://www.jac64.com/ * http://www.dreamfabric.com/c64 * --------------------------------------------------- */ package com.dreamfabric.jac64; /** * This mixes four channels of SID sounds (or three) * * 0 - 2 => 16 bits channels from the SID chip * * 3 => 8 bits from PSID sample player * * * Created: Fri Oct 28 17:45:19 2005 * * @author Joakim Eriksson * @version 1.0 */ public class SIDMixer { public static int BYTES_PER_SAMPLE = 2; public static final boolean NO_SOUND = false; public static final boolean DEBUG = false; // 16 bits, 1 second buffer public static int DL_BUFFER_SIZE = SIDVoice6581.SAMPLE_RATE * 2; // public static final int SYNCH_TIME = 1000 / 50; public static final int SYNCH_BUFFER = ((SIDVoice6581.SAMPLE_RATE * 2) / 50); public static final int EFX_NONE = 0; public static final int EFX_FLANGER_1 = 1; public static final int EFX_FLANGER_2 = 2; public static final int EFX_FLANGER_3 = 3; public static final int EFX_PHASER_1 = 4; public static final int EFX_PHASER_2 = 5; public static final int EFX_CHORUS_1 = 6; public static final int EFX_CHORUS_2= 7; public static final int EFX_ECHO_1 = 8; public static final int EFX_ECHO_2 = 9; public static final int EFX_REV_SMALL = 10; public static final int EFX_REV_MED = 11; public static final int EFX_REV_LARGE = 12; public static final int EFX_FSWEEP = 13; public static final int EFX_FSWEEP_RES = 14; private String[] efxNames = { "EFX_NONE", "EFX_FLANGER_1", "EFX_FLANGER_2", "EFX_FLANGER_3", "EFX_PHASER_1", "EFX_PHASER_2", "EFX_CHORUS_1", "EFX_CHORUS_2", "EFX_ECHO_1", "EFX_ECHO_2", "EFX_REV_SMALL", "EFX_REV_MED", "EFX_REV_LARGE", "EFX_FSWEEP", "EFX_FSWEEP_RES", }; // if set this will never block... public boolean fullSpeed = false; private SIDVoice psid; private SIDVoice6581[] channels; private boolean soundOn = true; private SIDMixerListener listener = null; private AudioDriver driver; // Fixed size buffers byte[] buffer = new byte[SIDVoice6581.GENLEN * 2]; byte[] syncBuffer = new byte[4096]; int[] intBuffer = new int[SIDVoice6581.GENLEN]; int[] noFltBuffer = new int[SIDVoice6581.GENLEN]; // Effects buffers and variables boolean effects = false; //boolean moog = false; public static final int LFO_WAVELEN = 500; int[] echo; int echoSize = 0; int echoPos = 0; int echoLFODiff = 0; int echoLFODiffMax = 0; int echoLFODepth = 50; int echoFeedback = 0; int echoLFOSpeed = 0; int echoLFOPos = 0; int echoDW = 50; int maxefx; int minefx; int sidVol = 15; int sidVolArr[] = new int[SIDVoice6581.GENLEN]; long lastCycles = 0; // Filter (some stuff public for viewing ) int filterVal = 0; public int cutoff = 0; public int resonance = 0; int filterOn = 0; int masterVolume = 100; boolean lpOn = false; boolean hpOn = false; boolean bpOn = false; // SIDFilter long vlp; long vhp; long vbp; long w0; long div1024Q; // ExtFilter long exVlp; long exVhp; long exVo; long exw0lp; long exw0hp; int irq = 0; int[] sine10Hz; //private MoogFilter moogFilter; //private int moogResonance = 0; //private int moogCutoff = 0; //private int mdep = 0; public SIDMixer() { } /** * Creates a new <code>SIDMixer</code> instance. * * */ public SIDMixer(SIDVoice6581[] channels, SIDVoice sample, AudioDriver driver) { init(channels, sample, driver); } public void init(SIDVoice6581[] channels, SIDVoice sample, AudioDriver driver) { this.channels = channels; psid = sample; this.driver = driver; // moogFilter = new MoogFilter(SID6581.SAMPLE_RATE); // moogFilter.setFilterParams(120, 0.5); System.out.println("Micros per SIDGen: " + getMicrosPerGen()); // From resid... (~1 Mhz clock...) Extfilter exw0hp = 105; exw0lp = 104858; sine10Hz = new int[LFO_WAVELEN]; for (int i = 0, n = LFO_WAVELEN; i < n; i++) { sine10Hz[i] = (int) (500 + (500 * Math.sin(i * 2 * 3.1415 / LFO_WAVELEN))); } setEFX(EFX_ECHO_1); setEFX(EFX_NONE); } public void setListener(SIDMixerListener front) { listener = front; } // ------------------------------------------------------------------- // Sound effects after the mixer! // ------------------------------------------------------------------- public void setEchoTime(int millisDelay) { int sampleSize = millisDelay * SIDVoice6581.SAMPLE_RATE / 1000; System.out.println("SamplesDelay: " + sampleSize); echoSize = sampleSize; echo = new int[echoSize]; echoLFODiffMax = (echoSize * echoLFODepth) / 110; echoLFODiff = 0; echoPos = 0; } public int getEchoTime() { return (1000 * echoSize) / SIDVoice6581.SAMPLE_RATE; } public int getEFXCount() { return efxNames.length; } public String getEFXName(int efx) { return efxNames[efx]; } public void setEchoFeedback(int percen) { echoFeedback = percen; } public int getEchoFeedback() { return echoFeedback; } public void setEchoLFOSpeed(int hzd10) { // 10Hz wave => hz * 10 for speed in that... echoLFOSpeed = hzd10; } public int getEchoLFOSpeed() { return echoLFOSpeed; } public void setEchoDW(int percent) { echoDW = percent; } public int getEchoDW() { return echoDW; } public void setEchoLFODepth(int percent) { echoLFODepth = percent; echoLFODiffMax = (echoSize * echoLFODepth) / 110; } public int getEchoLFODepth() { return echoLFODepth; } public boolean getEffectsOn() { return effects; } public void setEFX(int fx) { fx = fx % efxNames.length; effects = true; // moog = false; switch (fx) { case EFX_NONE: effects = false; break; case EFX_FLANGER_1: setEchoTime(5); setEchoFeedback(75); setEchoLFOSpeed(1); setEchoLFODepth(35); setEchoDW(33); break; case EFX_FLANGER_2: setEchoTime(15); setEchoFeedback(70); setEchoLFOSpeed(5); setEchoLFODepth(35); setEchoDW(35); break; case EFX_FLANGER_3: setEchoTime(2); setEchoFeedback(85); setEchoLFOSpeed(3); setEchoLFODepth(55); setEchoDW(30); break; case EFX_PHASER_1: setEchoTime(10); setEchoFeedback(0); setEchoLFOSpeed(1); setEchoLFODepth(75); setEchoDW(50); break; case EFX_PHASER_2: setEchoTime(3); setEchoFeedback(0); setEchoLFOSpeed(2); setEchoLFODepth(85); setEchoDW(40); break; case EFX_CHORUS_1: setEchoTime(25); setEchoFeedback(60); setEchoLFOSpeed(1); setEchoLFODepth(35); setEchoDW(35); break; case EFX_CHORUS_2: setEchoTime(30); setEchoFeedback(50); setEchoLFOSpeed(5); setEchoLFODepth(25); setEchoDW(35); break; case EFX_ECHO_1: setEchoTime(150); setEchoFeedback(0); setEchoLFOSpeed(0); setEchoLFODepth(0); setEchoDW(33); break; case EFX_ECHO_2: setEchoTime(300); setEchoFeedback(33); setEchoLFOSpeed(0); setEchoLFODepth(0); setEchoDW(45); break; case EFX_REV_SMALL: setEchoTime(70); setEchoFeedback(40); setEchoLFOSpeed(0); setEchoLFODepth(0); setEchoDW(33); break; case EFX_REV_MED: setEchoTime(130); setEchoFeedback(50); setEchoLFOSpeed(0); setEchoLFODepth(0); setEchoDW(33); break; case EFX_REV_LARGE: setEchoTime(100); setEchoFeedback(70); setEchoLFOSpeed(0); setEchoLFODepth(0); setEchoDW(40); break; // case EFX_FSWEEP: // effects = false; // moog = true; // moogResonance = 30; // mdep = 60; // mfrq = 1; // moogCutoff = 1000; // break; // case EFX_FSWEEP_RES: // effects = false; // moog = true; // moogResonance = 75; // mdep = 70; // mfrq = 4; // moogCutoff = 500; // break; } if (listener != null) { listener.updateValues(); } } public void setEffectsOn(boolean fx) { effects = fx; } public boolean isEffectsOn() { return effects; } // ------------------------------------------------------------------- // Control for the SID Mixer // ------------------------------------------------------------------- public void setFilterCutoffLO(int data) { cutoff = cutoff & 0xff8 | (data & 0x07); recalcFilter(); } public void setFilterCutoffHI(int data) { cutoff = cutoff & 0x07 | (data << 3); recalcFilter(); } public void setFilterResonance(int data) { resonance = data; recalcFilter(); } public void setFilterCtrl(int data) { lpOn = (data & 0x10) > 0; bpOn = (data & 0x20) > 0; hpOn = (data & 0x40) > 0; if (DEBUG) { System.out.println("Filter: " + data + " => " + (lpOn ? "LP " : "") + (bpOn ? "BP " : "") + (hpOn ? "HP " : "")); } recalcFilter(); } public void setFilterOn(int on) { filterOn = on; } public void setMoogFilterOn(boolean on) { // moog = on; } public boolean isMoogFilterOn() { return false; // moog; } public void setMoogResonance(int percent) { // moogResonance = percent; } public int getMoogResonance() { return 0; //moogResonance; } public void setMoogCutoff(int frq) { // moogCutoff = frq; } public int getMoogCutoff() { return 0;// moogCutoff; } public void setMoogSpeed(int hz10) { // mfrq = hz10; } public int getMoogSpeed() { return 0;//mfrq; } public void setMoogDepth(int dep) { //mdep = dep; } public int getMoogDepth() { return 0; //mdep; } public void setVolume(int vol) { sidVol = vol; } public void setVolume(int vol, long cycles) { if (lastCycles > 0) { int pos = (int) ((cycles - lastCycles) / (SIDVoice6581.CYCLES_PER_SAMPLE + 10)); sidVolArr[pos % SIDVoice6581.GENLEN] = vol; } else { sidVol = vol; } } private void recalcFilter() { int fCutoff = 30 + (12000 * cutoff / 2048); w0 = (long) (2 * Math.PI * fCutoff * 1.048576); div1024Q = (int) (1024.0 / (0.707 + 1.0 * resonance / 15)); } public void stop() { setVolume(0); } public void reset() { exVo = 0; exVhp = 0; exVlp = 0; cutoff = 0; resonance = 0; w0 = 0; div1024Q = 0; filterOn = 0; maxefx = 0; minefx = 0; for (int i = 0, n = SIDVoice6581.GENLEN; i < n; i++) { sidVolArr[i] = -1; } setVolume(15); recalcFilter(); } public void printStatus() { System.out.println("SIDMixer ----------------------------"); System.out.println("Volume: " + sidVol); System.out.println("FilterOn: " + filterOn); System.out.println("Cutoff: " + cutoff); System.out.println("Resonance: " + resonance); System.out.println("Max Efx:" + maxefx); System.out.println("Min Efx:" + minefx); // moogFilter.printStatus(); } public void setFullSpeed(boolean fs) { System.out.println("Set full speed: " + fs); fullSpeed = fs; } public boolean fullSpeed() { return fullSpeed; } public int[] getBuffer() { return intBuffer; } public static final int SLEEP_SYNC = 1; private int sleep = 100; private int syncMode = 0;//SLEEP_SYNC; private double avg = 10; private long lastTime = System.currentTimeMillis(); private long micros = 0; public boolean updateSound(long cycles) { // For all the normal SID generator channels generate the sound! // and mix it into the intBuffer // should return true when irq!!! irq++; boolean trueIRQ = (irq % SIDVoice6581.GENS_PER_IRQ) == 0; // How do we delay the short time that we should delay here??? if (trueIRQ) { if (syncMode == SLEEP_SYNC && !fullSpeed) { long elapsed = System.currentTimeMillis() - lastTime; lastTime = System.currentTimeMillis(); avg = 0.99 * avg + 0.01 * elapsed; // avg should be 20 => // 20 = sleep + X // avg = sleep + X // sleep = avg - X if (avg < 20) sleep++; if (avg > 20) sleep--; System.out.println("Avg: " + avg + " Sleep: " + sleep / 10.0 + " " + (driver.getMicros() - micros)); double slm = 19.75 - ((driver.getMicros() - micros) / 1000.0); try { Thread.sleep((int) slm); } catch (Exception e) { e.printStackTrace(); } micros = driver.getMicros(); } } if (!driver.hasSound()) { // No sound - no continue... return trueIRQ; } // If either fullspeed or SLEEP_SYNC then check if there are room // for more samples! if (fullSpeed || syncMode == SLEEP_SYNC) { if (driver.available() < SIDVoice6581.GENLEN * 2) { return false; } } lastCycles = cycles; if (soundOn) { int [] tBuf; if (psid != null) { tBuf = psid.generateSound(cycles); for (int j = 0, m = SIDVoice6581.GENLEN; j < m; j++) { intBuffer[j] = 0; // From a byte array (low 8 bits) noFltBuffer[j] = (tBuf[j] << 4); } } else { for (int j = 0, m = SIDVoice6581.GENLEN; j < m; j++) { intBuffer[j] = 0; noFltBuffer[j] = 0; } } for (int i = 0, n = channels.length; i < n; i++) { int[] buf = channels[i].generateSound(cycles); if ((filterOn & (1 << i)) > 0) tBuf = intBuffer; else tBuf = noFltBuffer; for (int j = 0, m = SIDVoice6581.GENLEN; j < m; j++) { tBuf[j] += buf[j] >> 2; } } // Internal SID emulation processing // Filters... for (int i = 0, n = SIDVoice6581.GENLEN; i < n; i++) { int inval = intBuffer[i]; // SID filter - filter shuold be fed even if not on... if (filterOn > 0) { // 8 + 8 + 7 => 23 which is the number of cycles per sample vbp -= 8 * w0 * vhp >> 20; vlp -= 8 * w0 * vbp >> 20; vhp = (vbp * div1024Q >> 10) - vlp - inval; vbp -= 8 * w0 * vhp >> 20; vlp -= 8 * w0 * vbp >> 20; vhp = (vbp * div1024Q >> 10) - vlp - inval; vbp -= 7 * w0 * vhp >> 20; vlp -= 7 * w0 * vbp >> 20; vhp = (vbp * div1024Q >> 10) - vlp - inval; inval = (int) ((bpOn ? vbp : 0) + (hpOn ? vhp : 0) + (lpOn ? vlp : 0)); } inval += noFltBuffer[i]; // 13 bits * sidVol => 13 + 4 = 17 bits... take it down to 14... if (sidVolArr[i] != -1) { sidVol = sidVolArr[i]; sidVolArr[i] = -1; } inval = (inval * sidVol) >> 2; // External filter // run 8 + 8 + 7 cycles exVlp += (8 * exw0lp >> 8) * (inval - exVlp) >> 12; exVhp += exw0hp * 8 * (exVlp - exVhp) >> 20; exVlp += (8 * exw0lp >> 8) * (inval - exVlp) >> 12; exVhp += exw0hp * 8 * (exVlp - exVhp) >> 20; exVo = exVlp - exVhp; exVlp += (7 * exw0lp >> 8) * (inval - exVlp) >> 12; exVhp += exw0hp * 7 * (exVlp - exVhp) >> 20; intBuffer[i] = (int) exVo; } // if (moog) // moogFilter.performFilter(intBuffer, SID6581.GENLEN); if (effects) { for (int i = 0, n = SIDVoice6581.GENLEN; i < n; i++) { int exVal = intBuffer[i]; int echoRead = (echoPos + echoLFODiff) % echoSize; int out = (exVal * (100 - echoDW) + echo[echoRead] * echoDW) / 100; if (out > 32767) out = 32767; if (out < -32767) out = -32767; intBuffer[i] = out; exVal = exVal + ((echo[echoRead] * echoFeedback) / 100); if (exVal > maxefx) maxefx = exVal; if (exVal < minefx) minefx = exVal; if (exVal > 32767) exVal = 32767; if (exVal < -32767) exVal = -32767; echo[echoPos] = exVal; echoPos = (echoPos + 1) % echoSize; } } int bIndex = 0; if (BYTES_PER_SAMPLE == 2) { for (int i = 0, n = SIDVoice6581.GENLEN; i < n; i++) { buffer[bIndex++] = (byte) (intBuffer[i] & 0xff); buffer[bIndex++] = (byte) ((intBuffer[i] >> 8)); } } else { for (int i = 0, n = SIDVoice6581.GENLEN; i < n; i++) { buffer[bIndex++] = (byte) ((intBuffer[i] >> 8)); } } } driver.write(buffer); if (trueIRQ) { echoLFODiff = (echoLFODiffMax * sine10Hz[echoLFOPos]) / 1000; echoLFOPos = (echoLFOPos + echoLFOSpeed) % LFO_WAVELEN; } return trueIRQ; } public int getMicrosPerGen() { long mpg = 1000000l * SIDVoice6581.GENLEN; return (int) (mpg / SIDVoice6581.SAMPLE_RATE); } public boolean soundOn() { return soundOn; } public void setSoundOn(boolean on) { soundOn = on; if (soundOn == false) { for (int i = 0, n = buffer.length; i < n; i++) { buffer[i] = 0; } } } }