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.