Update to v098r14 release.

byuu says:

Changelog:
- improved attenuation of biquad filter by computing butterworth Q
  coefficients correctly (instead of using the same constant)
- adding 1e-25 to each input sample into the biquad filters to try and
  prevent denormalization
- updated normalization from [0.0 to 1.0] to [-1.0 to +1.0]; volume/reverb
  happen in floating-point mode now
- good amount of work to make the base Emulator::Audio support any number
  of output channels
  - so that we don't have to do separate work on left/right channels;
    and can instead share the code for each channel
- Emulator::Interface::audioSample(int16 left, int16 right); changed to:
  - Emulator::Interface::audioSample(double* samples, uint channels);
  - samples are normalized [-1.0 to +1.0]
  - for now at least, channels will be the value given to
    Emulator::Audio::reset()
- fixed GUI crash on startup when audio driver is set to None

I'm probably going to be updating ruby to accept normalized doubles as
well; but I'm not sure if I will try and support anything other 2-channel
audio output. It'll depend on how easy it is to do so; perhaps it'll be
a per-driver setting.

The denormalization thing is fierce. If that happens, it drops the
emulator framerate from 220fps to about 20fps for Game Boy emulation. And
that happens basically whenever audio output is silent. I'm probably
also going to make a nall/denormal.hpp file at some point with
platform-specific functionality to set the CPU state to "denormals as
zero" where applicable. I'll still add the 1e-25 offset (inaudible)
as another fallback.
This commit is contained in:
Tim Allen 2016-06-01 21:23:22 +10:00
parent 839813d0f1
commit fdc41611cf
21 changed files with 107 additions and 120 deletions

View File

