2010-08-09 13:28:56 +00:00
|
|
|
#include <alsa/asoundlib.h>
|
|
|
|
|
|
|
|
namespace ruby {
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
struct pAudioALSA {
|
2010-08-09 13:28:56 +00:00
|
|
|
struct {
|
2015-06-15 22:16:43 +00:00
|
|
|
snd_pcm_t* handle = nullptr;
|
|
|
|
snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
|
2010-08-09 13:28:56 +00:00
|
|
|
snd_pcm_uframes_t buffer_size;
|
|
|
|
snd_pcm_uframes_t period_size;
|
2015-06-15 22:16:43 +00:00
|
|
|
int channels = 2;
|
|
|
|
const char* name = "default";
|
2010-08-09 13:28:56 +00:00
|
|
|
} device;
|
|
|
|
|
|
|
|
struct {
|
2015-06-15 22:16:43 +00:00
|
|
|
uint32_t* data = nullptr;
|
|
|
|
unsigned length = 0;
|
2010-08-09 13:28:56 +00:00
|
|
|
} buffer;
|
|
|
|
|
|
|
|
struct {
|
2015-06-15 22:16:43 +00:00
|
|
|
bool synchronize = false;
|
|
|
|
unsigned frequency = 22050;
|
|
|
|
unsigned latency = 60;
|
2010-08-09 13:28:56 +00:00
|
|
|
} settings;
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
~pAudioALSA() {
|
|
|
|
term();
|
|
|
|
}
|
|
|
|
|
|
|
|
auto cap(const string& name) -> bool {
|
2010-08-09 13:28:56 +00:00
|
|
|
if(name == Audio::Synchronize) return true;
|
|
|
|
if(name == Audio::Frequency) return true;
|
|
|
|
if(name == Audio::Latency) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
auto get(const string& name) -> any {
|
2010-08-09 13:28:56 +00:00
|
|
|
if(name == Audio::Synchronize) return settings.synchronize;
|
|
|
|
if(name == Audio::Frequency) return settings.frequency;
|
|
|
|
if(name == Audio::Latency) return settings.latency;
|
2015-06-15 22:16:43 +00:00
|
|
|
return {};
|
2010-08-09 13:28:56 +00:00
|
|
|
}
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
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>();
|
2010-08-09 13:28:56 +00:00
|
|
|
if(device.handle) init();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
if(name == Audio::Frequency && value.is<unsigned>()) {
|
|
|
|
if(settings.frequency != value.get<unsigned>()) {
|
|
|
|
settings.frequency = value.get<unsigned>();
|
2010-08-09 13:28:56 +00:00
|
|
|
if(device.handle) init();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
if(name == Audio::Latency && value.is<unsigned>()) {
|
|
|
|
if(settings.latency != value.get<unsigned>()) {
|
|
|
|
settings.latency = value.get<unsigned>();
|
2010-08-09 13:28:56 +00:00
|
|
|
if(device.handle) init();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
auto sample(uint16_t left, uint16_t right) -> void {
|
2010-08-09 13:28:56 +00:00
|
|
|
if(!device.handle) return;
|
|
|
|
|
|
|
|
buffer.data[buffer.length++] = left + (right << 16);
|
|
|
|
if(buffer.length < device.period_size) return;
|
|
|
|
|
|
|
|
snd_pcm_sframes_t avail;
|
|
|
|
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;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
int error = snd_pcm_wait(device.handle, -1);
|
|
|
|
if(error < 0) snd_pcm_recover(device.handle, error, 1);
|
|
|
|
}
|
|
|
|
} while(avail < buffer.length);
|
|
|
|
|
|
|
|
//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
|
|
|
|
|
2013-05-02 11:25:45 +00:00
|
|
|
uint32_t* buffer_ptr = buffer.data;
|
2010-08-09 13:28:56 +00:00
|
|
|
int i = 4;
|
|
|
|
|
|
|
|
while((buffer.length > 0) && i--) {
|
|
|
|
snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length);
|
|
|
|
if(written < 0) {
|
|
|
|
//no samples written
|
|
|
|
snd_pcm_recover(device.handle, written, 1);
|
|
|
|
} else if(written <= buffer.length) {
|
|
|
|
buffer.length -= written;
|
|
|
|
buffer_ptr += written;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(i < 0) {
|
|
|
|
if(buffer.data == buffer_ptr) {
|
|
|
|
buffer.length--;
|
|
|
|
buffer_ptr++;
|
|
|
|
}
|
|
|
|
memmove(buffer.data, buffer_ptr, buffer.length * sizeof(uint32_t));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
auto clear() -> void {
|
2010-08-09 13:28:56 +00:00
|
|
|
}
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
auto init() -> bool {
|
2010-08-09 13:28:56 +00:00
|
|
|
term();
|
|
|
|
|
|
|
|
if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) {
|
|
|
|
term();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//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(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
|
|
|
|
|
2013-05-02 11:25:45 +00:00
|
|
|
snd_pcm_hw_params_t* hwparams;
|
|
|
|
snd_pcm_sw_params_t* swparams;
|
2010-08-09 13:28:56 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
auto term() -> void {
|
2010-08-09 13:28:56 +00:00
|
|
|
if(device.handle) {
|
Update to v084r01 release.
I rewrote the S-SMP processor core (implementation of the 256 opcodes),
utilizing my new 6502-like syntax. It matches what bass v05r01 uses.
Took 10 hours.
Due to being able to group the "mov reg,mem" opcodes together with
"adc/sbc/ora/and/eor/cmp" sets, the total code size was reduced from
55.7KB to 42.5KB for identical accuracy and speed.
I also dropped the trick I was using to pass register variables as
template arguments, and instead just use a switch table to pass them as
function arguments. Makes the table a lot easier to read.
Passes all of my S-SMP tests, and all of blargg's
arithmetic/cycle-timing S-SMP tests. Runs Zelda 3 great as well. Didn't
test further.
This does have the potential to cause some regressions if I've messed
anything up, and none of the above tests caught it, so as always,
testing would be appreciated.
Anyway, yeah. By writing the actual processor with this new mnemonic
set, it confirms the parallels I've made.
My guess is that Sony really did clone the 6502, but was worried about
legal implications or something and changed the mnemonics last-minute.
(Note to self: need to re-enable snes.random before v085 official.)
EDIT: oh yeah, I also commented out the ALSA snd_pcm_drain() inside
term(). Without it, there is a tiny pop when the driver is
re-initialized. But with it, the entire emulator would lock up for five
whole seconds waiting on that call to complete. I'll take the pop any
day over that.
2011-11-17 12:05:35 +00:00
|
|
|
//snd_pcm_drain(device.handle); //prevents popping noise; but causes multi-second lag
|
2010-08-09 13:28:56 +00:00
|
|
|
snd_pcm_close(device.handle);
|
|
|
|
device.handle = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(buffer.data) {
|
|
|
|
delete[] buffer.data;
|
|
|
|
buffer.data = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DeclareAudio(ALSA)
|
|
|
|
|
|
|
|
};
|