From 4b1815deb209a912c742c411cd285dc5627679ac Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Fri, 28 Oct 2011 23:25:42 +0300 Subject: [PATCH] Portaudio support for sound --- Makefile | 5 + SDL/joystick-sdl.cpp | 1 + portaudio/sound-portaudio.cpp | 213 ++++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 SDL/joystick-sdl.cpp create mode 100644 portaudio/sound-portaudio.cpp diff --git a/Makefile b/Makefile index 20dbeb29..5791e069 100644 --- a/Makefile +++ b/Makefile @@ -60,12 +60,17 @@ endif ifeq ($(SOUND), SDL) PLATFORM_OBJECTS += SDL/sound-sdl.$(OBJECT_SUFFIX) else +ifeq ($(SOUND), PORTAUDIO) +PLATFORM_OBJECTS += portaudio/sound-portaudio.$(OBJECT_SUFFIX) +PLATFORM_LDFLAGS += -lportaudio +else ifeq ($(SOUND), DUMMY) PLATFORM_OBJECTS += dummy/sound-dummy.$(OBJECT_SUFFIX) else $(error "Unsupported sound type") endif endif +endif ifeq ($(GRAPHICS), SDL) ifneq ($(JOYSTICK), SDL) diff --git a/SDL/joystick-sdl.cpp b/SDL/joystick-sdl.cpp new file mode 100644 index 00000000..c2d3edc1 --- /dev/null +++ b/SDL/joystick-sdl.cpp @@ -0,0 +1 @@ +//Empty \ No newline at end of file diff --git a/portaudio/sound-portaudio.cpp b/portaudio/sound-portaudio.cpp new file mode 100644 index 00000000..2a959582 --- /dev/null +++ b/portaudio/sound-portaudio.cpp @@ -0,0 +1,213 @@ +#include "window.hpp" +#include "render.hpp" +#include +#include "command.hpp" +#include "framerate.hpp" +#include "misc.hpp" +#include "lsnes.hpp" +#include "settings.hpp" +#include +#include +#include +#include "keymapper.hpp" +#include "framerate.hpp" +#include +#include +#include + +#define WATCHDOG_TIMEOUT 15 +#define MAXMESSAGES 6 +#define MSGHISTORY 1000 +#define MAXHISTORY 1000 + +#include +#include +#include +#include + +namespace +{ + uint32_t audio_playback_freq = 0; + const size_t audiobuf_size = 8192; + uint16_t audiobuf[audiobuf_size]; + volatile size_t audiobuf_get = 0; + volatile size_t audiobuf_put = 0; + uint64_t sampledup_ctr = 0; + uint64_t sampledup_inc = 0; + uint64_t sampledup_mod = 1; + PaStream* s; + + void calculate_sampledup(uint32_t rate_n, uint32_t rate_d) + { + if(!audio_playback_freq) { + //Sound disabled. + sampledup_ctr = 0; + sampledup_inc = 0; + sampledup_mod = 0; + } else { + sampledup_ctr = 0; + sampledup_inc = rate_n; + sampledup_mod = rate_d * audio_playback_freq + rate_n; + } + } + + function_ptr_command enable_sound("enable-sound", "Enable/Disable sound", + "Syntax: enable-sound \nEnable or disable sound.\n", + [](const std::string& args) throw(std::bad_alloc, std::runtime_error) { + std::string s = args; + if(s == "on" || s == "true" || s == "1" || s == "enable" || s == "enabled") + window::sound_enable(true); + else if(s == "off" || s == "false" || s == "0" || s == "disable" || s == "disabled") + window::sound_enable(false); + else + throw std::runtime_error("Bad sound setting"); + }); + + int audiocb(const void *input, void *output, unsigned long frame_count, + const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags flags, void* user) + { + static uint16_t lprev, rprev; + int16_t* _output = reinterpret_cast(output); + for(unsigned long i = 0; i < frame_count; i++) { + uint16_t l, r; + if(audiobuf_get == audiobuf_put) { + l = lprev; + r = rprev; + } else { + l = lprev = audiobuf[audiobuf_get++]; + r = rprev = audiobuf[audiobuf_get++]; + if(audiobuf_get == audiobuf_size) + audiobuf_get = 0; + } + *(_output++) = l - 32768; + *(_output++) = r - 32768; + } + return 0; + } + + double attempts[] = {44100, 48000, 32000, -1}; +} + +void window::sound_enable(bool enable) throw() +{ + PaError err; + if(enable) + err = Pa_StartStream(s); + else + err = Pa_StopStream(s); + if(err != paNoError) + window::message(std::string("Portaudio error (start/stop): ") + Pa_GetErrorText(err)); +} + +void sound_init() +{ + PaError err = Pa_Initialize(); + if(err != paNoError) { + window::message(std::string("Portaudio error (init): ") + Pa_GetErrorText(err)); + window::message("Audio can't be initialized, audio playback disabled"); + //Disable audio. + audio_playback_freq = 0; + calculate_sampledup(32000, 1); + return; + } + + PaDeviceIndex device = 0; + PaDeviceIndex forcedevice = static_cast(-1); + if(getenv("LSNES_FORCE_AUDIO_OUT")) { + forcedevice = atoi(getenv("LSNES_FORCE_AUDIO_OUT")); + std::ostringstream str; + str << "Attempting to force sound output #" << forcedevice; + window::message(str.str()); + device = forcedevice; + } + { + std::ostringstream str; + str << "Detected " << Pa_GetDeviceCount() << " sound output devices."; + window::message(str.str()); + } + for(PaDeviceIndex j = 0; j < Pa_GetDeviceCount(); j++) { + const PaDeviceInfo* inf = Pa_GetDeviceInfo(j); + window::message(std::string("Audio device: ") + inf->name); + } + + unsigned i = 0; + double freq = 0; + while((freq = attempts[i]) > 0) { + PaStreamParameters output; + memset(&output, 0, sizeof(output)); + output.device = device; + output.channelCount = 2; + output.sampleFormat = paInt16; + output.suggestedLatency = Pa_GetDeviceInfo(device)->defaultLowOutputLatency; + output.hostApiSpecificStreamInfo = NULL; + err = Pa_OpenStream(&s, NULL, &output, freq, 0, 0, audiocb, NULL); + if(err == paNoError) + break; + { + std::ostringstream str; + str << "Portaudio: Can't open audio at " << freq << " using '" + << Pa_GetDeviceInfo(device)->name << "': " << Pa_GetErrorText(err) << std::endl; + window::message(str.str()); + } + + i++; + if(attempts[i] <= 0 && device < Pa_GetDeviceCount()) { + i = 0; + device++; + if(forcedevice != static_cast(-1)) + break; + } + if(device == Pa_GetDeviceCount()) + break; + } + + //GO!!! + if(freq <= 0) { + window::message(std::string("Portaudio error (open): ") + Pa_GetErrorText(err)); + window::message("Audio can't be initialized, audio playback disabled"); + //Disable audio. + audio_playback_freq = 0; + calculate_sampledup(32000, 1); + return; + } + { + std::ostringstream str; + str << "Portaudio: Opened audio at " << freq << " using '" << Pa_GetDeviceInfo(device)->name + << "'" << std::endl; + window::message(str.str()); + } + audio_playback_freq = freq; + calculate_sampledup(32000, 1); + err = Pa_StartStream(s); + if(err != paNoError) + window::message(std::string("Portaudio error (start): ") + Pa_GetErrorText(err)); +} + +void sound_quit() +{ + PaError err = Pa_StopStream(s); + if(err != paNoError) + window::message(std::string("Portaudio error (stop): ") + Pa_GetErrorText(err)); + Pa_Terminate(); +} + +void window::play_audio_sample(uint16_t left, uint16_t right) throw() +{ + sampledup_ctr += sampledup_inc; + while(sampledup_ctr < sampledup_mod) { + audiobuf[audiobuf_put++] = left; + audiobuf[audiobuf_put++] = right; + if(audiobuf_put == audiobuf_size) + audiobuf_put = 0; + sampledup_ctr += sampledup_inc; + } + sampledup_ctr -= sampledup_mod; +} + +void window::set_sound_rate(uint32_t rate_n, uint32_t rate_d) +{ + uint32_t g = gcd(rate_n, rate_d); + calculate_sampledup(rate_n / g, rate_d / g); +} + +const char* sound_plugin_name = "Portaudio sound plugin";