diff --git a/higan/audio/audio.cpp b/higan/audio/audio.cpp index 1d532cc1..8fb963a6 100644 --- a/higan/audio/audio.cpp +++ b/higan/audio/audio.cpp @@ -31,6 +31,13 @@ auto Audio::setInterface(Interface* interface) -> void { this->interface = interface; } +auto Audio::setFrequency(double frequency) -> void { + this->frequency = frequency; + for(auto& stream : streams) { + stream->setFrequency(stream->inputFrequency, frequency); + } +} + auto Audio::setVolume(double volume) -> void { this->volume = volume; } diff --git a/higan/audio/audio.hpp b/higan/audio/audio.hpp index 3a390491..ca027f3a 100644 --- a/higan/audio/audio.hpp +++ b/higan/audio/audio.hpp @@ -15,6 +15,7 @@ struct Audio { auto reset(maybe channels = nothing, maybe frequency = nothing) -> void; auto setInterface(Interface* interface) -> void; + auto setFrequency(double frequency) -> void; auto setVolume(double volume) -> void; auto setBalance(double balance) -> void; auto setReverb(bool enabled) -> void; @@ -51,11 +52,13 @@ struct Filter { struct Stream { auto reset(uint channels, double inputFrequency, double outputFrequency) -> void; + auto setFrequency(double inputFrequency, maybe outputFrequency = nothing) -> void; + auto addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes = 1) -> void; auto pending() const -> bool; - auto read(double* samples) -> uint; - auto write(const double* samples) -> void; + auto read(double samples[]) -> uint; + auto write(const double samples[]) -> void; template auto sample(P&&... p) -> void { double samples[sizeof...(P)] = {forward

(p)...}; diff --git a/higan/audio/stream.cpp b/higan/audio/stream.cpp index a84f23bf..7bf23dc2 100644 --- a/higan/audio/stream.cpp +++ b/higan/audio/stream.cpp @@ -11,6 +11,15 @@ auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency } } +auto Stream::setFrequency(double inputFrequency, maybe outputFrequency) -> void { + this->inputFrequency = inputFrequency; + if(outputFrequency) this->outputFrequency = outputFrequency(); + + for(auto& channel : channels) { + channel.resampler.reset(this->inputFrequency, this->outputFrequency); + } +} + auto Stream::addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes) -> void { for(auto& channel : channels) { for(auto pass : range(passes)) { @@ -40,12 +49,12 @@ auto Stream::pending() const -> bool { return channels && channels[0].resampler.pending(); } -auto Stream::read(double* samples) -> uint { +auto Stream::read(double samples[]) -> uint { for(auto c : range(channels)) samples[c] = channels[c].resampler.read(); return channels.size(); } -auto Stream::write(const double* samples) -> void { +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) { diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index f6f63cc9..934545f8 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "103.15"; + static const string Version = "103.16"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/target-tomoko/GNUmakefile b/higan/target-tomoko/GNUmakefile index 110b985d..5506d0da 100644 --- a/higan/target-tomoko/GNUmakefile +++ b/higan/target-tomoko/GNUmakefile @@ -23,15 +23,15 @@ ifeq ($(platform),windows) ruby += input.windows else ifeq ($(platform),macosx) ruby += #video.cgl - ruby += #audio.openal + ruby += audio.openal ruby += #input.quartz input.carbon else ifeq ($(platform),linux) ruby += video.xshm #video.glx video.xv video.xshm video.sdl - ruby += audio.oss #audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao + ruby += audio.oss audio.openal #audio.alsa audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao ruby += input.sdl input.xlib #input.udev input.sdl input.xlib else ifeq ($(platform),bsd) ruby += video.xshm #video.glx video.xv video.xshm video.sdl - ruby += audio.oss #audio.alsa + ruby += audio.oss audio.openal ruby += input.sdl input.xlib endif diff --git a/higan/target-tomoko/configuration/configuration.cpp b/higan/target-tomoko/configuration/configuration.cpp index ce0cc90c..14d736ec 100644 --- a/higan/target-tomoko/configuration/configuration.cpp +++ b/higan/target-tomoko/configuration/configuration.cpp @@ -50,7 +50,6 @@ Settings::Settings() { set("Audio/Volume", 100); set("Audio/Balance", 50); set("Audio/Reverb/Enable", false); - set("Audio/Resampler", "Sinc"); set("Input/Driver", ruby::Input::optimalDriver()); set("Input/Frequency", 5); diff --git a/higan/target-tomoko/presentation/presentation.cpp b/higan/target-tomoko/presentation/presentation.cpp index 72f4b5eb..d41aeaf2 100644 --- a/higan/target-tomoko/presentation/presentation.cpp +++ b/higan/target-tomoko/presentation/presentation.cpp @@ -82,7 +82,7 @@ Presentation::Presentation() { program->updateVideoShader(); }); loadShaders(); - synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).setVisible(false).onToggle([&] { + synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).onToggle([&] { settings["Video/Synchronize"].setValue(synchronizeVideo.checked()); video->setBlocking(synchronizeVideo.checked()); }); @@ -99,20 +99,23 @@ Presentation::Presentation() { statusBar.setVisible(showStatusBar.checked()); if(visible()) resizeViewport(); }); - showConfiguration.setText("Configuration ...").onActivate([&] { - //if no emulation core active; default to hotkeys panel - if(!emulator) return settingsManager->show(3); - - //default to input panel with current core's input settings active - for(auto item : settingsManager->input.emulatorList.items()) { - if(systemMenu.text() == item.text()) { - item.setSelected(); - settingsManager->input.emulatorList.doChange(); - break; + showVideoSettings.setText("Video Settings ...").onActivate([&] { settingsManager->show(0); }); + showAudioSettings.setText("Audio Settings ...").onActivate([&] { settingsManager->show(1); }); + showInputSettings.setText("Input Settings ...").onActivate([&] { + if(emulator) { + //default input panel to current core's input settings + for(auto item : settingsManager->input.emulatorList.items()) { + if(systemMenu.text() == item.text()) { + item.setSelected(); + settingsManager->input.emulatorList.doChange(); + break; + } } } settingsManager->show(2); }); + showHotkeySettings.setText("Hotkey Settings ...").onActivate([&] { settingsManager->show(3); }); + showAdvancedSettings.setText("Advanced Settings ...").onActivate([&] { settingsManager->show(4); }); toolsMenu.setText("Tools").setVisible(false); saveQuickStateMenu.setText("Save Quick State"); diff --git a/higan/target-tomoko/presentation/presentation.hpp b/higan/target-tomoko/presentation/presentation.hpp index c7513be6..f525d57e 100644 --- a/higan/target-tomoko/presentation/presentation.hpp +++ b/higan/target-tomoko/presentation/presentation.hpp @@ -43,8 +43,12 @@ struct Presentation : Window { MenuCheckItem synchronizeAudio{&settingsMenu}; MenuCheckItem muteAudio{&settingsMenu}; MenuCheckItem showStatusBar{&settingsMenu}; - MenuSeparator showConfigurationSeparator{&settingsMenu}; - MenuItem showConfiguration{&settingsMenu}; + MenuSeparator settingsSeparator{&settingsMenu}; + MenuItem showVideoSettings{&settingsMenu}; + MenuItem showAudioSettings{&settingsMenu}; + MenuItem showInputSettings{&settingsMenu}; + MenuItem showHotkeySettings{&settingsMenu}; + MenuItem showAdvancedSettings{&settingsMenu}; Menu toolsMenu{&menuBar}; Menu saveQuickStateMenu{&toolsMenu}; MenuItem saveSlot1{&saveQuickStateMenu}; diff --git a/higan/target-tomoko/program/program.cpp b/higan/target-tomoko/program/program.cpp index b1a4fc5b..3c3255af 100644 --- a/higan/target-tomoko/program/program.cpp +++ b/higan/target-tomoko/program/program.cpp @@ -38,13 +38,12 @@ Program::Program(string_vector args) { video->setContext(presentation->viewport.handle()); video->setBlocking(settings["Video/Synchronize"].boolean()); if(!video->ready()) MessageDialog().setText("Failed to initialize video driver").warning(); - presentation->clearViewport(); audio = Audio::create(settings["Audio/Driver"].text()); audio->setContext(presentation->viewport.handle()); + audio->setDevice(settings["Audio/Device"].text()); audio->setBlocking(settings["Audio/Synchronize"].boolean()); - audio->setFrequency(settings["Audio/Frequency"].natural()); audio->setChannels(2); if(!audio->ready()) MessageDialog().setText("Failed to initialize audio driver").warning(); diff --git a/higan/target-tomoko/program/utility.cpp b/higan/target-tomoko/program/utility.cpp index ec9453b5..cb223aba 100644 --- a/higan/target-tomoko/program/utility.cpp +++ b/higan/target-tomoko/program/utility.cpp @@ -80,8 +80,9 @@ auto Program::updateAudioDriver() -> void { audio->clear(); audio->setDevice(settings["Audio/Device"].text()); audio->setExclusive(settings["Audio/Exclusive"].boolean()); -//audio->setFrequency(settings["Audio/Frequency"].natural()); + audio->setFrequency(settings["Audio/Frequency"].real()); audio->setLatency(settings["Audio/Latency"].natural()); + Emulator::audio.setFrequency(settings["Audio/Frequency"].real()); } auto Program::updateAudioEffects() -> void { diff --git a/higan/target-tomoko/settings/audio.cpp b/higan/target-tomoko/settings/audio.cpp index 5f850fdf..476a01d9 100644 --- a/higan/target-tomoko/settings/audio.cpp +++ b/higan/target-tomoko/settings/audio.cpp @@ -6,33 +6,13 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { driverLabel.setFont(Font().setBold()).setText("Driver Settings"); - auto information = audio->information(); - deviceLabel.setText("Device:"); - for(auto& device : information.devices) { - deviceList.append(ComboButtonItem().setText(device)); - if(device == settings["Audio/Device"].text()) { - deviceList.item(deviceList.itemCount() - 1).setSelected(); - } - } - deviceList.onChange([&] { updateDriver(); }); + deviceList.onChange([&] { updateDriver(); updateDriverLists(); }); frequencyLabel.setText("Frequency:"); - for(auto& frequency : information.frequencies) { - frequencyList.append(ComboButtonItem().setText(frequency)); - if(frequency == settings["Audio/Frequency"].natural()) { - frequencyList.item(frequencyList.itemCount() - 1).setSelected(); - } - } frequencyList.onChange([&] { updateDriver(); }); latencyLabel.setText("Latency:"); - for(auto& latency : information.latencies) { - latencyList.append(ComboButtonItem().setText(latency)); - if(latency == settings["Audio/Latency"].natural()) { - latencyList.item(latencyList.itemCount() - 1).setSelected(); - } - } latencyList.onChange([&] { updateDriver(); }); exclusiveMode.setText("Exclusive mode"); @@ -50,19 +30,24 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { reverbEnable.setText("Reverb").setChecked(settings["Audio/Reverb/Enable"].boolean()).onToggle([&] { updateEffects(); }); - updateDriver(); - updateEffects(); + updateDriverLists(); + updateDriver(true); + updateEffects(true); } -auto AudioSettings::updateDriver() -> void { +//when changing audio drivers, device/frequency/latency values may no longer be valid for new driver +//updateDriverLists() will try to select a match if one is found +//otherwise, this function will force now-invalid settings to the first setting in each list +auto AudioSettings::updateDriver(bool initializing) -> void { settings["Audio/Device"].setValue(deviceList.selected().text()); settings["Audio/Frequency"].setValue(frequencyList.selected().text()); settings["Audio/Latency"].setValue(latencyList.selected().text()); settings["Audio/Exclusive"].setValue(exclusiveMode.checked()); - program->updateAudioDriver(); + + if(!initializing) program->updateAudioDriver(); } -auto AudioSettings::updateEffects() -> void { +auto AudioSettings::updateEffects(bool initializing) -> void { settings["Audio/Volume"].setValue(volumeSlider.position()); volumeValue.setText({volumeSlider.position(), "%"}); @@ -71,5 +56,35 @@ auto AudioSettings::updateEffects() -> void { settings["Audio/Reverb/Enable"].setValue(reverbEnable.checked()); - program->updateAudioEffects(); + if(!initializing) program->updateAudioEffects(); +} + +//called during initialization, and after changing audio device +//each audio device may have separately supported frequencies and/or latencies +auto AudioSettings::updateDriverLists() -> void { + auto information = audio->information(); + + deviceList.reset(); + for(auto& device : information.devices) { + deviceList.append(ComboButtonItem().setText(device)); + if(device == settings["Audio/Device"].text()) { + deviceList.item(deviceList.itemCount() - 1).setSelected(); + } + } + + frequencyList.reset(); + for(auto& frequency : information.frequencies) { + frequencyList.append(ComboButtonItem().setText(frequency)); + if(frequency == settings["Audio/Frequency"].real()) { + frequencyList.item(frequencyList.itemCount() - 1).setSelected(); + } + } + + latencyList.reset(); + for(auto& latency : information.latencies) { + latencyList.append(ComboButtonItem().setText(latency)); + if(latency == settings["Audio/Latency"].natural()) { + latencyList.item(latencyList.itemCount() - 1).setSelected(); + } + } } diff --git a/higan/target-tomoko/settings/settings.hpp b/higan/target-tomoko/settings/settings.hpp index 7910c967..fe9636f3 100644 --- a/higan/target-tomoko/settings/settings.hpp +++ b/higan/target-tomoko/settings/settings.hpp @@ -63,26 +63,13 @@ struct AudioSettings : TabFrameItem { HorizontalSlider balanceSlider{&balanceLayout, Size{~0, 0}}; CheckLabel reverbEnable{&layout, Size{~0, 0}}; - auto updateDriver() -> void; - auto updateEffects() -> void; + auto updateDriver(bool initializing = false) -> void; + auto updateEffects(bool initializing = false) -> void; + auto updateDriverLists() -> void; }; struct InputSettings : TabFrameItem { InputSettings(TabFrame*); - auto updateControls() -> void; - auto activeEmulator() -> InputEmulator&; - auto activePort() -> InputPort&; - auto activeDevice() -> InputDevice&; - auto reloadPorts() -> void; - auto reloadDevices() -> void; - auto reloadMappings() -> void; - auto refreshMappings() -> void; - auto assignMapping() -> void; - auto assignMouseInput(uint id) -> void; - auto inputEvent(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void; - - InputMapping* activeMapping = nullptr; - Timer timer; VerticalLayout layout{this}; HorizontalLayout focusLayout{&layout, Size{~0, 0}}; @@ -101,17 +88,25 @@ struct InputSettings : TabFrameItem { Widget spacer{&controlLayout, Size{~0, 0}}; Button resetButton{&controlLayout, Size{80, 0}}; Button eraseButton{&controlLayout, Size{80, 0}}; + + auto updateControls() -> void; + auto activeEmulator() -> InputEmulator&; + auto activePort() -> InputPort&; + auto activeDevice() -> InputDevice&; + auto reloadPorts() -> void; + auto reloadDevices() -> void; + auto reloadMappings() -> void; + auto refreshMappings() -> void; + auto assignMapping() -> void; + auto assignMouseInput(uint id) -> void; + auto inputEvent(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue, bool allowMouseInput = false) -> void; + + InputMapping* activeMapping = nullptr; + Timer timer; }; struct HotkeySettings : TabFrameItem { HotkeySettings(TabFrame*); - auto reloadMappings() -> void; - auto refreshMappings() -> void; - auto assignMapping() -> void; - auto inputEvent(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue) -> void; - - InputMapping* activeMapping = nullptr; - Timer timer; VerticalLayout layout{this}; TableView mappingList{&layout, Size{~0, ~0}}; @@ -119,6 +114,14 @@ struct HotkeySettings : TabFrameItem { Widget spacer{&controlLayout, Size{~0, 0}}; Button resetButton{&controlLayout, Size{80, 0}}; Button eraseButton{&controlLayout, Size{80, 0}}; + + auto reloadMappings() -> void; + auto refreshMappings() -> void; + auto assignMapping() -> void; + auto inputEvent(shared_pointer device, uint group, uint input, int16 oldValue, int16 newValue) -> void; + + InputMapping* activeMapping = nullptr; + Timer timer; }; struct AdvancedSettings : TabFrameItem { @@ -143,8 +146,6 @@ struct AdvancedSettings : TabFrameItem { struct SettingsManager : Window { SettingsManager(); - auto setVisible(bool visible = true) -> SettingsManager&; - auto show(uint setting) -> void; VerticalLayout layout{this}; TabFrame panel{&layout, Size{~0, ~0}}; @@ -153,8 +154,10 @@ struct SettingsManager : Window { InputSettings input{&panel}; HotkeySettings hotkeys{&panel}; AdvancedSettings advanced{&panel}; - StatusBar statusBar{this}; + + auto setVisible(bool visible = true) -> SettingsManager&; + auto show(uint setting) -> void; }; extern unique_pointer settingsManager; diff --git a/ruby/audio/asio.cpp b/ruby/audio/asio.cpp index b5a5fd48..88f9f9e1 100644 --- a/ruby/audio/asio.cpp +++ b/ruby/audio/asio.cpp @@ -22,9 +22,10 @@ struct AudioASIO : Audio { } auto context() -> uintptr { return _context; } + auto device() -> string { return _device; } auto blocking() -> bool { return _blocking; } auto channels() -> uint { return _channels; } - auto frequency() -> uint { return _frequency; } + auto frequency() -> double { return _frequency; } auto latency() -> uint { return _latency; } auto setContext(uintptr context) -> bool { @@ -33,6 +34,12 @@ struct AudioASIO : Audio { return initialize(); } + auto setDevice(string device) -> bool { + if(_device == device) return true; + _device = device; + return initialize(); + } + auto setBlocking(bool blocking) -> bool { if(_blocking == blocking) return true; _blocking = blocking; @@ -51,18 +58,6 @@ struct AudioASIO : Audio { return initialize(); } - auto output(const double samples[]) -> void { - if(!_ready) return; - if(_blocking) { - while(_queue.count >= _latency); - } - for(uint n : range(_channels)) { - _queue.samples[_queue.write][n] = samples[n]; - } - _queue.write++; - _queue.count++; - } - auto clear() -> void { if(!_ready) return; for(uint n : range(_channels)) { @@ -75,6 +70,18 @@ struct AudioASIO : Audio { _queue.count = 0; } + auto output(const double samples[]) -> void { + if(!_ready) return; + if(_blocking) { + while(_queue.count >= _latency); + } + for(uint n : range(_channels)) { + _queue.samples[_queue.write][n] = samples[n]; + } + _queue.write++; + _queue.count++; + } + private: auto initialize() -> bool { terminate(); @@ -236,7 +243,7 @@ private: string _device; bool _blocking = true; uint _channels = 2; - uint _frequency = 48000; + double _frequency = 48000.0; uint _latency = 0; struct Queue { diff --git a/ruby/audio/openal.cpp b/ruby/audio/openal.cpp index 1895fa90..261a45ba 100644 --- a/ruby/audio/openal.cpp +++ b/ruby/audio/openal.cpp @@ -7,188 +7,188 @@ #endif struct AudioOpenAL : Audio { - ~AudioOpenAL() { term(); } + AudioOpenAL() { initialize(); } + ~AudioOpenAL() { terminate(); } - struct { - ALCdevice* handle = nullptr; - ALCcontext* context = nullptr; - ALuint source = 0; - ALenum format = AL_FORMAT_STEREO16; - unsigned latency = 0; - unsigned queueLength = 0; - } device; + auto ready() -> bool { return _ready; } - struct { - uint32_t* data = nullptr; - unsigned length = 0; - unsigned size = 0; - } buffer; - - struct { - bool synchronize = true; - unsigned frequency = 48000; - unsigned latency = 40; - } settings; - - auto cap(const string& name) -> bool { - if(name == Audio::Synchronize) return true; - if(name == Audio::Frequency) return true; - if(name == Audio::Latency) return true; - return false; + auto information() -> Information { + Information information; + for(auto& device : queryDevices()) information.devices.append(device); + information.channels = {2}; + information.frequencies = {44100.0, 48000.0, 96000.0}; + information.latencies = {20, 40, 60, 80, 100}; + return information; } - auto get(const string& name) -> any { - if(name == Audio::Synchronize) return settings.synchronize; - if(name == Audio::Frequency) return settings.frequency; - if(name == Audio::Latency) return settings.latency; - return {}; + auto device() -> string { return _device; } + auto blocking() -> bool { return _blocking; } + auto channels() -> uint { return _channels; } + auto frequency() -> double { return (double)_frequency; } + auto latency() -> uint { return _latency; } + + auto setDevice(string device) -> bool { + if(_device == device) return true; + _device = device; + return initialize(); } - auto set(const string& name, const any& value) -> bool { - if(name == Audio::Synchronize && value.is()) { - settings.synchronize = value.get(); - return true; - } - - if(name == Audio::Frequency && value.is()) { - settings.frequency = value.get(); - return true; - } - - if(name == Audio::Latency && value.is()) { - if(settings.latency != value.get()) { - settings.latency = value.get(); - updateLatency(); - } - return true; - } - - return false; + auto setBlocking(bool blocking) -> bool { + if(_blocking == blocking) return true; + _blocking = blocking; + return true; } - auto sample(int16_t left, int16_t right) -> void { - buffer.data[buffer.length++] = (uint16_t)left << 0 | (uint16_t)right << 16; - if(buffer.length < buffer.size) return; + auto setFrequency(double frequency) -> bool { + if(_frequency == (uint)frequency) return true; + _frequency = (uint)frequency; + return initialize(); + } - ALuint albuffer = 0; + auto setLatency(uint latency) -> bool { + if(_latency == latency) return true; + _latency = latency; + if(_ready) updateLatency(); + return true; + } + + auto output(const double samples[]) -> void { + _buffer[_bufferLength] = int16_t(samples[0] * 32768.0) << 0; + _buffer[_bufferLength] |= int16_t(samples[1] * 32768.0) << 16; + if(++_bufferLength < _bufferSize) return; + + ALuint alBuffer = 0; int processed = 0; while(true) { - alGetSourcei(device.source, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(_source, AL_BUFFERS_PROCESSED, &processed); while(processed--) { - alSourceUnqueueBuffers(device.source, 1, &albuffer); - alDeleteBuffers(1, &albuffer); - device.queueLength--; + alSourceUnqueueBuffers(_source, 1, &alBuffer); + alDeleteBuffers(1, &alBuffer); + _queueLength--; } //wait for buffer playback to catch up to sample generation if not synchronizing - if(settings.synchronize == false || device.queueLength < 3) break; + if(!_blocking || _queueLength < 3) break; } - if(device.queueLength < 3) { - alGenBuffers(1, &albuffer); - alBufferData(albuffer, device.format, buffer.data, buffer.size * 4, settings.frequency); - alSourceQueueBuffers(device.source, 1, &albuffer); - device.queueLength++; + if(_queueLength < 3) { + alGenBuffers(1, &alBuffer); + alBufferData(alBuffer, _format, _buffer, _bufferSize * 4, _frequency); + alSourceQueueBuffers(_source, 1, &alBuffer); + _queueLength++; } ALint playing; - alGetSourcei(device.source, AL_SOURCE_STATE, &playing); - if(playing != AL_PLAYING) alSourcePlay(device.source); - buffer.length = 0; + alGetSourcei(_source, AL_SOURCE_STATE, &playing); + if(playing != AL_PLAYING) alSourcePlay(_source); + _bufferLength = 0; } - auto clear() -> void { - } +private: + auto initialize() -> bool { + terminate(); - auto init() -> bool { + if(!queryDevices().find(_device)) _device = ""; + _queueLength = 0; updateLatency(); - device.queueLength = 0; bool success = false; - if(device.handle = alcOpenDevice(nullptr)) { - if(device.context = alcCreateContext(device.handle, nullptr)) { - alcMakeContextCurrent(device.context); - alGenSources(1, &device.source); + if(_openAL = alcOpenDevice(_device)) { + if(_context = alcCreateContext(_openAL, nullptr)) { + alcMakeContextCurrent(_context); + alGenSources(1, &_source); - //alSourcef (device.source, AL_PITCH, 1.0); - //alSourcef (device.source, AL_GAIN, 1.0); - //alSource3f(device.source, AL_POSITION, 0.0, 0.0, 0.0); - //alSource3f(device.source, AL_VELOCITY, 0.0, 0.0, 0.0); - //alSource3f(device.source, AL_DIRECTION, 0.0, 0.0, 0.0); - //alSourcef (device.source, AL_ROLLOFF_FACTOR, 0.0); - //alSourcei (device.source, AL_SOURCE_RELATIVE, AL_TRUE); + //alSourcef (_source, AL_PITCH, 1.0); + //alSourcef (_source, AL_GAIN, 1.0); + //alSource3f(_source, AL_POSITION, 0.0, 0.0, 0.0); + //alSource3f(_source, AL_VELOCITY, 0.0, 0.0, 0.0); + //alSource3f(_source, AL_DIRECTION, 0.0, 0.0, 0.0); + //alSourcef (_source, AL_ROLLOFF_FACTOR, 0.0); + //alSourcei (_source, AL_SOURCE_RELATIVE, AL_TRUE); alListener3f(AL_POSITION, 0.0, 0.0, 0.0); alListener3f(AL_VELOCITY, 0.0, 0.0, 0.0); - ALfloat listener_orientation[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; - alListenerfv(AL_ORIENTATION, listener_orientation); + ALfloat listenerOrientation[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + alListenerfv(AL_ORIENTATION, listenerOrientation); success = true; } } - if(success == false) { - term(); - return false; - } - - return true; + if(!success) return terminate(), false; + return _ready = true; } - auto term() -> void { - if(alIsSource(device.source) == AL_TRUE) { + auto terminate() -> void { + _ready = false; + + if(alIsSource(_source) == AL_TRUE) { int playing = 0; - alGetSourcei(device.source, AL_SOURCE_STATE, &playing); + alGetSourcei(_source, AL_SOURCE_STATE, &playing); if(playing == AL_PLAYING) { - alSourceStop(device.source); + alSourceStop(_source); int queued = 0; - alGetSourcei(device.source, AL_BUFFERS_QUEUED, &queued); + alGetSourcei(_source, AL_BUFFERS_QUEUED, &queued); while(queued--) { - ALuint albuffer = 0; - alSourceUnqueueBuffers(device.source, 1, &albuffer); - alDeleteBuffers(1, &albuffer); - device.queueLength--; + ALuint alBuffer = 0; + alSourceUnqueueBuffers(_source, 1, &alBuffer); + alDeleteBuffers(1, &alBuffer); + _queueLength--; } } - alDeleteSources(1, &device.source); - device.source = 0; + alDeleteSources(1, &_source); + _source = 0; } - if(device.context) { + if(_context) { alcMakeContextCurrent(nullptr); - alcDestroyContext(device.context); - device.context = 0; + alcDestroyContext(_context); + _context = nullptr; } - if(device.handle) { - alcCloseDevice(device.handle); - device.handle = 0; + if(_openAL) { + alcCloseDevice(_openAL); + _openAL = nullptr; } - if(buffer.data) { - delete[] buffer.data; - buffer.data = 0; - } + delete[] _buffer; + _buffer = nullptr; } -private: auto queryDevices() -> string_vector { string_vector result; - const char* buffer = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); - if(!buffer) return result; + const char* list = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + if(!list) return result; - while(buffer[0] || buffer[1]) { - result.append(buffer); - while(buffer[0]) buffer++; + while(list[0] || list[1]) { + result.append(list); + while(list[0]) list++; } return result; } auto updateLatency() -> void { - if(buffer.data) delete[] buffer.data; - buffer.size = settings.frequency * settings.latency / 1000.0 + 0.5; - buffer.data = new uint32_t[buffer.size](); + delete[] _buffer; + _bufferSize = _frequency * _latency / 1000.0 + 0.5; + _buffer = new uint32_t[_bufferSize](); } + + bool _ready = false; + string _device; + bool _blocking = true; + uint _channels = 2; + uint _frequency = 48000; + uint _latency = 20; + + ALCdevice* _openAL = nullptr; + ALCcontext* _context = nullptr; + ALuint _source = 0; + ALenum _format = AL_FORMAT_STEREO16; + uint _queueLength = 0; + + uint32_t* _buffer = nullptr; + uint _bufferLength = 0; + uint _bufferSize = 0; }; diff --git a/ruby/audio/oss.cpp b/ruby/audio/oss.cpp index 2e84994f..1932259e 100644 --- a/ruby/audio/oss.cpp +++ b/ruby/audio/oss.cpp @@ -23,7 +23,7 @@ struct AudioOSS : Audio { Information information; information.devices = {"/dev/dsp"}; for(auto& device : directory::files("/dev/", "dsp?*")) information.devices.append(string{"/dev/", device}); - information.frequencies = {44100, 48000, 96000}; + information.frequencies = {44100.0, 48000.0, 96000.0}; information.latencies = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; information.channels = {1, 2}; return information; @@ -32,7 +32,7 @@ struct AudioOSS : Audio { auto device() -> string { return _device; } auto blocking() -> bool { return _blocking; } auto channels() -> uint { return _channels; } - auto frequency() -> uint { return _frequency; } + auto frequency() -> double { return _frequency; } auto latency() -> uint { return _latency; } auto setDevice(string device) -> bool { @@ -54,7 +54,7 @@ struct AudioOSS : Audio { return initialize(); } - auto setFrequency(uint frequency) -> bool { + auto setFrequency(double frequency) -> bool { if(_frequency == frequency) return true; _frequency = frequency; return initialize(); @@ -78,6 +78,10 @@ private: auto initialize() -> bool { terminate(); + if(!information().devices.find(_device)) { + _device = information().devices.left(); + } + _fd = open(_device, O_WRONLY, O_NONBLOCK); if(_fd < 0) return false; @@ -86,9 +90,11 @@ private: //policy: 0 = minimum latency (higher CPU usage); 10 = maximum latency (lower CPU usage) int policy = min(10, _latency); ioctl(_fd, SNDCTL_DSP_POLICY, &policy); - ioctl(_fd, SNDCTL_DSP_CHANNELS, &_channels); + int channels = _channels; + ioctl(_fd, SNDCTL_DSP_CHANNELS, &channels); ioctl(_fd, SNDCTL_DSP_SETFMT, &_format); - ioctl(_fd, SNDCTL_DSP_SPEED, &_frequency); + int frequency = _frequency; + ioctl(_fd, SNDCTL_DSP_SPEED, &frequency); updateBlocking(); return _ready = true; @@ -110,11 +116,11 @@ private: } bool _ready = false; - string _device = "/dev/dsp"; + string _device; bool _blocking = true; - int _channels = 2; - int _frequency = 48000; - int _latency = 2; + uint _channels = 2; + double _frequency = 48000.0; + uint _latency = 2; int _fd = -1; int _format = AFMT_S16_LE; diff --git a/ruby/audio/wasapi.cpp b/ruby/audio/wasapi.cpp index a7b9c895..59decb3a 100644 --- a/ruby/audio/wasapi.cpp +++ b/ruby/audio/wasapi.cpp @@ -6,157 +6,161 @@ #include struct AudioWASAPI : Audio { - ~AudioWASAPI() { term(); } + AudioWASAPI() { initialize(); } + ~AudioWASAPI() { terminate(); } - struct { - bool exclusive = false; - uint latency = 80; - bool synchronize = true; - } settings; + auto ready() -> bool { return _ready; } - struct { - uint channels = 0; - uint frequency = 0; - uint mode = 0; - uint precision = 0; - } device; - - auto cap(const string& name) -> bool { - if(name == Audio::Exclusive) return true; - if(name == Audio::Latency) return true; - if(name == Audio::Synchronize) return true; - if(name == Audio::Frequency) return true; - return false; + auto information() -> Information { + Information information; + information.devices = {"Default"}; + information.channels = {2}; + information.frequencies = {}; + information.latencies = {20, 40, 60, 80, 100}; + return information; } - auto get(const string& name) -> any { - if(name == Audio::Exclusive) return settings.exclusive; - if(name == Audio::Latency) return settings.latency; - if(name == Audio::Synchronize) return settings.synchronize; - if(name == Audio::Frequency) return device.frequency; - return {}; + auto exclusive() -> bool { return _exclusive; } + auto blocking() -> bool { return _blocking; } + auto channels() -> uint { return _channels; } + auto frequency() -> double { return (double)_frequency; } + auto latency() -> uint { return _latency; } + + auto setExclusive(bool exclusive) -> bool { + if(_exclusive == exclusive) return true; + _exclusive = exclusive; + return initialize(); } - auto set(const string& name, const any& value) -> bool { - if(name == Audio::Exclusive && value.get()) { - if(audioDevice) term(), init(); - settings.exclusive = value.get(); - return true; - } - - if(name == Audio::Latency && value.get()) { - if(audioDevice) term(), init(); - settings.latency = value.get(); - return true; - } - - if(name == Audio::Synchronize && value.is()) { - settings.synchronize = value.get(); - return true; - } - - return false; + auto setBlocking(bool blocking) -> bool { + if(_blocking == blocking) return true; + _blocking = blocking; + return true; } - auto sample(int16_t left, int16_t right) -> void { - queuedFrames.append((uint16_t)left << 0 | (uint16_t)right << 16); + auto setFrequency(double frequency) -> bool { + if(_frequency == frequency) return true; + _frequency = frequency; + return initialize(); + } - if(!available() && queuedFrames.size() >= bufferSize) { - if(settings.synchronize) while(!available()); //wait for free sample slot - else queuedFrames.takeLeft(); //drop sample (run ahead) + auto setLatency(uint latency) -> bool { + if(_latency == latency) return true; + _latency = latency; + return initialize(); + } + + auto clear() -> void { + _audioClient->Stop(); + _audioClient->Reset(); + for(auto n : range(available())) write(0, 0); + _audioClient->Start(); + } + + auto output(const double samples[]) -> void { + _queuedFrames.append(int16_t(samples[0] * 32768.0) << 0 | int16_t(samples[1] * 32768.0) << 16); + + if(!available() && _queuedFrames.size() >= _bufferSize) { + if(_blocking) { + while(!available()); //wait for free sample slot + } else { + _queuedFrames.takeLeft(); //drop sample (run ahead) + } } uint32_t cachedFrame = 0; for(auto n : range(available())) { - if(queuedFrames) cachedFrame = queuedFrames.takeLeft(); + if(_queuedFrames) cachedFrame = _queuedFrames.takeLeft(); write(cachedFrame >> 0, cachedFrame >> 16); } } - auto clear() -> void { - audioClient->Stop(); - audioClient->Reset(); - for(auto n : range(available())) write(0, 0); - audioClient->Start(); - } +private: + auto initialize() -> bool { + if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&_enumerator) != S_OK) return false; + if(_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &_audioDevice) != S_OK) return false; + if(_audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&_audioClient) != S_OK) return false; - auto init() -> bool { - if(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&enumerator) != S_OK) return false; - if(enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &audioDevice) != S_OK) return false; - if(audioDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&audioClient) != S_OK) return false; - - if(settings.exclusive) { - if(audioDevice->OpenPropertyStore(STGM_READ, &propertyStore) != S_OK) return false; - if(propertyStore->GetValue(PKEY_AudioEngine_DeviceFormat, &propVariant) != S_OK) return false; - waveFormat = (WAVEFORMATEX*)propVariant.blob.pBlobData; - if(audioClient->GetDevicePeriod(nullptr, &devicePeriod) != S_OK) return false; - auto latency = max(devicePeriod, (REFERENCE_TIME)settings.latency * 10'000); //1ms to 100ns units - if(audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, 0, latency, latency, waveFormat, nullptr) != S_OK) return false; + if(_exclusive) { + if(_audioDevice->OpenPropertyStore(STGM_READ, &_propertyStore) != S_OK) return false; + if(_propertyStore->GetValue(PKEY_AudioEngine_DeviceFormat, &_propVariant) != S_OK) return false; + _waveFormat = (WAVEFORMATEX*)_propVariant.blob.pBlobData; + if(_audioClient->GetDevicePeriod(nullptr, &_devicePeriod) != S_OK) return false; + auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units + if(_audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, 0, latency, latency, _waveFormat, nullptr) != S_OK) return false; DWORD taskIndex = 0; - taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex); + _taskHandle = AvSetMmThreadCharacteristics(L"Pro Audio", &taskIndex); } else { - if(audioClient->GetMixFormat(&waveFormat) != S_OK) return false; - if(audioClient->GetDevicePeriod(&devicePeriod, nullptr)) return false; - auto latency = max(devicePeriod, (REFERENCE_TIME)settings.latency * 10'000); //1ms to 100ns units - if(audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, latency, 0, waveFormat, nullptr) != S_OK) return false; + if(_audioClient->GetMixFormat(&waveFormat) != S_OK) return false; + if(_audioClient->GetDevicePeriod(&_devicePeriod, nullptr)) return false; + auto latency = max(_devicePeriod, (REFERENCE_TIME)_latency * 10'000); //1ms to 100ns units + if(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, latency, 0, _waveFormat, nullptr) != S_OK) return false; } - if(audioClient->GetService(IID_IAudioRenderClient, (void**)&renderClient) != S_OK) return false; - if(audioClient->GetBufferSize(&bufferSize) != S_OK) return false; + if(_audioClient->GetService(IID_IAudioRenderClient, (void**)&_renderClient) != S_OK) return false; + if(_audioClient->GetBufferSize(&_bufferSize) != S_OK) return false; - device.channels = waveFormat->nChannels; - device.frequency = waveFormat->nSamplesPerSec; - device.mode = ((WAVEFORMATEXTENSIBLE*)waveFormat)->SubFormat.Data1; - device.precision = waveFormat->wBitsPerSample; + _channels = waveFormat->nChannels; + _frequency = waveFormat->nSamplesPerSec; + _mode = ((WAVEFORMATEXTENSIBLE*)_waveFormat)->SubFormat.Data1; + _precision = _waveFormat->wBitsPerSample; - audioClient->Start(); - return true; + _audioClient->Start(); + return _ready = true; } - auto term() -> void { - if(audioClient) audioClient->Stop(); - if(renderClient) renderClient->Release(), renderClient = nullptr; - if(waveFormat) CoTaskMemFree(waveFormat), waveFormat = nullptr; - if(audioClient) audioClient->Release(), audioClient = nullptr; - if(audioDevice) audioDevice->Release(), audioDevice = nullptr; - if(taskHandle) AvRevertMmThreadCharacteristics(taskHandle), taskHandle = nullptr; + auto terminate() -> void { + if(_audioClient) _audioClient->Stop(); + if(_renderClient) _renderClient->Release(), _renderClient = nullptr; + if(_waveFormat) CoTaskMemFree(_waveFormat), _waveFormat = nullptr; + if(_audioClient) _audioClient->Release(), _audioClient = nullptr; + if(_audioDevice) _audioDevice->Release(), _audioDevice = nullptr; + if(_taskHandle) AvRevertMmThreadCharacteristics(_taskHandle), _taskHandle = nullptr; } -private: auto available() -> uint { uint32_t padding = 0; - audioClient->GetCurrentPadding(&padding); + _audioClient->GetCurrentPadding(&padding); return bufferSize - padding; } auto write(int16_t left, int16_t right) -> void { - if(renderClient->GetBuffer(1, &bufferData) != S_OK) return; + if(_renderClient->GetBuffer(1, &_bufferData) != S_OK) return; - if(device.channels >= 2 && device.mode == 1 && device.precision == 16) { - auto buffer = (int16_t*)bufferData; + if(_channels >= 2 && _mode == 1 && _precision == 16) { + auto buffer = (int16_t*)_bufferData; buffer[0] = left; buffer[1] = right; } - if(device.channels >= 2 && device.mode == 3 && device.precision == 32) { - auto buffer = (float*)bufferData; - buffer[0] = left / 32768.0; + if(_channels >= 2 && _mode == 3 && _precision == 32) { + auto buffer = (float*)_bufferData; + buffer[0] = left / 32768.0; buffer[1] = right / 32768.0; } - renderClient->ReleaseBuffer(1, 0); + _renderClient->ReleaseBuffer(1, 0); } - IMMDeviceEnumerator* enumerator = nullptr; - IMMDevice* audioDevice = nullptr; - IPropertyStore* propertyStore = nullptr; - IAudioClient* audioClient = nullptr; - IAudioRenderClient* renderClient = nullptr; - WAVEFORMATEX* waveFormat = nullptr; - PROPVARIANT propVariant; - HANDLE taskHandle = nullptr; - REFERENCE_TIME devicePeriod = 0; - uint32_t bufferSize = 0; //in frames - uint8_t* bufferData = nullptr; - vector queuedFrames; + bool _exclusive = false; + bool _blocking = true; + uint _channels = 2; + uint _frequency = 48000; + uint _latency = 20; + + uint _mode = 0; + uint _precision = 0; + + IMMDeviceEnumerator* _enumerator = nullptr; + IMMDevice* _audioDevice = nullptr; + IPropertyStore* _propertyStore = nullptr; + IAudioClient* _audioClient = nullptr; + IAudioRenderClient* _renderClient = nullptr; + WAVEFORMATEX* _waveFormat = nullptr; + PROPVARIANT _propVariant; + HANDLE _taskHandle = nullptr; + REFERENCE_TIME _devicePeriod = 0; + uint32_t _bufferSize = 0; //in frames + uint8_t* _bufferData = nullptr; + vector _queuedFrames; }; diff --git a/ruby/ruby.hpp b/ruby/ruby.hpp index 4d798883..1d48ba57 100644 --- a/ruby/ruby.hpp +++ b/ruby/ruby.hpp @@ -54,8 +54,8 @@ struct Audio { static auto availableDrivers() -> nall::string_vector; struct Information { - nall::vector devices; - nall::vector frequencies; + nall::string_vector devices; + nall::vector frequencies; nall::vector latencies; nall::vector channels; }; @@ -63,14 +63,14 @@ struct Audio { virtual ~Audio() = default; virtual auto ready() -> bool { return true; } - virtual auto information() -> Information { return {{"None"}, {48000}, {0}, {2}}; } + virtual auto information() -> Information { return {{"None"}, {48000.0}, {0}, {2}}; } virtual auto exclusive() -> bool { return false; } virtual auto context() -> uintptr { return 0; } virtual auto device() -> nall::string { return "None"; } virtual auto blocking() -> bool { return false; } virtual auto channels() -> uint { return 2; } - virtual auto frequency() -> uint { return 48000; } + virtual auto frequency() -> double { return 48000.0; } virtual auto latency() -> uint { return 0; } virtual auto setExclusive(bool exclusive) -> bool { return false; } @@ -78,7 +78,7 @@ struct Audio { virtual auto setDevice(nall::string device) -> bool { return false; } virtual auto setBlocking(bool blocking) -> bool { return false; } virtual auto setChannels(uint channels) -> bool { return false; } - virtual auto setFrequency(uint frequency) -> bool { return false; } + virtual auto setFrequency(double frequency) -> bool { return false; } virtual auto setLatency(uint latency) -> bool { return false; } virtual auto clear() -> void {}