1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or
5  *  (at your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *  GNU General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15  */
16 
17 #include "sound.h"
18 #include "sndintrn.h"
19 #include "libs/sound/trackplayer.h"
20 #include "trackint.h"
21 #include "libs/log.h"
22 #include "libs/memlib.h"
23 #include "options.h"
24 #include <ctype.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <memory.h>
28 
29 
30 static int track_count;       // total number of tracks
31 static int no_page_break;     // set when combining several tracks into one
32 
33 // The one and only sample we play. Track switching is done by modifying
34 // this sample while it is playing. StreamDecoderTaskFunc() picks up the
35 // changes *mostly* seamlessly (keyword: mostly).
36 // This is technically a hack, but a decent one ;)
37 static TFB_SoundSample *sound_sample;
38 
39 static volatile uint32 tracks_length; // total length of tracks in game units
40 
41 static TFB_SoundChunk *chunks_head;   // first decoder in linked list
42 static TFB_SoundChunk *chunks_tail;   // last decoder in linked list
43 static TFB_SoundChunk *last_sub;      // last chunk in the list with a subtitle
44 
45 static TFB_SoundChunk *cur_chunk;     // currently playing chunk
46 static TFB_SoundChunk *cur_sub_chunk; // currently displayed subtitle chunk
47 
48 // Accesses to cur_chunk and cur_sub_chunk are guarded by stream_mutex,
49 // because these should only be accesses by the DoInput and the
50 // stream player threads. Any other accesses would go unguarded.
51 // Other data structures are unguarded and should only be accessed from
52 // the DoInput thread at certain times, i.e. nothing can be modified
53 // between StartTrack() and JumpTrack()/StopTrack() calls.
54 // Use caution when changing code, as you may need to guard other data
55 // structures the same way.
56 
57 static void seek_track (sint32 offset);
58 
59 // stream callbacks
60 static bool OnStreamStart (TFB_SoundSample* sample);
61 static bool OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer);
62 static void OnStreamEnd (TFB_SoundSample* sample);
63 static void OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag);
64 
65 static TFB_SoundCallbacks trackCBs =
66 {
67 	OnStreamStart,
68 	OnChunkEnd,
69 	OnStreamEnd,
70 	OnBufferTag,
71 	NULL
72 };
73 
74 static inline sint32
chunk_end_time(TFB_SoundChunk * chunk)75 chunk_end_time (TFB_SoundChunk *chunk)
76 {
77 	return (sint32) ((chunk->start_time + chunk->decoder->length)
78 			* ONE_SECOND);
79 }
80 
81 static inline sint32
tracks_end_time(void)82 tracks_end_time (void)
83 {
84 	return chunk_end_time (chunks_tail);
85 }
86 
87 //JumpTrack currently aborts the current track. However, it doesn't clear the
88 //data-structures as StopTrack does.  this allows for rewind even after the
89 //track has finished playing
90 //Question:  Should 'abort' call StopTrack?
91 void
JumpTrack(void)92 JumpTrack (void)
93 {
94 	if (!sound_sample)
95 		return; // nothing to skip
96 
97 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
98 	seek_track (tracks_length + 1);
99 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
100 }
101 
102 // This should just start playing a stream
103 void
PlayTrack(void)104 PlayTrack (void)
105 {
106 	if (!sound_sample)
107 		return; // nothing to play
108 
109 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
110 	tracks_length = tracks_end_time ();
111 	// decoder will be set in OnStreamStart()
112 	cur_chunk = chunks_head;
113 	// Always scope the speech data, we may need it
114 	PlayStream (sound_sample, SPEECH_SOURCE, false, true, true);
115  	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
116 }
117 
118 void
PauseTrack(void)119 PauseTrack (void)
120 {
121 	if (!sound_sample)
122 		return; // nothing to pause
123 
124 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
125 	PauseStream (SPEECH_SOURCE);
126 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
127 }
128 
129 // ResumeTrack should resume a paused track, and do nothing for a playing track
130 void
ResumeTrack(void)131 ResumeTrack (void)
132 {
133 	audio_IntVal state;
134 
135 	if (!sound_sample)
136 		return; // nothing to resume
137 
138 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
139 
140 	if (!cur_chunk)
141 	{	// not playing anything, so no resuming
142 		UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
143 		return;
144 	}
145 
146 	audio_GetSourcei (soundSource[SPEECH_SOURCE].handle, audio_SOURCE_STATE, &state);
147 	if (state == audio_PAUSED)
148 		ResumeStream (SPEECH_SOURCE);
149 
150 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
151 }
152 
153 COUNT
PlayingTrack(void)154 PlayingTrack (void)
155 {
156 	// This ignores the paused state and simply returns what track
157 	// *should* be playing
158 	COUNT result = 0;  // default is none
159 
160 	if (!sound_sample)
161 		return 0; // not playing anything
162 
163 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
164 	if (cur_chunk)
165 		result = cur_chunk->track_num + 1;
166 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
167 
168 	return result;
169 }
170 
171 void
StopTrack(void)172 StopTrack (void)
173 {
174 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
175 	StopStream (SPEECH_SOURCE);
176 	track_count = 0;
177 	tracks_length = 0;
178 	cur_chunk = NULL;
179 	cur_sub_chunk = NULL;
180 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
181 
182 	if (chunks_head)
183 	{
184 		chunks_tail = NULL;
185 		destroy_SoundChunk_list (chunks_head);
186 		chunks_head = NULL;
187 		last_sub = NULL;
188 	}
189 	if (sound_sample)
190 	{
191 		// We delete the decoders ourselves
192 		sound_sample->decoder = NULL;
193 		TFB_DestroySoundSample (sound_sample);
194 		sound_sample = NULL;
195 	}
196 }
197 
198 static void
DoTrackTag(TFB_SoundChunk * chunk)199 DoTrackTag (TFB_SoundChunk *chunk)
200 {
201 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
202 	if (chunk->callback)
203 		chunk->callback(0);
204 
205 	cur_sub_chunk = chunk;
206 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
207 }
208 
209 // This func is called by PlayStream() when stream is about
210 // to start. We have a chance to tweak the stream here.
211 // This is called on the DoInput thread.
212 static bool
OnStreamStart(TFB_SoundSample * sample)213 OnStreamStart (TFB_SoundSample* sample)
214 {
215 	if (sample != sound_sample)
216 		return false; // Huh? Why did we get called on this?
217 
218 	if (!cur_chunk)
219 		return false; // Stream shouldn't be playing at all
220 
221 	// Adjust the sample to play what we want
222 	sample->decoder = cur_chunk->decoder;
223 	sample->offset = (sint32) (cur_chunk->start_time * ONE_SECOND);
224 
225 	if (cur_chunk->tag_me)
226 		DoTrackTag (cur_chunk);
227 
228 	return true;
229 }
230 
231 // This func is called by StreamDecoderTaskFunc() when the last buffer
232 // of the current chunk has been decoded (not when it has been *played*).
233 // This is called on the stream task thread.
234 static bool
OnChunkEnd(TFB_SoundSample * sample,audio_Object buffer)235 OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer)
236 {
237 	if (sample != sound_sample)
238 		return false; // Huh? Why did we get called on this?
239 
240 	if (!cur_chunk || !cur_chunk->next)
241 	{	// all chunks and tracks are done
242 		return false;
243 	}
244 
245 	// Move on to the next chunk
246 	cur_chunk = cur_chunk->next;
247 	// Adjust the sample to play what we want
248 	sample->decoder = cur_chunk->decoder;
249 	SoundDecoder_Rewind (sample->decoder);
250 
251 	log_add (log_Info, "Switching to stream %s at pos %d",
252 			sample->decoder->filename, sample->decoder->start_sample);
253 
254 	if (cur_chunk->tag_me)
255 	{	// Tag the last buffer of the chunk with the next chunk
256 		TFB_TagBuffer (sample, buffer, (intptr_t)cur_chunk);
257 	}
258 
259 	return true;
260 }
261 
262 // This func is called by StreamDecoderTaskFunc() when stream has ended
263 // This is called on the stream task thread.
264 static void
OnStreamEnd(TFB_SoundSample * sample)265 OnStreamEnd (TFB_SoundSample* sample)
266 {
267 	if (sample != sound_sample)
268 		return; // Huh? Why did we get called on this?
269 
270 	cur_chunk = NULL;
271 	cur_sub_chunk = NULL;
272 }
273 
274 // This func is called by StreamDecoderTaskFunc() when a tagged buffer
275 // has finished playing.
276 // This is called on the stream task thread.
277 static void
OnBufferTag(TFB_SoundSample * sample,TFB_SoundTag * tag)278 OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag)
279 {
280 	TFB_SoundChunk* chunk = (TFB_SoundChunk*) tag->data;
281 
282 	assert (sizeof (tag->data) >= sizeof (chunk));
283 
284 	if (sample != sound_sample)
285 		return; // Huh? Why did we get called on this?
286 
287 	TFB_ClearBufferTag (tag);
288 	DoTrackTag (chunk);
289 }
290 
291 // Parse the timestamps string into an int array.
292 // Rerturns number of timestamps parsed.
293 static int
GetTimeStamps(UNICODE * TimeStamps,sint32 * time_stamps)294 GetTimeStamps (UNICODE *TimeStamps, sint32 *time_stamps)
295 {
296 	int pos;
297 	int num = 0;
298 
299 	while (*TimeStamps && (pos = strcspn (TimeStamps, ",\r\n")))
300 	{
301 		UNICODE valStr[32];
302 		uint32 val;
303 
304 		memcpy (valStr, TimeStamps, pos);
305 		valStr[pos] = '\0';
306 		val = strtoul (valStr, NULL, 10);
307 		if (val)
308 		{
309 			*time_stamps = val;
310 			num++;
311 			time_stamps++;
312 		}
313 		TimeStamps += pos;
314 		TimeStamps += strspn (TimeStamps, ",\r\n");
315 	}
316 	return (num);
317 }
318 
319 #define TEXT_SPEED 80
320 // Returns number of parsed pages
321 static int
SplitSubPages(UNICODE * text,UNICODE * pages[],sint32 timestamp[],int size)322 SplitSubPages (UNICODE *text, UNICODE *pages[], sint32 timestamp[], int size)
323 {
324 	int lead_ellips = 0;
325 	COUNT page;
326 
327 	for (page = 0; page < size && *text != '\0'; ++page)
328 	{
329 		int aft_ellips;
330 		int pos;
331 
332 		// seek to EOL or end of the string
333 		pos = strcspn (text, "\r\n");
334 		// XXX: this will only work when ASCII punctuation and spaces
335 		//   are used exclusively
336 		aft_ellips = 3 * (text[pos] != '\0' && pos > 0 &&
337 				!ispunct (text[pos - 1]) && !isspace (text[pos - 1]));
338 		pages[page] = HMalloc (sizeof (UNICODE) *
339 				(lead_ellips + pos + aft_ellips + 1));
340 		if (lead_ellips)
341 			strcpy (pages[page], "..");
342 		memcpy (pages[page] + lead_ellips, text, pos);
343 		pages[page][lead_ellips + pos] = '\0'; // string term
344 		if (aft_ellips)
345 			strcpy (pages[page] + lead_ellips + pos, "...");
346 		timestamp[page] = pos * TEXT_SPEED;
347 		if (timestamp[page] < 1000)
348 			timestamp[page] = 1000;
349 		lead_ellips = aft_ellips ? 2 : 0;
350 		text += pos;
351 		// Skip any EOL
352 		text += strspn (text, "\r\n");
353 	}
354 
355 	return page;
356 }
357 
358 // decodes several tracks into one and adds it to queue
359 // track list is NULL-terminated
360 // May only be called after at least one SpliceTrack(). This is a limitation
361 // for the sake of timestamps, but it does not have to be so.
362 void
SpliceMultiTrack(UNICODE * TrackNames[],UNICODE * TrackText)363 SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText)
364 {
365 #define MAX_MULTI_TRACKS  20
366 #define MAX_MULTI_BUFFERS 100
367 	TFB_SoundDecoder* track_decs[MAX_MULTI_TRACKS + 1];
368 	int tracks;
369 	int slen1, slen2;
370 
371 	if (!TrackText)
372 	{
373 		log_add (log_Debug, "SpliceMultiTrack(): no track text");
374 		return;
375 	}
376 
377 	if (!sound_sample || !chunks_tail)
378 	{
379 		log_add (log_Warning, "SpliceMultiTrack(): Cannot be called before SpliceTrack()");
380 		return;
381 	}
382 
383 	log_add (log_Info, "SpliceMultiTrack(): loading...");
384 	for (tracks = 0; *TrackNames && tracks < MAX_MULTI_TRACKS; TrackNames++, tracks++)
385 	{
386 		track_decs[tracks] = SoundDecoder_Load (contentDir, *TrackNames,
387 				32768, 0, - 3 * TEXT_SPEED);
388 		if (track_decs[tracks])
389 		{
390 			log_add (log_Info, "  track: %s, decoder: %s, rate %d format %x",
391 					*TrackNames,
392 					SoundDecoder_GetName (track_decs[tracks]),
393 					track_decs[tracks]->frequency,
394 					track_decs[tracks]->format);
395 			SoundDecoder_DecodeAll (track_decs[tracks]);
396 
397 			chunks_tail->next = create_SoundChunk (track_decs[tracks], sound_sample->length);
398 			chunks_tail = chunks_tail->next;
399 			chunks_tail->track_num = track_count - 1;
400 			sound_sample->length += track_decs[tracks]->length;
401 		}
402 		else
403 		{
404 			log_add (log_Warning, "SpliceMultiTrack(): couldn't load %s\n",
405 					*TrackNames);
406 			tracks--;
407 		}
408 	}
409 	track_decs[tracks] = 0; // term
410 
411 	if (tracks == 0)
412 	{
413 		log_add (log_Warning, "SpliceMultiTrack(): no tracks loaded");
414 		return;
415 	}
416 
417 	slen1 = strlen (last_sub->text);
418 	slen2 = strlen (TrackText);
419 	last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1);
420 	strcpy (last_sub->text + slen1, TrackText);
421 
422 	no_page_break = 1;
423 }
424 
425 // XXX: This code and the entire trackplayer are begging to be overhauled
426 void
SpliceTrack(UNICODE * TrackName,UNICODE * TrackText,UNICODE * TimeStamp,CallbackFunction cb)427 SpliceTrack (UNICODE *TrackName, UNICODE *TrackText, UNICODE *TimeStamp, CallbackFunction cb)
428 {
429 	static UNICODE last_track_name[128] = "";
430 	static unsigned long dec_offset = 0;
431 #define MAX_PAGES 50
432 	UNICODE *pages[MAX_PAGES];
433 	sint32 time_stamps[MAX_PAGES];
434 	int num_pages;
435 	int page;
436 
437 	if (!TrackText)
438 		return;
439 
440 	if (!TrackName)
441 	{	// Appending a piece of subtitles to the last track
442 		int slen1, slen2;
443 
444 		if (track_count == 0)
445 		{
446 			log_add (log_Warning, "SpliceTrack(): Tried to append a subtitle,"
447 					" but no current track");
448 			return;
449 		}
450 
451 		if (!last_sub || !last_sub->text)
452 		{
453 			log_add (log_Warning, "SpliceTrack(): Tried to append a subtitle"
454 					" to a NULL string");
455 			return;
456 		}
457 
458 		num_pages = SplitSubPages (TrackText, pages, time_stamps, MAX_PAGES);
459 		if (num_pages == 0)
460 		{
461 			log_add (log_Warning, "SpliceTrack(): Failed to parse subtitles");
462 			return;
463 		}
464 		// The last page's stamp is a suggested value. The track should
465 		// actually play to the end.
466 		time_stamps[num_pages - 1] = -time_stamps[num_pages - 1];
467 
468 		// Add the first piece to the last subtitle page
469 		slen1 = strlen (last_sub->text);
470 		slen2 = strlen (pages[0]);
471 		last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1);
472 		strcpy (last_sub->text + slen1, pages[0]);
473 		HFree (pages[0]);
474 
475 		// Add the rest of the pages
476 		for (page = 1; page < num_pages; ++page)
477 		{
478 			TFB_SoundChunk *next_sub = find_next_page (last_sub);
479 			if (next_sub)
480 			{	// nodes prepared by previous call, just fill in the subs
481 				next_sub->text = pages[page];
482 				last_sub = next_sub;
483 			}
484 			else
485 			{	// probably no timestamps were provided, so need more work
486 				TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir,
487 						last_track_name, 4096, dec_offset, time_stamps[page]);
488 				if (!decoder)
489 				{
490 					log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName);
491 					break;
492 				}
493 				dec_offset += (unsigned long)(decoder->length * 1000);
494 				chunks_tail->next = create_SoundChunk (decoder, sound_sample->length);
495 				chunks_tail = chunks_tail->next;
496 				chunks_tail->tag_me = 1;
497 				chunks_tail->track_num = track_count - 1;
498 				chunks_tail->text = pages[page];
499 				chunks_tail->callback = cb;
500 				// TODO: We may have to tag only one page with a callback
501 				//cb = NULL;
502 				last_sub = chunks_tail;
503 				sound_sample->length += decoder->length;
504 			}
505 		}
506 	}
507 	else
508 	{	// Adding a new track
509 		int num_timestamps = 0;
510 
511 		utf8StringCopy (last_track_name, sizeof (last_track_name), TrackName);
512 
513 		num_pages = SplitSubPages (TrackText, pages, time_stamps, MAX_PAGES);
514 		if (num_pages == 0)
515 		{
516 			log_add (log_Warning, "SpliceTrack(): Failed to parse sutitles");
517 			return;
518 		}
519 		// The last page's stamp is a suggested value. The track should
520 		// actually play to the end.
521 		time_stamps[num_pages - 1] = -time_stamps[num_pages - 1];
522 
523 		if (no_page_break && track_count)
524 		{
525 			int slen1, slen2;
526 
527 			slen1 = strlen (last_sub->text);
528 			slen2 = strlen (pages[0]);
529 			last_sub->text = HRealloc (last_sub->text, slen1 + slen2 + 1);
530 			strcpy (last_sub->text + slen1, pages[0]);
531 			HFree (pages[0]);
532 		}
533 		else
534 			track_count++;
535 
536 		log_add (log_Info, "SpliceTrack(): loading %s", TrackName);
537 
538 		if (TimeStamp)
539 		{
540 			num_timestamps = GetTimeStamps (TimeStamp, time_stamps) + 1;
541 			if (num_timestamps < num_pages)
542 			{
543 				log_add (log_Warning, "SpliceTrack(): number of timestamps"
544 						" doesn't match number of pages!");
545 			}
546 			else if (num_timestamps > num_pages)
547 			{	// We most likely will get more subtitles appended later
548 				// Set the last page to the rest of the track
549 				time_stamps[num_timestamps - 1] = -100000;
550 			}
551 		}
552 		else
553 		{
554 			num_timestamps = num_pages;
555 		}
556 
557 		// Reset the offset for the new track
558 		dec_offset = 0;
559 		for (page = 0; page < num_timestamps; ++page)
560 		{
561 			TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir,
562 					TrackName, 4096, dec_offset, time_stamps[page]);
563 			if (!decoder)
564 			{
565 				log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName);
566 				break;
567 			}
568 
569 			if (!sound_sample)
570 			{
571 				sound_sample = TFB_CreateSoundSample (NULL, 8, &trackCBs);
572 				chunks_head = create_SoundChunk (decoder, 0.0);
573 				chunks_tail = chunks_head;
574 			}
575 			else
576 			{
577 				chunks_tail->next = create_SoundChunk (decoder, sound_sample->length);
578 				chunks_tail = chunks_tail->next;
579 			}
580 			dec_offset += (unsigned long)(decoder->length * 1000);
581 #if 0
582 			log_add (log_Debug, "page (%d of %d): %d ts: %d",
583 					page, num_pages,
584 					dec_offset, time_stamps[page]);
585 #endif
586 			sound_sample->length += decoder->length;
587 			chunks_tail->track_num = track_count - 1;
588 			if (!no_page_break)
589 			{
590 				chunks_tail->tag_me = 1;
591 				if (page < num_pages)
592 				{
593 					chunks_tail->text = pages[page];
594 					last_sub = chunks_tail;
595 				}
596 				chunks_tail->callback = cb;
597 				// TODO: We may have to tag only one page with a callback
598 				//cb = NULL;
599 			}
600 			no_page_break = 0;
601 		}
602 	}
603 }
604 
605 // This function figures out the chunk that should be playing based on
606 // 'offset' into the total playing time of all tracks. It then sets
607 // the speech source's sample to the necessary decoder and seeks the
608 // decoder to the proper point.
609 // XXX: This means that whatever speech has already been queued on the
610 //   source will continue playing, so we may need some small timing
611 //   adjustments. It may be simpler to just call PlayStream().
612 static void
seek_track(sint32 offset)613 seek_track (sint32 offset)
614 {
615 	TFB_SoundChunk *cur;
616 	TFB_SoundChunk *last_tag = NULL;
617 
618 	if (!sound_sample)
619 		return; // nothing to recompute
620 
621 	if (offset < 0)
622 		offset = 0;
623 	else if ((uint32)offset > tracks_length)
624 		offset = tracks_length + 1;
625 
626 	// Adjusting the stream start time is the only way we can arbitrarily
627 	// seek the stream right now
628 	soundSource[SPEECH_SOURCE].start_time = GetTimeCounter () - offset;
629 
630 	// Find the chunk that should be playing at this time offset
631 	for (cur = chunks_head; cur && offset >= chunk_end_time (cur);
632 			cur = cur->next)
633 	{
634 		// .. looking for the last callback as we go along
635 		// XXX: this effectively set the last point where Fot is looking at.
636 		// TODO: this should be somehow changed if we implement more
637 		//   callbacks, like Melnorme trading, offloading at Starbase, etc.
638 		if (cur->tag_me)
639 			last_tag = cur;
640 	}
641 
642 	if (cur)
643 	{
644 		cur_chunk = cur;
645 		SoundDecoder_Seek (cur->decoder, (uint32) (((float)offset / ONE_SECOND
646 				- cur->start_time) * 1000));
647 		sound_sample->decoder = cur->decoder;
648 
649 		if (cur->tag_me)
650 			last_tag = cur;
651 		if (last_tag)
652 			DoTrackTag (last_tag);
653 	}
654 	else
655 	{	// The offset is beyond the length of all tracks
656 		StopStream (SPEECH_SOURCE);
657 		cur_chunk = NULL;
658 		cur_sub_chunk = NULL;
659 	}
660 }
661 
662 static sint32
get_current_track_pos(void)663 get_current_track_pos (void)
664 {
665 	sint32 start_time = soundSource[SPEECH_SOURCE].start_time;
666 	sint32 pos = GetTimeCounter () - start_time;
667 	if (pos < 0)
668 		pos = 0;
669 	else if ((uint32)pos > tracks_length)
670 		pos = tracks_length;
671 	return pos;
672 }
673 
674 void
FastReverse_Smooth(void)675 FastReverse_Smooth (void)
676 {
677 	sint32 offset;
678 
679 	if (!sound_sample)
680 		return; // nothing is playing, so.. bye!
681 
682 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
683 
684 	offset = get_current_track_pos ();
685 	offset -= ACCEL_SCROLL_SPEED;
686 	seek_track (offset);
687 
688 	// Restart the stream in case it ended previously
689 	if (!PlayingStream (SPEECH_SOURCE))
690 		PlayStream (sound_sample, SPEECH_SOURCE, false, true, false);
691 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
692 }
693 
694 void
FastForward_Smooth(void)695 FastForward_Smooth (void)
696 {
697 	sint32 offset;
698 
699 	if (!sound_sample)
700 		return; // nothing is playing, so.. bye!
701 
702 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
703 
704 	offset = get_current_track_pos ();
705 	offset += ACCEL_SCROLL_SPEED;
706 	seek_track (offset);
707 
708 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
709 }
710 
711 void
FastReverse_Page(void)712 FastReverse_Page (void)
713 {
714 	TFB_SoundChunk *prev;
715 
716 	if (!sound_sample)
717 		return; // nothing is playing, so.. bye!
718 
719 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
720 	prev = find_prev_page (cur_sub_chunk);
721 	if (prev)
722 	{	// Set the chunk to be played
723 		cur_chunk = prev;
724 		cur_sub_chunk = prev;
725 		// Decoder will be set in OnStreamStart()
726 		PlayStream (sound_sample, SPEECH_SOURCE, false, true, true);
727 	}
728 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
729 }
730 
731 void
FastForward_Page(void)732 FastForward_Page (void)
733 {
734 	TFB_SoundChunk *next;
735 
736 	if (!sound_sample)
737 		return; // nothing is playing, so.. bye!
738 
739 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
740 	next = find_next_page (cur_sub_chunk);
741 	if (next)
742 	{	// Set the chunk to be played
743 		cur_chunk = next;
744 		cur_sub_chunk = next;
745 		// Decoder will be set in OnStreamStart()
746 		PlayStream (sound_sample, SPEECH_SOURCE, false, true, true);
747 	}
748 	else
749 	{	// End of the tracks (pun intended)
750 		seek_track (tracks_length + 1);
751 	}
752 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
753 }
754 
755 // Tells current position of streaming speech in the units
756 // specified by the caller.
757 // This is normally called on the ambient_anim_task thread.
758 int
GetTrackPosition(int in_units)759 GetTrackPosition (int in_units)
760 {
761 	uint32 offset;
762 	uint32 length = tracks_length;
763 			// detach from the static one, otherwise, we can race for
764 			// it and thus divide by 0
765 
766 	if (!sound_sample || length == 0)
767 		return 0; // nothing is playing
768 
769 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
770 	offset = get_current_track_pos ();
771 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
772 
773 	return in_units * offset / length;
774 }
775 
776 TFB_SoundChunk *
create_SoundChunk(TFB_SoundDecoder * decoder,float start_time)777 create_SoundChunk (TFB_SoundDecoder *decoder, float start_time)
778 {
779 	TFB_SoundChunk *chunk;
780 	chunk = HCalloc (sizeof (*chunk));
781 	chunk->decoder = decoder;
782 	chunk->start_time = start_time;
783 	return chunk;
784 }
785 
786 void
destroy_SoundChunk_list(TFB_SoundChunk * chunk)787 destroy_SoundChunk_list (TFB_SoundChunk *chunk)
788 {
789 	TFB_SoundChunk *next = NULL;
790 	for ( ; chunk; chunk = next)
791 	{
792 		next = chunk->next;
793 		if (chunk->decoder)
794 			SoundDecoder_Free (chunk->decoder);
795 		HFree (chunk->text);
796 		HFree (chunk);
797 	}
798 }
799 
800 // Returns the next chunk with a subtitle
801 TFB_SoundChunk *
find_next_page(TFB_SoundChunk * cur)802 find_next_page (TFB_SoundChunk *cur)
803 {
804 	if (!cur)
805 		return NULL;
806 	for (cur = cur->next; cur && !cur->tag_me; cur = cur->next)
807 		;
808 	return cur;
809 }
810 
811 // Returns the previous chunk with a subtitle.
812 // cur == 0 is treated as end of the list.
813 TFB_SoundChunk *
find_prev_page(TFB_SoundChunk * cur)814 find_prev_page (TFB_SoundChunk *cur)
815 {
816 	TFB_SoundChunk *prev;
817 	TFB_SoundChunk *last_valid = chunks_head;
818 
819 	if (cur == chunks_head)
820 		return cur; // cannot go below the first track
821 
822 	for (prev = chunks_head; prev && prev != cur; prev = prev->next)
823 	{
824 		if (prev->tag_me)
825 			last_valid = prev;
826 	}
827 	return last_valid;
828 }
829 
830 
831 // External access to the chunks list
832 SUBTITLE_REF
GetFirstTrackSubtitle(void)833 GetFirstTrackSubtitle (void)
834 {
835 	return chunks_head;
836 }
837 
838 // External access to the chunks list
839 SUBTITLE_REF
GetNextTrackSubtitle(SUBTITLE_REF LastRef)840 GetNextTrackSubtitle (SUBTITLE_REF LastRef)
841 {
842 	if (!LastRef)
843 		return NULL; // enumeration already ended
844 
845 	return find_next_page (LastRef);
846 }
847 
848 // External access to the chunk subtitles
849 const UNICODE *
GetTrackSubtitleText(SUBTITLE_REF SubRef)850 GetTrackSubtitleText (SUBTITLE_REF SubRef)
851 {
852 	if (!SubRef)
853 		return NULL;
854 
855 	return SubRef->text;
856 }
857 
858 // External access to currently active subtitle text
859 // Returns NULL is none is active
860 const UNICODE *
GetTrackSubtitle(void)861 GetTrackSubtitle (void)
862 {
863 	const UNICODE *cur_sub = NULL;
864 
865 	if (!sound_sample)
866 		return NULL; // not playing anything
867 
868 	LockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
869 	if (cur_sub_chunk)
870 		cur_sub = cur_sub_chunk->text;
871 	UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex);
872 
873 	return cur_sub;
874 }
875