summaryrefslogtreecommitdiff
path: root/pcsound/pcsound_bsd.c
diff options
context:
space:
mode:
authorSimon Howard2007-09-03 00:30:51 +0000
committerSimon Howard2007-09-03 00:30:51 +0000
commit0517fad3f4e89304dc7a4ad18c5785e02173ca6c (patch)
treea63deca7e08c2a27c94a53e0bda21ffab9fcde94 /pcsound/pcsound_bsd.c
parent91eea3f8acd63d1641e03fc7088c833db7d0daf1 (diff)
downloadchocolate-doom-0517fad3f4e89304dc7a4ad18c5785e02173ca6c.tar.gz
chocolate-doom-0517fad3f4e89304dc7a4ad18c5785e02173ca6c.tar.bz2
chocolate-doom-0517fad3f4e89304dc7a4ad18c5785e02173ca6c.zip
Add pcsound driver for OpenBSD.
Subversion-branch: /trunk/chocolate-doom Subversion-revision: 965
Diffstat (limited to 'pcsound/pcsound_bsd.c')
-rw-r--r--pcsound/pcsound_bsd.c315
1 files changed, 315 insertions, 0 deletions
diff --git a/pcsound/pcsound_bsd.c b/pcsound/pcsound_bsd.c
new file mode 100644
index 00000000..00367d1f
--- /dev/null
+++ b/pcsound/pcsound_bsd.c
@@ -0,0 +1,315 @@
+// Emacs style mode select -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2007 Simon Howard
+//
+// 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.
+//
+// DESCRIPTION:
+// PC speaker driver for [Open]BSD
+// (Should be NetBSD as well, but untested).
+//
+//-----------------------------------------------------------------------------
+
+#include "config.h"
+
+#ifdef HAVE_DEV_ISA_SPKRIO_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <dev/isa/spkrio.h>
+#include <sys/ioctl.h>
+#include <sys/signal.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "SDL.h"
+#include "SDL_thread.h"
+
+#include "pcsound.h"
+#include "pcsound_internal.h"
+
+#define SPEAKER_DEVICE "/dev/speaker"
+
+//
+// This driver is far more complicated than it should be, because
+// OpenBSD has sucky support for threads. Because multithreading
+// is done in userspace, invoking the ioctl to make the speaker
+// beep will lock all threads until the beep has completed.
+//
+// Thus, to get the beeping to occur in real-time, we must invoke
+// the ioctl in a separate process. To do this, a separate
+// sound server is forked that listens on a socket for tones to
+// play. When a tone is received, a reply is sent back to the
+// main process and the tone played.
+//
+// Meanwhile, back in the main process, there is a sound thread
+// that runs, invoking the pcsound callback function to get
+// more tones. This blocks on the sound server socket, waiting
+// for replies. In this way, when the sound server finishes
+// playing a tone, the next one is sent.
+//
+// This driver is a bit less accurate than the others, because
+// we can only specify sound durations in 1/100ths of a second,
+// as opposed to the normal millisecond durations.
+
+static pcsound_callback_func callback;
+static int sound_server_pid;
+static int sleep_adjust = 0;
+static int sound_thread_running;
+static SDL_Thread *sound_thread_handle;
+static int sound_server_pipe[2];
+
+// Play a sound, checking how long the system call takes to complete
+// and autoadjusting for drift.
+
+static void AdjustedBeep(int speaker_handle, int ms, int freq)
+{
+ unsigned int start_time;
+ unsigned int end_time;
+ unsigned int actual_time;
+ tone_t tone;
+
+ // Adjust based on previous error to keep the tempo right
+
+ if (sleep_adjust > ms)
+ {
+ sleep_adjust -= ms;
+ return;
+ }
+ else
+ {
+ ms -= sleep_adjust;
+ }
+
+ // Invoke the system call and time how long it takes
+
+ start_time = SDL_GetTicks();
+
+ tone.duration = ms / 10; // in 100ths of a second
+ tone.frequency = freq;
+
+ // Always a positive duration
+
+ if (tone.duration < 1)
+ {
+ tone.duration = 1;
+ }
+
+ if (ioctl(speaker_handle, SPKRTONE, &tone) != 0)
+ {
+ perror("ioctl");
+ return;
+ }
+
+ end_time = SDL_GetTicks();
+
+ if (end_time > start_time)
+ {
+ actual_time = end_time - start_time;
+ }
+ else
+ {
+ actual_time = ms;
+ }
+
+ if (actual_time < ms)
+ {
+ actual_time = ms;
+ }
+
+ // Save sleep_adjust for next time
+
+ sleep_adjust = actual_time - ms;
+}
+
+static void SoundServer(void)
+{
+ int speaker_handle;
+ tone_t tone;
+ int result;
+
+ // Try to open the speaker device
+
+ speaker_handle = open(SPEAKER_DEVICE, O_WRONLY);
+
+ if (speaker_handle == -1)
+ {
+ // Don't have permissions for the console device?
+
+ fprintf(stderr, "PCSound_BSD_Init: Failed to open '%s': %s\n",
+ SPEAKER_DEVICE, strerror(errno));
+ return;
+ }
+
+ // Run in a loop, invoking the callback
+
+ for (;;)
+ {
+ result = read(sound_server_pipe[1], &tone, sizeof(tone_t));
+
+ if (result < 0)
+ {
+ perror("read");
+ return;
+ }
+
+ // Send back a response, so the main process knows to send another
+
+ write(sound_server_pipe[1], &tone, sizeof(tone_t));
+
+ // Beep! (blocks until complete)
+
+ AdjustedBeep(speaker_handle, tone.duration, tone.frequency);
+ }
+
+ // Finished, close the handle
+
+ close(speaker_handle);
+}
+
+// Start up the sound server. Returns non-zero if successful.
+
+static int StartSoundServer(void)
+{
+ int result;
+
+ // Create a pipe for communications
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sound_server_pipe) < 0)
+ {
+ perror("socketpair");
+ return 0;
+ }
+
+ // Start a separate process to generate PC speaker output
+ // We can't use the SDL threading functions because OpenBSD's
+ // threading sucks :-(
+
+ result = fork();
+
+ if (result < 0)
+ {
+ fprintf(stderr, "Failed to fork sound server!\n");
+ return 0;
+ }
+ else if (result == 0)
+ {
+ // This is the child (sound server)
+
+ SoundServer();
+
+ exit(0);
+ }
+ else
+ {
+ // This is the parent
+
+ sound_server_pid = result;
+ }
+
+ return 1;
+}
+
+static void StopSoundServer(void)
+{
+ int status;
+
+ kill(sound_server_pid, SIGINT);
+ waitpid(sound_server_pid, &status, 0);
+}
+
+static int SoundThread(void *unused)
+{
+ tone_t tone;
+ int duration;
+ int frequency;
+
+ while (sound_thread_running)
+ {
+ // Get the next frequency to play
+
+ callback(&duration, &frequency);
+
+//printf("dur: %i, freq: %i\n", duration, frequency);
+
+ // Build up a tone structure and send to the sound server
+
+ tone.frequency = frequency;
+ tone.duration = duration;
+
+ if (write(sound_server_pipe[0], &tone, sizeof(tone_t)) < 0)
+ {
+ perror("write");
+ break;
+ }
+
+ // Wait until the sound server responds before sending another
+
+ if (read(sound_server_pipe[0], &tone, sizeof(tone_t)) < 0)
+ {
+ perror("read");
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int PCSound_BSD_Init(pcsound_callback_func callback_func)
+{
+ callback = callback_func;
+
+ if (!StartSoundServer())
+ {
+ fprintf(stderr, "PCSound_BSD_Init: Failed to start sound server.\n");
+ return 0;
+ }
+
+ sound_thread_running = 1;
+ sound_thread_handle = SDL_CreateThread(SoundThread, NULL);
+
+ return 1;
+}
+
+static void PCSound_BSD_Shutdown(void)
+{
+ // Stop the sound thread
+
+ sound_thread_running = 0;
+
+ SDL_WaitThread(sound_thread_handle, NULL);
+
+ // Stop the sound server
+
+ StopSoundServer();
+}
+
+pcsound_driver_t pcsound_bsd_driver =
+{
+ "BSD",
+ PCSound_BSD_Init,
+ PCSound_BSD_Shutdown,
+};
+
+#endif /* #ifdef HAVE_DEV_ISA_SPKRIO_H */
+