Update to v102r15 release.

byuu says:

Changelog:

  - nall: added DSP::IIR::OnePole (which is a first-order IIR filter)
  - FC/APU: removed strong highpass, weak hipass filters (and the
    dummied out lowpass filter)
  - MS,GG,MD/PSG: removed lowpass filter
  - MS,GG,MD/PSG: audio was not being centered properly; removed
    centering for now
  - MD/YM2612: fixed clipping of accumulator from 18 signed bits to 14
    signed bits (-0x2000 to +0x1fff) [Cydrak]
  - MD/YM2612: removed lowpass filter
  - PCE/PSG: audio was not being centered properly; removed centering
    for now

First thing is that I've removed all of the ad-hoc audio filtering.
Emulator::Stream intrinsically provides a three-pass, second-order
biquad IIR butterworth lowpass filter that clips frequencies above 20KHz
with very good attenuation (as good as IIR gets, anyway.)

It doesn't really make sense to have the various cores running
additional lowpass filters. If we want to filter frequencies below
20KHz, then I can adapt Emulator::Audio::createStream() to take a cutoff
frequency value, and we can do it all at once, with much better quality.

Right now, I don't know what frequencies are best to cut off the various
other audio cores, so they're just gone for now.

As for the highpass filters for the Famicom core, well ... you don't get
aliasing from resampling low frequencies. And generally speaking, too
low a frequency will be inaudible anyway. All these were doing was
killing possible bass (if they were too strong.) We can add them again,
but only if someone can convert Ryphecha's ad-hoc magic integers into a
frequency cutoff. In which case, I'll use my biquad IIR filter to do it
even better. On this note, it may prove useful to do this for the MD PSG
as well, to try and head off unnecessary clamping when mixing with the
YM2612.

Finally, there was the audio centering issue that affected the
MS,GG,MD,PCE,SG cores. It was flooring the "silent" audio level, which
was resulting in extremely heavy distortion if you tried listening to
higan and, say, audacious at the same time. Without the botched
centering, this distortion is completely gone now.

However, without any centering, we've halved the potential volume range.
This means the audio slider in higan's audio settings panel will start
clamping twice as quickly. So ultimately, we need to figure out how to
fix the centering. This isn't as simple as just subtracting less. We
will probably have to center every individual audio channel before
summing them to do this properly.

Results:

On the Mega Drive, Altered Beast sounds quite a bit better, a lot less
distortion now. But it's still not perfect, especially sound effects.
Further, Bare Knuckle / Streets of Rage still has really bad sound
effects. It looks like I broke something in Cydrak's code when trying to
adapt it to my style =(
This commit is contained in:
Tim Allen 2017-03-07 07:23:22 +11:00
parent 89d47914b9
commit 7e7003fd29
17 changed files with 156 additions and 207 deletions

View File

@ -7,7 +7,7 @@ auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency
channel.iir.resize(order / 2); channel.iir.resize(order / 2);
for(auto phase : range(order / 2)) { for(auto phase : range(order / 2)) {
double q = DSP::IIR::Biquad::butterworth(order, phase); double q = DSP::IIR::Biquad::butterworth(order, phase);
channel.iir[phase].reset(DSP::IIR::Biquad::Type::LowPass, 20000.0 / inputFrequency, q); channel.iir[phase].reset(DSP::IIR::Biquad::Type::LowPass, 20000.0, inputFrequency, q);
} }
} }

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; static const string Name = "higan";
static const string Version = "102.14"; static const string Version = "102.15";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "http://byuu.org/"; static const string Website = "http://byuu.org/";

View File

