Refactor/cleanup of SyncTrack and added serialization to/from json.

This commit is contained in:
Martin Felis 2025-04-11 12:21:06 +02:00
parent 3b5537fc9d
commit 36c7f7a11e
2 changed files with 269 additions and 187 deletions

View File

@ -6,57 +6,62 @@
#define ANIMTESTBED_SYNCTRACK_H #define ANIMTESTBED_SYNCTRACK_H
#include <cassert> #include <cassert>
#include <cmath>
#include <iostream> #include "3rdparty/json/json.hpp"
constexpr int cSyncTrackMaxIntervals = 8; constexpr int cSyncTrackMaxIntervals = 8;
/// Metadata used for synced animation blending.
//
// A SyncTrack consists of multiple SyncInterval that are adjacent to each
// other.
//
// Important definitions:
//
// - Absolute Time: time within an animation duration in seconds.
// - Ratio: time relative to the animations duration, e.g. 0.5 corresponds to
// 50% of the duration.
// - SyncTime is a floating point value where the integer parts defines the
// SyncInterval and the fractional part the fraction within the interval. I.e.
// a SyncTime of 5.332 means it is ~33% through interval 5.
//
// A SyncInterval is defined by a ratio of the starting point and the ratio of
// the interval's duration.
struct SyncTrack { struct SyncTrack {
SyncTrack() : m_duration(0.f), m_num_intervals(0) { SyncTrack() : m_duration(0.f), m_num_intervals(1) {
for (int i = 0; i < cSyncTrackMaxIntervals; i++) { for (int i = 0; i < cSyncTrackMaxIntervals; i++) {
m_sync_markers[i] = 0.f; m_interval_start_ratio[i] = 0.f;
m_interval_ratio[i] = 0.f; m_interval_duration_ratio[i] = 0.f;
m_interval_ratio[i] = 0.f;
} }
m_interval_duration_ratio[0] = 1.0f;
} }
float m_duration; float m_duration;
int m_num_intervals; int m_num_intervals;
float m_sync_markers[cSyncTrackMaxIntervals]; float m_interval_start_ratio
float m_interval_start[cSyncTrackMaxIntervals]; [cSyncTrackMaxIntervals]; //< Starting time of interval in absolute time.
float m_interval_ratio[cSyncTrackMaxIntervals]; float m_interval_duration_ratio[cSyncTrackMaxIntervals]; //<
void CalcIntervals() {
if (m_num_intervals == 0) {
m_num_intervals = 1;
m_sync_markers[0] = 0.f;
}
for (int i = 0; i < m_num_intervals; i++) {
assert(m_sync_markers[i] >= 0.f && m_sync_markers[i] <= 1.0f);
int end_index = i < m_num_intervals - 1 ? i + 1 : 0;
m_interval_start[i] = m_sync_markers[i];
float interval_end = m_sync_markers[end_index];
if (interval_end < m_interval_start[i]) {
interval_end += 1.0f;
}
m_interval_ratio[i] = interval_end - m_interval_start[i];
}
}
float CalcSyncFromAbsTime(float abs_time) { float CalcSyncFromAbsTime(float abs_time) {
float sync_time = fmodf(abs_time, m_duration) / m_duration; for (int i = 0; i < m_num_intervals; i++) {
float query_abs_time = abs_time;
float interval_start = m_interval_start_ratio[i] * m_duration;
float interval_end =
interval_start + m_interval_duration_ratio[i] * m_duration;
int interval_index = 0; if (query_abs_time < interval_start) {
while (sync_time >= m_interval_ratio[interval_index]) { query_abs_time += m_duration;
sync_time -= m_interval_ratio[interval_index]; }
interval_index++; if (query_abs_time >= interval_start && query_abs_time < interval_end) {
return float(i)
+ (query_abs_time - interval_start)
/ (interval_end - interval_start);
}
} }
return float(interval_index) + sync_time / m_interval_ratio[interval_index]; assert(false && "Invalid absolute time");
return -1.f;
} }
float CalcRatioFromSyncTime(float sync_time) { float CalcRatioFromSyncTime(float sync_time) {
@ -64,8 +69,8 @@ struct SyncTrack {
int interval = int(sync_time - interval_ratio); int interval = int(sync_time - interval_ratio);
return fmodf( return fmodf(
m_interval_start[interval] m_interval_start_ratio[interval]
+ m_interval_ratio[interval] * interval_ratio, + m_interval_duration_ratio[interval] * interval_ratio,
1.0f); 1.0f);
} }
@ -78,8 +83,11 @@ struct SyncTrack {
} }
for (int i = 0; i < m_num_intervals; i++) { for (int i = 0; i < m_num_intervals; i++) {
if ((fabsf(m_interval_start[i] - other.m_interval_start[i]) > 1.0e-5) if ((fabsf(m_interval_start_ratio[i] - other.m_interval_start_ratio[i])
|| (fabsf(m_interval_ratio[i] - other.m_interval_ratio[i]) > 1.0e-5)
|| (fabsf(
m_interval_duration_ratio[i]
- other.m_interval_duration_ratio[i])
> 1.0e-5)) { > 1.0e-5)) {
return false; return false;
} }
@ -88,18 +96,40 @@ struct SyncTrack {
return true; return true;
} }
/** Constructs SyncTrack from markers.
*
* Markers are specified in absolute time and must be >= 0 and <= duration.
* They define the start of the interval. The last marker is implicitly the
* (possibly looped) first marker.
*/
static SyncTrack CreateFromMarkers( static SyncTrack CreateFromMarkers(
float duration, float duration,
int n_markers, const std::vector<float>& markers) {
float markers[cSyncTrackMaxIntervals]) { assert(markers.size() > 0);
assert(markers.size() < cSyncTrackMaxIntervals);
SyncTrack result; SyncTrack result;
result.m_duration = duration; result.m_duration = duration;
result.m_num_intervals = n_markers; result.m_num_intervals = markers.size();
for (int i = 0; i < n_markers; i++) {
result.m_sync_markers[i] = markers[i];
}
result.CalcIntervals(); for (int i = 0; i < markers.size(); i++) {
assert(markers[i] >= 0.f && markers[i] <= duration);
int end_index = i == (markers.size() - 1) ? 0 : i + 1;
float interval_start = markers[i];
float interval_end = markers[end_index];
if (interval_end == interval_start) {
interval_end = interval_start + duration;
} else if (interval_end < interval_start) {
interval_end += duration;
}
result.m_interval_start_ratio[i] = interval_start / duration;
result.m_interval_duration_ratio[i] =
(interval_end - interval_start) / duration;
}
return result; return result;
} }
@ -115,41 +145,62 @@ struct SyncTrack {
(1.0f - weight) * track_A.m_duration + weight * track_B.m_duration; (1.0f - weight) * track_A.m_duration + weight * track_B.m_duration;
float interval_0_offset = float interval_0_offset =
track_B.m_interval_start[0] - track_A.m_interval_start[0]; track_B.m_interval_start_ratio[0] - track_A.m_interval_start_ratio[0];
if (interval_0_offset > 0.5f) { if (interval_0_offset > 0.5f) {
interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f); interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f);
} else if (interval_0_offset < -0.5) { } else if (interval_0_offset < -0.5) {
interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f); interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f);
} }
result.m_interval_start[0] = fmodf( result.m_interval_start_ratio[0] = fmodf(
1.0 + (1.0f - weight) * track_A.m_interval_start[0] 1.0 + (1.0f - weight) * track_A.m_interval_start_ratio[0]
+ weight * (track_A.m_interval_start[0] + interval_0_offset), + weight * (track_A.m_interval_start_ratio[0] + interval_0_offset),
1.0f); 1.0f);
result.m_sync_markers[0] = result.m_interval_start[0];
for (int i = 0; i < result.m_num_intervals; i++) { for (int i = 0; i < result.m_num_intervals; i++) {
float interval_duration_A = track_A.m_interval_ratio[i]; float interval_duration_A = track_A.m_interval_duration_ratio[i];
float interval_duration_B = track_B.m_interval_ratio[i]; float interval_duration_B = track_B.m_interval_duration_ratio[i];
result.m_interval_ratio[i] = result.m_interval_duration_ratio[i] =
(1.0f - weight) * interval_duration_A + weight * interval_duration_B; (1.0f - weight) * interval_duration_A + weight * interval_duration_B;
if (i < cSyncTrackMaxIntervals) { if (i < cSyncTrackMaxIntervals) {
result.m_interval_start[i + 1] = result.m_interval_start_ratio[i + 1] =
result.m_interval_start[i] + result.m_interval_ratio[i]; result.m_interval_start_ratio[i]
if (result.m_interval_start[i + 1] > 1.0f) { + result.m_interval_duration_ratio[i];
result.m_interval_start[i + 1] = if (result.m_interval_start_ratio[i + 1] > 1.0f) {
fmodf(result.m_interval_start[i + 1], 1.0f); result.m_interval_start_ratio[i + 1] =
fmodf(result.m_interval_start_ratio[i + 1], 1.0f);
} }
result.m_sync_markers[i + 1] = result.m_interval_start[i + 1];
} }
} }
assert (result.m_num_intervals < cSyncTrackMaxIntervals); assert(result.m_num_intervals < cSyncTrackMaxIntervals);
return result; return result;
} }
}; };
inline void to_json(nlohmann::json& j, const SyncTrack& sync_track) {
j["type"] = "SyncTrack";
j["duration"] = sync_track.m_duration;
for (int i = 0; i < sync_track.m_num_intervals; i++) {
j["interval"][i]["start"] = sync_track.m_interval_start_ratio[i];
j["interval"][i]["ratio"] = sync_track.m_interval_duration_ratio[i];
}
}
inline void from_json(const nlohmann::json& j, SyncTrack& sync_track) {
assert(j["type"] == "SyncTrack");
sync_track.m_duration = j["duration"];
sync_track.m_num_intervals = j["interval"].size();
assert(sync_track.m_num_intervals < cSyncTrackMaxIntervals);
for (int i = 0; i < sync_track.m_num_intervals; i++) {
sync_track.m_interval_start_ratio[i] = j["interval"][i]["start"];
sync_track.m_interval_duration_ratio[i] = j["interval"][i]["ratio"];
}
}
#endif //ANIMTESTBED_SYNCTRACK_H #endif //ANIMTESTBED_SYNCTRACK_H

