diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp index 7f9cd8592e..e121bc2ee1 100644 --- a/src/s_advsound.cpp +++ b/src/s_advsound.cpp @@ -1772,6 +1772,100 @@ int S_FindSkinnedSoundEx (AActor *actor, const char *name, const char *extendedn return S_FindSkinnedSound (actor, id); } +//========================================================================== +// +// S_ParseTimeTag +// +// Passed the value of a loop point tag, converts it to numbers. +// +// This may be of the form 00:00:00.00 (HH:MM:SS.ss) to specify by play +// time. Various parts may be left off. The only requirement is that it +// contain a colon. e.g. To start the loop at 20 seconds in, you can use +// ":20", "0:20", "00:00:20", ":20.0", etc. Values after the decimal are +// fractions of a second. +// +// If you don't include a colon but just have a raw number, then it's +// the number of PCM samples at which to loop. +// +// Returns true if the tag made sense, false if not. +// +//========================================================================== + +bool S_ParseTimeTag(const char *tag, bool *as_samples, unsigned int *time) +{ + const char *bit = tag; + char ms[3] = { 0 }; + unsigned int times[3] = { 0 }; + int ms_pos = 0, time_pos = 0; + bool pcm = true, in_ms = false; + + for (bit = tag; *bit != '\0'; ++bit) + { + if (*bit >= '0' && *bit <= '9') + { + if (in_ms) + { + // Ignore anything past three fractional digits. + if (ms_pos < 3) + { + ms[ms_pos++] = *bit - '0'; + } + } + else + { + times[time_pos] = times[time_pos] * 10 + *bit - '0'; + } + } + else if (*bit == ':') + { + if (in_ms) + { // If we already specified milliseconds, we can't take any more parts. + return false; + } + pcm = false; + if (++time_pos == countof(times)) + { // Time too long. (Seriously, starting the loop days in?) + return false; + } + } + else if (*bit == '.') + { + if (pcm || in_ms) + { // It doesn't make sense to have fractional PCM values. + // It also doesn't make sense to have more than one dot. + return false; + } + in_ms = true; + } + else + { // Anything else: We don't understand this. + return false; + } + } + if (pcm) + { + *as_samples = true; + *time = times[0]; + } + else + { + unsigned int mytime = 0; + + // Add in hours, minutes, and seconds + for (int i = 0; i <= time_pos; ++i) + { + mytime = mytime * 60 + times[i]; + } + + // Add in milliseconds + mytime = mytime * 1000 + ms[0] * 100 + ms[1] * 10 + ms[2]; + + *as_samples = false; + *time = mytime; + } + return true; +} + //========================================================================== // // CCMD soundlist diff --git a/src/s_sound.h b/src/s_sound.h index 41820bec78..8d89d1a0de 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -354,6 +354,7 @@ void S_UnloadSound (sfxinfo_t *sfx); sfxinfo_t *S_LoadSound(sfxinfo_t *sfx); unsigned int S_GetMSLength(FSoundID sound); void S_ParseMusInfo(); +bool S_ParseTimeTag(const char *tag, bool *as_samples, unsigned int *time); // [RH] Prints sound debug info to the screen. // Modelled after Hexen's noise cheat. diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index e167d389f8..e94ec04b7d 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -61,6 +61,7 @@ extern HWND Window; #include "v_video.h" #include "v_palette.h" #include "cmdlib.h" +#include "s_sound.h" // MACROS ------------------------------------------------------------------ @@ -1435,6 +1436,26 @@ SoundStream *FMODSoundRenderer::CreateStream (SoundStreamCallback callback, int return capsule; } +//========================================================================== +// +// GetTagData +// +// Checks for a string-type tag, and returns its data. +// +//========================================================================== + +const char *GetTagData(FMOD::Sound *sound, const char *tag_name) +{ + FMOD_TAG tag; + + if (FMOD_OK == sound->getTag(tag_name, 0, &tag) && + (tag.datatype == FMOD_TAGDATATYPE_STRING || tag.datatype == FMOD_TAGDATATYPE_STRING_UTF8)) + { + return (const char *)tag.data; + } + return NULL; +} + //========================================================================== // // FMODSoundRenderer :: OpenStream @@ -1496,6 +1517,61 @@ SoundStream *FMODSoundRenderer::OpenStream(const char *filename_or_data, int fla } if (result == FMOD_OK) { + // Handle custom loop starts by checking for a "loop_start" tag. +#if 0 + FMOD_TAG tag; + int numtags; + if (FMOD_OK == stream->getNumTags(&numtags, NULL)) + { + for (int i = 0; i < numtags; ++i) + { + if (FMOD_OK == stream->getTag(NULL, i, &tag)) + { + Printf("Tag %2d. %d %s = %s\n", i, tag.datatype, tag.name, tag.data); + } + } + } +#endif + const char *tag_data; + unsigned int looppt[2]; + bool looppt_as_samples[2], have_looppt[2] = { false }; + static const char *const loop_tags[2] = { "LOOP_START", "LOOP_END" }; + + for (int i = 0; i < 2; ++i) + { + if (NULL != (tag_data = GetTagData(stream, loop_tags[i]))) + { + if (S_ParseTimeTag(tag_data, &looppt_as_samples[i], &looppt[i])) + { + have_looppt[i] = true; + } + else + { + Printf("Invalid %s tag: '%s'\n", loop_tags[i], tag_data); + } + } + } + if (have_looppt[0] && !have_looppt[1]) + { // Have a start tag, but not an end tag: End at the end of the song. + have_looppt[1] = (FMOD_OK == stream->getLength(&looppt[1], FMOD_TIMEUNIT_PCM)); + looppt_as_samples[1] = true; + } + else if (!have_looppt[0] && have_looppt[1]) + { // Have an end tag, but no start tag: Start at beginning of the song. + looppt[0] = 0; + looppt_as_samples[0] = true; + have_looppt[0] = true; + } + if (have_looppt[0] && have_looppt[1]) + { // Have both loop points: Try to set the loop. + FMOD_RESULT res = stream->setLoopPoints( + looppt[0], looppt_as_samples[0] ? FMOD_TIMEUNIT_PCM : FMOD_TIMEUNIT_MS, + looppt[1] - 1, looppt_as_samples[1] ? FMOD_TIMEUNIT_PCM : FMOD_TIMEUNIT_MS); + if (res != FMOD_OK) + { + Printf("Setting custom song loop points for song failed. Error %d\n", res); + } + } return new FMODStreamCapsule(stream, this, url ? filename_or_data : NULL); } return NULL;