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