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; };