mirror of
https://git.code.sf.net/p/fuse-emulator/fuse
synced 2026-01-27 01:41:34 +03:00
748 lines
22 KiB
C
748 lines
22 KiB
C
/* 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 <config.h>
|
|
|
|
#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 );
|
|
}
|