mirror of https://github.com/bsnes-emu/bsnes.git
Update to v103r22 release.
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 :|
This commit is contained in:
parent
80841deaa5
commit
e1223366a7
|
@ -12,7 +12,7 @@ using namespace nall;
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const string Name = "higan";
|
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 Author = "byuu";
|
||||||
static const string License = "GPLv3";
|
static const string License = "GPLv3";
|
||||||
static const string Website = "http://byuu.org/";
|
static const string Website = "http://byuu.org/";
|
||||||
|
|
|
@ -7,6 +7,43 @@
|
||||||
//as such, high level emulation is used as a necessary evil
|
//as such, high level emulation is used as a necessary evil
|
||||||
|
|
||||||
auto Cartridge::TAMA::second() -> void {
|
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 {
|
auto Cartridge::TAMA::read(uint16 address) -> uint8 {
|
||||||
|
@ -23,12 +60,30 @@ auto Cartridge::TAMA::read(uint16 address) -> uint8 {
|
||||||
return 0xf0 | io.ready;
|
return 0xf0 | io.ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(io.select == 0x0c) {
|
if(io.mode == 0 || io.mode == 1) {
|
||||||
return 0xf0 | io.output.bits(0,3);
|
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) {
|
if(io.mode == 2 || io.mode == 4) {
|
||||||
return 0xf0 | io.output.bits(4,7);
|
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;
|
return 0xff;
|
||||||
|
@ -42,6 +97,9 @@ auto Cartridge::TAMA::read(uint16 address) -> uint8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Cartridge::TAMA::write(uint16 address, uint8 data) -> void {
|
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((address & 0xe001) == 0xa000) { //$a000-bfff (even)
|
||||||
if(io.select == 0x00) {
|
if(io.select == 0x00) {
|
||||||
io.rom.bank.bits(0,3) = data.bits(0,3);
|
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) {
|
if(io.mode == 1) {
|
||||||
io.output = cartridge.ram.read(io.index);
|
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;
|
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 {
|
auto Cartridge::TAMA::power() -> void {
|
||||||
io = {};
|
io = {};
|
||||||
}
|
}
|
||||||
|
@ -123,4 +218,15 @@ auto Cartridge::TAMA::serialize(serializer& s) -> void {
|
||||||
s.integer(io.input);
|
s.integer(io.input);
|
||||||
s.integer(io.output);
|
s.integer(io.output);
|
||||||
s.integer(io.rom.bank);
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ struct TAMA : Mapper {
|
||||||
auto second() -> void;
|
auto second() -> void;
|
||||||
auto read(uint16 address) -> uint8;
|
auto read(uint16 address) -> uint8;
|
||||||
auto write(uint16 address, uint8 data) -> void;
|
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 power() -> void;
|
||||||
auto serialize(serializer&) -> void;
|
auto serialize(serializer&) -> void;
|
||||||
|
|
||||||
|
@ -18,4 +16,18 @@ struct TAMA : Mapper {
|
||||||
uint5 bank;
|
uint5 bank;
|
||||||
} rom;
|
} rom;
|
||||||
} io;
|
} 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;
|
} tama;
|
||||||
|
|
|
@ -19,16 +19,16 @@ ui_objects += $(if $(call streq,$(platform),windows),ui-resource)
|
||||||
# platform
|
# platform
|
||||||
ifeq ($(platform),windows)
|
ifeq ($(platform),windows)
|
||||||
ruby += video.wgl video.direct3d video.directdraw video.gdi
|
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
|
ruby += input.windows
|
||||||
else ifeq ($(platform),macosx)
|
else ifeq ($(platform),macosx)
|
||||||
ruby += video.cgl
|
ruby += video.cgl
|
||||||
ruby += audio.openal
|
ruby += audio.openal
|
||||||
ruby += #input.quartz input.carbon
|
ruby += input.quartz input.carbon
|
||||||
else ifeq ($(platform),linux)
|
else ifeq ($(platform),linux)
|
||||||
ruby += video.glx video.xvideo video.xshm video.sdl
|
ruby += video.glx video.xvideo video.xshm video.sdl
|
||||||
ruby += audio.oss audio.openal #audio.alsa audio.pulseaudio audio.pulseaudiosimple audio.ao
|
ruby += audio.oss audio.alsa audio.openal audio.pulseaudio audio.pulseaudiosimple audio.ao
|
||||||
ruby += input.sdl input.xlib #input.udev
|
ruby += input.sdl input.xlib input.udev
|
||||||
else ifeq ($(platform),bsd)
|
else ifeq ($(platform),bsd)
|
||||||
ruby += video.glx video.xvideo video.xshm video.sdl
|
ruby += video.glx video.xvideo video.xshm video.sdl
|
||||||
ruby += audio.oss audio.openal
|
ruby += audio.oss audio.openal
|
||||||
|
|
|
@ -1,212 +1,139 @@
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
||||||
struct AudioALSA : Audio {
|
struct AudioALSA : Audio {
|
||||||
~AudioALSA() { term(); }
|
AudioALSA() { initialize(); }
|
||||||
|
~AudioALSA() { terminate(); }
|
||||||
|
|
||||||
struct {
|
auto ready() -> bool { return _ready; }
|
||||||
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;
|
|
||||||
|
|
||||||
struct {
|
auto blocking() -> bool { return _blocking; }
|
||||||
uint32_t* data = nullptr;
|
auto channels() -> uint { return 2; }
|
||||||
unsigned length = 0;
|
auto frequency() -> double { return _frequency; }
|
||||||
} buffer;
|
auto latency() -> uint { return _latency; }
|
||||||
|
|
||||||
struct {
|
auto setBlocking(bool blocking) -> bool {
|
||||||
bool synchronize = false;
|
if(_blocking == blocking) return true;
|
||||||
unsigned frequency = 48000;
|
_blocking = blocking;
|
||||||
unsigned latency = 60;
|
return true;
|
||||||
} 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 get(const string& name) -> any {
|
auto setFrequency(double frequency) -> bool {
|
||||||
if(name == Audio::Synchronize) return settings.synchronize;
|
if(_frequency == frequency) return true;
|
||||||
if(name == Audio::Frequency) return settings.frequency;
|
_frequency = frequency;
|
||||||
if(name == Audio::Latency) return settings.latency;
|
return initialize();
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto set(const string& name, const any& value) -> bool {
|
auto setLatency(uint latency) -> bool {
|
||||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
if(_latency == latency) return true;
|
||||||
if(settings.synchronize != value.get<bool>()) {
|
_latency = latency;
|
||||||
settings.synchronize = value.get<bool>();
|
return initialize();
|
||||||
if(device.handle) init();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
|
||||||
if(settings.frequency != value.get<unsigned>()) {
|
|
||||||
settings.frequency = value.get<unsigned>();
|
|
||||||
if(device.handle) init();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(name == Audio::Latency && value.is<unsigned>()) {
|
|
||||||
if(settings.latency != value.get<unsigned>()) {
|
|
||||||
settings.latency = value.get<unsigned>();
|
|
||||||
if(device.handle) init();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sample(int16_t left, int16_t right) -> void {
|
auto output(const double samples[]) -> void {
|
||||||
if(!device.handle) return;
|
if(!ready()) return;
|
||||||
|
|
||||||
buffer.data[buffer.length++] = (uint16_t)left << 0 | (uint16_t)right << 16;
|
_buffer[_offset++] = uint16_t(samples[0] * 32768.0) << 0 | uint16_t(samples[1] * 32768.0) << 16;
|
||||||
if(buffer.length < device.period_size) return;
|
if(_offset < _periodSize) return;
|
||||||
|
|
||||||
snd_pcm_sframes_t avail;
|
snd_pcm_sframes_t available;
|
||||||
do {
|
do {
|
||||||
avail = snd_pcm_avail_update(device.handle);
|
available = snd_pcm_avail_update(_interface);
|
||||||
if(avail < 0) snd_pcm_recover(device.handle, avail, 1);
|
if(available < 0) snd_pcm_recover(_interface, available, 1);
|
||||||
if(avail < buffer.length) {
|
if(available < _offset) {
|
||||||
if(settings.synchronize == false) {
|
if(!_blocking) {
|
||||||
buffer.length = 0;
|
_offset = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int error = snd_pcm_wait(device.handle, -1);
|
int error = snd_pcm_wait(_interface, -1);
|
||||||
if(error < 0) snd_pcm_recover(device.handle, error, 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
|
uint32_t* output = _buffer;
|
||||||
#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;
|
|
||||||
int i = 4;
|
int i = 4;
|
||||||
|
|
||||||
while((buffer.length > 0) && i--) {
|
while(_offset > 0 && i--) {
|
||||||
snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length);
|
snd_pcm_sframes_t written = snd_pcm_writei(_interface, output, _offset);
|
||||||
if(written < 0) {
|
if(written < 0) {
|
||||||
//no samples written
|
//no samples written
|
||||||
snd_pcm_recover(device.handle, written, 1);
|
snd_pcm_recover(_interface, written, 1);
|
||||||
} else if(written <= buffer.length) {
|
} else if(written <= _offset) {
|
||||||
buffer.length -= written;
|
_offset -= written;
|
||||||
buffer_ptr += written;
|
output += written;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(i < 0) {
|
if(i < 0) {
|
||||||
if(buffer.data == buffer_ptr) {
|
if(buffer == output) {
|
||||||
buffer.length--;
|
_offset--;
|
||||||
buffer_ptr++;
|
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 {
|
auto terminate() -> void {
|
||||||
if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) {
|
_ready = false;
|
||||||
term();
|
|
||||||
return 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(_buffer) {
|
||||||
#if 0
|
delete[] _buffer;
|
||||||
if(snd_pcm_set_params(device.handle, device.format, SND_PCM_ACCESS_RW_INTERLEAVED,
|
_buffer = nullptr;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,73 +1,76 @@
|
||||||
#include <ao/ao.h>
|
#include <ao/ao.h>
|
||||||
|
|
||||||
struct AudioAO : Audio {
|
struct AudioAO : Audio {
|
||||||
~AudioAO() { term(); }
|
AudioAO() { initialize(); }
|
||||||
|
~AudioAO() { terminate(); }
|
||||||
|
|
||||||
int driver_id;
|
auto ready() -> bool { return _ready; }
|
||||||
ao_sample_format driver_format;
|
|
||||||
ao_device* audio_device = nullptr;
|
|
||||||
|
|
||||||
struct {
|
auto information() -> Information {
|
||||||
unsigned frequency = 48000;
|
Information information;
|
||||||
} settings;
|
information.devices = {_device};
|
||||||
|
information.frequencies = {44100.0, 48000.0, 96000.0};
|
||||||
auto cap(const string& name) -> bool {
|
information.latencies = {100};
|
||||||
if(name == Audio::Frequency) return true;
|
information.channels = {2};
|
||||||
return false;
|
return information;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto get(const string& name) -> any {
|
auto blocking() -> bool { return true; }
|
||||||
if(name == Audio::Frequency) return settings.frequency;
|
auto channels() -> uint { return 2; }
|
||||||
return {};
|
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 {
|
auto output(const double samples[]) -> void {
|
||||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
uint32_t sample = uint16_t(samples[0] * 32768.0) << 0 | uint16_t(samples[1] * 32768.0) << 0;
|
||||||
settings.frequency = value.get<unsigned>();
|
ao_play(_interface, (char*)&sample, 4); //this may need to be byte swapped for big endian
|
||||||
if(audio_device) init();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sample(int16_t left, int16_t right) -> void {
|
auto initialize() -> bool {
|
||||||
uint32_t samp = (uint16_t)left << 0 | (uint16_t)right << 0;
|
terminate();
|
||||||
ao_play(audio_device, (char*)&samp, 4); //This may need to be byte swapped for Big Endian
|
|
||||||
}
|
|
||||||
|
|
||||||
auto clear() -> void {
|
|
||||||
}
|
|
||||||
|
|
||||||
auto init() -> bool {
|
|
||||||
ao_initialize();
|
ao_initialize();
|
||||||
|
|
||||||
driver_id = ao_default_driver_id(); //ao_driver_id((const char*)driver)
|
int driverID = ao_default_driver_id();
|
||||||
if(driver_id < 0) return false;
|
if(driverID < 0) return false;
|
||||||
|
|
||||||
driver_format.bits = 16;
|
ao_sample_format format;
|
||||||
driver_format.channels = 2;
|
format.bits = 16;
|
||||||
driver_format.rate = settings.frequency;
|
format.channels = 2;
|
||||||
driver_format.byte_format = AO_FMT_LITTLE;
|
format.rate = (uint)_frequency;
|
||||||
|
format.byte_format = AO_FMT_LITTLE;
|
||||||
|
|
||||||
ao_option* options = nullptr;
|
ao_info* information = ao_driver_info(driverID);
|
||||||
ao_info *di = ao_driver_info(driver_id);
|
if(!information) return false;
|
||||||
if(!di) return false;
|
_device = information->short_name;
|
||||||
if(!strcmp(di->short_name, "alsa")) {
|
if(_device == "alsa") {
|
||||||
ao_append_option(&options, "buffer_time", "100000"); //100ms latency (default was 500ms)
|
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);
|
_interface = ao_open_live(driverID, &format, options);
|
||||||
if(!audio_device) return false;
|
if(!_interface) return false;
|
||||||
|
|
||||||
return true;
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
if(audio_device) {
|
_ready = false;
|
||||||
ao_close(audio_device);
|
if(_interface) {
|
||||||
audio_device = nullptr;
|
ao_close(_interface);
|
||||||
|
_interface = nullptr;
|
||||||
}
|
}
|
||||||
ao_shutdown();
|
ao_shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _ready = false;
|
||||||
|
string _device = "Default";
|
||||||
|
|
||||||
|
int _driverID;
|
||||||
|
ao_device* _interface = nullptr;
|
||||||
};
|
};
|
||||||
|
|
|
@ -59,7 +59,7 @@ struct AudioASIO : Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void {
|
auto clear() -> void {
|
||||||
if(!_ready) return;
|
if(!ready()) return;
|
||||||
for(uint n : range(_channels)) {
|
for(uint n : range(_channels)) {
|
||||||
memory::fill(_channel[n].buffers[0], _latency * _sampleSize);
|
memory::fill(_channel[n].buffers[0], _latency * _sampleSize);
|
||||||
memory::fill(_channel[n].buffers[1], _latency * _sampleSize);
|
memory::fill(_channel[n].buffers[1], _latency * _sampleSize);
|
||||||
|
@ -71,7 +71,7 @@ struct AudioASIO : Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output(const double samples[]) -> void {
|
auto output(const double samples[]) -> void {
|
||||||
if(!_ready) return;
|
if(!ready()) return;
|
||||||
if(_blocking) {
|
if(_blocking) {
|
||||||
while(_queue.count >= _latency);
|
while(_queue.count >= _latency);
|
||||||
}
|
}
|
||||||
|
@ -146,20 +146,22 @@ private:
|
||||||
default: return false; //unsupported sample format
|
default: return false; //unsupported sample format
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ready = true;
|
||||||
clear();
|
clear();
|
||||||
if(_asio->start() != ASE_OK) return false;
|
if(_asio->start() != ASE_OK) return _ready = false;
|
||||||
return _ready = true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto terminate() -> void {
|
auto terminate() -> void {
|
||||||
_ready = false;
|
_ready = false;
|
||||||
_devices.reset();
|
_devices.reset();
|
||||||
_active = {};
|
_active = {};
|
||||||
if(!_asio) return;
|
if(_asio) {
|
||||||
_asio->stop();
|
_asio->stop();
|
||||||
_asio->disposeBuffers();
|
_asio->disposeBuffers();
|
||||||
_asio->Release();
|
_asio->Release();
|
||||||
_asio = nullptr;
|
_asio = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -1,184 +1,172 @@
|
||||||
#include <dsound.h>
|
#include <dsound.h>
|
||||||
|
|
||||||
struct AudioDirectSound : Audio {
|
struct AudioDirectSound : Audio {
|
||||||
~AudioDirectSound() { term(); }
|
AudioDirectSound() { initialize(); }
|
||||||
|
~AudioDirectSound() { terminate(); }
|
||||||
|
|
||||||
LPDIRECTSOUND ds = nullptr;
|
auto ready() -> bool { return _ready; }
|
||||||
LPDIRECTSOUNDBUFFER dsb_p = nullptr;
|
|
||||||
LPDIRECTSOUNDBUFFER dsb_b = nullptr;
|
|
||||||
DSBUFFERDESC dsbd;
|
|
||||||
WAVEFORMATEX wfx;
|
|
||||||
|
|
||||||
struct {
|
auto information() -> Information {
|
||||||
uint rings = 0;
|
Information information;
|
||||||
uint latency = 0;
|
information.devices = {"Default"};
|
||||||
|
information.frequencies = {44100.0, 48000.0, 96000.0};
|
||||||
uint32_t* buffer = nullptr;
|
information.latencies = {40, 60, 80, 100};
|
||||||
uint bufferoffset = 0;
|
information.channels = {2};
|
||||||
|
return information;
|
||||||
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 get(const string& name) -> any {
|
auto blocking() -> bool { return _blocking; }
|
||||||
if(name == Audio::Handle) return (uintptr_t)settings.handle;
|
auto channels() -> uint { return _channels; }
|
||||||
if(name == Audio::Synchronize) return settings.synchronize;
|
auto frequency() -> double { return _frequency; }
|
||||||
if(name == Audio::Frequency) return settings.frequency;
|
auto latency() -> uint { return _latency; }
|
||||||
if(name == Audio::Latency) return settings.latency;
|
|
||||||
return {};
|
auto setBlocking(bool blocking) -> bool {
|
||||||
|
if(_blocking == blocking) return true;
|
||||||
|
_blocking = blocking;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto set(const string& name, const any& value) -> bool {
|
auto setFrequency(double frequency) -> bool {
|
||||||
if(name == Audio::Handle && value.is<uintptr_t>()) {
|
if(_frequency == frequency) return true;
|
||||||
settings.handle = (HWND)value.get<uintptr_t>();
|
_frequency = frequency;
|
||||||
return true;
|
return initialize();
|
||||||
}
|
|
||||||
|
|
||||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
|
||||||
settings.synchronize = value.get<bool>();
|
|
||||||
if(ds) clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(name == Audio::Frequency && value.is<uint>()) {
|
|
||||||
settings.frequency = value.get<uint>();
|
|
||||||
if(ds) init();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(name == Audio::Latency && value.is<uint>()) {
|
|
||||||
//latency settings below 40ms causes DirectSound to hang
|
|
||||||
settings.latency = max(40u, value.get<uint>());
|
|
||||||
if(ds) init();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sample(int16_t left, int16_t right) -> void {
|
auto setLatency(uint latency) -> bool {
|
||||||
device.buffer[device.bufferoffset++] = (uint16_t)left << 0 | (uint16_t)right << 16;
|
if(_latency == latency) return true;
|
||||||
if(device.bufferoffset < device.latency) return;
|
_latency = latency;
|
||||||
device.bufferoffset = 0;
|
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;
|
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
|
//wait until playback buffer has an empty ring to write new audio data to
|
||||||
while(device.distance >= device.rings - 1) {
|
while(_ringDistance >= _rings - 1) {
|
||||||
dsb_b->GetCurrentPosition(&pos, 0);
|
DWORD position;
|
||||||
uint activering = pos / (device.latency * 4);
|
_secondary->GetCurrentPosition(&position, 0);
|
||||||
if(activering == device.readring) continue;
|
uint ringActive = position / (_period * 4);
|
||||||
|
if(ringActive == _ringRead) continue;
|
||||||
|
|
||||||
//subtract number of played rings from ring distance counter
|
//subtract number of played rings from ring distance counter
|
||||||
device.distance -= (device.rings + activering - device.readring) % device.rings;
|
_ringDistance -= (_rings + ringActive - _ringRead) % _rings;
|
||||||
device.readring = activering;
|
_ringRead = ringActive;
|
||||||
|
|
||||||
if(device.distance < 2) {
|
if(_ringDistance < 2) {
|
||||||
//buffer underflow; set max distance to recover quickly
|
//buffer underflow; set max distance to recover quickly
|
||||||
device.distance = device.rings - 1;
|
_ringDistance = _rings - 1;
|
||||||
device.writering = (device.rings + device.readring - 1) % device.rings;
|
_ringWrite = (_rings + _ringRead - 1) % _rings;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
device.writering = (device.writering + 1) % device.rings;
|
_ringWrite = (_ringWrite + 1) % _rings;
|
||||||
device.distance = (device.distance + 1) % device.rings;
|
_ringDistance = (_ringDistance + 1) % _rings;
|
||||||
|
|
||||||
if(dsb_b->Lock(device.writering * device.latency * 4, device.latency * 4, &output, &size, 0, 0, 0) == DS_OK) {
|
void* output;
|
||||||
memcpy(output, device.buffer, device.latency * 4);
|
DWORD size;
|
||||||
dsb_b->Unlock(output, size, 0, 0);
|
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 {
|
private:
|
||||||
device.readring = 0;
|
auto initialize() -> bool {
|
||||||
device.writering = device.rings - 1;
|
terminate();
|
||||||
device.distance = device.rings - 1;
|
|
||||||
|
|
||||||
device.bufferoffset = 0;
|
_rings = 8;
|
||||||
if(device.buffer) memset(device.buffer, 0, device.latency * device.rings * 4);
|
_period = _frequency * _latency / _rings / 1000.0 + 0.5;
|
||||||
|
_buffer = new uint32_t[_period * _rings];
|
||||||
|
_offset = 0;
|
||||||
|
|
||||||
if(!dsb_b) return;
|
if(DirectSoundCreate(0, &_interface, 0) != DS_OK) return term(), false;
|
||||||
dsb_b->Stop();
|
_interface->SetCooperativeLevel(GetDesktopWindow(), DSSCL_PRIORITY);
|
||||||
dsb_b->SetCurrentPosition(0);
|
|
||||||
|
|
||||||
DWORD size;
|
DSBUFFERDESC primaryDescription = {};
|
||||||
void* output;
|
primaryDescription.dwSize = sizeof(DSBUFFERDESC);
|
||||||
dsb_b->Lock(0, device.latency * device.rings * 4, &output, &size, 0, 0, 0);
|
primaryDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||||
memset(output, 0, size);
|
primaryDescription.dwBufferBytes = 0;
|
||||||
dsb_b->Unlock(output, size, 0, 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 {
|
DSBUFFERDESC secondaryDescription = {};
|
||||||
settings.handle = GetDesktopWindow();
|
secondaryDescription.dwSize = sizeof(DSBUFFERDESC);
|
||||||
|
secondaryDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE;
|
||||||
device.rings = 8;
|
secondaryDescription.dwBufferBytes = _period * _rings * 4;
|
||||||
device.latency = settings.frequency * settings.latency / device.rings / 1000.0 + 0.5;
|
secondaryDescription.guid3DAlgorithm = GUID_NULL;
|
||||||
device.buffer = new uint32_t[device.latency * device.rings];
|
secondaryDescription.lpwfxFormat = &waveFormat;
|
||||||
device.bufferoffset = 0;
|
_interface->CreateSoundBuffer(&secondaryDescription, &_secondary, 0);
|
||||||
|
_secondary->SetFrequency((uint)_frequency);
|
||||||
if(DirectSoundCreate(0, &ds, 0) != DS_OK) return term(), false;
|
_secondary->SetCurrentPosition(0);
|
||||||
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);
|
|
||||||
|
|
||||||
|
_ready = true;
|
||||||
clear();
|
clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
if(device.buffer) {
|
_ready = false;
|
||||||
delete[] device.buffer;
|
if(_buffer) { delete[] _buffer; _buffer = nullptr; }
|
||||||
device.buffer = nullptr;
|
if(_secondary) { _secondary->Stop(); _secondary->Release(); _secondary = nullptr; }
|
||||||
}
|
if(_primary) { _primary->Stop(); _primary->Release(); _primary = nullptr; }
|
||||||
|
if(_interface) { _interface->Release(); _interface = 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; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -67,7 +67,7 @@ struct AudioOSS : Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output(const double samples[]) -> void {
|
auto output(const double samples[]) -> void {
|
||||||
if(!_ready) return;
|
if(!ready()) return;
|
||||||
for(auto n : range(_channels)) {
|
for(auto n : range(_channels)) {
|
||||||
int16_t sample = samples[n] * 32768.0;
|
int16_t sample = samples[n] * 32768.0;
|
||||||
auto unused = write(_fd, &sample, 2);
|
auto unused = write(_fd, &sample, 2);
|
||||||
|
|
|
@ -1,159 +1,149 @@
|
||||||
#include <pulse/pulseaudio.h>
|
#include <pulse/pulseaudio.h>
|
||||||
|
|
||||||
struct AudioPulseAudio : Audio {
|
struct AudioPulseAudio : Audio {
|
||||||
~AudioPulseAudio() { term(); }
|
AudioPulseAudio() { initialize(); }
|
||||||
|
~AudioPulseAudio() { terminate(); }
|
||||||
|
|
||||||
struct {
|
auto ready() -> bool { return _ready; }
|
||||||
pa_mainloop* mainloop = nullptr;
|
|
||||||
pa_context* context = nullptr;
|
|
||||||
pa_stream* stream = nullptr;
|
|
||||||
pa_sample_spec spec;
|
|
||||||
pa_buffer_attr buffer_attr;
|
|
||||||
bool first;
|
|
||||||
} device;
|
|
||||||
|
|
||||||
struct {
|
auto information() -> Information {
|
||||||
uint32_t* data = nullptr;
|
Information information;
|
||||||
size_t size;
|
information.devices = {"Default"};
|
||||||
unsigned offset;
|
information.frequencies = {44100.0, 48000.0, 96000.0};
|
||||||
} buffer;
|
information.latencies = {20, 40, 60, 80, 100};
|
||||||
|
information.channels = {2};
|
||||||
struct {
|
return information;
|
||||||
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 get(const string& name) -> any {
|
auto blocking() -> bool { return _blocking; }
|
||||||
if(name == Audio::Synchronize) return settings.synchronize;
|
auto channels() -> uint { return 2; }
|
||||||
if(name == Audio::Frequency) return settings.frequency;
|
auto frequency() -> double { return _frequency; }
|
||||||
if(name == Audio::Latency) return settings.latency;
|
auto latency() -> uint { return _latency; }
|
||||||
return {};
|
|
||||||
|
auto setBlocking(bool blocking) -> bool {
|
||||||
|
if(_blocking == blocking) return true;
|
||||||
|
_blocking = blocking;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto set(const string& name, const any& value) -> bool {
|
auto setFrequency(double frequency) -> bool {
|
||||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
if(_frequency == frequency) return true;
|
||||||
settings.synchronize = value.get<bool>();
|
_frequency = frequency;
|
||||||
return true;
|
return initialize();
|
||||||
}
|
|
||||||
|
|
||||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
|
||||||
settings.frequency = value.get<unsigned>();
|
|
||||||
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<unsigned>()) {
|
|
||||||
settings.latency = value.get<unsigned>();
|
|
||||||
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 sample(int16_t left, int16_t right) -> void {
|
auto setLatency(uint latency) -> bool {
|
||||||
pa_stream_begin_write(device.stream, (void**)&buffer.data, &buffer.size);
|
if(_latency == latency) return true;
|
||||||
buffer.data[buffer.offset++] = (uint16_t)left << 0 | (uint16_t)right << 16;
|
_latency = latency;
|
||||||
if((buffer.offset + 1) * pa_frame_size(&device.spec) <= buffer.size) return;
|
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) {
|
while(true) {
|
||||||
if(device.first) {
|
if(_first) {
|
||||||
device.first = false;
|
_first = false;
|
||||||
pa_mainloop_iterate(device.mainloop, 0, NULL);
|
pa_mainloop_iterate(_mainLoop, 0, nullptr);
|
||||||
} else {
|
} else {
|
||||||
pa_mainloop_iterate(device.mainloop, 1, NULL);
|
pa_mainloop_iterate(_mainLoop, 1, nullptr);
|
||||||
}
|
}
|
||||||
unsigned length = pa_stream_writable_size(device.stream);
|
uint length = pa_stream_writable_size(_stream);
|
||||||
if(length >= buffer.offset * pa_frame_size(&device.spec)) break;
|
if(length >= buffer.offset * pa_frame_size(&_specification)) break;
|
||||||
if(settings.synchronize == false) {
|
if(!_blocking) {
|
||||||
buffer.offset = 0;
|
_offset = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pa_stream_write(device.stream, (const void*)buffer.data, buffer.offset * pa_frame_size(&device.spec), NULL, 0LL, PA_SEEK_RELATIVE);
|
pa_stream_write(_stream, (const void*)_buffer, _offset * pa_frame_size(&_specification), nullptr, 0LL, PA_SEEK_RELATIVE);
|
||||||
buffer.data = 0;
|
_buffer = nullptr;
|
||||||
buffer.offset = 0;
|
_offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void {
|
private:
|
||||||
}
|
auto initialize() -> bool {
|
||||||
|
terminate();
|
||||||
|
|
||||||
auto init() -> bool {
|
_mainLoop = pa_mainloop_new();
|
||||||
device.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_state_t contextState;
|
||||||
pa_context_connect(device.context, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
|
||||||
|
|
||||||
pa_context_state_t cstate;
|
|
||||||
do {
|
do {
|
||||||
pa_mainloop_iterate(device.mainloop, 1, NULL);
|
pa_mainloop_iterate(_mainLoop, 1, nullptr);
|
||||||
cstate = pa_context_get_state(device.context);
|
contextState = pa_context_get_state(_context);
|
||||||
if(!PA_CONTEXT_IS_GOOD(cstate)) return false;
|
if(!PA_CONTEXT_IS_GOOD(contextState)) return false;
|
||||||
} while(cstate != PA_CONTEXT_READY);
|
} while(contextState != PA_CONTEXT_READY);
|
||||||
|
|
||||||
device.spec.format = PA_SAMPLE_S16LE;
|
_specification.format = PA_SAMPLE_S16LE;
|
||||||
device.spec.channels = 2;
|
_specification.channels = 2;
|
||||||
device.spec.rate = settings.frequency;
|
_specification.rate = (uint)_frequency;
|
||||||
device.stream = pa_stream_new(device.context, "audio", &device.spec, NULL);
|
_stream = pa_stream_new(_context, "audio", &_specification, nullptr);
|
||||||
|
|
||||||
device.buffer_attr.maxlength = -1;
|
pa_buffer_attr bufferAttributes;
|
||||||
device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec);
|
bufferAttributes.maxlength = -1;
|
||||||
device.buffer_attr.prebuf = -1;
|
bufferAttributes.tlength = pa_usec_to_bytes(_latency * PA_USEC_PER_MSEC, &_specification);
|
||||||
device.buffer_attr.minreq = -1;
|
bufferAttributes.prebuf = -1;
|
||||||
device.buffer_attr.fragsize = -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_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 {
|
do {
|
||||||
pa_mainloop_iterate(device.mainloop, 1, NULL);
|
pa_mainloop_iterate(_mainLoop, 1, nullptr);
|
||||||
sstate = pa_stream_get_state(device.stream);
|
streamState = pa_stream_get_state(_stream);
|
||||||
if(!PA_STREAM_IS_GOOD(sstate)) return false;
|
if(!PA_STREAM_IS_GOOD(streamState)) return false;
|
||||||
} while(sstate != PA_STREAM_READY);
|
} while(streamState != PA_STREAM_READY);
|
||||||
|
|
||||||
buffer.size = 960;
|
_period = 960;
|
||||||
buffer.offset = 0;
|
_offset = 0;
|
||||||
device.first = true;
|
_first = true;
|
||||||
|
return _ready = true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
if(buffer.data) {
|
_ready = false;
|
||||||
pa_stream_cancel_write(device.stream);
|
|
||||||
buffer.data = nullptr;
|
if(_buffer) {
|
||||||
|
pa_stream_cancel_write(_buffer);
|
||||||
|
_buffer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(device.stream) {
|
if(_stream) {
|
||||||
pa_stream_disconnect(device.stream);
|
pa_stream_disconnect(_stream);
|
||||||
pa_stream_unref(device.stream);
|
pa_stream_unref(_stream);
|
||||||
device.stream = nullptr;
|
_stream = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(device.context) {
|
if(_context) {
|
||||||
pa_context_disconnect(device.context);
|
pa_context_disconnect(_context);
|
||||||
pa_context_unref(device.context);
|
pa_context_unref(_context);
|
||||||
device.context = nullptr;
|
_context = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(device.mainloop) {
|
if(_mainLoop) {
|
||||||
pa_mainloop_free(device.mainloop);
|
pa_mainloop_free(_mainLoop);
|
||||||
device.mainloop = nullptr;
|
_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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,94 +2,91 @@
|
||||||
#include <pulse/error.h>
|
#include <pulse/error.h>
|
||||||
|
|
||||||
struct AudioPulseAudioSimple : Audio {
|
struct AudioPulseAudioSimple : Audio {
|
||||||
~AudioPulseAudioSimple() { term(); }
|
AudioPulseAudio() { initialize(); }
|
||||||
|
~AudioPulseAudioSimple() { terminate(); }
|
||||||
|
|
||||||
struct {
|
auto ready() -> bool { return _ready; }
|
||||||
pa_simple* handle = nullptr;
|
|
||||||
pa_sample_spec spec;
|
|
||||||
} device;
|
|
||||||
|
|
||||||
struct {
|
auto information() -> Information {
|
||||||
uint32_t* data = nullptr;
|
Information information;
|
||||||
unsigned offset = 0;
|
information.devices = {"Default"};
|
||||||
} buffer;
|
information.frequencies = {44100.0, 48000.0, 96000.0};
|
||||||
|
information.latencies = {40};
|
||||||
struct {
|
information.channels = {2};
|
||||||
unsigned frequency = 48000;
|
return information;
|
||||||
} settings;
|
|
||||||
|
|
||||||
auto cap(const string& name) -> bool {
|
|
||||||
if(name == Audio::Frequency) return true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto get(const string& name) -> any {
|
auto blocking() -> bool { return true; }
|
||||||
if(name == Audio::Frequency) return settings.frequency;
|
auto channels() -> uint { return 2; }
|
||||||
return {};
|
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 {
|
auto output(const double samples[]) -> void {
|
||||||
if(name == Audio::Frequency && value.is<unsigned>()) {
|
if(!ready()) return;
|
||||||
settings.frequency = value.get<unsigned>();
|
|
||||||
if(device.handle) init();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
_buffer[_offset++] = uint16_t(samples[0] * 32768.0) << 0 | uint16_t(samples[1] * 32768.0) << 16;
|
||||||
}
|
if(_offset >= 64) {
|
||||||
|
|
||||||
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) {
|
|
||||||
int error;
|
int error;
|
||||||
pa_simple_write(device.handle, (const void*)buffer.data, buffer.offset * sizeof(uint32_t), &error);
|
pa_simple_write(_interface, (const void*)_buffer, _offset * sizeof(uint32_t), &error);
|
||||||
buffer.offset = 0;
|
_offset = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void {
|
private:
|
||||||
}
|
auto initialize() -> bool {
|
||||||
|
terminate();
|
||||||
|
|
||||||
auto init() -> bool {
|
pa_sample_spec specification;
|
||||||
device.spec.format = PA_SAMPLE_S16LE;
|
specification.format = PA_SAMPLE_S16LE;
|
||||||
device.spec.channels = 2;
|
specification.channels = 2;
|
||||||
device.spec.rate = settings.frequency;
|
specification.rate = (uint)_frequency;
|
||||||
|
|
||||||
int error = 0;
|
int error = 0;
|
||||||
device.handle = pa_simple_new(
|
_interface = pa_simple_new(
|
||||||
0, //default server
|
0, //default server
|
||||||
"ruby::pulseaudiosimple", //application name
|
"ruby::pulseAudioSimple", //application name
|
||||||
PA_STREAM_PLAYBACK, //direction
|
PA_STREAM_PLAYBACK, //direction
|
||||||
0, //default device
|
0, //default device
|
||||||
"audio", //stream description
|
"audio", //stream description
|
||||||
&device.spec, //sample format
|
&specification, //sample format
|
||||||
0, //default channel map
|
0, //default channel map
|
||||||
0, //default buffering attributes
|
0, //default buffering attributes
|
||||||
&error //error code
|
&error //error code
|
||||||
);
|
);
|
||||||
if(!device.handle) {
|
if(!_interface) return false;
|
||||||
fprintf(stderr, "ruby::pulseaudiosimple failed to initialize - %s\n", pa_strerror(error));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.data = new uint32_t[64];
|
_buffer = new uint32_t[64]();
|
||||||
buffer.offset = 0;
|
_offset = 0;
|
||||||
return true;
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
if(device.handle) {
|
_ready = false;
|
||||||
|
|
||||||
|
if(_interface) {
|
||||||
int error;
|
int error;
|
||||||
pa_simple_flush(device.handle, &error);
|
pa_simple_flush(_interface, &error);
|
||||||
pa_simple_free(device.handle);
|
pa_simple_free(_interface);
|
||||||
device.handle = nullptr;
|
_interface = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(buffer.data) {
|
if(_buffer) {
|
||||||
delete[] buffer.data;
|
delete[] _buffer;
|
||||||
buffer.data = nullptr;
|
_buffer = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _ready = false;
|
||||||
|
double _frequency = 48000.0;
|
||||||
|
|
||||||
|
pa_simple* _interface = nullptr;
|
||||||
|
|
||||||
|
uint32_t* _buffer = nullptr;
|
||||||
|
uint _offset = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -59,24 +59,28 @@ struct AudioWASAPI : Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear() -> void {
|
auto clear() -> void {
|
||||||
|
if(!ready()) return;
|
||||||
_queue.read = 0;
|
_queue.read = 0;
|
||||||
_queue.write = 0;
|
_queue.write = 0;
|
||||||
_queue.count = 0;
|
_queue.count = 0;
|
||||||
if(!_audioClient) return;
|
|
||||||
_audioClient->Stop();
|
_audioClient->Stop();
|
||||||
_audioClient->Reset();
|
_audioClient->Reset();
|
||||||
_audioClient->Start();
|
_audioClient->Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output(const double samples[]) -> void {
|
auto output(const double samples[]) -> void {
|
||||||
if(_queue.count < _bufferSize) {
|
if(!ready()) return;
|
||||||
for(uint n : range(_channels)) {
|
|
||||||
_queue.samples[_queue.write][n] = samples[n];
|
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;
|
_mode = waveFormat.SubFormat.Data1;
|
||||||
_precision = waveFormat.Format.wBitsPerSample;
|
_precision = waveFormat.Format.wBitsPerSample;
|
||||||
|
|
||||||
|
_ready = true;
|
||||||
clear();
|
clear();
|
||||||
return _ready = true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto terminate() -> void {
|
auto terminate() -> void {
|
||||||
|
_ready = false;
|
||||||
_devices.reset();
|
_devices.reset();
|
||||||
if(_audioClient) _audioClient->Stop();
|
if(_audioClient) _audioClient->Stop();
|
||||||
if(_renderClient) _renderClient->Release(), _renderClient = nullptr;
|
if(_renderClient) _renderClient->Release(), _renderClient = nullptr;
|
||||||
|
|
|
@ -1,30 +1,18 @@
|
||||||
#include "keyboard/carbon.cpp"
|
#include "keyboard/carbon.cpp"
|
||||||
|
|
||||||
struct InputCarbon : Input {
|
struct InputCarbon : Input {
|
||||||
InputKeyboardCarbon carbonKeyboard;
|
InputCarbon() : _keyboard(*this) { initialize(); }
|
||||||
InputCarbon() : carbonKeyboard(*this) {}
|
~InputCarbon() { terminate(); }
|
||||||
~InputCarbon() { term(); }
|
|
||||||
|
|
||||||
auto cap(const string& name) -> bool {
|
auto ready() -> bool { return _ready; }
|
||||||
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 acquired() -> bool { return false; }
|
||||||
auto acquire() -> bool { return false; }
|
auto acquire() -> bool { return false; }
|
||||||
auto release() -> bool { return false; }
|
auto release() -> bool { return false; }
|
||||||
auto acquired() -> bool { return false; }
|
|
||||||
|
|
||||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||||
vector<shared_pointer<HID::Device>> devices;
|
vector<shared_pointer<HID::Device>> devices;
|
||||||
carbonKeyboard.poll(devices);
|
_keyboard.poll(devices);
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,12 +20,19 @@ struct InputCarbon : Input {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
private:
|
||||||
if(!carbonKeyboard.init()) return false;
|
auto initialize() -> bool {
|
||||||
return true;
|
terminate();
|
||||||
|
if(!_keyboard.initialize()) return false;
|
||||||
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto term() -> void {
|
||||||
carbonKeyboard.term();
|
_ready = false;
|
||||||
|
_keyboard.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _ready = false;
|
||||||
|
|
||||||
|
InputKeyboardCarbon _keyboard;
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,14 +12,14 @@ struct InputJoypadUdev {
|
||||||
udev_list_entry* item = nullptr;
|
udev_list_entry* item = nullptr;
|
||||||
|
|
||||||
struct JoypadInput {
|
struct JoypadInput {
|
||||||
signed code = 0;
|
int code = 0;
|
||||||
unsigned id = 0;
|
uint id = 0;
|
||||||
int16_t value = 0;
|
int16_t value = 0;
|
||||||
input_absinfo info;
|
input_absinfo info;
|
||||||
|
|
||||||
JoypadInput() {}
|
JoypadInput() {}
|
||||||
JoypadInput(signed code) : code(code) {}
|
JoypadInput(int code) : code(code) {}
|
||||||
JoypadInput(signed code, unsigned id) : code(code), id(id) {}
|
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; }
|
||||||
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 keybit[(KEY_MAX + 7) / 8] = {0};
|
||||||
uint8_t absbit[(ABS_MAX + 7) / 8] = {0};
|
uint8_t absbit[(ABS_MAX + 7) / 8] = {0};
|
||||||
uint8_t ffbit[(FF_MAX + 7) / 8] = {0};
|
uint8_t ffbit[(FF_MAX + 7) / 8] = {0};
|
||||||
unsigned effects = 0;
|
uint effects = 0;
|
||||||
|
|
||||||
string name;
|
string name;
|
||||||
string manufacturer;
|
string manufacturer;
|
||||||
|
@ -49,11 +49,11 @@ struct InputJoypadUdev {
|
||||||
set<JoypadInput> hats;
|
set<JoypadInput> hats;
|
||||||
set<JoypadInput> buttons;
|
set<JoypadInput> buttons;
|
||||||
bool rumble = false;
|
bool rumble = false;
|
||||||
unsigned effectID = 0;
|
uint effectID = 0;
|
||||||
};
|
};
|
||||||
vector<Joypad> joypads;
|
vector<Joypad> joypads;
|
||||||
|
|
||||||
auto assign(shared_pointer<HID::Joypad> hid, unsigned groupID, unsigned inputID, int16_t value) -> void {
|
auto assign(shared_pointer<HID::Joypad> hid, uint groupID, uint inputID, int16_t value) -> void {
|
||||||
auto& group = hid->group(groupID);
|
auto& group = hid->group(groupID);
|
||||||
if(group.input(inputID).value() == value) return;
|
if(group.input(inputID).value() == value) return;
|
||||||
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
||||||
|
@ -65,21 +65,21 @@ struct InputJoypadUdev {
|
||||||
|
|
||||||
for(auto& jp : joypads) {
|
for(auto& jp : joypads) {
|
||||||
input_event events[32];
|
input_event events[32];
|
||||||
signed length = 0;
|
int length = 0;
|
||||||
while((length = read(jp.fd, events, sizeof(events))) > 0) {
|
while((length = read(jp.fd, events, sizeof(events))) > 0) {
|
||||||
length /= sizeof(input_event);
|
length /= sizeof(input_event);
|
||||||
for(unsigned i = 0; i < length; i++) {
|
for(uint i : range(length)) {
|
||||||
signed code = events[i].code;
|
int code = events[i].code;
|
||||||
signed type = events[i].type;
|
int type = events[i].type;
|
||||||
signed value = events[i].value;
|
int value = events[i].value;
|
||||||
|
|
||||||
if(type == EV_ABS) {
|
if(type == EV_ABS) {
|
||||||
if(auto input = jp.axes.find({code})) {
|
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;
|
value = (value - input().info.minimum) * 65535ll / range - 32767;
|
||||||
assign(jp.hid, HID::Joypad::GroupID::Axis, input().id, sclamp<16>(value));
|
assign(jp.hid, HID::Joypad::GroupID::Axis, input().id, sclamp<16>(value));
|
||||||
} else if(auto input = jp.hats.find({code})) {
|
} 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;
|
value = (value - input().info.minimum) * 65535ll / range - 32767;
|
||||||
assign(jp.hid, HID::Joypad::GroupID::Hat, input().id, sclamp<16>(value));
|
assign(jp.hid, HID::Joypad::GroupID::Hat, input().id, sclamp<16>(value));
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ struct InputJoypadUdev {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
auto initialize() -> bool {
|
||||||
context = udev_new();
|
context = udev_new();
|
||||||
if(context == nullptr) return false;
|
if(context == nullptr) return false;
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ struct InputJoypadUdev {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
if(enumerator) { udev_enumerate_unref(enumerator); enumerator = nullptr; }
|
if(enumerator) { udev_enumerate_unref(enumerator); enumerator = nullptr; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,10 +210,10 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned axes = 0;
|
uint axes = 0;
|
||||||
unsigned hats = 0;
|
uint hats = 0;
|
||||||
unsigned buttons = 0;
|
uint buttons = 0;
|
||||||
for(signed i = 0; i < ABS_MISC; i++) {
|
for(int i = 0; i < ABS_MISC; i++) {
|
||||||
if(testBit(jp.absbit, i)) {
|
if(testBit(jp.absbit, i)) {
|
||||||
if(i >= ABS_HAT0X && i <= ABS_HAT3Y) {
|
if(i >= ABS_HAT0X && i <= ABS_HAT3Y) {
|
||||||
if(auto hat = jp.hats.insert({i, hats++})) {
|
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)) {
|
if(testBit(jp.keybit, i)) {
|
||||||
jp.buttons.insert({i, buttons++});
|
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)) {
|
if(testBit(jp.keybit, i)) {
|
||||||
jp.buttons.insert({i, buttons++});
|
jp.buttons.insert({i, buttons++});
|
||||||
}
|
}
|
||||||
|
@ -259,14 +259,14 @@ private:
|
||||||
uint64_t pathID = Hash::CRC32(jp.deviceName.data(), jp.deviceName.size()).value();
|
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);
|
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(uint n : range(jp.axes.size())) jp.hid->axes().append(n);
|
||||||
for(unsigned n = 0; n < jp.hats.size(); n++) jp.hid->hats().append(n);
|
for(uint n : range(jp.hats.size())) jp.hid->hats().append(n);
|
||||||
for(unsigned n = 0; n < jp.buttons.size(); n++) jp.hid->buttons().append(n);
|
for(uint n : range(jp.buttons.size())) jp.hid->buttons().append(n);
|
||||||
jp.hid->setRumble(jp.rumble);
|
jp.hid->setRumble(jp.rumble);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto removeJoypad(udev_device* device, const string& deviceNode) -> void {
|
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) {
|
if(joypads[n].deviceNode == deviceNode) {
|
||||||
close(joypads[n].fd);
|
close(joypads[n].fd);
|
||||||
joypads.remove(n);
|
joypads.remove(n);
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct InputKeyboardCarbon {
|
||||||
devices.append(hid);
|
devices.append(hid);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
auto initialize() -> bool {
|
||||||
keys.append({0x35, "Escape"});
|
keys.append({0x35, "Escape"});
|
||||||
keys.append({0x7a, "F1"});
|
keys.append({0x7a, "F1"});
|
||||||
keys.append({0x78, "F2"});
|
keys.append({0x78, "F2"});
|
||||||
|
@ -153,6 +153,6 @@ struct InputKeyboardCarbon {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,7 @@ struct InputKeyboardQuartz {
|
||||||
devices.append(hid);
|
devices.append(hid);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
auto initialize() -> bool {
|
||||||
keys.append({"Escape", kVK_Escape});
|
keys.append({"Escape", kVK_Escape});
|
||||||
keys.append({"F1", kVK_F1});
|
keys.append({"F1", kVK_F1});
|
||||||
keys.append({"F2", kVK_F2});
|
keys.append({"F2", kVK_F2});
|
||||||
|
@ -148,6 +148,6 @@ struct InputKeyboardQuartz {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,30 +1,18 @@
|
||||||
#include "keyboard/quartz.cpp"
|
#include "keyboard/quartz.cpp"
|
||||||
|
|
||||||
struct InputQuartz : Input {
|
struct InputQuartz : Input {
|
||||||
InputKeyboardQuartz quartzKeyboard;
|
InputQuartz() : _keyboard(*this) { initialize(); }
|
||||||
InputQuartz() : quartzKeyboard(*this) {}
|
~InputQuartz() { terminate(); }
|
||||||
~InputQuartz() { term(); }
|
|
||||||
|
|
||||||
auto cap(const string& name) -> bool {
|
auto ready() -> bool { return _ready; }
|
||||||
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 acquired() -> bool { return false; }
|
||||||
auto acquire() -> bool { return false; }
|
auto acquire() -> bool { return false; }
|
||||||
auto release() -> bool { return false; }
|
auto release() -> bool { return false; }
|
||||||
auto acquired() -> bool { return false; }
|
|
||||||
|
|
||||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||||
vector<shared_pointer<HID::Device>> devices;
|
vector<shared_pointer<HID::Device>> devices;
|
||||||
quartzKeyboard.poll(devices);
|
_keyboard.poll(devices);
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,12 +20,18 @@ struct InputQuartz : Input {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto init() -> bool {
|
auto initialize() -> bool {
|
||||||
if(!quartzKeyboard.init()) return false;
|
terminate();
|
||||||
return true;
|
if(!_keyboard.init()) return false;
|
||||||
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
quartzKeyboard.term();
|
_ready = false;
|
||||||
|
_keyboard.term();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _ready = false;
|
||||||
|
|
||||||
|
InputKeyboardQuartz _keyboard;
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,6 +47,7 @@ struct InputSDL : Input {
|
||||||
private:
|
private:
|
||||||
auto initialize() -> bool {
|
auto initialize() -> bool {
|
||||||
terminate();
|
terminate();
|
||||||
|
if(!_context) return false;
|
||||||
if(!_keyboard.initialize()) return false;
|
if(!_keyboard.initialize()) return false;
|
||||||
if(!_mouse.initialize(_context)) return false;
|
if(!_mouse.initialize(_context)) return false;
|
||||||
if(!_joypad.initialize()) return false;
|
if(!_joypad.initialize()) return false;
|
||||||
|
|
|
@ -13,72 +13,64 @@
|
||||||
#include "joypad/udev.cpp"
|
#include "joypad/udev.cpp"
|
||||||
|
|
||||||
struct InputUdev : Input {
|
struct InputUdev : Input {
|
||||||
InputKeyboardXlib xlibKeyboard;
|
InputUdev() : _keyboard(*this), _mouse(*this), _joypad(*this) { initialize(); }
|
||||||
InputMouseXlib xlibMouse;
|
~InputUdev() { terminate(); }
|
||||||
InputJoypadUdev udev;
|
|
||||||
InputUdev() : xlibKeyboard(*this), xlibMouse(*this), udev(*this) {}
|
|
||||||
~InputUdev() { term(); }
|
|
||||||
|
|
||||||
struct Settings {
|
auto ready() -> bool { return _ready; }
|
||||||
uintptr_t handle = 0;
|
|
||||||
} settings;
|
|
||||||
|
|
||||||
auto cap(const string& name) -> bool {
|
auto context() -> uintptr { return _context; }
|
||||||
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 get(const string& name) -> any {
|
auto setContext(uintptr context) -> bool {
|
||||||
if(name == Input::Handle) return settings.handle;
|
if(_context == context) return true;
|
||||||
return {};
|
_context = context;
|
||||||
}
|
return initialize();
|
||||||
|
|
||||||
auto set(const string& name, const any& value) -> bool {
|
|
||||||
if(name == Input::Handle && value.is<uintptr_t>()) {
|
|
||||||
settings.handle = value.get<uintptr_t>();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto acquire() -> bool {
|
|
||||||
return xlibMouse.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto release() -> bool {
|
|
||||||
return xlibMouse.release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto acquired() -> bool {
|
auto acquired() -> bool {
|
||||||
return xlibMouse.acquired();
|
return _mouse.acquired();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto acquire() -> bool {
|
||||||
|
return _mouse.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto release() -> bool {
|
||||||
|
return _mouse.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto poll() -> vector<shared_pointer<HID::Device>> {
|
auto poll() -> vector<shared_pointer<HID::Device>> {
|
||||||
vector<shared_pointer<HID::Device>> devices;
|
vector<shared_pointer<HID::Device>> devices;
|
||||||
xlibKeyboard.poll(devices);
|
_keyboard.poll(devices);
|
||||||
xlibMouse.poll(devices);
|
_mouse.poll(devices);
|
||||||
udev.poll(devices);
|
_joypad.poll(devices);
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto rumble(uint64_t id, bool enable) -> bool {
|
auto rumble(uint64_t id, bool enable) -> bool {
|
||||||
return udev.rumble(id, enable);
|
return _joypad.rumble(id, enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
auto init() -> bool {
|
auto init() -> bool {
|
||||||
if(xlibKeyboard.init() == false) return false;
|
terminate();
|
||||||
if(xlibMouse.init(settings.handle) == false) return false;
|
if(!_context) return false;
|
||||||
if(udev.init() == false) return false;
|
if(!_keyboard.initialize()) return false;
|
||||||
return true;
|
if(!_mouse.initialize(_context)) return false;
|
||||||
|
if(!_joypad.initialize()) return false;
|
||||||
|
return _ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto term() -> void {
|
auto terminate() -> void {
|
||||||
xlibKeyboard.term();
|
_ready = false;
|
||||||
xlibMouse.term();
|
_keyboard.terminate();
|
||||||
udev.term();
|
_mouse.terminate();
|
||||||
|
_joypad.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _ready = false;
|
||||||
|
uintptr _context = 0;
|
||||||
|
|
||||||
|
InputKeyboardXlib _keyboard;
|
||||||
|
InputMouseXlib _mouse;
|
||||||
|
InputJoypadUdev _joypad;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue