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