mirror of https://github.com/bsnes-emu/bsnes.git
283 lines
8.2 KiB
C++
283 lines
8.2 KiB
C++
#include "asio.hpp"
|
|
|
|
struct AudioASIO : Audio {
|
|
static AudioASIO* self;
|
|
AudioASIO() { self = this; initialize(); }
|
|
~AudioASIO() { terminate(); }
|
|
|
|
auto ready() -> bool { return _ready; }
|
|
|
|
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 context() -> uintptr { return _context; }
|
|
auto device() -> string { return _device; }
|
|
auto blocking() -> bool { return _blocking; }
|
|
auto channels() -> uint { return _channels; }
|
|
auto frequency() -> double { return _frequency; }
|
|
auto latency() -> uint { return _latency; }
|
|
|
|
auto setContext(uintptr context) -> bool {
|
|
if(_context == context) return true;
|
|
_context = context;
|
|
return initialize();
|
|
}
|
|
|
|
auto setDevice(string device) -> bool {
|
|
if(_device == device) return true;
|
|
_device = device;
|
|
return initialize();
|
|
}
|
|
|
|
auto setBlocking(bool blocking) -> bool {
|
|
if(_blocking == blocking) return true;
|
|
_blocking = blocking;
|
|
return initialize();
|
|
}
|
|
|
|
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 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 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++;
|
|
}
|
|
|
|
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"})) {
|
|
_devices.append({candidate.trimRight("\\", 1L), classID});
|
|
if(candidate == _device) _active = _devices.right();
|
|
}
|
|
}
|
|
if(!_devices) return false;
|
|
|
|
if(!_active.name) {
|
|
_active = _devices.left();
|
|
_device = _active.name;
|
|
}
|
|
|
|
CLSID classID;
|
|
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(!_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;
|
|
|
|
_frequency = _active.sampleRate;
|
|
_latency = _latency < _active.minimumBufferSize ? _active.minimumBufferSize : _latency;
|
|
_latency = _latency > _active.maximumBufferSize ? _active.maximumBufferSize : _latency;
|
|
|
|
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
|
|
}
|
|
|
|
_ready = true;
|
|
clear();
|
|
if(_asio->start() != ASE_OK) return _ready = false;
|
|
return true;
|
|
}
|
|
|
|
auto terminate() -> void {
|
|
_ready = false;
|
|
_devices.reset();
|
|
_active = {};
|
|
if(_asio) {
|
|
_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;
|
|
double _frequency = 48000.0;
|
|
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;
|