2018-11-16 00:42:29 +01:00
|
|
|
/*****************************************************************************\
|
|
|
|
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
|
|
|
|
This file is licensed under the Snes9x License.
|
|
|
|
For further information, consult the LICENSE file in the root directory.
|
|
|
|
\*****************************************************************************/
|
|
|
|
|
2010-09-25 17:46:12 +02:00
|
|
|
#include "gtk_sound_driver_portaudio.h"
|
2019-02-07 02:41:33 +01:00
|
|
|
#include "gtk_s9x.h"
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
static inline int frames_to_bytes(int frames)
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
2019-02-06 00:21:23 +01:00
|
|
|
return frames * 4;
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
static void port_audio_samples_available_callback(void *data)
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
((S9xPortAudioSoundDriver *)data)->samples_available();
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 21:52:26 +01:00
|
|
|
S9xPortAudioSoundDriver::S9xPortAudioSoundDriver()
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
|
|
|
audio_stream = NULL;
|
2017-11-21 01:12:57 +01:00
|
|
|
sound_buffer = NULL;
|
|
|
|
sound_buffer_size = 0;
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
void S9xPortAudioSoundDriver::init()
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
|
|
|
PaError err;
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
err = Pa_Initialize();
|
2010-09-25 17:46:12 +02:00
|
|
|
|
|
|
|
if (err != paNoError)
|
2019-02-07 02:41:33 +01:00
|
|
|
fprintf(stderr,
|
|
|
|
"Couldn't initialize PortAudio: %s\n",
|
|
|
|
Pa_GetErrorText(err));
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
void S9xPortAudioSoundDriver::terminate()
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
stop();
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
S9xSetSamplesAvailableCallback(NULL, NULL);
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
Pa_Terminate();
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2017-11-21 01:12:57 +01:00
|
|
|
if (sound_buffer)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
free(sound_buffer);
|
2017-11-21 01:12:57 +01:00
|
|
|
sound_buffer = NULL;
|
|
|
|
}
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
void S9xPortAudioSoundDriver::start()
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
|
|
|
PaError err;
|
|
|
|
|
|
|
|
if (audio_stream != NULL && !(gui_config->mute_sound))
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
if ((Pa_IsStreamActive(audio_stream)))
|
2010-09-25 17:46:12 +02:00
|
|
|
return;
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
err = Pa_StartStream(audio_stream);
|
2010-09-25 17:46:12 +02:00
|
|
|
|
|
|
|
if (err != paNoError)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
fprintf(stderr, "Error: %s\n", Pa_GetErrorText(err));
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
void S9xPortAudioSoundDriver::stop()
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
|
|
|
if (audio_stream != NULL)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
Pa_StopStream(audio_stream);
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-28 23:32:32 +01:00
|
|
|
bool S9xPortAudioSoundDriver::open_device()
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
PaStreamParameters param;
|
|
|
|
const PaDeviceInfo *device_info;
|
2010-09-25 17:46:12 +02:00
|
|
|
const PaHostApiInfo *hostapi_info;
|
2019-02-07 02:41:33 +01:00
|
|
|
PaError err = paNoError;
|
2010-09-25 17:46:12 +02:00
|
|
|
|
|
|
|
if (audio_stream != NULL)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
printf("Shutting down sound for reset\n");
|
|
|
|
err = Pa_CloseStream(audio_stream);
|
2010-09-25 17:46:12 +02:00
|
|
|
|
|
|
|
if (err != paNoError)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
fprintf(stderr,
|
|
|
|
"Couldn't reset audio stream.\nError: %s\n",
|
|
|
|
Pa_GetErrorText(err));
|
2018-12-28 23:32:32 +01:00
|
|
|
return true;
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
audio_stream = NULL;
|
|
|
|
}
|
|
|
|
|
2019-02-06 00:21:23 +01:00
|
|
|
param.channelCount = 2;
|
|
|
|
param.sampleFormat = paInt16;
|
2010-09-25 17:46:12 +02:00
|
|
|
param.hostApiSpecificStreamInfo = NULL;
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
printf("PortAudio sound driver initializing...\n");
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
for (int i = 0; i < Pa_GetHostApiCount(); i++)
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
printf(" --> ");
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
hostapi_info = Pa_GetHostApiInfo(i);
|
2010-09-25 17:46:12 +02:00
|
|
|
if (!hostapi_info)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
printf("Host API #%d has no info\n", i);
|
2010-09-25 17:46:12 +02:00
|
|
|
err = paNotInitialized;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
device_info = Pa_GetDeviceInfo(hostapi_info->defaultOutputDevice);
|
2010-09-25 17:46:12 +02:00
|
|
|
if (!device_info)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
printf("(%s)...No device info available.\n", hostapi_info->name);
|
2010-09-25 17:46:12 +02:00
|
|
|
err = paNotInitialized;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
param.device = hostapi_info->defaultOutputDevice;
|
|
|
|
param.suggestedLatency = gui_config->sound_buffer_size * 0.001;
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
printf("(%s : %s, latency %dms)...",
|
|
|
|
hostapi_info->name,
|
|
|
|
device_info->name,
|
|
|
|
(int)(param.suggestedLatency * 1000.0));
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
fflush(stdout);
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
err = Pa_OpenStream(&audio_stream,
|
|
|
|
NULL,
|
|
|
|
¶m,
|
|
|
|
Settings.SoundPlaybackRate,
|
|
|
|
0,
|
|
|
|
paNoFlag,
|
|
|
|
NULL,
|
|
|
|
NULL);
|
2017-11-21 01:12:57 +01:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
int frames = Pa_GetStreamWriteAvailable(audio_stream);
|
|
|
|
output_buffer_size = frames_to_bytes(frames);
|
2010-09-25 17:46:12 +02:00
|
|
|
|
|
|
|
if (err == paNoError)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
printf("OK\n");
|
2010-09-25 17:46:12 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
printf("Failed (%s)\n",
|
|
|
|
Pa_GetErrorText(err));
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
if (err != paNoError || Pa_GetHostApiCount() < 1)
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
fprintf(stderr,
|
|
|
|
"Couldn't initialize sound\n");
|
2018-12-28 23:32:32 +01:00
|
|
|
return false;
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
S9xSetSamplesAvailableCallback(port_audio_samples_available_callback, this);
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
fflush(stdout);
|
|
|
|
fflush(stderr);
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2018-12-28 23:32:32 +01:00
|
|
|
return true;
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
void S9xPortAudioSoundDriver::samples_available()
|
2010-09-25 17:46:12 +02:00
|
|
|
{
|
2017-11-21 01:12:57 +01:00
|
|
|
int frames;
|
|
|
|
int bytes;
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
frames = Pa_GetStreamWriteAvailable(audio_stream);
|
2017-11-21 01:12:57 +01:00
|
|
|
|
|
|
|
if (Settings.DynamicRateControl)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
S9xUpdateDynamicRate(frames_to_bytes(frames), output_buffer_size);
|
2017-11-21 01:12:57 +01:00
|
|
|
}
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
S9xFinalizeSamples();
|
2019-02-10 01:48:17 +01:00
|
|
|
int snes_frames_available = S9xGetSampleCount() >> 1;
|
2010-09-25 17:46:12 +02:00
|
|
|
|
2019-02-10 01:48:17 +01:00
|
|
|
if (Settings.DynamicRateControl && !Settings.SoundSync)
|
2017-11-21 01:12:57 +01:00
|
|
|
{
|
|
|
|
// Using rate control, we should always keep the emulator's sound buffers empty to
|
|
|
|
// maintain an accurate measurement.
|
2019-02-07 02:41:33 +01:00
|
|
|
if (frames < (S9xGetSampleCount() >> 1))
|
2017-11-21 01:12:57 +01:00
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
S9xClearSamples();
|
2017-11-21 01:12:57 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-10 01:48:17 +01:00
|
|
|
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
|
|
|
|
{
|
|
|
|
while (frames < snes_frames_available)
|
|
|
|
{
|
2019-02-12 19:00:03 +01:00
|
|
|
int usec_to_sleep = (snes_frames_available - frames) * 10000 /
|
|
|
|
(Settings.SoundPlaybackRate / 100);
|
|
|
|
usleep(usec_to_sleep > 0 ? usec_to_sleep : 0);
|
2019-02-10 01:48:17 +01:00
|
|
|
frames = Pa_GetStreamWriteAvailable(audio_stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
frames = MIN(frames, snes_frames_available);
|
2019-02-07 02:41:33 +01:00
|
|
|
bytes = frames_to_bytes(frames);
|
2017-11-21 01:12:57 +01:00
|
|
|
|
|
|
|
if (sound_buffer_size < bytes || sound_buffer == NULL)
|
|
|
|
{
|
2019-02-07 02:41:33 +01:00
|
|
|
sound_buffer = (uint8 *)realloc(sound_buffer, bytes);
|
2017-11-21 01:12:57 +01:00
|
|
|
sound_buffer_size = bytes;
|
|
|
|
}
|
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
S9xMixSamples(sound_buffer, frames << 1);
|
2017-11-21 01:12:57 +01:00
|
|
|
|
2019-02-07 02:41:33 +01:00
|
|
|
Pa_WriteStream(audio_stream, sound_buffer, frames);
|
2010-09-25 17:46:12 +02:00
|
|
|
}
|