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:
Tim Allen 2017-07-28 21:42:24 +10:00
parent 80841deaa5
commit e1223366a7
19 changed files with 728 additions and 715 deletions

View File

@ -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/";

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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:

View File

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

View File

@ -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);

View File

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

View File

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

View File

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

View File

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

View File

@ -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);

View File

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

View File

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

View File

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

View File

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

View File

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