Update to v103r01 release.

byuu says:

Changelog:

  - nall/dsp: improve one pole coefficient calculations [Fatbag]
  - higan/audio: reworked filters to support selection of either one
    pole (first-order) or biquad (second-order) filters
      - note: the design is not stable yet; so forks should not put too
        much effort into synchronizing with this change yet
  - fc: added first-order filters as per NESdev wiki (90hz lowpass +
    440hz lowpass + 14khz highpass)
  - fc: created separate NTSC-J and NTSC-U regions
      - NESdev wiki says the Japanese Famicom uses a separate audio
        filtering strategy, but details are fuzzy
      - there's also cartridge audio output being disabled on NES units;
        and differences with controllers
      - this stuff will be supported in the future, just adding the
        support for it now
  - gba: corrected serious bugs in PSG wave channel emulation [Cydrak]
      - note that if there are still bugs here, it's my fault
  - md/psg,ym2612: added first-order low-pass 2840hz filter to match
    VA3-VA6 Mega Drives
  - md/psg: lowered volume relative to the YM2612
      - using 0x1400; multiple people agreed it was the closest to the
        hardware recordings against a VA6
  - ms,md/psg: don't serialize the volume levels array
  - md/vdp: Hblank bit acts the same during Vblank as outside of it (it
    isn't always set during Vblank)
  - md/vdp: return isPAL in bit 0 of control port reads
  - tomoko: change command-line option separator from : to |
      - [Editor's note: This change was present in the public v103,
        but it's in this changelog because it was made after the v103 WIP]
  - higan/all: change the 20hz high-pass filters from second-order
    three-pass to first-order one-pass
      - these filters are meant to remove DC bias, but I honestly can't
        hear a difference with or without them
      - so there's really no sense wasting CPU power with an extremely
        powerful filter here

Things I did not do:

  - change icarus install rule
  - work on 8-bit Mega Drive SRAM
  - work on Famicom or Mega Drive region detection heuristics in icarus

My long-term dream plan is to devise a special user-configurable
filtering system where you can set relative volumes and create your own
list of filters (any number of them in any order at any frequency), that
way people can make the systems sound however they want.

Right now, the sanest place to put this information is inside the
$system.sys/manifest.bml files. But that's not very user friendly, and
upgrading to new versions will lose these changes if you don't copy them
over manually. Of course, cluttering the GUI with a fancy filter editor
is probably supreme overkill for 99% of users, so maybe that's fine.
This commit is contained in:
Tim Allen 2017-06-26 11:41:58 +10:00
parent f6d7922e62
commit ecc7e899e0
30 changed files with 260 additions and 230 deletions

View File

@ -1,5 +1,6 @@
#pragma once
#include <nall/dsp/iir/one-pole.hpp>
#include <nall/dsp/iir/biquad.hpp>
#include <nall/dsp/resampler/cubic.hpp>
@ -7,6 +8,7 @@ namespace Emulator {
struct Interface;
struct Audio;
struct Filter;
struct Stream;
struct Audio {
@ -37,11 +39,19 @@ private:
friend class Stream;
};
struct Filter {
enum class Order : uint { First, Second };
enum class Type : uint { LowPass, HighPass };
Order order;
DSP::IIR::OnePole onePole; //first-order
DSP::IIR::Biquad biquad; //second-order
};
struct Stream {
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
auto addLowPassFilter(double cutoffFrequency, uint passes = 1) -> void;
auto addHighPassFilter(double cutoffFrequency, uint passes = 1) -> void;
auto addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes = 1) -> void;
auto pending() const -> bool;
auto read(double* samples) -> uint;
@ -54,7 +64,7 @@ struct Stream {
private:
struct Channel {
vector<DSP::IIR::Biquad> filters;
vector<Filter> filters;
DSP::Resampler::Cubic resampler;
};
vector<Channel> channels;

View File

@ -11,22 +11,27 @@ auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency
}
}
auto Stream::addLowPassFilter(double cutoffFrequency, uint passes) -> void {
auto Stream::addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes) -> void {
for(auto& channel : channels) {
for(auto pass : range(passes)) {
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
channel.filters.append(DSP::IIR::Biquad{});
channel.filters.right().reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q);
}
}
}
Filter filter{order};
auto Stream::addHighPassFilter(double cutoffFrequency, uint passes) -> void {
for(auto& channel : channels) {
for(auto pass : range(passes)) {
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
channel.filters.append(DSP::IIR::Biquad{});
channel.filters.right().reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q);
if(order == Filter::Order::First) {
DSP::IIR::OnePole::Type _type;
if(type == Filter::Type::LowPass) _type = DSP::IIR::OnePole::Type::LowPass;
if(type == Filter::Type::HighPass) _type = DSP::IIR::OnePole::Type::HighPass;
filter.onePole.reset(_type, cutoffFrequency, inputFrequency);
}
if(order == Filter::Order::Second) {
DSP::IIR::Biquad::Type _type;
if(type == Filter::Type::LowPass) _type = DSP::IIR::Biquad::Type::LowPass;
if(type == Filter::Type::HighPass) _type = DSP::IIR::Biquad::Type::HighPass;
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.biquad.reset(_type, cutoffFrequency, inputFrequency, q);
}
channel.filters.append(filter);
}
}
}
@ -43,7 +48,12 @@ auto Stream::read(double* samples) -> uint {
auto Stream::write(const double* samples) -> void {
for(auto c : range(channels)) {
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
for(auto& filter : channels[c].filters) sample = filter.process(sample);
for(auto& filter : channels[c].filters) {
switch(filter.order) {
case Filter::Order::First: sample = filter.onePole.process(sample); break;
case Filter::Order::Second: sample = filter.biquad.process(sample); break;
}
}
channels[c].resampler.write(sample);
}

View File

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

View File

@ -74,8 +74,10 @@ auto APU::setSample(int16 sample) -> void {
auto APU::power() -> void {
create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(1, frequency() / rate());
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 90.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 440.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 14000.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
pulse[0].power();
pulse[1].power();

View File

@ -1,7 +1,7 @@
struct APU : Thread {
shared_pointer<Emulator::Stream> stream;
inline auto rate() const -> uint { return Region::NTSC() ? 12 : 16; }
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//apu.cpp
APU();

View File

@ -57,7 +57,7 @@ auto APU::DMC::clock() -> uint8 {
}
}
periodCounter = Region::NTSC() ? dmcPeriodTableNTSC[period] : dmcPeriodTablePAL[period];
periodCounter = Region::PAL() ? dmcPeriodTablePAL[period] : dmcPeriodTableNTSC[period];
}
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
@ -73,7 +73,7 @@ auto APU::DMC::power() -> void {
irqPending = 0;
period = 0;
periodCounter = Region::NTSC() ? dmcPeriodTableNTSC[0] : dmcPeriodTablePAL[0];
periodCounter = Region::PAL() ? dmcPeriodTablePAL[0] : dmcPeriodTableNTSC[0];
irqEnable = 0;
loopMode = 0;
dacLatch = 0;

View File

@ -19,7 +19,7 @@ auto APU::Noise::clock() -> uint8 {
}
lfsr = (lfsr >> 1) | (feedback << 14);
periodCounter = Region::NTSC() ? apu.noisePeriodTableNTSC[period] : apu.noisePeriodTablePAL[period];
periodCounter = Region::PAL() ? apu.noisePeriodTablePAL[period] : apu.noisePeriodTableNTSC[period];
}
return result;

View File

@ -16,7 +16,7 @@ auto Cartridge::main() -> void {
}
auto Cartridge::load() -> bool {
if(auto loaded = platform->load(ID::Famicom, "Famicom", "fc", {"NTSC", "PAL"})) {
if(auto loaded = platform->load(ID::Famicom, "Famicom", "fc", {"NTSC-J", "NTSC-U", "PAL"})) {
information.pathID = loaded.pathID();
information.region = loaded.option();
} else return false;

View File

@ -2,7 +2,7 @@
#include "board/board.hpp"
struct Cartridge : Thread {
inline auto rate() const -> uint { return Region::NTSC() ? 12 : 16; }
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//cartridge.cpp
static auto Enter() -> void;

View File

@ -1,5 +1,5 @@
struct CPU : Processor::MOS6502, Thread {
inline auto rate() const -> uint { return Region::NTSC() ? 12 : 16; }
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//cpu.cpp
static auto Enter() -> void;

View File

@ -30,7 +30,8 @@ namespace Famicom {
};
struct Region {
static inline auto NTSC() -> bool;
static inline auto NTSCJ() -> bool;
static inline auto NTSCU() -> bool;
static inline auto PAL() -> bool;
};

View File

@ -1,6 +1,6 @@
struct PPU : Thread {
inline auto rate() const -> uint { return Region::NTSC() ? 4 : 5; }
inline auto vlines() const -> uint { return Region::NTSC() ? 262 : 312; }
inline auto rate() const -> uint { return Region::PAL() ? 5 : 4; }
inline auto vlines() const -> uint { return Region::PAL() ? 312 : 262; }
//ppu.cpp
static auto Enter() -> void;

View File

@ -32,8 +32,12 @@ auto System::load(Emulator::Interface* interface) -> bool {
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
if(cartridge.region() == "NTSC") {
information.region = Region::NTSC;
if(cartridge.region() == "NTSC-J") {
information.region = Region::NTSCJ;
information.frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
}
if(cartridge.region() == "NTSC-U") {
information.region = Region::NTSCU;
information.frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
}
if(cartridge.region() == "PAL") {

View File

@ -1,5 +1,5 @@
struct System {
enum class Region : uint { NTSC, PAL };
enum class Region : uint { NTSCJ, NTSCU, PAL };
auto loaded() const -> bool { return information.loaded; }
auto region() const -> Region { return information.region; }
@ -33,7 +33,7 @@ private:
struct Information {
bool loaded = false;
Region region = Region::NTSC;
Region region = Region::NTSCJ;
double frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
string manifest;
} information;
@ -53,5 +53,6 @@ struct Peripherals {
extern System system;
extern Peripherals peripherals;
auto Region::NTSC() -> bool { return system.region() == System::Region::NTSC; }
auto Region::NTSCJ() -> bool { return system.region() == System::Region::NTSCJ; }
auto Region::NTSCU() -> bool { return system.region() == System::Region::NTSCU; }
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }

View File

@ -55,8 +55,8 @@ auto APU::power() -> void {
create(Enter, 2 * 1024 * 1024);
if(!Model::SuperGameBoy()) {
stream = Emulator::audio.createStream(2, frequency());
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
}
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;

View File

@ -77,8 +77,8 @@ auto APU::step(uint clocks) -> void {
auto APU::power() -> void {
create(APU::Enter, 16'777'216);
stream = Emulator::audio.createStream(2, frequency() / 64.0);
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
clock = 0;
square1.power();

View File

@ -1,8 +1,6 @@
struct APU : Thread, IO {
shared_pointer<Emulator::Stream> stream;
#include "registers.hpp"
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
@ -16,6 +14,165 @@ struct APU : Thread, IO {
auto serialize(serializer&) -> void;
uint clock;
struct Registers {
struct SoundBias {
uint10 level;
uint2 amplitude;
} bias;
} regs;
struct Sweep {
uint3 shift;
uint1 direction;
uint3 frequency;
uint1 enable;
uint1 negate;
uint3 period;
};
struct Envelope {
uint3 frequency;
uint1 direction;
uint4 volume;
uint3 period;
auto dacEnable() const -> bool { return volume || direction; }
};
struct Square {
Envelope envelope;
uint1 enable;
uint6 length;
uint2 duty;
uint11 frequency;
uint1 counter;
uint1 initialize;
int shadowfrequency;
uint1 signal;
uint4 output;
uint period;
uint3 phase;
uint4 volume;
auto run() -> void;
auto clocklength() -> void;
auto clockenvelope() -> void;
};
struct Square1 : Square {
Sweep sweep;
auto runsweep(bool update) -> void;
auto clocksweep() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} square1;
struct Square2 : Square {
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} square2;
struct Wave {
uint1 mode;
uint1 bank;
uint1 dacenable;
uint8 length;
uint3 volume;
uint11 frequency;
uint1 counter;
uint1 initialize;
uint4 pattern[2 * 32];
uint1 enable;
uint4 output;
uint5 patternaddr;
uint1 patternbank;
uint4 patternsample;
uint period;
auto run() -> void;
auto clocklength() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto readram(uint addr) const -> uint8;
auto writeram(uint addr, uint8 byte) -> void;
auto power() -> void;
} wave;
struct Noise {
Envelope envelope;
uint6 length;
uint3 divisor;
uint1 narrowlfsr;
uint4 frequency;
uint1 counter;
uint1 initialize;
uint1 enable;
uint15 lfsr;
uint4 output;
uint period;
uint4 volume;
auto divider() const -> uint;
auto run() -> void;
auto clocklength() -> void;
auto clockenvelope() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} noise;
struct Sequencer {
uint2 volume;
uint3 lvolume;
uint3 rvolume;
uint1 lenable[4];
uint1 renable[4];
uint1 masterenable;
uint12 base;
uint3 step;
int16 lsample;
int16 rsample;
uint10 loutput;
uint10 routput;
auto sample() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} sequencer;
struct FIFO {
int8 samples[32];
int8 active;
int8 output;
uint5 rdoffset;
uint5 wroffset;
uint6 size;
uint1 volume; //0 = 50%, 1 = 100%
uint1 lenable;
uint1 renable;
uint1 timer;
auto sample() -> void;
auto read() -> void;
auto write(int8 byte) -> void;
auto reset() -> void;
auto power() -> void;
} fifo[2];
};
extern APU apu;

View File

@ -1,158 +0,0 @@
struct Registers {
struct SoundBias {
uint10 level;
uint2 amplitude;
} bias;
} regs;
struct Sweep {
uint3 shift;
uint1 direction;
uint3 frequency;
uint1 enable;
uint1 negate;
uint3 period;
};
struct Envelope {
uint3 frequency;
uint1 direction;
uint4 volume;
uint3 period;
auto dacEnable() const -> bool { return volume || direction; }
};
struct Square {
Envelope envelope;
uint1 enable;
uint6 length;
uint2 duty;
uint11 frequency;
uint1 counter;
uint1 initialize;
int shadowfrequency;
uint1 signal;
uint4 output;
uint period;
uint3 phase;
uint4 volume;
auto run() -> void;
auto clocklength() -> void;
auto clockenvelope() -> void;
};
struct Square1 : Square {
Sweep sweep;
auto runsweep(bool update) -> void;
auto clocksweep() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} square1;
struct Square2 : Square {
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} square2;
struct Wave {
uint1 mode;
uint1 bank;
uint1 dacenable;
uint8 length;
uint3 volume;
uint11 frequency;
uint1 counter;
uint1 initialize;
uint4 pattern[32];
uint1 enable;
uint4 output;
uint4 patternaddr;
uint1 patternbank;
uint4 patternsample;
uint period;
auto run() -> void;
auto clocklength() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto readram(uint addr) const -> uint8;
auto writeram(uint addr, uint8 byte) -> void;
auto power() -> void;
} wave;
struct Noise {
Envelope envelope;
uint6 length;
uint3 divisor;
uint1 narrowlfsr;
uint4 frequency;
uint1 counter;
uint1 initialize;
uint1 enable;
uint15 lfsr;
uint4 output;
uint period;
uint4 volume;
auto divider() const -> uint;
auto run() -> void;
auto clocklength() -> void;
auto clockenvelope() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} noise;
struct Sequencer {
uint2 volume;
uint3 lvolume;
uint3 rvolume;
uint1 lenable[4];
uint1 renable[4];
uint1 masterenable;
uint12 base;
uint3 step;
int16 lsample;
int16 rsample;
uint10 loutput;
uint10 routput;
auto sample() -> void;
auto read(uint addr) const -> uint8;
auto write(uint addr, uint8 byte) -> void;
auto power() -> void;
} sequencer;
struct FIFO {
int8 samples[32];
int8 active;
int8 output;
uint5 rdoffset;
uint5 wroffset;
uint6 size;
uint1 volume; //0 = 50%, 1 = 100%
uint1 lenable;
uint1 renable;
uint1 timer;
auto sample() -> void;
auto read() -> void;
auto write(int8 byte) -> void;
auto reset() -> void;
auto power() -> void;
} fifo[2];

View File

@ -1,7 +1,7 @@
auto APU::Wave::run() -> void {
if(period && --period == 0) {
period = 1 * (2048 - frequency);
patternsample = pattern[patternbank * 16 + patternaddr++];
patternsample = pattern[patternbank << 5 | patternaddr++];
if(patternaddr == 0) patternbank ^= mode;
}
@ -66,14 +66,14 @@ auto APU::Wave::write(uint addr, uint8 byte) -> void {
auto APU::Wave::readram(uint addr) const -> uint8 {
uint8 byte = 0;
byte |= pattern[addr * 2 + 0] << 0;
byte |= pattern[addr * 2 + 1] << 4;
byte |= pattern[!bank << 5 | addr << 1 | 0] << 0;
byte |= pattern[!bank << 5 | addr << 1 | 1] << 4;
return byte;
}
auto APU::Wave::writeram(uint addr, uint8 byte) -> void {
pattern[addr * 2 + 0] = byte >> 0;
pattern[addr * 2 + 1] = byte >> 4;
pattern[!bank << 5 | addr << 1 | 0] = byte >> 0;
pattern[!bank << 5 | addr << 1 | 1] = byte >> 4;
}
auto APU::Wave::power() -> void {

View File

@ -37,12 +37,13 @@ auto PSG::step(uint clocks) -> void {
auto PSG::power() -> void {
create(PSG::Enter, system.colorburst() / 16.0);
stream = Emulator::audio.createStream(1, frequency());
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 2840.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
select = 0;
for(auto n : range(15)) {
levels[n] = 0x2000 * pow(2, n * -2.0 / 6.0) + 0.5;
levels[n] = 0x1400 * pow(2, n * -2.0 / 6.0) + 0.5;
}
levels[15] = 0;

View File

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

View File

@ -116,8 +116,9 @@ auto VDP::readControlPort() -> uint16 {
uint16 result = 0b0011'0100'0000'0000;
result |= 1 << 9; //FIFO empty
result |= (state.vcounter >= screenHeight()) << 3; //vertical blank
result |= (state.vcounter >= screenHeight() || state.hcounter >= 1280) << 2; //horizontal blank
result |= (state.hcounter >= 1280) << 2; //horizontal blank
result |= io.command.bit(5) << 1; //DMA active
result |= Region::PAL() << 0;
return result;
}

View File

@ -30,6 +30,7 @@ auto VDP::main() -> void {
if(io.verticalBlankInterruptEnable) {
cpu.raise(CPU::Interrupt::VerticalBlank);
}
//todo: should only stay high for ~2573/2 clocks
apu.setINT(true);
}

View File

@ -157,6 +157,9 @@ auto YM2612::step(uint clocks) -> void {
auto YM2612::power() -> void {
create(YM2612::Enter, system.colorburst() * 15.0 / 7.0);
stream = Emulator::audio.createStream(2, frequency() / 144.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 2840.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
io = {};
lfo = {};

View File

@ -44,8 +44,8 @@ auto PSG::power() -> void {
//use stereo mode for both; output same sample to both channels for Master System
create(PSG::Enter, system.colorburst() / 16.0);
stream = Emulator::audio.createStream(2, frequency());
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
select = 0;
for(auto n : range(15)) {

View File

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

View File

@ -52,8 +52,8 @@ auto PSG::step(uint clocks) -> void {
auto PSG::power() -> void {
create(PSG::Enter, system.colorburst());
stream = Emulator::audio.createStream(2, frequency());
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
memory::fill(&io, sizeof(IO));
for(auto C : range(6)) channel[C].power(C);

View File

@ -48,8 +48,8 @@ auto ICD2::unload() -> void {
auto ICD2::power() -> void {
create(ICD2::Enter, system.colorburst() * 6.0 / 5.0);
stream = Emulator::audio.createStream(2, frequency() / 2.0);
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
r6003 = 0x00;
r6004 = 0xff;

View File

@ -67,8 +67,8 @@ auto APU::step(uint clocks) -> void {
auto APU::power() -> void {
create(APU::Enter, 3'072'000);
stream = Emulator::audio.createStream(2, frequency());
stream->addLowPassFilter(20000.0, 3);
stream->addHighPassFilter(20.0, 3);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
bus.map(this, 0x004a, 0x004c);
bus.map(this, 0x004e, 0x0050);

View File

@ -26,20 +26,19 @@ auto OnePole::reset(Type type, double cutoffFrequency, double samplingFrequency)
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;
double x = cos(2.0 * Math::Pi * cutoffFrequency / samplingFrequency);
if(type == Type::LowPass) {
b1 = +2.0 - x - sqrt((+2.0 - x) * (+2.0 - x) - 1);
a0 = 1.0 - b1;
} else {
b1 = -2.0 - x + sqrt((-2.0 - x) * (-2.0 - x) - 1);
a0 = 1.0 + b1;
}
}
auto OnePole::process(double in) -> double {
return z1 = in * a0 + z1 * b1;
}
}}}