It Talks!

...though not well (sounds like it's chewing gum, plus there are nasty
delays in gameplay).


git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@1113 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
urchlay 2006-06-11 22:43:55 +00:00
parent 758a4ed3ff
commit 228e7c1a5d
6 changed files with 276 additions and 21 deletions

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: SoundSDL.cxx,v 1.30 2006-06-09 02:45:11 urchlay Exp $
// $Id: SoundSDL.cxx,v 1.31 2006-06-11 22:43:55 urchlay Exp $
//============================================================================
#ifdef SOUND_SUPPORT
@ -158,6 +158,7 @@ void SoundSDL::initialize()
<< " Volume : " << myVolume << endl
<< " Frag size : " << fragsize << endl
<< " Frequency : " << myHardwareSpec.freq << endl
<< " Format : " << myHardwareSpec.format << endl
<< " TIA Freq. : " << tiafreq << endl
<< " Channels : " << myNumChannels << endl
<< " Clip volume: " << (int)clipvol << endl << endl;
@ -407,6 +408,24 @@ void SoundSDL::callback(void* udata, uInt8* stream, int len)
{
SoundSDL* sound = (SoundSDL*)udata;
sound->processFragment(stream, (Int32)len);
cerr << "SoundSDL::callback(): len==" << len << endl;
// See if we need sound from the AtariVox
AtariVox *vox = sound->myOSystem->console().atariVox();
if(vox)
{
// If so, mix 'em together (this is a crappy way to mix audio streams...)
uInt8 *s = stream;
for(int i=0; i<len/OUTPUT_BUFFER_SIZE; i++)
{
int count;
uInt8 *voxSamples = vox->getSpeakJet()->getSamples(&count);
if(!count)
break;
SDL_MixAudio(s, voxSamples, OUTPUT_BUFFER_SIZE, SDL_MIX_MAXVOLUME);
s += OUTPUT_BUFFER_SIZE;
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -14,7 +14,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: AtariVox.hxx,v 1.2 2006-06-11 07:13:19 urchlay Exp $
// $Id: AtariVox.hxx,v 1.3 2006-06-11 22:43:55 urchlay Exp $
//============================================================================
#ifndef ATARIVOX_HXX
@ -33,7 +33,7 @@
driver code.
@author B. Watson
@version $Id: AtariVox.hxx,v 1.2 2006-06-11 07:13:19 urchlay Exp $
@version $Id: AtariVox.hxx,v 1.3 2006-06-11 22:43:55 urchlay Exp $
*/
class AtariVox : public Controller
{
@ -83,6 +83,8 @@ class AtariVox : public Controller
void setSystem(System *system);
SpeakJet* getSpeakJet() { return mySpeakJet; }
private:
void clockDataIn(bool value);
void shiftIn(bool value);

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: Console.cxx,v 1.91 2006-06-11 07:13:19 urchlay Exp $
// $Id: Console.cxx,v 1.92 2006-06-11 22:43:55 urchlay Exp $
//============================================================================
#include <assert.h>
@ -141,7 +141,7 @@ Console::Console(const uInt8* image, uInt32 size, const string& md5,
myControllers[0] = new Joystick(leftjack, *myEvent);
}
AtariVox *vox = 0;
vox = 0;
// Construct right controller
if(right == "BOOSTER-GRIP")

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: Console.hxx,v 1.43 2006-03-25 00:34:17 stephena Exp $
// $Id: Console.hxx,v 1.44 2006-06-11 22:43:55 urchlay Exp $
//============================================================================
#ifndef CONSOLE_HXX
@ -32,12 +32,13 @@ class System;
#include "TIA.hxx"
#include "Cart.hxx"
#include "M6532.hxx"
#include "AtariVox.hxx"
/**
This class represents the entire game console.
@author Bradford W. Mott
@version $Id: Console.hxx,v 1.43 2006-03-25 00:34:17 stephena Exp $
@version $Id: Console.hxx,v 1.44 2006-06-11 22:43:55 urchlay Exp $
*/
class Console
{
@ -235,6 +236,8 @@ class Console
void togglePFBit() { toggleTIABit(TIA::PF, "PF"); }
void enableBits(bool enable);
AtariVox *atariVox() { return vox; }
private:
void toggleTIABit(TIA::TIABit bit, const string& bitname, bool show = true);
void setDeveloperProperties();
@ -268,6 +271,8 @@ class Console
// A RIOT of my own! (...with apologies to The Clash...)
M6532 *myRiot;
AtariVox *vox;
// Indicates whether the console was correctly initialized
// We don't really care why it wasn't initialized ...
bool myIsInitializedFlag;

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: SpeakJet.cxx,v 1.2 2006-06-11 21:49:04 stephena Exp $
// $Id: SpeakJet.cxx,v 1.3 2006-06-11 22:43:55 urchlay Exp $
//============================================================================
#include "SpeakJet.hxx"
@ -21,6 +21,36 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SpeakJet::SpeakJet()
{
// Initialize output buffers. Each one points to the next element,
// except the last, which points back to the first.
SpeechBuffer *first = &outputBuffers[0];
SpeechBuffer *last = 0;
for(int i=0; i<SPEECH_BUFFERS; i++) {
SpeechBuffer *sb = &outputBuffers[i];
sb->items = 0;
sb->lock = SDL_CreateSemaphore(1);
if(last) {
last->next = sb;
}
last = sb;
}
last->next = first;
myCurrentOutputBuffer = ourCurrentWriteBuffer = first;
ourCurrentWritePosition = 0;
// Init rsynth library
darray_init(&rsynthSamples, sizeof(short), 2048);
/*
rsynth = rsynth_init(samp_rate, mSec_per_frame,
rsynth_speaker(F0Hz, gain, Elements),
save_sample, flush_samples, &samples);
*/
rsynth = rsynth_init(31400, 10.0,
rsynth_speaker(133.0, 57, Elements),
save_sample, flush_samples, &rsynthSamples);
spawnThread();
}
@ -32,21 +62,86 @@ SpeakJet::~SpeakJet()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SpeakJet::spawnThread()
{
ourInputSemaphore = SDL_CreateSemaphore(1); // 1==unlocked
uInt32 sem = SDL_SemValue(ourInputSemaphore);
cerr << "before SDL_CreateThread(), sem==" << sem << endl;
ourThread = SDL_CreateThread(thread, 0);
ourInputSemaphore = SDL_CreateSemaphore(1);
sem = SDL_SemValue(ourInputSemaphore);
cerr << "after SDL_CreateThread(), sem==" << sem << endl;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int SpeakJet::thread(void *data) {
cerr << "rsynth thread spawned" << endl;
while(1) {
speak();
usleep(10);
}
return 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SpeakJet::write(uInt8 code)
{
// TODO: clean up this mess.
const char *rsynthPhones = xlatePhoneme(code);
cerr << "rsynth: \"" << rsynthPhones << "\"" << endl;
int len = strlen(rsynthPhones);
if(ourInputCount + len + 1 >= INPUT_BUFFER_SIZE) {
cerr << "phonemeBuffer is full, dropping" << endl;
return;
}
uInt32 sem = SDL_SemValue(ourInputSemaphore);
cerr << "write() waiting on semaphore (value " << sem << ")" << endl;
SDL_SemWait(ourInputSemaphore);
cerr << "write() got semaphore" << endl;
for(int i=0; i<len; i++)
phonemeBuffer[ourInputCount++] = rsynthPhones[i];
phonemeBuffer[ourInputCount] = '\0';
cerr << "phonemeBuffer contains \"" << phonemeBuffer << "\"" << endl;
cerr << "write() releasing semaphore" << endl;
SDL_SemPost(ourInputSemaphore);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SpeakJet::speak()
{
// TODO: clean up this mess.
static char myInput[INPUT_BUFFER_SIZE];
if(!ourInputCount)
return;
uInt32 sem = SDL_SemValue(ourInputSemaphore);
cerr << "speak() waiting on semaphore (value " << sem << ")" << endl;
SDL_SemWait(ourInputSemaphore);
cerr << "speak() got semaphore" << endl;
// begin locked section
bool foundSpace = false;
for(int i=0; i<ourInputCount; i++)
if( (myInput[i] = phonemeBuffer[i]) == ' ')
foundSpace = true;
if(ourInputCount >= INPUT_BUFFER_SIZE - 5)
foundSpace = true;
if(foundSpace)
ourInputCount = 0;
// end locked section
cerr << "speak() releasing semaphore" << endl;
SDL_SemPost(ourInputSemaphore);
if(foundSpace)
{
// Lock current buffer. save_sample will unlock it when it gets full.
SDL_SemWait(ourCurrentWriteBuffer->lock);
rsynth_phones(rsynth, myInput, strlen(myInput));
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -63,8 +158,16 @@ const char *SpeakJet::xlatePhoneme(uInt8 code)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 *SpeakJet::getSamples(int *count) {
*count = 0;
return 0;
static uInt8 contents[OUTPUT_BUFFER_SIZE];
SDL_sem *lock = myCurrentOutputBuffer->lock;
SDL_SemWait(lock);
*count = myCurrentOutputBuffer->items;
for(int i=0; i<*count; i++)
contents[i] = myCurrentOutputBuffer->contents[i];
myCurrentOutputBuffer->items = 0;
myCurrentOutputBuffer = myCurrentOutputBuffer->next;
SDL_SemPost(lock);
return contents;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -73,6 +176,82 @@ bool SpeakJet::chipReady()
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void *SpeakJet::save_sample(void *user_data,
float sample,
unsigned nsamp,
rsynth_t *rsynth)
{
static long clip_max;
static float peak;
short shortSamp;
uInt8 output;
darray_t *buf = (darray_t *) user_data;
shortSamp = clip(&clip_max, sample, &peak);
darray_short(buf, shortSamp);
// Convert to 8-bit
// output = (uInt8)( (((float)shortSamp) + 32768.0) / 256.0 );
double d = shortSamp + 32768.0;
output = (uInt8)(d/256.0);
cerr << "Output sample: " << ((int)(output)) << endl;
// Put in buffer
ourCurrentWriteBuffer->contents[ourCurrentWritePosition++] = output;
ourCurrentWriteBuffer->items = ourCurrentWritePosition;
// If buffer is full, unlock it and use the next one.
if(ourCurrentWritePosition == OUTPUT_BUFFER_SIZE)
{
SDL_SemWait(ourCurrentWriteBuffer->next->lock);
SDL_SemPost(ourCurrentWriteBuffer->lock);
ourCurrentWriteBuffer = ourCurrentWriteBuffer->next;
ourCurrentWriteBuffer->items = ourCurrentWritePosition = 0;
}
return (void *) buf;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void *SpeakJet::flush_samples(void *user_data,
unsigned nsamp,
rsynth_t *rsynth)
{
darray_t *buf = (darray_t *) user_data;
buf->items = 0;
for (;ourCurrentWritePosition < OUTPUT_BUFFER_SIZE; ourCurrentWritePosition++)
ourCurrentWriteBuffer->contents[ourCurrentWritePosition] = 0;
ourCurrentWritePosition = 0;
SDL_SemPost(ourCurrentWriteBuffer->lock);
ourCurrentWriteBuffer = ourCurrentWriteBuffer->next; // NOT locked
return (void *) buf;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
short SpeakJet::clip(long *clip_max, float input, float *peak)
{
long temp = (long) input;
float isq = input * input;
#ifdef PEAK
if (isq > *peak)
*peak = isq;
#else
*peak += isq;
#endif
if (-temp > *clip_max)
*clip_max = -temp;
if (temp > *clip_max)
*clip_max = temp;
if (temp < -32767) {
temp = -32767;
}
else if (temp > 32767) {
temp = 32767;
}
return (temp);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/*

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: SpeakJet.hxx,v 1.2 2006-06-11 21:49:04 stephena Exp $
// $Id: SpeakJet.hxx,v 1.3 2006-06-11 22:43:55 urchlay Exp $
//============================================================================
#ifndef SPEAKJET_HXX
@ -75,7 +75,7 @@
anyway).
@author B. Watson
@version $Id: SpeakJet.hxx,v 1.2 2006-06-11 21:49:04 stephena Exp $
@version $Id: SpeakJet.hxx,v 1.3 2006-06-11 22:43:55 urchlay Exp $
*/
#include "bspf.hxx"
@ -84,6 +84,22 @@
#include <SDL_thread.h>
#include "rsynth/rsynth.h"
class SpeechBuffer;
enum { INPUT_BUFFER_SIZE = 128 };
enum { OUTPUT_BUFFER_SIZE = 128 };
enum { SPEECH_BUFFERS = 1024 };
static SDL_sem *ourInputSemaphore;
static rsynth_t *rsynth;
static darray_t rsynthSamples;
// phonemeBuffer holds *translated* phonemes (e.g. rsynth phonemes,
// not SpeakJet phonemes).
static char phonemeBuffer[INPUT_BUFFER_SIZE];
// How many bytes are in the input buffer?
static uInt16 ourInputCount;
class SpeakJet
{
public:
@ -126,28 +142,43 @@ class SpeakJet
*/
bool chipReady();
private:
// function that spawns the rsynth thread
void spawnThread();
// function that the rsynth thread runs...
// ...and it has to be a *function*, not a method, because SDL's
// written in C. Dammit.
static int thread(void *data);
private:
enum { INPUT_BUFFER_SIZE = 128 };
uInt16 myBufferSize;
// These functions are called from the rsynth thread context only
// speak() is our locking wrapper for rsynth_phones()
static void speak();
static void *save_sample(void *user_data,
float sample,
unsigned nsamp,
rsynth_t *rsynth);
static void *flush_samples(void *user_data,
unsigned nsamp,
rsynth_t *rsynth);
static short clip(long *clip_max, float input, float *peak);
private:
// True if last code was 20 thru 29
bool needParameter;
// phonemeBuffer holds *translated* phonemes (e.g. rsynth phonemes,
// not SpeakJet phonemes).
char phonemeBuffer[INPUT_BUFFER_SIZE];
uInt8 phonemeCount; // number of phonemes in the phonemeBuffer
static const char *ourPhonemeTable[];
SDL_Thread *ourThread;
SpeechBuffer *myCurrentOutputBuffer;
// We use this semaphore like so:
// Main thread locks it initially
// Main thread gathers up phonemes, storing in the input buffer,
@ -158,7 +189,10 @@ class SpeakJet
// When the rsynth thread unblocks, it quickly copies the buffer to
// a private buffer, then unlocks the semaphore so the main thread
// can re-use the buffer.
SDL_sem *ourInputSemaphore;
// Note to self: locking decrements the semaphore; unlocking increments
// To lock (blocking): SDL_SemWait()
// To unlock: SDL_SemPost()
// Each output buffer also needs its own locking semaphore:
// rsynth thread locks each buffer as it fills it, then unlocks it
@ -180,6 +214,22 @@ class SpeakJet
// Convert a SpeakJet phoneme into one or more rsynth phonemes.
// Input range is 0 to 255, but not all codes are supported yet.
static const char *xlatePhoneme(uInt8 code);
};
// Where our output samples go.
struct SpeechBuffer
{
SDL_sem *lock;
SpeechBuffer *next;
int items;
uInt8 contents[OUTPUT_BUFFER_SIZE];
};
// For now, just a static array of them
static SpeechBuffer outputBuffers[SPEECH_BUFFERS];
static SpeechBuffer *ourCurrentWriteBuffer;
static uInt8 ourCurrentWritePosition;
#endif