diff --git a/include/dmusic.h b/include/dmusic.h index f5ada4a..9d45789 100644 --- a/include/dmusic.h +++ b/include/dmusic.h @@ -230,6 +230,17 @@ DMAPI DmGuid const* DmSegment_getGuid(DmSegment const* slf); /// \return A read-only pointer to the segment's name in UTF-8. DMAPI char const* DmSegment_getName(DmSegment const* slf); +/// \brief Get the length of the given segment in seconds. +/// +/// The number of PCM samples required to render `n` seconds of the segment can be calculated like this: +/// \f$x_{samples} = n \cdot x_{rate} \cdot n_{channels}\f$ +/// where \f$x_{rate}\f$ is the sample rate to use (usually 44100 Hz) and \f$n_{channels}\f$ is the number +/// of PCM output channels (1 for mono and 2 for stereo PCM). +/// +/// \param slf[in] The segment to get the length of +/// \return The number of seconds one repeat of the segment takes. +DMAPI double DmSegment_getLength(DmSegment const* slf); + /// \} /// \defgroup DmLoaderGroup Loader diff --git a/src/Common.c b/src/Common.c index 6651be1..9c49601 100644 --- a/src/Common.c +++ b/src/Common.c @@ -85,11 +85,16 @@ uint32_t Dm_getMeasureLength(DmTimeSignature sig) { return v; } -double Dm_getTicksPerSample(DmTimeSignature time_signature, double beats_per_minute, uint32_t sample_rate) { +double Dm_getTicksPerSecond(DmTimeSignature time_signature, double beats_per_minute) { uint32_t pulses_per_beat = Dm_getBeatLength(time_signature); // unit: music-time per beat double beats_per_second = beats_per_minute / DmInt_SECONDS_PER_MINUTE; // unit: 1 per second double pulses_per_second = pulses_per_beat * beats_per_second; // unit: music-time per second - double pulses_per_sample = pulses_per_second / sample_rate; // unit: music-time per sample + return pulses_per_second; +} + +double Dm_getTicksPerSample(DmTimeSignature time_signature, double beats_per_minute, uint32_t sample_rate) { + double pulses_per_second = Dm_getTicksPerSecond(time_signature, beats_per_minute); // unit: music-time per second + double pulses_per_sample = pulses_per_second / sample_rate; // unit: music-time per sample return pulses_per_sample; } diff --git a/src/Segment.c b/src/Segment.c index 303df8c..e0065a1 100644 --- a/src/Segment.c +++ b/src/Segment.c @@ -88,3 +88,33 @@ char const* DmSegment_getName(DmSegment const* slf) { return slf->info.unam; } + +double DmSegment_getLength(DmSegment const* slf) { + if (slf == NULL) { + return 0; + } + + // NOTE: This assumes that the tempo messages are ordered from earliest to latest + DmTimeSignature signature = {4, 4, 4}; + uint32_t offset = 0; + double tempo = 100.; + double duration = 0; + + for (unsigned i = 0; i < slf->messages.length; ++i) { + DmMessage* msg = &slf->messages.data[i]; + if (msg->type != DmMessage_TEMPO) { + continue; + } + + uint32_t ticks = msg->time - offset; + tempo = msg->tempo.tempo; + + duration += ticks / Dm_getTicksPerSecond(signature, tempo); + offset = msg->time; + } + + uint32_t ticks = slf->length - offset; + duration += ticks / Dm_getTicksPerSecond(signature, tempo); + + return duration; +} diff --git a/src/_Internal.h b/src/_Internal.h index 0445f91..76ed684 100644 --- a/src/_Internal.h +++ b/src/_Internal.h @@ -573,6 +573,7 @@ DMINT void DmTimeSignature_parse(DmTimeSignature* slf, DmRiff* rif); DMINT uint32_t Dm_getBeatLength(DmTimeSignature sig); DMINT uint32_t Dm_getMeasureLength(DmTimeSignature sig); +DMINT double Dm_getTicksPerSecond(DmTimeSignature time_signature, double beats_per_minute); DMINT double Dm_getTicksPerSample(DmTimeSignature time_signature, double beats_per_minute, uint32_t sample_rate); DMINT uint32_t Dm_getTimeOffset(uint32_t grid_start, int32_t time_offset, DmTimeSignature sig); DMINT uint32_t Dm_getSampleCountForDuration(uint32_t duration,