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 {
|
||||
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/";
|
||||
|
|
|
@ -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,6 +60,7 @@ auto Cartridge::TAMA::read(uint16 address) -> uint8 {
|
|||
return 0xf0 | io.ready;
|
||||
}
|
||||
|
||||
if(io.mode == 0 || io.mode == 1) {
|
||||
if(io.select == 0x0c) {
|
||||
return 0xf0 | io.output.bits(0,3);
|
||||
}
|
||||
|
@ -30,6 +68,23 @@ auto Cartridge::TAMA::read(uint16 address) -> uint8 {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,212 +1,139 @@
|
|||
#include <alsa/asoundlib.h>
|
||||
|
||||
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 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 set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
||||
if(settings.synchronize != value.get<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
if(device.handle) init();
|
||||
}
|
||||
auto setBlocking(bool blocking) -> bool {
|
||||
if(_blocking == blocking) return true;
|
||||
_blocking = blocking;
|
||||
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;
|
||||
auto setFrequency(double frequency) -> bool {
|
||||
if(_frequency == frequency) return true;
|
||||
_frequency = frequency;
|
||||
return initialize();
|
||||
}
|
||||
|
||||
if(name == Audio::Latency && value.is<unsigned>()) {
|
||||
if(settings.latency != value.get<unsigned>()) {
|
||||
settings.latency = value.get<unsigned>();
|
||||
if(device.handle) init();
|
||||
}
|
||||
return true;
|
||||
auto setLatency(uint latency) -> bool {
|
||||
if(_latency == latency) return true;
|
||||
_latency = latency;
|
||||
return initialize();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
auto output(const double samples[]) -> void {
|
||||
if(!ready()) return;
|
||||
|
||||
auto sample(int16_t left, int16_t right) -> void {
|
||||
if(!device.handle) return;
|
||||
_buffer[_offset++] = uint16_t(samples[0] * 32768.0) << 0 | uint16_t(samples[1] * 32768.0) << 16;
|
||||
if(_offset < _periodSize) return;
|
||||
|
||||
buffer.data[buffer.length++] = (uint16_t)left << 0 | (uint16_t)right << 16;
|
||||
if(buffer.length < device.period_size) 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(_buffer) {
|
||||
delete[] _buffer;
|
||||
_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
bool _ready = false;
|
||||
bool _blocking = true;
|
||||
double _frequency = 48000.0;
|
||||
uint _latency = 40;
|
||||
|
||||
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_t* _interface = nullptr;
|
||||
snd_pcm_uframes_t _bufferSize;
|
||||
snd_pcm_uframes_t _periodSize;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
uint32_t* _buffer = nullptr;
|
||||
uint _offset = 0;
|
||||
};
|
||||
|
|
|
@ -1,73 +1,76 @@
|
|||
#include <ao/ao.h>
|
||||
|
||||
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<unsigned>()) {
|
||||
settings.frequency = value.get<unsigned>();
|
||||
if(audio_device) init();
|
||||
return true;
|
||||
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
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
auto initialize() -> bool {
|
||||
terminate();
|
||||
|
||||
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 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_info* information = ao_driver_info(driverID);
|
||||
if(!information) return false;
|
||||
_device = information->short_name;
|
||||
if(_device == "alsa") {
|
||||
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)
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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,21 +146,23 @@ 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;
|
||||
if(_asio) {
|
||||
_asio->stop();
|
||||
_asio->disposeBuffers();
|
||||
_asio->Release();
|
||||
_asio = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static auto _bufferSwitch(long doubleBufferInput, ASIOBool directProcess) -> void {
|
||||
|
|
|
@ -1,184 +1,172 @@
|
|||
#include <dsound.h>
|
||||
|
||||
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 set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Handle && value.is<uintptr_t>()) {
|
||||
settings.handle = (HWND)value.get<uintptr_t>();
|
||||
auto setBlocking(bool blocking) -> bool {
|
||||
if(_blocking == blocking) return true;
|
||||
_blocking = blocking;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
if(ds) clear();
|
||||
return true;
|
||||
auto setFrequency(double frequency) -> bool {
|
||||
if(_frequency == frequency) return true;
|
||||
_frequency = frequency;
|
||||
return initialize();
|
||||
}
|
||||
|
||||
if(name == Audio::Frequency && value.is<uint>()) {
|
||||
settings.frequency = value.get<uint>();
|
||||
if(ds) init();
|
||||
return true;
|
||||
auto setLatency(uint latency) -> bool {
|
||||
if(_latency == latency) return true;
|
||||
_latency = latency;
|
||||
return initialize();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
auto clear() -> void {
|
||||
if(!ready()) return;
|
||||
|
||||
return false;
|
||||
}
|
||||
_ringRead = 0;
|
||||
_ringWrite = _rings - 1;
|
||||
_ringDistance = _rings - 1;
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
auto clear() -> void {
|
||||
device.readring = 0;
|
||||
device.writering = device.rings - 1;
|
||||
device.distance = device.rings - 1;
|
||||
|
||||
device.bufferoffset = 0;
|
||||
if(device.buffer) memset(device.buffer, 0, device.latency * device.rings * 4);
|
||||
|
||||
if(!dsb_b) return;
|
||||
dsb_b->Stop();
|
||||
dsb_b->SetCurrentPosition(0);
|
||||
|
||||
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);
|
||||
|
||||
dsb_b->Play(0, 0, DSBPLAY_LOOPING);
|
||||
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 init() -> bool {
|
||||
settings.handle = GetDesktopWindow();
|
||||
private:
|
||||
auto initialize() -> bool {
|
||||
terminate();
|
||||
|
||||
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;
|
||||
_rings = 8;
|
||||
_period = _frequency * _latency / _rings / 1000.0 + 0.5;
|
||||
_buffer = new uint32_t[_period * _rings];
|
||||
_offset = 0;
|
||||
|
||||
if(DirectSoundCreate(0, &ds, 0) != DS_OK) return term(), false;
|
||||
ds->SetCooperativeLevel((HWND)settings.handle, DSSCL_PRIORITY);
|
||||
if(DirectSoundCreate(0, &_interface, 0) != DS_OK) return term(), false;
|
||||
_interface->SetCooperativeLevel(GetDesktopWindow(), 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);
|
||||
DSBUFFERDESC primaryDescription = {};
|
||||
primaryDescription.dwSize = sizeof(DSBUFFERDESC);
|
||||
primaryDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||
primaryDescription.dwBufferBytes = 0;
|
||||
primaryDescription.lpwfxFormat = 0;
|
||||
_interface->CreateSoundBuffer(&primaryDescription, &_primary, 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);
|
||||
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);
|
||||
|
||||
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;
|
||||
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; }
|
||||
}
|
||||
|
||||
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 {
|
||||
if(!_ready) return;
|
||||
if(!ready()) return;
|
||||
for(auto n : range(_channels)) {
|
||||
int16_t sample = samples[n] * 32768.0;
|
||||
auto unused = write(_fd, &sample, 2);
|
||||
|
|
|
@ -1,159 +1,149 @@
|
|||
#include <pulse/pulseaudio.h>
|
||||
|
||||
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 set(const string& name, const any& value) -> bool {
|
||||
if(name == Audio::Synchronize && value.is<bool>()) {
|
||||
settings.synchronize = value.get<bool>();
|
||||
auto setBlocking(bool blocking) -> bool {
|
||||
if(_blocking == blocking) return true;
|
||||
_blocking = blocking;
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
auto setFrequency(double frequency) -> bool {
|
||||
if(_frequency == frequency) return true;
|
||||
_frequency = frequency;
|
||||
return initialize();
|
||||
}
|
||||
|
||||
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;
|
||||
auto setLatency(uint latency) -> bool {
|
||||
if(_latency == latency) return true;
|
||||
_latency = latency;
|
||||
return initialize();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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 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;
|
||||
};
|
||||
|
|
|
@ -2,94 +2,91 @@
|
|||
#include <pulse/error.h>
|
||||
|
||||
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<unsigned>()) {
|
||||
settings.frequency = value.get<unsigned>();
|
||||
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 = new uint32_t[64]();
|
||||
_offset = 0;
|
||||
return _ready = true;
|
||||
}
|
||||
|
||||
buffer.data = new uint32_t[64];
|
||||
buffer.offset = 0;
|
||||
return true;
|
||||
}
|
||||
auto terminate() -> void {
|
||||
_ready = false;
|
||||
|
||||
auto term() -> void {
|
||||
if(device.handle) {
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -59,26 +59,30 @@ 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) {
|
||||
if(!ready()) return;
|
||||
|
||||
for(uint n : range(_channels)) {
|
||||
_queue.samples[_queue.write][n] = samples[n];
|
||||
}
|
||||
_queue.write++;
|
||||
_queue.count++;
|
||||
} else if(WaitForSingleObject(_eventHandle, _blocking ? INFINITE : 0) == WAIT_OBJECT_0) {
|
||||
|
||||
if(_queue.count >= _bufferSize) {
|
||||
if(WaitForSingleObject(_eventHandle, _blocking ? INFINITE : 0) == WAIT_OBJECT_0) {
|
||||
write();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
auto initialize() -> bool {
|
||||
|
@ -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;
|
||||
|
|
|
@ -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<shared_pointer<HID::Device>> {
|
||||
vector<shared_pointer<HID::Device>> 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;
|
||||
};
|
||||
|
|
|
@ -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<JoypadInput> hats;
|
||||
set<JoypadInput> buttons;
|
||||
bool rumble = false;
|
||||
unsigned effectID = 0;
|
||||
uint effectID = 0;
|
||||
};
|
||||
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);
|
||||
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);
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<shared_pointer<HID::Device>> {
|
||||
vector<shared_pointer<HID::Device>> 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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<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 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<shared_pointer<HID::Device>> {
|
||||
vector<shared_pointer<HID::Device>> 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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue