/*
* HalfNES by Andrew Hoffman
* Licensed under the GNU GPL Version 3. See LICENSE file
*/
package com.grapeshot.halfnes.audio;
//import com.grapeshot.halfnes.ui.DebugUI;
import static com.grapeshot.halfnes.utils.*;
//import java.awt.image.BufferedImage;
import java.util.Arrays;
/**
*
* @author Andrew
*/
public class VRC7SoundChip implements ExpansionSoundChip {
//Emulates the YM2413 sound chip, pretty much only found in Lagrange Point
//sound test in lagrange point: hold A and B on controller 2 and reset.
//this is the cut-down version from the vrc7. Only 6 channels, no percussion.
private static enum EnvState {
CUTOFF, ATTACK, DECAY, RELEASE;
}
private final EnvState[] modenv_state = new EnvState[6], carenv_state = new EnvState[6];
private final int[] vol = new int[6], freq = new int[6],
octave = new int[6], instrument = new int[6],
mod = new int[6],
oldmodout = new int[6], out = new int[6];
private final boolean[] key = new boolean[6], chSust = new boolean[6];
private int fmctr = 0, amctr = 0; //free running counter for indices
private final double[] phase = new double[6];
private final int[] usertone = new int[8], modenv_vol = new int[6], carenv_vol = new int[6];
private final int[][] instdata = { //instrument parameters
usertone, //modifiable user tone register is instrument 0
//i'm surprised no one's bothered to decap it and take a look
//here's the latest one from rainwarrior aug.2012
{0x03, 0x21, 0x05, 0x06, 0xB8, 0x82, 0x42, 0x27},
{0x13, 0x41, 0x13, 0x0D, 0xD8, 0xD6, 0x23, 0x12},
{0x31, 0x11, 0x08, 0x08, 0xFA, 0x9A, 0x22, 0x02},
{0x31, 0x61, 0x18, 0x07, 0x78, 0x64, 0x30, 0x27},
{0x22, 0x21, 0x1E, 0x06, 0xF0, 0x76, 0x08, 0x28},
{0x02, 0x01, 0x06, 0x00, 0xF0, 0xF2, 0x03, 0xF5},
{0x21, 0x61, 0x1D, 0x07, 0x82, 0x81, 0x16, 0x07},
{0x23, 0x21, 0x1A, 0x17, 0xCF, 0x72, 0x25, 0x17},
{0x15, 0x11, 0x25, 0x00, 0x4F, 0x71, 0x00, 0x11},
{0x85, 0x01, 0x12, 0x0F, 0x99, 0xA2, 0x40, 0x02},
{0x07, 0xC1, 0x69, 0x07, 0xF3, 0xF5, 0xA7, 0x12},
{0x71, 0x23, 0x0D, 0x06, 0x66, 0x75, 0x23, 0x16},
{0x01, 0x02, 0xD3, 0x05, 0xA3, 0x92, 0xF7, 0x52},
{0x61, 0x63, 0x0C, 0x00, 0x94, 0xAF, 0x34, 0x06},
{0x21, 0x62, 0x0D, 0x00, 0xB1, 0xA0, 0x54, 0x17}
};
private final static int[] LOGSIN = genlogsintbl(), EXP = genexptbl(),
AM = genamtbl();
private final static double[] MULTIPLIER = {0.5, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 10, 12, 12, 15, 15}, VIBRATO = genvibtbl();
private final static int[] KEYSCALE = {0, 1536, 2048, 2368, 2560,
2752, 2880, 3008, 3072, 3200, 3264, 3328, 3392, 3456, 3520, 3584
};
public VRC7SoundChip() {
Arrays.fill(modenv_state, EnvState.CUTOFF);
Arrays.fill(carenv_state, EnvState.CUTOFF);
Arrays.fill(modenv_vol, 511);
Arrays.fill(carenv_vol, 511);
}
public static int clamp(final int a) {
return (a != (a & 0xff)) ? ((a < 0) ? 0 : 255) : a;
}
private static double[] genvibtbl() {
//vibrato wavetable. Yes this is a waste of memory. sue me.
//from looking at genplus gx code, the vibrato depth is supposed
//to vary per octave, but exactly how is complex.
double l = 1789773 / 6.;
double f = 6.4;
int depth = 10; //blatant guess
double[] tbl = new double[(int) Math.ceil(l / f)];
for (int x = 0; x < tbl.length; ++x) {
tbl[x] = (depth * tri(2 * Math.PI * f * x / l));
}
return tbl;
}
private static int[] genamtbl() {
double l = 1789773 / 6.;
double f = 3.7;
int depth = 128;
int[] tbl = new int[(int) Math.ceil(l / f)];//one full cycle of wave
for (int x = 0; x < tbl.length; ++x) {
tbl[x] = (int) (depth * tri(2 * Math.PI * f * x / l) + depth);
//should be a triangle wave?
}
return tbl;
}
private static double tri(double x) {
//triangle wave function.
x %= 2 * Math.PI;
if (x < (Math.PI / 2)) {
return x / (Math.PI);
} else if (x < (3 * Math.PI) / 2) {
return 1 - (x / (Math.PI));
} else {
return x / (Math.PI) - 2;
}
}
private static int[] genlogsintbl() {
int[] tbl = new int[256];
for (int i = 0; i < tbl.length; ++i) {
//y = round(-log(sin((x+0.5)*pi/256/2))/log(2)*256)
//see https://docs.google.com/Doc?id=dd8kqn9f_13cqjkf4gp for info
tbl[i] = (int) Math.round(-Math.log(Math.sin((i + 0.5) * Math.PI / 256 / 2)) / Math.log(2) * 256);
}
return tbl;
}
private static int[] genexptbl() {
int[] tbl = new int[256];
for (int i = 0; i < tbl.length; ++i) {
//y = round((power(2, x/256)-1)*1024)
tbl[i] = (int) Math.round((Math.pow(2, i / 256.) - 1) * 1024.);
}
return tbl;
}
@Override
public final void write(int register, int data) {
switch (register) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
//parameters for instrument 0 (user settable instrument)
usertone[register & 7] = data;
break;
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15: //frequency registers for ch. 0-5
int n = register - 0x10;
freq[n] = (freq[n] & 0xf00) | data;
break;
case 0x20:
case 0x21:
case 0x22:
case 0x23:
case 0x24:
case 0x25: // ??stooof
//f: Upper bit of frequency
//o: Octave Select
//t: Channel keying on/off (key on = note starts, key off: note decays).
//s: bit 5 is _channel_ sustain,
//?: bit 6 and 7 are unused?
int m = register - 0x20;
octave[m] = (data >> 1) & 7;
freq[m] = (freq[m] & 0xff) | ((data & 1) << 8);
if (((data & (BIT4)) != 0) && !key[m]) {
//when note is keyed on
carenv_state[m] = EnvState.CUTOFF;
modenv_state[m] = EnvState.CUTOFF;
// printarray(key);
}
//TODO: when key is released,
//modulator release shouldn't do anything if sustain is on
//http://famitracker.com/forum/posts.php?id=6804
key[m] = ((data & (BIT4)) != 0);
chSust[m] = ((data & (BIT5)) != 0);
break;
case 0x30:
case 0x31:
case 0x32:
case 0x33:
case 0x34:
case 0x35: //top 4 bits instrument number, bottom 4 volume
int j = register - 0x30;
vol[j] = data & 0xf;
//System.err.println(j + " " + hex(data));
instrument[j] = (data >> 4) & 0xf;
break;
default:
//System.err.println(hex(register) + " doesn't exist " + hex(data));
}
}
int ch = 0;
@Override
public final void clock(final int cycle) {
/*
real chip runs at ~3.6 mhz from a separate oscillator
but this emulation operates at NTSC NES clock freq of 1.789
this would be a problem if anyone wanted to use it on a PAL system
real chip takes 72 cycles to advance through all ch (49716 hz)
I update all ch every 36 NES clocks (about the same speed) but I do the
carrier and the modulator in the same cycle instead of separately
*/
for (int i = 0; i < cycle; ++i) {
ch = (ch + 1) % (36);
if (ch < 6) {
operate();
}
}
}
private void operate() {
fmctr = (fmctr + 1) % VIBRATO.length;
amctr = (amctr + 1) % AM.length;
phase[ch] += (1 / 512.) * (freq[ch] << (octave[ch]));
//Tuned this with audacity so it's definitely ok this time.
phase[ch] %= 1024;
int[] inst = instdata[instrument[ch]];
//envelopes
int modEnvelope = setenvelope(inst, modenv_state, modenv_vol, ch, false)
<< 2;
int carEnvelope = setenvelope(inst, carenv_state, carenv_vol, ch, true)
<< 2;
//key scaling
int keyscale = KEYSCALE[freq[ch] >> 5] - 512 * (7 - octave[ch]);
if (keyscale < 0) {
keyscale = 0;
}
int modks = inst[2] >> 6;
modks = (modks == 0) ? 0 : (keyscale >> (3 - modks));
int carks = (inst[3] >> 6);
carks = (carks == 0) ? 0 : (keyscale >> (3 - carks));
int fb = (~inst[3] & 7);
//now the operator cells
//invaluable info: http://gendev.spritesmind.net/forum/viewtopic.php?t=386
//http://www.smspower.org/maxim/Documents/YM2413ApplicationManual
//http://forums.nesdev.com/viewtopic.php?f=3&t=9102
final double modVibrato = ((inst[0] & (BIT6)) != 0) ? VIBRATO[fmctr] * (1 << octave[ch]) : 0;
final double modFreqMultiplier = MULTIPLIER[inst[0] & 0xf];
final int modFeedback = (fb == 7) ? 0 : (mod[ch] + oldmodout[ch]) >> (2 + fb);
//no i don't know why it adds the last 2 old outputs but MAME
//does it that way and the feedback doesn't sound right w/o it
final int mod_f = modFeedback + (int) (modVibrato + modFreqMultiplier * phase[ch]);
//each of these values is an attenuation value
final int modVol = (inst[2] & 0x3f) * 32;//modulator vol
final int modAM = ((inst[0] & (BIT7)) != 0) ? AM[amctr] : 0;
final boolean modRectify = ((inst[3] & (BIT3)) != 0);
//calculate modulator operator value
mod[ch] = operator(mod_f, (int) (modVol + modEnvelope + modks + modAM), modRectify) << 2;
oldmodout[ch] = mod[ch];
//now repeat most of that for the carrier
final double carVibrato = ((inst[1] & (BIT6)) != 0) ? VIBRATO[fmctr] * (freq[ch] << octave[ch]) / 512. : 0;
final double carFreqMultiplier = MULTIPLIER[inst[1] & 0xf];
final int carFeedback = (mod[ch] + oldmodout[ch]) >> 1; //inaccurately named
final int car_f = carFeedback + (int) (carVibrato + carFreqMultiplier * phase[ch]);
final int carVol = vol[ch] * 128; //4 bits for carrier vol not 6
final int carAM = ((inst[1] & (BIT7)) != 0) ? AM[amctr] : 0;
final boolean carRectify = ((inst[3] & (BIT4)) != 0);
out[ch] = operator(car_f, (int) (carVol + carEnvelope + carks + carAM), carRectify) << 2;
outputSample(ch);
}
private int operator(final int phase, final int gain, final boolean rectify) {
return exp((logsin(phase, rectify) + gain));
}
private int exp(int val) {
//perform e^x function on 13 bit fp output value using the hardware table on the chip
//value should never be negative; if it is, find out why.
// if (val < 0) {
// val = 0;
// System.err.println("why");
//
// }
//values saturate instead of rolling over in the actual hardware
if (val > (BIT13) - 1) {
val = (BIT13) - 1;
}
//val &= (BIT12); //uncomment for sega smash pack vol. 1 for dreamcast
int mantissa = EXP[(-val & 0xff)];
int exponent = (-val) >> 8;
// int a = (int) Math.scalb(mantissa + 1024, exponent) * s; //correct but slow
return ((((mantissa + 1024) >> (-exponent)))) * s; //not correct for negative #s
}
private int s; // sign flag: positive (1), negative (-1) or muted (0)
private int logsin(final int x, final boolean rectify) {
//s stores sign of the output, in actual hw the sign bit bypasses
//everything else and goes directly to the dac.
switch ((x >> 8) & 3) {
case 0:
s = 1;
return LOGSIN[(x & 0xff)];
case 1:
s = 1;
return LOGSIN[255 - (x & 0xff)];
case 2:
s = rectify ? 0 : -1;
return LOGSIN[(x & 0xff)];
case 3:
default:
s = rectify ? 0 : -1;
return LOGSIN[255 - (x & 0xff)];
}
}
int lpaccum = 0;
int lpaccum2 = 0;
private void outputSample(int ch) {
int sample = out[ch] * 24;
//two stage low pass filter (looked @ schematic of hybrid on PCB)
sample += lpaccum;
lpaccum -= sample >> 2;
int j = lpaccum;
j += lpaccum2;
lpaccum2 -= j >> 2;
}
@Override
public final int getval() {
return lpaccum2;
}
final private static int ZEROVOL = 8388608; //2^23
final private static int MAXVOL = 0;
// Twiddler t = new Twiddler(0.4);
private int setenvelope(final int[] instrument, final EnvState[] state,
final int[] vol, final int ch, final boolean isCarrier) {
final boolean keyscaleRate = ((instrument[(isCarrier ? 1 : 0)] & (BIT4)) != 0);
final int ksrShift = keyscaleRate ?
(octave[ch] << 1) + (freq[ch] >> 8)
: octave[ch] >> 1;
//^ the key scaling bit (java should really have unions, this is such a mess)
/*
Most of these constants were computed backwards from a
table in a badly translated YM2413 technical manual
(that was given in ms to decay 40db) and are thus only approximate.
The key scaling stuff is similarly just a best guess.
I suspect it's a 23 bit int value and each envelope update changes
at least 1 LSB
*/
//from docs on the OPL3: envelope starts at 511 (max attenuation)
//and counts down to zero (no attenuation)
//on real HW the envelope out is probably the upper 9 bits of a 23 bit
//attenuation register (this would add 1 LSB per clock at slowest rate)
//System.err.println(state[ch]);
switch (state[ch]) {
default:
case CUTOFF:
if (vol[ch] < ZEROVOL) {
vol[ch] += 16384; //decay to off in 10ms
/*
programmer's manual seems to say it takes a few ms to decay
before the new note starts its attack run.
need to listen really hard to the HW recordings and tune
this by ear
*/
} else {
vol[ch] = ZEROVOL;
if (key[ch]) {
/*the programmer's manual suggests that sound has to
decay back to zero volume when keyed on before the attack
happens, but other references don't say this
*/
state[ch] = EnvState.ATTACK;
phase[ch] = 0;
//reset phase to avoid popping? can't tell if the chip does this.
}
}
break;
case ATTACK:
if (vol[ch] > MAXVOL + 8) {
vol[ch]-= ATTACKVAL[
(instrument[(isCarrier ? 5 : 4)] >> 4) * 4
+ ksrShift
];
} else {
state[ch] = EnvState.DECAY;
}
if (!key[ch]) {
state[ch] = EnvState.RELEASE;
}
break;
case DECAY:
if (vol[ch] < ((instrument[(isCarrier ? 7 : 6)] >> 4)) * 524288) { // <-- check this 524288
//the higher the sustain value is, the lower the volume when
//it switches to sustain.
vol[ch] += DECAYVAL[(instrument[(isCarrier ? 5 : 4)] & 0xf) * 4
+ ksrShift];
} else {
state[ch] = EnvState.RELEASE;
}
if (!key[ch]) {
state[ch] = EnvState.RELEASE;
}
break;
case RELEASE:
//there are 3 things we need to know:
//1. Is the key on?
//2. Is the channel sustain bit set?
//3. Is bit 5 in patch register 0 or 1 set?
//What makes this especially confusing is that the sustain bit in the patch
//is bit 5 and the sustain bit in the channel is *also* a bit 5,
//in its respective register (ugh)
//for consistency with it though let's call the channel sustain SUS
//and the patch register D5
boolean d5 = ((instrument[isCarrier ? 1 : 0] & (BIT5)) != 0);
boolean SUS = chSust[ch];
if (key[ch]) {
//if we're keyed on
if (d5) {
//sustained tone
//don't decay at all
} else {
//percussive tone
//decay at release rate
vol[ch] += DECAYVAL[(instrument[(isCarrier ? 7 : 6)] & 0xf) * 4
+ ksrShift];
}
} else {
//key is off
if (d5) {
//sustained tone
if (SUS) {
//decay at rate of 1.2 seconds to cut off
vol[ch] += DECAYVAL[5 * 4
+ ksrShift];
} else {
//decay at release rate
vol[ch] += DECAYVAL[(instrument[(isCarrier ? 7 : 6)] & 0xf) * 4
+ ksrShift];
}
} else {
//percussive tone
if (SUS) {
//decay at rate of 1.2 seconds to cut off
vol[ch] += DECAYVAL[5 * 4
+ ksrShift];
} else {
//decay at release rate prime, 310ms to cutoff
//according to the docs,
//or a rate of 7 according
//to tests on nesdev forums.
vol[ch] += DECAYVAL[7 * 4
+ ksrShift];
//maybe we do apply key scaling to these still
}
}
}
break;
//there was also something about one of the decay values not working
//if a modulator or something
//on the famitracker forums or somewhere
}
if (vol[ch] < MAXVOL) {
vol[ch] = MAXVOL;
}
if (vol[ch] > ZEROVOL) {
vol[ch] = ZEROVOL;
}
if(state[ch] == EnvState.ATTACK){
//logarithmic envelope attack
//48 dB - (48 dB * ln(EGC) / ln(1<<23)) from Disch's doc
//also accounting for env ctr is running down not up
//this is slow and probably wrong vs real chip
//but stays for now bc it works and sounds ok
int output = 8388608 - (int)(8388608 * Math.log(8388608 - vol[ch]) / Math.log(8388608));
return output >> 14;
}
return vol[ch] >> 14;
}
private final static int[] ATTACKVAL = {0, 0, 0, 0,
98, 120, 146, 171,
195, 216, 293, 341,
390, 471, 602, 683,
780, 964, 1168, 1366,
1560, 1927, 2315, 2731,
3075, 3855, 4682, 5461,
6242, 8035, 9364, 10921,
12480, 15423, 18727, 21856,
24960, 30847, 37413, 43713,
51130, 61580, 74991, 87425,
99841, 123161, 149319, 173949,
200870, 241044, 281218, 312464,
337461, 401739, 496266, 562435,
602609, 766957, 937392, 1205218,
8388607, 8388607, 8388607, 8388607,
8388607, 8388607, 8388607, 8388607,
8388607, 8388607, 8388607, 8388607,
8388607, 8388607, 8388607, 8388607};
private final static int[] DECAYVAL = {0, 0, 0, 0,
8, 10, 12, 14, //+2
16, 20, 24, 28, //+4
32, 40, 48, 56, //+8
65, 77, 96, 112, //+16
129, 161, 193, 224, //+32
258, 321, 386, 449, //+64
516, 643, 771, 898, //+128
1032, 1285, 1542, 1796, //+256
2064, 2570, 3084, 3591, //+512
4211, 5268, 6167, 7183, //+1024
8255, 10282, 12407, 14360, //+2048
16510, 20552, 24668, 28745, //+4096
33020, 41154, 49336, 57391, //+8192
66169, 82308, 98673, 114783, //+16384
132859, 132859, 132859, 132859,
132859, 132859, 132859, 132859,
132859, 132859, 132859, 132859,
132859, 132859, 132859, 132859,};
}
//these 2 tables calculated with excel based on the envelope length table
//in the YM2413 programming guide.
//decay table is SO CLOSE to neat powers of 2
//and it really has to be created with simple logic
//on the real chip where there is no table of envelope values.