This is sound.c in view mode; [Download] [Up]
/*
* sound.c - General code for the sound interface
*
* Written by
* Teemu Rantanen (tvr@cs.hut.fi)
*
* Resource and cmdline code by
* Ettore Perazzoli (ettore@comm2000.it)
*
* This file is part of VICE, the Versatile Commodore Emulator.
* See README for copyright notice.
*
* 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 <stdio.h>
#include <time.h>
#include "vice.h"
#include "sound.h"
#include "cmdline.h"
#include "resources.h"
#include "vsync.h"
#include "ui.h"
#include "maincpu.h"
#include "utils.h"
/* ------------------------------------------------------------------------- */
/* Resource handling -- Added by Ettore 98-04-26. */
/* FIXME: We need sanity checks! And do we really need all of these
`sound_close()' calls? */
static int playback_enabled; /* app_resources.sound */
static int sample_rate; /* app_resources.soundSampleRate */
static char *device_name; /* app_resources.soundDeviceName */
static char *device_arg; /* app_resources.soundDeviceArg */
static int buffer_size; /* app_resources.soundBufferSize */
static int suspend_time; /* app_resources.soundSuspendTime */
static int speed_adjustment_setting; /* app_resources.soundSpeedAdjustment */
static int oversampling_factor; /* app_resources.soundOversample */
static int set_playback_enabled(resource_value_t v)
{
playback_enabled = (int)v;
if (playback_enabled)
vsync_disable_timer();
sound_close();
return 0;
}
static int set_sample_rate(resource_value_t v)
{
sample_rate = (int) v;
sound_close();
return 0;
}
static int set_device_name(resource_value_t v)
{
string_set(&device_name, (char *) v);
sound_close();
return 0;
}
static int set_device_arg(resource_value_t v)
{
string_set(&device_arg, (char *) v);
sound_close();
return 0;
}
static int set_buffer_size(resource_value_t v)
{
buffer_size = (int)v;
sound_close();
return 0;
}
static int set_suspend_time(resource_value_t v)
{
suspend_time = (int)v;
if (suspend_time < 0)
suspend_time = 0;
sound_close();
return 0;
}
static int set_speed_adjustment_setting(resource_value_t v)
{
speed_adjustment_setting = (int)v;
sound_close();
return 0;
}
static int set_oversampling_factor(resource_value_t v)
{
oversampling_factor = (int)v;
if (oversampling_factor < 0 || oversampling_factor > 3) {
fprintf(stderr, "Warning: invalid oversampling factor %d."
" Forcing 3.\n", oversampling_factor);
oversampling_factor = 3;
}
sound_close();
return 0;
}
static resource_t resources[] = {
{ "Sound", RES_INTEGER, (resource_value_t) 0,
(resource_value_t *) &playback_enabled, set_playback_enabled },
{ "SoundSampleRate", RES_INTEGER,
(resource_value_t) SOUND_SAMPLE_RATE,
(resource_value_t *) &sample_rate, set_sample_rate },
{ "SoundDeviceName", RES_STRING, (resource_value_t) NULL,
(resource_value_t) &device_name, set_device_name },
{ "SoundDeviceArg", RES_STRING, (resource_value_t) NULL,
(resource_value_t *) &device_arg, set_device_arg },
{ "SoundBufferSize", RES_INTEGER, (resource_value_t) SOUND_SAMPLE_BUFFER_SIZE,
(resource_value_t *) &buffer_size, set_buffer_size },
{ "SoundSuspendTime", RES_INTEGER, (resource_value_t) 0,
(resource_value_t *) &suspend_time, set_suspend_time },
{ "SoundSpeedAdjustment", RES_INTEGER, (resource_value_t) SOUND_ADJUST_FLEXIBLE,
(resource_value_t *) &speed_adjustment_setting,
set_speed_adjustment_setting },
{ "SoundOversample", RES_INTEGER, (resource_value_t) 0,
(resource_value_t *) &oversampling_factor, set_oversampling_factor },
{ NULL }
};
int sound_init_resources(void)
{
return resources_register(resources);
}
/* ------------------------------------------------------------------------- */
/* Command-line options -- Added by Ettore 98-05-09. */
static cmdline_option_t cmdline_options[] = {
{ "-sound", SET_RESOURCE, 0, NULL, NULL, "Sound", (resource_value_t) 1,
NULL, "Enable sound playback" },
{ "+sound", SET_RESOURCE, 0, NULL, NULL, "Sound", (resource_value_t) 0,
NULL, "Disable sound playback" },
{ "-soundrate", SET_RESOURCE, 1, NULL, NULL, "SoundSampleRate", NULL,
"<value>", "Set sound sample rate to <value> Hz" },
{ "-soundbufsize", SET_RESOURCE, 1, NULL, NULL, "SoundBufferSize", NULL,
"<value>", "Set sound buffer size to <value> msec" },
{ "-sounddev", SET_RESOURCE, 1, NULL, NULL, "SoundDeviceName", NULL,
"<name>", "Specify sound driver" },
{ "-soundarg", SET_RESOURCE, 1, NULL, NULL, "SoundDeviceArg", NULL,
"<args>", "Specify initialization parameters for sound driver" },
{ "-soundsync", SET_RESOURCE, 1, NULL, NULL, "SoundSpeedAdjustment", NULL,
"<sync>",
"Set sound speed adjustment (0: flexible, 1: adjusting, 2: exact)" },
{ NULL }
};
int sound_init_cmdline_options(void)
{
return cmdline_register_options(cmdline_options);
}
/* ------------------------------------------------------------------------- */
/* timing constants */
static unsigned int cycles_per_sec;
static unsigned int cycles_per_rfsh;
static double rfsh_per_sec;
/* Flag: Is warp mode enabled? */
static int warp_mode_enabled;
#define BUFSIZE 32768
typedef struct
{
/* sid itself */
sound_t *psid;
/* warnings */
warn_t *pwarn;
warn_t *pwarndev;
/* number of clocks between each sample. used value */
double clkstep;
/* number of clocks between each sample. original value */
double origclkstep;
/* factor between those two clksteps */
double clkfactor;
/* time of last sample generated */
double fclk;
/* time of last write to sid. used for pdev->dump() */
CLOCK wclk;
/* sample buffer */
SWORD buffer[BUFSIZE];
/* sample buffer pointer */
int bufptr;
/* pointer to device structure in use */
sound_device_t *pdev;
/* number of samples in a fragment */
int fragsize;
/* number of fragments in kernel buffer */
int fragnr;
/* number of samples in kernel buffer */
int bufsize;
/* return value of first pdev->bufferstatus() call to device */
int firststatus;
/* constants related to adjusting sound */
int prevused;
int prevfill;
/* is the device suspended? */
int issuspended;
SWORD lastsample;
/* nr of samples to oversame / real sample */
DWORD oversamplenr;
/* number of shift needed on oversampling */
DWORD oversampleshift;
} snddata_t;
static snddata_t snddata;
/* device registration code */
static sound_device_t *sound_devices[32];
int sound_register_device(sound_device_t *pdevice)
{
int i;
for (i = 0; sound_devices[i]; i++);
sound_devices[i] = pdevice;
return 0;
}
/* close sid device and show error dialog if needed */
static int closesound(char *msg)
{
if (snddata.pdev)
{
warn(snddata.pwarn, -1, "closing device `%s'", snddata.pdev->name);
if (snddata.pdev->close)
snddata.pdev->close(snddata.pwarndev);
snddata.pdev = NULL;
}
if (snddata.pwarndev)
{
warn_free(snddata.pwarndev);
snddata.pwarndev = NULL;
}
if (snddata.psid)
{
sound_machine_close(snddata.psid);
snddata.psid = NULL;
}
if (msg)
{
suspend_speed_eval();
if (strcmp(msg, ""))
{
ui_error(msg);
playback_enabled = 0;
ui_update_menus();
}
}
snddata.prevused = snddata.prevfill = 0;
return 1;
}
/* code to disable sid for a given number of seconds if needed */
static int disabletime;
static void suspendsound(char *reason)
{
disabletime = time(0);
warn(snddata.pwarn, -1, "SUSPEND: disabling sound for %d secs (%s)",
suspend_time, reason);
closesound("");
}
static void enablesound(void)
{
int now, diff;
if (!disabletime)
return;
now = time(0);
diff = now - disabletime;
if (diff < 0 || diff >= suspend_time)
{
warn(snddata.pwarn, -1, "ENABLE");
disabletime = 0;
}
}
/* open sound device */
static int initsid(void)
{
int i, tmp;
sound_device_t *pdev;
char *name;
char *param;
int speed;
int fragsize;
int fragnr;
double bufsize;
char err[1024];
if (suspend_time > 0 && disabletime)
return 1;
name = device_name;
if (name && !strcmp(name, ""))
name = NULL;
param = device_arg;
if (param && !strcmp(param, ""))
param = NULL;
tmp = buffer_size;
if (tmp < 100 || tmp > 1000)
tmp = SOUND_SAMPLE_BUFFER_SIZE;
bufsize = tmp / 1000.0;
speed = sample_rate;
if (speed < 8000 || speed > 50000)
speed = SOUND_SAMPLE_RATE;
/* calculate optimal fragments */
fragsize = speed / ((int)rfsh_per_sec);
for (i = 1; 1 << i < fragsize; i++);
fragsize = 1 << i;
fragnr = (speed*bufsize + fragsize - 1) / fragsize;
if (fragnr < 3)
fragnr = 3;
for (i = 0; (pdev = sound_devices[i]); i++)
{
if ((name && pdev->name && !strcmp(pdev->name, name)) ||
(!name && pdev->name))
{
if (pdev->init)
{
sprintf(err, "SOUND(%s)", pdev->name);
snddata.pwarndev = warn_init(err, 128);
tmp = pdev->init(snddata.pwarndev, param, &speed,
&fragsize, &fragnr, bufsize);
if (tmp)
{
sprintf(err, "Audio: initialization failed for device `%s'.",
pdev->name);
return closesound(err);
}
}
snddata.issuspended = -1;
snddata.lastsample = 0;
snddata.pdev = pdev;
snddata.fragsize = fragsize;
snddata.fragnr = fragnr;
snddata.bufsize = fragsize*fragnr;
snddata.bufptr = 0;
warn(snddata.pwarn, -1,
"opened device `%s' speed %dHz fragsize %.3fs bufsize %.3fs",
pdev->name, speed, (double)fragsize / speed,
(double)snddata.bufsize / speed);
sample_rate = speed;
snddata.oversampleshift = oversampling_factor;
snddata.oversamplenr = 1 << snddata.oversampleshift;
snddata.psid = sound_machine_open(speed*snddata.oversamplenr,
cycles_per_sec);
if (!snddata.psid)
return closesound("Audio: Cannot initialize sound module");
if (pdev->bufferstatus)
snddata.firststatus = pdev->bufferstatus(snddata.pwarndev, 1);
snddata.clkstep = (double)cycles_per_sec / speed;
if (snddata.oversamplenr > 1)
{
snddata.clkstep /= snddata.oversamplenr;
warn(snddata.pwarn, -1, "using %dx oversampling",
snddata.oversamplenr);
}
snddata.origclkstep = snddata.clkstep;
snddata.clkfactor = 1.0;
snddata.fclk = clk;
snddata.wclk = clk;
return 0;
}
}
sprintf(err, "Audio: device `%s' not found or not supported.", name);
return closesound(err);
}
/* run sid */
static int sound_run_sound(void)
{
int nr, i;
/* XXX: implement the exact ... */
if (!playback_enabled || warp_mode_enabled)
return 1;
if (suspend_time > 0 && disabletime)
return 1;
if (!snddata.pdev)
{
i = initsid();
if (i)
return i;
}
nr = (clk - snddata.fclk) / snddata.clkstep;
if (!nr)
return 0;
if (snddata.bufptr + nr > BUFSIZE)
return closesound("Audio: sound buffer overflow.");
sound_machine_calculate_samples(snddata.psid,
snddata.buffer + snddata.bufptr,
nr);
snddata.bufptr += nr;
snddata.fclk += nr*snddata.clkstep;
return 0;
}
/* flush all generated samples from buffer to sounddevice. adjust sid runspeed
to match real running speed of program */
int sound_flush(int relative_speed)
{
int i, nr, space, used, fill = 0, dir = 0;
if (!playback_enabled || warp_mode_enabled)
return 0;
if (suspend_time > 0)
enablesound();
i = sound_run_sound();
if (i)
return 0;
sound_resume();
if (snddata.pdev->flush)
{
char *state = sound_machine_dump_state(snddata.psid);
i = snddata.pdev->flush(snddata.pwarndev, state);
free(state);
if (i)
{
closesound("Audio: cannot flush.");
return 0;
}
}
nr = snddata.bufptr -
snddata.bufptr % (snddata.fragsize*snddata.oversamplenr);
if (!nr)
return 0;
/* handle oversampling */
if (snddata.oversamplenr > 1)
{
int j, k, newnr;
SDWORD v;
newnr = nr >> snddata.oversampleshift;
for (k = i = 0; i < newnr; i++)
{
for (v = j = 0; j < snddata.oversamplenr; j++)
v += snddata.buffer[k++];
snddata.buffer[i] = v >> snddata.oversampleshift;
}
for (i = 0; i < snddata.bufptr - nr; i++)
snddata.buffer[newnr + i] = snddata.buffer[nr + i];
snddata.bufptr -= (nr - newnr);
nr = newnr;
}
/* adjust speed */
if (snddata.pdev->bufferstatus)
{
space = snddata.pdev->bufferstatus(snddata.pwarndev, 0);
if (!snddata.firststatus)
space = snddata.bufsize - space;
used = snddata.bufsize - space;
if (space < 0 || used < 0)
{
warn(snddata.pwarn, -1, "fragment problems %d %d %d",
space, used, snddata.firststatus);
closesound("Audio: fragment problems.");
return 0;
}
/* buffer empty */
if (used <= snddata.fragsize)
{
SWORD *p, v;
int j;
static int prev;
int now;
if (suspend_time > 0)
{
now = time(0);
if (now == prev)
{
suspendsound("buffer overruns");
return 0;
}
prev = now;
}
j = snddata.fragsize*snddata.fragnr - nr;
if (j > snddata.bufsize / 2
&& speed_adjustment_setting != SOUND_ADJUST_ADJUSTING
&& relative_speed)
{
j = snddata.fragsize*(snddata.fragnr/2);
}
j *= sizeof(*p);
if (j > 0)
{
p = alloca(j);
v = snddata.bufptr > 0 ? snddata.buffer[0] : 0;
for (i = 0; i < j / sizeof(*p); i++)
p[i] = (float)v*i/(j / sizeof(*p));
i = snddata.pdev->write(snddata.pwarndev, p,
j / sizeof(*p));
if (i)
{
closesound("Audio: write to sound device failed.");
return 0;
}
snddata.lastsample = v;
}
fill = j;
}
if (speed_adjustment_setting != SOUND_ADJUST_ADJUSTING
&& relative_speed > 0)
snddata.clkfactor = relative_speed / 100.0;
else
{
if (snddata.prevfill)
snddata.prevused = used;
snddata.clkfactor *= 1.0 + 0.9*(used - snddata.prevused)/
snddata.bufsize;
}
snddata.prevused = used;
snddata.prevfill = fill;
if (speed_adjustment_setting == SOUND_ADJUST_EXACT)
{
/* finetune VICE timer */
static int lasttime = 0;
static int minspace = 0;
int t = time(0);
if (minspace > space - nr)
minspace = space - nr;
if (t != lasttime)
{
lasttime = t;
if (minspace <= 0)
dir = -1;
if (minspace > snddata.fragsize)
dir = 1;
#if 0
printf("sync %d %d %f\n", dir, minspace, snddata.clkfactor);
#endif
minspace = snddata.bufsize;
}
}
else
snddata.clkfactor *= 0.9 + (used+nr)*0.12/snddata.bufsize;
snddata.clkstep = snddata.origclkstep * snddata.clkfactor;
if (cycles_per_rfsh / snddata.clkstep >= snddata.bufsize)
{
if (suspend_time > 0)
suspendsound("running too slow");
else
closesound("Audio: running too slow.");
return 0;
}
if (nr > space && nr < used)
nr = space;
}
i = snddata.pdev->write(snddata.pwarndev, snddata.buffer, nr);
if (i)
{
closesound("Audio: write to sounddevice failed.");
return 0;
}
snddata.lastsample = snddata.buffer[nr-1];
snddata.bufptr -= nr;
if (snddata.bufptr > 0)
{
for (i = 0; i < snddata.bufptr; i++)
snddata.buffer[i] = snddata.buffer[i + nr];
}
return dir;
}
/* close sid */
void sound_close(void)
{
closesound(NULL);
}
/* suspend sid (eg. before pause) */
void sound_suspend(void)
{
int i;
SWORD *p, v;
if (snddata.pdev)
{
if (snddata.pdev->write && snddata.issuspended == 0)
{
p = xmalloc(snddata.fragsize*sizeof(SWORD));
if (!p)
return;
v = snddata.lastsample;
for (i = 0; i < snddata.fragsize; i++)
p[i] = v - (float)v*i/snddata.fragsize;
i = snddata.pdev->write(snddata.pwarndev, p, snddata.fragsize);
free(p);
if (i)
return;
}
if (snddata.pdev->suspend && snddata.issuspended == 0)
{
i = snddata.pdev->suspend(snddata.pwarndev);
if (i)
return;
}
snddata.issuspended = 1;
}
}
/* resume sid */
void sound_resume(void)
{
int i;
if (snddata.pdev)
{
if (snddata.pdev->resume && snddata.issuspended == 1)
{
i = snddata.pdev->resume(snddata.pwarndev);
snddata.issuspended = i ? 1 : 0;
}
else
snddata.issuspended = 0;
}
}
/* initialize sid at program start -time */
void sound_init(unsigned int clock_rate, unsigned int ticks_per_frame)
{
cycles_per_sec = clock_rate;
cycles_per_rfsh = ticks_per_frame;
rfsh_per_sec = (1.0 / ((double)cycles_per_rfsh / (double)cycles_per_sec));
snddata.pwarn = warn_init("SOUND", 128);
sound_machine_init();
#if defined(HAVE_LINUX_SOUNDCARD_H) || defined(HAVE_MACHINE_SOUNDCARD_H)
sound_init_uss_device();
#endif
#if defined(HAVE_DMEDIA_AUDIO_H)
sound_init_sgi_device();
#endif
#if defined(HAVE_SYS_AUDIOIO_H)
sound_init_sun_device();
#endif
#if defined(HAVE_SYS_AUDIO_H)
sound_init_hpux_device();
#endif
#if defined(HAVE_LIBUMSOBJ) && defined(HAVE_UMS_UMSAUDIODEVICE_H) && defined(HAVE_UMS_UMSBAUDDEVICE_H)
sound_init_aix_device();
#endif
#if defined(HAVE_SDL_AUDIO_H) && defined(HAVE_SDL_SLEEP_H)
sound_init_sdl_device();
#endif
#ifdef __MSDOS__
#ifdef USE_MIDAS_SOUND
sound_init_midas_device();
#else
sound_init_sb_device();
#endif
#endif
sound_init_dummy_device();
sound_init_fs_device();
sound_init_speed_device();
sound_init_dump_device();
#if 0
sound_init_test_device(); /* XXX: missing */
#endif
}
void sound_prevent_clk_overflow(CLOCK sub)
{
snddata.fclk -= sub;
snddata.wclk -= sub;
if (snddata.psid)
sound_machine_prevent_clk_overflow(snddata.psid, sub);
}
double sound_sample_position(void)
{
return (clk - snddata.fclk) / snddata.clkstep;
}
int sound_read(ADDRESS addr)
{
if (sound_run_sound())
return -1;
return sound_machine_read(snddata.psid, addr);
}
void sound_store(ADDRESS addr, BYTE val)
{
int i;
if (sound_run_sound() == 0)
{
sound_machine_store(snddata.psid, addr, val);
if (snddata.pdev->dump)
{
i = snddata.pdev->dump(snddata.pwarndev, addr, val,
clk - snddata.wclk);
snddata.wclk = clk;
if (i)
closesound("Audio: store to sounddevice failed.");
}
}
}
void sound_set_warp_mode(int value)
{
warp_mode_enabled = value;
sound_close();
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.