diff --git a/BasiliskII/ChangeLog b/BasiliskII/ChangeLog index 83c93648..3c1ff4dc 100644 --- a/BasiliskII/ChangeLog +++ b/BasiliskII/ChangeLog @@ -8,6 +8,8 @@ V0.8 (snapshot) - (default addressing mode, if possible) - Unix: added screen updates on SEGV signals [Gwenole Beauchesne] (activated through the "--enable-vosf" configure option) + - Unix: added IRIX audio driver [Brian J. Johnson] + - Unix: improved timing of periodic threads - AmigaOS: enabled floppy support, fixed floppy bugs [Jürgen Lachmann] - AmigaOS: Amiga mouse pointer is hidden inside windowed Mac displays - AmigaOS/sys_amiga.cpp: workaround for 2060scsi.device bug when @@ -18,7 +20,6 @@ V0.8 (snapshot) - FInfo/FXInfo, replaced get/set_finder_*() functions by get/set_finfo() - AmigaOS: added MacsBug support (tested with MacsBug6.6.1), fixed bug [Jürgen Lachmann] - - Unix: improved timing of periodic threads - include/macos_util.h: defines FOURCC() macro to make MacOS-like four-character-codes, replaced most instances of multi-character constants in the sources by this macro to avoid compiler warnings diff --git a/BasiliskII/src/Unix/Irix/audio_irix.cpp b/BasiliskII/src/Unix/Irix/audio_irix.cpp new file mode 100644 index 00000000..c4b9ecab --- /dev/null +++ b/BasiliskII/src/Unix/Irix/audio_irix.cpp @@ -0,0 +1,413 @@ +/* + * audio_irix.cpp - Audio support, SGI Irix implementation + * + * Basilisk II (C) 1997-2000 Christian Bauer + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "sysdeps.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "cpu_emulation.h" +#include "main.h" +#include "prefs.h" +#include "user_strings.h" +#include "audio.h" +#include "audio_defs.h" + +#define DEBUG 0 +#include "debug.h" + + +// Supported sample rates, sizes and channels (defaults) +int audio_num_sample_rates = 1; +uint32 audio_sample_rates[] = {44100 << 16}; +int audio_num_sample_sizes = 1; +uint16 audio_sample_sizes[] = {16}; +int audio_num_channel_counts = 1; +uint16 audio_channel_counts[] = {2}; + +// Global variables +static int audio_fd = -1; // fd from audio library +static sem_t audio_irq_done_sem; // Signal from interrupt to streaming thread: data block read +static bool sem_inited = false; // Flag: audio_irq_done_sem initialized +static int sound_buffer_size; // Size of sound buffer in bytes +static int sound_buffer_fill_point; // Fill buffer when this many frames are empty +static uint8 silence_byte = 0; // Byte value to use to fill sound buffers with silence +static pthread_t stream_thread; // Audio streaming thread +static pthread_attr_t stream_thread_attr; // Streaming thread attributes +static bool stream_thread_active = false; // Flag: streaming thread installed +static volatile bool stream_thread_cancel = false; // Flag: cancel streaming thread + +// IRIX libaudio control structures +static ALconfig config; +static ALport port; + + +// Prototypes +static void *stream_func(void *arg); + + +/* + * Initialization + */ + +// Set AudioStatus to reflect current audio stream format +static void set_audio_status_format(void) +{ + AudioStatus.sample_rate = audio_sample_rates[0]; + AudioStatus.sample_size = audio_sample_sizes[0]; + AudioStatus.channels = audio_channel_counts[0]; +} + +// Init libaudio, returns false on error +bool audio_init_al(void) +{ + ALpv pv[2]; + + printf("Using libaudio audio output\n"); + + // Try to open the audio library + + config = alNewConfig(); + alSetSampFmt(config, AL_SAMPFMT_TWOSCOMP); + alSetWidth(config, AL_SAMPLE_16); + alSetChannels(config, 2); // stereo + alSetDevice(config, AL_DEFAULT_OUTPUT); // Allow selecting via prefs? + + port = alOpenPort("BasiliskII", "w", config); + if (port == NULL) { + fprintf(stderr, "ERROR: Cannot open audio port: %s\n", + alGetErrorString(oserror())); + return false; + } + + // Set the sample rate + + pv[0].param = AL_RATE; + pv[0].value.ll = alDoubleToFixed(audio_sample_rates[0] >> 16); + pv[1].param = AL_MASTER_CLOCK; + pv[1].value.i = AL_CRYSTAL_MCLK_TYPE; + if (alSetParams(AL_DEFAULT_OUTPUT, pv, 2) < 0) { + fprintf(stderr, "ERROR: libaudio setparams failed: %s\n", + alGetErrorString(oserror())); + alClosePort(port); + return false; + } + + // TODO: list all supported sample formats? + + // Set AudioStatus again because we now know more about the sound + // system's capabilities + set_audio_status_format(); + + // Compute sound buffer size and libaudio refill point + + config = alGetConfig(port); + audio_frames_per_block = alGetQueueSize(config); + if (audio_frames_per_block < 0) { + fprintf(stderr, "ERROR: couldn't get queue size: %s\n", + alGetErrorString(oserror())); + alClosePort(port); + return false; + } + D(bug("alGetQueueSize %d\n", audio_frames_per_block)); + + alZeroFrames(port, audio_frames_per_block); // so we don't underflow + + // Put a limit on the Mac sound buffer size, to decrease delay + if (audio_frames_per_block > 2048) + audio_frames_per_block = 2048; + // Try to keep the buffer pretty full. 5000 samples of slack works well. + sound_buffer_fill_point = alGetQueueSize(config) - 5000; + if (sound_buffer_fill_point < 0) + sound_buffer_fill_point = alGetQueueSize(config) / 3; + D(bug("fill point %d\n", sound_buffer_fill_point)); + + sound_buffer_size = (AudioStatus.sample_size >> 3) * AudioStatus.channels * audio_frames_per_block; + + // Get a file descriptor we can select() on + + audio_fd = alGetFD(port); + if (audio_fd < 0) { + fprintf(stderr, "ERROR: couldn't get libaudio file descriptor: %s\n", + alGetErrorString(oserror())); + alClosePort(port); + return false; + } + + return true; +} + + +/* + * Initialization + */ + +void AudioInit(void) +{ + // Init audio status (defaults) and feature flags + set_audio_status_format(); + AudioStatus.mixer = 0; + AudioStatus.num_sources = 0; + audio_component_flags = cmpWantsRegisterMessage | kStereoOut | k16BitOut; + + // Sound disabled in prefs? Then do nothing + if (PrefsFindBool("nosound")) + return; + + // Try to open audio library + if (!audio_init_al()) + return; + + // Init semaphore + if (sem_init(&audio_irq_done_sem, 0, 0) < 0) + return; + sem_inited = true; + + // Start streaming thread + pthread_attr_init(&stream_thread_attr); +#if defined(_POSIX_THREAD_PRIORITY_SCHEDULING) + if (geteuid() == 0) { + pthread_attr_setinheritsched(&stream_thread_attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedpolicy(&stream_thread_attr, SCHED_FIFO); + struct sched_param fifo_param; + fifo_param.sched_priority = (sched_get_priority_min(SCHED_FIFO) + sched_get_priority_max(SCHED_FIFO)) / 2; + pthread_attr_setschedparam(&stream_thread_attr, &fifo_param); + } +#endif + stream_thread_active = (pthread_create(&stream_thread, &stream_thread_attr, stream_func, NULL) == 0); + + // Everything OK + audio_open = true; +} + + +/* + * Deinitialization + */ + +void AudioExit(void) +{ + // Stop stream and delete semaphore + if (stream_thread_active) { + stream_thread_cancel = true; +#ifdef HAVE_PTHREAD_CANCEL + pthread_cancel(stream_thread); +#endif + pthread_join(stream_thread, NULL); + stream_thread_active = false; + } + if (sem_inited) + sem_destroy(&audio_irq_done_sem); + + // Close audio library + alClosePort(port); +} + + +/* + * First source added, start audio stream + */ + +void audio_enter_stream() +{ + // Streaming thread is always running to avoid clicking noises +} + + +/* + * Last source removed, stop audio stream + */ + +void audio_exit_stream() +{ + // Streaming thread is always running to avoid clicking noises +} + + +/* + * Streaming function + */ + +static void *stream_func(void *arg) +{ + int16 *last_buffer = new int16[sound_buffer_size / 2]; + fd_set audio_fdset; + int numfds, was_error; + + numfds = audio_fd + 1; + FD_ZERO(&audio_fdset); + + while (!stream_thread_cancel) { + if (AudioStatus.num_sources) { + + // Trigger audio interrupt to get new buffer + D(bug("stream: triggering irq\n")); + SetInterruptFlag(INTFLAG_AUDIO); + TriggerInterrupt(); + D(bug("stream: waiting for ack\n")); + sem_wait(&audio_irq_done_sem); + D(bug("stream: ack received\n")); + + uint32 apple_stream_info; // Mac address of SoundComponentData struct describing next buffer + // Get size of audio data + apple_stream_info = ReadMacInt32(audio_data + adatStreamInfo); + + if (apple_stream_info) { + int work_size = ReadMacInt32(apple_stream_info + scd_sampleCount) * (AudioStatus.sample_size >> 3) * AudioStatus.channels; + D(bug("stream: work_size %d\n", work_size)); + if (work_size > sound_buffer_size) + work_size = sound_buffer_size; + if (work_size == 0) + goto silence; + + // Send data to audio library + if (work_size == sound_buffer_size) + alWriteFrames(port, Mac2HostAddr(ReadMacInt32(apple_stream_info + scd_buffer)), audio_frames_per_block); + else { + // Last buffer + Mac2Host_memcpy(last_buffer, ReadMacInt32(apple_stream_info + scd_buffer), work_size); + memset((uint8 *)last_buffer + work_size, silence_byte, sound_buffer_size - work_size); + alWriteFrames(port, last_buffer, audio_frames_per_block); + } + D(bug("stream: data written\n")); + } else + goto silence; + + } else { + + // Audio not active, play silence + silence: // D(bug("stream: silence\n")); + alZeroFrames(port, audio_frames_per_block); + } + + // Wait for fill point to be reached (may be immediate) + + if (alSetFillPoint(port, sound_buffer_fill_point) < 0) { + fprintf(stderr, "ERROR: alSetFillPoint failed: %s\n", + alGetErrorString(oserror())); + // Should stop the audio here.... + } + + do { + errno = 0; + FD_SET(audio_fd, &audio_fdset); + was_error = select(numfds, NULL, &audio_fdset, NULL, NULL); + } while(was_error < 0 && (errno == EINTR)); + if (was_error < 0) { + fprintf(stderr, "ERROR: select returned %d, errno %d\n", + was_error, errno); + // Should stop audio here.... + } + } + delete[] last_buffer; + return NULL; +} + + +/* + * MacOS audio interrupt, read next data block + */ + +void AudioInterrupt(void) +{ + D(bug("AudioInterrupt\n")); + + // Get data from apple mixer + if (AudioStatus.mixer) { + M68kRegisters r; + r.a[0] = audio_data + adatStreamInfo; + r.a[1] = AudioStatus.mixer; + Execute68k(audio_data + adatGetSourceData, &r); + D(bug(" GetSourceData() returns %08lx\n", r.d[0])); + } else + WriteMacInt32(audio_data + adatStreamInfo, 0); + + // Signal stream function + sem_post(&audio_irq_done_sem); + D(bug("AudioInterrupt done\n")); +} + + +/* + * Set sampling parameters + * "index" is an index into the audio_sample_rates[] etc. arrays + * It is guaranteed that AudioStatus.num_sources == 0 + */ + +void audio_set_sample_rate(int index) +{ +} + +void audio_set_sample_size(int index) +{ +} + +void audio_set_channels(int index) +{ +} + + +/* + * Get/set volume controls (volume values received/returned have the left channel + * volume in the upper 16 bits and the right channel volume in the lower 16 bits; + * both volumes are 8.8 fixed point values with 0x0100 meaning "maximum volume")) + */ + +bool audio_get_main_mute(void) +{ + return false; +} + +uint32 audio_get_main_volume(void) +{ + return 0x01000100; +} + +bool audio_get_speaker_mute(void) +{ + return false; +} + +uint32 audio_get_speaker_volume(void) +{ + return 0x01000100; +} + +void audio_set_main_mute(bool mute) +{ +} + +void audio_set_main_volume(uint32 vol) +{ +} + +void audio_set_speaker_mute(bool mute) +{ +} + +void audio_set_speaker_volume(uint32 vol) +{ +} diff --git a/BasiliskII/src/Unix/aclocal.m4 b/BasiliskII/src/Unix/aclocal.m4 index 3243d88f..7d10bf23 100644 --- a/BasiliskII/src/Unix/aclocal.m4 +++ b/BasiliskII/src/Unix/aclocal.m4 @@ -245,6 +245,8 @@ AC_ARG_ENABLE(esdtest, [ --disable-esdtest Do not try to compile and run if test "$ESD_CONFIG" = "no" ; then no_esd=yes else + AC_LANG_SAVE + AC_LANG_C ESD_CFLAGS=`$ESD_CONFIG $esdconf_args --cflags` ESD_LIBS=`$ESD_CONFIG $esdconf_args --libs` @@ -321,6 +323,7 @@ int main () ],, no_esd=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) CFLAGS="$ac_save_CFLAGS" LIBS="$ac_save_LIBS" + AC_LANG_RESTORE fi fi if test "x$no_esd" = x ; then @@ -340,6 +343,8 @@ int main () echo "*** Could not run ESD test program, checking why..." CFLAGS="$CFLAGS $ESD_CFLAGS" LIBS="$LIBS $ESD_LIBS" + AC_LANG_SAVE + AC_LANG_C AC_TRY_LINK([ #include #include @@ -359,6 +364,7 @@ int main () echo "*** may want to edit the esd-config script: $ESD_CONFIG" ]) CFLAGS="$ac_save_CFLAGS" LIBS="$ac_save_LIBS" + AC_LANG_RESTORE fi fi ESD_CFLAGS="" diff --git a/BasiliskII/src/Unix/configure.in b/BasiliskII/src/Unix/configure.in index a0d7e190..e94dd8fd 100644 --- a/BasiliskII/src/Unix/configure.in +++ b/BasiliskII/src/Unix/configure.in @@ -259,10 +259,11 @@ solaris*) DEFINES="$DEFINES -DBSD_COMP -D_POSIX_PTHREAD_SEMANTICS" ;; irix*) + AUDIOSRC=Irix/audio_irix.cpp EXTRASYSSRCS=Irix/unaligned.c dnl IRIX headers work fine, but somehow don't define or use "STDC_HEADERS" DEFINES="$DEFINES -DCRTSCTS=CNEW_RTSCTS -DB230400=B115200 -DSTDC_HEADERS" - LIBS="$LIBS -lm" + LIBS="$LIBS -laudio" ;; esac diff --git a/BasiliskII/src/Unix/main_unix.cpp b/BasiliskII/src/Unix/main_unix.cpp index 9f2293d6..49263ca2 100644 --- a/BasiliskII/src/Unix/main_unix.cpp +++ b/BasiliskII/src/Unix/main_unix.cpp @@ -860,27 +860,35 @@ uint64 GetTicks_usec(void) /* * Delay by specified number of microseconds (<1 second) - * (adapted from SDL_Delay() source) + * (adapted from SDL_Delay() source; this function is designed to provide + * the highest accuracy possible) */ void Delay_usec(uint32 usec) { int was_error; -#ifndef __linux__ // Non-Linux implementations need to calculate time left + +#if defined(linux) + struct timeval tv; +#elif defined(__FreeBSD__) || defined(sgi) + struct timespec elapsed, tv; +#else // Non-Linux implementations need to calculate time left uint64 then, now, elapsed; #endif - struct timeval tv; // Set the timeout interval - Linux only needs to do this once -#ifdef __linux__ +#if defined(linux) tv.tv_sec = 0; tv.tv_usec = usec; +#elif defined(__FreeBSD__) + elapsed.tv_sec = 0; + elapsed.tv_nsec = usec * 1000; #else then = GetTicks_usec(); #endif do { errno = 0; -#ifndef __linux__ +#if !defined(linux) && !defined(__FreeBSD__) && !defined(sgi) /* Calculate the time interval left (in case of interrupt) */ now = GetTicks_usec(); elapsed = now - then; @@ -891,7 +899,13 @@ void Delay_usec(uint32 usec) tv.tv_sec = 0; tv.tv_usec = usec; #endif +#if defined(__FreeBSD__) || defined(sgi) + tv.tv_sec = elapsed.tv_sec; + tv.tv_nsec = elapsed.tv_nsec; + was_error = nanosleep(&tv, &elapsed); +#else was_error = select(0, NULL, NULL, NULL, &tv); +#endif } while (was_error && (errno == EINTR)); } diff --git a/BasiliskII/src/Unix/video_x.cpp b/BasiliskII/src/Unix/video_x.cpp index 83ec7b86..b223deb1 100644 --- a/BasiliskII/src/Unix/video_x.cpp +++ b/BasiliskII/src/Unix/video_x.cpp @@ -1239,7 +1239,7 @@ void VideoInterrupt(void) void video_set_palette(uint8 *pal) { -#ifdef HAVE_PTHREDS +#ifdef HAVE_PTHREADS pthread_mutex_lock(&palette_lock); #endif