/* sound.c: Sound support Copyright (c) 2000-2016 Russell Marks, Matan Ziv-Av, Philip Kendall, Fredrick Meunier, Patrik Rak 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 */ /* 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). */ #include #include "fuse.h" #include "infrastructure/startup_manager.h" #include "machine.h" #include "movie.h" #include "options.h" #include "settings.h" #include "sound.h" #include "tape.h" #include "timer/timer.h" #include "ui/ui.h" #include "sound/blipbuffer.h" /* Do we have any of our sound devices available? */ /* configuration */ int sound_enabled = 0; /* Are we currently using the sound card */ static int sound_enabled_ever = 0; /* whether sound has *ever* been in use; see sound_ay_write() and sound_ay_reset() */ int sound_stereo_ay = SOUND_STEREO_AY_NONE; /* local copy of settings_current.stereo_ay */ /* 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 */ /* 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 int sound_framesiz; static int sound_channels; static unsigned int ay_tone_levels[16]; static unsigned int ay_tone_tick[3], ay_tone_high[3], ay_noise_tick; static unsigned int ay_tone_cycles, ay_env_cycles; static unsigned int ay_env_internal_tick, ay_env_tick; static unsigned int ay_tone_period[3], ay_noise_period, ay_env_period; /* Local copy of the AY registers */ static libspectrum_byte sound_ay_registers[16]; struct ay_change_tag { libspectrum_dword tstates; unsigned char reg, val; }; static struct ay_change_tag ay_change[ AY_CHANGE_MAX ]; static int ay_change_count; Blip_Buffer *left_buf = NULL; Blip_Buffer *right_buf = NULL; blip_sample_t *samples = NULL; Blip_Synth *left_beeper_synth = NULL, *right_beeper_synth = NULL; Blip_Synth *ay_a_synth = NULL, *ay_b_synth = NULL, *ay_c_synth = NULL; Blip_Synth *ay_a_synth_r = NULL, *ay_b_synth_r = NULL, *ay_c_synth_r = NULL; Blip_Synth *left_specdrum_synth = NULL, *right_specdrum_synth = NULL; Blip_Synth *left_covox_synth = NULL, *right_covox_synth = NULL; struct speaker_type_tag { int bass; double treble; }; static struct speaker_type_tag speaker_type[] = { { 200, -37.0 }, { 1000, -67.0 }, { 0, 0.0 } }; static double sound_get_volume( int volume ) { if( volume < 0 ) volume = 0; else if( volume > 100 ) volume = 100; return volume / 100.0; } /* Returns the emulation speed adjusted processor speed */ libspectrum_dword sound_get_effective_processor_speed( void ) { return machine_current->timings.processor_speed / 100 * settings_current.emulation_speed; } static int sound_init_blip( Blip_Buffer **buf, Blip_Synth **synth ) { *buf = new_Blip_Buffer(); blip_buffer_set_clock_rate( *buf, sound_get_effective_processor_speed() ); /* Allow up to 1s of playback buffer - this allows us to cope with slowing down to 2% of speed where a single Speccy frame generates just under 1s of sound */ if ( blip_buffer_set_sample_rate( *buf, settings_current.sound_freq, 1000 ) ) { sound_end(); ui_error( UI_ERROR_ERROR, "out of memory at %s:%d", __FILE__, __LINE__ ); return 0; } *synth = new_Blip_Synth(); blip_synth_set_volume( *synth, sound_get_volume( settings_current.volume_beeper ) ); blip_synth_set_output( *synth, *buf ); blip_buffer_set_bass_freq( *buf, speaker_type[ option_enumerate_sound_speaker_type() ].bass ); blip_synth_set_treble_eq( *synth, speaker_type[ option_enumerate_sound_speaker_type() ].treble ); return 1; } static void sound_ay_init( void ) { /* 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; ay_noise_tick = ay_noise_period = 0; ay_env_internal_tick = ay_env_tick = ay_env_period = 0; ay_tone_cycles = ay_env_cycles = 0; for( f = 0; f < 3; f++ ) ay_tone_tick[f] = ay_tone_high[f] = 0, ay_tone_period[f] = 1; ay_change_count = 0; } #ifndef UI_WIN32 #define MIN_SPEED_PERCENTAGE 2 #define MAX_SPEED_PERCENTAGE 500 #else /* #ifndef UI_WIN32 */ /* We are limiting speed until bugs in the DirectSound driver are resolved, see [bugs:#364] for more details */ #define MIN_SPEED_PERCENTAGE 50 #define MAX_SPEED_PERCENTAGE 300 #endif /* #ifndef UI_WIN32 */ static int is_in_sound_enabled_range( void ) { return settings_current.emulation_speed >= MIN_SPEED_PERCENTAGE && settings_current.emulation_speed <= MAX_SPEED_PERCENTAGE; } void sound_init( const char *device ) { float hz; double treble; Blip_Synth **ay_left_synth; Blip_Synth **ay_mid_synth; Blip_Synth **ay_mid_synth_r; Blip_Synth **ay_right_synth; /* Allow sound as long as emulation speed is greater than 2% (less than that and a single Speccy frame generates more than a seconds worth of sound which is bigger than the maximum Blip_Buffer of 1 second) */ if( !( !sound_enabled && settings_current.sound && is_in_sound_enabled_range() ) ) return; /* only try for stereo if we need it */ sound_stereo_ay = option_enumerate_sound_stereo_ay(); if( settings_current.sound && sound_lowlevel_init( device, &settings_current.sound_freq, &sound_stereo_ay ) ) return; if( !sound_init_blip(&left_buf, &left_beeper_synth) ) return; if( sound_stereo_ay != SOUND_STEREO_AY_NONE && !sound_init_blip(&right_buf, &right_beeper_synth) ) return; treble = speaker_type[ option_enumerate_sound_speaker_type() ].treble; ay_a_synth = new_Blip_Synth(); blip_synth_set_volume( ay_a_synth, sound_get_volume( settings_current.volume_ay) ); blip_synth_set_treble_eq( ay_a_synth, treble ); ay_b_synth = new_Blip_Synth(); blip_synth_set_volume( ay_b_synth, sound_get_volume( settings_current.volume_ay) ); blip_synth_set_treble_eq( ay_b_synth, treble ); ay_c_synth = new_Blip_Synth(); blip_synth_set_volume( ay_c_synth, sound_get_volume( settings_current.volume_ay) ); blip_synth_set_treble_eq( ay_c_synth, treble ); left_specdrum_synth = new_Blip_Synth(); blip_synth_set_volume( left_specdrum_synth, sound_get_volume( settings_current.volume_specdrum ) ); blip_synth_set_output( left_specdrum_synth, left_buf ); blip_synth_set_treble_eq( left_specdrum_synth, treble ); left_covox_synth = new_Blip_Synth(); blip_synth_set_volume( left_covox_synth, sound_get_volume( settings_current.volume_covox ) ); blip_synth_set_output( left_covox_synth, left_buf ); blip_synth_set_treble_eq( left_covox_synth, treble ); /* 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). */ ay_a_synth_r = NULL; ay_b_synth_r = NULL; ay_c_synth_r = NULL; if( sound_stereo_ay != SOUND_STEREO_AY_NONE ) { /* Attach the Blip_Synth's we've already created as appropriate, and * create one more Blip_Synth for the middle channel's right buffer. */ if( sound_stereo_ay == SOUND_STEREO_AY_ACB ) { ay_left_synth = &ay_a_synth; ay_mid_synth = &ay_c_synth; ay_mid_synth_r = &ay_c_synth_r; ay_right_synth = &ay_b_synth; } else if ( sound_stereo_ay == SOUND_STEREO_AY_ABC ) { ay_left_synth = &ay_a_synth; ay_mid_synth = &ay_b_synth; ay_mid_synth_r = &ay_b_synth_r; ay_right_synth = &ay_c_synth; } else { ui_error( UI_ERROR_ERROR, "unknown AY stereo separation type: %d", sound_stereo_ay ); fuse_abort(); } blip_synth_set_output( *ay_left_synth, left_buf ); blip_synth_set_output( *ay_mid_synth, left_buf ); blip_synth_set_output( *ay_right_synth, right_buf ); *ay_mid_synth_r = new_Blip_Synth(); blip_synth_set_volume( *ay_mid_synth_r, sound_get_volume( settings_current.volume_ay ) ); blip_synth_set_output( *ay_mid_synth_r, right_buf ); blip_synth_set_treble_eq( *ay_mid_synth_r, treble ); right_specdrum_synth = new_Blip_Synth(); blip_synth_set_volume( right_specdrum_synth, sound_get_volume( settings_current.volume_specdrum ) ); blip_synth_set_output( right_specdrum_synth, right_buf ); blip_synth_set_treble_eq( right_specdrum_synth, treble ); right_covox_synth = new_Blip_Synth(); blip_synth_set_volume( right_covox_synth, sound_get_volume( settings_current.volume_covox ) ); blip_synth_set_output( right_covox_synth, right_buf ); blip_synth_set_treble_eq( right_covox_synth, treble ); } else { blip_synth_set_output( ay_a_synth, left_buf ); blip_synth_set_output( ay_b_synth, left_buf ); blip_synth_set_output( ay_c_synth, left_buf ); } sound_enabled = sound_enabled_ever = 1; sound_channels = ( sound_stereo_ay != SOUND_STEREO_AY_NONE ? 2 : 1 ); /* Adjust relative processor speed to deal with adjusting sound generation frequency against emulation speed (more flexible than adjusting generated sample rate) */ hz = ( float )sound_get_effective_processor_speed() / machine_current->timings.tstates_per_frame; /* Size of audio data we will get from running a single Spectrum frame */ sound_framesiz = ( float )settings_current.sound_freq / hz; sound_framesiz++; samples = libspectrum_new0( blip_sample_t, sound_framesiz * sound_channels ); /* initialize movie settings... */ movie_init_sound( settings_current.sound_freq, sound_stereo_ay ); } void sound_pause( void ) { if( sound_enabled ) sound_end(); } void sound_unpause( void ) { /* No sound if fastloading in progress */ if( settings_current.fastload && timer_fastloading_active() ) return; sound_init( settings_current.sound_device ); } void sound_end( void ) { if( sound_enabled ) { delete_Blip_Synth( &left_beeper_synth ); delete_Blip_Synth( &right_beeper_synth ); delete_Blip_Synth( &ay_a_synth ); delete_Blip_Synth( &ay_b_synth ); delete_Blip_Synth( &ay_c_synth ); delete_Blip_Synth( &ay_a_synth_r ); delete_Blip_Synth( &ay_b_synth_r ); delete_Blip_Synth( &ay_c_synth_r ); delete_Blip_Synth( &left_specdrum_synth ); delete_Blip_Synth( &right_specdrum_synth ); delete_Blip_Synth( &left_covox_synth ); delete_Blip_Synth( &right_covox_synth ); delete_Blip_Buffer( &left_buf ); delete_Blip_Buffer( &right_buf ); if( settings_current.sound ) sound_lowlevel_end(); libspectrum_free( samples ); sound_enabled = 0; } } void sound_register_startup( void ) { startup_manager_module dependencies[] = { STARTUP_MANAGER_MODULE_SETUID }; startup_manager_register( STARTUP_MANAGER_MODULE_SOUND, dependencies, ARRAY_SIZE( dependencies ), NULL, NULL, sound_end ); } static inline void ay_do_tone( int level, unsigned int tone_count, int *var, int chan ) { *var = 0; ay_tone_tick[ chan ] += tone_count; if( ay_tone_tick[ chan ] >= ay_tone_period[ chan ] ) { ay_tone_tick[ chan ] -= ay_tone_period[ chan ]; ay_tone_high[ chan ] = !ay_tone_high[ chan ]; } if( level ) { if( ay_tone_high[ chan ] ) *var = level; else { *var = 0; } } } /* bitmasks for envelope */ #define AY_ENV_CONT 8 #define AY_ENV_ATTACK 4 #define AY_ENV_ALT 2 #define AY_ENV_HOLD 1 /* the AY steps down the external clock by 16 for tone and noise generators */ #define AY_CLOCK_DIVISOR 16 /* all Spectrum models and clones with an AY seem to count down the master clock by 2 to drive the AY */ #define AY_CLOCK_RATIO 2 static void sound_ay_overlay( void ) { static int rng = 1; static int noise_toggle = 0; static int env_first = 1, env_rev = 0, env_counter = 15; int tone_level[3]; int mixer, envshape; int g, level; libspectrum_dword f; struct ay_change_tag *change_ptr = ay_change; int changes_left = ay_change_count; int reg, r; int chan1, chan2, chan3; int last_chan1 = 0, last_chan2 = 0, last_chan3 = 0; unsigned int tone_count, noise_count; /* If no AY chip, don't produce any AY sound (!) */ if( !( periph_is_active( PERIPH_TYPE_FULLER) || periph_is_active( PERIPH_TYPE_MELODIK ) || machine_current->capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_AY ) ) return; for( f = 0; f < machine_current->timings.tstates_per_frame; f+= AY_CLOCK_DIVISOR * AY_CLOCK_RATIO ) { /* update ay registers. */ while( changes_left && f >= change_ptr->tstates ) { 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 */ ay_tone_period[r] = ( sound_ay_registers[ reg & ~1 ] | ( sound_ay_registers[ reg | 1 ] & 15 ) << 8 ); if( !ay_tone_period[r] ) ay_tone_period[r]++; /* important to get this right, otherwise e.g. Ghouls 'n' Ghosts * has really scratchy, horrible-sounding vibrato. */ if( ay_tone_tick[r] >= ay_tone_period[r] * 2 ) ay_tone_tick[r] %= ay_tone_period[r] * 2; break; case 6: ay_noise_tick = 0; ay_noise_period = ( sound_ay_registers[ reg ] & 31 ); break; case 11: case 12: ay_env_period = sound_ay_registers[11] | ( sound_ay_registers[12] << 8 ); break; case 13: ay_env_internal_tick = ay_env_tick = ay_env_cycles = 0; env_first = 1; env_rev = 0; env_counter = ( 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[ sound_ay_registers[ 8 + g ] & 15 ]; /* envelope */ envshape = sound_ay_registers[13]; level = ay_tone_levels[ env_counter ]; for( g = 0; g < 3; g++ ) if( sound_ay_registers[ 8 + g ] & 16 ) tone_level[g] = level; /* envelope output counter gets incr'd every 16 AY cycles. */ ay_env_cycles += AY_CLOCK_DIVISOR; noise_count = 0; while( ay_env_cycles >= 16 ) { ay_env_cycles -= 16; noise_count++; ay_env_tick++; while( ay_env_tick >= ay_env_period ) { ay_env_tick -= ay_env_period; /* do a 1/16th-of-period incr/decr if needed */ if( env_first || ( ( envshape & AY_ENV_CONT ) && !( envshape & AY_ENV_HOLD ) ) ) { if( env_rev ) env_counter -= ( envshape & AY_ENV_ATTACK ) ? 1 : -1; else env_counter += ( envshape & AY_ENV_ATTACK ) ? 1 : -1; if( env_counter < 0 ) env_counter = 0; if( env_counter > 15 ) env_counter = 15; } ay_env_internal_tick++; while( ay_env_internal_tick >= 16 ) { ay_env_internal_tick -= 16; /* end of cycle */ if( !( envshape & AY_ENV_CONT ) ) env_counter = 0; else { if( envshape & AY_ENV_HOLD ) { if( env_first && ( envshape & AY_ENV_ALT ) ) env_counter = ( env_counter ? 0 : 15 ); } else { /* non-hold */ if( envshape & AY_ENV_ALT ) env_rev = !env_rev; else env_counter = ( envshape & AY_ENV_ATTACK ) ? 0 : 15; } } env_first = 0; } /* don't keep trying if period is zero */ if( !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 = sound_ay_registers[7]; ay_tone_cycles += AY_CLOCK_DIVISOR; tone_count = ay_tone_cycles >> 3; ay_tone_cycles &= 7; if( ( mixer & 1 ) == 0 ) { level = chan1; ay_do_tone( level, tone_count, &chan1, 0 ); } if( ( mixer & 0x08 ) == 0 && noise_toggle ) chan1 = 0; if( ( mixer & 2 ) == 0 ) { level = chan2; ay_do_tone( level, tone_count, &chan2, 1 ); } if( ( mixer & 0x10 ) == 0 && noise_toggle ) chan2 = 0; if( ( mixer & 4 ) == 0 ) { level = chan3; ay_do_tone( level, tone_count, &chan3, 2 ); } if( ( mixer & 0x20 ) == 0 && noise_toggle ) chan3 = 0; if( last_chan1 != chan1 ) { blip_synth_update( ay_a_synth, f, chan1 ); if( ay_a_synth_r ) blip_synth_update( ay_a_synth_r, f, chan1 ); last_chan1 = chan1; } if( last_chan2 != chan2 ) { blip_synth_update( ay_b_synth, f, chan2 ); if( ay_b_synth_r ) blip_synth_update( ay_b_synth_r, f, chan2 ); last_chan2 = chan2; } if( last_chan3 != chan3 ) { blip_synth_update( ay_c_synth, f, chan3 ); if( ay_c_synth_r ) blip_synth_update( ay_c_synth_r, f, chan3 ); last_chan3 = chan3; } /* update noise RNG/filter */ ay_noise_tick += noise_count; while( ay_noise_tick >= ay_noise_period ) { ay_noise_tick -= ay_noise_period; if( ( rng & 1 ) ^ ( ( rng & 2 ) ? 1 : 0 ) ) noise_toggle = !noise_toggle; /* rng is 17-bit shift reg, bit 0 is output. * input is bit 0 xor bit 3. */ if( rng & 1 ) { rng ^= 0x24000; } rng >>= 1; /* don't keep trying if period is zero */ if( !ay_noise_period ) break; } } } /* don't make the change immediately; record it for later, * to be made by sound_frame() (via sound_ay_overlay()). */ void sound_ay_write( int reg, int val, libspectrum_dword now ) { if( ay_change_count < AY_CHANGE_MAX ) { ay_change[ ay_change_count ].tstates = now; ay_change[ ay_change_count ].reg = ( reg & 15 ); ay_change[ ay_change_count ].val = val; ay_change_count++; } } /* no need to call this initially, but should be called * on reset otherwise. */ void sound_ay_reset( void ) { int f; /* recalculate timings based on new machines ay clock */ sound_ay_init(); ay_change_count = 0; for( f = 0; f < 16; f++ ) sound_ay_write( f, 0, 0 ); for( f = 0; f < 3; f++ ) ay_tone_high[f] = 0; ay_tone_cycles = ay_env_cycles = 0; } /* * sound_specdrum_write - very simple routine * as the output is already a digitized waveform */ void sound_specdrum_write( libspectrum_word port GCC_UNUSED, libspectrum_byte val ) { if( periph_is_active( PERIPH_TYPE_SPECDRUM ) ) { blip_synth_update( left_specdrum_synth, tstates, ( val - 128) * 128); if( right_specdrum_synth ) { blip_synth_update( right_specdrum_synth, tstates, ( val - 128) * 128); } machine_current->specdrum.specdrum_dac = val - 128; } } /* * sound_covox_write - very simple routine * as the output is already a digitized waveform */ void sound_covox_write( libspectrum_word port GCC_UNUSED, libspectrum_byte val ) { if( periph_is_active( PERIPH_TYPE_COVOX_FB ) || periph_is_active( PERIPH_TYPE_COVOX_DD ) ) { blip_synth_update( left_covox_synth, tstates, val * 128); if( right_covox_synth ) { blip_synth_update( right_covox_synth, tstates, val * 128); } machine_current->covox.covox_dac = val; } } void sound_frame( void ) { long count; if( !sound_enabled ) return; /* overlay AY sound */ sound_ay_overlay(); blip_buffer_end_frame( left_buf, machine_current->timings.tstates_per_frame ); if( sound_stereo_ay != SOUND_STEREO_AY_NONE ) { blip_buffer_end_frame( right_buf, machine_current->timings.tstates_per_frame ); /* Read left channel into even samples, right channel into odd samples: LRLRLRLRLR... */ count = blip_buffer_read_samples( left_buf, samples, sound_framesiz, 1 ); blip_buffer_read_samples( right_buf, samples + 1, count, 1 ); count <<= 1; } else { count = blip_buffer_read_samples( left_buf, samples, sound_framesiz, BLIP_BUFFER_DEF_STEREO ); } if( settings_current.sound ) sound_lowlevel_frame( samples, count ); if( movie_recording ) movie_add_sound( samples, count ); ay_change_count = 0; } void sound_beeper( libspectrum_dword at_tstates, int on ) { static int beeper_ampl[] = { 0, AMPL_TAPE, AMPL_BEEPER, AMPL_BEEPER+AMPL_TAPE }; int val; if( !sound_enabled ) return; if( tape_is_playing() ) { /* Timex machines have no loading noise */ if( !settings_current.sound_load || machine_current->timex ) on = on & 0x02; } else { /* ULA book says that MIC only isn't enough to drive the speaker as output voltage is below the 1.4v threshold */ if( on == 1 ) on = 0; } val = beeper_ampl[on]; blip_synth_update( left_beeper_synth, at_tstates, val ); if( sound_stereo_ay != SOUND_STEREO_AY_NONE ) blip_synth_update( right_beeper_synth, at_tstates, val ); }