From e1223366a78182b250bfaf0b3194480d8c3c553f Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 28 Jul 2017 21:42:24 +1000 Subject: [PATCH] Update to v103r22 release. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit byuu says: Changelog: - ruby: ported all remaining drivers to new API¹ - ruby/wasapi: fix for dropping one sample per period [SuperMikeMan] - gb: emulated most of the TAMA RTC; but RTC state is still volatile² ¹: the new ports are: - audio/{directsound, alsa, pulseaudio, pulseaudiosimple, ao} - input/{udev, quartz, carbon} It's pretty much guaranteed many of them will have compilation errors. Please paste the error logs and I'll try to fix them up. It may take a WIP or two to get there. It's also possible things broke from the updates. If so, I could use help comparing the old file to the new file, looking for mistakes, since I can't test on these platforms apart from audio/directsound. Please report working drivers in this list, so we can mark them off the list. I'll need both macOS and Linux testers. audio/directsound.cpp:112:    if(DirectSoundCreate(0, &_interface, 0) != DS_OK) return terminate(), false; ²: once I get this working, I'll add load/save support for the RTC values. For now, the RTC data will be lost when you close the emulator. Right now, you can set the date/time in real-time mode, and when you start the game, the time will be correct, and the time will tick forward. Note that it runs off emulated time instead of actual real time, so if you fast-forward to 300%, one minute will be 20 seconds. The really big limitation right now is that when you exit the game, and restart it, and resume a new game, the hour spot gets corrupted, and this seems to instantly kill your pet. Fun. This is crazy because the commands the game sends to the TAMA interface are identical between starting a new game and getting in-game versus loading a game. It's likely going to require disassembling the game's code and seeing what in the hell it's doing, but I am extremely bad at LR35092 assembly. Hopefully endrift can help here :| --- higan/emulator/emulator.hpp | 2 +- higan/gb/cartridge/tama/tama.cpp | 156 ++++++++++++++--- higan/gb/cartridge/tama/tama.hpp | 16 +- higan/target-tomoko/GNUmakefile | 8 +- ruby/audio/alsa.cpp | 275 +++++++++++------------------- ruby/audio/ao.cpp | 99 +++++------ ruby/audio/asio.cpp | 20 ++- ruby/audio/directsound.cpp | 276 +++++++++++++++---------------- ruby/audio/oss.cpp | 2 +- ruby/audio/pulseaudio.cpp | 224 ++++++++++++------------- ruby/audio/pulseaudiosimple.cpp | 117 +++++++------ ruby/audio/wasapi.cpp | 24 ++- ruby/input/carbon.cpp | 37 ++--- ruby/input/joypad/udev.cpp | 52 +++--- ruby/input/keyboard/carbon.cpp | 4 +- ruby/input/keyboard/quartz.cpp | 4 +- ruby/input/quartz.cpp | 38 ++--- ruby/input/sdl.cpp | 1 + ruby/input/udev.cpp | 88 +++++----- 19 files changed, 728 insertions(+), 715 deletions(-) diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index a1e997a2..2c9a5648 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.21"; + static const string Version = "103.22"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/gb/cartridge/tama/tama.cpp b/higan/gb/cartridge/tama/tama.cpp index 199bfac6..c6ddf7ba 100644 --- a/higan/gb/cartridge/tama/tama.cpp +++ b/higan/gb/cartridge/tama/tama.cpp @@ -7,6 +7,43 @@ //as such, high level emulation is used as a necessary evil auto Cartridge::TAMA::second() -> void { + if(++rtc.second >= 60) { + rtc.second = 0; + + if(++rtc.minute >= 60) { + rtc.minute = 0; + + if(rtc.hourMode == 0 && ++rtc.hour >= 12) { + rtc.hour = 0; + rtc.meridian++; + } + + if(rtc.hourMode == 1 && ++rtc.hour >= 24) { + rtc.hour = 0; + rtc.meridian = rtc.hour >= 12; + } + + if((rtc.hourMode == 0 && rtc.hour == 0 && rtc.meridian == 0) + || (rtc.hourMode == 1 && rtc.hour == 0) + ) { + uint days[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; + if(rtc.leapYear == 0) days[1] = 29; //extra day in February for leap years + + if(++rtc.day > days[(rtc.month - 1) % 12]) { + rtc.day = 1; + + if(++rtc.month > 12) { + rtc.month = 1; + rtc.leapYear++; + + if(++rtc.year >= 100) { + rtc.year = 0; + } + } + } + } + } + } } auto Cartridge::TAMA::read(uint16 address) -> uint8 { @@ -23,12 +60,30 @@ auto Cartridge::TAMA::read(uint16 address) -> uint8 { return 0xf0 | io.ready; } - if(io.select == 0x0c) { - return 0xf0 | io.output.bits(0,3); + if(io.mode == 0 || io.mode == 1) { + if(io.select == 0x0c) { + return 0xf0 | io.output.bits(0,3); + } + + if(io.select == 0x0d) { + return 0xf0 | io.output.bits(4,7); + } } - if(io.select == 0x0d) { - return 0xf0 | io.output.bits(4,7); + if(io.mode == 2 || io.mode == 4) { + if(io.select == 0x0c || io.select == 0x0d) { + uint4 data; + if(rtc.index == 0) data = rtc.minute % 10; + if(rtc.index == 1) data = rtc.minute / 10; + if(rtc.index == 2) data = rtc.hour % 10; + if(rtc.index == 3) data = rtc.hour / 10; + if(rtc.index == 4) data = rtc.day / 10; + if(rtc.index == 5) data = rtc.day % 10; + if(rtc.index == 6) data = rtc.month / 10; + if(rtc.index == 7) data = rtc.month % 10; + rtc.index++; + return 0xf0 | data; + } } return 0xff; @@ -42,6 +97,9 @@ auto Cartridge::TAMA::read(uint16 address) -> uint8 { } auto Cartridge::TAMA::write(uint16 address, uint8 data) -> void { + auto toBCD = [](uint8 data) -> uint8 { return (data / 10) * 16 + (data % 10); }; + auto fromBCD = [](uint8 data) -> uint8 { return (data / 16) * 10 + (data % 16); }; + if((address & 0xe001) == 0xa000) { //$a000-bfff (even) if(io.select == 0x00) { io.rom.bank.bits(0,3) = data.bits(0,3); @@ -74,6 +132,64 @@ auto Cartridge::TAMA::write(uint16 address, uint8 data) -> void { if(io.mode == 1) { io.output = cartridge.ram.read(io.index); } + + if(io.mode == 2 && io.index == 0x04) { + rtc.minute = fromBCD(io.input); + } + + if(io.mode == 2 && io.index == 0x05) { + rtc.hour = fromBCD(io.input); + rtc.meridian = rtc.hour >= 12; + } + + if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0x7) { + uint8 day = toBCD(rtc.day); + day.bits(0,3) = io.input.bits(4,7); + rtc.day = fromBCD(day); + } + + if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0x8) { + uint8 day = toBCD(rtc.day); + day.bits(4,7) = io.input.bits(4,7); + rtc.day = fromBCD(day); + } + + if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0x9) { + uint8 month = toBCD(rtc.month); + month.bits(0,3) = io.input.bits(4,7); + rtc.month = fromBCD(month); + } + + if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0xa) { + uint8 month = toBCD(rtc.month); + month.bits(4,7) = io.input.bits(4,7); + rtc.month = fromBCD(month); + } + + if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0xb) { + uint8 year = toBCD(rtc.year); + year.bits(0,3) = io.input.bits(4,7); + rtc.year = fromBCD(year); + } + + if(io.mode == 4 && io.index == 0x00 && io.input.bits(0,3) == 0xc) { + uint8 year = toBCD(rtc.year); + year.bits(4,7) = io.input.bits(4,7); + rtc.year = fromBCD(year); + } + + if(io.mode == 4 && io.index == 0x02 && io.input.bits(0,3) == 0xa) { + rtc.hourMode = io.input.bit(4); + rtc.second = 0; //hack: unclear where this is really being set (if it is at all) + } + + if(io.mode == 4 && io.index == 0x02 && io.input.bits(0,3) == 0xe) { + rtc.test = io.input.bits(4,7); + } + + if(io.mode == 2 && io.index == 0x06) { + rtc.index = 0; + } } return; @@ -90,27 +206,6 @@ auto Cartridge::TAMA::write(uint16 address, uint8 data) -> void { } } -auto Cartridge::TAMA::readRTC(uint1 page, uint4 address) -> uint4 { - if(address >= 13) return 0xf; - auto ram = cartridge.rtc.read(page * 13 + address.bits(1,3)); - if(!address.bit(0)) { - return ram.bits(0,3); - } else { - return ram.bits(4,7); - } -} - -auto Cartridge::TAMA::writeRTC(uint1 page, uint4 address, uint4 data) -> void { - if(address >= 13) return; - auto ram = cartridge.rtc.read(page * 13 + address.bits(1,3)); - if(!address.bit(0)) { - ram.bits(0,3) = data; - } else { - ram.bits(4,7) = data; - } - cartridge.rtc.write(page * 13 + address.bits(1,3), ram); -} - auto Cartridge::TAMA::power() -> void { io = {}; } @@ -123,4 +218,15 @@ auto Cartridge::TAMA::serialize(serializer& s) -> void { s.integer(io.input); s.integer(io.output); s.integer(io.rom.bank); + + s.integer(rtc.year); + s.integer(rtc.month); + s.integer(rtc.day); + s.integer(rtc.hour); + s.integer(rtc.minute); + s.integer(rtc.second); + s.integer(rtc.meridian); + s.integer(rtc.leapYear); + s.integer(rtc.hourMode); + s.integer(rtc.test); } diff --git a/higan/gb/cartridge/tama/tama.hpp b/higan/gb/cartridge/tama/tama.hpp index 28643063..09244445 100644 --- a/higan/gb/cartridge/tama/tama.hpp +++ b/higan/gb/cartridge/tama/tama.hpp @@ -2,8 +2,6 @@ struct TAMA : Mapper { auto second() -> void; auto read(uint16 address) -> uint8; auto write(uint16 address, uint8 data) -> void; - auto readRTC(uint1 page, uint4 address) -> uint4; - auto writeRTC(uint1 page, uint4 address, uint4 data) -> void; auto power() -> void; auto serialize(serializer&) -> void; @@ -18,4 +16,18 @@ struct TAMA : Mapper { uint5 bank; } rom; } io; + + struct RTC { + uint8 year; //0 - 99 + uint8 month; //1 - 12 + uint8 day; //1 - 31 + uint8 hour; //0 - 23 + uint8 minute; //0 - 59 + uint8 second; //0 - 59 + uint1 meridian; //0 = AM; 1 = PM + uint2 leapYear; //0 = leap year; 1-3 = non-leap year + uint1 hourMode; //0 = 12-hour; 1 = 24-hour + uint4 test; + uint8 index; + } rtc; } tama; diff --git a/higan/target-tomoko/GNUmakefile b/higan/target-tomoko/GNUmakefile index 0ea2f9bd..e3d8683b 100644 --- a/higan/target-tomoko/GNUmakefile +++ b/higan/target-tomoko/GNUmakefile @@ -19,16 +19,16 @@ ui_objects += $(if $(call streq,$(platform),windows),ui-resource) # platform ifeq ($(platform),windows) ruby += video.wgl video.direct3d video.directdraw video.gdi - ruby += audio.asio audio.wasapi audio.xaudio2 #audio.directsound + ruby += audio.asio audio.wasapi audio.xaudio2 audio.directsound ruby += input.windows else ifeq ($(platform),macosx) ruby += video.cgl ruby += audio.openal - ruby += #input.quartz input.carbon + ruby += input.quartz input.carbon else ifeq ($(platform),linux) ruby += video.glx video.xvideo video.xshm video.sdl - ruby += audio.oss audio.openal #audio.alsa audio.pulseaudio audio.pulseaudiosimple audio.ao - ruby += input.sdl input.xlib #input.udev + ruby += audio.oss audio.alsa audio.openal audio.pulseaudio audio.pulseaudiosimple audio.ao + ruby += input.sdl input.xlib input.udev else ifeq ($(platform),bsd) ruby += video.glx video.xvideo video.xshm video.sdl ruby += audio.oss audio.openal diff --git a/ruby/audio/alsa.cpp b/ruby/audio/alsa.cpp index cc56a4f2..dfbb581c 100644 --- a/ruby/audio/alsa.cpp +++ b/ruby/audio/alsa.cpp @@ -1,212 +1,139 @@ #include struct AudioALSA : Audio { - ~AudioALSA() { term(); } + AudioALSA() { initialize(); } + ~AudioALSA() { terminate(); } - struct { - snd_pcm_t* handle = nullptr; - snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; - snd_pcm_uframes_t buffer_size; - snd_pcm_uframes_t period_size; - int channels = 2; - const char* name = "default"; - } device; + auto ready() -> bool { return _ready; } - struct { - uint32_t* data = nullptr; - unsigned length = 0; - } buffer; + auto blocking() -> bool { return _blocking; } + auto channels() -> uint { return 2; } + auto frequency() -> double { return _frequency; } + auto latency() -> uint { return _latency; } - struct { - bool synchronize = false; - unsigned frequency = 48000; - unsigned latency = 60; - } 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 setBlocking(bool blocking) -> bool { + if(_blocking == blocking) return true; + _blocking = blocking; + return true; } - 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 setFrequency(double frequency) -> bool { + if(_frequency == frequency) return true; + _frequency = frequency; + return initialize(); } - auto set(const string& name, const any& value) -> bool { - if(name == Audio::Synchronize && value.is()) { - if(settings.synchronize != value.get()) { - settings.synchronize = value.get(); - if(device.handle) init(); - } - return true; - } - - if(name == Audio::Frequency && value.is()) { - if(settings.frequency != value.get()) { - settings.frequency = value.get(); - if(device.handle) init(); - } - return true; - } - - if(name == Audio::Latency && value.is()) { - if(settings.latency != value.get()) { - settings.latency = value.get(); - if(device.handle) init(); - } - return true; - } - - return false; + auto setLatency(uint latency) -> bool { + if(_latency == latency) return true; + _latency = latency; + return initialize(); } - auto sample(int16_t left, int16_t right) -> void { - if(!device.handle) return; + auto output(const double samples[]) -> void { + if(!ready()) return; - buffer.data[buffer.length++] = (uint16_t)left << 0 | (uint16_t)right << 16; - if(buffer.length < device.period_size) return; + _buffer[_offset++] = uint16_t(samples[0] * 32768.0) << 0 | uint16_t(samples[1] * 32768.0) << 16; + if(_offset < _periodSize) return; - snd_pcm_sframes_t avail; + snd_pcm_sframes_t available; do { - avail = snd_pcm_avail_update(device.handle); - if(avail < 0) snd_pcm_recover(device.handle, avail, 1); - if(avail < buffer.length) { - if(settings.synchronize == false) { - buffer.length = 0; + available = snd_pcm_avail_update(_interface); + if(available < 0) snd_pcm_recover(_interface, available, 1); + if(available < _offset) { + if(!_blocking) { + _offset = 0; return; } - int error = snd_pcm_wait(device.handle, -1); - if(error < 0) snd_pcm_recover(device.handle, error, 1); + int error = snd_pcm_wait(_interface, -1); + if(error < 0) snd_pcm_recover(_interface, error, 1); } - } while(avail < buffer.length); + } while(available < _offset); - //below code has issues with PulseAudio sound server - #if 0 - if(settings.synchronize == false) { - snd_pcm_sframes_t avail = snd_pcm_avail_update(device.handle); - if(avail < device.period_size) { - buffer.length = 0; - return; - } - } - #endif - - uint32_t* buffer_ptr = buffer.data; + uint32_t* output = _buffer; int i = 4; - while((buffer.length > 0) && i--) { - snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length); + while(_offset > 0 && i--) { + snd_pcm_sframes_t written = snd_pcm_writei(_interface, output, _offset); if(written < 0) { //no samples written - snd_pcm_recover(device.handle, written, 1); - } else if(written <= buffer.length) { - buffer.length -= written; - buffer_ptr += written; + snd_pcm_recover(_interface, written, 1); + } else if(written <= _offset) { + _offset -= written; + output += written; } } if(i < 0) { - if(buffer.data == buffer_ptr) { - buffer.length--; - buffer_ptr++; + if(buffer == output) { + _offset--; + output++; } - memmove(buffer.data, buffer_ptr, buffer.length * sizeof(uint32_t)); + memory::move(buffer, output, _offset * sizeof(uint32_t)); } } - auto clear() -> void { +private: + auto initialize() -> bool { + terminate(); + + if(snd_pcm_open(&_interface, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) return terminate(), false; + + uint rate = (uint)_frequency; + uint bufferTime = _latency * 1000; + uint periodTime = _latency * 1000 / 4; + + snd_pcm_hw_params_t* hardwareParameters; + snd_pcm_hw_params_alloca(&hardwareParameters); + if(snd_pcm_hw_params_any(_interface, hardwareParameters) < 0) return terminate(), false; + + if(snd_pcm_hw_params_set_access(_interface, hardwareParameters, SND_PCM_ACCESS_RW_INTERLEAVED) < 0 + || snd_pcm_hw_params_set_format(_interface, hardwareParameters, SND_PCM_FORMAT_S16_LE) < 0 + || snd_pcm_hw_params_set_channels(_interface, hardwareParameters, 2) < 0 + || snd_pcm_hw_params_set_rate_near(_interface, hardwareParameters, &rate, 0) < 0 + || snd_pcm_hw_params_set_period_time_near(_interface, hardwareParameters, &periodTime, 0) < 0 + || snd_pcm_hw_params_set_buffer_time_near(_interface, hardwareParameters, &bufferTime, 0) < 0 + ) return terminate(), false; + + if(snd_pcm_hw_params(_interface, hardwareParameters) < 0) return terminate(), false; + if(snd_pcm_get_params(_interface, &_bufferSize, &_periodSize) < 0) return terminate(), false; + + snd_pcm_sw_params_t* softwareParameters; + snd_pcm_sw_params_alloca(&softwareParameters); + if(snd_pcm_sw_params_current(_interface, softwareParameters) < 0) return terminate(), false; + if(snd_pcm_sw_params_set_start_threshold(_interface, softwareParameters, + (_bufferSize / _periodSize) * _periodSize) < 0 + ) return terminate(), false; + if(snd_pcm_sw_params(_interface, softwareParameters) < 0) return terminate(), false; + + _buffer = new uint32_t[_periodSize](); + _offset = 0; + return _ready = true; } - auto init() -> bool { - if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) { - term(); - return false; + auto terminate() -> void { + _ready = false; + + if(_interface) { + //snd_pcm_drain(_interface); //prevents popping noise; but causes multi-second lag + snd_pcm_close(_interface); + _interface = nullptr; } - //below code will not work with 24khz frequency rate (ALSA library bug) - #if 0 - if(snd_pcm_set_params(device.handle, device.format, SND_PCM_ACCESS_RW_INTERLEAVED, - device.channels, settings.frequency, 1, settings.latency * 1000) < 0) { - //failed to set device parameters - term(); - return false; - } - - if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) { - device.period_size = settings.latency * 1000 * 1e-6 * settings.frequency / 4; - } - #endif - - snd_pcm_hw_params_t* hwparams; - snd_pcm_sw_params_t* swparams; - unsigned rate = settings.frequency; - unsigned buffer_time = settings.latency * 1000; - unsigned period_time = settings.latency * 1000 / 4; - - snd_pcm_hw_params_alloca(&hwparams); - if(snd_pcm_hw_params_any(device.handle, hwparams) < 0) { - term(); - return false; - } - - if(snd_pcm_hw_params_set_access(device.handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0 - || snd_pcm_hw_params_set_format(device.handle, hwparams, device.format) < 0 - || snd_pcm_hw_params_set_channels(device.handle, hwparams, device.channels) < 0 - || snd_pcm_hw_params_set_rate_near(device.handle, hwparams, &rate, 0) < 0 - || snd_pcm_hw_params_set_period_time_near(device.handle, hwparams, &period_time, 0) < 0 - || snd_pcm_hw_params_set_buffer_time_near(device.handle, hwparams, &buffer_time, 0) < 0 - ) { - term(); - return false; - } - - if(snd_pcm_hw_params(device.handle, hwparams) < 0) { - term(); - return false; - } - - if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) { - term(); - return false; - } - - snd_pcm_sw_params_alloca(&swparams); - if(snd_pcm_sw_params_current(device.handle, swparams) < 0) { - term(); - return false; - } - - if(snd_pcm_sw_params_set_start_threshold(device.handle, swparams, - (device.buffer_size / device.period_size) * device.period_size) < 0 - ) { - term(); - return false; - } - - if(snd_pcm_sw_params(device.handle, swparams) < 0) { - term(); - return false; - } - - buffer.data = new uint32_t[device.period_size]; - return true; - } - - auto term() -> void { - if(device.handle) { - //snd_pcm_drain(device.handle); //prevents popping noise; but causes multi-second lag - snd_pcm_close(device.handle); - device.handle = 0; - } - - if(buffer.data) { - delete[] buffer.data; - buffer.data = 0; + if(_buffer) { + delete[] _buffer; + _buffer = nullptr; } } + + bool _ready = false; + bool _blocking = true; + double _frequency = 48000.0; + uint _latency = 40; + + snd_pcm_t* _interface = nullptr; + snd_pcm_uframes_t _bufferSize; + snd_pcm_uframes_t _periodSize; + + uint32_t* _buffer = nullptr; + uint _offset = 0; }; diff --git a/ruby/audio/ao.cpp b/ruby/audio/ao.cpp index 22fc6e32..31e0306f 100644 --- a/ruby/audio/ao.cpp +++ b/ruby/audio/ao.cpp @@ -1,73 +1,76 @@ #include struct AudioAO : Audio { - ~AudioAO() { term(); } + AudioAO() { initialize(); } + ~AudioAO() { terminate(); } - int driver_id; - ao_sample_format driver_format; - ao_device* audio_device = nullptr; + auto ready() -> bool { return _ready; } - struct { - unsigned frequency = 48000; - } settings; - - auto cap(const string& name) -> bool { - if(name == Audio::Frequency) return true; - return false; + auto information() -> Information { + Information information; + information.devices = {_device}; + information.frequencies = {44100.0, 48000.0, 96000.0}; + information.latencies = {100}; + information.channels = {2}; + return information; } - auto get(const string& name) -> any { - if(name == Audio::Frequency) return settings.frequency; - return {}; + auto blocking() -> bool { return true; } + auto channels() -> uint { return 2; } + auto frequency() -> double { return _frequency; } + auto latency() -> uint { return 100; } + + auto setFrequency(double frequency) -> bool { + if(_frequency == frequency) return true; + _frequency = frequency; + return initialize(); } - auto set(const string& name, const any& value) -> bool { - if(name == Audio::Frequency && value.is()) { - settings.frequency = value.get(); - if(audio_device) init(); - return true; - } - - return false; + auto output(const double samples[]) -> void { + uint32_t sample = uint16_t(samples[0] * 32768.0) << 0 | uint16_t(samples[1] * 32768.0) << 0; + ao_play(_interface, (char*)&sample, 4); //this may need to be byte swapped for big endian } - auto sample(int16_t left, int16_t right) -> void { - uint32_t samp = (uint16_t)left << 0 | (uint16_t)right << 0; - ao_play(audio_device, (char*)&samp, 4); //This may need to be byte swapped for Big Endian - } + auto initialize() -> bool { + terminate(); - auto clear() -> void { - } - - auto init() -> bool { ao_initialize(); - driver_id = ao_default_driver_id(); //ao_driver_id((const char*)driver) - if(driver_id < 0) return false; + int driverID = ao_default_driver_id(); + if(driverID < 0) return false; - driver_format.bits = 16; - driver_format.channels = 2; - driver_format.rate = settings.frequency; - driver_format.byte_format = AO_FMT_LITTLE; + ao_sample_format format; + format.bits = 16; + format.channels = 2; + format.rate = (uint)_frequency; + format.byte_format = AO_FMT_LITTLE; - ao_option* options = nullptr; - ao_info *di = ao_driver_info(driver_id); - if(!di) return false; - if(!strcmp(di->short_name, "alsa")) { - ao_append_option(&options, "buffer_time", "100000"); //100ms latency (default was 500ms) + ao_info* information = ao_driver_info(driverID); + if(!information) return false; + _device = information->short_name; + if(_device == "alsa") { + ao_option* options = nullptr; + ao_append_option(&options, "buffer_time", "100000"); //100ms latency (default was 500ms) } - audio_device = ao_open_live(driver_id, &driver_format, options); - if(!audio_device) return false; + _interface = ao_open_live(driverID, &format, options); + if(!_interface) return false; - return true; + return _ready = true; } - auto term() -> void { - if(audio_device) { - ao_close(audio_device); - audio_device = nullptr; + auto terminate() -> void { + _ready = false; + if(_interface) { + ao_close(_interface); + _interface = nullptr; } ao_shutdown(); } + + bool _ready = false; + string _device = "Default"; + + int _driverID; + ao_device* _interface = nullptr; }; diff --git a/ruby/audio/asio.cpp b/ruby/audio/asio.cpp index 88f9f9e1..41dc6b2a 100644 --- a/ruby/audio/asio.cpp +++ b/ruby/audio/asio.cpp @@ -59,7 +59,7 @@ struct AudioASIO : Audio { } auto clear() -> void { - if(!_ready) return; + if(!ready()) return; for(uint n : range(_channels)) { memory::fill(_channel[n].buffers[0], _latency * _sampleSize); memory::fill(_channel[n].buffers[1], _latency * _sampleSize); @@ -71,7 +71,7 @@ struct AudioASIO : Audio { } auto output(const double samples[]) -> void { - if(!_ready) return; + if(!ready()) return; if(_blocking) { while(_queue.count >= _latency); } @@ -146,20 +146,22 @@ private: default: return false; //unsupported sample format } + _ready = true; clear(); - if(_asio->start() != ASE_OK) return false; - return _ready = true; + if(_asio->start() != ASE_OK) return _ready = false; + return true; } auto terminate() -> void { _ready = false; _devices.reset(); _active = {}; - if(!_asio) return; - _asio->stop(); - _asio->disposeBuffers(); - _asio->Release(); - _asio = nullptr; + if(_asio) { + _asio->stop(); + _asio->disposeBuffers(); + _asio->Release(); + _asio = nullptr; + } } private: diff --git a/ruby/audio/directsound.cpp b/ruby/audio/directsound.cpp index ba96b119..32501aa6 100644 --- a/ruby/audio/directsound.cpp +++ b/ruby/audio/directsound.cpp @@ -1,184 +1,172 @@ #include struct AudioDirectSound : Audio { - ~AudioDirectSound() { term(); } + AudioDirectSound() { initialize(); } + ~AudioDirectSound() { terminate(); } - LPDIRECTSOUND ds = nullptr; - LPDIRECTSOUNDBUFFER dsb_p = nullptr; - LPDIRECTSOUNDBUFFER dsb_b = nullptr; - DSBUFFERDESC dsbd; - WAVEFORMATEX wfx; + auto ready() -> bool { return _ready; } - struct { - uint rings = 0; - uint latency = 0; - - uint32_t* buffer = nullptr; - uint bufferoffset = 0; - - uint readring = 0; - uint writering = 0; - int distance = 0; - } device; - - struct { - HWND handle = nullptr; - bool synchronize = false; - uint frequency = 48000; - uint latency = 120; - } settings; - - auto cap(const string& name) -> bool { - if(name == Audio::Handle) return true; - 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; + information.devices = {"Default"}; + information.frequencies = {44100.0, 48000.0, 96000.0}; + information.latencies = {40, 60, 80, 100}; + information.channels = {2}; + return information; } - auto get(const string& name) -> any { - if(name == Audio::Handle) return (uintptr_t)settings.handle; - if(name == Audio::Synchronize) return settings.synchronize; - if(name == Audio::Frequency) return settings.frequency; - if(name == Audio::Latency) return settings.latency; - return {}; + auto blocking() -> bool { return _blocking; } + auto channels() -> uint { return _channels; } + auto frequency() -> double { return _frequency; } + auto latency() -> uint { return _latency; } + + auto setBlocking(bool blocking) -> bool { + if(_blocking == blocking) return true; + _blocking = blocking; + return true; } - auto set(const string& name, const any& value) -> bool { - if(name == Audio::Handle && value.is()) { - settings.handle = (HWND)value.get(); - return true; - } - - if(name == Audio::Synchronize && value.is()) { - settings.synchronize = value.get(); - if(ds) clear(); - return true; - } - - if(name == Audio::Frequency && value.is()) { - settings.frequency = value.get(); - if(ds) init(); - return true; - } - - if(name == Audio::Latency && value.is()) { - //latency settings below 40ms causes DirectSound to hang - settings.latency = max(40u, value.get()); - if(ds) init(); - return true; - } - - return false; + auto setFrequency(double frequency) -> bool { + if(_frequency == frequency) return true; + _frequency = frequency; + return initialize(); } - auto sample(int16_t left, int16_t right) -> void { - device.buffer[device.bufferoffset++] = (uint16_t)left << 0 | (uint16_t)right << 16; - if(device.bufferoffset < device.latency) return; - device.bufferoffset = 0; + auto setLatency(uint latency) -> bool { + if(_latency == latency) return true; + _latency = latency; + return initialize(); + } + + auto clear() -> void { + if(!ready()) return; + + _ringRead = 0; + _ringWrite = _rings - 1; + _ringDistance = _rings - 1; + + if(_buffer) memory::fill(_buffer, _period * _rings * 4); + _offset = 0; + + if(!_secondary) return; + _secondary->Stop(); + _secondary->SetCurrentPosition(0); - DWORD pos, size; void* output; + DWORD size; + _secondary->Lock(0, _period * _rings * 4, &output, &size, 0, 0, 0); + memory::fill(output, size); + _secondary->Unlock(output, size, 0, 0); - if(settings.synchronize) { + _secondary->Play(0, 0, DSBPLAY_LOOPING); + } + + auto output(const double samples[]) -> void { + if(!ready()) return; + + _buffer[_offset++] = uint16_t(samples[0] * 32768.0) << 0 | uint16_t(samples[1] * 32768.0) << 16; + if(_offset < _period) return; + _offset = 0; + + if(_blocking) { //wait until playback buffer has an empty ring to write new audio data to - while(device.distance >= device.rings - 1) { - dsb_b->GetCurrentPosition(&pos, 0); - uint activering = pos / (device.latency * 4); - if(activering == device.readring) continue; + while(_ringDistance >= _rings - 1) { + DWORD position; + _secondary->GetCurrentPosition(&position, 0); + uint ringActive = position / (_period * 4); + if(ringActive == _ringRead) continue; //subtract number of played rings from ring distance counter - device.distance -= (device.rings + activering - device.readring) % device.rings; - device.readring = activering; + _ringDistance -= (_rings + ringActive - _ringRead) % _rings; + _ringRead = ringActive; - if(device.distance < 2) { + if(_ringDistance < 2) { //buffer underflow; set max distance to recover quickly - device.distance = device.rings - 1; - device.writering = (device.rings + device.readring - 1) % device.rings; + _ringDistance = _rings - 1; + _ringWrite = (_rings + _ringRead - 1) % _rings; break; } } } - device.writering = (device.writering + 1) % device.rings; - device.distance = (device.distance + 1) % device.rings; + _ringWrite = (_ringWrite + 1) % _rings; + _ringDistance = (_ringDistance + 1) % _rings; - if(dsb_b->Lock(device.writering * device.latency * 4, device.latency * 4, &output, &size, 0, 0, 0) == DS_OK) { - memcpy(output, device.buffer, device.latency * 4); - dsb_b->Unlock(output, size, 0, 0); + void* output; + DWORD size; + if(_secondary->Lock(_ringWrite * _period * 4, _period * 4, &output, &size, 0, 0, 0) == DS_OK) { + memory::copy(output, _buffer, _period * 4); + _secondary->Unlock(output, size, 0, 0); } } - auto clear() -> void { - device.readring = 0; - device.writering = device.rings - 1; - device.distance = device.rings - 1; +private: + auto initialize() -> bool { + terminate(); - device.bufferoffset = 0; - if(device.buffer) memset(device.buffer, 0, device.latency * device.rings * 4); + _rings = 8; + _period = _frequency * _latency / _rings / 1000.0 + 0.5; + _buffer = new uint32_t[_period * _rings]; + _offset = 0; - if(!dsb_b) return; - dsb_b->Stop(); - dsb_b->SetCurrentPosition(0); + if(DirectSoundCreate(0, &_interface, 0) != DS_OK) return term(), false; + _interface->SetCooperativeLevel(GetDesktopWindow(), DSSCL_PRIORITY); - DWORD size; - void* output; - dsb_b->Lock(0, device.latency * device.rings * 4, &output, &size, 0, 0, 0); - memset(output, 0, size); - dsb_b->Unlock(output, size, 0, 0); + DSBUFFERDESC primaryDescription = {}; + primaryDescription.dwSize = sizeof(DSBUFFERDESC); + primaryDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; + primaryDescription.dwBufferBytes = 0; + primaryDescription.lpwfxFormat = 0; + _interface->CreateSoundBuffer(&primaryDescription, &_primary, 0); - dsb_b->Play(0, 0, DSBPLAY_LOOPING); - } + WAVEFORMATEX waveFormat = {}; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nChannels = _channels; + waveFormat.nSamplesPerSec = (uint)_frequency; + waveFormat.wBitsPerSample = 16; + waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + _primary->SetFormat(&waveFormat); - auto init() -> bool { - settings.handle = GetDesktopWindow(); - - device.rings = 8; - device.latency = settings.frequency * settings.latency / device.rings / 1000.0 + 0.5; - device.buffer = new uint32_t[device.latency * device.rings]; - device.bufferoffset = 0; - - if(DirectSoundCreate(0, &ds, 0) != DS_OK) return term(), false; - ds->SetCooperativeLevel((HWND)settings.handle, DSSCL_PRIORITY); - - memory::fill(&dsbd, sizeof(dsbd)); - dsbd.dwSize = sizeof(dsbd); - dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; - dsbd.dwBufferBytes = 0; - dsbd.lpwfxFormat = 0; - ds->CreateSoundBuffer(&dsbd, &dsb_p, 0); - - memory::fill(&wfx, sizeof(wfx)); - wfx.wFormatTag = WAVE_FORMAT_PCM; - wfx.nChannels = 2; - wfx.nSamplesPerSec = settings.frequency; - wfx.wBitsPerSample = 16; - wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels; - wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; - dsb_p->SetFormat(&wfx); - - memory::fill(&dsbd, sizeof(dsbd)); - dsbd.dwSize = sizeof(dsbd); - dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE; - dsbd.dwBufferBytes = device.latency * device.rings * sizeof(uint32_t); - dsbd.guid3DAlgorithm = GUID_NULL; - dsbd.lpwfxFormat = &wfx; - ds->CreateSoundBuffer(&dsbd, &dsb_b, 0); - dsb_b->SetFrequency(settings.frequency); - dsb_b->SetCurrentPosition(0); + DSBUFFERDESC secondaryDescription = {}; + secondaryDescription.dwSize = sizeof(DSBUFFERDESC); + secondaryDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE; + secondaryDescription.dwBufferBytes = _period * _rings * 4; + secondaryDescription.guid3DAlgorithm = GUID_NULL; + secondaryDescription.lpwfxFormat = &waveFormat; + _interface->CreateSoundBuffer(&secondaryDescription, &_secondary, 0); + _secondary->SetFrequency((uint)_frequency); + _secondary->SetCurrentPosition(0); + _ready = true; clear(); return true; } - auto term() -> void { - if(device.buffer) { - delete[] device.buffer; - device.buffer = nullptr; - } - - if(dsb_b) { dsb_b->Stop(); dsb_b->Release(); dsb_b = nullptr; } - if(dsb_p) { dsb_p->Stop(); dsb_p->Release(); dsb_p = nullptr; } - if(ds) { ds->Release(); ds = nullptr; } + auto terminate() -> void { + _ready = false; + if(_buffer) { delete[] _buffer; _buffer = nullptr; } + if(_secondary) { _secondary->Stop(); _secondary->Release(); _secondary = nullptr; } + if(_primary) { _primary->Stop(); _primary->Release(); _primary = nullptr; } + if(_interface) { _interface->Release(); _interface = nullptr; } } + + bool _ready = false; + bool _blocking = true; + uint _channels = 2; + double _frequency = 48000.0; + uint _latency = 40; + + LPDIRECTSOUND _interface = nullptr; + LPDIRECTSOUNDBUFFER _primary = nullptr; + LPDIRECTSOUNDBUFFER _secondary = nullptr; + + uint32_t* _buffer = nullptr; + uint _offset = 0; + + uint _period = 0; + uint _rings = 0; + uint _ringRead = 0; + uint _ringWrite = 0; + int _ringDistance = 0; }; diff --git a/ruby/audio/oss.cpp b/ruby/audio/oss.cpp index 1932259e..d501079b 100644 --- a/ruby/audio/oss.cpp +++ b/ruby/audio/oss.cpp @@ -67,7 +67,7 @@ struct AudioOSS : Audio { } auto output(const double samples[]) -> void { - if(!_ready) return; + if(!ready()) return; for(auto n : range(_channels)) { int16_t sample = samples[n] * 32768.0; auto unused = write(_fd, &sample, 2); diff --git a/ruby/audio/pulseaudio.cpp b/ruby/audio/pulseaudio.cpp index e6097493..a74e1f2c 100644 --- a/ruby/audio/pulseaudio.cpp +++ b/ruby/audio/pulseaudio.cpp @@ -1,159 +1,149 @@ #include struct AudioPulseAudio : Audio { - ~AudioPulseAudio() { term(); } + AudioPulseAudio() { initialize(); } + ~AudioPulseAudio() { terminate(); } - struct { - pa_mainloop* mainloop = nullptr; - pa_context* context = nullptr; - pa_stream* stream = nullptr; - pa_sample_spec spec; - pa_buffer_attr buffer_attr; - bool first; - } device; + auto ready() -> bool { return _ready; } - struct { - uint32_t* data = nullptr; - size_t size; - unsigned offset; - } buffer; - - struct { - bool synchronize = false; - unsigned frequency = 48000; - unsigned latency = 60; - } 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; + information.devices = {"Default"}; + information.frequencies = {44100.0, 48000.0, 96000.0}; + information.latencies = {20, 40, 60, 80, 100}; + information.channels = {2}; + 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 blocking() -> bool { return _blocking; } + auto channels() -> uint { return 2; } + auto frequency() -> double { return _frequency; } + auto latency() -> uint { return _latency; } + + auto setBlocking(bool blocking) -> bool { + if(_blocking == blocking) return true; + _blocking = blocking; + return true; } - 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(); - if(device.stream) { - pa_operation_unref(pa_stream_update_sample_rate(device.stream, settings.frequency, NULL, NULL)); - } - return true; - } - - if(name == Audio::Latency && value.is()) { - settings.latency = value.get(); - if(device.stream) { - device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec); - pa_stream_set_buffer_attr(device.stream, &device.buffer_attr, NULL, NULL); - } - return true; - } - - return false; + auto setFrequency(double frequency) -> bool { + if(_frequency == frequency) return true; + _frequency = frequency; + return initialize(); } - auto sample(int16_t left, int16_t right) -> void { - pa_stream_begin_write(device.stream, (void**)&buffer.data, &buffer.size); - buffer.data[buffer.offset++] = (uint16_t)left << 0 | (uint16_t)right << 16; - if((buffer.offset + 1) * pa_frame_size(&device.spec) <= buffer.size) return; + auto setLatency(uint latency) -> bool { + if(_latency == latency) return true; + _latency = latency; + return initialize(); + } + + auto output(const double samples[]) -> void { + pa_stream_begin_write(_stream, (void**)&_buffer, &_period); + _buffer[_offset++] = uint16_t(samples[0] * 32768.0) << 0 | uint16_t(samples[1] * 32768.0) << 16; + if((_offset + 1) * pa_frame_size(&_specification) <= _period) return; while(true) { - if(device.first) { - device.first = false; - pa_mainloop_iterate(device.mainloop, 0, NULL); + if(_first) { + _first = false; + pa_mainloop_iterate(_mainLoop, 0, nullptr); } else { - pa_mainloop_iterate(device.mainloop, 1, NULL); + pa_mainloop_iterate(_mainLoop, 1, nullptr); } - unsigned length = pa_stream_writable_size(device.stream); - if(length >= buffer.offset * pa_frame_size(&device.spec)) break; - if(settings.synchronize == false) { - buffer.offset = 0; + uint length = pa_stream_writable_size(_stream); + if(length >= buffer.offset * pa_frame_size(&_specification)) break; + if(!_blocking) { + _offset = 0; return; } } - pa_stream_write(device.stream, (const void*)buffer.data, buffer.offset * pa_frame_size(&device.spec), NULL, 0LL, PA_SEEK_RELATIVE); - buffer.data = 0; - buffer.offset = 0; + pa_stream_write(_stream, (const void*)_buffer, _offset * pa_frame_size(&_specification), nullptr, 0LL, PA_SEEK_RELATIVE); + _buffer = nullptr; + _offset = 0; } - auto clear() -> void { - } +private: + auto initialize() -> bool { + terminate(); - auto init() -> bool { - device.mainloop = pa_mainloop_new(); + _mainLoop = pa_mainloop_new(); + _context = pa_context_new(pa_mainloop_get_api(_mainLoop), "ruby::pulseAudio"); + pa_context_connect(_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr); - device.context = pa_context_new(pa_mainloop_get_api(device.mainloop), "ruby::pulseaudio"); - pa_context_connect(device.context, NULL, PA_CONTEXT_NOFLAGS, NULL); - - pa_context_state_t cstate; + pa_context_state_t contextState; do { - pa_mainloop_iterate(device.mainloop, 1, NULL); - cstate = pa_context_get_state(device.context); - if(!PA_CONTEXT_IS_GOOD(cstate)) return false; - } while(cstate != PA_CONTEXT_READY); + pa_mainloop_iterate(_mainLoop, 1, nullptr); + contextState = pa_context_get_state(_context); + if(!PA_CONTEXT_IS_GOOD(contextState)) return false; + } while(contextState != PA_CONTEXT_READY); - device.spec.format = PA_SAMPLE_S16LE; - device.spec.channels = 2; - device.spec.rate = settings.frequency; - device.stream = pa_stream_new(device.context, "audio", &device.spec, NULL); + _specification.format = PA_SAMPLE_S16LE; + _specification.channels = 2; + _specification.rate = (uint)_frequency; + _stream = pa_stream_new(_context, "audio", &_specification, nullptr); - device.buffer_attr.maxlength = -1; - device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec); - device.buffer_attr.prebuf = -1; - device.buffer_attr.minreq = -1; - device.buffer_attr.fragsize = -1; + pa_buffer_attr bufferAttributes; + bufferAttributes.maxlength = -1; + bufferAttributes.tlength = pa_usec_to_bytes(_latency * PA_USEC_PER_MSEC, &_specification); + bufferAttributes.prebuf = -1; + bufferAttributes.minreq = -1; + bufferAttributes.fragsize = -1; pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY | PA_STREAM_VARIABLE_RATE); - pa_stream_connect_playback(device.stream, NULL, &device.buffer_attr, flags, NULL, NULL); + pa_stream_connect_playback(_stream, nullptr, &bufferAttributes, flags, nullptr, nullptr); - pa_stream_state_t sstate; + pa_stream_state_t streamState; do { - pa_mainloop_iterate(device.mainloop, 1, NULL); - sstate = pa_stream_get_state(device.stream); - if(!PA_STREAM_IS_GOOD(sstate)) return false; - } while(sstate != PA_STREAM_READY); + pa_mainloop_iterate(_mainLoop, 1, nullptr); + streamState = pa_stream_get_state(_stream); + if(!PA_STREAM_IS_GOOD(streamState)) return false; + } while(streamState != PA_STREAM_READY); - buffer.size = 960; - buffer.offset = 0; - device.first = true; - - return true; + _period = 960; + _offset = 0; + _first = true; + return _ready = true; } - auto term() -> void { - if(buffer.data) { - pa_stream_cancel_write(device.stream); - buffer.data = nullptr; + auto terminate() -> void { + _ready = false; + + if(_buffer) { + pa_stream_cancel_write(_buffer); + _buffer = nullptr; } - if(device.stream) { - pa_stream_disconnect(device.stream); - pa_stream_unref(device.stream); - device.stream = nullptr; + if(_stream) { + pa_stream_disconnect(_stream); + pa_stream_unref(_stream); + _stream = nullptr; } - if(device.context) { - pa_context_disconnect(device.context); - pa_context_unref(device.context); - device.context = nullptr; + if(_context) { + pa_context_disconnect(_context); + pa_context_unref(_context); + _context = nullptr; } - if(device.mainloop) { - pa_mainloop_free(device.mainloop); - device.mainloop = nullptr; + if(_mainLoop) { + pa_mainloop_free(_mainLoop); + _mainLoop = nullptr; } } + + bool _ready = false; + bool _blocking = true; + double _frequency = 48000.0; + uint _latency = 40; + + uint32_t* _buffer = nullptr; + size_t _period = 0; + uint _offset = 0; + + pa_mainloop* _mainLoop = nullptr; + pa_context* _context = nullptr; + pa_stream* _stream = nullptr; + pa_sample_spec _specification; + bool _first = true; }; diff --git a/ruby/audio/pulseaudiosimple.cpp b/ruby/audio/pulseaudiosimple.cpp index e361e5c7..6e228ae5 100644 --- a/ruby/audio/pulseaudiosimple.cpp +++ b/ruby/audio/pulseaudiosimple.cpp @@ -2,94 +2,91 @@ #include struct AudioPulseAudioSimple : Audio { - ~AudioPulseAudioSimple() { term(); } + AudioPulseAudio() { initialize(); } + ~AudioPulseAudioSimple() { terminate(); } - struct { - pa_simple* handle = nullptr; - pa_sample_spec spec; - } device; + auto ready() -> bool { return _ready; } - struct { - uint32_t* data = nullptr; - unsigned offset = 0; - } buffer; - - struct { - unsigned frequency = 48000; - } settings; - - auto cap(const string& name) -> bool { - if(name == Audio::Frequency) return true; - return false; + auto information() -> Information { + Information information; + information.devices = {"Default"}; + information.frequencies = {44100.0, 48000.0, 96000.0}; + information.latencies = {40}; + information.channels = {2}; + return information; } - auto get(const string& name) -> any { - if(name == Audio::Frequency) return settings.frequency; - return {}; + auto blocking() -> bool { return true; } + auto channels() -> uint { return 2; } + auto frequency() -> double { return _frequency; } + auto latency() -> uint { return 40; } + + auto setFrequency(double frequency) -> bool { + if(_frequency == frequency) return true; + _frequency = frequency; + return initialize(); } - auto set(const string& name, const any& value) -> bool { - if(name == Audio::Frequency && value.is()) { - settings.frequency = value.get(); - if(device.handle) init(); - return true; - } + auto output(const double samples[]) -> void { + if(!ready()) return; - return false; - } - - auto sample(int16_t left, int16_t right) -> void { - if(!device.handle) return; - - buffer.data[buffer.offset++] = (uint16_t)left << 0 | (uint16_t)right << 16; - if(buffer.offset >= 64) { + _buffer[_offset++] = uint16_t(samples[0] * 32768.0) << 0 | uint16_t(samples[1] * 32768.0) << 16; + if(_offset >= 64) { int error; - pa_simple_write(device.handle, (const void*)buffer.data, buffer.offset * sizeof(uint32_t), &error); - buffer.offset = 0; + pa_simple_write(_interface, (const void*)_buffer, _offset * sizeof(uint32_t), &error); + _offset = 0; } } - auto clear() -> void { - } +private: + auto initialize() -> bool { + terminate(); - auto init() -> bool { - device.spec.format = PA_SAMPLE_S16LE; - device.spec.channels = 2; - device.spec.rate = settings.frequency; + pa_sample_spec specification; + specification.format = PA_SAMPLE_S16LE; + specification.channels = 2; + specification.rate = (uint)_frequency; int error = 0; - device.handle = pa_simple_new( + _interface = pa_simple_new( 0, //default server - "ruby::pulseaudiosimple", //application name + "ruby::pulseAudioSimple", //application name PA_STREAM_PLAYBACK, //direction 0, //default device "audio", //stream description - &device.spec, //sample format + &specification, //sample format 0, //default channel map 0, //default buffering attributes &error //error code ); - if(!device.handle) { - fprintf(stderr, "ruby::pulseaudiosimple failed to initialize - %s\n", pa_strerror(error)); - return false; - } + if(!_interface) return false; - buffer.data = new uint32_t[64]; - buffer.offset = 0; - return true; + _buffer = new uint32_t[64](); + _offset = 0; + return _ready = true; } - auto term() -> void { - if(device.handle) { + auto terminate() -> void { + _ready = false; + + if(_interface) { int error; - pa_simple_flush(device.handle, &error); - pa_simple_free(device.handle); - device.handle = nullptr; + pa_simple_flush(_interface, &error); + pa_simple_free(_interface); + _interface = nullptr; } - if(buffer.data) { - delete[] buffer.data; - buffer.data = nullptr; + if(_buffer) { + delete[] _buffer; + _buffer = nullptr; } } + + bool _ready = false; + double _frequency = 48000.0; + + pa_simple* _interface = nullptr; + + uint32_t* _buffer = nullptr; + uint _offset = 0; }; diff --git a/ruby/audio/wasapi.cpp b/ruby/audio/wasapi.cpp index 4b368048..4031bd34 100644 --- a/ruby/audio/wasapi.cpp +++ b/ruby/audio/wasapi.cpp @@ -59,24 +59,28 @@ struct AudioWASAPI : Audio { } auto clear() -> void { + if(!ready()) return; _queue.read = 0; _queue.write = 0; _queue.count = 0; - if(!_audioClient) return; _audioClient->Stop(); _audioClient->Reset(); _audioClient->Start(); } auto output(const double samples[]) -> void { - if(_queue.count < _bufferSize) { - for(uint n : range(_channels)) { - _queue.samples[_queue.write][n] = samples[n]; + if(!ready()) return; + + for(uint n : range(_channels)) { + _queue.samples[_queue.write][n] = samples[n]; + } + _queue.write++; + _queue.count++; + + if(_queue.count >= _bufferSize) { + if(WaitForSingleObject(_eventHandle, _blocking ? INFINITE : 0) == WAIT_OBJECT_0) { + write(); } - _queue.write++; - _queue.count++; - } else if(WaitForSingleObject(_eventHandle, _blocking ? INFINITE : 0) == WAIT_OBJECT_0) { - write(); } } @@ -156,11 +160,13 @@ private: _mode = waveFormat.SubFormat.Data1; _precision = waveFormat.Format.wBitsPerSample; + _ready = true; clear(); - return _ready = true; + return true; } auto terminate() -> void { + _ready = false; _devices.reset(); if(_audioClient) _audioClient->Stop(); if(_renderClient) _renderClient->Release(), _renderClient = nullptr; diff --git a/ruby/input/carbon.cpp b/ruby/input/carbon.cpp index 42728fe1..a992316b 100644 --- a/ruby/input/carbon.cpp +++ b/ruby/input/carbon.cpp @@ -1,30 +1,18 @@ #include "keyboard/carbon.cpp" struct InputCarbon : Input { - InputKeyboardCarbon carbonKeyboard; - InputCarbon() : carbonKeyboard(*this) {} - ~InputCarbon() { term(); } + InputCarbon() : _keyboard(*this) { initialize(); } + ~InputCarbon() { terminate(); } - auto cap(const string& name) -> bool { - if(name == Input::KeyboardSupport) return true; - return false; - } - - auto get(const string& name) -> any { - return {}; - } - - auto set(const string& name, const any& value) -> bool { - return false; - } + auto ready() -> bool { return _ready; } + auto acquired() -> bool { return false; } auto acquire() -> bool { return false; } auto release() -> bool { return false; } - auto acquired() -> bool { return false; } auto poll() -> vector> { vector> devices; - carbonKeyboard.poll(devices); + _keyboard.poll(devices); return devices; } @@ -32,12 +20,19 @@ struct InputCarbon : Input { return false; } - auto init() -> bool { - if(!carbonKeyboard.init()) return false; - return true; +private: + auto initialize() -> bool { + terminate(); + if(!_keyboard.initialize()) return false; + return _ready = true; } auto term() -> void { - carbonKeyboard.term(); + _ready = false; + _keyboard.terminate(); } + + bool _ready = false; + + InputKeyboardCarbon _keyboard; }; diff --git a/ruby/input/joypad/udev.cpp b/ruby/input/joypad/udev.cpp index 6c02a7cf..580c6270 100644 --- a/ruby/input/joypad/udev.cpp +++ b/ruby/input/joypad/udev.cpp @@ -12,14 +12,14 @@ struct InputJoypadUdev { udev_list_entry* item = nullptr; struct JoypadInput { - signed code = 0; - unsigned id = 0; + int code = 0; + uint id = 0; int16_t value = 0; input_absinfo info; JoypadInput() {} - JoypadInput(signed code) : code(code) {} - JoypadInput(signed code, unsigned id) : code(code), id(id) {} + JoypadInput(int code) : code(code) {} + JoypadInput(int code, uint id) : code(code), id(id) {} bool operator< (const JoypadInput& source) const { return code < source.code; } bool operator==(const JoypadInput& source) const { return code == source.code; } }; @@ -36,7 +36,7 @@ struct InputJoypadUdev { uint8_t keybit[(KEY_MAX + 7) / 8] = {0}; uint8_t absbit[(ABS_MAX + 7) / 8] = {0}; uint8_t ffbit[(FF_MAX + 7) / 8] = {0}; - unsigned effects = 0; + uint effects = 0; string name; string manufacturer; @@ -49,11 +49,11 @@ struct InputJoypadUdev { set hats; set buttons; bool rumble = false; - unsigned effectID = 0; + uint effectID = 0; }; vector joypads; - auto assign(shared_pointer hid, unsigned groupID, unsigned inputID, int16_t value) -> void { + auto assign(shared_pointer hid, uint groupID, uint inputID, int16_t value) -> void { auto& group = hid->group(groupID); if(group.input(inputID).value() == value) return; input.doChange(hid, groupID, inputID, group.input(inputID).value(), value); @@ -65,21 +65,21 @@ struct InputJoypadUdev { for(auto& jp : joypads) { input_event events[32]; - signed length = 0; + int length = 0; while((length = read(jp.fd, events, sizeof(events))) > 0) { length /= sizeof(input_event); - for(unsigned i = 0; i < length; i++) { - signed code = events[i].code; - signed type = events[i].type; - signed value = events[i].value; + for(uint i : range(length)) { + int code = events[i].code; + int type = events[i].type; + int value = events[i].value; if(type == EV_ABS) { if(auto input = jp.axes.find({code})) { - signed range = input().info.maximum - input().info.minimum; + int range = input().info.maximum - input().info.minimum; value = (value - input().info.minimum) * 65535ll / range - 32767; assign(jp.hid, HID::Joypad::GroupID::Axis, input().id, sclamp<16>(value)); } else if(auto input = jp.hats.find({code})) { - signed range = input().info.maximum - input().info.minimum; + int range = input().info.maximum - input().info.minimum; value = (value - input().info.minimum) * 65535ll / range - 32767; assign(jp.hid, HID::Joypad::GroupID::Hat, input().id, sclamp<16>(value)); } @@ -114,7 +114,7 @@ struct InputJoypadUdev { return false; } - auto init() -> bool { + auto initialize() -> bool { context = udev_new(); if(context == nullptr) return false; @@ -141,7 +141,7 @@ struct InputJoypadUdev { return true; } - auto term() -> void { + auto terminate() -> void { if(enumerator) { udev_enumerate_unref(enumerator); enumerator = nullptr; } } @@ -210,10 +210,10 @@ private: } } - unsigned axes = 0; - unsigned hats = 0; - unsigned buttons = 0; - for(signed i = 0; i < ABS_MISC; i++) { + uint axes = 0; + uint hats = 0; + uint buttons = 0; + for(int i = 0; i < ABS_MISC; i++) { if(testBit(jp.absbit, i)) { if(i >= ABS_HAT0X && i <= ABS_HAT3Y) { if(auto hat = jp.hats.insert({i, hats++})) { @@ -226,12 +226,12 @@ private: } } } - for(signed i = BTN_JOYSTICK; i < KEY_MAX; i++) { + for(int i = BTN_JOYSTICK; i < KEY_MAX; i++) { if(testBit(jp.keybit, i)) { jp.buttons.insert({i, buttons++}); } } - for(signed i = BTN_MISC; i < BTN_JOYSTICK; i++) { + for(int i = BTN_MISC; i < BTN_JOYSTICK; i++) { if(testBit(jp.keybit, i)) { jp.buttons.insert({i, buttons++}); } @@ -259,14 +259,14 @@ private: uint64_t pathID = Hash::CRC32(jp.deviceName.data(), jp.deviceName.size()).value(); jp.hid->setID(pathID << 32 | jp.vendorID.hex() << 16 | jp.productID.hex() << 0); - for(unsigned n = 0; n < jp.axes.size(); n++) jp.hid->axes().append(n); - for(unsigned n = 0; n < jp.hats.size(); n++) jp.hid->hats().append(n); - for(unsigned n = 0; n < jp.buttons.size(); n++) jp.hid->buttons().append(n); + for(uint n : range(jp.axes.size())) jp.hid->axes().append(n); + for(uint n : range(jp.hats.size())) jp.hid->hats().append(n); + for(uint n : range(jp.buttons.size())) jp.hid->buttons().append(n); jp.hid->setRumble(jp.rumble); } auto removeJoypad(udev_device* device, const string& deviceNode) -> void { - for(unsigned n = 0; n < joypads.size(); n++) { + for(uint n : range(joypads.size())) { if(joypads[n].deviceNode == deviceNode) { close(joypads[n].fd); joypads.remove(n); diff --git a/ruby/input/keyboard/carbon.cpp b/ruby/input/keyboard/carbon.cpp index d2beb90f..5c014405 100644 --- a/ruby/input/keyboard/carbon.cpp +++ b/ruby/input/keyboard/carbon.cpp @@ -31,7 +31,7 @@ struct InputKeyboardCarbon { devices.append(hid); } - auto init() -> bool { + auto initialize() -> bool { keys.append({0x35, "Escape"}); keys.append({0x7a, "F1"}); keys.append({0x78, "F2"}); @@ -153,6 +153,6 @@ struct InputKeyboardCarbon { return true; } - auto term() -> void { + auto terminate() -> void { } }; diff --git a/ruby/input/keyboard/quartz.cpp b/ruby/input/keyboard/quartz.cpp index 7757fa62..90d998dd 100644 --- a/ruby/input/keyboard/quartz.cpp +++ b/ruby/input/keyboard/quartz.cpp @@ -26,7 +26,7 @@ struct InputKeyboardQuartz { devices.append(hid); } - auto init() -> bool { + auto initialize() -> bool { keys.append({"Escape", kVK_Escape}); keys.append({"F1", kVK_F1}); keys.append({"F2", kVK_F2}); @@ -148,6 +148,6 @@ struct InputKeyboardQuartz { return true; } - auto term() -> void { + auto terminate() -> void { } }; diff --git a/ruby/input/quartz.cpp b/ruby/input/quartz.cpp index 82320042..26c5eb3f 100644 --- a/ruby/input/quartz.cpp +++ b/ruby/input/quartz.cpp @@ -1,30 +1,18 @@ #include "keyboard/quartz.cpp" struct InputQuartz : Input { - InputKeyboardQuartz quartzKeyboard; - InputQuartz() : quartzKeyboard(*this) {} - ~InputQuartz() { term(); } + InputQuartz() : _keyboard(*this) { initialize(); } + ~InputQuartz() { terminate(); } - auto cap(const string& name) -> bool { - if(name == Input::KeyboardSupport) return true; - return false; - } - - auto get(const string& name) -> any { - return {}; - } - - auto set(const string& name, const any& value) -> bool { - return false; - } + auto ready() -> bool { return _ready; } + auto acquired() -> bool { return false; } auto acquire() -> bool { return false; } auto release() -> bool { return false; } - auto acquired() -> bool { return false; } auto poll() -> vector> { vector> devices; - quartzKeyboard.poll(devices); + _keyboard.poll(devices); return devices; } @@ -32,12 +20,18 @@ struct InputQuartz : Input { return false; } - auto init() -> bool { - if(!quartzKeyboard.init()) return false; - return true; + auto initialize() -> bool { + terminate(); + if(!_keyboard.init()) return false; + return _ready = true; } - auto term() -> void { - quartzKeyboard.term(); + auto terminate() -> void { + _ready = false; + _keyboard.term(); } + + bool _ready = false; + + InputKeyboardQuartz _keyboard; }; diff --git a/ruby/input/sdl.cpp b/ruby/input/sdl.cpp index e30601ff..2998ad0e 100644 --- a/ruby/input/sdl.cpp +++ b/ruby/input/sdl.cpp @@ -47,6 +47,7 @@ struct InputSDL : Input { private: auto initialize() -> bool { terminate(); + if(!_context) return false; if(!_keyboard.initialize()) return false; if(!_mouse.initialize(_context)) return false; if(!_joypad.initialize()) return false; diff --git a/ruby/input/udev.cpp b/ruby/input/udev.cpp index 26f54d62..94dfb787 100644 --- a/ruby/input/udev.cpp +++ b/ruby/input/udev.cpp @@ -13,72 +13,64 @@ #include "joypad/udev.cpp" struct InputUdev : Input { - InputKeyboardXlib xlibKeyboard; - InputMouseXlib xlibMouse; - InputJoypadUdev udev; - InputUdev() : xlibKeyboard(*this), xlibMouse(*this), udev(*this) {} - ~InputUdev() { term(); } + InputUdev() : _keyboard(*this), _mouse(*this), _joypad(*this) { initialize(); } + ~InputUdev() { terminate(); } - struct Settings { - uintptr_t handle = 0; - } settings; + auto ready() -> bool { return _ready; } - auto cap(const string& name) -> bool { - if(name == Input::Handle) return true; - if(name == Input::KeyboardSupport) return true; - if(name == Input::MouseSupport) return true; - if(name == Input::JoypadSupport) return true; - if(name == Input::JoypadRumbleSupport) return true; - return false; - } + auto context() -> uintptr { return _context; } - auto get(const string& name) -> any { - if(name == Input::Handle) return settings.handle; - return {}; - } - - auto set(const string& name, const any& value) -> bool { - if(name == Input::Handle && value.is()) { - settings.handle = value.get(); - return true; - } - return false; - } - - auto acquire() -> bool { - return xlibMouse.acquire(); - } - - auto release() -> bool { - return xlibMouse.release(); + auto setContext(uintptr context) -> bool { + if(_context == context) return true; + _context = context; + return initialize(); } auto acquired() -> bool { - return xlibMouse.acquired(); + return _mouse.acquired(); + } + + auto acquire() -> bool { + return _mouse.acquire(); + } + + auto release() -> bool { + return _mouse.release(); } auto poll() -> vector> { vector> devices; - xlibKeyboard.poll(devices); - xlibMouse.poll(devices); - udev.poll(devices); + _keyboard.poll(devices); + _mouse.poll(devices); + _joypad.poll(devices); return devices; } auto rumble(uint64_t id, bool enable) -> bool { - return udev.rumble(id, enable); + return _joypad.rumble(id, enable); } +private: auto init() -> bool { - if(xlibKeyboard.init() == false) return false; - if(xlibMouse.init(settings.handle) == false) return false; - if(udev.init() == false) return false; - return true; + terminate(); + if(!_context) return false; + if(!_keyboard.initialize()) return false; + if(!_mouse.initialize(_context)) return false; + if(!_joypad.initialize()) return false; + return _ready = true; } - auto term() -> void { - xlibKeyboard.term(); - xlibMouse.term(); - udev.term(); + auto terminate() -> void { + _ready = false; + _keyboard.terminate(); + _mouse.terminate(); + _joypad.terminate(); } + + bool _ready = false; + uintptr _context = 0; + + InputKeyboardXlib _keyboard; + InputMouseXlib _mouse; + InputJoypadUdev _joypad; };