diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 2a24626cc..2e9adba5e 100755 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -39,13 +39,13 @@ using namespace nall; namespace higan { static const string Name = "higan"; - static const string Version = "106.233"; + static const string Version = "106.234"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org"; //incremented only when serialization format changes - static const string SerializerVersion = "106.210"; + static const string SerializerVersion = "106.234"; namespace Constants { namespace Colorburst { diff --git a/higan/emulator/node/audio/stream.cpp b/higan/emulator/node/audio/stream.cpp index c60b30175..37eb4300d 100644 --- a/higan/emulator/node/audio/stream.cpp +++ b/higan/emulator/node/audio/stream.cpp @@ -5,6 +5,7 @@ auto Stream::setChannels(uint channels) -> void { auto Stream::setFrequency(double frequency) -> void { _frequency = frequency; + setResamplerFrequency(_resamplerFrequency); } auto Stream::setResamplerFrequency(double resamplerFrequency) -> void { diff --git a/higan/emulator/node/audio/stream.hpp b/higan/emulator/node/audio/stream.hpp index c4f9b7e16..42ffe369f 100644 --- a/higan/emulator/node/audio/stream.hpp +++ b/higan/emulator/node/audio/stream.hpp @@ -40,6 +40,6 @@ struct Stream : Object { DSP::Resampler::Cubic resampler; }; vector _channels; - double _frequency = 0; - double _resamplerFrequency = 0; + double _frequency = 48000.0; + double _resamplerFrequency = 48000.0; }; diff --git a/higan/sfc/coprocessor/icd/icd.cpp b/higan/sfc/coprocessor/icd/icd.cpp index fcb10c562..bfb7a3040 100755 --- a/higan/sfc/coprocessor/icd/icd.cpp +++ b/higan/sfc/coprocessor/icd/icd.cpp @@ -6,6 +6,10 @@ ICD icd; #include "io.cpp" #include "serialization.cpp" +auto ICD::clockFrequency() const -> double { + return Frequency ? Frequency : system.cpuFrequency(); +} + auto ICD::load(Node::Peripheral parent, Node::Peripheral from) -> void { GameBoy::superGameBoy = this; GameBoy::SuperGameBoyInterface::load((Node::Object&)parent, Node::serialize(from)); @@ -15,16 +19,15 @@ auto ICD::load(Node::Peripheral parent, Node::Peripheral from) -> void { auto ICD::unload() -> void { GameBoy::SuperGameBoyInterface::unload(); -} -auto ICD::name() const -> string { - return GameBoy::interface->game(); + cpu.coprocessors.removeByValue(this); + Thread::destroy(); } auto ICD::main() -> void { #if 0 - static uint n=0; - float x=sin((2*3.141592*(n++/64)*1000.0)/44100.0)*0.1; + static uint n = 0; + float x = sin((2*3.141592*(n++/64)*1000.0)/44100.0)*0.1; GameBoy::apu.stream->sample(x, x); Thread::step(2); Thread::synchronize(cpu); @@ -43,7 +46,7 @@ auto ICD::main() -> void { auto ICD::power(bool reset) -> void { //SGB1 uses CPU oscillator; SGB2 uses dedicated oscillator - Thread::create((Frequency ? Frequency : system.cpuFrequency()) / 5.0, [&] { + Thread::create(clockFrequency() / 5.0, [&] { while(true) { if(scheduler.serializing()) GameBoy::system.runToSave(); scheduler.serialize(); @@ -82,6 +85,7 @@ auto ICD::power(bool reset) -> void { vcounter = 0; GameBoy::system.power(); + GameBoy::apu.stream->setFrequency(clockFrequency() / 5.0 / 2.0); } #endif diff --git a/higan/sfc/coprocessor/icd/icd.hpp b/higan/sfc/coprocessor/icd/icd.hpp index bb4f527bd..a66c0f13b 100755 --- a/higan/sfc/coprocessor/icd/icd.hpp +++ b/higan/sfc/coprocessor/icd/icd.hpp @@ -2,10 +2,11 @@ struct ICD : Platform, GameBoy::SuperGameBoyInterface, Thread { //icd.cpp + auto clockFrequency() const -> double; + auto load(Node::Peripheral, Node::Peripheral) -> void; auto unload() -> void; - auto name() const -> string; auto main() -> void; auto power(bool reset = false) -> void; diff --git a/higan/sfc/coprocessor/icd/io.cpp b/higan/sfc/coprocessor/icd/io.cpp index eb7159a4a..6268e1b96 100755 --- a/higan/sfc/coprocessor/icd/io.cpp +++ b/higan/sfc/coprocessor/icd/io.cpp @@ -56,11 +56,13 @@ auto ICD::writeIO(uint24 address, uint8 data) -> void { } auto frequency = system.cpuFrequency(); switch(data.bit(0,1)) { - case 0: Thread::setFrequency(frequency / 4); break; //fast (glitchy, even on real hardware) - case 1: Thread::setFrequency(frequency / 5); break; //normal - case 2: Thread::setFrequency(frequency / 7); break; //slow - case 3: Thread::setFrequency(frequency / 9); break; //very slow + case 0: frequency /= 4; break; //fast (glitchy, even on real hardware) + case 1: frequency /= 5; break; //normal + case 2: frequency /= 7; break; //slow + case 3: frequency /= 9; break; //very slow } + Thread::setFrequency(frequency); + GameBoy::apu.stream->setFrequency(frequency / 2.0); r6003 = data; return; } diff --git a/higan/target-higan/emulator/platform.cpp b/higan/target-higan/emulator/platform.cpp index d0e8d6139..a5b3b46d7 100644 --- a/higan/target-higan/emulator/platform.cpp +++ b/higan/target-higan/emulator/platform.cpp @@ -150,38 +150,41 @@ auto Emulator::video(higan::Node::Screen node, const uint32_t* data, uint pitch, auto Emulator::audio(higan::Node::Stream) -> void { if(!streams) return; //should never occur - //wait until every stream has pending samples to be mixed - for(auto stream : streams) { - if(!stream->pending()) return; - } - - //mix all streams together - double samples[2] = {0.0, 0.0}; - for(auto& stream : streams) { - double buffer[2]; - uint channels = stream->read(buffer); - if(channels == 1) { - //monaural -> stereo mixing - samples[0] += buffer[0]; - samples[1] += buffer[0]; - } else { - //stereo mixing - samples[0] += buffer[0]; - samples[1] += buffer[1]; + //process all pending frames (there may be more than one waiting) + while(true) { + //only process a frame if all streams have at least one pending frame + for(auto& stream : streams) { + if(!stream->pending()) return; } - } - //apply volume, balance, and clamping to the output samples - double volume = !settings.audio.mute ? settings.audio.volume : 0.0; - double balance = settings.audio.balance; - for(uint c : range(2)) { - samples[c] = max(-1.0, min(+1.0, samples[c] * volume)); - if(balance < 0.0) samples[1] *= 1.0 + balance; - if(balance > 0.0) samples[0] *= 1.0 - balance; - } + //mix all frames together + double samples[2] = {0.0, 0.0}; + for(auto& stream : streams) { + double buffer[2]; + uint channels = stream->read(buffer); + if(channels == 1) { + //monaural -> stereo mixing + samples[0] += buffer[0]; + samples[1] += buffer[0]; + } else { + //stereo mixing + samples[0] += buffer[0]; + samples[1] += buffer[1]; + } + } - //send samples to the audio output device - audioInstance.output(samples); + //apply volume, balance, and clamping to the output frame + double volume = !settings.audio.mute ? settings.audio.volume : 0.0; + double balance = settings.audio.balance; + for(uint c : range(2)) { + samples[c] = max(-1.0, min(+1.0, samples[c] * volume)); + if(balance < 0.0) samples[1] *= 1.0 + balance; + if(balance > 0.0) samples[0] *= 1.0 - balance; + } + + //send frame to the audio output device + audioInstance.output(samples); + } } auto Emulator::input(higan::Node::Input input) -> void { diff --git a/higan/target-higan/menus/help.cpp b/higan/target-higan/menus/help.cpp new file mode 100644 index 000000000..6883c4ce2 --- /dev/null +++ b/higan/target-higan/menus/help.cpp @@ -0,0 +1,19 @@ +HelpMenu::HelpMenu(MenuBar* parent) : Menu(parent) { + setText("Help"); + webpage.setIcon(Icon::Application::Browser).setText("Webpage ...").onActivate([&] { + invoke("https://byuu.org/higan"); + }); + userGuide.setIcon(Icon::Application::Browser).setText("User Guide ...").onActivate([&] { + invoke("https://byuu.org/higan/user-guide"); + }); + about.setIcon(Icon::Prompt::Question).setText("About ...").onActivate([&] { + AboutDialog() + .setLogo(Resource::Logo) + .setVersion(higan::Version) + .setAuthor(higan::Author) + .setLicense(higan::License) + .setWebsite(higan::Website) + .setAlignment(program, {0.5f, program.panelLayout.visible() ? 0.32f : 0.5f}) + .show(); + }); +} diff --git a/higan/target-higan/menus/menus.cpp b/higan/target-higan/menus/menus.cpp index 05af9ac66..0d63f720a 100644 --- a/higan/target-higan/menus/menus.cpp +++ b/higan/target-higan/menus/menus.cpp @@ -3,3 +3,4 @@ #include "system.cpp" #include "settings.cpp" #include "tools.cpp" +#include "help.cpp" diff --git a/higan/target-higan/menus/menus.hpp b/higan/target-higan/menus/menus.hpp index 8760b4741..866388bd0 100644 --- a/higan/target-higan/menus/menus.hpp +++ b/higan/target-higan/menus/menus.hpp @@ -59,7 +59,8 @@ struct ToolsMenu : Menu { struct HelpMenu : Menu { HelpMenu(MenuBar*); - MenuItem documentation{this}; + MenuItem webpage{this}; + MenuItem userGuide{this}; MenuSeparator separator{this}; MenuItem about{this}; }; diff --git a/higan/target-higan/menus/tools.cpp b/higan/target-higan/menus/tools.cpp index 65a5ca834..2ad171612 100644 --- a/higan/target-higan/menus/tools.cpp +++ b/higan/target-higan/menus/tools.cpp @@ -20,22 +20,3 @@ ToolsMenu::ToolsMenu(MenuBar* parent) : Menu(parent) { pauseEmulation.setText("Pause Emulation"); } - -// - -HelpMenu::HelpMenu(MenuBar* parent) : Menu(parent) { - setText("Help"); - documentation.setIcon(Icon::Application::Browser).setText("Documentation ...").onActivate([&] { - invoke("https://doc.byuu.org/higan"); - }); - about.setIcon(Icon::Prompt::Question).setText("About ...").onActivate([&] { - AboutDialog() - .setLogo(Resource::Logo) - .setVersion(higan::Version) - .setAuthor(higan::Author) - .setLicense(higan::License) - .setWebsite(higan::Website) - .setAlignment(program, {0.5f, program.panelLayout.visible() ? 0.32f : 0.5f}) - .show(); - }); -} diff --git a/hiro/windows/widget/label.cpp b/hiro/windows/widget/label.cpp index b833217c0..987162a00 100755 --- a/hiro/windows/widget/label.cpp +++ b/hiro/windows/widget/label.cpp @@ -94,22 +94,6 @@ auto pLabel::windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> ma return msg == WM_ERASEBKGND; } - if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { - switch(msg) { - case WM_LBUTTONDOWN: self().doMousePress(Mouse::Button::Left); break; - case WM_MBUTTONDOWN: self().doMousePress(Mouse::Button::Middle); break; - case WM_RBUTTONDOWN: self().doMousePress(Mouse::Button::Right); break; - } - } - - if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) { - switch(msg) { - case WM_LBUTTONUP: self().doMouseRelease(Mouse::Button::Left); break; - case WM_MBUTTONUP: self().doMouseRelease(Mouse::Button::Middle); break; - case WM_RBUTTONUP: self().doMouseRelease(Mouse::Button::Right); break; - } - } - return pWidget::windowProc(hwnd, msg, wparam, lparam); } diff --git a/icarus/icarus.hpp b/icarus/icarus.hpp index 563d61163..5f8ff8be4 100644 --- a/icarus/icarus.hpp +++ b/icarus/icarus.hpp @@ -6,7 +6,7 @@ using namespace hiro; namespace icarus { static const string Name = "icarus"; - static const string Version = "106.206"; + static const string Version = "106.234"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org"; diff --git a/nall/chrono.hpp b/nall/chrono.hpp index 779075be9..b9c116220 100644 --- a/nall/chrono.hpp +++ b/nall/chrono.hpp @@ -53,6 +53,9 @@ inline auto timestamp() -> uint64_t { inline auto timestamp(const string& datetime) -> uint64_t { static const uint monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; uint64_t timestamp = 0; + if(datetime.match("??????????")) { + return datetime.natural(); + } if(datetime.match("????*")) { uint year = datetime.slice(0, 4).natural(); if(year < 1970 || year > 2199) return 0; diff --git a/nall/dsp/resampler/cubic.hpp b/nall/dsp/resampler/cubic.hpp index 7f0bccfd0..eaa4aafff 100644 --- a/nall/dsp/resampler/cubic.hpp +++ b/nall/dsp/resampler/cubic.hpp @@ -7,6 +7,9 @@ namespace nall::DSP::Resampler { struct Cubic { + inline auto inputFrequency() const -> double { return _inputFrequency; } + inline auto outputFrequency() const -> double { return _outputFrequency; } + inline auto reset(double inputFrequency, double outputFrequency = 0, uint queueSize = 0) -> void; inline auto setInputFrequency(double inputFrequency) -> void; inline auto pending() const -> bool; @@ -15,41 +18,41 @@ struct Cubic { inline auto serialize(serializer&) -> void; private: - double inputFrequency; - double outputFrequency; + double _inputFrequency; + double _outputFrequency; - double ratio; - double fraction; - double history[4]; - queue samples; + double _ratio; + double _fraction; + double _history[4]; + queue _samples; }; auto Cubic::reset(double inputFrequency, double outputFrequency, uint queueSize) -> void { - this->inputFrequency = inputFrequency; - this->outputFrequency = outputFrequency ? outputFrequency : this->inputFrequency; + _inputFrequency = inputFrequency; + _outputFrequency = outputFrequency ? outputFrequency : _inputFrequency; - ratio = inputFrequency / outputFrequency; - fraction = 0.0; - for(auto& sample : history) sample = 0.0; - samples.resize(queueSize ? queueSize : this->outputFrequency * 0.02); //default to 20ms max queue size + _ratio = _inputFrequency / _outputFrequency; + _fraction = 0.0; + for(auto& sample : _history) sample = 0.0; + _samples.resize(queueSize ? queueSize : _outputFrequency * 0.02); //default to 20ms max queue size } auto Cubic::setInputFrequency(double inputFrequency) -> void { - this->inputFrequency = inputFrequency; - ratio = inputFrequency / outputFrequency; + _inputFrequency = inputFrequency; + _ratio = _inputFrequency / _outputFrequency; } auto Cubic::pending() const -> bool { - return samples.pending(); + return _samples.pending(); } auto Cubic::read() -> double { - return samples.read(); + return _samples.read(); } auto Cubic::write(double sample) -> void { - auto& mu = fraction; - auto& s = history; + auto& mu = _fraction; + auto& s = _history; s[0] = s[1]; s[1] = s[2]; @@ -62,20 +65,20 @@ auto Cubic::write(double sample) -> void { double C = s[2] - s[0]; double D = s[1]; - samples.write(A * mu * mu * mu + B * mu * mu + C * mu + D); - mu += ratio; + _samples.write(A * mu * mu * mu + B * mu * mu + C * mu + D); + mu += _ratio; } mu -= 1.0; } auto Cubic::serialize(serializer& s) -> void { - s.real(inputFrequency); - s.real(outputFrequency); - s.real(ratio); - s.real(fraction); - s.array(history); - samples.serialize(s); + s.real(_inputFrequency); + s.real(_outputFrequency); + s.real(_ratio); + s.real(_fraction); + s.array(_history); + _samples.serialize(s); } } diff --git a/nall/encode/wav.hpp b/nall/encode/wav.hpp new file mode 100644 index 000000000..1b4248557 --- /dev/null +++ b/nall/encode/wav.hpp @@ -0,0 +1,52 @@ +#pragma once + +namespace nall::Encode { + +struct WAV { + static auto stereo_16bit(const string& filename, array_view left, array_view right, uint frequency) -> bool { + if(left.size() != right.size()) return false; + static uint channels = 2; + static uint bits = 16; + static uint samples = left.size(); + + file_buffer fp; + if(!fp.open(filename, file::mode::write)) return false; + + fp.write('R'); + fp.write('I'); + fp.write('F'); + fp.write('F'); + fp.writel(4 + (8 + 16) + (8 + samples * 4), 4); + + fp.write('W'); + fp.write('A'); + fp.write('V'); + fp.write('E'); + + fp.write('f'); + fp.write('m'); + fp.write('t'); + fp.write(' '); + fp.writel(16, 4); + fp.writel(1, 2); + fp.writel(channels, 2); + fp.writel(frequency, 4); + fp.writel(frequency * channels * bits, 4); + fp.writel(channels * bits, 2); + fp.writel(bits, 2); + + fp.write('d'); + fp.write('a'); + fp.write('t'); + fp.write('a'); + fp.writel(samples * 4, 4); + for(uint sample : range(samples)) { + fp.writel(left[sample], 2); + fp.writel(right[sample], 2); + } + + return true; + } +}; + +}