mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-25 05:21:02 +00:00
- Added support for custom loop points for songs. This does not work with MP3 because of the
way MP3 obfuscates custom tags. Vorbis and FLAC are fine. (I could make it work with MP3, but you should be using Vorbis instead.) They are: * LOOP_START: Start time for the loop. If omitted, the song repeats from the beginning. * LOOP_END: End time for the loop. If omitted, the song loops at the end. (If you need to specify this, why aren't you using a shorter song.) You only need to specify one of these tags to set the custom loop. Naturally, you can set them both, as well. The format for each tag is the same: * If it contains a colon (:), it specifies by time. This may be of the form 00:00:00.00 (HH:MM:SS.ss) to specify by play. Various parts may be left off. 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 and accurate to one millisecond. * 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. * Any characters other than digits (0-9), colons (:), or a single decimal point for the seconds portion will result in the tag being ignored. SVN r2424 (trunk)
This commit is contained in:
parent
305a03d7c8
commit
99a2014ead
3 changed files with 171 additions and 0 deletions
|
@ -1772,6 +1772,100 @@ int S_FindSkinnedSoundEx (AActor *actor, const char *name, const char *extendedn
|
||||||
return S_FindSkinnedSound (actor, id);
|
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
|
// CCMD soundlist
|
||||||
|
|
|
@ -354,6 +354,7 @@ void S_UnloadSound (sfxinfo_t *sfx);
|
||||||
sfxinfo_t *S_LoadSound(sfxinfo_t *sfx);
|
sfxinfo_t *S_LoadSound(sfxinfo_t *sfx);
|
||||||
unsigned int S_GetMSLength(FSoundID sound);
|
unsigned int S_GetMSLength(FSoundID sound);
|
||||||
void S_ParseMusInfo();
|
void S_ParseMusInfo();
|
||||||
|
bool S_ParseTimeTag(const char *tag, bool *as_samples, unsigned int *time);
|
||||||
|
|
||||||
// [RH] Prints sound debug info to the screen.
|
// [RH] Prints sound debug info to the screen.
|
||||||
// Modelled after Hexen's noise cheat.
|
// Modelled after Hexen's noise cheat.
|
||||||
|
|
|
@ -61,6 +61,7 @@ extern HWND Window;
|
||||||
#include "v_video.h"
|
#include "v_video.h"
|
||||||
#include "v_palette.h"
|
#include "v_palette.h"
|
||||||
#include "cmdlib.h"
|
#include "cmdlib.h"
|
||||||
|
#include "s_sound.h"
|
||||||
|
|
||||||
// MACROS ------------------------------------------------------------------
|
// MACROS ------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -1435,6 +1436,26 @@ SoundStream *FMODSoundRenderer::CreateStream (SoundStreamCallback callback, int
|
||||||
return capsule;
|
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
|
// FMODSoundRenderer :: OpenStream
|
||||||
|
@ -1496,6 +1517,61 @@ SoundStream *FMODSoundRenderer::OpenStream(const char *filename_or_data, int fla
|
||||||
}
|
}
|
||||||
if (result == FMOD_OK)
|
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 new FMODStreamCapsule(stream, this, url ? filename_or_data : NULL);
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
Loading…
Reference in a new issue