2013-10-06 06:22:08 +00:00
|
|
|
/*
|
2015-01-31 21:57:10 +00:00
|
|
|
* Apple // emulator for *nix
|
|
|
|
*
|
|
|
|
* This software package is subject to the GNU General Public License
|
|
|
|
* version 2 or later (your choice) as published by the Free Software
|
|
|
|
* Foundation.
|
|
|
|
*
|
|
|
|
* THERE ARE NO WARRANTIES WHATSOEVER.
|
|
|
|
*
|
|
|
|
*/
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
/* Apple //e speaker support. Source inspired/derived from AppleWin.
|
|
|
|
*
|
|
|
|
* - ~46 //e cycles per PC sample (played back at 22.050kHz) -- (CLK_6502/SPKR_SAMPLE_RATE)
|
2013-10-06 06:22:08 +00:00
|
|
|
*
|
2015-01-31 21:57:10 +00:00
|
|
|
* The soundcard output drives how much 6502 emulation is done in real-time. If the soundcard buffer is running out of
|
|
|
|
* sample-data, then more 6502 cycles need to be executed to top-up the buffer, and vice-versa.
|
|
|
|
*
|
|
|
|
* This is in contrast to the AY8910 voices (mockingboard/phasor), which can simply generate more data if their buffers
|
|
|
|
* are running low.
|
2013-10-06 06:22:08 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "common.h"
|
2014-01-23 04:42:34 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
#define DEBUG_SPEAKER (!defined(NDEBUG) && 0) // enable to print timing stats
|
|
|
|
#if DEBUG_SPEAKER
|
|
|
|
# define SPEAKER_LOG(...) LOG(__VA_ARGS__)
|
2013-10-06 06:22:08 +00:00
|
|
|
#else
|
2015-01-31 21:57:10 +00:00
|
|
|
# define SPEAKER_LOG(...)
|
2013-10-06 06:22:08 +00:00
|
|
|
#endif
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
#define MAX_REMAINDER_BUFFER (((CLK_6502_INT*(unsigned int)CPU_SCALE_FASTEST)/SPKR_SAMPLE_RATE)+1)
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
#define SOUNDCORE_BUFFER_SIZE (MAX_SAMPLES*sizeof(int16_t)*1/*mono*/)
|
|
|
|
#define QUARTER_SIZE (SOUNDCORE_BUFFER_SIZE/4)
|
|
|
|
#define IDEAL_MIN (SOUNDCORE_BUFFER_SIZE/4) // minimum goldilocks zone for samples-in-play
|
|
|
|
#define IDEAL_MAX (SOUNDCORE_BUFFER_SIZE/2) // maximum goldilocks-zone for samples-in-play
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
static int16_t samples_buffer[SPKR_SAMPLE_RATE * sizeof(int16_t)] = { 0 }; // holds max 1 second of samples
|
|
|
|
static int16_t remainder_buffer[MAX_REMAINDER_BUFFER * sizeof(int16_t)] = { 0 }; // holds enough to create one sample (averaged)
|
|
|
|
static unsigned int samples_buffer_idx = 0;
|
|
|
|
static unsigned int remainder_buffer_size = 0;
|
|
|
|
static unsigned int remainder_buffer_idx = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
static int16_t speaker_amplitude = SPKR_DATA_INIT;
|
|
|
|
static int16_t speaker_data = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
static double cycles_per_sample = 0.0;
|
|
|
|
static unsigned long long cycles_last_update = 0;
|
|
|
|
static unsigned long long cycles_quiet_time = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
static bool speaker_accessed_since_last_flush = false;
|
|
|
|
static bool speaker_recently_active = false;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
static bool speaker_going_silent = false;
|
|
|
|
static unsigned int speaker_silent_step = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
static int samples_adjustment_counter = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
static VOICE SpeakerVoice = { 0 };
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
/*
|
|
|
|
* Because disk image loading is slow (AKA close-to-original-//e-speed), we may auto-switch to "fullspeed" for faster
|
|
|
|
* loading when all the following heuristics hold true:
|
|
|
|
* - Disk motor is on
|
|
|
|
* - Speaker has not been toggled in some time (is not "active")
|
|
|
|
* - The graphics state is not "dirty"
|
|
|
|
*
|
|
|
|
* In fullspeed mode we output only quiet samples (zero-amplitude) at such a rate as to prevent the streaming audio from
|
|
|
|
* either underflowing or overflowing.
|
|
|
|
*
|
|
|
|
* We will also auto-switch back to the last configured "scaled" speed when the speaker is toggled.
|
|
|
|
*/
|
|
|
|
static void _speaker_init_timing(void) {
|
|
|
|
// 46.28 //e cycles for 22.05kHz sample rate
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
// AppleWin NOTE : use integer value: Better for MJ Mahon's RT.SYNTH.DSK (integer multiples of 1.023MHz Clk)
|
|
|
|
cycles_per_sample = (unsigned int)(cycles_persec_target / (double)SPKR_SAMPLE_RATE);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
unsigned int last_remainder_buffer_size = remainder_buffer_size;
|
|
|
|
remainder_buffer_size = (unsigned int)cycles_per_sample;
|
|
|
|
if ((double)remainder_buffer_size != cycles_per_sample) {
|
|
|
|
++remainder_buffer_size;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
2015-01-31 21:57:10 +00:00
|
|
|
assert(remainder_buffer_size <= MAX_REMAINDER_BUFFER);
|
|
|
|
|
|
|
|
if (last_remainder_buffer_size == remainder_buffer_size) {
|
|
|
|
// no change ... insure seamless remainder_buffer
|
|
|
|
} else {
|
|
|
|
SPEAKER_LOG("changing remainder buffer size");
|
|
|
|
remainder_buffer_idx = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
2015-01-31 21:57:10 +00:00
|
|
|
if (cycles_last_update > cycles_count_total) {
|
|
|
|
SPEAKER_LOG("resetting speaker cycles counter");
|
|
|
|
cycles_last_update = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (is_fullspeed) {
|
|
|
|
remainder_buffer_idx = 0;
|
|
|
|
samples_buffer_idx = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
/*
|
|
|
|
* Adds to the samples_buffer the number of samples since the last invocation of this function.
|
|
|
|
*
|
|
|
|
* Speaker output square wave example:
|
|
|
|
* _______ ____ _____________________! . +speaker_amplitude
|
|
|
|
* silence _ .
|
|
|
|
* threshold _ .
|
|
|
|
* _ remainder _ .
|
|
|
|
* average _ .
|
|
|
|
* _______ ____________ ______ ___ . 0
|
|
|
|
*
|
|
|
|
* - When the speaker is accessed by the emulated program, the output (speaker_data) is toggled between the
|
|
|
|
* positive amplitude or zero
|
|
|
|
* - Evenly-divisible samples since last function call (cycles_diff/cycles_per_sample) are put directly into the
|
|
|
|
* samples_buffer for output to audio system backend
|
|
|
|
* - (+) Fractional samples are put into a remainder_buffer to be averaged and then transfered to the sample_buffer
|
|
|
|
* when there is enough data for 1 whole sample (possibly on a subsequent invocation of this function)
|
|
|
|
* - (+) If the speaker has not been toggled with output at +amplitude for a certain number of machine cycles, we
|
|
|
|
* gradually step the samples down to the zero bound of true quiet. (This is done to avoid glitching when
|
|
|
|
* pausing/unpausing emulation for GUI/menus and auto-switching between full and configured speeds)
|
|
|
|
*/
|
|
|
|
static void _speaker_update(/*bool toggled*/) {
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (!is_fullspeed) {
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
unsigned long long cycles_diff = cycles_count_total - cycles_last_update;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (remainder_buffer_idx) {
|
|
|
|
// top-off previous previous fractional remainder cycles
|
|
|
|
while (cycles_diff && (remainder_buffer_idx < remainder_buffer_size)) {
|
|
|
|
remainder_buffer[remainder_buffer_idx] = speaker_data;
|
|
|
|
++remainder_buffer_idx;
|
|
|
|
--cycles_diff;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (remainder_buffer_idx == remainder_buffer_size) {
|
|
|
|
// now have a complete extra sample from (previous + now) ... calculate mean value
|
|
|
|
remainder_buffer_idx = 0;
|
|
|
|
int sample_mean = 0;
|
|
|
|
for (unsigned int i=0; i<remainder_buffer_size; i++) {
|
|
|
|
sample_mean += (int)remainder_buffer[i];
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
sample_mean /= (int)remainder_buffer_size;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (samples_buffer_idx < SPKR_SAMPLE_RATE) {
|
|
|
|
samples_buffer[samples_buffer_idx++] = (int16_t)sample_mean;
|
|
|
|
}
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
const unsigned long long samples_count = (unsigned long long)((double)cycles_diff / cycles_per_sample);
|
|
|
|
unsigned long long num_samples = samples_count;
|
|
|
|
const unsigned long long cycles_remainder = (unsigned long long)((double)cycles_diff - (double)num_samples * cycles_per_sample);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
// populate samples_buffer with whole samples
|
|
|
|
while (num_samples && (samples_buffer_idx < SPKR_SAMPLE_RATE)) {
|
|
|
|
samples_buffer[samples_buffer_idx++] = speaker_data;
|
|
|
|
if (speaker_going_silent && speaker_data) {
|
|
|
|
speaker_data -= speaker_silent_step;
|
|
|
|
}
|
|
|
|
--num_samples;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (cycles_remainder > 0) {
|
|
|
|
// populate remainder_buffer with fractional samples
|
|
|
|
assert(remainder_buffer_idx == 0 && "should have already dealt with remainder buffer");
|
|
|
|
assert(cycles_remainder < remainder_buffer_size && "otherwise there should have been another whole sample");
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
while (remainder_buffer_idx<cycles_remainder) {
|
|
|
|
remainder_buffer[remainder_buffer_idx] = speaker_data;
|
|
|
|
++remainder_buffer_idx;
|
|
|
|
}
|
|
|
|
#if 0
|
|
|
|
} else if (toggled && samples_count) {
|
|
|
|
samples_buffer[samples_buffer_idx-1] = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
#endif
|
2015-01-31 21:57:10 +00:00
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (UNLIKELY(samples_buffer_idx >= SPKR_SAMPLE_RATE)) {
|
|
|
|
assert(samples_buffer_idx == SPKR_SAMPLE_RATE && "should be at exactly the end, no further");
|
|
|
|
ERRLOG("OOPS, overflow in speaker samples buffer");
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
cycles_last_update = cycles_count_total;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
/*
|
|
|
|
* Submits "quiet" samples to the audio system backend when CPU thread is running fullspeed, to keep the audio streaming
|
|
|
|
* topped up.
|
|
|
|
*
|
|
|
|
* 20150131 NOTE : it seems that there are still cases where we glitch OpenAL (seemingly on transitioning back to
|
|
|
|
* regular speed sample submissions). I have not been able to fully isolate the cause, but this has been minimized by
|
|
|
|
* always trending the samples to the zero value when there has been a sufficient amount of speaker silence.
|
|
|
|
*/
|
|
|
|
static void _submit_samples_buffer_fullspeed(void) {
|
|
|
|
samples_adjustment_counter = 0;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (!SpeakerVoice.bActive) {
|
|
|
|
return;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
unsigned long bytes_queued = 0;
|
|
|
|
long err = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &bytes_queued, NULL);
|
|
|
|
if (err) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
assert(bytes_queued <= SOUNDCORE_BUFFER_SIZE);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (bytes_queued >= IDEAL_MAX) {
|
|
|
|
return;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
unsigned int num_samples_pad = (IDEAL_MAX - bytes_queued) / sizeof(int16_t);
|
|
|
|
if (num_samples_pad == 0) {
|
|
|
|
return;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
unsigned long system_buffer_size = 0;
|
|
|
|
int16_t *system_samples_buffer = NULL;
|
|
|
|
if (!DSGetLock(SpeakerVoice.lpDSBvoice, /*unused*/ 0, num_samples_pad*sizeof(int16_t), &system_samples_buffer, &system_buffer_size, NULL, NULL)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
assert(num_samples_pad*sizeof(int16_t) <= system_buffer_size);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
//SPEAKER_LOG("bytes_queued:%d enqueueing %d quiet samples", bytes_queued, num_samples_pad);
|
|
|
|
for (unsigned int i=0; i<num_samples_pad; i++) {
|
|
|
|
system_samples_buffer[i] = speaker_data;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
SpeakerVoice.lpDSBvoice->Unlock(SpeakerVoice.lpDSBvoice->_this, (void*)system_samples_buffer, system_buffer_size, NULL, 0);
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
// Submits samples from the samples_buffer to the audio system backend when running at a normal scaled-speed. This also
|
|
|
|
// generates cycles feedback to the main CPU timing routine depending on the needs of the streaming audio (more or less
|
|
|
|
// data).
|
|
|
|
static unsigned int _submit_samples_buffer(const unsigned int num_samples) {
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
assert(num_samples);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (!SpeakerVoice.bActive) {
|
|
|
|
return num_samples;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
unsigned long bytes_queued = 0;
|
|
|
|
long err = SpeakerVoice.lpDSBvoice->GetCurrentPosition(SpeakerVoice.lpDSBvoice->_this, &bytes_queued, NULL);
|
|
|
|
if (err) {
|
|
|
|
return num_samples;
|
|
|
|
}
|
|
|
|
assert(bytes_queued <= SOUNDCORE_BUFFER_SIZE);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
//
|
|
|
|
// calculate CPU cycles feedback adjustment to prevent system audio buffer under/overflow
|
|
|
|
//
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
const int error_increment = SoundCore_GetErrorInc();
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (bytes_queued < IDEAL_MIN) {
|
|
|
|
samples_adjustment_counter += error_increment; // need moar data
|
|
|
|
} else if (bytes_queued > IDEAL_MAX) {
|
|
|
|
samples_adjustment_counter -= error_increment; // need less data
|
|
|
|
} else {
|
|
|
|
samples_adjustment_counter = 0; // Acceptable amount of data in buffer
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
const int samples_adjustment_max = SoundCore_GetErrorMax(); // capped to a max/min
|
|
|
|
if (samples_adjustment_counter < -samples_adjustment_max) {
|
|
|
|
samples_adjustment_counter = -samples_adjustment_max;
|
|
|
|
} else if (samples_adjustment_counter > samples_adjustment_max) {
|
|
|
|
samples_adjustment_counter = samples_adjustment_max;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
cycles_speaker_feedback = (int)(samples_adjustment_counter * cycles_per_sample);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
//SPEAKER_LOG("feedback:%d samples_adjustment_counter:%d", cycles_speaker_feedback, samples_adjustment_counter);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
//
|
|
|
|
// copy samples to audio system backend
|
|
|
|
//
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
const unsigned int bytes_free = SOUNDCORE_BUFFER_SIZE - bytes_queued;
|
|
|
|
unsigned int num_samples_to_use = num_samples;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (num_samples_to_use * sizeof(int16_t) > bytes_free) {
|
|
|
|
num_samples_to_use = bytes_free / sizeof(int16_t);
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (num_samples_to_use) {
|
|
|
|
unsigned long system_buffer_size = 0;
|
|
|
|
int16_t *system_samples_buffer = NULL;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (!DSGetLock(SpeakerVoice.lpDSBvoice, /*unused*/0, (unsigned long)num_samples_to_use*sizeof(int16_t), &system_samples_buffer, &system_buffer_size, NULL, NULL)) {
|
|
|
|
return num_samples;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
memcpy(system_samples_buffer, &samples_buffer[0], system_buffer_size);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
err = SpeakerVoice.lpDSBvoice->Unlock(SpeakerVoice.lpDSBvoice->_this, (void*)system_samples_buffer, system_buffer_size, NULL, 0);
|
|
|
|
if (err) {
|
|
|
|
return num_samples;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
return num_samples_to_use;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
// speaker public API functions
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
void speaker_destroy(void) {
|
|
|
|
if (SpeakerVoice.lpDSBvoice && SpeakerVoice.bActive) {
|
|
|
|
SpeakerVoice.lpDSBvoice->Stop(SpeakerVoice.lpDSBvoice->_this);
|
|
|
|
SpeakerVoice.bActive = false;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
2015-01-31 21:57:10 +00:00
|
|
|
DSReleaseSoundBuffer(&SpeakerVoice);
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
void speaker_init(void) {
|
|
|
|
SpeakerVoice.bActive = true;
|
2015-06-13 05:47:23 +00:00
|
|
|
long err = DSGetSoundBuffer(&SpeakerVoice, 0, SOUNDCORE_BUFFER_SIZE, SPKR_SAMPLE_RATE, 1);
|
2015-01-31 21:57:10 +00:00
|
|
|
assert(!err);
|
|
|
|
_speaker_init_timing();
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
void speaker_reset(void) {
|
|
|
|
_speaker_init_timing();
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
void speaker_flush(void) {
|
|
|
|
assert(pthread_self() == cpu_thread_id);
|
|
|
|
|
|
|
|
if (is_fullspeed) {
|
|
|
|
cycles_quiet_time = cycles_count_total;
|
|
|
|
speaker_going_silent = false;
|
|
|
|
speaker_accessed_since_last_flush = false;
|
|
|
|
} else {
|
|
|
|
if (speaker_accessed_since_last_flush) {
|
|
|
|
cycles_quiet_time = 0;
|
|
|
|
speaker_going_silent = false;
|
|
|
|
speaker_accessed_since_last_flush = false;
|
|
|
|
} else {
|
|
|
|
if (!cycles_quiet_time) {
|
|
|
|
cycles_quiet_time = cycles_count_total;
|
|
|
|
}
|
|
|
|
const unsigned int cycles_diff = (unsigned int)(cycles_persec_target/10); // 0.1sec of cycles
|
|
|
|
if ((cycles_count_total - cycles_quiet_time) > cycles_diff*2) {
|
|
|
|
// After 0.2sec of //e cycles time set inactive flag (allows auto-switch to full speed for fast disk access)
|
|
|
|
speaker_recently_active = false;
|
|
|
|
speaker_going_silent = false;
|
|
|
|
speaker_data = 0;
|
|
|
|
} else if ((speaker_data == speaker_amplitude) && (cycles_count_total - cycles_quiet_time > cycles_diff)) {
|
|
|
|
// After 0.1sec of //e cycles time start reducing samples to zero (if they aren't there already). This
|
|
|
|
// process attempts to mask the extraneous clicks when freezing/restarting emulation (GUI access) and
|
|
|
|
// glitching from the audio system backend
|
|
|
|
speaker_going_silent = true;
|
|
|
|
speaker_silent_step = 1;
|
|
|
|
SPEAKER_LOG("speaker going silent");
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
2015-01-31 21:57:10 +00:00
|
|
|
}
|
|
|
|
_speaker_update(/*toggled:false*/);
|
|
|
|
|
|
|
|
unsigned int samples_used = 0;
|
|
|
|
if (is_fullspeed) {
|
|
|
|
assert(!samples_buffer_idx && "should be all quiet samples");
|
|
|
|
_submit_samples_buffer_fullspeed();
|
|
|
|
} else if (samples_buffer_idx) {
|
|
|
|
samples_used = _submit_samples_buffer(samples_buffer_idx);
|
|
|
|
}
|
|
|
|
assert(samples_used <= samples_buffer_idx);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (samples_used) {
|
|
|
|
memmove(samples_buffer, &samples_buffer[samples_used], samples_buffer_idx-samples_used);
|
|
|
|
samples_buffer_idx -= samples_used;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
bool speaker_is_active(void) {
|
|
|
|
return SpeakerVoice.bActive && speaker_recently_active;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
void speaker_set_volume(int16_t amplitude) {
|
|
|
|
speaker_amplitude = (amplitude/4);
|
2013-11-17 19:20:26 +00:00
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
double speaker_cycles_per_sample(void) {
|
|
|
|
return cycles_per_sample;
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
// VM system entry point
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
GLUE_C_READ(speaker_toggle)
|
2013-10-06 06:22:08 +00:00
|
|
|
{
|
2015-01-31 21:57:10 +00:00
|
|
|
assert(pthread_self() == cpu_thread_id);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
timing_checkpoint_cycles();
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
#if DIRECT_SPEAKER_ACCESS
|
|
|
|
#error this used to be implemented ...
|
|
|
|
// AFAIK ... this requires an actual speaker device and ability to access it (usually requiring this program to be
|
|
|
|
// running setuid-operator or (gasp) setuid-root ... maybe
|
2013-10-23 04:22:43 +00:00
|
|
|
#else
|
2015-01-31 21:57:10 +00:00
|
|
|
speaker_accessed_since_last_flush = true;
|
|
|
|
speaker_recently_active = true;
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (timing_should_auto_adjust_speed()) {
|
|
|
|
is_fullspeed = false;
|
|
|
|
}
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
_speaker_update(/*toggled:true*/);
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
if (!is_fullspeed) {
|
|
|
|
if (speaker_data) {
|
|
|
|
speaker_data = 0;
|
|
|
|
} else {
|
|
|
|
speaker_data = speaker_amplitude;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2013-10-06 06:22:08 +00:00
|
|
|
|
2015-01-31 21:57:10 +00:00
|
|
|
return floating_bus();
|
2013-10-06 06:22:08 +00:00
|
|
|
}
|
|
|
|
|