/* * 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 #include #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 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 Set length of the hardware sound buffer in milliseconds"); #ifdef SYNCHRONIZED_SOUND Log_print("\t-snddelay 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; }