/**
* This file is a part of JaC64 - a Java C64 Emulator
* Main Developer: Joakim Eriksson (Dreamfabric.com)
* Contact: joakime@sics.se
* Web: http://www.dreamfabric.com/c64
* ---------------------------------------------------
*/
package com.dreamfabric.jac64;
/**
*
*
* @author Joakim Eriksson (joakime@sics.se)
* @version $Revision: 1.3 $, $Date: 2005/11/21 07:01:33 $
*/
public class SIDVoice6581 extends SIDVoice {
public static final int UPDATE_CYCLES = 1000; // 2 x each ms
public static final int PER_SEC = 1000000 / UPDATE_CYCLES;
public static final boolean DEBUG = false;
private int[] memory;
private int sidbase = 0;
// Frq = (Freg * Fclk / 16777216) Hz
// public static final double FRQCONV = 0.060975609;
// PAL Clock speed:
public static final int SAMPLE_RATE = 44000;
public static final double FRQCONV = 985248.4 / 16777216;
public static final int TRIANGLE = 0x1;
public static final int SAW = 0x2;
public static final int PULSE = 0x4;
public static final int NOISE = 0x8;
public static final int NONE = 0x0;
private int[] ST_LOOKUP = RS6581Waves.wave6581__ST;
private int[] PST_LOOKUP = RS6581Waves.wave6581_PST;
private int[] PT_LOOKUP = RS6581Waves.wave6581_P_T;
private int[] PS_LOOKUP = RS6581Waves.wave6581_PS_;
public static final String[] WAVE = new String[] {
"NONE", "TRIANGLE", "SAW", "TRI_SAW",
"PULSE", "TRI_PULSE", "SAW_PULSE", "TRI_SAW_PULSE",
"NOISE", "NOISE_T", "NOISE_S", "NOISE_TS",
"NOISE_P", "NOISE_TP", "NOISE_SP", "NOISE_TSP"};
// Should be public when used in software synths...
private final int ATTACK = 1;
private final int DECAY = 2;
private final int SUSTAIN = 3;
private final int RELEASE = 4;
private final int FINISHED = 5;
public static final String[] ADSR_PHASES = new String[]
{"-", "Attack", "Decay", "Sustain", "Relase", "Finished"};
final static int waveLen = 44000;
// The wavebuffers for the precalculated waves (shared between SIDs)
private static int[] sawWave;
private static int[] triangleWave;
private static int[] triangleWaveD2;
private static int[] pulseWave;
public final static int GENLEN = 44000 / PER_SEC;
public static final int GENS_PER_IRQ = (SAMPLE_RATE / 50) / GENLEN;
public static final int SAMPLE_BITS = 12;
// Maybe for 1/50th of a second???
public static final int MAXGENLEN = 10 * GENLEN;
public static final double SAMPLES_PER_MICRO = SAMPLE_RATE / 1000000.0;
public static final int CYCLES_PER_SAMPLE = 1000000 / SAMPLE_RATE;
public static final int VOLUME_SIZE = 4096;
public static final int DELAY_LEN = 44000/50;
byte[] buffer = new byte[4096];
int delPos = 0;
int diffMin = 0;
int diffDt = 1;
int delay[] = new int[DELAY_LEN];
int generated;
int smp;
int pwid = waveLen / 2;
long nextSample = 0;
long next_nextSample;
int noiseData = 0;
long noise_reg = 0x7ffff8;
boolean debugGen = false;
boolean sync = false;
boolean ring = false;
boolean testBit = false;
int testZero = 0;
private int[] outBuffer;
// Start stop ADSR stuff!! (not yet implemented, and how to do with gain?)
// Number of adsr microseconds (cycles)
long attackTime[] = { 2000, 8000, 16000, 24000,
38000, 56000, 68000, 80000,
100000, 250000, 500000, 800000,
1000000, 3000000, 5000000, 8000000};
public static final int ADSR_BITS = 22;
public static final int ADSR_RATE_BITS = 32;
long ATTACK_MAX = 255L << ADSR_BITS;
long RELEASE_FINISH_LEVEL = (1L << ADSR_BITS) - 1;
int ATTACK_FUZZ = 128; // For the comparison so that we do not overshoot!
long attackDelta[] = new long[attackTime.length];
long decayExp[] = {
4216751472L,4275278366L,4285111523L,4288394265L
,4290814737L,4292149050L,4292646253L,4292994329L
,4293388850L,4294335848L,4294651560L,4294769958L
,4294809425L,4294914671L,4294935721L,4294947561L
}; // was new long[attackTime.length];
long adsrLevel22 = 0;
long adsrSusLevel22 = 0;
long decayFactor = 0;
long releaseFactor = 0;
// Used while DECAY & RELEASE!
long currentDecayFactor = 0;
long currentAttackRate = 0;
int adsrPhase = ATTACK;
int adsrBug = 0;
int ad;
int sr;
// 8 => div by 2...
// 32 => mul by 2... // Same for all SID Channels!!!
public static int frqFac = 16;
// Stuff for the viewers...
public double adsrLevel;
long lastAttackTime = 0;
boolean soundOn = false;
// Maybe frq should not be integer?
public static final int FRQ_BITS = 4;
public static final long WAVELEN = waveLen << FRQ_BITS;
public static final long WAVELEN_HALF = WAVELEN / 2;
public long frq = 1;
public double trueFrq = 0;
public int wave;
public long next_frq; // FRQ for modulation, etc
public SIDVoice6581 next;
public SIDVoice6581(int mem[], int sb) {
intBuffer = new int[2048];
outBuffer = new int[2048];
memory = mem;
sidbase = sb;
System.out.println("SIDBASE: " + sidbase);
System.out.println("GENLEN: " + GENLEN);
for (int i = 0, n = attackTime.length; i < n; i++) {
int noSamples = (int) ((SAMPLE_RATE * attackTime[i]) / 1000000L);
// From zero to 255
long attack = (255L << ADSR_BITS) / noSamples;
attackDelta[i] = (long)(attack);
}
// No log/exp in J2ME
// System.out.println("Decay exp:");
// for (int i = 0, n = attackTime.length; i < n; i++) {
// int noSamples = (int) ((SAMPLE_RATE * attackTime[i] * 3) / 1000000L);
// double decayFactor = Math.exp(DECAY_LOG/noSamples);
// decayExp[i] = (long)((1L << ADSR_RATE_BITS) * decayFactor);
// System.out.println("," + decayExp[i]);
// }
// ensure that all arrays exists
if (sawWave == null) {
sawWave = new int[waveLen];
triangleWave = new int[waveLen];
triangleWaveD2 = new int[waveLen];
pulseWave = new int[2 * waveLen];
}
// -------------------------------------------------------------------
// This will create similar waves as was created using the 24 bit
// accumulator in the sid (which upper 12 bits for the sound gen.)
// -------------------------------------------------------------------
// Create all the waves!
double val = 0;
double dval = 0xfff / (double) waveLen;
// Create SAW
for (int i = 0; i < waveLen; i++) {
sawWave[i] = (int) val;
val = val + dval;
}
// Create PULSE (according to re-sid it is starting high!)
for (int i = 0; i < waveLen; i++) {
pulseWave[i] = 0xfff;
pulseWave[i + waveLen] = 0x0;
}
// Create Triangle
val = 0;
dval = 0xfff * 2 / (double) waveLen;
for (int i = 0; i < waveLen / 2; i++) {
triangleWave[i] = (int) val;
triangleWave[i + waveLen / 2] = (int) (0xfff - val);
triangleWaveD2[i] = (int) (val/2);
triangleWaveD2[i + waveLen / 2] = (int) ((0xfff - val) /2);
val = val + dval;
}
}
public void init() {
wave = NONE;
sync = false;
testBit = false;
ring = false;
nextSample = 0;
next_nextSample = 0;
next_frq = 0;
setControl(0,0);
}
public void setControl(int data, long cycles) {
wave = data >> 4;
if (DEBUG) {
System.out.println(getSIDNo() +
" Setting Wave: " + WAVE[wave] + " at " + cycles + " lastGen: " + lastCycles + " Gate: " + (data&1));
}
boolean oldTest = testBit;
testBit = (data & 0x08) != 0;
if (oldTest && !testBit) {
// When cleared -> reset sample play...
// System.out.println(getSIDNo() + " Testbit cleared!!!>>> " + cycles + " zeroed:" +
// testZero);
testZero = 0;
nextSample = 0;
noise_reg = 0x7ffff8;
}
// if (!oldTest && testBit) {
// System.out.println(getSIDNo() + " Testbit set!!!>>>" + cycles);
// }
if ((data & 1) > 0)
soundOn(cycles);
else
soundOff(cycles);
sync = (data & 2) != 0;
ring = (data & 4) != 0;
}
public void soundOn(long cycles){
if(!soundOn) {
// Set the data for the ADSR_12
if ((sr & 0xf) > (ad >> 4)) {
adsrBug = (int) (0x8000 * SAMPLES_PER_MICRO);
if (DEBUG)
System.out.println(getSIDNo() +
" CTRATK_ADSR BUG Triggered for " + adsrBug +
" samples" + " level: " +
(adsrLevel22 >> ADSR_BITS));
}
currentAttackRate = attackDelta[(memory[sidbase + 5] & 0xf0) >> 4];
decayFactor = decayExp[(memory[sidbase + 5] & 0x0f)];
releaseFactor = decayExp[(memory[sidbase + 6] & 0x0f)];
adsrSusLevel22 = (memory[sidbase + 6] & 0xf0 + 8) << ADSR_BITS;
currentDecayFactor = decayFactor;
adsrPhase = ATTACK;
// System.out.println(getSIDNo() + " Starting ATTACK: AD=" +
// memory[sidbase + 5] + " SR="+ memory[sidbase + 6] +
// " adsrLevel: " + (adsrLevel22 >> ADSR_BITS) + " " + cycles);
// ADSR Level should not be reset???
// adsrLevel22 = 0;
lastAttackTime = cycles;
soundOn = true;
}
}
public void soundOff(long cycles){
adsrPhase = RELEASE;
soundOn = false;
debugGen = false;
currentDecayFactor = releaseFactor;
adsrSusLevel22 = RELEASE_FINISH_LEVEL;
// System.out.println(getSIDNo() + " RELEASE: " + memory[sidbase + 6] +
// " diff from attack: " + (cycles - lastAttackTime));
}
public void setAD(int data, long cycles) {
// ADSR_12 just update the rates - nothing more to do!
currentAttackRate = attackDelta[(memory[sidbase + 5] & 0xf0) >> 4];
decayFactor = decayExp[(memory[sidbase + 5] & 0x0f)];
if (DEBUG) {
System.out.println(getSIDNo() + " Setting AD: " +
memory[sidbase + 5] + " " + cycles + " diff " +
(cycles - lastAttackTime) + " ADSRLv: " +
(adsrLevel22 >> ADSR_BITS));
}
ad = data;
// If we are currently decaying... change the current decay factor!
if (adsrPhase != RELEASE) {
if (currentDecayFactor > decayFactor) {
adsrBug = (int) (0x8000 * SAMPLES_PER_MICRO);
if (DEBUG) {
System.out.println(getSIDNo() +
" DEC_ADSR BUG Triggered for " + adsrBug +
" samples" + " level: " +
(adsrLevel22 >> ADSR_BITS));
}
}
currentDecayFactor = decayFactor;
}
}
public void setSR(int data, long cycles) {
int tmp = ((memory[sidbase + 6] & 0xf0) + 8) << ADSR_BITS;
releaseFactor = decayExp[(memory[sidbase + 6] & 0x0f)];
if (DEBUG) {
System.out.println(getSIDNo() + " Setting SR: " +
Integer.toString(memory[sidbase + 6], 16) +
" " + cycles + " diff " +
(cycles - lastAttackTime) + " ADSRLv: " +
(adsrLevel22 >> ADSR_BITS));
}
// Restart decay if sustain is reached, but we have lowered sustain level!!!
// ???
sr = data;
// If we are currently releasing...
if (adsrPhase == RELEASE) {
if (currentDecayFactor > releaseFactor) {
adsrBug = (int) (0x8000 * SAMPLES_PER_MICRO);
if (DEBUG) {
System.out.println(getSIDNo() +
" REL_ADSR BUG Triggered for " + adsrBug +
" samples" + " level: " +
(adsrLevel22 >> ADSR_BITS));
}
}
currentDecayFactor = releaseFactor;
} else if (adsrPhase == SUSTAIN) {
if (tmp < adsrSusLevel22) {
currentDecayFactor = releaseFactor;
adsrPhase = RELEASE;
}
}
adsrSusLevel22 = tmp;
}
public void reset() {
init();
soundOff(0);
}
public void printStatus() {
System.out.println("SID: " + getSIDNo() + " ----------------------------");
System.out.println("Wave: " + WAVE[wave]);
System.out.println("Frequency: " + frq + " PWid:" + pwid + " " + trueFrq);
System.out.println("ADSRLevel: " + (adsrLevel22 >> ADSR_BITS));
System.out.println("ADSRPhase: " + ADSR_PHASES[adsrPhase]);
System.out.println("AD reg: " + Integer.toString(memory[sidbase + 5], 16));
System.out.println("SR reg: " + Integer.toString(memory[sidbase + 6], 16));
System.out.println("WavePos: " + nextSample + " pulse => " + ((nextSample < pwid) ? "0xfff" : "0"));
if (ring)System.out.println("RING MODULATION");
if (sync)System.out.println("SYNCHRONIZATION");
if (testBit) System.out.println("TEST BIT SET!");
}
private int getSIDNo() {
return ((sidbase & 0xf) / 7);
}
private long lastCycles = 0;
private int decayTimes = 0;
// last sample
public int lastSample = 0;
// ADSR is 8 bits
public int adsrVol = 0;
long nanos;
long total;
int pwidArr[] = new int[SIDVoice6581.GENLEN];
// This is a crappy method... should not be used later...
public int[] generateSound(long cycles) {
updateSound(cycles);
// Cheat down generated to 0
generated = 0;
return outBuffer;
}
public void updatePulseWidth(long cycles) {
if ((wave & PULSE) != 0 && lastCycles > 0) {
int pos = (int) ((cycles - lastCycles) /
(SIDVoice6581.CYCLES_PER_SAMPLE + 10));
pwidArr[pos % SIDVoice6581.GENLEN] =
((((memory[sidbase + 3]& 0xf) << 8) +
memory[sidbase + 2]) * waveLen) / 4095;
} else {
pwid = ((((memory[sidbase + 3]& 0xf) << 8) +
memory[sidbase + 2]) * waveLen) / 4095;
}
}
public void updateSound(long cycles) {
// if (getSIDNo() == 0) {
// total = System.nanoTime() - nanos;
// }
// nanos = System.nanoTime();
int[] lookup;
int[] lookupW;
lastCycles = cycles;
// Always generate data - if cycle driven?!
// Maybe make the frq to be << 4 so that it is even more exact than
// now???
frq = (int) (0.5 + (1 << FRQ_BITS) * ((memory[sidbase + 1] << 8) +
memory[sidbase]) * FRQCONV);
trueFrq = (1 << FRQ_BITS) *
((memory[sidbase + 1] << 8) + memory[sidbase]) * FRQCONV;
pwid = ((((memory[sidbase + 3]& 0xf) << 8) +
memory[sidbase + 2])
* waveLen) / 4095;
if (frqFac != 16) {
frq = (frq * frqFac) >> 4;
trueFrq = (trueFrq * frqFac) / 16;
}
if (next != null) {
next_frq = next.frq;
// next_nextSample = next.nextSample;
}
int bIndex = generated;
switch(wave) {
case NONE:
for (int i = 0; i < GENLEN; i++) {
intBuffer[bIndex++] = 0xfff;
nextSample = (nextSample + frq) % WAVELEN;
}
break;
case TRIANGLE:
if (ring) {
boolean msb0 = false;
for (int i = 0; i < GENLEN; i++) {
boolean msb = (msb0 = nextSample >= WAVELEN_HALF) ^
(next_nextSample >= WAVELEN_HALF);
long samp = nextSample;
// Differ => modify position!
if (msb0 != msb) {
samp += msb0 ? -WAVELEN_HALF : WAVELEN_HALF;
}
intBuffer[bIndex++] = triangleWave[(int)(samp >> FRQ_BITS)];
nextSample = (nextSample + frq) % WAVELEN;
next_nextSample = (next_nextSample + next_frq) % WAVELEN;
}
} else if (!sync) {
for (int i = 0; i < GENLEN; i++) {
intBuffer[bIndex++] = triangleWave[(int)(nextSample >> FRQ_BITS)];
nextSample = (nextSample + frq) % WAVELEN;
}
} else {
// SYNCH
for (int i = 0; i < GENLEN; i++) {
intBuffer[bIndex++] = triangleWave[(int)(nextSample >> FRQ_BITS)];
nextSample = (nextSample + frq) % WAVELEN;
next_nextSample += next_frq;
if (next_nextSample > WAVELEN) {
nextSample = 0;
next_nextSample -= WAVELEN;
}
}
}
break;
case SAW:
if (!sync) {
for (int i = 0; i < GENLEN; i++) {
intBuffer[bIndex++] = sawWave[(int)(nextSample >> FRQ_BITS)];
nextSample = (nextSample + frq) % WAVELEN;
}
} else {
// SYNCH
for (int i = 0; i < GENLEN; i++) {
intBuffer[bIndex++] = sawWave[(int)(nextSample >> FRQ_BITS)];
nextSample = (nextSample + frq) % WAVELEN;
next_nextSample += next_frq;
if (next_nextSample > WAVELEN) {
nextSample = 0;
next_nextSample -= WAVELEN;
}
}
}
break;
case SAW | TRIANGLE:
if (!sync) {
for (int i = 0; i < GENLEN; i++) {
intBuffer[bIndex++] = ST_LOOKUP[sawWave[(int)
(nextSample >> FRQ_BITS)]];
nextSample = (nextSample + frq) % WAVELEN;
}
} else {
// SYNCH
for (int i = 0; i < GENLEN; i++) {
intBuffer[bIndex++] = ST_LOOKUP[sawWave[(int)
(nextSample >> FRQ_BITS)]];
nextSample = (nextSample + frq) % WAVELEN;
next_nextSample += next_frq;
if (next_nextSample > WAVELEN) {
nextSample = 0;
next_nextSample -= WAVELEN;
}
}
}
break;
case PULSE:
if (!sync) {
for (int i = 0; i < GENLEN; i++) {
if (pwidArr[i] != -1) {
pwid = pwidArr[i];
pwidArr[i] = -1;
}
intBuffer[bIndex++] =
pulseWave[pwid + (int)(nextSample >> FRQ_BITS)];
nextSample = (nextSample + frq) % WAVELEN;
}
} else {
for (int i = 0; i < GENLEN; i++) {
if (pwidArr[i] != -1) {
pwid = pwidArr[i];
pwidArr[i] = -1;
}
intBuffer[bIndex++] =
pulseWave[pwid + (int) (nextSample >> FRQ_BITS)];
nextSample = (nextSample + frq) % WAVELEN;
next_nextSample += next_frq;
if (next_nextSample > WAVELEN) {
nextSample = 0;
next_nextSample -= WAVELEN;
}
}
}
break;
case PULSE | SAW:
case PULSE | TRIANGLE:
case PULSE | SAW | TRIANGLE:
if (wave == (PULSE | SAW)) {
lookup = PS_LOOKUP;
lookupW = sawWave;
} else if (wave == (PULSE | TRIANGLE)) {
lookup = PT_LOOKUP;
lookupW = triangleWaveD2;
} else {
lookup = PST_LOOKUP;
lookupW = sawWave;
}
if (!sync) {
for (int i = 0; i < GENLEN; i++) {
if (pwidArr[i] != -1) {
pwid = pwidArr[i];
pwidArr[i] = -1;
}
int sam1 = pulseWave[pwid + (int) (nextSample >> FRQ_BITS)];
intBuffer[bIndex++] = sam1 != 0 ?
lookup[lookupW[(int)(nextSample >> FRQ_BITS)]] : 0;
nextSample = (nextSample + frq) % WAVELEN;
}
} else {
for (int i = 0; i < GENLEN; i++) {
if (pwidArr[i] != -1) {
pwid = pwidArr[i];
pwidArr[i] = -1;
}
int sam1 = pulseWave[pwid + (int)(nextSample >> FRQ_BITS)];
intBuffer[bIndex++] = sam1 != 0 ?
lookup[lookupW[(int)(nextSample >> FRQ_BITS)]] : 0;
nextSample = (nextSample + frq) % WAVELEN;
next_nextSample += next_frq;
if (next_nextSample > WAVELEN) {
nextSample = 0;
next_nextSample -= WAVELEN;
}
}
}
break;
case NOISE:
case NOISE | PULSE:
case NOISE | TRIANGLE:
case NOISE | SAW:
case NOISE | PULSE | SAW:
case NOISE | TRIANGLE | SAW:
case NOISE | PULSE | TRIANGLE:
case NOISE | PULSE | TRIANGLE |SAW:
// Noise generator inspired/adapted from the Re-sid sid emulator!
// But should be shifted even if noise is not played!!!
int delay = waveLen / 32;
for (int i = 0; i < GENLEN; i++) {
int newNoiseData = 0;
int loops = 0;
while (delay < 0) {
int bit0 = (int)
((noise_reg >> 22) ^ (noise_reg >> 17)) & 0x1;
noise_reg <<= 1;
noise_reg &= 0x7fffff;
noise_reg |= bit0;
if (loops < 4) {
newNoiseData += (int) (((noise_reg & 0x400000L) >> 11) |
((noise_reg & 0x100000L) >> 10) |
((noise_reg & 0x010000L) >> 7) |
((noise_reg & 0x002000L) >> 5) |
((noise_reg & 0x000800L) >> 4) |
((noise_reg & 0x000080L) >> 1) |
((noise_reg & 0x000010L) << 1) |
((noise_reg & 0x000004L) << 2));
loops++;
}
delay += waveLen / 32;
}
if (loops > 0) {
noiseData = (newNoiseData / loops);
}
delay -= frq;
intBuffer[bIndex++] = noiseData;
// Move register position even if sample not played...
nextSample = (nextSample + frq) % WAVELEN;
}
break;
default:
System.out.println("WAVE NOT IMPLEMENTED: " + wave);
}
// Afterprocessing after the waves has been generated!
// Setting ADSR volume
bIndex = generated;
for (int i = 0; i < GENLEN; i++) {
// ADSR_12 update!
if (adsrBug > 0) {
// If the ADSR bug have been triggered the ADSR will be "locked"
adsrBug--;
} else {
if (adsrPhase == ATTACK) {
adsrLevel22 += currentAttackRate;
if (adsrLevel22 + ATTACK_FUZZ > ATTACK_MAX) {
adsrLevel22 = ATTACK_MAX;
adsrPhase = DECAY;
}
} else if (adsrPhase == DECAY || adsrPhase == RELEASE) {
decayTimes++;
adsrLevel22 = (adsrLevel22 * currentDecayFactor) >> ADSR_RATE_BITS;
if (adsrLevel22 < adsrSusLevel22) {
// Do something!!!
if (adsrPhase == DECAY) {
decayTimes = 0;
adsrLevel22 = adsrSusLevel22;
adsrPhase = SUSTAIN;
} else {
adsrSusLevel22 = 0;
adsrPhase = FINISHED;
}
}
}
}
adsrVol = (int) (adsrLevel22 >> ADSR_BITS);
// For debug!
adsrLevel = (adsrLevel22 >> ADSR_BITS) / 255.0;
if (testBit) {
testZero++;
outBuffer[bIndex] = 0;
} else {
// FIX the sample output buffer to be centered around 0 + add volume
// How should this be done to really work fine - check resid?
// Resid goes down to 13 bits before filter (so do I?!)
outBuffer[bIndex] = 0x400 + ((intBuffer[bIndex] * adsrVol) >> 7);
}
bIndex++;
}
// if (getSIDNo() == 0) {
// long diff = System.nanoTime() - nanos;
// System.out.println("Elapsed ns: " + diff + " %: " +
// (diff * 100.0 / total));
// }
}
public int lastSample() {
return (intBuffer[(smp++) % GENLEN] >> 3) & 0xff;
}
}