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);
for(auto phase : range(order / 2)) {
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 {
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 License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -49,15 +49,11 @@ auto APU::main() -> void {
clockFrameCounterDivider();
int output = pulseDAC[pulse_output] + dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output];
output = filter.runHipassStrong(output);
int output = 0;
output += pulseDAC[pulse_output];
output += dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output];
output += cartridgeSample;
output = filter.runHipassWeak(output);
//output = filter.runLopass(output);
output = sclamp<16>(output);
stream->sample(output / 32768.0);
stream->sample(sclamp<16>(output) / 32768.0);
tick();
}
@ -79,10 +75,6 @@ auto APU::power() -> void {
create(APU::Enter, system.colorburst() * 6.0);
stream = Emulator::audio.createStream(1, system.colorburst() / 2.0);
filter.hipassStrong = 0;
filter.hipassWeak = 0;
filter.lopass = 0;
pulse[0].power();
pulse[1].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 {
frame.counter++;

View File

@ -16,20 +16,6 @@ struct APU : Thread {
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 {
auto volume() const -> uint;
auto clock() -> void;
@ -174,7 +160,6 @@ struct APU : Thread {
auto clockFrameCounter() -> void;
auto clockFrameCounterDivider() -> void;
Filter filter;
FrameCounter frame;
uint8 enabledChannels;

View File

@ -1,8 +1,6 @@
auto APU::serialize(serializer& s) -> void {
Thread::serialize(s);
filter.serialize(s);
pulse[0].serialize(s);
pulse[1].serialize(s);
triangle.serialize(s);
@ -13,12 +11,6 @@ auto APU::serialize(serializer& s) -> void {
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 {
s.integer(speed);
s.integer(useSpeedAsVolume);

View File

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

View File

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

View File

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

View File

@ -6,14 +6,6 @@ auto YM2612::TimerA::run() -> void {
line |= irq;
}
auto YM2612::TimerA::power() -> void {
enable = 0;
irq = 0;
line = 0;
period = 0;
counter = 0;
}
auto YM2612::TimerB::run() -> void {
if(!enable) return;
if(++divider) return;
@ -22,12 +14,3 @@ auto YM2612::TimerB::run() -> void {
counter = period;
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);
}
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(channel.leftEnable ) left += voiceData;
if(channel.rightEnable) right += voiceData;
}
int cutoff = 20;
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);
stream->sample(sclamp<16>(left) / 32768.0, sclamp<16>(right) / 32768.0);
}
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);
stream = Emulator::audio.createStream(2, frequency());
memory::fill(&io, sizeof(IO));
memory::fill(&lfo, sizeof(LFO));
memory::fill(&dac, sizeof(DAC));
memory::fill(&envelope, sizeof(Envelope));
timerA.power();
timerB.power();
io = {};
lfo = {};
dac = {};
envelope = {};
timerA = {};
timerB = {};
for(auto& channel : channels) channel.power();
const uint positive = 0;

View File

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

View File

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

View File

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

View File

@ -7,8 +7,6 @@ auto PSG::serialize(serializer& s) -> void {
noise.serialize(s);
s.integer(select);
s.integer(lowpassLeft);
s.integer(lowpassRight);
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(outputLeft / 32768.0 - 1.0, outputRight / 32768.0 - 1.0);
stream->sample(sclamp<16>(outputLeft) / 32768.0, sclamp<16>(outputRight) / 32768.0);
step(1);
}
@ -57,7 +56,7 @@ auto PSG::power() -> void {
memory::fill(&io, sizeof(IO));
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
for(uint n : range(31)) {
volumeScalar[n] = level;

View File

@ -15,23 +15,25 @@ struct Biquad {
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 static auto butterworth(uint order, uint phase) -> double;
private:
Type type; //filter type
double cutoff; //frequency cutoff
Type type;
double cutoffFrequency;
double samplingFrequency;
double quality; //frequency response quality
double gain; //peak gain
double a0, a1, a2, b1, b2; //coefficients
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->cutoff = cutoff;
this->cutoffFrequency = cutoffFrequency;
this->samplingFrequency = samplingFrequency;
this->quality = quality;
this->gain = gain;
@ -39,7 +41,7 @@ auto Biquad::reset(Type type, double cutoff, double quality, double gain) -> voi
z2 = 0.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 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;
}
}
}}}