--- generator/main/gensound.c 2020/03/04 04:46:43 1.1 +++ generator/main/gensound.c 2020/03/04 04:47:08 1.1.1.3 @@ -1,20 +1,13 @@ /* Generator is (c) James Ponder, 1997-2001 http://www.squish.net/generator/ */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include "generator.h" #include "gensound.h" +#include "gensoundp.h" #include "vdp.h" #include "ui.h" +#include "sn76496.h" #ifdef JFM # include "jfm.h" @@ -23,46 +16,44 @@ # include "fm.h" #endif -#include - /*** variables externed ***/ -int sound_feedback = 0; /* -1, running out of sound - +0, lots of sound, do something */ -int sound_debug = 0; /* 0 for no debug, 1 for debug */ -int sound_minfields = 5; /* minimum fields to try to keep buffered */ -int sound_maxfields = 10; /* max fields to buffer before blocking */ +int sound_debug = 0; /* debug mode */ +int sound_feedback = 0; /* -1, running out of sound + +0, lots of sound, do something */ +unsigned int sound_minfields = 5; /* min fields to try to buffer */ +unsigned int sound_maxfields = 10; /* max fields before blocking */ +unsigned int sound_speed = SOUND_SAMPLERATE; /* sample rate */ +unsigned int sound_sampsperfield; /* samples per field */ +unsigned int sound_threshold; /* samples in buffer aiming for */ uint8 sound_regs1[256]; uint8 sound_regs2[256]; uint8 sound_address1 = 0; uint8 sound_address2 = 0; uint8 sound_keys[8]; +int sound_logsample = 0; /* sample to log or -1 if none */ +unsigned int sound_on = 1; /* sound enabled */ +unsigned int sound_psg = 1; /* psg enabled */ +unsigned int sound_fm = 1; /* fm enabled */ -/* SN76489 snd_cpu; */ +/* pal is lowest framerate */ +uint16 sound_soundbuf[2][SOUND_MAXRATE / 50]; /*** forward references ***/ -/* void snd_sndout(int chan, int freq, int vol); */ +static void sound_process(void); +static void sound_writetolog(unsigned char c); /*** file scoped variables ***/ -#define SOUND_MAXRATE 44100 -#define SOUND_FRAGMENTS 12 - -static unsigned int sound_sampsperfield = 0; -static int sound_dev = -1; -/* static int sound_dump = 0; */ -static uint16 soundbuf[2][SOUND_MAXRATE/50]; /* pal is lowest framerate */ static int sound_active = 0; -static int sound_format; -static int sound_stereo; -static int sound_speed; -static int sound_frag; -static int sound_fragsize; -static unsigned int sound_threshold; /* bytes in buffer we're trying for */ +static uint8 *sound_logdata; /* log data */ +static unsigned int sound_logdata_size; /* sound_logdata size */ +static unsigned int sound_logdata_p; /* current log data offset */ +static unsigned int sound_fieldhassamples; /* flag if field has samples */ #ifdef JFM - static t_jfm_ctx *sound_ctx; +static t_jfm_ctx *sound_ctx; #endif /*** sound_init - initialise this sub-unit ***/ @@ -71,18 +62,44 @@ int sound_init(void) { int ret; + /* The sound_minfields parameter specifies how many fields worth of sound we + should try and keep around (a display field is 1/50th or 1/60th of a + second) - generator works by trying to keep sound_minfields number of + fields worth of sound around, and drops plotting frames to keep up on slow + machines */ + + sound_sampsperfield = sound_speed / vdp_framerate; + sound_threshold = sound_sampsperfield * sound_minfields; + ret = sound_start(); if (ret) return ret; #ifdef JFM - if ((sound_ctx = jfm_init(0, 2612, vdp_clock/7, sound_speed, - NULL, NULL)) == NULL) { + if ((sound_ctx = jfm_init(0, 2612, vdp_clock / 7, sound_speed, + NULL, NULL)) == NULL) { +#else + if (YM2612Init(1, vdp_clock / 7, sound_speed, NULL, NULL)) { +#endif + LOG_VERBOSE(("YM2612 failed init")); + sound_stop(); + return 1; + } + if (SN76496Init(0, vdp_clock / 15, 0, sound_speed)) { + LOG_VERBOSE(("SN76496 failed init")); + sound_stop(); +#ifdef JFM + jfm_final(sound_ctx); #else - if (YM2612Init(1, vdp_clock/7, sound_speed, NULL, NULL)) { + YM2612Shutdown(); #endif - close(sound_dev); return 1; } + if (sound_logdata) + free(sound_logdata); + sound_logdata_size = 8192; + sound_logdata = malloc(sound_logdata_size); + if (!sound_logdata) + ui_err("out of memory"); LOG_VERBOSE(("YM2612 Initialised @ sample rate %d", sound_speed)); return 0; } @@ -99,96 +116,22 @@ void sound_final(void) #endif } -/*** sound_start - start sound hardware ***/ +/*** sound_start - start sound ***/ int sound_start(void) { - audio_buf_info sound_info; - - /* The threshold parameter specifies how many fields worth of sound we - should try and keep around (a display field is 1/50th or 1/60th of a - second) - generator works by trying to keep threshold number of fields - worth of sound around, and drops plotting frames to keep up on slow - machines */ - if (sound_active) sound_stop(); - LOG_VERBOSE(("Initialising sound...")); - sound_sampsperfield = SOUND_SAMPLERATE / vdp_framerate; - - /* sound_dump = open("/tmp/sound_dump", O_CREAT|O_WRONLY|O_TRUNC); */ - - if ((sound_dev = open(SOUND_DEVICE, O_WRONLY, 0)) == -1) { - LOG_CRITICAL(("open " SOUND_DEVICE " failed: %s", strerror(errno))); - return 1; - } - sound_frag = (sound_maxfields<<16 | - (int)(ceil(log10(sound_sampsperfield*4)/log10(2)))); - if (ioctl(sound_dev, SNDCTL_DSP_SETFRAGMENT, &sound_frag) == -1) { - LOG_CRITICAL(("Error setting fragment size: %s", strerror(errno))); - close(sound_dev); - return 1; - } - sound_format = AFMT_S16_LE; - if (ioctl(sound_dev, SNDCTL_DSP_SETFMT, &sound_format) == -1) { - LOG_CRITICAL(("Error setting sound device format: %s", strerror(errno))); - close(sound_dev); - return 1; - } - if (sound_format != AFMT_S16_LE && sound_format != AFMT_S16_BE) { - LOG_CRITICAL(("Sound device format not supported (must be 16 bit)")); - close(sound_dev); - return 1; - } - sound_stereo = 1; - if (ioctl(sound_dev, SNDCTL_DSP_STEREO, &sound_stereo) == -1) { - LOG_CRITICAL(("Error setting stereo: %s", strerror(errno))); - close(sound_dev); - return 1; - } - if (sound_stereo != 1) { - LOG_CRITICAL(("Sound device does not support stereo")); - close(sound_dev); - return 1; - } - sound_speed = SOUND_SAMPLERATE; - if (ioctl(sound_dev, SNDCTL_DSP_SPEED, &sound_speed) == -1) { - LOG_CRITICAL(("Error setting sound speed: %s", strerror(errno))); - close(sound_dev); - return 1; - } - if (sound_speed != SOUND_SAMPLERATE) { - if (abs(sound_speed-SOUND_SAMPLERATE) > 50) { - LOG_NORMAL(("Warning: Sample rate not exactly 22050")); - } else { - LOG_CRITICAL(("Sound device does not support sample rate %d " - "(returned %d)", SOUND_SAMPLERATE, sound_speed)); - close(sound_dev); - return 1; - } - } - if (ioctl(sound_dev, SNDCTL_DSP_GETOSPACE, &sound_info) == -1) { - LOG_CRITICAL(("Error getting output space info", strerror(errno))); - close(sound_dev); - return 1; - } - LOG_VERBOSE(("Total allocated fragments = %d (requested %d)", - sound_info.fragstotal, sound_maxfields)); - LOG_VERBOSE(("Fragment size = %d (requested %d)", sound_info.fragsize, - sound_sampsperfield*4)); - LOG_VERBOSE(("Bytes free in buffer = %d", sound_info.bytes)); - sound_threshold = sound_sampsperfield*4*sound_minfields; - LOG_VERBOSE(("Theshold = %d (%d fields of sound === %dms latency)", - sound_threshold, sound_minfields, - (int)(1000*(float)sound_minfields/(float)vdp_framerate))); - if ((signed)sound_threshold >= sound_info.bytes) { - LOG_CRITICAL(("Threshold exceeds bytes free in buffer")); - close(sound_dev); - return 1; + LOG_VERBOSE(("Starting sound...")); + if (soundp_start() == -1) { + LOG_VERBOSE(("Failed to start sound hardware")); + return -1; } sound_active = 1; + LOG_VERBOSE(("Started sound.")); return 0; } + /*** sound_stop - stop sound ***/ void sound_stop(void) @@ -196,13 +139,11 @@ void sound_stop(void) if (!sound_active) return; LOG_VERBOSE(("Stopping sound...")); - if (sound_dev != -1) - close(sound_dev); - sound_dev = -1; + soundp_stop(); sound_active = 0; LOG_VERBOSE(("Stopped sound.")); } - + /*** sound_reset - reset sound sub-unit ***/ int sound_reset(void) @@ -211,98 +152,95 @@ int sound_reset(void) return sound_init(); } -/*** sound_genreset - reset genesis sound ***/ +/*** sound_startfield - start of frame ***/ -void sound_genreset(void) +void sound_startfield(void) { -#ifdef JFM - jfm_reset(sound_ctx); -#else - YM2612ResetChip(0); -#endif + sound_logdata_p = 0; + if (gen_musiclog == musiclog_gnm) { + sound_writetolog(0); + sound_writetolog((vdp_totlines >> 8) & 0xff); + sound_writetolog(vdp_totlines & 0xff); + sound_fieldhassamples = 0; + } } +/*** sound_endfield - end frame and output sound ***/ -/*** sound_process - process sound ***/ - -void sound_process(void) +void sound_endfield(void) { - static sint16 *tbuf[2]; - int s1 = (sound_sampsperfield*(vdp_line))/vdp_totlines; - int s2 = (sound_sampsperfield*(vdp_line+1))/vdp_totlines; + int pending; + uint8 *p, *o; - tbuf[0] = soundbuf[0] + s1; - tbuf[1] = soundbuf[1] + s1; - - /* printf("YM2612: %d to %d\n", s1, s2); */ + if (gen_musiclog) { + if (gen_musiclog == musiclog_gym) { + /* GYM format ends a field with a 0 byte */ + sound_writetolog(0); + } else { + /* GNM format requires sifting through the data */ + if (!sound_fieldhassamples) { + /* we're only removing data, so we're going to write to the buffer + we're reading from */ + o = sound_logdata + 3; + for (p = sound_logdata + 3; + p < (sound_logdata + sound_logdata_p); p++) { + if ((*p & 0xF0) != 0x00 || *p == 4) + ui_err("assertion of no samples failed"); + switch (*p) { + case 0: + ui_err("field marker in middle of field data"); + case 1: + case 2: + /* FM data, copy 2 and two bytes to output */ + *o++ = *p++; + *o++ = *p++; + *o++ = *p; + break; + case 3: + /* PSG data, copy 3 and one byte to output */ + *o++ = *p++; + *o++ = *p; + break; + case 5: + /* these are the bytes we're trying to strip */ + /* do nothing */ + break; + default: + ui_err("invalid data in sound log buffer"); + } + } + /* update new end of buffer */ + sound_logdata_p = o - sound_logdata; + sound_logdata[1] = 0; /* high byte number of samples */ + sound_logdata[2] = 0; /* low byte number of samples */ + } + } + /* write data */ + ui_musiclog(sound_logdata, sound_logdata_p); + /* sound_startfield resets everything */ + } - if (s2 > s1) -#ifdef JFM - jfm_update(sound_ctx, (void **)tbuf, s2 - s1); -#else - YM2612UpdateOne(0, (void **)tbuf, s2 -s1); -#endif -} + if (!sound_on) { + /* sound is turned off - let generator continue */ + sound_feedback = 0; + return; + } -/*** sound_endfield - end frame and output sound ***/ + /* work out feedback from sound system */ -void sound_endfield(void) -{ - unsigned int i; - uint16 buffer[(SOUND_MAXRATE/50)*2]; /* pal is slowest framerate */ -#ifdef WORDS_BIGENDIAN - int endian = 1; -#else - int endian = 0; -#endif - audio_buf_info sound_info; - unsigned int pending; - struct timeval tv; - - if (ioctl(sound_dev, SNDCTL_DSP_GETOSPACE, &sound_info) == -1) - ui_err("Error getting output space info", strerror(errno)); - pending = (sound_info.fragstotal*sound_info.fragsize)-sound_info.bytes; - if (pending < sound_threshold) + if ((pending = soundp_samplesbuffered()) == -1) + ui_err("Failed to read pending bytes in output sound buffer"); + if ((unsigned int)pending < sound_threshold) sound_feedback = -1; else sound_feedback = 0; + if (sound_debug) { - LOG_VERBOSE(("End of field - sound card says output buffer left = %d bytes", - sound_info.bytes)); - LOG_VERBOSE(("Sound card says %d frags @ %d bytes each = %d bytes", - sound_info.fragstotal, sound_info.fragsize, - sound_info.fragstotal*sound_info.fragsize)); - LOG_VERBOSE(("Therefore, pending = %d, compare with threshold %d = %d", - pending, sound_threshold, sound_feedback)); - } - if (sound_format == AFMT_S16_LE && endian == 0) { - /* device matches endianness of host */ - for (i = 0; i < sound_sampsperfield; i++) { - buffer[i*2] = soundbuf[0][i]; - buffer[i*2+1] = soundbuf[1][i]; - } - } else { - /* device is odd and is different to host endianness */ - for (i = 0; i < sound_sampsperfield; i++) { - buffer[i*2] = (soundbuf[0][i] >> 8) | ((soundbuf[0][i] << 8) & 0xff00); - buffer[i*2+1] = (soundbuf[1][i] >> 8) | ((soundbuf[1][i] << 8) & 0xff00); - } - } - if (sound_debug) { - gettimeofday(&tv, NULL); - LOG_REQUEST(("%ld:%ld - before write", tv.tv_sec, tv.tv_usec)); - } - if (write(sound_dev, buffer, sound_sampsperfield*4) == -1) { - if (errno != EINTR) - ui_err("Error writing to sound device: %s", strerror(errno)); - } - /* - if (write(sound_dump, buffer, sound_sampsperfield*4) == -1) - ui_err("Error writing to dump file: %s", strerror(errno)); - */ - if (sound_debug) { - gettimeofday(&tv, NULL); - LOG_REQUEST(("%ld:%ld - after write", tv.tv_sec, tv.tv_usec)); + LOG_VERBOSE(("End of field - sound system says %d bytes buffered", + pending)); + LOG_VERBOSE(("Threshold %d, therefore feedback = %d ", sound_threshold, + sound_feedback)); } + soundp_output(sound_soundbuf[0], sound_soundbuf[1], sound_sampsperfield); } /*** sound_ym2612fetch - fetch byte from ym2612 chip ***/ @@ -325,16 +263,31 @@ void sound_ym2612store(uint8 addr, uint8 sound_address1 = data; break; case 1: - if (sound_address1 == 0x28 && (data & 3) != 3) - sound_keys[data & 7] = data >> 4; if (sound_address1 == 0x2a) { + /* sample data */ sound_keys[7] = 0; + sound_logsample = data; + } else { + if (gen_musiclog != musiclog_off) { + /* GYM and GNM */ + sound_writetolog(1); + sound_writetolog(sound_address1); + sound_writetolog(data); + } } + if (sound_address1 == 0x28 && (data & 3) != 3) + sound_keys[data & 7] = data >> 4; if (sound_address1 == 0x2b) sound_keys[7] = data & 0x80 ? 0xf : 0; sound_regs1[sound_address1] = data; break; case 2: + if (gen_musiclog != musiclog_off) { + /* GYM and GNM */ + sound_writetolog(2); + sound_writetolog(sound_address2); + sound_writetolog(data); + } sound_address2 = data; break; case 3: @@ -348,3 +301,126 @@ void sound_ym2612store(uint8 addr, uint8 #endif } +/*** sound_sn76496store - store a byte to the sn76496 chip ***/ + +void sound_sn76496store(uint8 data) +{ + if (gen_musiclog != musiclog_off) { + /* GYM and GNM */ + sound_writetolog(3); + sound_writetolog(data); + } + SN76496Write(0, data); +} + +/*** sound_genreset - reset genesis sound ***/ + +void sound_genreset(void) +{ +#ifdef JFM + jfm_reset(sound_ctx); +#else + YM2612ResetChip(0); +#endif +} + +/*** sound_line - called at end of line ***/ + +void sound_line(void) +{ + if (gen_musiclog == musiclog_gnm) { + /* GNM log */ + if (sound_logsample == -1) { + sound_writetolog(5); + } else { + if ((sound_logsample & 0xF0) == 0) { + sound_writetolog(4); + } + sound_writetolog(sound_logsample); + sound_logsample = -1; + /* mark the fact that we have encountered a sample this field */ + sound_fieldhassamples = 1; + } + } + sound_process(); +} + +/*** sound_process - process sound ***/ + +static void sound_process(void) +{ + static sint16 *tbuf[2]; + int s1 = (sound_sampsperfield * (vdp_line)) / vdp_totlines; + int s2 = (sound_sampsperfield * (vdp_line + 1)) / vdp_totlines; + /* pal is lowest framerate */ + uint16 sn76496buf[SOUND_MAXRATE / 50]; /* far too much but who cares */ + unsigned int samples = s2 - s1; + unsigned int i; + + tbuf[0] = sound_soundbuf[0] + s1; + tbuf[1] = sound_soundbuf[1] + s1; + + if (s2 > s1) { + if (sound_fm) +#ifdef JFM + jfm_update(sound_ctx, (void **)tbuf, samples1); +#else + YM2612UpdateOne(0, tbuf, samples); +#endif + if (sound_psg) + SN76496Update(0, sn76496buf, samples); + + /* SN76496 outputs sound in range -0x4000 to 0x3fff + YM2612 ouputs sound in range -0x8000 to 0x7fff - therefore + we take 3/4 of the YM2612 and add half the SN76496 */ + + if (sound_fm && sound_psg) { + for (i = 0; i < samples; i++) { + sint16 snsample = sn76496buf[i] - 0x4000; + sint32 l = (tbuf[0][i] * 3) >> 2; /* left channel */ + sint32 r = (tbuf[1][i] * 3) >> 2; /* right channel */ + l += snsample >> 1; + r += snsample >> 1; + tbuf[0][i] = l; + tbuf[1][i] = r; + } + } else if (!sound_fm && !sound_psg) { + /* no sound */ + memset(tbuf[0], 0, 2 * samples); + memset(tbuf[1], 0, 2 * samples); + } else if (sound_fm) { + /* fm only */ + for (i = 0; i < samples; i++) { + sint32 l = (tbuf[0][i] * 3) >> 2; /* left channel */ + sint32 r = (tbuf[1][i] * 3) >> 2; /* right channel */ + tbuf[0][i] = l; + tbuf[1][i] = r; + } + } else { + /* psg only */ + for (i = 0; i < samples; i++) { + sint16 snsample = sn76496buf[i] - 0x4000; + tbuf[0][i] = snsample >> 1; + tbuf[1][i] = snsample >> 1; + } + } + } +} + +/*** sound_writetolog - write to music log buffer ***/ + +static void sound_writetolog(unsigned char c) +{ + /* write the byte to the self-expanding memory log - when we have the whole + field's worth of data we then sift through the data (see the + documentation on the GNM log format) and then pass it to ui_writelog */ + + sound_logdata[sound_logdata_p++] = c; + if (sound_logdata_p >= sound_logdata_size) { + LOG_VERBOSE(("sound log buffer limit increased")); + sound_logdata_size+= 8192; + sound_logdata = realloc(sound_logdata, sound_logdata_size); + if (!sound_logdata) + ui_err("out of memory"); + } +}