Update to v103r15 release.

byuu says:

Changelog:

  - ruby: rewrote the API interfaces for Video, Audio, Input
  - ruby/audio: can now select the number of output channels (not useful
    to higan, sorry)
  - ruby/asio: various improvements
  - tomoko: audio settings panel can now select separate audio devices
    (for ASIO, OSS so far)
  - tomoko: audio settings panel frequency and latency lists are
    dynamically populated now

Note: due to the ruby API rewrite, most drivers will not compile. Right
now, the following work:

  - video: Direct3D, XShm
  - audio: ASIO, OSS
  - input: Windows, SDL, Xlib

It takes a really long time to rewrite these (six hours to do the
above), so it's going to be a while before we're back at 100%
functionality again.

Errata:

  - ASIO needs device(), setDevice()
  - need to call setDevice() at program startup to populate
    frequency/latency settings properly
  - changing the device and/or frequency needs to update the emulator
    resampler rates

The really hard part is going to be the last one: the only way to change
the emulator frequency is to flush all the audio streams and then
recompute all the coefficients for the resamplers. If this is called
during emulation, all audio streams will be erased and thus no sound
will be output. I'll most likely be forced to simply ignore
device/frequency changes until the user loads another game. It is at
least possible to toggle the latency dynamically.
This commit is contained in:
Tim Allen
2017-07-17 15:11:18 +10:00
parent 17697317d4
commit 4129630d97
29 changed files with 998 additions and 886 deletions

View File

