diff --git a/higan/audio/audio.cpp b/higan/audio/audio.cpp index cc441d0b..9a7fcaa2 100644 --- a/higan/audio/audio.cpp +++ b/higan/audio/audio.cpp @@ -5,11 +5,15 @@ namespace Emulator { #include "stream.cpp" Audio audio; -auto Audio::reset() -> void { +auto Audio::reset(maybe channels_, maybe 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 { - shared_pointer stream = new Stream{channels, frequency}; - stream->setFrequency(this->frequency); + shared_pointer 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); } } diff --git a/higan/audio/audio.hpp b/higan/audio/audio.hpp index eb22049c..7b0e0007 100644 --- a/higan/audio/audio.hpp +++ b/higan/audio/audio.hpp @@ -10,55 +10,52 @@ struct Audio; struct Stream; struct Audio { - auto reset() -> void; + auto reset(maybe channels = nothing, maybe 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; - auto poll() -> void; - private: + auto process() -> void; + Interface* interface = nullptr; vector> streams; + + uint channels = 0; double frequency = 0.0; + double volume = 1.0; double balance = 0.0; bool reverbEnable = false; - vector>> reverb; + vector>> 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 auto sample(P&&... p) -> void { - int16 samples[sizeof...(P)] = {forward

(p)...}; + double samples[sizeof...(P)] = {forward

(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> iir; - vector resampler; + const uint order = 6; //Nth-order filter (must be an even number) + struct Channel { + vector iir; + DSP::Resampler::Cubic resampler; + }; + vector channels; friend class Audio; }; diff --git a/higan/audio/stream.cpp b/higan/audio/stream.cpp index 3363126f..dc39be22 100644 --- a/higan/audio/stream.cpp +++ b/higan/audio/stream.cpp @@ -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(); } diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index be02c33e..15761d2b 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -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/"; diff --git a/higan/emulator/interface.hpp b/higan/emulator/interface.hpp index 8ae94725..a3c9faac 100644 --- a/higan/emulator/interface.hpp +++ b/higan/emulator/interface.hpp @@ -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); } diff --git a/higan/fc/apu/apu.cpp b/higan/fc/apu/apu.cpp index 71a99408..c8091b2d 100644 --- a/higan/fc/apu/apu.cpp +++ b/higan/fc/apu/apu.cpp @@ -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(); } diff --git a/higan/fc/interface/interface.cpp b/higan/fc/interface/interface.cpp index b77615cf..345ee3ac 100644 --- a/higan/fc/interface/interface.cpp +++ b/higan/fc/interface/interface.cpp @@ -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; diff --git a/higan/gb/apu/apu.cpp b/higan/gb/apu/apu.cpp index 3aae8ca4..f7491ecc 100644 --- a/higan/gb/apu/apu.cpp +++ b/higan/gb/apu/apu.cpp @@ -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 diff --git a/higan/gba/apu/apu.cpp b/higan/gba/apu/apu.cpp index 247d34d1..3daecacd 100644 --- a/higan/gba/apu/apu.cpp +++ b/higan/gba/apu/apu.cpp @@ -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); } diff --git a/higan/sfc/coprocessor/icd2/icd2.cpp b/higan/sfc/coprocessor/icd2/icd2.cpp index e1b2a006..56334fef 100644 --- a/higan/sfc/coprocessor/icd2/icd2.cpp +++ b/higan/sfc/coprocessor/icd2/icd2.cpp @@ -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(); diff --git a/higan/sfc/coprocessor/icd2/interface/interface.cpp b/higan/sfc/coprocessor/icd2/interface/interface.cpp index 6440abef..3d901e49 100644 --- a/higan/sfc/coprocessor/icd2/interface/interface.cpp +++ b/higan/sfc/coprocessor/icd2/interface/interface.cpp @@ -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 { diff --git a/higan/sfc/coprocessor/icd2/interface/interface.hpp b/higan/sfc/coprocessor/icd2/interface/interface.hpp index f6dcde6c..b9064f9a 100644 --- a/higan/sfc/coprocessor/icd2/interface/interface.hpp +++ b/higan/sfc/coprocessor/icd2/interface/interface.hpp @@ -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 { diff --git a/higan/sfc/coprocessor/msu1/msu1.cpp b/higan/sfc/coprocessor/msu1/msu1.cpp index 0f2442b0..6d8b4622 100644 --- a/higan/sfc/coprocessor/msu1/msu1.cpp +++ b/higan/sfc/coprocessor/msu1/msu1.cpp @@ -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(); } diff --git a/higan/sfc/dsp/echo.cpp b/higan/sfc/dsp/echo.cpp index b8f3e751..301e4ae0 100644 --- a/higan/sfc/dsp/echo.cpp +++ b/higan/sfc/dsp/echo.cpp @@ -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 { diff --git a/higan/target-tomoko/program/interface.cpp b/higan/target-tomoko/program/interface.cpp index 8a3808e5..ae26f7f6 100644 --- a/higan/target-tomoko/program/interface.cpp +++ b/higan/target-tomoko/program/interface.cpp @@ -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); } diff --git a/higan/target-tomoko/program/medium.cpp b/higan/target-tomoko/program/medium.cpp index 07a2ce62..511bb2de 100644 --- a/higan/target-tomoko/program/medium.cpp +++ b/higan/target-tomoko/program/medium.cpp @@ -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()); + Emulator::audio.reset(2, audio->get(Audio::Frequency).get(44100)); emulator = &interface; connectDevices(); emulator->load(medium.id); diff --git a/higan/target-tomoko/program/program.hpp b/higan/target-tomoko/program/program.hpp index b49140dc..2c80d1da 100644 --- a/higan/target-tomoko/program/program.hpp +++ b/higan/target-tomoko/program/program.hpp @@ -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; diff --git a/higan/target-tomoko/settings/audio.cpp b/higan/target-tomoko/settings/audio.cpp index 5c59207f..f43faaa7 100644 --- a/higan/target-tomoko/settings/audio.cpp +++ b/higan/target-tomoko/settings/audio.cpp @@ -24,7 +24,7 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { latencyCombo.onChange([&] { updateDriver(); }); frequencyLabel.setText("Frequency:"); - auto frequencyValue = audio->get(Audio::Frequency).get(); + auto frequencyValue = audio->get(Audio::Frequency).get(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); diff --git a/higan/ws/apu/apu.cpp b/higan/ws/apu/apu.cpp index fd106a1a..317ae454 100644 --- a/higan/ws/apu/apu.cpp +++ b/higan/ws/apu/apu.cpp @@ -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 { diff --git a/nall/dsp/iir/biquad.hpp b/nall/dsp/iir/biquad.hpp index f20cedfa..b73dcb57 100644 --- a/nall/dsp/iir/biquad.hpp +++ b/nall/dsp/iir/biquad.hpp @@ -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))); +} + }}} diff --git a/nall/interpolation.hpp b/nall/interpolation.hpp index 587a9b2b..007f2ec2 100644 --- a/nall/interpolation.hpp +++ b/nall/interpolation.hpp @@ -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; }