@ -49,15 +49,11 @@ auto APU::main() -> void {
clockFrameCounterDivider(); clockFrameCounterDivider();
int output = pulseDAC[pulse_output] + dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output]; int output = 0;
output += pulseDAC[pulse_output];
output = filter.runHipassStrong(output); output += dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output];
output += cartridgeSample; output += cartridgeSample;
output = filter.runHipassWeak(output); stream->sample(sclamp<16>(output) / 32768.0);
//output = filter.runLopass(output);
output = sclamp<16>(output);
stream->sample(output / 32768.0);
tick(); tick();
} }
@ -79,10 +75,6 @@ auto APU::power() -> void {
create(APU::Enter, system.colorburst() * 6.0); create(APU::Enter, system.colorburst() * 6.0);
stream = Emulator::audio.createStream(1, system.colorburst() / 2.0); stream = Emulator::audio.createStream(1, system.colorburst() / 2.0);
filter.hipassStrong = 0;
filter.hipassWeak = 0;
filter.lopass = 0;
pulse[0].power(); pulse[0].power();
pulse[1].power(); pulse[1].power();
triangle.power(); triangle.power();
@ -265,21 +257,6 @@ auto APU::writeIO(uint16 addr, uint8 data) -> void {
} }
} }
auto APU::Filter::runHipassStrong(int sample) -> int {
hipassStrong += ((((int64)sample << 16) - (hipassStrong >> 16)) * HiPassStrong) >> 16;
return sample - (hipassStrong >> 32);
}
auto APU::Filter::runHipassWeak(int sample) -> int {
hipassWeak += ((((int64)sample << 16) - (hipassWeak >> 16)) * HiPassWeak) >> 16;
return sample - (hipassWeak >> 32);
}
auto APU::Filter::runLopass(int sample) -> int {
lopass += ((((int64)sample << 16) - (lopass >> 16)) * LoPass) >> 16;
return (lopass >> 32);
}
auto APU::clockFrameCounter() -> void { auto APU::clockFrameCounter() -> void {
frame.counter++; frame.counter++;

View File

@ -16,20 +16,6 @@ struct APU : Thread {
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
struct Filter {
auto runHipassStrong(int sample) -> int;
auto runHipassWeak(int sample) -> int;
auto runLopass(int sample) -> int;
auto serialize(serializer&) -> void;
enum : int { HiPassStrong = 225574, HiPassWeak = 57593, LoPass = 86322413 };
int64 hipassStrong;
int64 hipassWeak;
int64 lopass;
};
struct Envelope { struct Envelope {
auto volume() const -> uint; auto volume() const -> uint;
auto clock() -> void; auto clock() -> void;
@ -174,7 +160,6 @@ struct APU : Thread {
auto clockFrameCounter() -> void; auto clockFrameCounter() -> void;
auto clockFrameCounterDivider() -> void; auto clockFrameCounterDivider() -> void;
Filter filter;
FrameCounter frame; FrameCounter frame;
uint8 enabledChannels; uint8 enabledChannels;

View File

@ -1,8 +1,6 @@
auto APU::serialize(serializer& s) -> void { auto APU::serialize(serializer& s) -> void {
Thread::serialize(s); Thread::serialize(s);
filter.serialize(s);
pulse[0].serialize(s); pulse[0].serialize(s);
pulse[1].serialize(s); pulse[1].serialize(s);
triangle.serialize(s); triangle.serialize(s);
@ -13,12 +11,6 @@ auto APU::serialize(serializer& s) -> void {
s.integer(cartridgeSample); s.integer(cartridgeSample);
} }
auto APU::Filter::serialize(serializer& s) -> void {
s.integer(hipassStrong);
s.integer(hipassWeak);
s.integer(lopass);
}
auto APU::Envelope::serialize(serializer& s) -> void { auto APU::Envelope::serialize(serializer& s) -> void {
s.integer(speed); s.integer(speed);
s.integer(useSpeedAsVolume); s.integer(useSpeedAsVolume);

View File

@ -18,16 +18,12 @@ auto PSG::main() -> void {
noise.run(); noise.run();
int output = 0; int output = 0;
if(tone0.output) output += levels[tone0.volume]; output += levels[tone0.volume] * tone0.output;
if(tone1.output) output += levels[tone1.volume]; output += levels[tone1.volume] * tone1.output;
if(tone2.output) output += levels[tone2.volume]; output += levels[tone2.volume] * tone2.output;
if(noise.output) output += levels[noise.volume]; output += levels[noise.volume] * noise.output;
lowpass += (output - lowpass) * 20 / 256; stream->sample(sclamp<16>(output) / 32768.0);
output = output * 2 / 6 + lowpass * 3 / 4;
output = sclamp<16>(output - 32768);
stream->sample(output / 32768.0);
step(1); step(1);
} }
@ -42,9 +38,8 @@ auto PSG::power() -> void {
stream = Emulator::audio.createStream(1, frequency()); stream = Emulator::audio.createStream(1, frequency());
select = 0; select = 0;
lowpass = 0;
for(auto n : range(15)) { for(auto n : range(15)) {
levels[n] = 0x1000 * pow(2, n * -2.0 / 6.0) + 0.5; levels[n] = 0x2000 * pow(2, n * -2.0 / 6.0) + 0.5;
} }
levels[15] = 0; levels[15] = 0;

View File

@ -40,8 +40,7 @@ private:
} noise; } noise;
uint3 select; uint3 select;
int lowpass; int16 levels[16];
uint16 levels[16];
}; };
extern PSG psg; extern PSG psg;

View File

@ -135,8 +135,8 @@ auto YM2612::Channel::Operator::updateLevel() -> void {
} }
auto YM2612::Channel::power() -> void { auto YM2612::Channel::power() -> void {
leftEnable = true; leftEnable = 1;
rightEnable = true; rightEnable = 1;
algorithm = 0; algorithm = 0;
feedback = 0; feedback = 0;
@ -146,8 +146,8 @@ auto YM2612::Channel::power() -> void {
mode = 0; mode = 0;
for(auto& op : operators) { for(auto& op : operators) {
op.keyOn = false; op.keyOn = 0;
op.lfoEnable = false; op.lfoEnable = 0;
op.detune = 0; op.detune = 0;
op.multiple = 0; op.multiple = 0;
op.totalLevel = 0; op.totalLevel = 0;
@ -180,11 +180,11 @@ auto YM2612::Channel::power() -> void {
op.envelope.sustainLevel = 0; op.envelope.sustainLevel = 0;
op.envelope.releaseRate = 1; op.envelope.releaseRate = 1;
op.ssg.enable = false; op.ssg.enable = 0;
op.ssg.attack = false; op.ssg.attack = 0;
op.ssg.alternate = false; op.ssg.alternate = 0;
op.ssg.hold = false; op.ssg.hold = 0;
op.ssg.invert = false; op.ssg.invert = 0;
op.updatePitch(); op.updatePitch();
op.updateLevel(); op.updateLevel();

View File

@ -6,14 +6,6 @@ auto YM2612::TimerA::run() -> void {
line |= irq; line |= irq;
} }
auto YM2612::TimerA::power() -> void {
enable = 0;
irq = 0;
line = 0;
period = 0;
counter = 0;
}
auto YM2612::TimerB::run() -> void { auto YM2612::TimerB::run() -> void {
if(!enable) return; if(!enable) return;
if(++divider) return; if(++divider) return;
@ -22,12 +14,3 @@ auto YM2612::TimerB::run() -> void {
counter = period; counter = period;
line |= irq; line |= irq;
} }
auto YM2612::TimerB::power() -> void {
enable = 0;
irq = 0;
line = 0;
period = 0;
counter = 0;
divider = 0;
}

View File

@ -137,22 +137,14 @@ auto YM2612::sample() -> void {
accumulator += out(0) + out(1) + out(2) + out(3); accumulator += out(0) + out(1) + out(2) + out(3);
} }
int voiceData = outMask & min(max(accumulator, -0x1ffff), +0x1ffff); int voiceData = sclamp<14>(accumulator) & outMask;
if(dac.enable && (&channel == &channels[5])) voiceData = dac.sample << 6; if(dac.enable && (&channel == &channels[5])) voiceData = dac.sample << 6;
if(channel.leftEnable ) left += voiceData; if(channel.leftEnable ) left += voiceData;
if(channel.rightEnable) right += voiceData; if(channel.rightEnable) right += voiceData;
} }
int cutoff = 20; stream->sample(sclamp<16>(left) / 32768.0, sclamp<16>(right) / 32768.0);
lpfLeft = (left - lpfLeft) * cutoff / 256;
lpfRight = (right - lpfRight) * cutoff / 256;
left = left * 2 / 6 + lpfLeft * 3 / 4;
right = right * 2 / 6 + lpfRight * 3 / 4;
stream->sample(left / 32768.0, right / 32768.0);
} }
auto YM2612::step(uint clocks) -> void { auto YM2612::step(uint clocks) -> void {
@ -165,13 +157,12 @@ auto YM2612::power() -> void {
create(YM2612::Enter, system.colorburst() * 15.0 / 7.0 / 144.0); create(YM2612::Enter, system.colorburst() * 15.0 / 7.0 / 144.0);
stream = Emulator::audio.createStream(2, frequency()); stream = Emulator::audio.createStream(2, frequency());
memory::fill(&io, sizeof(IO)); io = {};
memory::fill(&lfo, sizeof(LFO)); lfo = {};
memory::fill(&dac, sizeof(DAC)); dac = {};
memory::fill(&envelope, sizeof(Envelope)); envelope = {};
timerA = {};
timerA.power(); timerB = {};
timerB.power();
for(auto& channel : channels) channel.power(); for(auto& channel : channels) channel.power();
const uint positive = 0; const uint positive = 0;

View File

@ -17,49 +17,47 @@ struct YM2612 : Thread {
private: private:
struct IO { struct IO {
uint9 address; uint9 address = 0;
} io; } io;
struct LFO { struct LFO {
uint1 enable; uint1 enable = 0;
uint3 rate; uint3 rate = 0;
uint32 clock; uint32 clock = 0;
uint32 divider; uint32 divider = 0;
} lfo; } lfo;
struct DAC { struct DAC {
uint1 enable; uint1 enable = 0;
uint8 sample; uint8 sample = 0;
} dac; } dac;
struct Envelope { struct Envelope {
uint32 clock; uint32 clock = 0;
uint32 divider; uint32 divider = 0;
} envelope; } envelope;
struct TimerA { struct TimerA {
//timer.cpp //timer.cpp
auto run() -> void; auto run() -> void;
auto power() -> void;
uint1 enable; uint1 enable = 0;
uint1 irq; uint1 irq = 0;
uint1 line; uint1 line = 0;
uint10 period; uint10 period = 0;
uint10 counter; uint10 counter = 0;
} timerA; } timerA;
struct TimerB { struct TimerB {
//timer.cpp //timer.cpp
auto run() -> void; auto run() -> void;
auto power() -> void;
uint1 enable; uint1 enable = 0;
uint1 irq; uint1 irq = 0;
uint1 line; uint1 line = 0;
uint8 period; uint8 period = 0;
uint8 counter; uint8 counter = 0;
uint4 divider; uint4 divider = 0;
} timerB; } timerB;
enum : uint { Attack, Decay, Sustain, Release }; enum : uint { Attack, Decay, Sustain, Release };
@ -68,15 +66,15 @@ private:
//channel.cpp //channel.cpp
auto power() -> void; auto power() -> void;
bool leftEnable; uint1 leftEnable = 1;
bool rightEnable; uint1 rightEnable = 1;
uint3 algorithm; uint3 algorithm = 0;
uint3 feedback; uint3 feedback = 0;
uint3 vibrato; uint3 vibrato = 0;
uint2 tremolo; uint2 tremolo = 0;
uint2 mode; uint2 mode = 0;
struct Operator { struct Operator {
Channel& channel; Channel& channel;
@ -93,54 +91,54 @@ private:
auto updatePhase() -> void; auto updatePhase() -> void;
auto updateLevel() -> void; auto updateLevel() -> void;
bool keyOn; uint1 keyOn = 0;
bool lfoEnable; uint1 lfoEnable = 0;
uint3 detune; uint3 detune = 0;
uint4 multiple; uint4 multiple = 0;
uint7 totalLevel; uint7 totalLevel = 0;
uint16 outputLevel; uint16 outputLevel = 0x1fff;
int16 output; int16 output = 0;
int16 prior; int16 prior = 0;
struct Pitch { struct Pitch {
uint11 value; uint11 value = 0;
uint11 reload; uint11 reload = 0;
uint11 latch; uint11 latch = 0;
} pitch; } pitch;
struct Octave { struct Octave {
uint3 value; uint3 value = 0;
uint3 reload; uint3 reload = 0;
uint3 latch; uint3 latch = 0;
} octave; } octave;
struct Phase { struct Phase {
uint20 value; uint20 value = 0;
uint20 delta; uint20 delta = 0;
} phase; } phase;
struct Envelope { struct Envelope {
uint state; uint state = Release;
int rate; int rate = 0;
int divider; int divider = 11;
uint32 steps; uint32 steps = 0;
uint10 value; uint10 value = 0x3ff;
uint2 keyScale; uint2 keyScale = 0;
uint5 attackRate; uint5 attackRate = 0;
uint5 decayRate; uint5 decayRate = 0;
uint5 sustainRate; uint5 sustainRate = 0;
uint4 sustainLevel; uint4 sustainLevel = 0;
uint5 releaseRate; uint5 releaseRate = 1;
} envelope; } envelope;
struct SSG { struct SSG {
bool enable; uint1 enable = 0;
bool attack; uint1 attack = 0;
bool alternate; uint1 alternate = 0;
bool hold; uint1 hold = 0;
bool invert; uint1 invert = 0;
} ssg; } ssg;
} operators[4]{*this, *this, *this, *this}; } operators[4]{*this, *this, *this, *this};
@ -150,9 +148,6 @@ private:
uint16 sine[0x400]; uint16 sine[0x400];
int16 pow2[0x200]; int16 pow2[0x200];
int lpfLeft;
int lpfRight;
//constants.cpp //constants.cpp
struct EnvelopeRate { struct EnvelopeRate {
uint32_t divider; uint32_t divider;

View File

@ -19,26 +19,18 @@ auto PSG::main() -> void {
noise.run(); noise.run();
int left = 0; int left = 0;
if(tone0.output && tone0.left) left += levels[tone0.volume]; left += levels[tone0.volume] * tone0.output * tone0.left;
if(tone1.output && tone1.left) left += levels[tone1.volume]; left += levels[tone1.volume] * tone1.output * tone1.left;
if(tone2.output && tone2.left) left += levels[tone2.volume]; left += levels[tone2.volume] * tone2.output * tone2.left;
if(noise.output && noise.left) left += levels[noise.volume]; left += levels[noise.volume] * noise.output * noise.left;
lowpassLeft += (left - lowpassLeft) * 20 / 256;
left = left * 2 / 6 + lowpassLeft * 3 / 4;
left = sclamp<16>(left - 32768);
int right = 0; int right = 0;
if(tone0.output && tone0.right) right += levels[tone0.volume]; right += levels[tone0.volume] * tone0.output * tone0.right;
if(tone1.output && tone1.right) right += levels[tone1.volume]; right += levels[tone1.volume] * tone1.output * tone1.right;
if(tone2.output && tone2.right) right += levels[tone2.volume]; right += levels[tone2.volume] * tone2.output * tone2.right;
if(noise.output && noise.right) right += levels[noise.volume]; right += levels[noise.volume] * noise.output * noise.right;
lowpassRight += (right - lowpassRight) * 20 / 256; stream->sample(sclamp<16>(left) / 32768.0, sclamp<16>(right) / 32768.0);
right = right * 2 / 6 + lowpassRight * 3 / 4;
right = sclamp<16>(right - 32768);
stream->sample(left / 32768.0, right / 32768.0);
step(1); step(1);
} }
@ -54,8 +46,6 @@ auto PSG::power() -> void {
stream = Emulator::audio.createStream(2, frequency()); stream = Emulator::audio.createStream(2, frequency());
select = 0; select = 0;
lowpassLeft = 0;
lowpassRight = 0;
for(auto n : range(15)) { for(auto n : range(15)) {
levels[n] = 0x2000 * pow(2, n * -2.0 / 6.0) + 0.5; levels[n] = 0x2000 * pow(2, n * -2.0 / 6.0) + 0.5;
} }

View File

@ -56,9 +56,7 @@ private:
} noise; } noise;
uint3 select; uint3 select;
int lowpassLeft; int16 levels[16];
int lowpassRight;
uint16 levels[16];
}; };
extern PSG psg; extern PSG psg;

View File

@ -7,8 +7,6 @@ auto PSG::serialize(serializer& s) -> void {
noise.serialize(s); noise.serialize(s);
s.integer(select); s.integer(select);
s.integer(lowpassLeft);
s.integer(lowpassRight);
s.array(levels); s.array(levels);
} }

View File

@ -40,8 +40,7 @@ auto PSG::main() -> void {
} }
} }
//normalize 0.0 to 65536.0 => -1.0 to +1.0 stream->sample(sclamp<16>(outputLeft) / 32768.0, sclamp<16>(outputRight) / 32768.0);
stream->sample(outputLeft / 32768.0 - 1.0, outputRight / 32768.0 - 1.0);
step(1); step(1);
} }
@ -57,7 +56,7 @@ auto PSG::power() -> void {
memory::fill(&io, sizeof(IO)); memory::fill(&io, sizeof(IO));
for(auto C : range(6)) channel[C].power(C); for(auto C : range(6)) channel[C].power(C);
double level = 65536.0 / 6.0 / 32.0; //max volume / channels / steps double level = 32767.0 / 6.0 / 32.0; //max volume / channels / steps
double step = 48.0 / 32.0; //48dB volume range spread over 32 steps double step = 48.0 / 32.0; //48dB volume range spread over 32 steps
for(uint n : range(31)) { for(uint n : range(31)) {
volumeScalar[n] = level; volumeScalar[n] = level;

View File

@ -15,23 +15,25 @@ struct Biquad {
HighShelf, HighShelf,
}; };
inline auto reset(Type type, double cutoff, double quality, double gain = 0.0) -> void; inline auto reset(Type type, double cutoffFrequency, double samplingFrequency, double quality, double gain = 0.0) -> void;
inline auto process(double in) -> double; //normalized sample (-1.0 to +1.0) inline auto process(double in) -> double; //normalized sample (-1.0 to +1.0)
inline static auto butterworth(uint order, uint phase) -> double; inline static auto butterworth(uint order, uint phase) -> double;
private: private:
Type type; //filter type Type type;
double cutoff; //frequency cutoff double cutoffFrequency;
double samplingFrequency;
double quality; //frequency response quality double quality; //frequency response quality
double gain; //peak gain double gain; //peak gain
double a0, a1, a2, b1, b2; //coefficients double a0, a1, a2, b1, b2; //coefficients
double z1, z2; //second-order IIR double z1, z2; //second-order IIR
}; };
auto Biquad::reset(Type type, double cutoff, double quality, double gain) -> void { auto Biquad::reset(Type type, double cutoffFrequency, double samplingFrequency, double quality, double gain) -> void {
this->type = type; this->type = type;
this->cutoff = cutoff; this->cutoffFrequency = cutoffFrequency;
this->samplingFrequency = samplingFrequency;
this->quality = quality; this->quality = quality;
this->gain = gain; this->gain = gain;
@ -39,7 +41,7 @@ auto Biquad::reset(Type type, double cutoff, double quality, double gain) -> voi
z2 = 0.0; z2 = 0.0;
double v = pow(10, fabs(gain) / 20.0); double v = pow(10, fabs(gain) / 20.0);
double k = tan(Math::Pi * cutoff); double k = tan(Math::Pi * cutoffFrequency / samplingFrequency);
double q = quality; double q = quality;
double n = 0.0; double n = 0.0;

45
nall/dsp/iir/one-pole.hpp Normal file
View File

@ -0,0 +1,45 @@
#pragma once
//one-pole first-order IIR filter
namespace nall { namespace DSP { namespace IIR {
struct OnePole {
enum class Type : uint {
LowPass,
HighPass,
};
inline auto reset(Type type, double cutoffFrequency, double samplingFrequency) -> void;
inline auto process(double in) -> double; //normalized sample (-1.0 to +1.0)
private:
Type type;
double cutoffFrequency;
double samplingFrequency;
double a0, b1; //coefficients
double z1; //first-order IIR
};
auto OnePole::reset(Type type, double cutoffFrequency, double samplingFrequency) -> void {
this->type = type;
this->cutoffFrequency = cutoffFrequency;
this->samplingFrequency = samplingFrequency;
b1 = exp(-2.0 * Math::Pi * cutoffFrequency / samplingFrequency);
a0 = 1.0 - b1;
z1 = 0.0;
}
auto OnePole::process(double in) -> double {
z1 = in * a0 + z1 * b1;
switch(type) {
case Type::LowPass: return z1;
case Type::HighPass: return in - z1;
default: return 0.0;
}
}
}}}