/* * Apple // emulator for *ix * * This software package is subject to the GNU General Public License * version 3 or later (your choice) as published by the Free Software * Foundation. * * Copyright 2013-2015 Aaron Culliney * */ /* sound.c: Sound support Copyright (c) 2000-2007 Russell Marks, Matan Ziv-Av, Philip Kendall, Fredrick Meunier 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Author contact information: E-mail: philip-fuse@shadowmagic.org.uk */ // [AppleWin-TC] From FUSE's sound.c module #if 0 // !defined(APPLE2IX) #include "StdAfx.h" #include #include #include #include "AY8910.h" #include "Applewin.h" // For g_fh #include "Mockingboard.h" // For g_uTimer1IrqCount #include "YamlHelper.h" #else # include "common.h" # include "audio/AY8910.h" # include "audio/mockingboard.h" // For g_uTimer1IrqCount # if TESTING # include "greatest.h" # endif #endif /* The AY white noise RNG algorithm is based on info from MAME's ay8910.c - * MAME's licence explicitly permits free use of info (even encourages it). */ /* NB: I know some of this stuff looks fairly CPU-hogging. * For example, the AY code tracks changes with sub-frame timing * in a rather hairy way, and there's subsampling here and there. * But if you measure the CPU use, it doesn't actually seem * very high at all. And I speak as a Cyrix owner. :-) */ libspectrum_signed_word** g_ppSoundBuffers; // Used to pass param to sound_ay_overlay() /* configuration */ //int sound_enabled = 0; /* Are we currently using the sound card */ //int sound_enabled_ever = 0; /* if it's *ever* been in use; see // sound_ay_write() and sound_ay_reset() */ //int sound_stereo = 0; /* true for stereo *output sample* (only) */ //int sound_stereo_ay_abc = 0; /* (AY stereo) true for ABC stereo, else ACB */ //int sound_stereo_ay_narrow = 0; /* (AY stereo) true for narrow AY st. sep. */ //int sound_stereo_ay = 0; /* local copy of settings_current.stereo_ay */ //int sound_stereo_beeper = 0; /* and settings_current.stereo_beeper */ /* assume all three tone channels together match the beeper volume (ish). * Must be <=127 for all channels; 50+2+(24*3) = 124. * (Now scaled up for 16-bit.) */ //#define AMPL_BEEPER ( 50 * 256) //#define AMPL_TAPE ( 2 * 256 ) //#define AMPL_AY_TONE ( 24 * 256 ) /* three of these */ #define AMPL_AY_TONE ( 42 * 256 ) // 42*3 = 126 /* max. number of sub-frame AY port writes allowed; * given the number of port writes theoretically possible in a * 50th I think this should be plenty. */ //#define AY_CHANGE_MAX 8000 // [TC] Moved into AY8910.h ///* frequency to generate sound at for hifi sound */ //#define HIFI_FREQ 88200 #ifdef HAVE_SAMPLERATE static SRC_STATE *src_state; #endif /* #ifdef HAVE_SAMPLERATE */ int sound_generator_framesiz; int sound_framesiz; static int sound_generator_freq; static int sound_channels; static unsigned int ay_tone_levels[16]; //static libspectrum_signed_word *sound_buf, *tape_buf; //static float *convert_input_buffer, *convert_output_buffer; #if 0 /* beeper stuff */ static int sound_oldpos[2], sound_fillpos[2]; static int sound_oldval[2], sound_oldval_orig[2]; #endif #if 0 #define STEREO_BUF_SIZE 4096 static int pstereobuf[ STEREO_BUF_SIZE ]; static int pstereobufsiz, pstereopos; static int psgap = 250; static int rstereobuf_l[ STEREO_BUF_SIZE ], rstereobuf_r[ STEREO_BUF_SIZE ]; static int rstereopos, rchan1pos, rchan2pos, rchan3pos; #endif // Statics: double m_fCurrentCLK_AY8910 = 0.0; #ifndef APPLE2IX static void sound_end(CAY8910 *_this); #endif static void sound_ay_overlay(CAY8910 *_this); void CAY8910_init(CAY8910 *_this) { // Init the statics that were in sound_ay_overlay() _this->rng = 1; _this->noise_toggle = 0; _this->env_first = 1; _this->env_rev = 0; _this->env_counter = 15; //m_fCurrentCLK_AY8910 = cycles_persec_target; -- believe this is handled by an initial call to SetCLK() }; void sound_ay_init( CAY8910 *_this ) { /* AY output doesn't match the claimed levels; these levels are based * on the measurements posted to comp.sys.sinclair in Dec 2001 by * Matthew Westcott, adjusted as I described in a followup to his post, * then scaled to 0..0xffff. */ static const int levels[16] = { 0x0000, 0x0385, 0x053D, 0x0770, 0x0AD7, 0x0FD5, 0x15B0, 0x230C, 0x2B4C, 0x43C1, 0x5A4B, 0x732F, 0x9204, 0xAFF1, 0xD921, 0xFFFF }; int f; /* scale the values down to fit */ for( f = 0; f < 16; f++ ) ay_tone_levels[f] = ( levels[f] * AMPL_AY_TONE + 0x8000 ) / 0xffff; _this->ay_noise_tick = _this->ay_noise_period = 0; _this->ay_env_internal_tick = _this->ay_env_tick = _this->ay_env_period = 0; _this->ay_tone_subcycles = _this->ay_env_subcycles = 0; for( f = 0; f < 3; f++ ) _this->ay_tone_tick[f] = _this->ay_tone_high[f] = 0, _this->ay_tone_period[f] = 1; _this->ay_change_count = 0; } #ifdef APPLE2IX #define HZ_COMMON_DENOMINATOR 25 // FIXME TODO : why different than upstream? #endif static void sound_init( CAY8910 *_this, const char *device, unsigned long nSampleRate ) { // static int first_init = 1; // int f, ret; float hz; #ifdef HAVE_SAMPLERATE int error; #endif /* #ifdef HAVE_SAMPLERATE */ /* if we don't have any sound I/O code compiled in, don't do sound */ #ifdef NO_SOUND return; #endif #if 0 if( !( !sound_enabled && settings_current.sound && settings_current.emulation_speed == 100 ) ) return; sound_stereo_ay = settings_current.stereo_ay; sound_stereo_beeper = settings_current.stereo_beeper; /* only try for stereo if we need it */ if( sound_stereo_ay || sound_stereo_beeper ) sound_stereo = 1; ret = sound_lowlevel_init( device, &settings_current.sound_freq, &sound_stereo ); if( ret ) return; #endif #if 0 /* important to override these settings if not using stereo * (it would probably be confusing to mess with the stereo * settings in settings_current though, which is why we make copies * rather than using the real ones). */ if( !sound_stereo ) { sound_stereo_ay = 0; sound_stereo_beeper = 0; } sound_enabled = sound_enabled_ever = 1; sound_channels = ( sound_stereo ? 2 : 1 ); #endif sound_channels = 3; // 3 mono channels: ABC // hz = ( float ) machine_current->timings.processor_speed / // machine_current->timings.tstates_per_frame; hz = HZ_COMMON_DENOMINATOR; // sound_generator_freq = // settings_current.sound_hifi ? HIFI_FREQ : settings_current.sound_freq; sound_generator_freq = nSampleRate; sound_generator_framesiz = sound_generator_freq / (int)hz; #if 0 if( ( sound_buf = (libspectrum_signed_word*) malloc( sizeof( libspectrum_signed_word ) * sound_generator_framesiz * sound_channels ) ) == NULL || ( tape_buf = malloc( sizeof( libspectrum_signed_word ) * sound_generator_framesiz ) ) == NULL ) { if( sound_buf ) { free( sound_buf ); sound_buf = NULL; } sound_end(); return; } #endif // sound_framesiz = ( float ) settings_current.sound_freq / hz; sound_framesiz = sound_generator_freq / (int)hz; #ifdef HAVE_SAMPLERATE if( settings_current.sound_hifi ) { if( ( convert_input_buffer = malloc( sizeof( float ) * sound_generator_framesiz * sound_channels ) ) == NULL || ( convert_output_buffer = malloc( sizeof( float ) * sound_framesiz * sound_channels ) ) == NULL ) { if( convert_input_buffer ) { free( convert_input_buffer ); convert_input_buffer = NULL; } sound_end(); return; } } src_state = src_new( SRC_SINC_MEDIUM_QUALITY, sound_channels, &error ); if( error ) { ui_error( UI_ERROR_ERROR, "error initialising sample rate converter %s", src_strerror( error ) ); sound_end(); return; } #endif /* #ifdef HAVE_SAMPLERATE */ /* if we're resuming, we need to be careful about what * gets reset. The minimum we can do is the beeper * buffer positions, so that's here. */ #if 0 sound_oldpos[0] = sound_oldpos[1] = -1; sound_fillpos[0] = sound_fillpos[1] = 0; #endif /* this stuff should only happen on the initial call. * (We currently assume the new sample rate will be the * same as the previous one, hence no need to recalculate * things dependent on that.) */ #if 0 if( first_init ) { first_init = 0; for( f = 0; f < 2; f++ ) sound_oldval[f] = sound_oldval_orig[f] = 0; } #endif #if 0 if( sound_stereo_beeper ) { for( f = 0; f < STEREO_BUF_SIZE; f++ ) pstereobuf[f] = 0; pstereopos = 0; pstereobufsiz = ( sound_generator_freq * psgap ) / 22000; } if( sound_stereo_ay ) { int pos = ( sound_stereo_ay_narrow ? 3 : 6 ) * sound_generator_freq / 8000; for( f = 0; f < STEREO_BUF_SIZE; f++ ) rstereobuf_l[f] = rstereobuf_r[f] = 0; rstereopos = 0; /* the actual ACB/ABC bit :-) */ rchan1pos = -pos; if( sound_stereo_ay_abc ) rchan2pos = 0, rchan3pos = pos; else rchan2pos = pos, rchan3pos = 0; } #endif #if 0 ay_tick_incr = ( int ) ( 65536. * libspectrum_timings_ay_speed( machine_current-> machine ) / sound_generator_freq ); #endif _this->ay_tick_incr = ( int ) ( 65536. * m_fCurrentCLK_AY8910 / sound_generator_freq ); // [TC] } #if 0 void sound_pause( void ) { if( sound_enabled ) sound_end(); } void sound_unpause( void ) { /* No sound if fastloading in progress */ if( settings_current.fastload && tape_is_playing() ) return; sound_init( settings_current.sound_device ); } #endif static void sound_end( CAY8910 *_this ) { #if 0 if( sound_enabled ) { if( sound_buf ) { free( sound_buf ); sound_buf = NULL; free( tape_buf ); tape_buf = NULL; } if( convert_input_buffer ) { free( convert_input_buffer ); convert_input_buffer = NULL; } if( convert_output_buffer ) { free( convert_output_buffer ); convert_output_buffer = NULL; } #ifdef HAVE_SAMPLERATE if( src_state ) src_state = src_delete( src_state ); #endif /* #ifdef HAVE_SAMPLERATE */ sound_lowlevel_end(); sound_enabled = 0; } #endif #if 0 if( sound_buf ) { free( sound_buf ); sound_buf = NULL; } #endif } #if 0 /* write sample to buffer as pseudo-stereo */ static void sound_write_buf_pstereo( libspectrum_signed_word * out, int c ) { int bl = ( c - pstereobuf[ pstereopos ] ) / 2; int br = ( c + pstereobuf[ pstereopos ] ) / 2; if( bl < -AMPL_BEEPER ) bl = -AMPL_BEEPER; if( br < -AMPL_BEEPER ) br = -AMPL_BEEPER; if( bl > AMPL_BEEPER ) bl = AMPL_BEEPER; if( br > AMPL_BEEPER ) br = AMPL_BEEPER; *out = bl; out[1] = br; pstereobuf[ pstereopos ] = c; pstereopos++; if( pstereopos >= pstereobufsiz ) pstereopos = 0; } #endif /* not great having this as a macro to inline it, but it's only * a fairly short routine, and it saves messing about. * (XXX ummm, possibly not so true any more :-)) */ #define AY_GET_SUBVAL( chan ) \ ( level * 2 * _this->ay_tone_tick[ chan ] / tone_count ) #define AY_DO_TONE( var, chan ) \ ( var ) = 0; \ is_low = 0; \ if( level ) { \ if( _this->ay_tone_high[ chan ] ) \ ( var ) = ( level ); \ else { \ ( var ) = -( level ); \ is_low = 1; \ } \ } \ \ _this->ay_tone_tick[ chan ] += tone_count; \ count = 0; \ while( _this->ay_tone_tick[ chan ] >= _this->ay_tone_period[ chan ] ) { \ count++; \ _this->ay_tone_tick[ chan ] -= _this->ay_tone_period[ chan ]; \ _this->ay_tone_high[ chan ] = !_this->ay_tone_high[ chan ]; \ \ /* has to be here, unfortunately... */ \ if( count == 1 && level && _this->ay_tone_tick[ chan ] < tone_count ) { \ if( is_low ) \ ( var ) += AY_GET_SUBVAL( chan ); \ else \ ( var ) -= AY_GET_SUBVAL( chan ); \ } \ } \ \ /* if it's changed more than once during the sample, we can't */ \ /* represent it faithfully. So, just hope it's a sample. */ \ /* (That said, this should also help avoid aliasing noise.) */ \ if( count > 1 ) \ ( var ) = -( level ) #if 0 /* add val, correctly delayed on either left or right buffer, * to add the AY stereo positioning. This doesn't actually put * anything directly in sound_buf, though. */ #define GEN_STEREO( pos, val ) \ if( ( pos ) < 0 ) { \ rstereobuf_l[ rstereopos ] += ( val ); \ rstereobuf_r[ ( rstereopos - pos ) % STEREO_BUF_SIZE ] += ( val ); \ } else { \ rstereobuf_l[ ( rstereopos + pos ) % STEREO_BUF_SIZE ] += ( val ); \ rstereobuf_r[ rstereopos ] += ( val ); \ } #endif /* bitmasks for envelope */ #define AY_ENV_CONT 8 #define AY_ENV_ATTACK 4 #define AY_ENV_ALT 2 #define AY_ENV_HOLD 1 #ifdef APPLE2IX // defined above #else #define HZ_COMMON_DENOMINATOR 50 #endif static void sound_ay_overlay(CAY8910 *_this) { int tone_level[3]; int mixer, envshape; int f, g, level, count; // libspectrum_signed_word *ptr; struct ay_change_tag *change_ptr = _this->ay_change; int changes_left = _this->ay_change_count; int reg, r; int is_low; int chan1, chan2, chan3; unsigned int tone_count, noise_count; libspectrum_dword sfreq, cpufreq; ///* If no AY chip, don't produce any AY sound (!) */ // if( !machine_current->capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_AY ) // return; /* convert change times to sample offsets, use common denominator of 50 to avoid overflowing a dword */ sfreq = sound_generator_freq / HZ_COMMON_DENOMINATOR; // cpufreq = machine_current->timings.processor_speed / HZ_COMMON_DENOMINATOR; cpufreq = (libspectrum_dword) (m_fCurrentCLK_AY8910 / HZ_COMMON_DENOMINATOR); // [TC] for( f = 0; f < _this->ay_change_count; f++ ) _this->ay_change[f].ofs = (uint16_t) (( _this->ay_change[f].tstates * sfreq ) / cpufreq); // [TC] Added cast libspectrum_signed_word* pBuf1 = g_ppSoundBuffers[0]; libspectrum_signed_word* pBuf2 = g_ppSoundBuffers[1]; libspectrum_signed_word* pBuf3 = g_ppSoundBuffers[2]; // for( f = 0, ptr = sound_buf; f < sound_generator_framesiz; f++ ) { for( f = 0; f < sound_generator_framesiz; f++ ) { /* update ay registers. All this sub-frame change stuff * is pretty hairy, but how else would you handle the * samples in Robocop? :-) It also clears up some other * glitches. */ while( changes_left && f >= change_ptr->ofs ) { _this->sound_ay_registers[ reg = change_ptr->reg ] = change_ptr->val; change_ptr++; changes_left--; /* fix things as needed for some register changes */ switch ( reg ) { case 0: case 1: case 2: case 3: case 4: case 5: r = reg >> 1; /* a zero-len period is the same as 1 */ _this->ay_tone_period[r] = ( _this->sound_ay_registers[ reg & ~1 ] | ( _this->sound_ay_registers[ reg | 1 ] & 15 ) << 8 ); if( !_this->ay_tone_period[r] ) _this->ay_tone_period[r]++; /* important to get this right, otherwise e.g. Ghouls 'n' Ghosts * has really scratchy, horrible-sounding vibrato. */ if( _this->ay_tone_tick[r] >= _this->ay_tone_period[r] * 2 ) _this->ay_tone_tick[r] %= _this->ay_tone_period[r] * 2; break; case 6: _this->ay_noise_tick = 0; _this->ay_noise_period = ( _this->sound_ay_registers[ reg ] & 31 ); break; case 11: case 12: /* this one *isn't* fixed-point */ _this->ay_env_period = _this->sound_ay_registers[11] | ( _this->sound_ay_registers[12] << 8 ); break; case 13: _this->ay_env_internal_tick = _this->ay_env_tick = _this->ay_env_subcycles = 0; _this->env_first = 1; _this->env_rev = 0; _this->env_counter = ( _this->sound_ay_registers[13] & AY_ENV_ATTACK ) ? 0 : 15; break; } } /* the tone level if no enveloping is being used */ for( g = 0; g < 3; g++ ) tone_level[g] = ay_tone_levels[ _this->sound_ay_registers[ 8 + g ] & 15 ]; /* envelope */ envshape = _this->sound_ay_registers[13]; level = ay_tone_levels[ _this->env_counter ]; for( g = 0; g < 3; g++ ) if( _this->sound_ay_registers[ 8 + g ] & 16 ) tone_level[g] = level; /* envelope output counter gets incr'd every 16 AY cycles. * Has to be a while, as this is sub-output-sample res. */ _this->ay_env_subcycles += _this->ay_tick_incr; noise_count = 0; while( _this->ay_env_subcycles >= ( 16 << 16 ) ) { _this->ay_env_subcycles -= ( 16 << 16 ); noise_count++; _this->ay_env_tick++; while( _this->ay_env_tick >= _this->ay_env_period ) { _this->ay_env_tick -= _this->ay_env_period; /* do a 1/16th-of-period incr/decr if needed */ if( _this->env_first || ( ( envshape & AY_ENV_CONT ) && !( envshape & AY_ENV_HOLD ) ) ) { if( _this->env_rev ) _this->env_counter -= ( envshape & AY_ENV_ATTACK ) ? 1 : -1; else _this->env_counter += ( envshape & AY_ENV_ATTACK ) ? 1 : -1; if( _this->env_counter < 0 ) _this->env_counter = 0; if( _this->env_counter > 15 ) _this->env_counter = 15; } _this->ay_env_internal_tick++; while( _this->ay_env_internal_tick >= 16 ) { _this->ay_env_internal_tick -= 16; /* end of cycle */ if( !( envshape & AY_ENV_CONT ) ) _this->env_counter = 0; else { if( envshape & AY_ENV_HOLD ) { if( _this->env_first && ( envshape & AY_ENV_ALT ) ) _this->env_counter = ( _this->env_counter ? 0 : 15 ); } else { /* non-hold */ if( envshape & AY_ENV_ALT ) _this->env_rev = !_this->env_rev; else _this->env_counter = ( envshape & AY_ENV_ATTACK ) ? 0 : 15; } } _this->env_first = 0; } /* don't keep trying if period is zero */ if( !_this->ay_env_period ) break; } } /* generate tone+noise... or neither. * (if no tone/noise is selected, the chip just shoves the * level out unmodified. This is used by some sample-playing * stuff.) */ chan1 = tone_level[0]; chan2 = tone_level[1]; chan3 = tone_level[2]; mixer = _this->sound_ay_registers[7]; _this->ay_tone_subcycles += _this->ay_tick_incr; tone_count = _this->ay_tone_subcycles >> ( 3 + 16 ); _this->ay_tone_subcycles &= ( 8 << 16 ) - 1; if( ( mixer & 1 ) == 0 ) { level = chan1; AY_DO_TONE( chan1, 0 ); } if( ( mixer & 0x08 ) == 0 && _this->noise_toggle ) chan1 = 0; if( ( mixer & 2 ) == 0 ) { level = chan2; AY_DO_TONE( chan2, 1 ); } if( ( mixer & 0x10 ) == 0 && _this->noise_toggle ) chan2 = 0; if( ( mixer & 4 ) == 0 ) { level = chan3; AY_DO_TONE( chan3, 2 ); } if( ( mixer & 0x20 ) == 0 && _this->noise_toggle ) chan3 = 0; /* write the sample(s) */ *pBuf1++ = chan1; // [TC] *pBuf2++ = chan2; // [TC] *pBuf3++ = chan3; // [TC] #if 0 if( !sound_stereo ) { /* mono */ ( *ptr++ ) += chan1 + chan2 + chan3; } else { if( !sound_stereo_ay ) { /* stereo output, but mono AY sound; still, * incr separately in case of beeper pseudostereo. */ ( *ptr++ ) += chan1 + chan2 + chan3; ( *ptr++ ) += chan1 + chan2 + chan3; } else { /* stereo with ACB/ABC AY positioning. * Here we use real stereo positions for the channels. * Just because, y'know, it's cool and stuff. No, really. :-) * This is a little tricky, as it works by delaying sounds * on the left or right channels to model the delay you get * in the real world when sounds originate at different places. */ GEN_STEREO( rchan1pos, chan1 ); GEN_STEREO( rchan2pos, chan2 ); GEN_STEREO( rchan3pos, chan3 ); ( *ptr++ ) += rstereobuf_l[ rstereopos ]; ( *ptr++ ) += rstereobuf_r[ rstereopos ]; rstereobuf_l[ rstereopos ] = rstereobuf_r[ rstereopos ] = 0; rstereopos++; if( rstereopos >= STEREO_BUF_SIZE ) rstereopos = 0; } } #endif /* update noise RNG/filter */ _this->ay_noise_tick += noise_count; while( _this->ay_noise_tick >= _this->ay_noise_period ) { _this->ay_noise_tick -= _this->ay_noise_period; if( ( _this->rng & 1 ) ^ ( ( _this->rng & 2 ) ? 1 : 0 ) ) _this->noise_toggle = !_this->noise_toggle; /* rng is 17-bit shift reg, bit 0 is output. * input is bit 0 xor bit 2. */ _this->rng |= ( ( _this->rng & 1 ) ^ ( ( _this->rng & 4 ) ? 1 : 0 ) ) ? 0x20000 : 0; _this->rng >>= 1; /* don't keep trying if period is zero */ if( !_this->ay_noise_period ) break; } } } // AppleWin:TC Holding down ScrollLock will result in lots of AY changes /ay_change_count/ // - since sound_ay_overlay() is called to consume them. /* don't make the change immediately; record it for later, * to be made by sound_frame() (via sound_ay_overlay()). */ void sound_ay_write( CAY8910 *_this, int reg, int val, libspectrum_dword now ) { if( _this->ay_change_count < AY_CHANGE_MAX ) { _this->ay_change[ _this->ay_change_count ].tstates = now; _this->ay_change[ _this->ay_change_count ].reg = ( reg & 15 ); _this->ay_change[ _this->ay_change_count ].val = val; _this->ay_change_count++; } } /* no need to call this initially, but should be called * on reset otherwise. */ void sound_ay_reset( CAY8910 *_this ) { int f; CAY8910_init(_this); // AppleWin:TC /* recalculate timings based on new machines ay clock */ sound_ay_init(_this); _this->ay_change_count = 0; for( f = 0; f < 16; f++ ) sound_ay_write( _this, f, 0, 0 ); for( f = 0; f < 3; f++ ) _this->ay_tone_high[f] = 0; _this->ay_tone_subcycles = _this->ay_env_subcycles = 0; } #if 0 /* write stereo or mono beeper sample, and incr ptr */ #define SOUND_WRITE_BUF_BEEPER( ptr, val ) \ do { \ if( sound_stereo_beeper ) { \ sound_write_buf_pstereo( ( ptr ), ( val ) ); \ ( ptr ) += 2; \ } else { \ *( ptr )++ = ( val ); \ if( sound_stereo ) \ *( ptr )++ = ( val ); \ } \ } while(0) /* the tape version works by writing to a separate mono buffer, * which gets added after being generated. */ #define SOUND_WRITE_BUF( is_tape, ptr, val ) \ if( is_tape ) \ *( ptr )++ = ( val ); \ else \ SOUND_WRITE_BUF_BEEPER( ptr, val ) #endif #ifdef HAVE_SAMPLERATE static void sound_resample( void ) { int error; SRC_DATA data; data.data_in = convert_input_buffer; data.input_frames = sound_generator_framesiz; data.data_out = convert_output_buffer; data.output_frames = sound_framesiz; data.src_ratio = ( double ) settings_current.sound_freq / sound_generator_freq; data.end_of_input = 0; src_short_to_float_array( ( const short * ) sound_buf, convert_input_buffer, sound_generator_framesiz * sound_channels ); while( data.input_frames ) { error = src_process( src_state, &data ); if( error ) { ui_error( UI_ERROR_ERROR, "hifi sound downsample error %s", src_strerror( error ) ); sound_end(_this); return; } src_float_to_short_array( convert_output_buffer, ( short * ) sound_buf, data.output_frames_gen * sound_channels ); sound_lowlevel_frame( sound_buf, data.output_frames_gen * sound_channels ); data.data_in += data.input_frames_used * sound_channels; data.input_frames -= data.input_frames_used; } } #endif /* #ifdef HAVE_SAMPLERATE */ void sound_frame( CAY8910 *_this ) { #if 0 libspectrum_signed_word *ptr, *tptr; int f, bchan; int ampl = AMPL_BEEPER; if( !sound_enabled ) return; /* fill in remaining beeper/tape sound */ ptr = sound_buf + ( sound_stereo ? sound_fillpos[0] * 2 : sound_fillpos[0] ); for( bchan = 0; bchan < 2; bchan++ ) { for( f = sound_fillpos[ bchan ]; f < sound_generator_framesiz; f++ ) SOUND_WRITE_BUF( bchan, ptr, sound_oldval[ bchan ] ); ptr = tape_buf + sound_fillpos[1]; ampl = AMPL_TAPE; } /* overlay tape sound */ ptr = sound_buf; tptr = tape_buf; for( f = 0; f < sound_generator_framesiz; f++, tptr++ ) { ( *ptr++ ) += *tptr; if( sound_stereo ) ( *ptr++ ) += *tptr; } #endif /* overlay AY sound */ sound_ay_overlay(_this); #ifdef HAVE_SAMPLERATE /* resample from generated frequency down to output frequency if required */ if( settings_current.sound_hifi ) sound_resample(); else #endif /* #ifdef HAVE_SAMPLERATE */ #if 0 sound_lowlevel_frame( sound_buf, sound_generator_framesiz * sound_channels ); #endif #if 0 sound_oldpos[0] = sound_oldpos[1] = -1; sound_fillpos[0] = sound_fillpos[1] = 0; #endif _this->ay_change_count = 0; } #if 0 /* two beepers are supported - the real beeper (call with is_tape==0) * and a `fake' beeper which lets you hear when a tape is being played. */ void sound_beeper( int is_tape, int on ) { libspectrum_signed_word *ptr; int newpos, subpos; int val, subval; int f; int bchan = ( is_tape ? 1 : 0 ); int ampl = ( is_tape ? AMPL_TAPE : AMPL_BEEPER ); int vol = ampl * 2; if( !sound_enabled ) return; val = ( on ? -ampl : ampl ); if( val == sound_oldval_orig[ bchan ] ) return; /* XXX a lookup table might help here, but would need to regenerate it * whenever cycles_per_frame were changed (i.e. when machine type changed). */ newpos = ( tstates * sound_generator_framesiz ) / machine_current->timings.tstates_per_frame; subpos = ( ( ( libspectrum_signed_qword ) tstates ) * sound_generator_framesiz * vol ) / ( machine_current->timings.tstates_per_frame ) - vol * newpos; /* if we already wrote here, adjust the level. */ if( newpos == sound_oldpos[ bchan ] ) { /* adjust it as if the rest of the sample period were all in * the new state. (Often it will be, but if not, we'll fix * it later by doing this again.) */ if( on ) beeper_last_subpos[ bchan ] += vol - subpos; else beeper_last_subpos[ bchan ] -= vol - subpos; } else beeper_last_subpos[ bchan ] = ( on ? vol - subpos : subpos ); subval = ampl - beeper_last_subpos[ bchan ]; if( newpos >= 0 ) { /* fill gap from previous position */ if( is_tape ) ptr = tape_buf + sound_fillpos[1]; else ptr = sound_buf + ( sound_stereo ? sound_fillpos[0] * 2 : sound_fillpos[0] ); for( f = sound_fillpos[ bchan ]; f < newpos && f < sound_generator_framesiz; f++ ) SOUND_WRITE_BUF( bchan, ptr, sound_oldval[ bchan ] ); if( newpos < sound_generator_framesiz ) { /* newpos may be less than sound_fillpos, so... */ if( is_tape ) ptr = tape_buf + newpos; else ptr = sound_buf + ( sound_stereo ? newpos * 2 : newpos ); /* write subsample value */ SOUND_WRITE_BUF( bchan, ptr, subval ); } } sound_oldpos[ bchan ] = newpos; sound_fillpos[ bchan ] = newpos + 1; sound_oldval[ bchan ] = sound_oldval_orig[ bchan ] = val; } #endif uint8_t* GetAYRegsPtr(struct CAY8910 *_this) { return &(_this->sound_ay_registers[0]); } void SetCLK(double CLK) { m_fCurrentCLK_AY8910 = CLK; } // #define SS_YAML_KEY_AY8910 "AY8910" #define SS_YAML_KEY_TONE0_TICK "Tone0 Tick" #define SS_YAML_KEY_TONE1_TICK "Tone1 Tick" #define SS_YAML_KEY_TONE2_TICK "Tone2 Tick" #define SS_YAML_KEY_TONE0_HIGH "Tone0 High" #define SS_YAML_KEY_TONE1_HIGH "Tone1 High" #define SS_YAML_KEY_TONE2_HIGH "Tone2 High" #define SS_YAML_KEY_NOISE_TICK "Noise Tick" #define SS_YAML_KEY_TONE_SUBCYCLES "Tone Subcycles" #define SS_YAML_KEY_ENV_SUBCYCLES "Env Subcycles" #define SS_YAML_KEY_ENV_INTERNAL_TICK "Env Internal Tick" #define SS_YAML_KEY_ENV_TICK "Env Tick" #define SS_YAML_KEY_TICK_INCR "Tick Incr" #define SS_YAML_KEY_TONE0_PERIOD "Tone0 Period" #define SS_YAML_KEY_TONE1_PERIOD "Tone1 Period" #define SS_YAML_KEY_TONE2_PERIOD "Tone2 Period" #define SS_YAML_KEY_NOISE_PERIOD "Noise Period" #define SS_YAML_KEY_ENV_PERIOD "Env Period" #define SS_YAML_KEY_RNG "RNG" #define SS_YAML_KEY_NOISE_TOGGLE "Noise Toggle" #define SS_YAML_KEY_ENV_FIRST "Env First" #define SS_YAML_KEY_ENV_REV "Env Rev" #define SS_YAML_KEY_ENV_COUNTER "Env Counter" #define SS_YAML_KEY_REGISTERS "Registers" #define SS_YAML_KEY_REG_TONE0_PERIOD "Tone0 Period" #define SS_YAML_KEY_REG_TONE1_PERIOD "Tone1 Period" #define SS_YAML_KEY_REG_TONE2_PERIOD "Tone2 Period" #define SS_YAML_KEY_REG_NOISE_PERIOD "Noise Period" #define SS_YAML_KEY_REG_MIXER "Mixer" #define SS_YAML_KEY_REG_VOL0 "Vol0" #define SS_YAML_KEY_REG_VOL1 "Vol1" #define SS_YAML_KEY_REG_VOL2 "Vol2" #define SS_YAML_KEY_REG_ENV_PERIOD "Env Period" #define SS_YAML_KEY_REG_ENV_SHAPE "Env Shape" #define SS_YAML_KEY_REG_PORTA "PortA" #define SS_YAML_KEY_REG_PORTB "PortB" #define SS_YAML_KEY_CHANGE "Change" #define SS_YAML_VALUE_CHANGE_FORMAT "%d, %d, 0x%1X, 0x%02X" #if 1 // APPLE2IX static bool _saveStateData32(StateHelper_s *helper, uint32_t data) { int fd = helper->fd; uint8_t serialized[sizeof(uint32_t)] = { 0 }; serialized[0] = (uint8_t)((data & 0xFF000000) >> 24); serialized[1] = (uint8_t)((data & 0xFF0000 ) >> 16); serialized[2] = (uint8_t)((data & 0xFF00 ) >> 8); serialized[3] = (uint8_t)((data & 0xFF ) >> 0); return helper->save(fd, serialized, sizeof(serialized)); } static bool _loadStateData32(StateHelper_s *helper, uint32_t *data) { int fd = helper->fd; uint8_t serialized[sizeof(uint32_t)] = { 0 }; if (!helper->load(fd, serialized, sizeof(uint32_t))) { return false; } *data = (uint32_t)(serialized[0] << 24) | (uint32_t)(serialized[1] << 16) | (uint32_t)(serialized[2] << 8) | (uint32_t)(serialized[3] << 0); return true; } static bool _saveState(StateHelper_s *helper, struct CAY8910 *_this) { int fd = helper->fd; bool saved = false; do { if (!_saveStateData32(helper, _this->ay_tone_tick[0])) { break; } if (!_saveStateData32(helper, _this->ay_tone_tick[1])) { break; } if (!_saveStateData32(helper, _this->ay_tone_tick[2])) { break; } if (!_saveStateData32(helper, _this->ay_tone_high[0])) { break; } if (!_saveStateData32(helper, _this->ay_tone_high[1])) { break; } if (!_saveStateData32(helper, _this->ay_tone_high[2])) { break; } if (!_saveStateData32(helper, _this->ay_noise_tick)) { break; } if (!_saveStateData32(helper, _this->ay_tone_subcycles)) { break; } if (!_saveStateData32(helper, _this->ay_env_subcycles)) { break; } if (!_saveStateData32(helper, _this->ay_env_internal_tick)) { break; } if (!_saveStateData32(helper, _this->ay_env_tick)) { break; } if (!_saveStateData32(helper, _this->ay_tick_incr)) { break; } if (!_saveStateData32(helper, _this->ay_tone_period[0])) { break; } if (!_saveStateData32(helper, _this->ay_tone_period[1])) { break; } if (!_saveStateData32(helper, _this->ay_tone_period[2])) { break; } if (!_saveStateData32(helper, _this->ay_noise_period)) { break; } if (!_saveStateData32(helper, _this->ay_env_period)) { break; } if (!_saveStateData32(helper, _this->rng)) { break; } if (!_saveStateData32(helper, _this->noise_toggle)) { break; } if (!_saveStateData32(helper, _this->env_first)) { break; } if (!_saveStateData32(helper, _this->env_rev)) { break; } if (!_saveStateData32(helper, _this->env_counter)) { break; } // Registers { bool success = true; for (unsigned int i=0; i<16; i++) { uint8_t reg = _this->sound_ay_registers[i]; if (!helper->save(fd, ®, sizeof(reg))) { success = false; break; } } if (!success) { break; } } // Queued changes assert(_this->ay_change_count >= 0); if (!_saveStateData32(helper, _this->ay_change_count)) { break; } if (_this->ay_change_count) { bool success = true; for (int i=0; i<_this->ay_change_count; i++) { if (!_saveStateData32(helper, (uint32_t)_this->ay_change[i].tstates)) { success = false; break; } if (!_saveStateData32(helper, (uint32_t)(_this->ay_change[i].ofs))) { success = false; break; } uint8_t reg = _this->ay_change[i].reg; if (!helper->save(fd, ®, sizeof(reg))) { success = false; break; } uint8_t val = _this->ay_change[i].val; if (!helper->save(fd, &val, sizeof(val))) { success = false; break; } } if (!success) { break; } } saved = true; } while (0); return saved; } static bool _loadState(StateHelper_s *helper, struct CAY8910 *_this) { int fd = helper->fd; bool loaded = false; do { if (!_loadStateData32(helper, &(_this->ay_tone_tick[0]))) { break; } if (!_loadStateData32(helper, &(_this->ay_tone_tick[1]))) { break; } if (!_loadStateData32(helper, &(_this->ay_tone_tick[2]))) { break; } if (!_loadStateData32(helper, &(_this->ay_tone_high[0]))) { break; } if (!_loadStateData32(helper, &(_this->ay_tone_high[1]))) { break; } if (!_loadStateData32(helper, &(_this->ay_tone_high[2]))) { break; } if (!_loadStateData32(helper, &(_this->ay_noise_tick))) { break; } if (!_loadStateData32(helper, &(_this->ay_tone_subcycles))) { break; } if (!_loadStateData32(helper, &(_this->ay_env_subcycles))) { break; } if (!_loadStateData32(helper, &(_this->ay_env_internal_tick))) { break; } if (!_loadStateData32(helper, &(_this->ay_env_tick))) { break; } if (!_loadStateData32(helper, &(_this->ay_tick_incr))) { break; } if (!_loadStateData32(helper, &(_this->ay_tone_period[0]))) { break; } if (!_loadStateData32(helper, &(_this->ay_tone_period[1]))) { break; } if (!_loadStateData32(helper, &(_this->ay_tone_period[2]))) { break; } if (!_loadStateData32(helper, &(_this->ay_noise_period))) { break; } if (!_loadStateData32(helper, &(_this->ay_env_period))) { break; } if (!_loadStateData32(helper, (uint32_t *)&(_this->rng))) { break; } if (!_loadStateData32(helper, (uint32_t *)&(_this->noise_toggle))) { break; } if (!_loadStateData32(helper, (uint32_t *)&(_this->env_first))) { break; } if (!_loadStateData32(helper, (uint32_t *)&(_this->env_rev))) { break; } if (!_loadStateData32(helper, (uint32_t *)&(_this->env_counter))) { break; } // Registers { bool success = true; for (unsigned int i=0; i<16; i++) { if (!helper->load(fd, &(_this->sound_ay_registers[i]), sizeof(_this->sound_ay_registers[i]))) { success = false; break; } } if (!success) { break; } } // Queued changes if (!_loadStateData32(helper, (uint32_t *)&(_this->ay_change_count))) { break; } assert(_this->ay_change_count >= 0); if (_this->ay_change_count) { bool success = true; for (int i=0; i<_this->ay_change_count; i++) { if (!_loadStateData32(helper, (uint32_t *)&(_this->ay_change[i].tstates))) { success = false; break; } uint32_t ofs = 0; if (!_loadStateData32(helper, &ofs)) { success = false; break; } assert(ofs <= 65535); _this->ay_change[i].ofs = (uint16_t)ofs; if (!helper->load(fd, (uint8_t *)&(_this->ay_change[i].reg), sizeof(_this->ay_change[i].reg))) { success = false; break; } if (!helper->load(fd, (uint8_t *)&(_this->ay_change[i].val), sizeof(_this->ay_change[i].val))) { success = false; break; } } if (!success) { break; } } loaded = true; } while (0); return loaded; } # if TESTING static int _assert_testData32(const uint32_t data32, uint8_t **exData) { uint8_t *expected = *exData; uint32_t d32 = (uint32_t)(expected[0] << 24) | (uint32_t)(expected[1] << 16) | (uint32_t)(expected[2] << 8) | (uint32_t)(expected[3] << 0); ASSERT(d32 == data32); *exData += 4; PASS(); } int _testStateA2V2(struct CAY8910 *_this, uint8_t **exData) { uint8_t *p8 = *exData; _assert_testData32(_this->ay_tone_tick[0], &p8); _assert_testData32(_this->ay_tone_tick[1], &p8); _assert_testData32(_this->ay_tone_tick[2], &p8); _assert_testData32(_this->ay_tone_high[0], &p8); _assert_testData32(_this->ay_tone_high[1], &p8); _assert_testData32(_this->ay_tone_high[2], &p8); _assert_testData32(_this->ay_noise_tick, &p8); _assert_testData32(_this->ay_tone_subcycles, &p8); _assert_testData32(_this->ay_env_subcycles, &p8); _assert_testData32(_this->ay_env_internal_tick, &p8); _assert_testData32(_this->ay_env_tick, &p8); //_assert_testData32(_this->ay_tick_incr, &p8); -- IGNORE... this will change depending on sample rate p8 += 4; _assert_testData32(_this->ay_tone_period[0], &p8); _assert_testData32(_this->ay_tone_period[1], &p8); _assert_testData32(_this->ay_tone_period[2], &p8); _assert_testData32(_this->ay_noise_period, &p8); _assert_testData32(_this->ay_env_period, &p8); _assert_testData32(_this->rng, &p8); _assert_testData32(_this->noise_toggle, &p8); _assert_testData32(_this->env_first, &p8); _assert_testData32(_this->env_rev, &p8); _assert_testData32(_this->env_counter, &p8); // Registers for (unsigned int i=0; i<16; i++) { ASSERT(_this->sound_ay_registers[i] == *p8++); } int changeCount = _this->ay_change_count; _assert_testData32(changeCount, &p8); ASSERT(changeCount >= 0); if (changeCount) { for (int i=0; iay_change[i].tstates, &p8); _assert_testData32(_this->ay_change[i].ofs, &p8); ASSERT(_this->ay_change[i].reg == *p8++); ASSERT(_this->ay_change[i].val == *p8++); } } *exData = p8; PASS(); } # endif #else void CAY8910::SaveSnapshot(YamlSaveHelper& yamlSaveHelper, std::string& suffix) { std::string unit = std::string(SS_YAML_KEY_AY8910) + suffix; YamlSaveHelper::Label label(yamlSaveHelper, "%s:\n", unit.c_str()); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE0_TICK, ay_tone_tick[0]); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE1_TICK, ay_tone_tick[1]); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE2_TICK, ay_tone_tick[2]); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE0_HIGH, ay_tone_high[0]); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE1_HIGH, ay_tone_high[1]); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE2_HIGH, ay_tone_high[2]); yamlSaveHelper.SaveUint(SS_YAML_KEY_NOISE_TICK, ay_noise_tick); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE_SUBCYCLES, ay_tone_subcycles); yamlSaveHelper.SaveUint(SS_YAML_KEY_ENV_SUBCYCLES, ay_env_subcycles); yamlSaveHelper.SaveUint(SS_YAML_KEY_ENV_INTERNAL_TICK, ay_env_internal_tick); yamlSaveHelper.SaveUint(SS_YAML_KEY_ENV_TICK, ay_env_tick); yamlSaveHelper.SaveUint(SS_YAML_KEY_TICK_INCR, ay_tick_incr); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE0_PERIOD, ay_tone_period[0]); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE1_PERIOD, ay_tone_period[1]); yamlSaveHelper.SaveUint(SS_YAML_KEY_TONE2_PERIOD, ay_tone_period[2]); yamlSaveHelper.SaveUint(SS_YAML_KEY_NOISE_PERIOD, ay_noise_period); // APPLE2IX FIXME WHAT ABOUT : SS_YAML_KEY_ENV_PERIOD "Env Period" ... AppleWin forgot about this? Also do this on load ... yamlSaveHelper.SaveUint(SS_YAML_KEY_RNG, rng); yamlSaveHelper.SaveUint(SS_YAML_KEY_NOISE_TOGGLE, noise_toggle); yamlSaveHelper.SaveUint(SS_YAML_KEY_ENV_FIRST, env_first); yamlSaveHelper.SaveUint(SS_YAML_KEY_ENV_REV, env_rev); yamlSaveHelper.SaveUint(SS_YAML_KEY_ENV_COUNTER, env_counter); // New label { YamlSaveHelper::Label registers(yamlSaveHelper, "%s:\n", SS_YAML_KEY_REGISTERS); yamlSaveHelper.SaveHexUint12(SS_YAML_KEY_REG_TONE0_PERIOD, (UINT)(sound_ay_registers[1]<<8) | sound_ay_registers[0]); yamlSaveHelper.SaveHexUint12(SS_YAML_KEY_REG_TONE1_PERIOD, (UINT)(sound_ay_registers[3]<<8) | sound_ay_registers[2]); yamlSaveHelper.SaveHexUint12(SS_YAML_KEY_REG_TONE2_PERIOD, (UINT)(sound_ay_registers[5]<<8) | sound_ay_registers[4]); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_REG_NOISE_PERIOD, sound_ay_registers[6]); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_REG_MIXER, sound_ay_registers[7]); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_REG_VOL0, sound_ay_registers[8]); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_REG_VOL1, sound_ay_registers[9]); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_REG_VOL2, sound_ay_registers[10]); yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_REG_ENV_PERIOD, (UINT)(sound_ay_registers[12]<<8) | sound_ay_registers[11]); yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_REG_ENV_SHAPE, sound_ay_registers[13]); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_REG_PORTA, sound_ay_registers[14]); yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_REG_PORTB, sound_ay_registers[15]); } // New label if (ay_change_count) { YamlSaveHelper::Label change(yamlSaveHelper, "%s:\n", SS_YAML_KEY_CHANGE); for (int i=0; i> 8) & 0xf; period = (USHORT) yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_TONE1_PERIOD); sound_ay_registers[2] = period & 0xff; sound_ay_registers[3] = (period >> 8) & 0xf; period = (USHORT) yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_TONE2_PERIOD); sound_ay_registers[4] = period & 0xff; sound_ay_registers[5] = (period >> 8) & 0xf; sound_ay_registers[6] = yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_NOISE_PERIOD); sound_ay_registers[7] = yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_MIXER); sound_ay_registers[8] = yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_VOL0); sound_ay_registers[9] = yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_VOL1); sound_ay_registers[10] = yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_VOL2); period = (USHORT) yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_ENV_PERIOD); sound_ay_registers[11] = period & 0xff; sound_ay_registers[12] = period >> 8; sound_ay_registers[13] = yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_ENV_SHAPE); sound_ay_registers[14] = yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_PORTA); sound_ay_registers[15] = yamlLoadHelper.LoadUint(SS_YAML_KEY_REG_PORTB); yamlLoadHelper.PopMap(); ay_change_count = 0; if (yamlLoadHelper.GetSubMap(SS_YAML_KEY_CHANGE)) { while(1) { char szIndex[7]; sprintf_s(szIndex, sizeof(szIndex), "0x%04X", ay_change_count); bool bFound; std::string value = yamlLoadHelper.LoadString_NoThrow(szIndex, bFound); if (!bFound) break; // done if(4 != sscanf_s(value.c_str(), SS_YAML_VALUE_CHANGE_FORMAT, &ay_change[ay_change_count].tstates, &ay_change[ay_change_count].ofs, &ay_change[ay_change_count].reg, &ay_change[ay_change_count].val)) throw std::string("Card: AY8910: Failed to scanf change list"); ay_change_count++; if (ay_change_count > AY_CHANGE_MAX) throw std::string("Card: AY8910: Too many changes"); } yamlLoadHelper.PopMap(); } yamlLoadHelper.PopMap(); return true; } #endif /////////////////////////////////////////////////////////////////////////////// // AY8910 interface #ifndef APPLE2IX #include "CPU.h" // For g_nCumulativeCycles #endif static CAY8910 g_AY8910[MAX_8910]; #ifdef APPLE2IX static uint64_t g_uLastCumulativeCycles = 0; #else static unsigned __int64 g_uLastCumulativeCycles = 0; #endif #if MB_TRACING void _mb_trace_AY8910(int chip, FILE *mb_trace_fp) { if (!mb_trace_fp) { return; } assert(chip < MAX_8910); CAY8910 *_this = &g_AY8910[chip]; fprintf(mb_trace_fp, "\tAY8910(%d):\n", chip); fprintf(mb_trace_fp, "\t\tay_tone_tick:%u,%u,%u\n", _this->ay_tone_tick[0], _this->ay_tone_tick[1], _this->ay_tone_tick[2]); fprintf(mb_trace_fp, "\t\tay_tone_high:%u,%u,%u\n", _this->ay_tone_high[0], _this->ay_tone_high[1], _this->ay_tone_high[2]); fprintf(mb_trace_fp, "\t\tay_noise_tick:%u ay_tone_subcycles:%u ay_env_subcycles:%u\n", _this->ay_noise_tick, _this->ay_tone_subcycles, _this->ay_env_subcycles); fprintf(mb_trace_fp, "\t\tay_env_internal_tick:%u ay_env_tick:%u ay_tick_incr:%u\n", _this->ay_env_internal_tick, _this->ay_env_tick, _this->ay_tick_incr); fprintf(mb_trace_fp, "\t\tay_tone_period:%u,%u,%u\n", _this->ay_tone_period[0], _this->ay_tone_period[1], _this->ay_tone_period[2]); fprintf(mb_trace_fp, "\t\tay_noise_period:%u ay_env_period:%u rng:%d noise_toggle:%d\n", _this->ay_noise_period, _this->ay_env_period, _this->rng, _this->noise_toggle); fprintf(mb_trace_fp, "\t\tenv_first:%d env_rev:%d env_counter:%d\n", _this->env_first, _this->env_rev, _this->env_counter); fprintf(mb_trace_fp, "\t\tenv_first:%d env_rev:%d env_counter:%d\n", _this->env_first, _this->env_rev, _this->env_counter); fprintf(mb_trace_fp, "\t\tsound_ay_registers: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X\n", _this->sound_ay_registers[0], _this->sound_ay_registers[1], _this->sound_ay_registers[2], _this->sound_ay_registers[3], _this->sound_ay_registers[4], _this->sound_ay_registers[5], _this->sound_ay_registers[6], _this->sound_ay_registers[7], _this->sound_ay_registers[8], _this->sound_ay_registers[9], _this->sound_ay_registers[10], _this->sound_ay_registers[11], _this->sound_ay_registers[12], _this->sound_ay_registers[13], _this->sound_ay_registers[14], _this->sound_ay_registers[15]); fprintf(mb_trace_fp, "\t\tay_change_count:(%d)\n", _this->ay_change_count); for (unsigned int i=0; i<_this->ay_change_count; i++) { fprintf(mb_trace_fp, "\t\t\t%u: tstates:%lu ofs:%04X reg:%02X val:%02X\n", i, _this->ay_change[i].tstates, _this->ay_change[i].ofs, _this->ay_change[i].reg, _this->ay_change[i].val); } } #endif void _AYWriteReg(int chip, int r, int v #if MB_TRACING , FILE *mb_trace_fp #endif ) { libspectrum_dword uOffset = (libspectrum_dword) (cycles_count_total - g_uLastCumulativeCycles); #if MB_TRACING if (mb_trace_fp) { fprintf(mb_trace_fp, "\t uOffset: %lu\n", uOffset); } #endif sound_ay_write(&g_AY8910[chip], r, v, uOffset); } void AY8910_reset(int chip) { // Don't reset the AY CLK, as this is a property of the card (MB/Phasor), not the AY chip sound_ay_reset(&g_AY8910[chip]); // Calls: sound_ay_init(); } void AY8910UpdateSetCycles() { g_uLastCumulativeCycles = cycles_count_total; } void AY8910Update(int chip, int16_t** buffer, int nNumSamples) { AY8910UpdateSetCycles(); sound_generator_framesiz = nNumSamples; g_ppSoundBuffers = buffer; sound_frame(&g_AY8910[chip]); } void AY8910_InitAll(int nClock, unsigned long nSampleRate) { for (unsigned int i=0; i= MAX_8910) return NULL; return GetAYRegsPtr(&g_AY8910[uChip]); } #if 1 // APPLE2IX bool _ay8910_saveState(StateHelper_s *helper, unsigned int chip) { assert(chip < MAX_8910); return _saveState(helper, &g_AY8910[chip]); } bool _ay8910_loadState(StateHelper_s *helper, unsigned int chip) { assert(chip < MAX_8910); return _loadState(helper, &g_AY8910[chip]); } # if TESTING int _ay8910_testAssertA2V2(unsigned int chip, uint8_t **exData) { return _testStateA2V2(&g_AY8910[chip], exData); } # endif #else UINT AY8910_SaveSnapshot(YamlSaveHelper& yamlSaveHelper, UINT uChip, std::string& suffix) { if (uChip >= MAX_8910) return 0; g_AY8910[uChip].SaveSnapshot(yamlSaveHelper, suffix); return 1; } UINT AY8910_LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT uChip, std::string& suffix) { if (uChip >= MAX_8910) return 0; return g_AY8910[uChip].LoadSnapshot(yamlLoadHelper, suffix) ? 1 : 0; } #endif