mirror of https://github.com/stella-emu/stella.git
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:
parent
758a4ed3ff
commit
228e7c1a5d
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue