mirror of https://github.com/bsnes-emu/bsnes.git
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
|
#pragma once
|
||||||
|
|
||||||
|
#include <nall/dsp/iir/one-pole.hpp>
|
||||||
#include <nall/dsp/iir/biquad.hpp>
|
#include <nall/dsp/iir/biquad.hpp>
|
||||||
#include <nall/dsp/resampler/cubic.hpp>
|
#include <nall/dsp/resampler/cubic.hpp>
|
||||||
|
|
||||||
|
@ -7,6 +8,7 @@ namespace Emulator {
|
||||||
|
|
||||||
struct Interface;
|
struct Interface;
|
||||||
struct Audio;
|
struct Audio;
|
||||||
|
struct Filter;
|
||||||
struct Stream;
|
struct Stream;
|
||||||
|
|
||||||
struct Audio {
|
struct Audio {
|
||||||
|
@ -37,11 +39,19 @@ private:
|
||||||
friend class Stream;
|
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 {
|
struct Stream {
|
||||||
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
|
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
|
||||||
|
|
||||||
auto addLowPassFilter(double cutoffFrequency, uint passes = 1) -> void;
|
auto addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes = 1) -> void;
|
||||||
auto addHighPassFilter(double cutoffFrequency, uint passes = 1) -> void;
|
|
||||||
|
|
||||||
auto pending() const -> bool;
|
auto pending() const -> bool;
|
||||||
auto read(double* samples) -> uint;
|
auto read(double* samples) -> uint;
|
||||||
|
@ -54,7 +64,7 @@ struct Stream {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Channel {
|
struct Channel {
|
||||||
vector<DSP::IIR::Biquad> filters;
|
vector<Filter> filters;
|
||||||
DSP::Resampler::Cubic resampler;
|
DSP::Resampler::Cubic resampler;
|
||||||
};
|
};
|
||||||
vector<Channel> channels;
|
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& channel : channels) {
|
||||||
for(auto pass : range(passes)) {
|
for(auto pass : range(passes)) {
|
||||||
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
Filter filter{order};
|
||||||
channel.filters.append(DSP::IIR::Biquad{});
|
|
||||||
channel.filters.right().reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Stream::addHighPassFilter(double cutoffFrequency, uint passes) -> void {
|
if(order == Filter::Order::First) {
|
||||||
for(auto& channel : channels) {
|
DSP::IIR::OnePole::Type _type;
|
||||||
for(auto pass : range(passes)) {
|
if(type == Filter::Type::LowPass) _type = DSP::IIR::OnePole::Type::LowPass;
|
||||||
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
|
if(type == Filter::Type::HighPass) _type = DSP::IIR::OnePole::Type::HighPass;
|
||||||
channel.filters.append(DSP::IIR::Biquad{});
|
filter.onePole.reset(_type, cutoffFrequency, inputFrequency);
|
||||||
channel.filters.right().reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q);
|
}
|
||||||
|
|
||||||
|
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 {
|
auto Stream::write(const double* samples) -> void {
|
||||||
for(auto c : range(channels)) {
|
for(auto c : range(channels)) {
|
||||||
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
|
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);
|
channels[c].resampler.write(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 = "103";
|
static const string Version = "103.01";
|
||||||
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/";
|
||||||
|
|
|
@ -74,8 +74,10 @@ auto APU::setSample(int16 sample) -> void {
|
||||||
auto APU::power() -> void {
|
auto APU::power() -> void {
|
||||||
create(APU::Enter, system.frequency());
|
create(APU::Enter, system.frequency());
|
||||||
stream = Emulator::audio.createStream(1, frequency() / rate());
|
stream = Emulator::audio.createStream(1, frequency() / rate());
|
||||||
stream->addLowPassFilter(20000.0, 3);
|
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 90.0);
|
||||||
stream->addHighPassFilter(20.0, 3);
|
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[0].power();
|
||||||
pulse[1].power();
|
pulse[1].power();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
struct APU : Thread {
|
struct APU : Thread {
|
||||||
shared_pointer<Emulator::Stream> stream;
|
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.cpp
|
||||||
APU();
|
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) {
|
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
|
||||||
|
@ -73,7 +73,7 @@ auto APU::DMC::power() -> void {
|
||||||
irqPending = 0;
|
irqPending = 0;
|
||||||
|
|
||||||
period = 0;
|
period = 0;
|
||||||
periodCounter = Region::NTSC() ? dmcPeriodTableNTSC[0] : dmcPeriodTablePAL[0];
|
periodCounter = Region::PAL() ? dmcPeriodTablePAL[0] : dmcPeriodTableNTSC[0];
|
||||||
irqEnable = 0;
|
irqEnable = 0;
|
||||||
loopMode = 0;
|
loopMode = 0;
|
||||||
dacLatch = 0;
|
dacLatch = 0;
|
||||||
|
|
|
@ -19,7 +19,7 @@ auto APU::Noise::clock() -> uint8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
lfsr = (lfsr >> 1) | (feedback << 14);
|
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;
|
return result;
|
||||||
|
|
|
@ -16,7 +16,7 @@ auto Cartridge::main() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::load() -> bool {
|
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.pathID = loaded.pathID();
|
||||||
information.region = loaded.option();
|
information.region = loaded.option();
|
||||||
} else return false;
|
} else return false;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#include "board/board.hpp"
|
#include "board/board.hpp"
|
||||||
|
|
||||||
struct Cartridge : Thread {
|
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
|
//cartridge.cpp
|
||||||
static auto Enter() -> void;
|
static auto Enter() -> void;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
struct CPU : Processor::MOS6502, Thread {
|
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
|
//cpu.cpp
|
||||||
static auto Enter() -> void;
|
static auto Enter() -> void;
|
||||||
|
|
|
@ -30,7 +30,8 @@ namespace Famicom {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Region {
|
struct Region {
|
||||||
static inline auto NTSC() -> bool;
|
static inline auto NTSCJ() -> bool;
|
||||||
|
static inline auto NTSCU() -> bool;
|
||||||
static inline auto PAL() -> bool;
|
static inline auto PAL() -> bool;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
struct PPU : Thread {
|
struct PPU : Thread {
|
||||||
inline auto rate() const -> uint { return Region::NTSC() ? 4 : 5; }
|
inline auto rate() const -> uint { return Region::PAL() ? 5 : 4; }
|
||||||
inline auto vlines() const -> uint { return Region::NTSC() ? 262 : 312; }
|
inline auto vlines() const -> uint { return Region::PAL() ? 312 : 262; }
|
||||||
|
|
||||||
//ppu.cpp
|
//ppu.cpp
|
||||||
static auto Enter() -> void;
|
static auto Enter() -> void;
|
||||||
|
|
|
@ -32,8 +32,12 @@ auto System::load(Emulator::Interface* interface) -> bool {
|
||||||
auto document = BML::unserialize(information.manifest);
|
auto document = BML::unserialize(information.manifest);
|
||||||
if(!cartridge.load()) return false;
|
if(!cartridge.load()) return false;
|
||||||
|
|
||||||
if(cartridge.region() == "NTSC") {
|
if(cartridge.region() == "NTSC-J") {
|
||||||
information.region = Region::NTSC;
|
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;
|
information.frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
|
||||||
}
|
}
|
||||||
if(cartridge.region() == "PAL") {
|
if(cartridge.region() == "PAL") {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
struct System {
|
struct System {
|
||||||
enum class Region : uint { NTSC, PAL };
|
enum class Region : uint { NTSCJ, NTSCU, PAL };
|
||||||
|
|
||||||
auto loaded() const -> bool { return information.loaded; }
|
auto loaded() const -> bool { return information.loaded; }
|
||||||
auto region() const -> Region { return information.region; }
|
auto region() const -> Region { return information.region; }
|
||||||
|
@ -33,7 +33,7 @@ private:
|
||||||
|
|
||||||
struct Information {
|
struct Information {
|
||||||
bool loaded = false;
|
bool loaded = false;
|
||||||
Region region = Region::NTSC;
|
Region region = Region::NTSCJ;
|
||||||
double frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
|
double frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
|
||||||
string manifest;
|
string manifest;
|
||||||
} information;
|
} information;
|
||||||
|
@ -53,5 +53,6 @@ struct Peripherals {
|
||||||
extern System system;
|
extern System system;
|
||||||
extern Peripherals peripherals;
|
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; }
|
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
|
||||||
|
|
|
@ -55,8 +55,8 @@ auto APU::power() -> void {
|
||||||
create(Enter, 2 * 1024 * 1024);
|
create(Enter, 2 * 1024 * 1024);
|
||||||
if(!Model::SuperGameBoy()) {
|
if(!Model::SuperGameBoy()) {
|
||||||
stream = Emulator::audio.createStream(2, frequency());
|
stream = Emulator::audio.createStream(2, frequency());
|
||||||
stream->addLowPassFilter(20000.0, 3);
|
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
|
||||||
stream->addHighPassFilter(20.0, 3);
|
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
|
||||||
}
|
}
|
||||||
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
|
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 {
|
auto APU::power() -> void {
|
||||||
create(APU::Enter, 16'777'216);
|
create(APU::Enter, 16'777'216);
|
||||||
stream = Emulator::audio.createStream(2, frequency() / 64.0);
|
stream = Emulator::audio.createStream(2, frequency() / 64.0);
|
||||||
stream->addLowPassFilter(20000.0, 3);
|
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
|
||||||
stream->addHighPassFilter(20.0, 3);
|
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
|
||||||
|
|
||||||
clock = 0;
|
clock = 0;
|
||||||
square1.power();
|
square1.power();
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
struct APU : Thread, IO {
|
struct APU : Thread, IO {
|
||||||
shared_pointer<Emulator::Stream> stream;
|
shared_pointer<Emulator::Stream> stream;
|
||||||
|
|
||||||
#include "registers.hpp"
|
|
||||||
|
|
||||||
static auto Enter() -> void;
|
static auto Enter() -> void;
|
||||||
auto main() -> void;
|
auto main() -> void;
|
||||||
auto step(uint clocks) -> void;
|
auto step(uint clocks) -> void;
|
||||||
|
@ -16,6 +14,165 @@ struct APU : Thread, IO {
|
||||||
auto serialize(serializer&) -> void;
|
auto serialize(serializer&) -> void;
|
||||||
|
|
||||||
uint clock;
|
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;
|
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 {
|
auto APU::Wave::run() -> void {
|
||||||
if(period && --period == 0) {
|
if(period && --period == 0) {
|
||||||
period = 1 * (2048 - frequency);
|
period = 1 * (2048 - frequency);
|
||||||
patternsample = pattern[patternbank * 16 + patternaddr++];
|
patternsample = pattern[patternbank << 5 | patternaddr++];
|
||||||
if(patternaddr == 0) patternbank ^= mode;
|
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 {
|
auto APU::Wave::readram(uint addr) const -> uint8 {
|
||||||
uint8 byte = 0;
|
uint8 byte = 0;
|
||||||
byte |= pattern[addr * 2 + 0] << 0;
|
byte |= pattern[!bank << 5 | addr << 1 | 0] << 0;
|
||||||
byte |= pattern[addr * 2 + 1] << 4;
|
byte |= pattern[!bank << 5 | addr << 1 | 1] << 4;
|
||||||
return byte;
|
return byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::Wave::writeram(uint addr, uint8 byte) -> void {
|
auto APU::Wave::writeram(uint addr, uint8 byte) -> void {
|
||||||
pattern[addr * 2 + 0] = byte >> 0;
|
pattern[!bank << 5 | addr << 1 | 0] = byte >> 0;
|
||||||
pattern[addr * 2 + 1] = byte >> 4;
|
pattern[!bank << 5 | addr << 1 | 1] = byte >> 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto APU::Wave::power() -> void {
|
auto APU::Wave::power() -> void {
|
||||||
|
|
|
@ -37,12 +37,13 @@ auto PSG::step(uint clocks) -> void {
|
||||||
auto PSG::power() -> void {
|
auto PSG::power() -> void {
|
||||||
create(PSG::Enter, system.colorburst() / 16.0);
|
create(PSG::Enter, system.colorburst() / 16.0);
|
||||||
stream = Emulator::audio.createStream(1, frequency());
|
stream = Emulator::audio.createStream(1, frequency());
|
||||||
stream->addLowPassFilter(20000.0, 3);
|
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
|
||||||
stream->addHighPassFilter(20.0, 3);
|
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;
|
select = 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] = 0x1400 * pow(2, n * -2.0 / 6.0) + 0.5;
|
||||||
}
|
}
|
||||||
levels[15] = 0;
|
levels[15] = 0;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ auto PSG::serialize(serializer& s) -> void {
|
||||||
noise.serialize(s);
|
noise.serialize(s);
|
||||||
|
|
||||||
s.integer(select);
|
s.integer(select);
|
||||||
s.array(levels);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PSG::Tone::serialize(serializer& s) -> void {
|
auto PSG::Tone::serialize(serializer& s) -> void {
|
||||||
|
|
|
@ -116,8 +116,9 @@ auto VDP::readControlPort() -> uint16 {
|
||||||
uint16 result = 0b0011'0100'0000'0000;
|
uint16 result = 0b0011'0100'0000'0000;
|
||||||
result |= 1 << 9; //FIFO empty
|
result |= 1 << 9; //FIFO empty
|
||||||
result |= (state.vcounter >= screenHeight()) << 3; //vertical blank
|
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 |= io.command.bit(5) << 1; //DMA active
|
||||||
|
result |= Region::PAL() << 0;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ auto VDP::main() -> void {
|
||||||
if(io.verticalBlankInterruptEnable) {
|
if(io.verticalBlankInterruptEnable) {
|
||||||
cpu.raise(CPU::Interrupt::VerticalBlank);
|
cpu.raise(CPU::Interrupt::VerticalBlank);
|
||||||
}
|
}
|
||||||
|
//todo: should only stay high for ~2573/2 clocks
|
||||||
apu.setINT(true);
|
apu.setINT(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -157,6 +157,9 @@ auto YM2612::step(uint clocks) -> void {
|
||||||
auto YM2612::power() -> void {
|
auto YM2612::power() -> void {
|
||||||
create(YM2612::Enter, system.colorburst() * 15.0 / 7.0);
|
create(YM2612::Enter, system.colorburst() * 15.0 / 7.0);
|
||||||
stream = Emulator::audio.createStream(2, frequency() / 144.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 = {};
|
io = {};
|
||||||
lfo = {};
|
lfo = {};
|
||||||
|
|
|
@ -44,8 +44,8 @@ auto PSG::power() -> void {
|
||||||
//use stereo mode for both; output same sample to both channels for Master System
|
//use stereo mode for both; output same sample to both channels for Master System
|
||||||
create(PSG::Enter, system.colorburst() / 16.0);
|
create(PSG::Enter, system.colorburst() / 16.0);
|
||||||
stream = Emulator::audio.createStream(2, frequency());
|
stream = Emulator::audio.createStream(2, frequency());
|
||||||
stream->addLowPassFilter(20000.0, 3);
|
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
|
||||||
stream->addHighPassFilter(20.0, 3);
|
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
|
||||||
|
|
||||||
select = 0;
|
select = 0;
|
||||||
for(auto n : range(15)) {
|
for(auto n : range(15)) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ auto PSG::serialize(serializer& s) -> void {
|
||||||
noise.serialize(s);
|
noise.serialize(s);
|
||||||
|
|
||||||
s.integer(select);
|
s.integer(select);
|
||||||
s.array(levels);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PSG::Tone::serialize(serializer& s) -> void {
|
auto PSG::Tone::serialize(serializer& s) -> void {
|
||||||
|
|
|
@ -52,8 +52,8 @@ auto PSG::step(uint clocks) -> void {
|
||||||
auto PSG::power() -> void {
|
auto PSG::power() -> void {
|
||||||
create(PSG::Enter, system.colorburst());
|
create(PSG::Enter, system.colorburst());
|
||||||
stream = Emulator::audio.createStream(2, frequency());
|
stream = Emulator::audio.createStream(2, frequency());
|
||||||
stream->addLowPassFilter(20000.0, 3);
|
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
|
||||||
stream->addHighPassFilter(20.0, 3);
|
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
@ -48,8 +48,8 @@ auto ICD2::unload() -> void {
|
||||||
auto ICD2::power() -> void {
|
auto ICD2::power() -> void {
|
||||||
create(ICD2::Enter, system.colorburst() * 6.0 / 5.0);
|
create(ICD2::Enter, system.colorburst() * 6.0 / 5.0);
|
||||||
stream = Emulator::audio.createStream(2, frequency() / 2.0);
|
stream = Emulator::audio.createStream(2, frequency() / 2.0);
|
||||||
stream->addLowPassFilter(20000.0, 3);
|
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
|
||||||
stream->addHighPassFilter(20.0, 3);
|
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
|
||||||
|
|
||||||
r6003 = 0x00;
|
r6003 = 0x00;
|
||||||
r6004 = 0xff;
|
r6004 = 0xff;
|
||||||
|
|
|
@ -67,8 +67,8 @@ auto APU::step(uint clocks) -> void {
|
||||||
auto APU::power() -> void {
|
auto APU::power() -> void {
|
||||||
create(APU::Enter, 3'072'000);
|
create(APU::Enter, 3'072'000);
|
||||||
stream = Emulator::audio.createStream(2, frequency());
|
stream = Emulator::audio.createStream(2, frequency());
|
||||||
stream->addLowPassFilter(20000.0, 3);
|
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
|
||||||
stream->addHighPassFilter(20.0, 3);
|
stream->addFilter(Emulator::Filter::Order::Second, Emulator::Filter::Type::LowPass, 20000.0, 3);
|
||||||
|
|
||||||
bus.map(this, 0x004a, 0x004c);
|
bus.map(this, 0x004a, 0x004c);
|
||||||
bus.map(this, 0x004e, 0x0050);
|
bus.map(this, 0x004e, 0x0050);
|
||||||
|
|
|
@ -26,20 +26,19 @@ auto OnePole::reset(Type type, double cutoffFrequency, double samplingFrequency)
|
||||||
this->cutoffFrequency = cutoffFrequency;
|
this->cutoffFrequency = cutoffFrequency;
|
||||||
this->samplingFrequency = samplingFrequency;
|
this->samplingFrequency = samplingFrequency;
|
||||||
|
|
||||||
b1 = exp(-2.0 * Math::Pi * cutoffFrequency / samplingFrequency);
|
|
||||||
a0 = 1.0 - b1;
|
|
||||||
|
|
||||||
z1 = 0.0;
|
z1 = 0.0;
|
||||||
}
|
double x = cos(2.0 * Math::Pi * cutoffFrequency / samplingFrequency);
|
||||||
|
if(type == Type::LowPass) {
|
||||||
auto OnePole::process(double in) -> double {
|
b1 = +2.0 - x - sqrt((+2.0 - x) * (+2.0 - x) - 1);
|
||||||
z1 = in * a0 + z1 * b1;
|
a0 = 1.0 - b1;
|
||||||
|
} else {
|
||||||
switch(type) {
|
b1 = -2.0 - x + sqrt((-2.0 - x) * (-2.0 - x) - 1);
|
||||||
case Type::LowPass: return z1;
|
a0 = 1.0 + b1;
|
||||||
case Type::HighPass: return in - z1;
|
|
||||||
default: return 0.0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto OnePole::process(double in) -> double {
|
||||||
|
return z1 = in * a0 + z1 * b1;
|
||||||
|
}
|
||||||
|
|
||||||
}}}
|
}}}
|
||||||
|
|
Loading…
Reference in New Issue