@@ -1,90 +1,273 @@
#include "asio.hpp"
struct AudioASIO : Audio {
~AudioASIO() { term(); }
static AudioASIO* self;
AudioASIO() { self = this; initialize(); }
~AudioASIO() { terminate(); }
struct Settings {
HWND handle = nullptr;
bool synchronize = true;
uint frequency = 48000;
} settings;
auto ready() -> bool { return _ready; }
struct Driver {
string name;
string classID;
};
vector<Driver> drivers;
Driver driver;
IASIO* device = nullptr;
auto cap(const string& name) -> bool {
if(name == Audio::Handle) return true;
if(name == Audio::Synchronize) return true;
if(name == Audio::Frequency) return true;
return false;
auto information() -> Information {
Information information;
for(auto& device : _devices) information.devices.append(device.name);
information.frequencies = {_frequency};
uint latencies[] = {64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 6144}; //factors of 6144
for(auto& latency : latencies) {
if(latency < _active.minimumBufferSize) continue;
if(latency > _active.maximumBufferSize) continue;
information.latencies.append(latency);
}
information.channels = {1, 2};
return information;
}
auto get(const string& name) -> any {
if(name == Audio::Handle) return (uintptr)settings.handle;
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
return {};
auto context() -> uintptr { return _context; }
auto blocking() -> bool { return _blocking; }
auto channels() -> uint { return _channels; }
auto frequency() -> uint { return _frequency; }
auto latency() -> uint { return _latency; }
auto setContext(uintptr context) -> bool {
if(_context == context) return true;
_context = context;
return initialize();
}
auto set(const string& name, const any& value) -> bool {
if(name == Audio::Handle && value.is<uintptr>()) {
settings.handle = (HWND)value.get<uintptr>();
return true;
}
if(name == Audio::Synchronize && value.is<bool>()) {
settings.synchronize = value.get<bool>();
return true;
}
if(name == Audio::Frequency && value.is<uint>()) {
settings.frequency = value.get<uint>();
return true;
}
return false;
auto setBlocking(bool blocking) -> bool {
if(_blocking == blocking) return true;
_blocking = blocking;
return initialize();
}
auto sample(int16_t left, int16_t right) -> void {
auto setChannels(uint channels) -> bool {
if(_channels == channels) return true;
_channels = channels;
return initialize();
}
auto setLatency(uint latency) -> bool {
if(_latency == latency) return true;
_latency = latency;
return initialize();
}
auto output(const double samples[]) -> void {
if(!_ready) return;
if(_blocking) {
while(_queue.count >= _latency);
}
for(uint n : range(_channels)) {
_queue.samples[_queue.write][n] = samples[n];
}
_queue.write++;
_queue.count++;
}
auto clear() -> void {
if(!_ready) return;
for(uint n : range(_channels)) {
memory::fill(_channel[n].buffers[0], _latency * _sampleSize);
memory::fill(_channel[n].buffers[1], _latency * _sampleSize);
}
memory::fill(_queue.samples, sizeof(_queue.samples));
_queue.read = 0;
_queue.write = 0;
_queue.count = 0;
}
auto init() -> bool {
private:
auto initialize() -> bool {
terminate();
//enumerate available ASIO drivers from the registry
for(auto candidate : registry::contents("HKLM\\SOFTWARE\\ASIO\\")) {
if(auto classID = registry::read({"HKLM\\SOFTWARE\\ASIO\\", candidate, "CLSID"})) {
drivers.append({candidate.trimRight("\\", 1L), classID});
_devices.append({candidate.trimRight("\\", 1L), classID});
if(candidate == _device) _active = _devices.right();
}
}
if(!drivers) return false;
if(!_devices) return false;
//default to first driver for now
driver = drivers[0];
if(!_active.name) {
_active = _devices.left();
_device = _active.name;
}
CLSID classID;
if(CLSIDFromString((LPOLESTR)utf16_t(driver.classID), (LPCLSID)&classID) != S_OK) return false;
if(CoCreateInstance(classID, 0, CLSCTX_INPROC_SERVER, classID, (void**)&device) != S_OK) return false;
if(CLSIDFromString((LPOLESTR)utf16_t(_active.classID), (LPCLSID)&classID) != S_OK) return false;
if(CoCreateInstance(classID, 0, CLSCTX_INPROC_SERVER, classID, (void**)&_asio) != S_OK) return false;
if(!device->init((void*)settings.handle)) return false;
if(!_asio->init((void*)_context)) return false;
if(_asio->getSampleRate(&_active.sampleRate) != ASE_OK) return false;
if(_asio->getChannels(&_active.inputChannels, &_active.outputChannels) != ASE_OK) return false;
if(_asio->getBufferSize(
&_active.minimumBufferSize,
&_active.maximumBufferSize,
&_active.preferredBufferSize,
&_active.granularity
) != ASE_OK) return false;
//temporary debugging information
char driverName[4096] = {0};
device->getDriverName(driverName);
print("Driver: ", driverName, "\n");
print("Version: ", device->getDriverVersion(), "\n");
print("---\n");
_frequency = _active.sampleRate;
_latency = _latency < _active.minimumBufferSize ? _active.minimumBufferSize : _latency;
_latency = _latency > _active.maximumBufferSize ? _active.maximumBufferSize : _latency;
return true;
for(auto n : range(_channels)) {
_channel[n].isInput = false;
_channel[n].channelNum = n;
_channel[n].buffers[0] = nullptr;
_channel[n].buffers[1] = nullptr;
}
ASIOCallbacks callbacks;
callbacks.bufferSwitch = &AudioASIO::_bufferSwitch;
callbacks.sampleRateDidChange = &AudioASIO::_sampleRateDidChange;
callbacks.asioMessage = &AudioASIO::_asioMessage;
callbacks.bufferSwitchTimeInfo = &AudioASIO::_bufferSwitchTimeInfo;
if(_asio->createBuffers(_channel, _channels, _latency, &callbacks) != ASE_OK) return false;
if(_asio->getLatencies(&_active.inputLatency, &_active.outputLatency) != ASE_OK) return false;
//assume for the sake of sanity that all buffers use the same sample format ...
ASIOChannelInfo channelInformation = {};
channelInformation.channel = 0;
channelInformation.isInput = false;
if(_asio->getChannelInfo(&channelInformation) != ASE_OK) return false;
switch(_sampleFormat = channelInformation.type) {
case ASIOSTInt16LSB: _sampleSize = 2; break;
case ASIOSTInt24LSB: _sampleSize = 3; break;
case ASIOSTInt32LSB: _sampleSize = 4; break;
case ASIOSTFloat32LSB: _sampleSize = 4; break;
case ASIOSTFloat64LSB: _sampleSize = 8; break;
default: return false; //unsupported sample format
}
clear();
if(_asio->start() != ASE_OK) return false;
return _ready = true;
}
auto term() -> void {
auto terminate() -> void {
_ready = false;
_devices.reset();
_active = {};
if(!_asio) return;
_asio->stop();
_asio->disposeBuffers();
_asio->Release();
_asio = nullptr;
}
private:
static auto _bufferSwitch(long doubleBufferInput, ASIOBool directProcess) -> void {
return self->bufferSwitch(doubleBufferInput, directProcess);
}
static auto _sampleRateDidChange(ASIOSampleRate sampleRate) -> void {
return self->sampleRateDidChange(sampleRate);
}
static auto _asioMessage(long selector, long value, void* message, double* optional) -> long {
return self->asioMessage(selector, value, message, optional);
}
static auto _bufferSwitchTimeInfo(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime* {
return self->bufferSwitchTimeInfo(parameters, doubleBufferIndex, directProcess);
}
auto bufferSwitch(long doubleBufferInput, ASIOBool directProcess) -> void {
for(uint sampleIndex : range(_latency)) {
double samples[8] = {0};
if(_queue.count) {
for(uint n : range(_channels)) {
samples[n] = _queue.samples[_queue.read][n];
}
_queue.read++;
_queue.count--;
}
for(uint n : range(_channels)) {
auto buffer = (uint8_t*)_channel[n].buffers[doubleBufferInput];
buffer += sampleIndex * _sampleSize;
switch(_sampleFormat) {
case ASIOSTInt16LSB: {
*(int16_t*)buffer = samples[n] * double(1 << 15);
break;
}
case ASIOSTInt24LSB: {
int value = samples[n] * double(1 << 23);
buffer[0] = value >> 0;
buffer[1] = value >> 8;
buffer[2] = value >> 16;
break;
}
case ASIOSTInt32LSB: {
*(int32_t*)buffer = samples[n] * double(1 << 31);
break;
}
case ASIOSTFloat32LSB: {
*(float*)buffer = samples[n];
break;
}
case ASIOSTFloat64LSB: {
*(double*)buffer = samples[n];
break;
}
}
}
}
}
auto sampleRateDidChange(ASIOSampleRate sampleRate) -> void {
}
auto asioMessage(long selector, long value, void* message, double* optional) -> long {
return ASE_OK;
}
auto bufferSwitchTimeInfo(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime* {
return nullptr;
}
bool _ready = false;
uintptr _context = 0;
string _device;
bool _blocking = true;
uint _channels = 2;
uint _frequency = 48000;
uint _latency = 0;
struct Queue {
double samples[65536][8];
uint16_t read;
uint16_t write;
std::atomic<uint16_t> count;
};
struct Device {
string name;
string classID;
ASIOSampleRate sampleRate;
long inputChannels;
long outputChannels;
long inputLatency;
long outputLatency;
long minimumBufferSize;
long maximumBufferSize;
long preferredBufferSize;
long granularity;
};
Queue _queue;
vector<Device> _devices;
Device _active;
IASIO* _asio = nullptr;
ASIOBufferInfo _channel[8];
long _sampleFormat;
long _sampleSize;
};
AudioASIO* AudioASIO::self = nullptr;

View File

@@ -52,14 +52,6 @@ enum : long {
ASIOSTLastEntry,
};
struct ASIODriverInfo {
long asioVersion;
long driverVersion;
char name[32];
char errorMessage[124];
void* sysRef;
};
struct ASIOBufferInfo {
ASIOBool isInput;
long channelNum;
@@ -123,9 +115,9 @@ struct ASIOTime {
struct ASIOCallbacks {
auto (*bufferSwitch)(long doubleBufferIndex, ASIOBool directProcess) -> void;
auto (*sampleRateDidChange)(ASIOSampleRate sRate) -> void;
auto (*asioMessage)(long selector, long value, void* message, double* opt) -> long;
auto (*bufferSwitchTimeInfo)(ASIOTime* params, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime*;
auto (*sampleRateDidChange)(ASIOSampleRate sampleRate) -> void;
auto (*asioMessage)(long selector, long value, void* message, double* optional) -> long;
auto (*bufferSwitchTimeInfo)(ASIOTime* parameters, long doubleBufferIndex, ASIOBool directProcess) -> ASIOTime*;
};
enum : long {
kAsioSelectorSupported = 1,
@@ -147,25 +139,25 @@ enum : long {
};
struct IASIO : public IUnknown {
virtual auto init(void* sysHandle) -> ASIOBool;
virtual auto init(void* systemHandle) -> ASIOBool;
virtual auto getDriverName(char* name) -> void;
virtual auto getDriverVersion() -> long;
virtual auto getErrorMessage(char* error) -> void;
virtual auto start() -> ASIOError;
virtual auto stop() -> ASIOError;
virtual auto getChannels(long* numInputChannels, long* numOutputChannels) -> ASIOError = 0;
virtual auto getChannels(long* inputChannels, long* outputChannels) -> ASIOError = 0;
virtual auto getLatencies(long* inputLatency, long* outputLatency) -> ASIOError = 0;
virtual auto getBufferSize(long* minSize, long* maxSize, long* preferredSize, long* granularity) -> ASIOError = 0;
virtual auto getBufferSize(long* minimumSize, long* maximumSize, long* preferredSize, long* granularity) -> ASIOError = 0;
virtual auto canSampleRate(ASIOSampleRate sampleRate) -> ASIOError = 0;
virtual auto getSampleRate(ASIOSampleRate* sampleRate) -> ASIOError = 0;
virtual auto setSampleRate(ASIOSampleRate sampleRate) -> ASIOError = 0;
virtual auto getClockSources(ASIOClockSource* clocks, long* numSources) -> ASIOError = 0;
virtual auto getClockSources(ASIOClockSource* clocks, long* sources) -> ASIOError = 0;
virtual auto setClockSource(long reference) -> ASIOError = 0;
virtual auto getSamplePosition(ASIOSamples* sPos, ASIOTimeStamp* tStamp) -> ASIOError = 0;
virtual auto getChannelInfo(ASIOChannelInfo* info) -> ASIOError = 0;
virtual auto createBuffers(ASIOBufferInfo* bufferInfos, long numChannels, long bufferSize, ASIOCallbacks* callbacks) -> ASIOError = 0;
virtual auto getSamplePosition(ASIOSamples* samplePosition, ASIOTimeStamp* timeStamp) -> ASIOError = 0;
virtual auto getChannelInfo(ASIOChannelInfo* information) -> ASIOError = 0;
virtual auto createBuffers(ASIOBufferInfo* bufferInformation, long channels, long bufferSize, ASIOCallbacks* callbacks) -> ASIOError = 0;
virtual auto disposeBuffers() -> ASIOError = 0;
virtual auto controlPanel() -> ASIOError = 0;
virtual auto future(long selector, void* opt) -> ASIOError = 0;
virtual auto future(long selector, void* optional) -> ASIOError = 0;
virtual auto outputReady() -> ASIOError = 0;
};

View File

@@ -14,104 +14,108 @@
#endif
struct AudioOSS : Audio {
~AudioOSS() { term(); }
AudioOSS() { initialize(); }
~AudioOSS() { terminate(); }
struct {
int fd = -1;
int format = AFMT_S16_LE;
int channels = 2;
} device;
auto ready() -> bool { return _ready; }
struct {
string device = "/dev/dsp";
bool synchronize = true;
uint frequency = 48000;
uint latency = 60;
} settings;
auto cap(const string& name) -> bool {
if(name == Audio::Device) 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 = {"/dev/dsp"};
for(auto& device : directory::files("/dev/", "dsp?*")) information.devices.append(string{"/dev/", device});
information.frequencies = {44100, 48000, 96000};
information.latencies = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
information.channels = {1, 2};
return information;
}
auto get(const string& name) -> any {
if(name == Audio::Device) return settings.device;
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
if(name == Audio::Latency) return settings.latency;
return {};
auto device() -> string { return _device; }
auto blocking() -> bool { return _blocking; }
auto channels() -> uint { return _channels; }
auto frequency() -> uint { return _frequency; }
auto latency() -> uint { return _latency; }
auto setDevice(string device) -> bool {
if(_device == device) return true;
_device = device;
return initialize();
}
auto set(const string& name, const any& value) -> bool {
if(name == Audio::Device && value.is<string>()) {
settings.device = value.get<string>();
if(!settings.device) settings.device = "/dev/dsp";
return true;
}
if(name == Audio::Synchronize && value.is<bool>()) {
settings.synchronize = value.get<bool>();
updateSynchronization();
return true;
}
if(name == Audio::Frequency && value.is<uint>()) {
settings.frequency = value.get<uint>();
if(device.fd >= 0) init();
return true;
}
if(name == Audio::Latency && value.is<uint>()) {
settings.latency = value.get<uint>();
if(device.fd >= 0) init();
return true;
}
return false;
}
auto sample(int16_t left, int16_t right) -> void {
uint32_t sample = (uint16_t)left << 0 | (uint16_t)right << 16;
auto unused = write(device.fd, &sample, 4);
}
auto clear() -> void {
}
auto init() -> bool {
device.fd = open(settings.device, O_WRONLY, O_NONBLOCK);
if(device.fd < 0) return false;
int cooked = 1;
ioctl(device.fd, SNDCTL_DSP_COOKEDMODE, &cooked);
//policy: 0 = minimum latency (higher CPU usage); 10 = maximum latency (lower CPU usage)
int policy = min(10, settings.latency / 20); //note: latency measurement isn't exact
ioctl(device.fd, SNDCTL_DSP_POLICY, &policy);
int frequency = settings.frequency;
ioctl(device.fd, SNDCTL_DSP_CHANNELS, &device.channels);
ioctl(device.fd, SNDCTL_DSP_SETFMT, &device.format);
ioctl(device.fd, SNDCTL_DSP_SPEED, &frequency);
updateSynchronization();
auto setBlocking(bool blocking) -> bool {
if(_blocking == blocking) return true;
_blocking = blocking;
updateBlocking();
return true;
}
auto term() -> void {
if(device.fd >= 0) {
close(device.fd);
device.fd = -1;
auto setChannels(uint channels) -> bool {
if(_channels == channels) return true;
_channels = channels;
return initialize();
}
auto setFrequency(uint frequency) -> bool {
if(_frequency == frequency) return true;
_frequency = frequency;
return initialize();
}
auto setLatency(uint latency) -> bool {
if(_latency == latency) return true;
_latency = latency;
return initialize();
}
auto output(const double samples[]) -> void {
if(!_ready) return;
for(auto n : range(_channels)) {
int16_t sample = samples[n] * 32768.0;
auto unused = write(_fd, &sample, 2);
}
}
private:
auto updateSynchronization() -> void {
if(device.fd < 0) return;
auto flags = fcntl(device.fd, F_GETFL);
if(flags < 0) return;
settings.synchronize ? flags &=~ O_NONBLOCK : flags |= O_NONBLOCK;
fcntl(device.fd, F_SETFL, flags);
auto initialize() -> bool {
terminate();
_fd = open(_device, O_WRONLY, O_NONBLOCK);
if(_fd < 0) return false;
int cooked = 1;
ioctl(_fd, SNDCTL_DSP_COOKEDMODE, &cooked);
//policy: 0 = minimum latency (higher CPU usage); 10 = maximum latency (lower CPU usage)
int policy = min(10, _latency);
ioctl(_fd, SNDCTL_DSP_POLICY, &policy);
ioctl(_fd, SNDCTL_DSP_CHANNELS, &_channels);
ioctl(_fd, SNDCTL_DSP_SETFMT, &_format);
ioctl(_fd, SNDCTL_DSP_SPEED, &_frequency);
updateBlocking();
return _ready = true;
}
auto terminate() -> void {
_ready = false;
if(_fd < 0) return;
close(_fd);
_fd = -1;
}
auto updateBlocking() -> void {
if(!_ready) return;
auto flags = fcntl(_fd, F_GETFL);
if(flags < 0) return;
_blocking ? flags &=~ O_NONBLOCK : flags |= O_NONBLOCK;
fcntl(_fd, F_SETFL, flags);
}
bool _ready = false;
string _device = "/dev/dsp";
bool _blocking = true;
int _channels = 2;
int _frequency = 48000;
int _latency = 2;
int _fd = -1;
int _format = AFMT_S16_LE;
};