View File

@ -6,197 +6,228 @@
#include "catch.hpp" #include "catch.hpp"
TEST_CASE("Basic", "[SyncTrack]") { TEST_CASE("Basic", "[SyncTrack]") {
SyncTrack track_A; SyncTrack track_a;
track_A.m_num_intervals = 2; track_a.m_num_intervals = 2;
track_A.m_duration = 2.0; track_a.m_duration = 2.0;
track_A.m_interval_start[0] = 0.f; track_a.m_interval_start_ratio[0] = 0.f;
track_A.m_interval_ratio[0] = 0.7; track_a.m_interval_duration_ratio[0] = 0.7;
track_A.m_interval_start[1] = 0.7f; track_a.m_interval_start_ratio[1] = 0.7f;
track_A.m_interval_ratio[1] = 0.3; track_a.m_interval_duration_ratio[1] = 0.3;
SyncTrack track_B; SyncTrack track_b;
track_B.m_num_intervals = 2; track_b.m_num_intervals = 2;
track_B.m_duration = 1.5; track_b.m_duration = 1.5;
track_B.m_interval_start[0] = 0.0f; track_b.m_interval_start_ratio[0] = 0.0f;
track_B.m_interval_ratio[0] = 0.6; track_b.m_interval_duration_ratio[0] = 0.6;
track_B.m_interval_start[1] = 0.6f; track_b.m_interval_start_ratio[1] = 0.6f;
track_B.m_interval_ratio[1] = 0.4; track_b.m_interval_duration_ratio[1] = 0.4;
WHEN("Calculating sync time of track_B at 0.5 duration") { WHEN("Calculating sync time of track_B at 0.5 duration") {
float sync_time_at_0_75 = float sync_time_at_0_75 =
track_B.CalcSyncFromAbsTime(0.5 * track_B.m_duration); track_b.CalcSyncFromAbsTime(0.5 * track_b.m_duration);
REQUIRE(sync_time_at_0_75 == Catch::Detail::Approx(0.83333)); REQUIRE(sync_time_at_0_75 == Catch::Detail::Approx(0.83333));
} }
WHEN("Calculating sync time of track_B at 0.6 duration") { WHEN("Calculating sync time of track_B at 0.6 duration") {
float sync_time_at_0_6 = float sync_time_at_0_6 =
track_B.CalcSyncFromAbsTime(0.6 * track_B.m_duration); track_b.CalcSyncFromAbsTime(0.6 * track_b.m_duration);
REQUIRE(sync_time_at_0_6 == Catch::Detail::Approx(1.0)); REQUIRE(sync_time_at_0_6 == Catch::Detail::Approx(1.0));
} }
WHEN("Calculating sync time of track_B at 0.7 duration") { WHEN("Calculating sync time of track_B at 0.7 duration") {
float sync_time_at_0_7 = float sync_time_at_0_7 =
track_B.CalcSyncFromAbsTime(0.7 * track_B.m_duration); track_b.CalcSyncFromAbsTime(0.7 * track_b.m_duration);
REQUIRE(sync_time_at_0_7 == Catch::Detail::Approx(1.25)); REQUIRE(sync_time_at_0_7 == Catch::Detail::Approx(1.25));
} }
WHEN("Calculating sync time of track_B at 0.0 duration") { WHEN("Calculating sync time of track_B at 0.0 duration") {
float sync_time_at_1_0 = float sync_time_at_1_0 =
track_B.CalcSyncFromAbsTime(0.0 * track_B.m_duration); track_b.CalcSyncFromAbsTime(0.0 * track_b.m_duration);
REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(0.0)); REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(0.0));
} }
WHEN("Calculating sync time of track_B at 1.0 duration") { WHEN("Calculating sync time of track_B at 1.0 duration") {
float sync_time_at_1_0 = float sync_time_at_1_0 =
track_B.CalcSyncFromAbsTime(0.9999 * track_B.m_duration); track_b.CalcSyncFromAbsTime(0.9999 * track_b.m_duration);
REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(2.0).epsilon(0.001f)); REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(2.0).epsilon(0.001f));
} }
WHEN("Calculating ratio from sync time on track_A at 0.83333") { WHEN("Calculating ratio from sync time on track_A at 0.83333") {
float ratio = track_A.CalcRatioFromSyncTime(0.83333333); float ratio = track_a.CalcRatioFromSyncTime(0.83333333);
REQUIRE(ratio == Catch::Detail::Approx(0.5833333)); REQUIRE(ratio == Catch::Detail::Approx(0.5833333));
} }
WHEN("Calculating ratio from sync time on track_A at 0.83333") { WHEN("Calculating ratio from sync time on track_A at 0.83333") {
float ratio = track_A.CalcRatioFromSyncTime(1.25); float ratio = track_a.CalcRatioFromSyncTime(1.25);
REQUIRE(ratio == Catch::Detail::Approx(0.775)); REQUIRE(ratio == Catch::Detail::Approx(0.775));
} }
WHEN("Blending two synctracks with weight 0.") { WHEN("Blending two synctracks with weight 0.") {
SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B); SyncTrack blended = SyncTrack::Blend(0.f, track_a, track_b);
THEN("Result must equal track_A") { REQUIRE(track_A == blended); } THEN("Result must equal track_A") { REQUIRE(track_a == blended); }
} }
WHEN("Blending two synctracks with weight 1.") { WHEN("Blending two synctracks with weight 1.") {
SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B); SyncTrack blended = SyncTrack::Blend(1.f, track_a, track_b);
THEN("Result must equal track_B") { REQUIRE(track_B == blended); } THEN("Result must equal track_B") { REQUIRE(track_b == blended); }
} }
} }
TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") { TEST_CASE("Sync Track From Marker", "[SyncTrack]") {
SyncTrack track_A; SyncTrack track = SyncTrack::CreateFromMarkers(2.0f, {0.9f, 0.2f});
track_A.m_num_intervals = 2;
track_A.m_duration = 2.0;
track_A.m_sync_markers[0] = 0.9;
track_A.m_sync_markers[1] = 0.2;
WHEN("Calculating intervals") { WHEN("Querying Ratios") {
track_A.CalcIntervals(); CHECK(track.m_interval_start_ratio[0] == Catch::Detail::Approx(0.45f));
CHECK(track.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.65f));
CHECK(track_A.m_interval_start[0] == Catch::Detail::Approx(0.9f)); CHECK(track.m_interval_start_ratio[1] == Catch::Detail::Approx(0.1f));
CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3f)); CHECK(track.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.35f));
CHECK(track_A.m_interval_start[1] == Catch::Detail::Approx(0.2f)); WHEN("Querying ratio at sync time at 0.001") {
CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.7f)); float ratio = track.CalcRatioFromSyncTime(0.0001f);
CHECK(ratio == Catch::Detail::Approx(0.45).epsilon(0.001));
}
WHEN("Querying ratio at sync time at 1.001") { WHEN("Querying ratio at sync time at 0.9999") {
float ratio = track_A.CalcRatioFromSyncTime(1.0001f); float ratio = track.CalcRatioFromSyncTime(0.9999f);
CHECK(ratio == Catch::Detail::Approx(0.2).epsilon(0.001)); CHECK(ratio == Catch::Detail::Approx(0.1).epsilon(0.001));
} }
WHEN("Querying ratio at sync time at 1.001") { WHEN("Querying ratio at sync time at 1.001") {
float ratio = track_A.CalcRatioFromSyncTime(0.0001f); float ratio = track.CalcRatioFromSyncTime(1.0001f);
CHECK(ratio == Catch::Detail::Approx(0.9).epsilon(0.001)); CHECK(ratio == Catch::Detail::Approx(0.1).epsilon(0.001));
} }
WHEN("Querying ratio at sync time at 1.9999") { WHEN("Querying ratio at sync time at 1.9999") {
float ratio = track_A.CalcRatioFromSyncTime(0.9999f); float ratio = track.CalcRatioFromSyncTime(1.9999f);
CHECK(ratio == Catch::Detail::Approx(0.2).epsilon(0.001)); CHECK(ratio == Catch::Detail::Approx(0.45).epsilon(0.001));
} }
} }
WHEN("Blending sync track with 3 events") { WHEN("Querying SyncTime from Absolute Time") {
track_A.m_num_intervals = 3; WHEN("Querying absolute time at 0.9001s") {
track_A.m_duration = 2.0; float sync_time = track.CalcSyncFromAbsTime(0.9001f);
track_A.m_sync_markers[0] = 0.; CHECK_THAT(sync_time, Catch::WithinAbs(0.0, 0.001));
track_A.m_sync_markers[1] = 0.3;
track_A.m_sync_markers[2] = 0.9;
track_A.CalcIntervals();
SyncTrack track_B;
track_B.m_num_intervals = 3;
track_B.m_duration = 1.5;
track_B.m_sync_markers[0] = 0.7;
track_B.m_sync_markers[1] = 0.9;
track_B.m_sync_markers[2] = 0.2;
track_B.CalcIntervals();
WHEN("Calculating A's durations") {
CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3));
CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.6));
CHECK(track_A.m_interval_ratio[2] == Catch::Detail::Approx(0.1));
} }
WHEN("Calculating B's durations") { WHEN("Querying absolute time at 0.2001s") {
CHECK(track_B.m_interval_ratio[0] == Catch::Detail::Approx(0.2)); float sync_time = track.CalcSyncFromAbsTime(0.2001f);
CHECK(track_B.m_interval_ratio[1] == Catch::Detail::Approx(0.3)); CHECK_THAT(sync_time, Catch::WithinAbs(1.0, 0.001));
CHECK(track_B.m_interval_ratio[2] == Catch::Detail::Approx(0.5));
} }
WHEN("Blending two synctracks with weight 0.") { WHEN("Querying absolute time at 0.8999s") {
SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B); float sync_time = track.CalcSyncFromAbsTime(0.8999f);
CHECK_THAT(sync_time, Catch::WithinAbs(1.999, 0.001));
THEN("Result must equal track_A") { REQUIRE(track_A == blended); }
} }
WHEN("Blending two synctracks with weight 1.") { WHEN("Querying absolute time at 1.9999s") {
SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B); float sync_time = track.CalcSyncFromAbsTime(1.9999f);
CHECK_THAT(sync_time, Catch::WithinAbs(0.84615384, 0.001));
THEN("Result must equal track_B") { REQUIRE(track_B == blended); }
} }
}
}
WHEN("Blending with weight 0.2") { TEST_CASE("Sync Track Blending", "[SyncTrack]") {
float weight = 0.2f; SyncTrack track_a = SyncTrack::CreateFromMarkers(2.0, {0., 0.6, 1.8});
SyncTrack blended = SyncTrack::Blend(weight, track_A, track_B); SyncTrack track_b = SyncTrack::CreateFromMarkers(1.5f, {1.05, 1.35, 0.3});
REQUIRE( WHEN("Calculating A's durations") {
blended.m_duration CHECK(track_a.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.3));
== (1.0f - weight) * track_A.m_duration CHECK(track_a.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.6));
+ weight * track_B.m_duration); CHECK(track_a.m_interval_duration_ratio[2] == Catch::Detail::Approx(0.1));
REQUIRE( }
blended.m_interval_start[0]
== fmodf(
(1.0f - weight) * (track_A.m_interval_start[0] + 1.0f)
+ weight * (track_B.m_interval_start[0]),
1.0f));
REQUIRE(
blended.m_interval_ratio[1]
== (1.0f - weight) * (track_A.m_interval_ratio[1])
+ weight * (track_B.m_interval_ratio[1])
);
REQUIRE(
blended.m_interval_ratio[2]
== (1.0f - weight) * (track_A.m_interval_ratio[2])
+ weight * (track_B.m_interval_ratio[2])
);
}
WHEN("Inverted blending with weight 0.2") { WHEN("Calculating B's durations") {
float weight = 0.2f; CHECK(track_b.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.2));
SyncTrack blended = SyncTrack::Blend(weight, track_B, track_A); CHECK(track_b.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.3));
CHECK(track_b.m_interval_duration_ratio[2] == Catch::Detail::Approx(0.5));
}
REQUIRE( WHEN("Blending two synctracks with weight 0.") {
blended.m_duration SyncTrack blended = SyncTrack::Blend(0.f, track_a, track_b);
== (1.0f - weight) * track_B.m_duration
+ weight * track_A.m_duration); THEN("Result must equal track_A") { REQUIRE(track_a == blended); }
REQUIRE( }
blended.m_interval_start[0]
== fmodf( WHEN("Blending two synctracks with weight 1.") {
(1.0f - weight) * (track_B.m_interval_start[0]) SyncTrack blended = SyncTrack::Blend(1.f, track_a, track_b);
+ weight * (track_A.m_interval_start[0] + 1.0f),
1.0f)); THEN("Result must equal track_B") { REQUIRE(track_b == blended); }
REQUIRE( }
blended.m_interval_ratio[1]
== (1.0f - weight) * (track_B.m_interval_ratio[1]) WHEN("Blending with weight 0.2") {
+ weight * (track_A.m_interval_ratio[1]) float weight = 0.2f;
); SyncTrack blended = SyncTrack::Blend(weight, track_a, track_b);
REQUIRE(
blended.m_interval_ratio[2] REQUIRE(
== (1.0f - weight) * (track_B.m_interval_ratio[2]) blended.m_duration
+ weight * (track_A.m_interval_ratio[2]) == (1.0f - weight) * track_a.m_duration + weight * track_b.m_duration);
); REQUIRE(
} blended.m_interval_start_ratio[0]
== fmodf(
(1.0f - weight) * (track_a.m_interval_start_ratio[0] + 1.0f)
+ weight * (track_b.m_interval_start_ratio[0]),
1.0f));
REQUIRE(
blended.m_interval_duration_ratio[1]
== (1.0f - weight) * (track_a.m_interval_duration_ratio[1])
+ weight * (track_b.m_interval_duration_ratio[1]));
REQUIRE(
blended.m_interval_duration_ratio[2]
== (1.0f - weight) * (track_a.m_interval_duration_ratio[2])
+ weight * (track_b.m_interval_duration_ratio[2]));
}
WHEN("Inverted blending with weight 0.2") {
float weight = 0.2f;
SyncTrack blended = SyncTrack::Blend(weight, track_b, track_a);
REQUIRE(
blended.m_duration
== (1.0f - weight) * track_b.m_duration + weight * track_a.m_duration);
REQUIRE(
blended.m_interval_start_ratio[0]
== fmodf(
(1.0f - weight) * (track_b.m_interval_start_ratio[0])
+ weight * (track_a.m_interval_start_ratio[0] + 1.0f),
1.0f));
REQUIRE(
blended.m_interval_duration_ratio[1]
== (1.0f - weight) * (track_b.m_interval_duration_ratio[1])
+ weight * (track_a.m_interval_duration_ratio[1]));
REQUIRE(
blended.m_interval_duration_ratio[2]
== (1.0f - weight) * (track_b.m_interval_duration_ratio[2])
+ weight * (track_a.m_interval_duration_ratio[2]));
}
}
TEST_CASE("Serialization", "[SyncTrack]") {
SyncTrack track;
track.m_num_intervals = 3;
track.m_duration = 2.0;
track.m_interval_start_ratio[0] = 0.f;
track.m_interval_duration_ratio[0] = 0.7;
track.m_interval_start_ratio[1] = 0.7f;
track.m_interval_duration_ratio[1] = 0.3;
track.m_interval_start_ratio[2] = 0.7f;
track.m_interval_duration_ratio[2] = 0.3;
nlohmann::json synctrack_json = track;
const SyncTrack synctrack_deserialized = synctrack_json;
CHECK(synctrack_deserialized.m_duration == track.m_duration);
CHECK(synctrack_deserialized.m_num_intervals == track.m_num_intervals);
for (int i = 0; i < track.m_num_intervals; i++) {
CHECK(
synctrack_deserialized.m_interval_start_ratio[i]
== track.m_interval_start_ratio[i]);
CHECK(
synctrack_deserialized.m_interval_duration_ratio[i]
== track.m_interval_duration_ratio[i]);
} }
} }