mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-11-29 16:49:26 +00:00
233 lines
6.9 KiB
C++
233 lines
6.9 KiB
C++
#include "sdl-speaker.h"
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h> // for open()
|
|
|
|
extern "C"
|
|
{
|
|
#include <SDL.h>
|
|
#include <SDL_thread.h>
|
|
};
|
|
|
|
// What values do we use for logical speaker-high and speaker-low?
|
|
#define HIGHVAL (0x4FFF)
|
|
#define LOWVAL (-(0x4FFF))
|
|
|
|
#include "globals.h"
|
|
|
|
#define SDLSIZE (2048)
|
|
// But we want to keep more than just that, so we can fill it full every time
|
|
#define CACHEMULTIPLIER 2
|
|
|
|
#define WATERLEVEL SDLSIZE
|
|
|
|
#define AUDIO_SAMPLE_RATE_EXACT 44100
|
|
|
|
// FIXME: Globals; ick.
|
|
static volatile uint32_t bufIdx = 0;
|
|
static volatile short soundBuf[CACHEMULTIPLIER*SDLSIZE];
|
|
static pthread_mutex_t togmutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static volatile uint64_t skippedSamples = 0;
|
|
#define SAMPLEBYTES sizeof(short)
|
|
|
|
volatile uint8_t audioRunning = 0;
|
|
volatile int64_t lastFilledTime = 0;
|
|
|
|
|
|
// Debugging by writing a wav file with the sound output...
|
|
//#define DEBUG_OUT_WAV
|
|
#ifdef DEBUG_OUT_WAV
|
|
int outputFD = -1;
|
|
#endif
|
|
|
|
static void audioCallback(void *unused, Uint8 *stream, int len)
|
|
{
|
|
if (audioRunning==0)
|
|
audioRunning=1;
|
|
pthread_mutex_lock(&togmutex);
|
|
if (g_biosInterrupt) {
|
|
// While the BIOS is running, we don't put samples in the audio
|
|
// queue.
|
|
audioRunning = 0;
|
|
memset(stream, 0, SDLSIZE*SAMPLEBYTES);
|
|
pthread_mutex_unlock(&togmutex);
|
|
return;
|
|
}
|
|
|
|
if (audioRunning==1 && bufIdx >= WATERLEVEL) {
|
|
// Fully up and running now; we got a full cache
|
|
audioRunning = 2;
|
|
} else if (audioRunning==1) {
|
|
// waiting for first fill; return an empty buffer.
|
|
memset(stream, 0, SDLSIZE*SAMPLEBYTES);
|
|
return;
|
|
}
|
|
|
|
static short lastKnownSample = 0; // saved for when the apple is quiescent
|
|
|
|
if (bufIdx >= SDLSIZE) { // technically 'len/SAMPLEBYTES' but it should always be constant I think?
|
|
memcpy(stream, (void *)soundBuf, SDLSIZE*SAMPLEBYTES);
|
|
lastKnownSample = stream[SDLSIZE-1];
|
|
|
|
if (bufIdx > SDLSIZE) {
|
|
// move the remaining data down
|
|
memcpy((void *)soundBuf, (void *)&soundBuf[SDLSIZE], (bufIdx - SDLSIZE + 1)*SAMPLEBYTES);
|
|
bufIdx -= SDLSIZE;
|
|
}
|
|
} else {
|
|
if (bufIdx) {
|
|
// partial buffer exists
|
|
memcpy(stream, (void *)soundBuf, bufIdx*SAMPLEBYTES);
|
|
// and it's a partial underrun. Track the number of samples we skipped
|
|
// so we can keep the audio buffer in sync.
|
|
skippedSamples += SDLSIZE-bufIdx;
|
|
for (long i=0; i<SDLSIZE-bufIdx; i++) {
|
|
stream[bufIdx+i] = lastKnownSample;
|
|
}
|
|
bufIdx = 0;
|
|
} else {
|
|
// No big deal - buffer underrun might just mean nothing
|
|
// is trying to play audio right now.
|
|
skippedSamples += SDLSIZE;
|
|
|
|
memset(stream, 0, SDLSIZE*SAMPLEBYTES);
|
|
// memset(stream, lastKnownSample, SDLSIZE);
|
|
// Trend toward DC voltage = 0v
|
|
// if (lastKnownSample < 0x7F) lastKnownSample++;
|
|
// if (lastKnownSample >= 0x80) lastKnownSample--;
|
|
}
|
|
}
|
|
#ifdef DEBUG_OUT_WAV
|
|
if (outputFD == -1) {
|
|
outputFD = open("/tmp/out.wav", O_RDWR | O_CREAT | O_TRUNC, 0600);
|
|
unsigned char buf[44] = { 'R', 'I', 'F', 'F',
|
|
0xff,0xff,0xff,0, // size == 0 for now
|
|
'W', 'A', 'V', 'E',
|
|
'f', 'm', 't', ' ',
|
|
16,0,0,0, // no extensions
|
|
1,0, // PCM
|
|
1,0, // 1 channel
|
|
0x44, 0xAC, 0, 0, // 44100 Hz
|
|
0x44, 0xAC, 0, 0, // (sample rate * bits * channels)/8
|
|
1,0, // sample size (1 byte here b/c 1 channel @ 8bit)
|
|
8,0, // bits per sample
|
|
'd', 'a', 't', 'a',
|
|
0xff, 0xff, 0xff, 0, // size of data chunk
|
|
};
|
|
write(outputFD, buf, sizeof(buf));
|
|
}
|
|
|
|
write(outputFD, (void *)(stream), SDLSIZE*SAMPLEBYTES);
|
|
#endif
|
|
|
|
pthread_mutex_unlock(&togmutex);
|
|
}
|
|
|
|
SDLSpeaker::SDLSpeaker()
|
|
{
|
|
toggleState = false;
|
|
mixerValue = 0x80;
|
|
|
|
pthread_mutex_init(&togmutex, NULL);
|
|
}
|
|
|
|
SDLSpeaker::~SDLSpeaker()
|
|
{
|
|
}
|
|
|
|
void SDLSpeaker::begin()
|
|
{
|
|
SDL_AudioSpec audioDevice;
|
|
SDL_AudioSpec audioActual;
|
|
SDL_memset(&audioDevice, 0, sizeof(audioDevice));
|
|
audioDevice.freq = AUDIO_SAMPLE_RATE_EXACT; // count of 16-bit samples
|
|
audioDevice.format = AUDIO_S16;
|
|
audioDevice.channels = 1;
|
|
audioDevice.samples = SDLSIZE; // SDLSIZE 16-bit samples @ 44100Hz: 4096 is about 1/10th second out of sync
|
|
audioDevice.callback = audioCallback;
|
|
audioDevice.userdata = NULL;
|
|
|
|
memset((void *)&soundBuf[0], 0, CACHEMULTIPLIER*SDLSIZE*SAMPLEBYTES);
|
|
bufIdx = 0;
|
|
skippedSamples = 0;
|
|
audioRunning = 0;
|
|
|
|
SDL_OpenAudio(&audioDevice, &audioActual); // FIXME retval
|
|
printf("Actual: freq %d channels %d samples %d\n",
|
|
audioActual.freq, audioActual.channels, audioActual.samples);
|
|
// FIXME: if any of those don't match the orginal we're gonna be unhappy
|
|
SDL_PauseAudio(0);
|
|
}
|
|
|
|
void SDLSpeaker::toggle(int64_t c)
|
|
{
|
|
pthread_mutex_lock(&togmutex);
|
|
|
|
int64_t expectedCycleNumber = (float)c * (float)AUDIO_SAMPLE_RATE_EXACT / (float)g_speed;
|
|
if (lastFilledTime == 0) {
|
|
lastFilledTime = expectedCycleNumber;
|
|
}
|
|
// This subtracts skippedSamples because those were filled automatically
|
|
// by the audioCallback when we had no data.
|
|
int64_t audioBufferSamples = expectedCycleNumber - lastFilledTime - skippedSamples;
|
|
// If audioBufferSamples < 0, then we need to keep some
|
|
// skippedSamples for later; otherwise we can keep moving forward.
|
|
if (audioBufferSamples < 0) {
|
|
skippedSamples = -audioBufferSamples;
|
|
audioBufferSamples = 0;
|
|
} else {
|
|
// Otherwise we consumed them and can forget about it.
|
|
skippedSamples = 0;
|
|
}
|
|
|
|
int32_t newIdx = bufIdx + audioBufferSamples;
|
|
|
|
if (audioBufferSamples == 0) {
|
|
// If the toggle wouldn't result in at least 1 buffer sample change,
|
|
// then we'll blatantly skip it here. If this turns out to be
|
|
// a problem, we could try setting audioBufferSamples++ and then
|
|
// twiddle the lastFilledTime so it looks like it's more in the
|
|
// future, but I suspect that would mean missing more future events,
|
|
// just like we would have missed this one.
|
|
//
|
|
// But I think this is probably okay - because something that's
|
|
// toggling the speaker fast enough that our 44k audio can't keep
|
|
// up with the individual changes is likely to toggle again in a
|
|
// moment without significant distortion?
|
|
pthread_mutex_unlock(&togmutex);
|
|
return;
|
|
}
|
|
|
|
if (newIdx >= sizeof(soundBuf)/SAMPLEBYTES) {
|
|
printf("ERROR: buffer overrun: size %lu idx %d\n", sizeof(soundBuf)/SAMPLEBYTES, newIdx);
|
|
newIdx = (sizeof(soundBuf)/SAMPLEBYTES)-1;
|
|
}
|
|
lastFilledTime = expectedCycleNumber;
|
|
|
|
// Flip the toggle state
|
|
toggleState = !toggleState;
|
|
|
|
// Fill from bufIdx .. newIdx and set bufIdx to newIdx when done.
|
|
if (newIdx > bufIdx) {
|
|
long count = (long)newIdx - bufIdx;
|
|
for (long i=0; i<count; i++) {
|
|
soundBuf[bufIdx+i] = toggleState ? HIGHVAL : LOWVAL;
|
|
}
|
|
bufIdx = newIdx;
|
|
}
|
|
|
|
pthread_mutex_unlock(&togmutex);
|
|
}
|
|
|
|
void SDLSpeaker::maintainSpeaker(int64_t c, uint64_t microseconds)
|
|
{
|
|
}
|
|
|
|
void SDLSpeaker::beginMixing()
|
|
{
|
|
}
|
|
|
|
void SDLSpeaker::mixOutput(uint8_t v)
|
|
{
|
|
}
|