From ac3b01ce12ee1f0aa9714c1c2b2d58d08d06b9a1 Mon Sep 17 00:00:00 2001 From: avolkov Date: Sun, 18 Oct 2009 16:53:27 +0000 Subject: [PATCH] Trackplayer rewrite; fixes several obscure bugs; SpliceTrack() still a mess git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@3228 8092fc87-c524-0410-9efc-e669fe64eaf9 --- sc2/ChangeLog | 1 + sc2/src/libs/sound/music.c | 5 +- sc2/src/libs/sound/stream.c | 41 +- sc2/src/libs/sound/trackint.h | 33 +- sc2/src/libs/sound/trackplayer.c | 845 ++++++++++++++++--------------- sc2/src/libs/sound/trackplayer.h | 37 +- sc2/src/options.c | 6 +- sc2/src/uqm/comm.c | 310 ++++++------ sc2/src/uqm/comm.h | 17 +- sc2/src/uqm/commanim.c | 22 +- sc2/src/uqm/confirm.c | 5 - sc2/src/uqm/oscill.c | 2 +- sc2/src/uqm/setup.c | 5 +- sc2/src/uqm/util.c | 5 - 14 files changed, 671 insertions(+), 663 deletions(-) diff --git a/sc2/ChangeLog b/sc2/ChangeLog index ddf7454bb..e4f33bca2 100644 --- a/sc2/ChangeLog +++ b/sc2/ChangeLog @@ -1,4 +1,5 @@ Changes towards version 0.7: +- Trackplayer rewrite; fixed many bugs - Alex - Source tree reorg: libs/ moved out of sc2code/, msvc++/ moved to build/msvc6/, src/sc2code/ renamed to src/uqm/ - Coredev - Druuge no longer turn hostile after attempting a salvage (bug #1013) - Alex diff --git a/sc2/src/libs/sound/music.c b/sc2/src/libs/sound/music.c index ac39df07a..e2bfceb33 100644 --- a/sc2/src/libs/sound/music.c +++ b/sc2/src/libs/sound/music.c @@ -34,8 +34,8 @@ PLRPlaySong (MUSIC_REF MusicRef, BOOLEAN Continuous, BYTE Priority) if (pmus) { LockMutex (soundSource[MUSIC_SOURCE].stream_mutex); - PlayStream ((*pmus), MUSIC_SOURCE, Continuous, - speechVolumeScale == 0.0f, true); + // Always scope the music data, we may need it + PlayStream ((*pmus), MUSIC_SOURCE, Continuous, true, true); UnlockMutex (soundSource[MUSIC_SOURCE].stream_mutex); curMusicRef = MusicRef; @@ -104,6 +104,7 @@ snd_PlaySpeech (MUSIC_REF SpeechRef) if (pmus) { LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + // Do not need to scope the music-as-speech as of now PlayStream (*pmus, SPEECH_SOURCE, false, false, true); UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); diff --git a/sc2/src/libs/sound/stream.c b/sc2/src/libs/sound/stream.c index f53015193..3dad64be4 100644 --- a/sc2/src/libs/sound/stream.c +++ b/sc2/src/libs/sound/stream.c @@ -102,7 +102,10 @@ PlayStream (TFB_SoundSample *sample, uint32 source, bool looping, bool scope, soundSource[source].sbuf_size = pos + PAD_SCOPE_BYTES; soundSource[source].sbuf_start = pos; soundSource[source].sbuf_lasttime = GetTimeCounter (); - soundSource[source].start_time = (sint32)GetTimeCounter () - offset; + // Adjust the start time so it looks like the stream has been playing + // from the very beginning + soundSource[source].start_time = GetTimeCounter () - offset; + soundSource[source].pause_time = 0; soundSource[source].stream_should_be_playing = TRUE; audio_SourcePlay (soundSource[source].handle); } @@ -110,6 +113,8 @@ PlayStream (TFB_SoundSample *sample, uint32 source, bool looping, bool scope, void StopStream (uint32 source) { + StopSource (source); + soundSource[source].stream_should_be_playing = FALSE; soundSource[source].sample = NULL; @@ -122,20 +127,28 @@ StopStream (uint32 source) soundSource[source].sbuf_start = 0; soundSource[source].sbuf_size = 0; soundSource[source].sbuf_offset = 0; - - StopSource (source); + soundSource[source].pause_time = 0; } void PauseStream (uint32 source) { soundSource[source].stream_should_be_playing = FALSE; + if (!soundSource[source].pause_time) + soundSource[source].pause_time = GetTimeCounter (); audio_SourcePause (soundSource[source].handle); } void ResumeStream (uint32 source) { + if (soundSource[source].pause_time) + { // Adjust the start time so it looks like the stream has + // been playing all this time non-stop + soundSource[source].start_time += GetTimeCounter () + - soundSource[source].pause_time; + } + soundSource[source].pause_time = 0; soundSource[source].stream_should_be_playing = TRUE; audio_SourcePlay (soundSource[source].handle); } @@ -504,29 +517,35 @@ GraphForegroundStream (uint8 *data, sint32 width, sint32 height) long energy; - if (speechVolumeScale != 0.0f) - { // Use speech waveform when speech is enabled - source_num = SPEECH_SOURCE; + // Prefer speech to music + source_num = SPEECH_SOURCE; + source = &soundSource[source_num]; + LockMutex (source->stream_mutex); + if (speechVolumeScale != 0.0f && (!source->sample || + !source->sample->decoder || !source->sample->decoder->is_null)) + { // Use speech waveform, since it's available // Step is picked experimentally. Using step of 1 sample at 11025Hz, // because human speech is mostly in the low frequencies, and it looks // better this way. step = 1; - } else if (musicVolumeScale != 0.0f) - { // Use music waveform when speech is disabled + { // We do not have speech -- use music waveform + UnlockMutex (source->stream_mutex); source_num = MUSIC_SOURCE; + source = &soundSource[source_num]; + LockMutex (source->stream_mutex); + // Step is picked experimentally. Using step of 4 samples at 11025Hz. // It looks better this way. step = 4; } else - { + { // We do not have anything usable + UnlockMutex (source->stream_mutex); return 0; } - source = &soundSource[source_num]; - LockMutex (source->stream_mutex); if (!PlayingStream (source_num) || !source->sample || !source->sample->decoder || !source->sbuffer || source->sbuf_size == 0) diff --git a/sc2/src/libs/sound/trackint.h b/sc2/src/libs/sound/trackint.h index 7181a3014..9754f612e 100644 --- a/sc2/src/libs/sound/trackint.h +++ b/sc2/src/libs/sound/trackint.h @@ -17,28 +17,23 @@ #ifndef TRACKINT_H #define TRACKINT_H -typedef struct tfb_soundchain +struct tfb_soundchunk { - TFB_SoundDecoder *decoder; // points at the decoder to read from - float start_time; - int tag_me; - uint32 track_num; - UNICODE *text; - TFB_TrackCB callback; - struct tfb_soundchain *next; -} TFB_SoundChain; + TFB_SoundDecoder *decoder; // decoder for this chunk + float start_time; // relative time from track start + int tag_me; // set for chunks with subtitles + uint32 track_num; // logical track #, comm code needs this + UNICODE *text; // subtitle text + TFB_TrackCB callback; // comm callback, executed on chunk start + struct tfb_soundchunk *next; +}; -typedef struct tfb_soundchaindata -{ - TFB_SoundChain *read_chain_ptr; // points to chain read poistion - TFB_SoundChain *play_chain_ptr; // points to chain playing position +typedef struct tfb_soundchunk TFB_SoundChunk; -} TFB_SoundChainData; +TFB_SoundChunk *create_SoundChunk (TFB_SoundDecoder *decoder, float start_time); +void destroy_SoundChunk_list (TFB_SoundChunk *chain); +TFB_SoundChunk *find_next_page (TFB_SoundChunk *cur); +TFB_SoundChunk *find_prev_page (TFB_SoundChunk *cur); -extern TFB_SoundChain *chain_head; - -TFB_SoundChain *create_soundchain (TFB_SoundDecoder *decoder, float startTime); -void destroy_soundchain (TFB_SoundChain *chain); -TFB_SoundChain *get_chain_previous (TFB_SoundChain *head, TFB_SoundChain *current); #endif // TRACKINT_H diff --git a/sc2/src/libs/sound/trackplayer.c b/sc2/src/libs/sound/trackplayer.c index 3832cc102..1d841fa24 100644 --- a/sc2/src/libs/sound/trackplayer.c +++ b/sc2/src/libs/sound/trackplayer.c @@ -16,7 +16,7 @@ #include "sound.h" #include "libs/sound/trackplayer.h" -#include "libs/sound/trackint.h" +#include "trackint.h" #include "libs/log.h" #include "libs/memlib.h" #include "options.h" @@ -24,43 +24,66 @@ #include #include #include -// XXX: we should not include anything from uqm/ inside libs/ -#include "uqm/comm.h" -static int track_count; //total number of subtitle tracks -static int cur_track; //currently playing subtitle track -static UNICODE *cur_page = 0; //current page of subtitle track -static int no_page_break = 0; -static int track_pos_changed = 0; // set whenever ff, frev is enabled +static int track_count; // total number of tracks +static int no_page_break; // set when combining several tracks into one -static TFB_SoundSample *sound_sample = NULL; -TFB_SoundChain *chain_head = NULL; //first decoder in linked list -static TFB_SoundChain *chain_tail = NULL; //last decoder in linked list -static TFB_SoundChain *last_sub = NULL; //last element in the chain with a subtitle +// The one and only sample we play. Track switching is done by modifying +// this sample while it is playing. StreamDecoderTaskFunc() picks up the +// changes *mostly* seamlessly (keyword: mostly). +// This is technically a hack, but a decent one ;) +static TFB_SoundSample *sound_sample; -static Mutex track_mutex; //protects cur_track and track_count -void recompute_track_pos (TFB_SoundSample *sample, TFB_SoundChain *head, - sint32 offset); -static bool is_sample_playing(TFB_SoundSample* samp); +static volatile uint32 tracks_length; // total length of tracks in game units -void destroy_sound_sample (TFB_SoundSample *sample); +static TFB_SoundChunk *chunks_head; // first decoder in linked list +static TFB_SoundChunk *chunks_tail; // last decoder in linked list +static TFB_SoundChunk *last_sub; // last chunk in the list with a subtitle + +static TFB_SoundChunk *cur_chunk; // currently playing chunk +static TFB_SoundChunk *cur_sub_chunk; // currently displayed subtitle chunk + +// Accesses to cur_chunk and cur_sub_chunk are guarded by stream_mutex, +// because these should only be accesses by the DoInput and the +// stream player threads. Any other accesses would go unguarded. +// Other data structures are unguarded and should only be accessed from +// the DoInput thread at certain times, i.e. nothing can be modified +// between StartTrack() and JumpTrack()/StopTrack() calls. +// Use caution when changing code, as you may need to guard other data +// structures the same way. + +static void seek_track (sint32 offset); +static void destroy_SoundSample (TFB_SoundSample *sample); // stream callbacks -static bool OnTrackStart (TFB_SoundSample* sample); +static bool OnStreamStart (TFB_SoundSample* sample); static bool OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer); -static void OnTrackEnd (TFB_SoundSample* sample); -static void OnTrackTag (TFB_SoundSample* sample, TFB_SoundTag* tag); +static void OnStreamEnd (TFB_SoundSample* sample); +static void OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag); static TFB_SoundCallbacks trackCBs = { - OnTrackStart, + OnStreamStart, OnChunkEnd, - OnTrackEnd, - OnTrackTag, + OnStreamEnd, + OnBufferTag, NULL }; +static inline sint32 +chunk_end_time (TFB_SoundChunk *chunk) +{ + return (sint32) ((chunk->start_time + chunk->decoder->length) + * ONE_SECOND); +} + +static inline sint32 +tracks_end_time (void) +{ + return chunk_end_time (chunks_tail); +} + //JumpTrack currently aborts the current track. However, it doesn't clear the //data-structures as StopTrack does. this allows for rewind even after the //track has finished playing @@ -68,123 +91,83 @@ static TFB_SoundCallbacks trackCBs = void JumpTrack (void) { - TFB_SoundChainData* scd; - uint32 cur_time; - sint32 total_length = (sint32)((chain_tail->start_time + - chain_tail->decoder->length) * (float)ONE_SECOND); - if (!sound_sample) - return; - - scd = (TFB_SoundChainData*) sound_sample->data; + return; // nothing to skip LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - PauseStream (SPEECH_SOURCE); - cur_time = GetTimeCounter(); - soundSource[SPEECH_SOURCE].start_time = - (sint32)cur_time - total_length; - track_pos_changed = 1; - scd->play_chain_ptr = chain_tail; - recompute_track_pos (sound_sample, chain_head, total_length + 1); + seek_track (tracks_length + 1); UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + PlayingTrack(); } -//advance to the next track and start playing -// we no longer support advancing tracks this way. Instead this should just start playing -// a stream. +// This should just start playing a stream void PlayTrack (void) { - TFB_SoundChainData* scd; - if (!sound_sample) - return; + return; // nothing to play - scd = (TFB_SoundChainData*) sound_sample->data; - - if (scd->read_chain_ptr || sound_sample->decoder) - { - LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - PlayStream (sound_sample, - SPEECH_SOURCE, false, - speechVolumeScale != 0.0f, !track_pos_changed); - track_pos_changed = 0; - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - } + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + tracks_length = tracks_end_time (); + // decoder will be set in OnStreamStart() + cur_chunk = chunks_head; + // Always scope the speech data, we may need it + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); } -// ResumeTrack should resume a paused track, or start a stopped track, and do nothing -// for a playing track +void +PauseTrack (void) +{ + if (!sound_sample) + return; // nothing to pause + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + PauseStream (SPEECH_SOURCE); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// ResumeTrack should resume a paused track, and do nothing for a playing track void ResumeTrack (void) { - TFB_SoundChainData* scd; + audio_IntVal state; if (!sound_sample) + return; // nothing to resume + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + if (!cur_chunk) + { // not playing anything, so no resuming + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); return; - - scd = (TFB_SoundChainData*) sound_sample->data; - - if (scd->read_chain_ptr || sound_sample->decoder) - { - // Only try to start the track if there is something to play - audio_IntVal state; - BOOLEAN playing; - - LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - audio_GetSourcei (soundSource[SPEECH_SOURCE].handle, audio_SOURCE_STATE, &state); - playing = PlayingStream (SPEECH_SOURCE); - if (!track_pos_changed && !playing && state == audio_PAUSED) - { - /*adjust start time so the slider doesn't go crazy*/ - soundSource[SPEECH_SOURCE].start_time += GetTimeCounter () - soundSource[SPEECH_SOURCE].pause_time; - ResumeStream (SPEECH_SOURCE); - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - } - else if (! playing) - { - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - PlayTrack (); - } - else - { - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - } } + + audio_GetSourcei (soundSource[SPEECH_SOURCE].handle, audio_SOURCE_STATE, &state); + if (state == audio_PAUSED) + ResumeStream (SPEECH_SOURCE); + + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); } COUNT PlayingTrack (void) { - // this is not a great way to detect whether the track is playing, - // but as it should work during fast-forward/rewind, 'PlayingStream' can't be used -// if (track_count == 0) -// return ((COUNT)~0); - if (sound_sample && is_sample_playing (sound_sample)) - { - int last_track; - UNICODE *last_page; - LockMutex (track_mutex); - last_track = cur_track; - last_page = cur_page; - UnlockMutex (track_mutex); - if (do_subtitles (last_page)) - { - return cur_track + 1; - } - else - { - COUNT result; + // This ignores the paused state and simply returns what track + // *should* be playing + COUNT result = 0; // default is none - LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - result = (PlayingStream (SPEECH_SOURCE) ? (COUNT)(last_track + 1) : 0); - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - return result; - } - } + if (!sound_sample) + return 0; // not playing anything - return (0); + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (cur_chunk) + result = cur_chunk->track_num + 1; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return result; } void @@ -192,115 +175,117 @@ StopTrack (void) { LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); StopStream (SPEECH_SOURCE); + track_count = 0; + tracks_length = 0; + cur_chunk = NULL; + cur_sub_chunk = NULL; UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - if (chain_head) + if (chunks_head) { - destroy_soundchain (chain_head); - chain_head = NULL; - chain_tail = NULL; + chunks_tail = NULL; + destroy_SoundChunk_list (chunks_head); + chunks_head = NULL; last_sub = NULL; } if (sound_sample) { - DestroyMutex (track_mutex); - destroy_sound_sample (sound_sample); + destroy_SoundSample (sound_sample); sound_sample = NULL; } - track_count = 0; - cur_track = 0; - cur_page = 0; - do_subtitles ((void *)~0); -} - -static bool -is_sample_playing (TFB_SoundSample* sample) -{ - TFB_SoundChainData* scd = (TFB_SoundChainData*) sample->data; - - return (scd->read_chain_ptr && scd->play_chain_ptr) - || (!scd->read_chain_ptr && sample->decoder); } static void -DoTrackTag (TFB_SoundChain *chain) +DoTrackTag (TFB_SoundChunk *chunk) { - LockMutex (track_mutex); - if (chain->callback) - chain->callback (); - cur_track = chain->track_num; - cur_page = chain->text; - UnlockMutex (track_mutex); + if (chunk->callback) + chunk->callback (); + cur_sub_chunk = chunk; } +// This func is called by PlayStream() when stream is about +// to start. We have a chance to tweak the stream here. +// This is called on the DoInput thread. static bool -OnTrackStart (TFB_SoundSample* sample) +OnStreamStart (TFB_SoundSample* sample) { - TFB_SoundChainData* scd = (TFB_SoundChainData*) sample->data; + if (sample != sound_sample) + return false; // Huh? Why did we get called on this? - if (!scd->read_chain_ptr && !sample->decoder) - return false; + if (!cur_chunk) + return false; // Stream shouldn't be playing at all - if (scd->read_chain_ptr) - { - sample->decoder = scd->read_chain_ptr->decoder; - sample->offset = (sint32) (scd->read_chain_ptr->start_time * (float)ONE_SECOND); - } - else - sample->offset = 0; + // Adjust the sample to play what we want + sample->decoder = cur_chunk->decoder; + sample->offset = (sint32) (cur_chunk->start_time * ONE_SECOND); - scd->play_chain_ptr = scd->read_chain_ptr; - - if (scd->read_chain_ptr && scd->read_chain_ptr->tag_me) - DoTrackTag(scd->read_chain_ptr); + if (cur_chunk->tag_me) + DoTrackTag (cur_chunk); return true; } +// This func is called by StreamDecoderTaskFunc() when the last buffer +// of the current chunk has been decoded (not when it has been *played*). +// This is called on the stream task thread. static bool OnChunkEnd (TFB_SoundSample* sample, audio_Object buffer) { - TFB_SoundChainData* scd = (TFB_SoundChainData*) sample->data; + if (sample != sound_sample) + return false; // Huh? Why did we get called on this? - if (!scd->read_chain_ptr || !scd->read_chain_ptr->next) + if (!cur_chunk || !cur_chunk->next) + { // all chunks and tracks are done return false; + } - scd->read_chain_ptr = scd->read_chain_ptr->next; - sample->decoder = scd->read_chain_ptr->decoder; + // Move on to the next chunk + cur_chunk = cur_chunk->next; + // Adjust the sample to play what we want + sample->decoder = cur_chunk->decoder; SoundDecoder_Rewind (sample->decoder); + log_add (log_Info, "Switching to stream %s at pos %d", sample->decoder->filename, sample->decoder->start_sample); - if (sample->buffer_tag && scd->read_chain_ptr->tag_me) - { - TFB_TagBuffer (sample, buffer, scd->read_chain_ptr); + if (cur_chunk->tag_me) + { // Tag the last buffer of the chunk with the next chunk + TFB_TagBuffer (sample, buffer, cur_chunk); } return true; } +// This func is called by StreamDecoderTaskFunc() when stream has ended +// This is called on the stream task thread. static void -OnTrackEnd (TFB_SoundSample* sample) +OnStreamEnd (TFB_SoundSample* sample) { - TFB_SoundChainData* scd = (TFB_SoundChainData*) sample->data; + if (sample != sound_sample) + return; // Huh? Why did we get called on this? - sample->decoder = NULL; - scd->read_chain_ptr = NULL; + cur_chunk = NULL; + cur_sub_chunk = NULL; } +// This func is called by StreamDecoderTaskFunc() when a tagged buffer +// has finished playing. +// This is called on the stream task thread. static void -OnTrackTag (TFB_SoundSample* sample, TFB_SoundTag* tag) +OnBufferTag (TFB_SoundSample* sample, TFB_SoundTag* tag) { - TFB_SoundChainData* scd = (TFB_SoundChainData*) sample->data; - TFB_SoundChain* chain = (TFB_SoundChain*) tag->data; + TFB_SoundChunk* chunk = (TFB_SoundChunk*) tag->data; + + if (sample != sound_sample) + return; // Huh? Why did we get called on this? TFB_ClearBufferTag (tag); - DoTrackTag (chain); - - scd->play_chain_ptr = scd->read_chain_ptr; + DoTrackTag (chunk); } -int +// Parse the timestamps string into an int array. +// Rerturns number of timestamps parsed. +static int GetTimeStamps (UNICODE *TimeStamps, sint32 *time_stamps) { int pos; @@ -384,7 +369,7 @@ SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText) return; } - if (!sound_sample || !chain_tail) + if (!sound_sample || !chunks_tail) { log_add (log_Warning, "SpliceMultiTrack(): Cannot be called before SpliceTrack()"); return; @@ -404,8 +389,8 @@ SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText) track_decs[tracks]->format); SoundDecoder_DecodeAll (track_decs[tracks]); - chain_tail->next = create_soundchain (track_decs[tracks], sound_sample->length); - chain_tail = chain_tail->next; + chunks_tail->next = create_SoundChunk (track_decs[tracks], sound_sample->length); + chunks_tail = chunks_tail->next; sound_sample->length += track_decs[tracks]->length; } else @@ -436,7 +421,7 @@ void SpliceTrack (UNICODE *TrackName, UNICODE *TrackText, UNICODE *TimeStamp, TFB_TrackCB cb) { static UNICODE last_track_name[128] = ""; - static unsigned long startTime = 0; + static unsigned long dec_offset = 0; #define MAX_PAGES 50 UNICODE *pages[MAX_PAGES]; sint32 time_stamps[MAX_PAGES]; @@ -484,30 +469,31 @@ SpliceTrack (UNICODE *TrackName, UNICODE *TrackText, UNICODE *TimeStamp, TFB_Tra // Add the rest of the pages for (page = 1; page < num_pages; ++page) { - if (last_sub->next) + TFB_SoundChunk *next_sub = find_next_page (last_sub); + if (next_sub) { // nodes prepared by previous call, just fill in the subs - last_sub = last_sub->next; - last_sub->text = pages[page]; + next_sub->text = pages[page]; + last_sub = next_sub; } else { // probably no timestamps were provided, so need more work TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir, - last_track_name, 4096, startTime, time_stamps[page]); + last_track_name, 4096, dec_offset, time_stamps[page]); if (!decoder) { log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName); break; } - startTime += (unsigned long)(decoder->length * 1000); - chain_tail->next = create_soundchain (decoder, sound_sample->length); - chain_tail = chain_tail->next; - chain_tail->tag_me = 1; - chain_tail->track_num = track_count - 1; - chain_tail->text = pages[page]; - chain_tail->callback = cb; - // We have to tag only one page with a callback - cb = NULL; - last_sub = chain_tail; + dec_offset += (unsigned long)(decoder->length * 1000); + chunks_tail->next = create_SoundChunk (decoder, sound_sample->length); + chunks_tail = chunks_tail->next; + chunks_tail->tag_me = 1; + chunks_tail->track_num = track_count - 1; + chunks_tail->text = pages[page]; + chunks_tail->callback = cb; + // TODO: We may have to tag only one page with a callback + //cb = NULL; + last_sub = chunks_tail; sound_sample->length += decoder->length; } } @@ -562,12 +548,12 @@ SpliceTrack (UNICODE *TrackName, UNICODE *TrackText, UNICODE *TimeStamp, TFB_Tra num_timestamps = num_pages; } - startTime = 0; + // Reset the offset for the new track + dec_offset = 0; for (page = 0; page < num_timestamps; ++page) { - static float old_volume = 0.0f; TFB_SoundDecoder *decoder = SoundDecoder_Load (contentDir, - TrackName, 4096, startTime, time_stamps[page]); + TrackName, 4096, dec_offset, time_stamps[page]); if (!decoder) { log_add (log_Warning, "SpliceTrack(): couldn't load %s", TrackName); @@ -576,275 +562,246 @@ SpliceTrack (UNICODE *TrackName, UNICODE *TrackText, UNICODE *TimeStamp, TFB_Tra if (!sound_sample) { - TFB_SoundChainData* scd = HCalloc (sizeof (TFB_SoundChainData)); - track_mutex = CreateMutex ("trackplayer mutex", SYNC_CLASS_TOPLEVEL | SYNC_CLASS_AUDIO); - sound_sample = (TFB_SoundSample *) HMalloc (sizeof (TFB_SoundSample)); - sound_sample->data = scd; + sound_sample = HCalloc (sizeof (*sound_sample)); sound_sample->callbacks = trackCBs; sound_sample->num_buffers = 8; sound_sample->buffer_tag = HCalloc (sizeof (TFB_SoundTag) * sound_sample->num_buffers); - sound_sample->buffer = HMalloc (sizeof (audio_Object) * sound_sample->num_buffers); - sound_sample->decoder = decoder; + sound_sample->buffer = HCalloc (sizeof (audio_Object) * sound_sample->num_buffers); sound_sample->length = 0; audio_GenBuffers (sound_sample->num_buffers, sound_sample->buffer); - chain_head = create_soundchain (decoder, 0.0); - chain_tail = chain_head; - scd->read_chain_ptr = chain_head; - scd->play_chain_ptr = NULL; + chunks_head = create_SoundChunk (decoder, 0.0); + chunks_tail = chunks_head; } else { - chain_tail->next = create_soundchain (decoder, sound_sample->length); - chain_tail = chain_tail->next; + chunks_tail->next = create_SoundChunk (decoder, sound_sample->length); + chunks_tail = chunks_tail->next; } - startTime += (unsigned long)(decoder->length * 1000); + dec_offset += (unsigned long)(decoder->length * 1000); #if 0 log_add (log_Debug, "page (%d of %d): %d ts: %d", page, num_pages, - startTime, time_stamps[page]); + dec_offset, time_stamps[page]); #endif - if (decoder->is_null) - { - if (speechVolumeScale != 0.0f) - { - /* No voice ogg available so zeroing speech volume to - ensure proper operation of oscilloscope and music fading */ - old_volume = speechVolumeScale; - speechVolumeScale = 0.0f; - log_add (log_Warning, "SpliceTrack(): no voice ogg" - " available so setting speech volume to zero"); - } - } - else if (old_volume != 0.0f && speechVolumeScale != old_volume) - { - /* This time voice ogg is there */ - log_add (log_Warning, "SpliceTrack(): restoring speech volume"); - speechVolumeScale = old_volume; - old_volume = 0.0f; - } - sound_sample->length += decoder->length; if (!no_page_break) { - chain_tail->tag_me = 1; - // chain_tail->tag.value = (void *)(((track_count - 1) << 8) | page); - chain_tail->track_num = track_count - 1; + chunks_tail->tag_me = 1; + chunks_tail->track_num = track_count - 1; if (page < num_pages) { - chain_tail->text = pages[page]; - last_sub = chain_tail; + chunks_tail->text = pages[page]; + last_sub = chunks_tail; } - chain_tail->callback = cb; - // We have to tag only one page with a callback - cb = NULL; + chunks_tail->callback = cb; + // TODO: We may have to tag only one page with a callback + //cb = NULL; } no_page_break = 0; } } } -void -PauseTrack (void) +// This function figures out the chunk that should be playing based on +// 'offset' into the total playing time of all tracks. It then sets +// the speech source's sample to the necessary decoder and seeks the +// decoder to the proper point. +// XXX: This means that whatever speech has already been queued on the +// source will continue playing, so we may need some small timing +// adjustments. It may be simpler to just call PlayStream(). +static void +seek_track (sint32 offset) { - if (sound_sample && sound_sample->decoder) + TFB_SoundChunk *cur; + TFB_SoundChunk *last_tag = NULL; + + if (!sound_sample) + return; // nothing to recompute + + if (offset < 0) + offset = 0; + else if ((uint32)offset > tracks_length) + offset = tracks_length + 1; + + // Adjusting the stream start time is the only way we can arbitrarily + // seek the stream right now + soundSource[SPEECH_SOURCE].start_time = GetTimeCounter () - offset; + + // Find the chunk that should be playing at this time offset + for (cur = chunks_head; cur && offset >= chunk_end_time (cur); + cur = cur->next) { - LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - PauseStream (SPEECH_SOURCE); - soundSource[SPEECH_SOURCE].pause_time = GetTimeCounter (); - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + // .. looking for the last callback as we go along + // XXX: this effectively set the last point where Fot is looking at. + // TODO: this should be somehow changed if we implement more + // callbacks, like Melnorme trading, offloading at Starbase, etc. + if (cur->tag_me) + last_tag = cur; + } + + if (cur) + { + cur_chunk = cur; + SoundDecoder_Seek (cur->decoder, (uint32) (((float)offset / ONE_SECOND + - cur->start_time) * 1000)); + sound_sample->decoder = cur->decoder; + + if (cur->tag_me) + last_tag = cur; + if (last_tag) + DoTrackTag (last_tag); + } + else + { // The offset is beyond the length of all tracks + StopStream (SPEECH_SOURCE); + cur_chunk = NULL; + cur_sub_chunk = NULL; } } -void -recompute_track_pos (TFB_SoundSample *sample, TFB_SoundChain *head, sint32 offset) +static sint32 +get_current_track_pos (void) { - TFB_SoundChainData* scd; - TFB_SoundChain *cur = head; - - if (! sample) - return; - - scd = (TFB_SoundChainData*) sample->data; - - while (cur->next && - (sint32)(cur->next->start_time * (float)ONE_SECOND) < offset) - { - if (cur->tag_me) - DoTrackTag (cur); - cur = cur->next; - } - if (cur->tag_me) - DoTrackTag (cur); - if ((sint32)((cur->start_time + cur->decoder->length) * (float)ONE_SECOND) < offset) - { - scd->read_chain_ptr = NULL; - sample->decoder = NULL; - } - else - { - scd->read_chain_ptr = cur; - SoundDecoder_Seek(scd->read_chain_ptr->decoder, - (uint32)(1000 * (offset / (float)ONE_SECOND - cur->start_time))); - } + sint32 start_time = soundSource[SPEECH_SOURCE].start_time; + sint32 pos = GetTimeCounter () - start_time; + if (pos < 0) + pos = 0; + else if ((uint32)pos > tracks_length) + pos = tracks_length; + return pos; } void FastReverse_Smooth (void) { - if (sound_sample) - { - sint32 offset; - uint32 cur_time; - sint32 total_length = (sint32)((chain_tail->start_time + - chain_tail->decoder->length) * (float)ONE_SECOND); - LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - PauseStream (SPEECH_SOURCE); - cur_time = GetTimeCounter(); - track_pos_changed = 1; - if ((sint32)cur_time - soundSource[SPEECH_SOURCE].start_time > total_length) - soundSource[SPEECH_SOURCE].start_time = - (sint32)cur_time - total_length; + sint32 offset; - soundSource[SPEECH_SOURCE].start_time += ACCEL_SCROLL_SPEED; - if (soundSource[SPEECH_SOURCE].start_time > (sint32)cur_time) - { - soundSource[SPEECH_SOURCE].start_time = cur_time; - offset = 0; - } - else - offset = cur_time - soundSource[SPEECH_SOURCE].start_time; - recompute_track_pos (sound_sample, chain_head, offset); - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - PlayingTrack(); + if (!sound_sample) + return; // nothing is playing, so.. bye! - } -} + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + offset = get_current_track_pos (); + offset -= ACCEL_SCROLL_SPEED; + seek_track (offset); -void -FastReverse_Page (void) -{ - if (sound_sample) - { - TFB_SoundChainData* scd = (TFB_SoundChainData*) sound_sample->data; - TFB_SoundChain *prev; - - LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - prev = get_chain_previous (chain_head, scd->play_chain_ptr); - if (prev) - { - scd->read_chain_ptr = prev; - PlayStream (sound_sample, - SPEECH_SOURCE, false, - speechVolumeScale != 0.0f, true); - } - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - } + // Restart the stream in case it ended previously + if (!PlayingStream (SPEECH_SOURCE)) + PlayStream (sound_sample, SPEECH_SOURCE, false, true, false); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); } void FastForward_Smooth (void) { - if (sound_sample) - { - sint32 offset; - uint32 cur_time; - sint32 total_length = (sint32)((chain_tail->start_time + - chain_tail->decoder->length) * (float)ONE_SECOND); - LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - PauseStream (SPEECH_SOURCE); - cur_time = GetTimeCounter(); - soundSource[SPEECH_SOURCE].start_time -= ACCEL_SCROLL_SPEED; - if ((sint32)cur_time - soundSource[SPEECH_SOURCE].start_time > total_length) - soundSource[SPEECH_SOURCE].start_time = - (sint32)cur_time - total_length - 1; - offset = cur_time - soundSource[SPEECH_SOURCE].start_time; - track_pos_changed = 1; - recompute_track_pos (sound_sample, chain_head, offset); - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - PlayingTrack (); - } -} + sint32 offset; -int -FastForward_Page (void) -{ - if (sound_sample) - { - TFB_SoundChainData* scd = (TFB_SoundChainData*) sound_sample->data; - TFB_SoundChain *cur = scd->play_chain_ptr; + if (!sound_sample) + return; // nothing is playing, so.. bye! - LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - while (cur->next && !cur->next->tag_me) - cur = cur->next; - if (cur->next) - { - scd->read_chain_ptr = cur->next; - PlayStream (sound_sample, - SPEECH_SOURCE, false, - speechVolumeScale != 0.0f, true); - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - return TRUE; - } - else //means there are no more pages left - { - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - return FALSE; - } - } - return TRUE; -} - -// tells current position of streaming speech -int -GetSoundInfo (int max_len) -{ - uint32 length, offset; LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - if (soundSource[SPEECH_SOURCE].sample) - { - length = (uint32) (soundSource[SPEECH_SOURCE].sample->length * (float)ONE_SECOND); - offset = (uint32) (GetTimeCounter () - soundSource[SPEECH_SOURCE].start_time); - } - else - { - UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - return (0); + + offset = get_current_track_pos (); + offset += ACCEL_SCROLL_SPEED; + seek_track (offset); + + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +void +FastReverse_Page (void) +{ + TFB_SoundChunk *prev; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + prev = find_prev_page (cur_sub_chunk); + if (prev) + { // Set the chunk to be played + cur_chunk = prev; + cur_sub_chunk = prev; + // Decoder will be set in OnStreamStart() + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); } UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); - if (offset > length) - return max_len; - return (int)(max_len * offset / length); -} - -TFB_SoundChain * -create_soundchain (TFB_SoundDecoder *decoder, float startTime) -{ - TFB_SoundChain *chain; - chain = HMalloc (sizeof (TFB_SoundChain)); - chain->decoder = decoder; - chain->next = NULL; - chain->start_time = startTime; - chain->tag_me = 0; - chain->text = 0; - return chain; } void -destroy_soundchain (TFB_SoundChain *chain) +FastForward_Page (void) { - TFB_SoundChain *next = NULL; - for ( ; chain; chain = next) + TFB_SoundChunk *next; + + if (!sound_sample) + return; // nothing is playing, so.. bye! + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + next = find_next_page (cur_sub_chunk); + if (next) + { // Set the chunk to be played + cur_chunk = next; + cur_sub_chunk = next; + // Decoder will be set in OnStreamStart() + PlayStream (sound_sample, SPEECH_SOURCE, false, true, true); + } + else + { // End of the tracks (pun intended) + seek_track (tracks_length + 1); + } + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); +} + +// Tells current position of streaming speech in the units +// specified by the caller. +// This is normally called on the ambient_anim_task thread. +int +GetTrackPosition (int in_units) +{ + uint32 offset; + uint32 length = tracks_length; + // detach from the static one, otherwise, we can race for + // it and thus divide by 0 + + if (!sound_sample || length == 0) + return 0; // nothing is playing + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + offset = get_current_track_pos (); + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return in_units * offset / length; +} + +TFB_SoundChunk * +create_SoundChunk (TFB_SoundDecoder *decoder, float start_time) +{ + TFB_SoundChunk *chunk; + chunk = HMalloc (sizeof (TFB_SoundChunk)); + chunk->decoder = decoder; + chunk->next = NULL; + chunk->start_time = start_time; + chunk->tag_me = 0; + chunk->text = 0; + return chunk; +} + +void +destroy_SoundChunk_list (TFB_SoundChunk *chunk) +{ + TFB_SoundChunk *next = NULL; + for ( ; chunk; chunk = next) { - next = chain->next; - if (chain->decoder) - SoundDecoder_Free (chain->decoder); - HFree (chain->text); - HFree (chain); + next = chunk->next; + if (chunk->decoder) + SoundDecoder_Free (chunk->decoder); + HFree (chunk->text); + HFree (chunk); } } -void -destroy_sound_sample (TFB_SoundSample *sample) +static void +destroy_SoundSample (TFB_SoundSample *sample) { if (sample->buffer) { @@ -856,20 +813,78 @@ destroy_sound_sample (TFB_SoundSample *sample) HFree (sample); } -TFB_SoundChain * -get_chain_previous (TFB_SoundChain *head, TFB_SoundChain *current) +// Returns the next chunk with a subtitle +TFB_SoundChunk * +find_next_page (TFB_SoundChunk *cur) { - TFB_SoundChain *prev, *last_valid = NULL; - prev = head; - if (prev == current) - return prev; - while (prev->next) + if (!cur) + return NULL; + for (cur = cur->next; cur && !cur->tag_me; cur = cur->next) + ; + return cur; +} + +// Returns the previous chunk with a subtitle. +// cur == 0 is treated as end of the list. +TFB_SoundChunk * +find_prev_page (TFB_SoundChunk *cur) +{ + TFB_SoundChunk *prev; + TFB_SoundChunk *last_valid = chunks_head; + + if (cur == chunks_head) + return cur; // cannot go below the first track + + for (prev = chunks_head; prev && prev != cur; prev = prev->next) { if (prev->tag_me) last_valid = prev; - if (prev->next == current) - return last_valid; - prev = prev->next; } - return head; + return last_valid; +} + + +// External access to the chunks list +SUBTITLE_REF +GetFirstTrackSubtitle (void) +{ + return chunks_head; +} + +// External access to the chunks list +SUBTITLE_REF +GetNextTrackSubtitle (SUBTITLE_REF LastRef) +{ + if (!LastRef) + return NULL; // enumeration already ended + + return find_next_page (LastRef); +} + +// External access to the chunk subtitles +const UNICODE * +GetTrackSubtitleText (SUBTITLE_REF SubRef) +{ + if (!SubRef) + return NULL; + + return SubRef->text; +} + +// External access to currently active subtitle text +// Returns NULL is none is active +const UNICODE * +GetTrackSubtitle (void) +{ + const UNICODE *cur_sub = NULL; + + if (!sound_sample) + return NULL; // not playing anything + + LockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + if (cur_sub_chunk) + cur_sub = cur_sub_chunk->text; + UnlockMutex (soundSource[SPEECH_SOURCE].stream_mutex); + + return cur_sub; } diff --git a/sc2/src/libs/sound/trackplayer.h b/sc2/src/libs/sound/trackplayer.h index 026634a39..0ca14b4b3 100644 --- a/sc2/src/libs/sound/trackplayer.h +++ b/sc2/src/libs/sound/trackplayer.h @@ -21,22 +21,33 @@ #include "libs/compiler.h" - typedef void (*TFB_TrackCB) (void); #define ACCEL_SCROLL_SPEED 300 -void ResumeTrack(void); -void PauseTrack(void); -COUNT PlayingTrack(void); -void JumpTrack(void); -void FastForward_Smooth(void); -int FastForward_Page(void); -void FastReverse_Smooth(void); -void FastReverse_Page(void); -void StopTrack(void); -void SpliceTrack(UNICODE *filespec, UNICODE *textspec, UNICODE *TimeStamp, TFB_TrackCB cb); -void SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText); -int GetSoundInfo (int max_len); +extern void PlayTrack (void); +extern void StopTrack (void); +extern void JumpTrack (void); +extern void PauseTrack (void); +extern void ResumeTrack (void); +extern COUNT PlayingTrack (void); + +extern void FastReverse_Smooth (void); +extern void FastForward_Smooth (void); +extern void FastReverse_Page (void); +extern void FastForward_Page (void); + +extern void SpliceTrack (UNICODE *filespec, UNICODE *textspec, UNICODE *TimeStamp, TFB_TrackCB cb); +extern void SpliceMultiTrack (UNICODE *TrackNames[], UNICODE *TrackText); + +extern int GetTrackPosition (int in_units); + +typedef struct tfb_soundchunk *SUBTITLE_REF; + +extern SUBTITLE_REF GetFirstTrackSubtitle (void); +extern SUBTITLE_REF GetNextTrackSubtitle (SUBTITLE_REF LastRef); +extern const UNICODE *GetTrackSubtitleText (SUBTITLE_REF SubRef); + +extern const UNICODE *GetTrackSubtitle (void); #endif diff --git a/sc2/src/options.c b/sc2/src/options.c index 0f289cee9..d8b7acbac 100644 --- a/sc2/src/options.c +++ b/sc2/src/options.c @@ -507,6 +507,7 @@ BOOLEAN loadAddon (const char *addon) { uio_DirHandle *addonsDir, *addonDir; + int numLoaded; addonsDir = uio_openDirRelative (contentDir, "addons", 0); if (addonsDir == NULL) @@ -525,11 +526,12 @@ loadAddon (const char *addon) return FALSE; } - loadIndices (addonDir); + numLoaded = loadIndices (addonDir); uio_closeDir (addonDir); uio_closeDir (addonsDir); - return TRUE; + + return (numLoaded > 0); } void diff --git a/sc2/src/uqm/comm.c b/sc2/src/uqm/comm.c index 8fafbe7d4..616581b3e 100644 --- a/sc2/src/uqm/comm.c +++ b/sc2/src/uqm/comm.c @@ -38,7 +38,7 @@ #include "libs/graphics/gfx_common.h" #include "libs/inplib.h" #include "libs/sound/sound.h" -#include "libs/sound/trackint.h" +#include "libs/sound/trackplayer.h" #include "libs/log.h" #include @@ -76,14 +76,18 @@ typedef struct encounter_state } ENCOUNTER_STATE; static ENCOUNTER_STATE *pCurInputState; -static SUBTITLE_STATE subtitle_state = DONE_SUBTITLE; -static Mutex subtitle_mutex; -TEXT SubtitleText; +// Mutex guards accesses to SubtitleText, last_subtitle and clear_subtitles. +static Mutex subtitle_mutex; +// These vars are indirectly accessed by the ambient_anim_task +static volatile BOOLEAN clear_subtitles; +static TEXT SubtitleText; static const UNICODE * volatile last_subtitle; -CONTEXT TextCacheContext; -FRAME TextCacheFrame; +static CONTEXT TextCacheContext; +static FRAME TextCacheFrame; + +volatile BOOLEAN ClearSummary; RECT CommWndRect = { // default values; actually inited by HailAlien() @@ -91,6 +95,10 @@ RECT CommWndRect = { {0, 0} }; +static void ClearSubtitles (void); +static void CheckSubtitles (void); + + /* _count_lines - sees how many lines a given input string would take to * display given the line wrapping information */ @@ -432,9 +440,6 @@ uninit_communication (void) DestroyMutex (subtitle_mutex); } -volatile BOOLEAN ClearSummary; -static volatile BOOLEAN ClearSubtitle; - static void RefreshResponses (ENCOUNTER_STATE *pES) { @@ -488,8 +493,6 @@ RefreshResponses (ENCOUNTER_STATE *pES) static void FeedbackPlayerPhrase (UNICODE *pStr) { - last_subtitle = NULL; - SetContext (SpaceContext); BatchGraphics (); @@ -551,52 +554,54 @@ SpewPhrases (COUNT wait_track) DWORD TimeIn; COUNT which_track; FRAME F; - BOOLEAN passed = TRUE; + BOOLEAN rewind = FALSE; TimeIn = GetTimeCounter (); ContinuityBreak = FALSE; F = CommData.AlienFrame; if (wait_track == 0) - { + { // Restarting with a rewind wait_track = (COUNT)~0; which_track = (COUNT)~0; - goto Rewind; + rewind = TRUE; } which_track = PlayingTrack (); - if (which_track == 0) + if (which_track == 0 && !rewind) { // initial start of player if (wait_track == 1 || wait_track == (COUNT)~0) { - ResumeTrack (); UnlockMutex (GraphicsLock); + PlayTrack (); do { TaskSwitch (); - LockMutex (GraphicsLock); which_track = PlayingTrack (); - UnlockMutex (GraphicsLock); } while (!which_track); LockMutex (GraphicsLock); } } else if (which_track <= wait_track) + { // XXX: I don't know why this is here, but it is not harmful. + // We never actually pause in comm. ResumeTrack (); + } do { + BOOLEAN left = FALSE; + BOOLEAN right = FALSE; + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + { + which_track = 0; // abort break; + } UnlockMutex (GraphicsLock); - /* FIXME: is this a remnant of 128-tick clock? - * with 120-tick clock this will sleep for 1 tick -- - * for 1/120th of a second; if ONE_SECOND is upgraded - * it will probably sleep for 2/120th of a second. - * Might need to be fixed. - */ + // XXX: Executing this loop 64 times a second is a bit extreme SleepThreadUntil (TimeIn + (ONE_SECOND / 64)); TimeIn = GetTimeCounter (); #if DEMO_MODE || CREATE_JOURNAL @@ -608,79 +613,74 @@ SpewPhrases (COUNT wait_track) LockMutex (GraphicsLock); if (PulsedInputState.menu[KEY_MENU_CANCEL]) { - SetSliderImage (SetAbsFrameIndex (ActivityFrame, 8)); JumpTrack (); - CommData.AlienFrame = F; - do_subtitles ((void *)~0); - return (FALSE); + which_track = 0; // player stopped + break; } - if (which_track) + CheckSubtitles (); + + if (optSmoothScroll == OPT_PC) { - BOOLEAN left = FALSE; - BOOLEAN right = FALSE; - if (optSmoothScroll == OPT_PC) - { - left = PulsedInputState.menu[KEY_MENU_LEFT]; - right = PulsedInputState.menu[KEY_MENU_RIGHT]; - } - else if (optSmoothScroll == OPT_3DO) - { - left = ImmediateInputState.menu[KEY_MENU_LEFT]; - right = ImmediateInputState.menu[KEY_MENU_RIGHT]; - } - if (right) - { - SetSliderImage (SetAbsFrameIndex (ActivityFrame, 3)); - if (optSmoothScroll == OPT_PC && !FastForward_Page ()) - { - SetSliderImage (SetAbsFrameIndex (ActivityFrame, 8)); - JumpTrack (); - CommData.AlienFrame = F; - do_subtitles ((void *)~0); - return (FALSE); - } - else if (optSmoothScroll == OPT_3DO) - FastForward_Smooth (); - ContinuityBreak = TRUE; - CommData.AlienFrame = 0; - } - else if (left) - { -Rewind: - SetSliderImage (SetAbsFrameIndex (ActivityFrame, 4)); - if (optSmoothScroll == OPT_PC) - FastReverse_Page (); - else if (optSmoothScroll == OPT_3DO) - FastReverse_Smooth (); - ContinuityBreak = TRUE; - CommData.AlienFrame = 0; - } - else if (ContinuityBreak) - { - SetSliderImage (SetAbsFrameIndex (ActivityFrame, 2)); - which_track = PlayingTrack (); - if (which_track && which_track <= wait_track) - { - if (optSmoothScroll == OPT_3DO) - ResumeTrack (); - } - else - { - ContinuityBreak = FALSE; - passed = (which_track != 0); - break; - } - ContinuityBreak = FALSE; - } - else if (which_track == wait_track || wait_track == (COUNT)~0) - CommData.AlienFrame = F; + left = PulsedInputState.menu[KEY_MENU_LEFT]; + right = PulsedInputState.menu[KEY_MENU_RIGHT]; } - } while (ContinuityBreak - || ((which_track = PlayingTrack ()) && which_track <= wait_track)); + else if (optSmoothScroll == OPT_3DO) + { + left = ImmediateInputState.menu[KEY_MENU_LEFT]; + right = ImmediateInputState.menu[KEY_MENU_RIGHT]; + } + + if (right) + { + SetSliderImage (SetAbsFrameIndex (ActivityFrame, 3)); + if (optSmoothScroll == OPT_PC) + FastForward_Page (); + else if (optSmoothScroll == OPT_3DO) + FastForward_Smooth (); + ContinuityBreak = TRUE; + // XXX: Ugly hack: This causes all animations (talking and ambient) + // in ambient_anim_task to stop progressing. I see no reason why + // the animations cannot continue while seeking. This hack has + // spawned a multitude of workarounds in the comm code, and IMHO + // should be removed. + CommData.AlienFrame = 0; + } + else if (left || rewind) + { + rewind = FALSE; + SetSliderImage (SetAbsFrameIndex (ActivityFrame, 4)); + if (optSmoothScroll == OPT_PC) + FastReverse_Page (); + else if (optSmoothScroll == OPT_3DO) + FastReverse_Smooth (); + ContinuityBreak = TRUE; + // XXX: See ugly hack discussion above + CommData.AlienFrame = 0; + } + else if (ContinuityBreak) + { + // This is only done once the seeking is over (in the smooth + // scroll case, once the user releases the seek button) + ContinuityBreak = FALSE; + SetSliderImage (SetAbsFrameIndex (ActivityFrame, 2)); + } + else + { // XXX: See ugly hack discussion above + // Additionally, this used to have a buggy guard condition, which + // would cause the animations to remain paused in a couple cases + // after seeking back to the beginning. + // Broken cases were: Syreen "several hours later" and Starbase + // VUX Beast analysis by the scientist. + CommData.AlienFrame = F; + } + + which_track = PlayingTrack (); + + } while (ContinuityBreak || (which_track && which_track <= wait_track)); CommData.AlienFrame = F; - do_subtitles ((void *)~0); + ClearSubtitles (); if (!which_track || wait_track == (COUNT)~0) { // reached the end @@ -688,7 +688,9 @@ Rewind: return (FALSE); } - return (passed); + // We can only get here when we got to the requested track + // without ending or aborting + return TRUE; } static BOOLEAN @@ -848,7 +850,7 @@ typedef struct summary_state // extended state BOOLEAN Initialized; BOOLEAN PrintNext; - const TFB_SoundChain *NextSub; + SUBTITLE_REF NextSub; const UNICODE *LeftOver; } SUMMARY_STATE; @@ -863,7 +865,7 @@ DoConvSummary (SUMMARY_STATE *pSS) if (!pSS->Initialized) { pSS->PrintNext = TRUE; - pSS->NextSub = chain_head; + pSS->NextSub = GetFirstTrackSubtitle (); pSS->LeftOver = NULL; pSS->MenuRepeatDelay = 0; pSS->InputFunc = DoConvSummary; @@ -912,7 +914,7 @@ DoConvSummary (SUMMARY_STATE *pSS) oldFont = SetContextFont (TinyFont); for (row = 0; row < MAX_SUMM_ROWS && pSS->NextSub; - ++row, pSS->NextSub = pSS->NextSub->next) + ++row, pSS->NextSub = GetNextTrackSubtitle (pSS->NextSub)) { const unsigned char *next; @@ -923,7 +925,7 @@ DoConvSummary (SUMMARY_STATE *pSS) } else { - t.pStr = pSS->NextSub->text; + t.pStr = GetTrackSubtitleText (pSS->NextSub); if (!t.pStr) continue; } @@ -995,6 +997,7 @@ SelectResponse (ENCOUNTER_STATE *pES) LockMutex (GraphicsLock); FeedbackPlayerPhrase (pES->phrase_buf); StopTrack (); + ClearSubtitles (); SetSliderImage (SetAbsFrameIndex (ActivityFrame, 2)); UnlockMutex (GraphicsLock); @@ -1181,7 +1184,7 @@ DoCommunication (ENCOUNTER_STATE *pES) UnlockMutex (GraphicsLock); FlushColorXForms (); - ClearSubtitle = FALSE; + ClearSubtitles (); StopMusic (); StopSound (); @@ -1378,8 +1381,6 @@ InitCommunication (CONVERSATION which_comm) return 0; #endif - last_subtitle = NULL; - LockMutex (GraphicsLock); if (LastActivity & CHECK_LOAD) @@ -1654,62 +1655,6 @@ RaceCommunication (void) } } -SUBTITLE_STATE -do_subtitles (UNICODE *pStr) -{ - static UNICODE *last_page = NULL; - LockMutex (subtitle_mutex); - if (pStr == 0) - { - subtitle_state = DONE_SUBTITLE; - UnlockMutex (subtitle_mutex); - return (subtitle_state); - } - else if (pStr == (void *)~0) - { - subtitle_state = WAIT_SUBTITLE; - } - else - { - if (last_page == pStr) - { - UnlockMutex (subtitle_mutex); - return (subtitle_state); - } - subtitle_state = READ_SUBTITLE; - ClearSubtitle = TRUE; - } - last_page = pStr; - - switch (subtitle_state) - { - case READ_SUBTITLE: - { - /* Baseline may be updated by the ZFP */ - SubtitleText.baseline = CommData.AlienTextBaseline; - SubtitleText.align = CommData.AlienTextAlign; - SubtitleText.pStr = pStr; - SubtitleText.CharCount = (COUNT)~0; - subtitle_state = WAIT_SUBTITLE; - break; - } - case WAIT_SUBTITLE: - { - subtitle_state = DONE_SUBTITLE; - ClearSubtitle = TRUE; - } - case DONE_SUBTITLE: - break; - default: - // Should not happen - assert(false); - break; - } - UnlockMutex (subtitle_mutex); - - return (subtitle_state); -} - void RedrawSubtitles (void) { @@ -1718,23 +1663,60 @@ RedrawSubtitles (void) if (!optSubtitles) return; - t = SubtitleText; - add_text (1, &t); + LockMutex (subtitle_mutex); + if (SubtitleText.pStr) + { + t = SubtitleText; + add_text (1, &t); + } + UnlockMutex (subtitle_mutex); } -// Sets ClearSubtitle, returning the old value. The current subtitle state -// is also returned, through sub_state +// Returns clear_subtitles and resets it BOOLEAN -SetClearSubtitle (BOOLEAN flag, SUBTITLE_STATE *sub_state) +HaveSubtitlesChanged (void) { - BOOLEAN oldClearSubtitle; + BOOLEAN ret; LockMutex (subtitle_mutex); - oldClearSubtitle = ClearSubtitle; - *sub_state = subtitle_state; - ClearSubtitle = flag; + ret = clear_subtitles; + clear_subtitles = FALSE; UnlockMutex (subtitle_mutex); - return oldClearSubtitle; + return ret; } +static void +ClearSubtitles (void) +{ + LockMutex (subtitle_mutex); + clear_subtitles = TRUE; + last_subtitle = NULL; + SubtitleText.pStr = NULL; + SubtitleText.CharCount = 0; + UnlockMutex (subtitle_mutex); +} + +static void +CheckSubtitles (void) +{ + const UNICODE *pStr; + + pStr = GetTrackSubtitle (); + + LockMutex (subtitle_mutex); + if (pStr != SubtitleText.pStr) + { // Subtitles changed + clear_subtitles = TRUE; + // Baseline may be updated by the ZFP + SubtitleText.baseline = CommData.AlienTextBaseline; + SubtitleText.align = CommData.AlienTextAlign; + SubtitleText.pStr = pStr; + // may have been cleared too + if (pStr) + SubtitleText.CharCount = (COUNT)~0; + else + SubtitleText.CharCount = 0; + } + UnlockMutex (subtitle_mutex); +} diff --git a/sc2/src/uqm/comm.h b/sc2/src/uqm/comm.h index 9aa0c718d..b1ed4d6c1 100644 --- a/sc2/src/uqm/comm.h +++ b/sc2/src/uqm/comm.h @@ -21,16 +21,6 @@ #include "libs/compiler.h" #include "libs/gfxlib.h" - -typedef enum -{ - DONE_SUBTITLE, - NEXT_SUBTITLE, - READ_SUBTITLE, - SPACE_SUBTITLE, - WAIT_SUBTITLE, -} SUBTITLE_STATE; - #ifdef COMM_INTERNAL #define SLIDER_Y 107 @@ -38,8 +28,7 @@ typedef enum #include "commanim.h" -void DrawAlienFrame (FRAME aframe, SEQUENCE *pSeq); -BOOLEAN SetClearSubtitle (BOOLEAN flag, SUBTITLE_STATE *sub_state); +extern void DrawAlienFrame (FRAME aframe, SEQUENCE *pSeq); extern LOCDATA CommData; extern volatile BOOLEAN ClearSummary; @@ -48,11 +37,11 @@ extern volatile BOOLEAN ClearSummary; extern void init_communication (void); extern void uninit_communication (void); -extern SUBTITLE_STATE do_subtitles (UNICODE *pStr); extern void AlienTalkSegue (COUNT wait_track); BOOLEAN getLineWithinWidth(TEXT *pText, const unsigned char **startNext, SIZE maxWidth, COUNT maxChars); -void RedrawSubtitles (void); +extern void RedrawSubtitles (void); +extern BOOLEAN HaveSubtitlesChanged (void); extern RECT CommWndRect; /* comm window rect */ diff --git a/sc2/src/uqm/commanim.c b/sc2/src/uqm/commanim.c index 235362da5..b472bbe32 100644 --- a/sc2/src/uqm/commanim.c +++ b/sc2/src/uqm/commanim.c @@ -454,15 +454,15 @@ ambient_anim_task (void *data) { CONTEXT OldContext; BOOLEAN CheckSub = FALSE; - BOOLEAN ClearSub; - SUBTITLE_STATE sub_state; + BOOLEAN SubtitleChange; - ClearSub = SetClearSubtitle (FALSE, &sub_state); + // Clearing any active subtitles counts as 'change' + SubtitleChange = HaveSubtitlesChanged (); OldContext = SetContext (TaskContext); if (ColorChange || ClearSummary) - { + { // Redraw the whole comm screen FRAME F; F = CommData.AlienFrame; CommData.AlienFrame = CommFrame; @@ -470,16 +470,16 @@ ambient_anim_task (void *data) &Sequencer[CommData.NumAnimations - 1]); CommData.AlienFrame = F; CheckSub = TRUE; - ClearSub = ClearSummary; + SubtitleChange = ClearSummary; ColorChange = FALSE; ClearSummary = FALSE; } - if (Change || ClearSub) + if (Change || SubtitleChange) { STAMP s; s.origin.x = -SAFE_X; s.origin.y = 0; - if (ClearSub) + if (SubtitleChange) { s.frame = CommFrame; DrawStamp (&s); @@ -487,14 +487,14 @@ ambient_anim_task (void *data) i = CommData.NumAnimations; while (i--) { - if (ClearSub || FrameChanged[i]) + if (SubtitleChange || FrameChanged[i]) { s.frame = AnimFrame[i]; DrawStamp (&s); FrameChanged[i] = 0; } } - if (ClearSub && TransitionFrame) + if (SubtitleChange && TransitionFrame) { s.frame = TransitionFrame; DrawStamp (&s); @@ -515,7 +515,7 @@ ambient_anim_task (void *data) CheckSub = TRUE; } - if (CheckSub && sub_state >= SPACE_SUBTITLE) + if (CheckSub) RedrawSubtitles (); SetContext (OldContext); @@ -534,7 +534,7 @@ ambient_anim_task (void *data) } Task -StartCommAnimTask(void) +StartCommAnimTask (void) { return AssignTask (ambient_anim_task, 3072, "ambient animations"); } diff --git a/sc2/src/uqm/confirm.c b/sc2/src/uqm/confirm.c index 4f74f2940..6784abdce 100644 --- a/sc2/src/uqm/confirm.c +++ b/sc2/src/uqm/confirm.c @@ -18,7 +18,6 @@ #include "controls.h" #include "commglue.h" -#include "comm.h" #include "colors.h" #include "settings.h" #include "setup.h" @@ -180,11 +179,7 @@ DoConfirmExit (void) !(LastActivity & CHECK_RESTART)) ResumeGameClock (); if (CommData.ConversationPhrases && PlayingTrack ()) - { ResumeTrack (); - if (CommData.AlienTransitionDesc.AnimFlags & TALK_DONE) - do_subtitles ((void *)~0); - } return (result); } diff --git a/sc2/src/uqm/oscill.c b/sc2/src/uqm/oscill.c index 872ae54f9..eed60e1d0 100644 --- a/sc2/src/uqm/oscill.c +++ b/sc2/src/uqm/oscill.c @@ -156,7 +156,7 @@ Slider (void) if (sliderDisabled) return; - offs = GetSoundInfo (sliderSpace); + offs = GetTrackPosition (sliderSpace); if (offs != last_offs || sliderChanged) { sliderChanged = FALSE; diff --git a/sc2/src/uqm/setup.c b/sc2/src/uqm/setup.c index ffd6e2949..7d7f28f00 100644 --- a/sc2/src/uqm/setup.c +++ b/sc2/src/uqm/setup.c @@ -33,6 +33,7 @@ #include "libs/uio.h" #include "libs/file.h" #include "libs/graphics/gfx_common.h" +#include "libs/sound/sound.h" #include "libs/threadlib.h" #include "libs/vidlib.h" #include "libs/log.h" @@ -120,7 +121,9 @@ LoadKernel (int argc, char *argv[]) loadAddon ("3domusic"); } - loadAddon ("3dovoice"); /* Always try to use voice data */ + /* Always try to use voice data */ + if (!loadAddon ("3dovoice")) + speechVolumeScale = 0.0f; // XXX: need better no-speech indicator if (optPrecursorsMusic) { diff --git a/sc2/src/uqm/util.c b/sc2/src/uqm/util.c index 8f47a5b27..0d15724b3 100644 --- a/sc2/src/uqm/util.c +++ b/sc2/src/uqm/util.c @@ -17,7 +17,6 @@ */ #include "commglue.h" -#include "comm.h" #include "controls.h" #include "util.h" #include "setup.h" @@ -224,11 +223,7 @@ PauseGame (void) LOBYTE (GLOBAL (CurrentActivity)) != WON_LAST_BATTLE) ResumeGameClock (); if (CommData.ConversationPhrases && PlayingTrack ()) - { ResumeTrack (); - if (CommData.AlienTransitionDesc.AnimFlags & TALK_DONE) - do_subtitles ((void *)~0); - } UnlockMutex (GraphicsLock);