Files
libretro-atari800/atari800/src/sound.c
T
2015-12-14 14:00:35 +01:00

544 lines
16 KiB
C

/*
* sound.c - platform-independent interface for platform-specific sound output.
*
* Copyright (C) 2013 Tomasz Krasuski
* Copyright (C) 2013-2014 Atari800 development team (see DOC/CREDITS)
*
* This file is part of the Atari800 emulator project which emulates
* the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.
* Atari800 is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* Atari800 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with Atari800; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <stdio.h>
#include "sound.h"
#include "atari.h"
#include "log.h"
#include "platform.h"
#include "pokeysnd.h"
#include "util.h"
#define DEBUG 0
int Sound_enabled = 1;
Sound_setup_t Sound_desired = {
44100,
2,
1,
0
};
Sound_setup_t Sound_out;
static int paused = TRUE;
#ifndef SOUND_CALLBACK
static UBYTE *process_buffer = NULL;
static unsigned int process_buffer_size;
#endif /* !SOUND_CALLBACK */
#ifdef SYNCHRONIZED_SOUND
static UBYTE *sync_buffer = NULL;
static unsigned int sync_buffer_size;
/* Two invariants are held:
a) 0 <= sync_read_pos < sync_buffer_size
b) sync_read_pos <= sync_write_pos <= sync_buffer_size + sync_read_pos
sync_write_pos may be >= sync_buffer_size. In such case the actual write
position is sync_write_pos % sync_buffer_size. */
static unsigned int sync_write_pos;
static unsigned int sync_read_pos;
unsigned int Sound_latency = 20;
/* Cumulative audio difference. */
static double avg_fill;
/* Estimated fill of sync_buffer */
static unsigned int sync_est_fill;
/* If sync_est_fill goes outside this bounds, emulation speed is adjusted. */
static unsigned int sync_min_fill;
static unsigned int sync_max_fill;
#ifdef SOUND_CALLBACK
#endif /* SOUND_CALLBACK */
/* Time of last write of sudio to output device (either by Sound_Callback or
WriteOut). */
double last_audio_write_time;
#endif /* SYNCHRONIZED_SOUND */
enum { MAX_SAMPLE_SIZE = 2, /* for 16-bit */
#ifdef STEREO_SOUND
MAX_CHANNELS = 2,
#else /* !STEREO_SOUND */
MAX_CHANNELS = 1,
#endif /* !STEREO_SOUND */
MAX_FRAME_SIZE = MAX_SAMPLE_SIZE * MAX_CHANNELS
};
int Sound_ReadConfig(char *option, char *ptr)
{
if (strcmp(option, "SOUND_ENABLED") == 0)
return (Sound_enabled = Util_sscanbool(ptr)) != -1;
else if (strcmp(option, "SOUND_RATE") == 0)
return (Sound_desired.freq = Util_sscandec(ptr)) != -1;
else if (strcmp(option, "SOUND_BITS") == 0) {
int bits = Util_sscandec(ptr);
if (bits != 8 && bits != 16)
return FALSE;
Sound_desired.sample_size = bits / 8;
}
else if (strcmp(option, "SOUND_BUFFER_MS") == 0) {
int val = Util_sscandec(ptr);
if (val == -1)
return FALSE;
Sound_desired.buffer_ms = val;
}
#ifdef SYNCHRONIZED_SOUND
else if (strcmp(option, "SOUND_LATENCY") == 0)
return (Sound_latency = Util_sscandec(ptr)) != -1;
#endif /* SYNCHRONIZED_SOUND */
else
return FALSE;
return TRUE;
}
void Sound_WriteConfig(FILE *fp)
{
fprintf(fp, "SOUND_ENABLED=%u\n", Sound_enabled);
fprintf(fp, "SOUND_RATE=%u\n", Sound_desired.freq);
fprintf(fp, "SOUND_BITS=%u\n", Sound_desired.sample_size * 8);
fprintf(fp, "SOUND_BUFFER_MS=%u\n", Sound_desired.buffer_ms);
#ifdef SYNCHRONIZED_SOUND
fprintf(fp, "SOUND_LATENCY=%u\n", Sound_latency);
#endif /* SYNCHRONIZED_SOUND */
}
int Sound_Initialise(int *argc, char *argv[])
{
int i, j;
int help_only = FALSE;
for (i = j = 1; i < *argc; i++) {
int i_a = (i + 1 < *argc); /* is argument available? */
int a_m = FALSE; /* error, argument missing! */
int a_i = FALSE; /* error, argument invalid! */
if (strcmp(argv[i], "-sound") == 0)
Sound_enabled = 1;
else if (strcmp(argv[i], "-nosound") == 0)
Sound_enabled = 0;
else if (strcmp(argv[i], "-dsprate") == 0) {
if (i_a)
a_i = (Sound_desired.freq = Util_sscandec(argv[++i])) == -1;
else a_m = TRUE;
}
else if (strcmp(argv[i], "-audio16") == 0)
Sound_desired.sample_size = 2;
else if (strcmp(argv[i], "-audio8") == 0)
Sound_desired.sample_size = 1;
else if (strcmp(argv[i], "snd-buflen") == 0) {
if (i_a) {
int val = Util_sscandec(argv[++i]);
if (val == -1)
a_i = TRUE;
else
Sound_desired.buffer_ms = val;
}
else a_m = TRUE;
}
#ifdef SYNCHRONIZED_SOUND
else if (strcmp(argv[i], "-snddelay") == 0)
if (i_a)
Sound_latency = Util_sscandec(argv[++i]);
else a_m = TRUE;
#endif /* SYNCHRONIZED_SOUND */
else {
if (strcmp(argv[i], "-help") == 0) {
help_only = TRUE;
Log_print("\t-sound Enable sound");
Log_print("\t-nosound Disable sound");
Log_print("\t-dsprate <rate> Set sound output frequency in Hz");
Log_print("\t-audio16 Set sound output format to 16-bit");
Log_print("\t-audio8 Set sound output format to 8-bit");
Log_print("\t-snd-buflen <ms> Set length of the hardware sound buffer in milliseconds");
#ifdef SYNCHRONIZED_SOUND
Log_print("\t-snddelay <ms> Set sound latency in milliseconds");
#endif /* SYNCHRONIZED_SOUND */
}
argv[j++] = argv[i];
}
if (a_m) {
Log_print("Missing argument for '%s'", argv[i]);
return FALSE;
} else if (a_i) {
Log_print("Invalid argument for '%s'", argv[--i]);
return FALSE;
}
}
*argc = j;
if (help_only)
Sound_enabled = FALSE;
return TRUE;
}
int Sound_Setup(void)
{
/* Sanitize freq. */
if (POKEYSND_enable_new_pokey && Sound_desired.freq < 8192)
/* MZ POKEY seems to segfault or remain silent with rate < 8009 Hz. */
Sound_desired.freq = 8192;
else if (Sound_desired.freq < 1000)
/* Such low value is impractical. */
Sound_desired.freq = 1000;
else if (Sound_desired.freq > 65535)
/* POKEY emulation doesn't support rate > 65535 Hz. */
Sound_desired.freq = 65535;
Sound_desired.buffer_frames = Sound_desired.freq * Sound_desired.buffer_ms / 1000;
Sound_out = Sound_desired;
if (!(Sound_enabled = PLATFORM_SoundSetup(&Sound_out)))
return FALSE;
Sound_out.buffer_ms = Sound_out.buffer_frames * 1000 / Sound_out.freq;
/* Now setup contains actual audio output settings. */
if ((POKEYSND_enable_new_pokey && Sound_out.freq < 8192)
|| Sound_out.freq < 1000 || Sound_out.freq > 65535) {
Log_print("%d frequency not supported", Sound_out.freq);
Sound_Exit();
return FALSE;
}
if (Sound_out.channels > MAX_CHANNELS) {
Log_print("%d channels not supported", Sound_out.channels);
Sound_Exit();
return FALSE;
}
POKEYSND_stereo_enabled = Sound_out.channels == 2;
#ifndef SOUND_CALLBACK
free(process_buffer);
process_buffer_size = Sound_out.buffer_frames * Sound_out.channels * Sound_out.sample_size;
process_buffer = Util_malloc(process_buffer_size);
#endif /* !SOUND_CALLBACK */
POKEYSND_Init(POKEYSND_FREQ_17_EXACT, Sound_out.freq, Sound_out.channels, Sound_out.sample_size == 2 ? POKEYSND_BIT16 : 0);
#ifdef SYNCHRONIZED_SOUND
Sound_SetLatency(Sound_latency);
#endif /* SYNCHRONIZED_SOUND */
Sound_desired.freq = Sound_out.freq;
Sound_desired.sample_size = Sound_out.sample_size;
Sound_desired.channels = Sound_out.channels;
/* buffer_ms and buffer_frames are not copied from Sound_out back to
Sound_desired. The reason is, for some backends (e.g. SDL on PulseAudio)
opening audio always results in buffer size smaller than desired. If
the obtained value was copied from Sound_out to Sound_desired, repeated
calls to Sound_Setup would quickly decrease buffer_ms to 0. */
paused = TRUE;
return TRUE;
}
void Sound_Exit(void)
{
if (Sound_enabled) {
PLATFORM_SoundExit();
Sound_enabled = FALSE;
#ifndef SOUND_CALLBACK
free(process_buffer);
process_buffer = NULL;
#endif /* !SOUND_CALLBACK */
#ifdef SYNCHRONIZED_SOUND
free(sync_buffer);
sync_buffer = NULL;
#endif /* SYNCHRONIZED_SOUND */
}
}
void Sound_Pause(void)
{
if (Sound_enabled && !paused) {
/* stop audio output */
PLATFORM_SoundPause();
paused = TRUE;
}
}
void Sound_Continue(void)
{
if (Sound_enabled && paused) {
/* start audio output */
#ifdef SYNCHRONIZED_SOUND
/* sync_write_pos = sync_read_pos + sync_min_fill;
avg_fill = sync_min_fill;*/
last_audio_write_time = Util_time();
#endif /* SYNCHRONIZED_SOUND */
PLATFORM_SoundContinue();
paused = FALSE;
}
}
/* Fills buffer BUFFER with SIZE bytes of audio samples. */
static void FillBuffer(UBYTE *buffer, unsigned int size)
{
#ifdef SYNCHRONIZED_SOUND
unsigned int new_read_pos;
static UBYTE last_frame[MAX_FRAME_SIZE];
unsigned int bytes_per_frame = Sound_out.channels * Sound_out.sample_size;
unsigned int to_write = sync_write_pos - sync_read_pos;
if (to_write > 0) {
if (to_write > size)
to_write = size;
new_read_pos = sync_read_pos + to_write;
if (new_read_pos <= sync_buffer_size)
/* no wrap */
memcpy(buffer, sync_buffer + sync_read_pos, to_write);
else {
/* wraps */
unsigned int first_part_size = sync_buffer_size - sync_read_pos;
memcpy(buffer, sync_buffer + sync_read_pos, first_part_size);
memcpy(buffer + first_part_size, sync_buffer, to_write - first_part_size);
}
sync_read_pos = new_read_pos;
if (sync_read_pos > sync_buffer_size) {
sync_read_pos -= sync_buffer_size;
sync_write_pos -= sync_buffer_size;
}
/* Save the last frame as we may need it to fill underflow. */
memcpy(last_frame, buffer + to_write - bytes_per_frame, bytes_per_frame);
}
/* Just repeat the last good frame if underflow. */
if (to_write < size) {
#if DEBUG
Log_print("Sound buffer underflow: fill %d, needed %d",
to_write/Sound_out.channels/Sound_out.sample_size,
size/Sound_out.channels/Sound_out.sample_size);
#endif
do {
memcpy(buffer + to_write, last_frame, bytes_per_frame);
to_write += bytes_per_frame;
} while (to_write < size);
}
#else /* !SYNCHRONIZED_SOUND */
POKEYSND_Process(buffer, size / Sound_out.sample_size);
#endif /* !SYNCHRONIZED_SOUND */
}
#ifdef SOUND_CALLBACK
void Sound_Callback(UBYTE *buffer, unsigned int size)
{
#if DEBUG >= 2
Log_print("Callback: fill %u, needed %u",
(sync_write_pos - sync_read_pos) / Sound_out.channels / Sound_out.sample_size,
size / Sound_out.channels / Sound_out.sample_size);
#endif
FillBuffer(buffer, size);
#ifdef SYNCHRONIZED_SOUND
last_audio_write_time = Util_time();
#endif /* SYNCHRONIZED_SOUND */
}
#else /* !SOUND_CALLBACK */
/* Write audio to output device. */
static void WriteOut(void)
{
unsigned int avail = PLATFORM_SoundAvailable();
if (avail > 0) {
#if DEBUG >= 2
Log_print("WriteOut: fill %u, needed %u",
(sync_write_pos - sync_read_pos) / Sound_out.channels / Sound_out.sample_size,
avail / Sound_out.channels / Sound_out.sample_size);
#endif
/* On some platforms (eg. NestedVM) avail may be larger than process_buffer_size. */
do {
unsigned int len = avail > process_buffer_size ? process_buffer_size : avail;
FillBuffer(process_buffer, len);
PLATFORM_SoundWrite(process_buffer, len);
avail -= len;
} while (avail > 0);
#ifdef SYNCHRONIZED_SOUND
last_audio_write_time = Util_time();
#endif /* SYNCHRONIZED_SOUND */
}
}
#endif /* !SOUND_CALLBACK */
#ifdef SYNCHRONIZED_SOUND
static void UpdateSyncBuffer(void)
{
unsigned int bytes_written;
unsigned int samples_written;
unsigned int fill;
unsigned int new_write_pos;
PLATFORM_SoundLock();
/* Current fill of the audio buffer. */
fill = sync_write_pos - sync_read_pos;
/* Update sync_est_fill. */
{
unsigned int est_gap;
est_gap = (Util_time() - last_audio_write_time)*Sound_out.freq*Sound_out.channels*Sound_out.sample_size;
if (fill < est_gap)
sync_est_fill = 0;
else
sync_est_fill = fill - est_gap;
}
if (Atari800_turbo && sync_est_fill > sync_max_fill) {
PLATFORM_SoundUnlock();
return;
}
/* produce samples from the sound emulation */
samples_written = POKEYSND_UpdateProcessBuffer();
bytes_written = Sound_out.sample_size * samples_written;
/* if there isn't enough room... */
if (bytes_written > sync_buffer_size - fill) {
/* Overflow of sync_buffer. */
#if DEBUG
Log_print("Sound buffer overflow: free %d, needed %d",
(sync_buffer_size - fill)/Sound_out.channels/Sound_out.sample_size,
bytes_written/Sound_out.channels/Sound_out.sample_size);
#endif
/* Wait until hardware buffer can be filled, or wait until callback
makes place in the buffer. */
do {
PLATFORM_SoundUnlock();
/* Sleep for the duration of one full HW buffer. */
Util_sleep((double)Sound_out.buffer_frames / Sound_out.freq);
PLATFORM_SoundLock();
#ifndef SOUND_CALLBACK
WriteOut(); /* Write to audio buffer as much as possible. */
#endif /* SOUND_CALLBACK */
fill = sync_write_pos - sync_read_pos;
} while (bytes_written > sync_buffer_size - fill);
}
/* Now bytes_written <= audio_buffer_size + dsp_read_pos - dsp_write_pos) */
#if DEBUG >= 2
Log_print("UpdateSyncBuffer: est_gap: %f, fill %u, write %u",
(Util_time() - last_audio_write_time)*Sound_out.freq,
fill / Sound_out.channels/Sound_out.sample_size,
bytes_written / Sound_out.channels/Sound_out.sample_size);
#endif
/* now we copy the data into the buffer and adjust the positions */
new_write_pos = sync_write_pos + bytes_written;
if (new_write_pos/sync_buffer_size == sync_write_pos/sync_buffer_size)
/* no wrap */
memcpy(sync_buffer + sync_write_pos%sync_buffer_size, POKEYSND_process_buffer, bytes_written);
else {
/* wraps */
int first_part_size = sync_buffer_size - sync_write_pos%sync_buffer_size;
memcpy(sync_buffer + sync_write_pos%sync_buffer_size, POKEYSND_process_buffer, first_part_size);
memcpy(sync_buffer, POKEYSND_process_buffer + first_part_size, bytes_written - first_part_size);
}
sync_write_pos = new_write_pos;
if (sync_write_pos > sync_read_pos + sync_buffer_size)
sync_write_pos -= sync_buffer_size;
PLATFORM_SoundUnlock();
}
#endif /* SYNCHRONIZED_SOUND */
void Sound_Update(void)
{
if (!Sound_enabled || paused)
return;
#ifdef SYNCHRONIZED_SOUND
UpdateSyncBuffer();
#endif /* SYNCHRONIZED_SOUND */
#ifndef SOUND_CALLBACK
WriteOut();
#endif /* !SOUND_CALLBACK */
}
#ifdef SYNCHRONIZED_SOUND
void Sound_SetLatency(unsigned int latency)
{
Sound_latency = latency;
if (Sound_enabled) {
/* how many fragments in the audio buffer */
enum { SYNC_BUFFER_FRAGS = 5 };
unsigned int bytes_per_frame = Sound_out.channels * Sound_out.sample_size;
unsigned int latency_frames = Sound_out.freq*Sound_latency/1000;
PLATFORM_SoundLock();
sync_buffer_size = (latency_frames + SYNC_BUFFER_FRAGS*Sound_out.buffer_frames) * bytes_per_frame;
sync_min_fill = latency_frames * bytes_per_frame;
sync_max_fill = sync_min_fill + Sound_out.buffer_frames * bytes_per_frame;
avg_fill = sync_min_fill;
sync_read_pos = 0;
sync_write_pos = sync_min_fill;
free(sync_buffer);
sync_buffer = Util_malloc(sync_buffer_size);
memset(sync_buffer, 0, sync_buffer_size);
PLATFORM_SoundUnlock();
}
}
double Sound_AdjustSpeed(void)
{
double delay_mult = 1.0;
static double const alpha = 2.0/(1.0+40.0);
if (Sound_enabled && !paused) {
#if 1
avg_fill = avg_fill + alpha * (sync_est_fill - avg_fill);
if (avg_fill < sync_min_fill)
delay_mult = 0.95;
else if (avg_fill > sync_max_fill)
delay_mult = 1.05;
#endif
#if 0
if (sync_est_fill < sync_min_fill)
delay_mult = 0.95;
else if (sync_est_fill > sync_max_fill)
delay_mult = 1.05;
#endif
#if DEBUG >= 2
Log_print("delay_mult: %f, est_fill: %u, avg_fill: %f, buf_size: %u, min_fill: %u, max_fill: %u",
delay_mult,
sync_est_fill / Sound_out.channels / Sound_out.sample_size,
avg_fill / Sound_out.channels / Sound_out.sample_size,
sync_buffer_size / Sound_out.channels / Sound_out.sample_size,
sync_min_fill / Sound_out.channels / Sound_out.sample_size,
sync_max_fill / Sound_out.channels / Sound_out.sample_size);
#endif
}
return delay_mult;
}
#endif /* SYNCHRONIZED_SOUND */
unsigned int Sound_NextPow2(unsigned int num)
{
unsigned int result = 1;
while (result <= num)
result <<= 1;
return result;
}