mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-02-22 06:02:28 +01:00
byuu says: Changelog: - improved attenuation of biquad filter by computing butterworth Q coefficients correctly (instead of using the same constant) - adding 1e-25 to each input sample into the biquad filters to try and prevent denormalization - updated normalization from [0.0 to 1.0] to [-1.0 to +1.0]; volume/reverb happen in floating-point mode now - good amount of work to make the base Emulator::Audio support any number of output channels - so that we don't have to do separate work on left/right channels; and can instead share the code for each channel - Emulator::Interface::audioSample(int16 left, int16 right); changed to: - Emulator::Interface::audioSample(double* samples, uint channels); - samples are normalized [-1.0 to +1.0] - for now at least, channels will be the value given to Emulator::Audio::reset() - fixed GUI crash on startup when audio driver is set to None I'm probably going to be updating ruby to accept normalized doubles as well; but I'm not sure if I will try and support anything other 2-channel audio output. It'll depend on how easy it is to do so; perhaps it'll be a per-driver setting. The denormalization thing is fierce. If that happens, it drops the emulator framerate from 220fps to about 20fps for Game Boy emulation. And that happens basically whenever audio output is silent. I'm probably also going to make a nall/denormal.hpp file at some point with platform-specific functionality to set the CPU state to "denormals as zero" where applicable. I'll still add the 1e-25 offset (inaudible) as another fallback.
131 lines
3.2 KiB
C++
131 lines
3.2 KiB
C++
auto DSP::calculateFIR(bool channel, int index) -> int {
|
|
int sample = state.echoHistory[channel][(uint3)(state.echoHistoryOffset + index + 1)];
|
|
return (sample * (int8)REG(FIR + index * 0x10)) >> 6;
|
|
}
|
|
|
|
auto DSP::echoOutput(bool channel) -> int {
|
|
int output = (int16)((state._mainOut[channel] * (int8)REG(MVOLL + channel * 0x10)) >> 7)
|
|
+ (int16)((state._echoIn [channel] * (int8)REG(EVOLL + channel * 0x10)) >> 7);
|
|
return sclamp<16>(output);
|
|
}
|
|
|
|
auto DSP::echoRead(bool channel) -> void {
|
|
uint addr = state._echoPointer + channel * 2;
|
|
uint8 lo = smp.apuram[(uint16)(addr + 0)];
|
|
uint8 hi = smp.apuram[(uint16)(addr + 1)];
|
|
int s = (int16)((hi << 8) + lo);
|
|
state.echoHistory[channel][state.echoHistoryOffset] = s >> 1;
|
|
}
|
|
|
|
auto DSP::echoWrite(bool channel) -> void {
|
|
if(!(state._echoDisabled & 0x20)) {
|
|
uint addr = state._echoPointer + channel * 2;
|
|
int s = state._echoOut[channel];
|
|
smp.apuram[(uint16)(addr + 0)] = s;
|
|
smp.apuram[(uint16)(addr + 1)] = s >> 8;
|
|
}
|
|
|
|
state._echoOut[channel] = 0;
|
|
}
|
|
|
|
auto DSP::echo22() -> void {
|
|
//history
|
|
state.echoHistoryOffset++;
|
|
|
|
state._echoPointer = (uint16)((state._esa << 8) + state.echoOffset);
|
|
echoRead(0);
|
|
|
|
//FIR
|
|
int l = calculateFIR(0, 0);
|
|
int r = calculateFIR(1, 0);
|
|
|
|
state._echoIn[0] = l;
|
|
state._echoIn[1] = r;
|
|
}
|
|
|
|
auto DSP::echo23() -> void {
|
|
int l = calculateFIR(0, 1) + calculateFIR(0, 2);
|
|
int r = calculateFIR(1, 1) + calculateFIR(1, 2);
|
|
|
|
state._echoIn[0] += l;
|
|
state._echoIn[1] += r;
|
|
|
|
echoRead(1);
|
|
}
|
|
|
|
auto DSP::echo24() -> void {
|
|
int l = calculateFIR(0, 3) + calculateFIR(0, 4) + calculateFIR(0, 5);
|
|
int r = calculateFIR(1, 3) + calculateFIR(1, 4) + calculateFIR(1, 5);
|
|
|
|
state._echoIn[0] += l;
|
|
state._echoIn[1] += r;
|
|
}
|
|
|
|
auto DSP::echo25() -> void {
|
|
int l = state._echoIn[0] + calculateFIR(0, 6);
|
|
int r = state._echoIn[1] + calculateFIR(1, 6);
|
|
|
|
l = (int16)l;
|
|
r = (int16)r;
|
|
|
|
l += (int16)calculateFIR(0, 7);
|
|
r += (int16)calculateFIR(1, 7);
|
|
|
|
state._echoIn[0] = sclamp<16>(l) & ~1;
|
|
state._echoIn[1] = sclamp<16>(r) & ~1;
|
|
}
|
|
|
|
auto DSP::echo26() -> void {
|
|
//left output volumes
|
|
//(save sample for next clock so we can output both together)
|
|
state._mainOut[0] = echoOutput(0);
|
|
|
|
//echo feedback
|
|
int l = state._echoOut[0] + (int16)((state._echoIn[0] * (int8)REG(EFB)) >> 7);
|
|
int r = state._echoOut[1] + (int16)((state._echoIn[1] * (int8)REG(EFB)) >> 7);
|
|
|
|
state._echoOut[0] = sclamp<16>(l) & ~1;
|
|
state._echoOut[1] = sclamp<16>(r) & ~1;
|
|
}
|
|
|
|
auto DSP::echo27() -> void {
|
|
//output
|
|
int outl = state._mainOut[0];
|
|
int outr = echoOutput(1);
|
|
state._mainOut[0] = 0;
|
|
state._mainOut[1] = 0;
|
|
|
|
//todo: global muting isn't this simple
|
|
//(turns DAC on and off or something, causing small ~37-sample pulse when first muted)
|
|
if(REG(FLG) & 0x40) {
|
|
outl = 0;
|
|
outr = 0;
|
|
}
|
|
|
|
//output sample to DAC
|
|
stream->sample(outl / 32768.0, outr / 32768.0);
|
|
}
|
|
|
|
auto DSP::echo28() -> void {
|
|
state._echoDisabled = REG(FLG);
|
|
}
|
|
|
|
auto DSP::echo29() -> void {
|
|
state._esa = REG(ESA);
|
|
|
|
if(!state.echoOffset) state.echoLength = (REG(EDL) & 0x0f) << 11;
|
|
|
|
state.echoOffset += 4;
|
|
if(state.echoOffset >= state.echoLength) state.echoOffset = 0;
|
|
|
|
//write left echo
|
|
echoWrite(0);
|
|
|
|
state._echoDisabled = REG(FLG);
|
|
}
|
|
|
|
auto DSP::echo30() -> void {
|
|
//write right echo
|
|
echoWrite(1);
|
|
}
|