mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
89d47914b9
commit
7e7003fd29
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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/";
|
||||||
|
|
|
@ -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++;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}}}
|
Loading…
Reference in New Issue