mirror of
https://github.com/libretro/bsnes-libretro.git
synced 2024-11-23 08:59:40 +00:00
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:
parent
f6d7922e62
commit
ecc7e899e0
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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/";
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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") {
|
||||
|
@ -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; }
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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];
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 = {};
|
||||
|
@ -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)) {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}}}
|
||||
|
Loading…
Reference in New Issue
Block a user