2010-08-09 13:28:56 +00:00
|
|
|
#include <alsa/asoundlib.h>
|
|
|
|
|
2015-06-20 05:44:05 +00:00
|
|
|
struct AudioALSA : Audio {
|
|
|
|
~AudioALSA() { term(); }
|
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;
|
Update to v098r01 release.
byuu says:
Changelog:
- SFC: balanced profile removed
- SFC: performance profile removed
- SFC: code for handling non-threaded CPU, SMP, DSP, PPU removed
- SFC: Coprocessor, Controller (and expansion port) shared Thread code
merged to SFC::Cothread
- Cothread here just means "Thread with CPU affinity" (couldn't think
of a better name, sorry)
- SFC: CPU now has vector<Thread*> coprocessors, peripherals;
- this is the beginning of work to allow expansion port devices to be
dynamically changed at run-time
- ruby: all audio drivers default to 48000hz instead of 22050hz now if
no frequency is assigned
- note: the WASAPI driver can default to whatever the native frequency
is; doesn't have to be 48000hz
- tomoko: removed the ability to change the frequency from the UI (but
it will display the frequency used)
- tomoko: removed the timing settings panel
- the goal is to work toward smooth video via adaptive sync
- the model is broken by not being in control of the audio frequency
anyway
- it's further broken by PAL running at 50hz and WSC running at 75hz
- it was always broken anyway by SNES interlace timing varying from
progressive timing
- higan: audio/ stub created (for now, it's just nall/dsp/ moved here
and included as a header)
- higan: video/ stub created
- higan/GNUmakefile: now includes build rules for essential components
(libco, emulator, audio, video)
The audio changes are in preparation to merge wareya's awesome WASAPI
work without the need for the nall/dsp resampler.
2016-04-09 03:40:12 +00:00
|
|
|
unsigned frequency = 48000;
|
2015-06-15 22:16:43 +00:00
|
|
|
unsigned latency = 60;
|
2010-08-09 13:28:56 +00:00
|
|
|
} settings;
|
|
|
|
|
2015-06-15 22:16:43 +00:00
|
|
|
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
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|