/*
* HalfNES by Andrew Hoffman
* Licensed under the GNU GPL Version 3. See LICENSE file
*/
package com.grapeshot.halfnes.audio;
import com.grapeshot.halfnes.utils;
/**
*
* @author Andrew
*/
public class Namco163SoundChip implements ExpansionSoundChip {
/*
Warning for anyone making Namco 163 music:
* As the number of channels used increases, the sample rate decreases.
* Since the original chip only outputs one channel at a time and cycles
* between them, using all 8 channels causes very noticeable 10khz noise
* which is not implemented here.
*/
private final int[] registers = new int[128], out = new int[8];
private int numch, cycpos = 0, curch = 0;
@Override
public void clock(int cycles) {
numch = 1 + ((registers[127] >> 4) & 7);
for (int i = 0; i < cycles; ++i) {
cycpos = ++cycpos % 15;
if (cycpos == 0) {
curch = ++curch % numch;
clock_channel(curch);
}
}
}
private void clock_channel(final int ch) {
//get channel register start position
final int off = 0x80 - (8 * (ch + 1));
//get phase/freq value
int phase = (registers[off + 5] << 16) + (registers[off + 3] << 8) + registers[off + 1];
final int f = ((registers[off + 4] & 3) << 16) + (registers[off + 2] << 8) + registers[off];
//get waveform length
int len = (64 - (registers[off + 4] >> 2)) * 4;
//ugly heuristics: some NSFs were written with the
//expectation that you couldn't have more than 32 byte samples
//and that this field loops. Rolling Thunder shows this isn't the case
//but the NSFs work in Nintendlator/Nestopia and so have to work
//well if there's more than 3 zero samples in the wave data i guess
//the longer length probably isn't legit.
if (len > 32 && registers[off + 4] != 0) {
for (int i = 2; (i << 2) < len; ++i) {
if (registers[i - 2] == 0 && registers[i - 1] == 0 && registers[i] == 0) {
len = 0x20 - (registers[off + 4] & 0x1C);
//System.err.println("truncation");
break;
}
}
}
//get waveform start position
final int wavestart = registers[off + 6];
//get volume
phase = (phase + f) % (len << 16);
final int volume = registers[off + 7] & 0xf;
final int output = (getWavefromRAM(((phase >> 16) + wavestart) & 0xff) - 8) * volume;
//store phase back
registers[off + 5] = (phase >> 16) & 0xff;
registers[off + 3] = (phase >> 8) & 0xff;
registers[off + 1] = phase & 0xff;
out[ch] = output * 16;
output();
}
private int getWavefromRAM(final int addr) {
final int b = registers[(addr) >> 1];
return ((addr & (utils.BIT0)) != 0) ? b >> 4 : b & 0xf;
}
@Override
public void write(int register, int data) {
//System.err.println(numch);
registers[register] = data;
}
public int read(int register) {
return registers[register];
}
@Override
public int getval() {
return lpaccum << 2;
}
int lpaccum = 0;
private void output() {
int sample = 0;
for (int i = 0; i < numch; ++i) {
sample += out[i];
}
//this low pass filter is here to reduce noise in games using 8 channels
//while still letting me output 1 after the other like the real chip does
sample += lpaccum;
lpaccum -= sample * (1 / 16.);
}
}