@ -5,11 +5,15 @@ namespace Emulator {
#include "stream.cpp"
Audio audio;
auto Audio::reset() -> void {
auto Audio::reset(maybe<uint> channels_, maybe<double> frequency_) -> void {
if(channels_) channels = channels_();
if(frequency_) frequency = frequency_();
streams.reset();
reverb.reset();
reverb.resize(2);
for(auto c : range(2)) {
reverb.resize(channels);
for(auto c : range(channels)) {
reverb[c].resize(7);
reverb[c][0].resize(1229);
reverb[c][1].resize(1559);
@ -25,11 +29,6 @@ auto Audio::setInterface(Interface* interface) -> void {
this->interface = interface;
}
auto Audio::setFrequency(double frequency) -> void {
this->frequency = frequency;
for(auto& stream : streams) stream->setFrequency(frequency);
}
auto Audio::setVolume(double volume) -> void {
this->volume = volume;
}
@ -43,52 +42,48 @@ auto Audio::setReverb(bool enabled) -> void {
}
auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> {
shared_pointer<Stream> stream = new Stream{channels, frequency};
stream->setFrequency(this->frequency);
shared_pointer<Stream> stream = new Stream;
stream->reset(channels, frequency, this->frequency);
streams.append(stream);
return stream;
}
//audio mixer
auto Audio::poll() -> void {
auto Audio::process() -> void {
while(true) {
for(auto& stream : streams) {
if(!stream->pending()) return;
}
double left = 0.0, right = 0.0;
double samples[channels] = {0};
for(auto& stream : streams) {
double samples[2];
stream->read(samples);
left += samples[0];
right += samples[1];
}
left /= streams.size();
right /= streams.size();
double buffer[16];
uint length = stream->read(buffer), offset = 0;
if(balance < 0.0) right *= 1.0 + balance;
if(balance > 0.0) left *= 1.0 - balance;
//todo: apply volume, reverb before denormalization?
int ileft = (left * 65535.0) - 32768.0;
int iright = (right * 65535.0) - 32768.0;
if(reverbEnable) {
ileft *= 0.125;
for(auto n : range(7)) ileft += 0.125 * reverb[0][n].last();
for(auto n : range(7)) reverb[0][n].write(ileft);
ileft *= 8.000;
iright *= 0.125;
for(auto n : range(7)) iright += 0.125 * reverb[1][n].last();
for(auto n : range(7)) reverb[1][n].write(iright);
iright *= 8.000;
for(auto c : range(channels)) {
samples[c] += buffer[offset];
if(++offset >= length) offset = 0;
}
}
ileft *= volume;
iright *= volume;
for(auto c : range(channels)) {
samples[c] /= streams.size();
interface->audioSample(sclamp<16>(ileft), sclamp<16>(iright));
if(reverbEnable) {
samples[c] *= 0.125;
for(auto n : range(7)) samples[c] += 0.125 * reverb[c][n].last();
for(auto n : range(7)) reverb[c][n].write(samples[c]);
samples[c] *= 8.000;
}
samples[c] *= volume;
}
if(channels == 2) {
if(balance < 0.0) samples[1] *= 1.0 + balance;
if(balance > 0.0) samples[0] *= 1.0 - balance;
}
interface->audioSample(samples, channels);
}
}

View File

@ -10,55 +10,52 @@ struct Audio;
struct Stream;
struct Audio {
auto reset() -> void;
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
auto setInterface(Interface*) -> void;
auto setFrequency(double frequency) -> void;
auto setVolume(double volume) -> void;
auto setBalance(double balance) -> void;
auto setReverb(bool enabled) -> void;
auto createStream(uint channels, double frequency) -> shared_pointer<Stream>;
auto poll() -> void;
private:
auto process() -> void;
Interface* interface = nullptr;
vector<shared_pointer<Stream>> streams;
uint channels = 0;
double frequency = 0.0;
double volume = 1.0;
double balance = 0.0;
bool reverbEnable = false;
vector<vector<queue<int16>>> reverb;
vector<vector<queue<double>>> reverb;
friend class Stream;
};
struct Stream {
Stream(uint channels, double inputFrequency);
auto reset() -> void;
auto setFrequency(double outputFrequency) -> void;
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
auto pending() const -> bool;
auto read(double* samples) -> void;
auto write(int16* samples) -> void;
auto read(double* samples) -> uint;
auto write(const double* samples) -> void;
template<typename... P> auto sample(P&&... p) -> void {
int16 samples[sizeof...(P)] = {forward<P>(p)...};
double samples[sizeof...(P)] = {forward<P>(p)...};
write(samples);
}
private:
const uint channels;
const double inputFrequency;
double outputFrequency = 0.0;
double cutoffFrequency = 0.0;
const uint iirPasses = 3; //6th-order filter
vector<vector<DSP::IIR::Biquad>> iir;
vector<DSP::Resampler::Cubic> resampler;
const uint order = 6; //Nth-order filter (must be an even number)
struct Channel {
vector<DSP::IIR::Biquad> iir;
DSP::Resampler::Cubic resampler;
};
vector<Channel> channels;
friend class Audio;
};

View File

@ -1,49 +1,35 @@
Stream::Stream(uint channels, double inputFrequency) : channels(channels), inputFrequency(inputFrequency) {
}
auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void {
channels.reset();
channels.resize(channels_);
auto Stream::reset() -> void {
iir.reset();
resampler.reset();
}
auto Stream::setFrequency(double outputFrequency_) -> void {
reset();
outputFrequency = outputFrequency_;
cutoffFrequency = outputFrequency / inputFrequency;
iir.resize(channels);
if(cutoffFrequency <= 0.5) {
for(auto c : range(channels)) {
iir[c].resize(iirPasses);
for(auto p : range(iirPasses)) {
//attenuates frequencies that exceed the limits of human hearing (20KHz) to prevent aliasing
iir[c][p].reset(DSP::IIR::Biquad::Type::LowPass, 20000.0 / inputFrequency);
for(auto& channel : channels) {
if(outputFrequency / inputFrequency <= 0.5) {
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);
}
}
}
resampler.resize(channels);
for(auto c : range(channels)) {
resampler[c].reset(inputFrequency, outputFrequency);
channel.resampler.reset(inputFrequency, outputFrequency);
}
}
auto Stream::pending() const -> bool {
return resampler && resampler[0].pending();
return channels && channels[0].resampler.pending();
}
auto Stream::read(double* samples) -> void {
for(auto c : range(channels)) samples[c] = resampler[c].read();
if(channels == 1) samples[1] = samples[0]; //monaural->stereo hack
auto Stream::read(double* samples) -> uint {
for(auto c : range(channels)) samples[c] = channels[c].resampler.read();
return channels.size();
}
auto Stream::write(int16* samples) -> void {
auto Stream::write(const double* samples) -> void {
for(auto c : range(channels)) {
double sample = (samples[c] + 32768.0) / 65535.0; //normalize
for(auto& p : iir[c]) sample = p.process(sample);
resampler[c].write(sample);
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
for(auto& iir : channels[c].iir) sample = iir.process(sample);
channels[c].resampler.write(sample);
}
audio.poll();
audio.process();
}

View File

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

View File

@ -50,7 +50,7 @@ struct Interface {
virtual auto loadRequest(uint, string, bool) -> void {}
virtual auto saveRequest(uint, string) -> void {}
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
virtual auto audioSample(int16, int16) -> void {}
virtual auto audioSample(const double*, uint) -> void {}
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
virtual auto dipSettings(const Markup::Node&) -> uint { return 0; }
@ -64,7 +64,7 @@ struct Interface {
auto loadRequest(uint id, string path, bool required) -> void { return bind->loadRequest(id, path, required); }
auto saveRequest(uint id, string path) -> void { return bind->saveRequest(id, path); }
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
auto audioSample(int16 lsample, int16 rsample) -> void { return bind->audioSample(lsample, rsample); }
auto audioSample(const double* samples, uint channels) -> void { return bind->audioSample(samples, channels); }
auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); }
auto inputRumble(uint port, uint device, uint input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); }
auto dipSettings(const Markup::Node& node) -> uint { return bind->dipSettings(node); }

View File

@ -57,7 +57,7 @@ auto APU::main() -> void {
//output = filter.run_lopass(output);
output = sclamp<16>(output);
stream->sample(output);
stream->sample(output / 32768.0);
tick();
}

View File

@ -97,8 +97,8 @@ auto Interface::videoColor(uint32 n) -> uint64 {
v *= brightness / 12.0;
y += v;
i += v * cos((3.141592653 / 6.0) * (p + hue));
q += v * sin((3.141592653 / 6.0) * (p + hue));
i += v * cos((Math::Pi / 6.0) * (p + hue));
q += v * sin((Math::Pi / 6.0) * (p + hue));
}
i *= saturation;

View File

@ -26,9 +26,10 @@ auto APU::main() -> void {
hipass(sequencer.right, sequencer.rightBias);
if(!system.sgb()) {
stream->sample(sequencer.left, sequencer.right);
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
} else {
interface->audioSample(sequencer.left, sequencer.right);
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
interface->audioSample(samples, 2);
}
if(cycle == 0) { //512hz

View File

@ -63,7 +63,7 @@ auto APU::main() -> void {
if(regs.bias.amplitude == 3) lsample &= ~15, rsample &= ~15;
if(cpu.regs.mode == CPU::Registers::Mode::Stop) lsample = 0, rsample = 0;
stream->sample(sclamp<16>(lsample << 6), sclamp<16>(rsample << 6)); //should be <<5; use <<6 for added volume
stream->sample(sclamp<16>(lsample << 6) / 32768.0, sclamp<16>(rsample << 6) / 32768.0); //should be <<5; use <<6 for added volume
step(512);
}

View File

@ -24,7 +24,7 @@ auto ICD2::main() -> void {
step(GameBoy::system._clocksExecuted);
GameBoy::system._clocksExecuted = 0;
} else { //DMG halted
stream->sample(0, 0);
stream->sample(0.0, 0.0);
step(2); //two clocks per audio sample
}
synchronizeCPU();

View File

@ -119,8 +119,8 @@ auto ICD2::saveRequest(uint id, string name) -> void {
auto ICD2::videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {
}
auto ICD2::audioSample(int16 left, int16 right) -> void {
stream->sample(left, right);
auto ICD2::audioSample(const double* samples, uint channels) -> void {
stream->write(samples);
}
auto ICD2::inputPoll(uint port, uint device, uint id) -> int16 {

View File

@ -7,7 +7,7 @@ auto loadRequest(uint id, string name, bool required) -> void override;
auto saveRequest(uint id, string name) -> void override;
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void override;
auto audioSample(int16 lsample, int16 rsample) -> void override;
auto audioSample(const double* samples, uint channels) -> void override;
auto inputPoll(uint port, uint device, uint id) -> int16 override;
struct Packet {

View File

@ -38,7 +38,7 @@ auto MSU1::main() -> void {
right = sclamp<16>(rchannel);
if(dsp.mute()) left = 0, right = 0;
stream->sample(left, right);
stream->sample(left / 32768.0, right / 32768.0);
step(1);
synchronizeCPU();
}

View File

@ -103,7 +103,7 @@ auto DSP::echo27() -> void {
}
//output sample to DAC
stream->sample(outl, outr);
stream->sample(outl / 32768.0, outr / 32768.0);
}
auto DSP::echo28() -> void {

View File

@ -89,7 +89,9 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig
}
}
auto Program::audioSample(int16 left, int16 right) -> void {
auto Program::audioSample(const double* samples, uint channels) -> void {
int16 left = sclamp<16>(samples[0] * 32768.0);
int16 right = sclamp<16>(samples[1] * 32768.0);
audio->sample(left, right);
}

View File

@ -21,8 +21,7 @@ auto Program::loadMedium(Emulator::Interface& interface, Emulator::Interface::Me
folderPaths.append(location);
//note: the order of operations in this block of code is critical
Emulator::audio.reset();
Emulator::audio.setFrequency(audio->get(Audio::Frequency).get<uint>());
Emulator::audio.reset(2, audio->get(Audio::Frequency).get<uint>(44100));
emulator = &interface;
connectDevices();
emulator->load(medium.id);

View File

@ -10,7 +10,7 @@ struct Program : Emulator::Interface::Bind {
auto loadRequest(uint id, string path, bool required) -> void override;
auto saveRequest(uint id, string path) -> void override;
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void override;
auto audioSample(int16 lsample, int16 rsample) -> void override;
auto audioSample(const double* samples, uint channels) -> void override;
auto inputPoll(uint port, uint device, uint input) -> int16 override;
auto inputRumble(uint port, uint device, uint input, bool enable) -> void override;
auto dipSettings(const Markup::Node& node) -> uint override;

View File

@ -24,7 +24,7 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
latencyCombo.onChange([&] { updateDriver(); });
frequencyLabel.setText("Frequency:");
auto frequencyValue = audio->get(Audio::Frequency).get<uint>();
auto frequencyValue = audio->get(Audio::Frequency).get<uint>(44100);
frequencyCombo.append(ComboButtonItem().setText({frequencyValue, "hz"}));
frequencyCombo.setEnabled(false);
@ -40,7 +40,7 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
volumeLabel.setText("Volume:");
volumeValue.setAlignment(0.5);
volumeSlider.setLength(201).setPosition(settings["Audio/Volume"].natural()).onChange([&] { updateEffects(); });
volumeSlider.setLength(501).setPosition(settings["Audio/Volume"].natural()).onChange([&] { updateEffects(); });
balanceLabel.setText("Balance:");
balanceValue.setAlignment(0.5);

View File

@ -56,7 +56,7 @@ auto APU::dacRun() -> void {
right = 0;
}
stream->sample(left, right);
stream->sample(left / 32768.0, right / 32768.0);
}
auto APU::step(uint clocks) -> void {

View File

@ -1,6 +1,6 @@
#pragma once
//transposed direct form II biquadratic second-order IIR filter (butterworth)
//transposed direct form II biquadratic second-order IIR filter
namespace nall { namespace DSP { namespace IIR {
@ -15,8 +15,10 @@ struct Biquad {
HighShelf,
};
inline auto reset(Type type, double cutoff, double quality = 0.707107, double gain = 0.0) -> void;
inline auto process(double in) -> double; //normalized sample
inline auto reset(Type type, double cutoff, 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
@ -37,7 +39,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(3.141592 * cutoff);
double k = tan(Math::Pi * cutoff);
double q = quality;
double n = 0.0;
@ -143,4 +145,9 @@ auto Biquad::process(double in) -> double {
return out;
}
//compute Q values for N-order butterworth filtering
auto Biquad::butterworth(uint order, uint phase) -> double {
return -0.5 / cos(Math::Pi / 2.0 * (1.0 + (1.0 + (2.0 * phase + 1.0) / order)));
}
}}}

View File

@ -19,7 +19,7 @@ struct Interpolation {
}
static inline auto Cosine(double mu, double a, double b, double c, double d) -> double {
mu = (1.0 - cos(mu * 3.14159265)) / 2.0;
mu = (1.0 - cos(mu * Math::Pi)) / 2.0;
return b * (1.0 - mu) + c * mu;
}