1 /* License note by perry_m:
2 Permission has been granted by the authors Mike Coates and Tom Haukap
3 to distribute this file under the terms of the GNU GPL license of Atari800.
4 The original version written by Mike Coates is from MAME.
5 This extensively modified version with new samples and mixing written
6 by Tom Haukap is from PinMAME.
7 I have also made modifications to this file for use in Atari800 and
8 allow any modifications to be distributed under any of the licenses
9 of Atari800, MAME and PinMAME. */
10
11 /**************************************************************************
12
13 Votrax SC-01 Emulator
14
15 Mike@Dissfulfils.co.uk
16 Tom.Haukap@t-online.de
17 modified for Atari800 by perry_m@fastmail.fm
18
19 **************************************************************************
20
21 Votrax_Start - Start emulation, load samples from Votrax subdirectory
22 Votrax_Stop - End emulation, free memory used for samples
23 Votrax_PutByte - Write data to votrax port
24 Votrax_GetStatus - Return busy status (1 = busy)
25
26 **************************************************************************/
27
28 #ifdef PBI_DEBUG
29 #define VERBOSE 1
30 #endif
31
32 #if VERBOSE
33 #define LOG(x) printf x
34 #else
35 #define LOG(x)
36 #endif
37
38 #include "votrax.h"
39 #include <stdio.h>
40 #include <math.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include "util.h"
44
45 static struct {
46 int busy;
47
48 int actPhoneme;
49 int actIntonation;
50
51 struct Votrax_interface *intf;
52
53 SWORD* pActPos;
54 int iRemainingSamples;
55
56 SWORD *lpBuffer;
57 SWORD* pBufferPos;
58
59 int iSamplesInBuffer;
60 int iDelay; /* a count of samples to output '0' in a Delay state */
61
62 } votraxsc01_locals;
63
64 #define INT16 SWORD
65 #define UINT16 UWORD
66 #include "vtxsmpls.inc"
67
68 #if VERBOSE
69 static const char *PhonemeNames[65] =
70 {
71 "EH3","EH2","EH1","PA0","DT" ,"A2" ,"A1" ,"ZH",
72 "AH2","I3" ,"I2" ,"I1" ,"M" ,"N" ,"B" ,"V",
73 "CH" ,"SH" ,"Z" ,"AW1","NG" ,"AH1","OO1","OO",
74 "L" ,"K" ,"J" ,"H" ,"G" ,"F" ,"D" ,"S",
75 "A" ,"AY" ,"Y1" ,"UH3","AH" ,"P" ,"O" ,"I",
76 "U" ,"Y" ,"T" ,"R" ,"E" ,"W" ,"AE" ,"AE1",
77 "AW2","UH2","UH1","UH" ,"O2" ,"O1" ,"IU" ,"U1",
78 "THV","TH" ,"ER" ,"EH" ,"E1" ,"AW" ,"PA1","STOP",
79 0
80 };
81 #endif
82
83 /* phoneme types*/
84 #define PT_NS 0
85 #define PT_V 1
86 #define PT_VF 2
87 #define PT_F 3
88 #define PT_N 4
89 #define PT_VS 5
90 #define PT_FS 6
91
92
93 static int sample_rate[4] = {22050, 22050, 22050, 22050};
94
95 /* converts milliseconds to a count of samples */
time_to_samples(int ms)96 static int time_to_samples(int ms)
97 {
98 return sample_rate[votraxsc01_locals.actIntonation]*ms/1000;
99 }
100
PrepareVoiceData(int nextPhoneme,int nextIntonation)101 static void PrepareVoiceData(int nextPhoneme, int nextIntonation)
102 {
103 int iNextRemainingSamples;
104 SWORD *pNextPos, *lpHelp;
105
106 int iFadeOutSamples;
107 int iFadeOutPos;
108
109 int iFadeInSamples;
110 int iFadeInPos;
111
112 int doMix;
113 /* used only for SecondStart phonemes */
114 int AdditionalSamples;
115 /* dwCount is the length of samples to produce in ms from iLengthms */
116 int dwCount, i;
117
118 SWORD data;
119
120 AdditionalSamples = 0;
121 /* some phonenemes have a SecondStart */
122 if ( PhonemeData[votraxsc01_locals.actPhoneme].iType>=PT_VS && votraxsc01_locals.actPhoneme!=nextPhoneme ) {
123 AdditionalSamples = PhonemeData[votraxsc01_locals.actPhoneme].iSecondStart;
124 }
125
126 if ( PhonemeData[nextPhoneme].iType>=PT_VS ) {
127 /* 'stop phonemes' will stop playing until the next phoneme is sent*/
128 votraxsc01_locals.iRemainingSamples = 0;
129 return;
130 }
131
132 /* length of samples to produce*/
133 dwCount = time_to_samples(PhonemeData[nextPhoneme].iLengthms);
134
135 votraxsc01_locals.iSamplesInBuffer = dwCount+AdditionalSamples;
136
137 if ( AdditionalSamples )
138 memcpy(votraxsc01_locals.lpBuffer, PhonemeData[votraxsc01_locals.actPhoneme].lpStart[votraxsc01_locals.actIntonation], AdditionalSamples*sizeof(SWORD));
139
140 lpHelp = votraxsc01_locals.lpBuffer + AdditionalSamples;
141
142 iNextRemainingSamples = 0;
143 pNextPos = NULL;
144
145 iFadeOutSamples = 0;
146 iFadeOutPos = 0;
147
148 iFadeInSamples = 0;
149 iFadeInPos = 0;
150
151 doMix = 0;
152
153 /* set up processing*/
154 if ( PhonemeData[votraxsc01_locals.actPhoneme].sameAs!=PhonemeData[nextPhoneme].sameAs ) {
155 /* do something, if they are the same all FadeIn/Out values are 0, */
156 /* the buffer is simply filled with the samples of the new phoneme */
157
158 switch ( PhonemeData[votraxsc01_locals.actPhoneme].iType ) {
159 case PT_NS:
160 /* "fade" out NS:*/
161 iFadeOutSamples = time_to_samples(30);
162 iFadeOutPos = 0;
163
164 /* fade in new phoneme*/
165 iFadeInPos = -time_to_samples(30);
166 iFadeInSamples = time_to_samples(30);
167 break;
168
169 case PT_V:
170 case PT_VF:
171 switch ( PhonemeData[nextPhoneme].iType ){
172 case PT_F:
173 case PT_VF:
174 /* V-->F, V-->VF: fade out 30 ms fade in from 30 ms to 60 ms without mixing*/
175 iFadeOutPos = 0;
176 iFadeOutSamples = time_to_samples(30);
177
178 iFadeInPos = -time_to_samples(30);
179 iFadeInSamples = time_to_samples(30);
180 break;
181
182 case PT_N:
183 /* V-->N: fade out 40 ms fade from 0 ms to 40 ms without mixing*/
184 iFadeOutPos = 0;
185 iFadeOutSamples = time_to_samples(40);
186
187 iFadeInPos = -time_to_samples(10);
188 iFadeInSamples = time_to_samples(10);
189 break;
190
191 default:
192 /* fade out 20 ms, no fade in from 10 ms to 30 ms*/
193 iFadeOutPos = 0;
194 iFadeOutSamples = time_to_samples(20);
195
196 iFadeInPos = -time_to_samples(0);
197 iFadeInSamples = time_to_samples(20);
198 break;
199 }
200 break;
201
202 case PT_N:
203 switch ( PhonemeData[nextPhoneme].iType ){
204 case PT_V:
205 case PT_VF:
206 /* N-->V, N-->VF: fade out 30 ms fade in from 10 ms to 50 ms without mixing*/
207 iFadeOutPos = 0;
208 iFadeOutSamples = time_to_samples(30);
209
210 iFadeInPos = -time_to_samples(10);
211 iFadeInSamples = time_to_samples(40);
212 break;
213
214 default:
215 break;
216 }
217
218 case PT_VS:
219 case PT_FS:
220 iFadeOutPos = 0;
221 iFadeOutSamples = PhonemeData[votraxsc01_locals.actPhoneme].iLength[votraxsc01_locals.actIntonation] - PhonemeData[votraxsc01_locals.actPhoneme].iSecondStart;
222 votraxsc01_locals.pActPos = PhonemeData[votraxsc01_locals.actPhoneme].lpStart[votraxsc01_locals.actIntonation] + PhonemeData[votraxsc01_locals.actPhoneme].iSecondStart;
223 votraxsc01_locals.iRemainingSamples = iFadeOutSamples;
224 doMix = 1;
225
226 iFadeInPos = -time_to_samples(0);
227 iFadeInSamples = time_to_samples(0);
228
229 break;
230
231 default:
232 /* fade out 30 ms, no fade in*/
233 iFadeOutPos = 0;
234 iFadeOutSamples = time_to_samples(20);
235
236 iFadeInPos = -time_to_samples(20);
237 break;
238 }
239
240 if ( !votraxsc01_locals.iDelay ) {
241 /* this is true if after a stop and a phoneme was sent a second phoneme is sent*/
242 /* during the delay time of the chip. Ignore the first phoneme data*/
243 iFadeOutPos = 0;
244 iFadeOutSamples = 0;
245 }
246
247 }
248 else {
249 /* the next one is of the same type as the previous one; continue to use the samples of the last phoneme*/
250 iNextRemainingSamples = votraxsc01_locals.iRemainingSamples;
251 pNextPos = votraxsc01_locals.pActPos;
252 }
253
254 for (i=0; i<dwCount; i++)
255 {
256 data = 0x00;
257
258 /* fade out*/
259 if ( iFadeOutPos<iFadeOutSamples )
260 {
261 double dFadeOut = 1.0;
262
263 if ( !doMix )
264 dFadeOut = 1.0-sin((1.0*iFadeOutPos/iFadeOutSamples)*3.1415/2);
265
266 if ( !votraxsc01_locals.iRemainingSamples ) {
267 votraxsc01_locals.iRemainingSamples = PhonemeData[votraxsc01_locals.actPhoneme].iLength[votraxsc01_locals.actIntonation];
268 votraxsc01_locals.pActPos = PhonemeData[votraxsc01_locals.actPhoneme].lpStart[votraxsc01_locals.actIntonation];
269 }
270
271 data = (SWORD) (*votraxsc01_locals.pActPos++ * dFadeOut);
272
273 votraxsc01_locals.iRemainingSamples--;
274 iFadeOutPos++;
275 }
276
277 /* fade in or copy*/
278 if ( iFadeInPos>=0 )
279 {
280 double dFadeIn = 1.0;
281
282 if ( iFadeInPos<iFadeInSamples ) {
283 dFadeIn = sin((1.0*iFadeInPos/iFadeInSamples)*3.1415/2);
284 iFadeInPos++;
285 }
286
287 if ( !iNextRemainingSamples ) {
288 iNextRemainingSamples = PhonemeData[nextPhoneme].iLength[nextIntonation];
289 pNextPos = PhonemeData[nextPhoneme].lpStart[nextIntonation];
290 }
291
292 data += (SWORD) (*pNextPos++ * dFadeIn);
293
294 iNextRemainingSamples--;
295 }
296 iFadeInPos++;
297
298 *lpHelp++ = data;
299 }
300
301 votraxsc01_locals.pBufferPos = votraxsc01_locals.lpBuffer;
302
303 votraxsc01_locals.pActPos = pNextPos;
304 votraxsc01_locals.iRemainingSamples = iNextRemainingSamples;
305 }
306
Votrax_PutByte(UBYTE data)307 void Votrax_PutByte(UBYTE data)
308 {
309 int Phoneme, Intonation;
310
311 Phoneme = data & 0x3F;
312 Intonation = (data >> 6)&0x03;
313
314 #ifdef VERBOSE
315 if (!votraxsc01_locals.intf) {
316 LOG(("Error: votraxsc01_locals.intf not set"));
317 return;
318 }
319 #endif /* VERBOSE */
320 LOG(("Votrax SC-01: %s at intonation %d\n", PhonemeNames[Phoneme], Intonation));
321 PrepareVoiceData(Phoneme, Intonation);
322
323 if ( votraxsc01_locals.actPhoneme==0x3f )
324 votraxsc01_locals.iDelay = time_to_samples(20);
325
326 if ( !votraxsc01_locals.busy )
327 {
328 votraxsc01_locals.busy = 1;
329 if ( votraxsc01_locals.intf->BusyCallback )
330 (*votraxsc01_locals.intf->BusyCallback)(votraxsc01_locals.busy);
331 }
332
333 votraxsc01_locals.actPhoneme = Phoneme;
334 votraxsc01_locals.actIntonation = Intonation;
335 }
336
Votrax_GetStatus(void)337 UBYTE Votrax_GetStatus(void)
338 {
339 return votraxsc01_locals.busy;
340 }
341
Votrax_Update(int num,SWORD * buffer,int length)342 void Votrax_Update(int num, SWORD *buffer, int length)
343 {
344 int samplesToCopy;
345
346 #if 0
347 /* if it is a different intonation */
348 if ( num!=votraxsc01_locals.actIntonation ) {
349 /* clear buffer */
350 memset(buffer, 0x00, length*sizeof(SWORD));
351 return;
352 }
353 #endif
354
355 while ( length ) {
356 /* Case 1: if in a delay state, output 0's*/
357 if ( votraxsc01_locals.iDelay ) {
358 samplesToCopy = (length<=votraxsc01_locals.iDelay)?length:votraxsc01_locals.iDelay;
359
360 memset(buffer, 0x00, samplesToCopy*sizeof(SWORD));
361 buffer += samplesToCopy;
362
363 votraxsc01_locals.iDelay -= samplesToCopy;
364 length -= samplesToCopy; /* missing in the original */
365 }
366 /* Case 2: there are no samples left in the buffer */
367 else if ( votraxsc01_locals.iSamplesInBuffer==0 ) {
368 if ( votraxsc01_locals.busy ) {
369 /* busy -> idle */
370 votraxsc01_locals.busy = 0;
371 if ( votraxsc01_locals.intf->BusyCallback )
372 (*votraxsc01_locals.intf->BusyCallback)(votraxsc01_locals.busy);
373 }
374
375 if ( votraxsc01_locals.iRemainingSamples==0 ) {
376 if ( PhonemeData[votraxsc01_locals.actPhoneme].iType>=PT_VS ) {
377 votraxsc01_locals.pActPos = PhonemeData[0x3f].lpStart[0];
378 votraxsc01_locals.iRemainingSamples = PhonemeData[0x3f].iLength[0];
379 }
380 else {
381 votraxsc01_locals.pActPos = PhonemeData[votraxsc01_locals.actPhoneme].lpStart[votraxsc01_locals.actIntonation];
382 votraxsc01_locals.iRemainingSamples = PhonemeData[votraxsc01_locals.actPhoneme].iLength[votraxsc01_locals.actIntonation];
383 }
384
385 }
386
387 /* if there aren't enough remaining, reduce the amount */
388 samplesToCopy = (length<=votraxsc01_locals.iRemainingSamples)?length:votraxsc01_locals.iRemainingSamples;
389
390 memcpy(buffer, votraxsc01_locals.pActPos, samplesToCopy*sizeof(SWORD));
391 buffer += samplesToCopy;
392
393 votraxsc01_locals.pActPos += samplesToCopy;
394 votraxsc01_locals.iRemainingSamples -= samplesToCopy;
395
396 length -= samplesToCopy;
397 }
398 /* Case 3: output the samples in the buffer */
399 else {
400 samplesToCopy = (length<=votraxsc01_locals.iSamplesInBuffer)?length:votraxsc01_locals.iSamplesInBuffer;
401
402 memcpy(buffer, votraxsc01_locals.pBufferPos, samplesToCopy*sizeof(SWORD));
403 buffer += samplesToCopy;
404
405 votraxsc01_locals.pBufferPos += samplesToCopy;
406 votraxsc01_locals.iSamplesInBuffer -= samplesToCopy;
407
408 length -= samplesToCopy;
409 }
410 }
411 }
412
Votrax_Start(void * sound_interface)413 int Votrax_Start(void *sound_interface)
414 {
415 int i, buffer_size;
416 /* clear local variables */
417 memset(&votraxsc01_locals, 0x00, sizeof votraxsc01_locals);
418
419 /* copy interface */
420 votraxsc01_locals.intf = (struct Votrax_interface *)sound_interface;
421
422 votraxsc01_locals.actPhoneme = 0x3f;
423
424 /* find the largest possible size of iSamplesInBuffer */
425 buffer_size = 0;
426 for (i = 0; i <= 0x3f; i++) {
427 int dwCount;
428 int size;
429 int AdditionalSamples;
430 AdditionalSamples = PhonemeData[i].iSecondStart;
431 dwCount = time_to_samples(PhonemeData[i].iLengthms);
432 size = dwCount + AdditionalSamples;
433 if (size > buffer_size) buffer_size = size;
434 }
435 votraxsc01_locals.lpBuffer = (SWORD*) Util_malloc(buffer_size*sizeof(SWORD));
436 PrepareVoiceData(votraxsc01_locals.actPhoneme, votraxsc01_locals.actIntonation);
437 return 0;
438 }
439
Votrax_Stop(void)440 void Votrax_Stop(void)
441 {
442 if ( votraxsc01_locals.lpBuffer ) {
443 free(votraxsc01_locals.lpBuffer);
444 votraxsc01_locals.lpBuffer = NULL;
445 }
446 }
447
Votrax_Samples(int currentP,int nextP,int cursamples)448 int Votrax_Samples(int currentP, int nextP, int cursamples)
449 {
450 int AdditionalSamples = 0;
451 int dwCount;
452 int delay = 0;
453 /* some phonemes have a SecondStart */
454 if ( PhonemeData[currentP].iType>=PT_VS && currentP!=nextP) {
455 AdditionalSamples = PhonemeData[currentP].iSecondStart;
456 }
457
458 if ( PhonemeData[nextP].iType>=PT_VS ) {
459 /* 'stop phonemes' will stop playing until the next phoneme is sent*/
460 /* votraxsc01_locals.iRemainingSamples = 0; */
461 return cursamples;
462 }
463 if (currentP == 0x3f) delay = time_to_samples(20);
464
465 /* length of samples to produce*/
466 dwCount = time_to_samples(PhonemeData[nextP].iLengthms);
467 return dwCount + AdditionalSamples + delay ;
468 }
469
470 /*
471 vim:ts=4:sw=4:
472 */
473