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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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/";
|
||||
|
|
|
@ -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++;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -40,8 +40,7 @@ private:
|
|||
} noise;
|
||||
|
||||
uint3 select;
|
||||
int lowpass;
|
||||
uint16 levels[16];
|
||||
int16 levels[16];
|
||||
};
|
||||
|
||||
extern PSG psg;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -56,9 +56,7 @@ private:
|
|||
} noise;
|
||||
|
||||
uint3 select;
|
||||
int lowpassLeft;
|
||||
int lowpassRight;
|
||||
uint16 levels[16];
|
||||
int16 levels[16];
|
||||
};
|
||||
|
||||
extern PSG psg;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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