1
//============================================================================
5
// SS tttttt eeee ll ll aaaa
6
// SSSS tt ee ee ll ll aa
7
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8
// SS SS tt ee ll ll aa aa
9
// SSSS ttt eeeee llll llll aaaaa
11
// Copyright (c) 1995-2008 by Bradford W. Mott and the Stella team
13
// See the file "license" for information on usage and redistribution of
14
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16
// $Id: SpeakJet.hxx,v 1.10 2008/03/31 00:59:30 stephena Exp $
17
//============================================================================
19
#ifdef SPEAKJET_EMULATION
25
Emulation of the Magnevation SpeakJet.
26
This is the speech synthesizer chip used in the AtariVox.
27
See AtariVox.hxx and .cxx for AtariVox specifics.
29
This class doesn't attempt 100% accurate emulation of the SpeakJet,
30
as the chip contains a proprietary algorithm that does some complex
31
modelling (in other words, it doesn't just string samples together).
32
For this emulation, I use a library called rsynth, which does something
33
similar (models the human vocal/nasal tract), but is implemented
34
in a totally different way. You might say I'm emulating the spirit
35
of the SpeakJet, not the letter :)
37
Implementation details:
39
Both rsynth and the SpeakJet take a stream of phoneme codes and produce
42
My SpeakJet class accepts the SpeakJet phonemes, one at a time, and
43
translates them to rsynth phonemes (which are not quite one-to-one
44
equivalent). As each phoneme is translated, it's added to a phoneme
47
Because of the way rsynth is implemented, it needs a full word's worth
48
of phonemes in its buffer before its speech function is called. This
49
means I'll only call rsynth_phones() when I receive a SpeakJet code that
50
indicates a pause, or end-of-word, or a control code (set parameters
51
or such). This will result in a slight delay (typically, games will
52
send one SJ code per frame).
54
Also due to rsynth's implementation, I have to run it in a thread. This
55
is because rsynth_phones() is a monolithic function that needs a string
56
of phonemes, and takes a while to run (for the word "testing", it takes
57
1/4 second on an Athlon 64 @ 1800MHz). We can't have the emulator pause
58
for a quarter second while this happens, so I'll call rsynth_phones()
59
in a separate thread, and have it fill a buffer from which our main
60
thread will pull as much data as it needs. A typical word will be
61
30-40 thousand samples, and we only need fragsize/2 samples at a time.
63
As always when using threads, there will be locking in play...
65
rsynth's output is always 16-bit samples. This class will have to
66
convert them to 8-bit samples before feeding them to the SDL audio
69
When using the AtariVox, we'll use SDL stereo sound. The regular TIA
70
sound will come out the left channel, and the speech will come out
71
the right. This isn't ideal, but it's the easiest way to mix the two
72
(I don't want to add an SDL_mixer dependency). The real SpeakJet uses a
73
separate speaker from the 2600 (the 2600 TIA sound comes from the TV,
74
the SJ sound comes from a set of PC speakers), so splitting them to
75
the left and right channels isn't unreasonable... However, it means
76
no game can simultaneously use stereo sound and the AtariVox (for now,
80
@version $Id: SpeakJet.hxx,v 1.10 2008/03/31 00:59:30 stephena Exp $
86
#include <SDL_thread.h>
87
#include "rsynth/rsynth.h"
92
enum { INPUT_BUFFER_SIZE = 128 };
93
enum { OUTPUT_BUFFER_SIZE = 128 };
94
enum { SPEECH_BUFFERS = 1024 };
95
static SDL_sem *ourInputSemaphore;
96
static rsynth_t *rsynth;
97
static darray_t rsynthSamples;
98
// phonemeBuffer holds *translated* phonemes (e.g. rsynth phonemes,
99
// not SpeakJet phonemes).
100
static char phonemeBuffer[INPUT_BUFFER_SIZE];
101
// How many bytes are in the input buffer?
102
static uInt16 ourInputCount;
109
Create a new SpeakJet with given buffer size. We use a circular linked
110
list of fixed size, each node being a buffer of bufferSize 8-bit
113
@param bufferSize The size of each output buffer, presumably equal
122
Writes a SpeakJet phoneme (or other code).
123
These are the codes from page 16 of the Speaket User Manual.
124
Not all codes are emulated. In particular, the non-speech noises
125
(200 thru 254) will be treated as silence. Also, not all the
126
control codes will actually work (will document later).
128
@param code The SpeakJet code being written to the emulated chip
130
void write(uInt8 code);
133
Returns a buffer full of 8-bit samples. This should be called every
134
frame or so, or else the older buffers will get overwritten by new
137
@param count This will be set to the number of samples that are
138
returned. Value ranges from 0 to bufferSize.
140
uInt8 *getSamples(int *count);
143
Returns false if the phonemeBuffer is full, true otherwise.
148
// function that spawns the rsynth thread
151
// function that the rsynth thread runs...
152
// ...and it has to be a *function*, not a method, because SDL's
153
// written in C. Dammit.
154
static int thread(void *data);
157
// These functions are called from the rsynth thread context only
159
// speak() is our locking wrapper for rsynth_phones()
162
static void *save_sample(void *user_data,
167
static void *flush_samples(void *user_data,
171
static short clip(long *clip_max, float input, float *peak);
175
// True if last code was 20 thru 29
178
static const char *ourPhonemeTable[];
180
SDL_Thread *ourThread;
182
SpeechBuffer *myCurrentOutputBuffer;
184
// We use this semaphore like so:
185
// Main thread locks it initially
186
// Main thread gathers up phonemes, storing in the input buffer,
187
// until it hits a pause/space,
188
// then unlocks the semaphore.
189
// The rsynth thread blocks on the semaphore until the main thread
190
// is done feeding data into the buffer.
191
// When the rsynth thread unblocks, it quickly copies the buffer to
192
// a private buffer, then unlocks the semaphore so the main thread
193
// can re-use the buffer.
195
// Note to self: locking decrements the semaphore; unlocking increments
196
// To lock (blocking): SDL_SemWait()
197
// To unlock: SDL_SemPost()
199
// Each output buffer also needs its own locking semaphore:
200
// rsynth thread locks each buffer as it fills it, then unlocks it
201
// when it's done, and moves on to the next buffer in the circular
202
// list (blocking if it's locked).
204
// When the main thread is ready to play audio, it grabs its idea
205
// of what the next buffer is (blocking if it's locked), locks it, mixes
206
// its contents with the TIA audio data (if it's not an empty buffer),
207
// clears the buffer, then unlocks it.
208
// Note that, if the rsynth thread has been sleeping a while, all
209
// the buffers might be empty.
211
// When the rsynth thread runs out of input, it should probably
212
// listen on a condition, so it can be woken up when there's something
216
// Convert a SpeakJet phoneme into one or more rsynth phonemes.
217
// Input range is 0 to 255, but not all codes are supported yet.
218
static const char *xlatePhoneme(uInt8 code);
222
// Where our output samples go.
228
uInt8 contents[OUTPUT_BUFFER_SIZE];
231
// For now, just a static array of them
232
static SpeechBuffer outputBuffers[SPEECH_BUFFERS];
234
static SpeechBuffer *ourCurrentWriteBuffer;
235
static uInt8 ourCurrentWritePosition;