This repository has been archived on 2023-07-11. You can view files and clone it, but cannot push or open issues or pull requests.
chocolate-doom-wii/pcsound/pcsound_bsd.c
2016-10-30 18:40:00 -04:00

321 lines
7 KiB
C

//
// Copyright(C) 2005-2014 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.
//
// DESCRIPTION:
// PC speaker driver for [Open]BSD
// (Should be NetBSD as well, but untested).
//
#include "config.h"
// OpenBSD/NetBSD:
#ifdef HAVE_DEV_ISA_SPKRIO_H
#define HAVE_BSD_SPEAKER
#include <dev/isa/spkrio.h>
#endif
// FreeBSD
#ifdef HAVE_DEV_SPEAKER_SPEAKER_H
#define HAVE_BSD_SPEAKER
#include <dev/speaker/speaker.h>
#endif
#ifdef HAVE_BSD_SPEAKER
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.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(int speaker_handle)
{
tone_t tone;
int result;
// 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);
}
}
// Start up the sound server. Returns non-zero if successful.
static int StartSoundServer(void)
{
int result;
int speaker_handle;
// 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, "StartSoundServer: Failed to open '%s': %s\n",
SPEAKER_DEVICE, strerror(errno));
return 0;
}
// Create a pipe for communications
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sound_server_pipe) < 0)
{
perror("socketpair");
close(speaker_handle);
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");
close(speaker_handle);
return 0;
}
else if (result == 0)
{
// This is the child (sound server)
SoundServer(speaker_handle);
close(speaker_handle);
exit(0);
}
else
{
// This is the parent
sound_server_pid = result;
close(speaker_handle);
}
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_BSD_SPEAKER */