// Copyright Epic Games, Inc. All Rights Reserved. #include "Misc/AutomationTest.h" #include "FrameBasedMusicMap.h" #if 1 // WITH_DEV_AUTOMATION_TESTS namespace MusicEnvironmentTests { int32 BPMToMidiTempo(float Bpm) { return Bpm == 0.0 ? 0 : static_cast(60000000.0 / Bpm); } double TicksToMs(float Bpm, int32 DeltaTicks) { return static_cast(DeltaTicks) * static_cast(BPMToMidiTempo(Bpm)) / (static_cast(MusicalTime::TicksPerQuarterNote) * 1000.0); } double TicksToSeconds(float Bpm, int32 DeltaTicks) { return TicksToMs(Bpm, DeltaTicks) / 1000.0; } int32 BarsToTick(const FFrameBasedTimeSignature& TimeSig, int32 DeltaBars) { int32 TicksPerBeat = MusicalTime::TicksPerQuarterNote * 4 / TimeSig.Denominator; int32 TicksPerBar = TicksPerBeat * TimeSig.Numerator; return DeltaBars * TicksPerBar; } BEGIN_DEFINE_SPEC( FFrameBasedMusicMapSpec, "MusicEnvironment.FrameBasedMusicMap", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) struct FScopeContext { FScopeContext(FAutomationTestBase& InTest, const FString& Context) : Test(InTest) { Test.PushContext(Context); } ~FScopeContext() { Test.PopContext(); } FScopeContext(const FScopeContext& Other) = delete; FScopeContext(FScopeContext&& Other) = delete; FScopeContext& operator=(const FScopeContext& Other) = delete; FScopeContext operator=(FScopeContext Other) = delete; private: FAutomationTestBase& Test; }; bool TestMusicMapIsEmpty(UFrameBasedMusicMap* MusicMap) { FScopeContext ScopeContext(*this, TEXT("Test Music Map Is Empty")); bool Result = true; Result &= TestEqual(TEXT("MusicMap->FrameResolution"), MusicMap->FrameResolution, FFrameRate(0, 0)); Result &= TestTrue(TEXT("MusicMap->TempoMap.IsEmpty()"), MusicMap->TempoMap.IsEmpty()); Result &= TestTrue(TEXT("MusicMap->BarMap.IsEmpty()"), MusicMap->BarMap.IsEmpty()); return Result; } bool TestMusicMapInitialized(UFrameBasedMusicMap* MusicMap, const FFrameRate& FrameResolution, float TempoBpm, const FFrameBasedTimeSignature& TimeSig) { FScopeContext ScopeContext(*this, TEXT("Test Music Map Initialized")); bool Result = true; Result &= TestEqual(TEXT("MusicMap->FrameResolution"), MusicMap->FrameResolution, FrameResolution); if (TestEqual(TEXT("MusicMap->TempoMap.Num()"), MusicMap->TempoMap.Num(), 1)) { Result &= TestFrameBasedTempoPoint(TEXT("MusicMap->TempoMap[0]"), MusicMap->TempoMap[0], 0.0f, 0, TempoBpm); } else { Result = false; } if (TestEqual(TEXT("MusicMap->BarMap.Num()"), MusicMap->BarMap.Num(), 1)) { Result &= TestFrameBasedTimeSignaturePoint(TEXT("MusicMap->BarMap[0]"), MusicMap->BarMap[0], 0.0, 0, 0, TimeSig); } else { Result = false; } return Result; } bool TestFrameBasedTempoPoint(const FString& What, const FFrameBasedTempoPoint& Point, float ExpectedMs, int32 ExpectedTick, float ExpectedTempoBpm) { bool Result = true; auto ExactlyWhat = [&What](const FString& PropertyName) { return FString::Format(TEXT("{0}.{1}"), { What, PropertyName }); }; int32 MicrosecondsPerQuarterNote = BPMToMidiTempo(ExpectedTempoBpm); Result &= TestEqual(ExactlyWhat("OnMs"), Point.OnMs, ExpectedMs, 0.01f); Result &= TestEqual(ExactlyWhat("OnTick"), Point.OnTick, ExpectedTick); Result &= TestEqual(ExactlyWhat("MicrosecondsPerQuarterNote"), Point.MicrosecondsPerQuarterNote, MicrosecondsPerQuarterNote); return Result; } bool TestMusicMapTempoPoints(const FString& What, const TArray& ActualTempoPoints, const TArray& ExpectedTempoPoints) { if (TestEqual(FString::Format(TEXT("{0}.{1}"), {What, TEXT("Num()"})), ActualTempoPoints.Num(), ExpectedTempoPoints.Num())) { bool Result = true; for (int32 Index = 0; Index < ActualTempoPoints.Num(); ++Index) { float Ms = ExpectedTempoPoints[Index].OnMs; int32 Tick = ExpectedTempoPoints[Index].OnTick; float Bpm = ExpectedTempoPoints[Index].Bpm(); Result &= TestFrameBasedTempoPoint(FString::Format(TEXT("{0}[{1}]"), {What, Index}), ActualTempoPoints[Index], Ms, Tick, Bpm); } return Result; } return false; } bool TestFrameBasedTimeSignaturePoint(const FString& What, const FFrameBasedTimeSignaturePoint& Point, double ExpectedFrame, int32 ExpectedTick, int32 ExpectedBar, const FFrameBasedTimeSignature& ExpectedTimeSig) { auto ExactlyWhat = [&What](const FString& PropertyName) { return FString::Format(TEXT("{0}.{1}"), { What, PropertyName }); }; bool Result = true; Result &= TestEqual(ExactlyWhat(TEXT("OnFrame")), Point.OnFrame, ExpectedFrame, 0.01); Result &= TestEqual(ExactlyWhat(TEXT("OnTick")), Point.OnTick, ExpectedTick); Result &= TestEqual(ExactlyWhat(TEXT("OnBar")), Point.OnBar, ExpectedBar); Result &= TestEqual(ExactlyWhat(TEXT("TimeSignature.Numerator")), Point.TimeSignature.Numerator, ExpectedTimeSig.Numerator); Result &= TestEqual(ExactlyWhat(TEXT("TimeSignature.Denominator")), Point.TimeSignature.Denominator, ExpectedTimeSig.Denominator); return Result; } bool TestMusicMapTimeSignaturePoints(const FString& What, const TArray& ActualTimeSigPoints, const TArray& ExpectedBarMap) { if (TestEqual(FString::Format(TEXT("{0}.{1}"), {What, TEXT("Num()") }), ActualTimeSigPoints.Num(), ExpectedBarMap.Num())) { bool Result = true; for (int32 Index = 0; Index < ActualTimeSigPoints.Num(); ++Index) { double Frame = ExpectedBarMap[Index].OnFrame; int32 Tick = ExpectedBarMap[Index].OnTick; int32 Bar = ExpectedBarMap[Index].OnBar; FFrameBasedTimeSignature TimeSig = ExpectedBarMap[Index].TimeSignature; Result &= TestFrameBasedTimeSignaturePoint(FString::Format(TEXT("{0}[{1}]"), {What, Index}), ActualTimeSigPoints[Index], Frame, Tick, Bar, TimeSig); } } return false; } END_DEFINE_SPEC(FFrameBasedMusicMapSpec) void FFrameBasedMusicMapSpec::Define() { Describe(TEXT("Defaults"), [this]() { It(TEXT("Empty"), [this]() { UFrameBasedMusicMap* MusicMap = NewObject(); TestMusicMapIsEmpty(MusicMap); }); It(TEXT("Initialized"), [this]() { const FFrameRate FrameResolution(24000, 1); float TempoBpm = 120.0f; const FFrameBasedTimeSignature TimeSig(4, 4); UFrameBasedMusicMap* MusicMap = NewObject(); MusicMap->SetFrameResolution(FrameResolution); MusicMap->Init(TempoBpm, TimeSig); TestMusicMapInitialized(MusicMap, FrameResolution, TempoBpm, TimeSig); }); It(TEXT("AddTimeTimeSignature"), [this]() { const FFrameRate FrameResolution(24000, 1); float InitTempoBpm = 120.0f; const FFrameBasedTimeSignature InitTimeSig(4, 4); UFrameBasedMusicMap* MusicMap = NewObject(); MusicMap->SetFrameResolution(FrameResolution); MusicMap->Init(InitTempoBpm, InitTimeSig); if (!TestMusicMapInitialized(MusicMap, FrameResolution, InitTempoBpm, InitTimeSig)) { return; } int32 AtBar = 1; int32 AtTick = BarsToTick(InitTimeSig, AtBar); double AtFrame = FrameResolution.AsFrameTime(TicksToSeconds(InitTempoBpm, AtTick)).AsDecimal(); FFrameBasedTimeSignature WithTimeSig(3, 4); MusicMap->AddTimeSignature(AtTick, AtBar, WithTimeSig.Numerator, WithTimeSig.Denominator); TArray ExpectedTempoMap; ExpectedTempoMap.Add(FFrameBasedTempoPoint(InitTempoBpm, 0, 0.0f)); TArray ExpectedBarMap; ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(InitTimeSig, 0, 0, 0.0)); ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(WithTimeSig, 1, AtTick, AtFrame)); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); }); It(TEXT("AddTempo"), [this]() { const FFrameRate FrameResolution(24000, 1); float InitTempoBpm = 120.0f; const FFrameBasedTimeSignature InitTimeSig(4, 4); UFrameBasedMusicMap* MusicMap = NewObject(); MusicMap->SetFrameResolution(FrameResolution); MusicMap->Init(InitTempoBpm, InitTimeSig); if (!TestMusicMapInitialized(MusicMap, FrameResolution, InitTempoBpm, InitTimeSig)) { return; } int32 AtBar = 1; int32 AtTick = BarsToTick(InitTimeSig, AtBar); float AtMs = TicksToMs(InitTempoBpm, AtTick); float WithTempoBpm = 50.0f; MusicMap->AddTempo(AtTick, AtMs, WithTempoBpm); TArray ExpectedTempoMap; ExpectedTempoMap.Add(FFrameBasedTempoPoint(InitTempoBpm, 0, 0.0f)); ExpectedTempoMap.Add(FFrameBasedTempoPoint(WithTempoBpm, AtTick, AtMs)); TArray ExpectedBarMap; ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(InitTimeSig, 0, 0, 0.0)); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); }); It(TEXT("InsertTimeSignature"), [this]() { const FFrameRate FrameResolution(24000, 1); float InitTempoBpm = 120.0f; const FFrameBasedTimeSignature InitTimeSig(4, 4); UFrameBasedMusicMap* MusicMap = NewObject(); MusicMap->SetFrameResolution(FrameResolution); MusicMap->Init(InitTempoBpm, InitTimeSig); if (!TestMusicMapInitialized(MusicMap, FrameResolution, InitTempoBpm, InitTimeSig)) { return; } int32 AtBar = 1; int32 AtTick = BarsToTick(InitTimeSig, AtBar); double AtFrame = FrameResolution.AsFrameTime(TicksToSeconds(InitTempoBpm, AtTick)).AsDecimal(); FFrameBasedTimeSignature WithTimeSig(3, 4); MusicMap->InsertTimeSignature(AtBar, WithTimeSig.Numerator, WithTimeSig.Denominator); TArray ExpectedTempoMap; ExpectedTempoMap.Add(FFrameBasedTempoPoint(InitTempoBpm, 0, 0.0f)); TArray ExpectedBarMap; ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(InitTimeSig, 0, 0, 0.0)); ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(WithTimeSig, AtBar, AtTick, AtFrame)); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); }); It(TEXT("InsertTempo"), [this]() { const FFrameRate FrameResolution(24000, 1); float InitTempoBpm = 120.0f; const FFrameBasedTimeSignature InitTimeSig(4, 4); UFrameBasedMusicMap* MusicMap = NewObject(); MusicMap->SetFrameResolution(FrameResolution); MusicMap->Init(InitTempoBpm, InitTimeSig); if (!TestMusicMapInitialized(MusicMap, FrameResolution, InitTempoBpm, InitTimeSig)) { return; } int32 AtBar = 1; int32 AtTick = BarsToTick(InitTimeSig, AtBar); float AtMs = TicksToMs(InitTempoBpm, AtTick); float WithTempoBpm = 50.0f; MusicMap->InsertTempo(AtTick, WithTempoBpm); TArray ExpectedTempoMap; ExpectedTempoMap.Add(FFrameBasedTempoPoint(InitTempoBpm, 0, 0.0f)); ExpectedTempoMap.Add(FFrameBasedTempoPoint(WithTempoBpm, AtTick, AtMs)); TArray ExpectedBarMap; ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(InitTimeSig, 0, 0, 0.0)); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); }); }); Describe(TEXT("WithTempoChanges"), [this]() { It(TEXT("InsertTempo"), [this]() { const FFrameRate FrameResolution(24000, 1); float InitTempoBpm = 120.0f; const FFrameBasedTimeSignature InitTimeSig(4, 4); UFrameBasedMusicMap* MusicMap = NewObject(); MusicMap->SetFrameResolution(FrameResolution); MusicMap->Init(InitTempoBpm, InitTimeSig); if (!TestMusicMapInitialized(MusicMap, FrameResolution, InitTempoBpm, InitTimeSig)) { return; } TArray ExpectedTempoMap; TArray ExpectedBarMap; ExpectedTempoMap.Add(FFrameBasedTempoPoint(InitTempoBpm, 0, 0.0f)); ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(InitTimeSig, 0, 0, 0.0)); // add tempo point for tempo changes { int32 AtBar = 2; int32 AtTick = BarsToTick(InitTimeSig, AtBar); float AtMs = TicksToMs(InitTempoBpm, AtTick); float WithTempoBpm = 100.0f; MusicMap->AddTempo(AtTick, AtMs, WithTempoBpm); ExpectedTempoMap.Add(FFrameBasedTempoPoint(WithTempoBpm, AtTick, AtMs)); FScopeContext ScopeContext(*this, FString::Format(TEXT("Test Add First Tempo Change: {0}bpm Tick: {1}, Ms: {2}"), {WithTempoBpm, AtTick, AtMs})); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); } { int32 AtBar = 1; int32 AtTick = BarsToTick(InitTimeSig, AtBar); float AtMs = TicksToMs(InitTempoBpm, AtTick); float WithTempoBpm = 110.0f; MusicMap->InsertTempo(AtTick, WithTempoBpm); ExpectedTempoMap.Insert(FFrameBasedTempoPoint(WithTempoBpm, AtTick, AtMs), 1); // we have to adjust the time of the later tempo point after inserting a tempo point before it ExpectedTempoMap.Last().OnMs = AtMs + TicksToMs(WithTempoBpm, ExpectedTempoMap.Last().OnTick - AtTick); FScopeContext ScopeContext(*this, FString::Format(TEXT("Test Insert New Tempo Change: {0}bpm @ Tick: {1}, Ms: {2}"), {WithTempoBpm, AtTick, AtMs})); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); } }); It(TEXT("InsertTimeSignature"), [this]() { const FFrameRate FrameResolution(24000, 1); float InitTempoBpm = 120.0f; const FFrameBasedTimeSignature InitTimeSig(4, 4); float SecondTempoBpm = 100.0f; int32 SecondTempoTick = BarsToTick(InitTimeSig, 1); UFrameBasedMusicMap* MusicMap = NewObject(); MusicMap->SetFrameResolution(FrameResolution); MusicMap->Init(InitTempoBpm, InitTimeSig); if (!TestMusicMapInitialized(MusicMap, FrameResolution, InitTempoBpm, InitTimeSig)) { return; } TArray ExpectedTempoMap; TArray ExpectedBarMap; ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(InitTimeSig, 0, 0, 0.0)); ExpectedTempoMap.Add(FFrameBasedTempoPoint(InitTempoBpm, 0, 0.0f)); // add tempo point for tempo changes { float AtMs = TicksToMs(InitTempoBpm, SecondTempoTick); MusicMap->AddTempo(SecondTempoTick, AtMs, SecondTempoBpm); ExpectedTempoMap.Add(FFrameBasedTempoPoint(SecondTempoBpm, SecondTempoTick, AtMs)); FScopeContext ScopeContext(*this, FString::Format(TEXT("Test Add First Tempo Change: {0}bpm Tick: {1}, Ms: {2}"), {SecondTempoBpm, SecondTempoTick, AtMs})); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); } // insert Time Signature Change { int32 AtBar = 2; int32 AtTick = BarsToTick(InitTimeSig, AtBar); double AtMs = TicksToMs(InitTempoBpm, SecondTempoTick) + TicksToMs(SecondTempoBpm, AtTick - SecondTempoTick); FFrameTime FrameTime = FrameResolution.AsFrameTime(AtMs / 1000.0); FFrameBasedTimeSignature WithTimeSig(3, 4); MusicMap->InsertTimeSignature(AtBar, WithTimeSig.Numerator, WithTimeSig.Denominator); ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(WithTimeSig, AtBar, AtTick, FrameTime.AsDecimal())); FScopeContext ScopeContext(*this, FString::Format(TEXT("Test Insert New Time Signature Change: {0}/{1} @ Bar: {2}, Tick: {3}, Frame: {4} "), {WithTimeSig.Numerator, WithTimeSig.Denominator, AtBar, AtTick, FrameTime.AsDecimal()})); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); } }); }); Describe(TEXT("WithTimeSignatureChanges"), [this]() { It(TEXT("InsertTempo"), [this]() { const FFrameRate FrameResolution(24000, 1); float InitTempoBpm = 120.0f; const FFrameBasedTimeSignature InitTimeSig(4, 4); UFrameBasedMusicMap* MusicMap = NewObject(); MusicMap->SetFrameResolution(FrameResolution); MusicMap->Init(InitTempoBpm, InitTimeSig); if (!TestMusicMapInitialized(MusicMap, FrameResolution, InitTempoBpm, InitTimeSig)) { return; } TArray ExpectedTempoMap; TArray ExpectedBarMap; ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(InitTimeSig, 0, 0, 0.0)); ExpectedTempoMap.Add(FFrameBasedTempoPoint(InitTempoBpm, 0, 0.0f)); // add time signature for time signature changes { int32 AtBar = 2; int32 AtTick = BarsToTick(InitTimeSig, AtBar); FFrameTime FrameTime = FrameResolution.AsFrameTime(TicksToSeconds(InitTempoBpm, AtTick)); FFrameBasedTimeSignature WithTimeSig(3, 4); MusicMap->AddTimeSignature(AtTick, AtBar, WithTimeSig.Numerator, WithTimeSig.Denominator); ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(WithTimeSig, AtBar, AtTick, FrameTime.AsDecimal())); FScopeContext ScopeContext(*this, FString::Format(TEXT("Test Add First Time Signature Change: {0}/{1} @ Bar: {2}, Tick: {3}, Frame: {4}"), {WithTimeSig.Numerator, WithTimeSig.Denominator, AtBar, AtTick, FrameTime.AsDecimal()})); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); } { int32 AtBar = 1; int32 AtTick = BarsToTick(InitTimeSig, AtBar); float AtMs = TicksToMs(InitTempoBpm, AtTick); float WithTempoBpm = 110.0f; MusicMap->InsertTempo(AtTick, WithTempoBpm); ExpectedTempoMap.Insert(FFrameBasedTempoPoint(WithTempoBpm, AtTick, AtMs), 1); // we also have to adjust the time of the late time signature point since the tempo changed before it // calculate the ms based on the tick of the time sig. change double NewMs = AtMs + TicksToMs(WithTempoBpm, ExpectedBarMap.Last().OnTick - AtTick); FFrameTime NewFrameTime = FrameResolution.AsFrameTime(NewMs / 1000.0); ExpectedBarMap.Last().OnFrame = NewFrameTime.AsDecimal(); FScopeContext ScopeContext(*this, FString::Format(TEXT("Test Insert New Tempo Change: {0}bpm @ Tick: {1}, Ms: {2}"), {WithTempoBpm, AtTick, AtMs})); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); } }); It(TEXT("InsertTimeSignature"), [this]() { const FFrameRate FrameResolution(24000, 1); float InitTempoBpm = 120.0f; const FFrameBasedTimeSignature InitTimeSig(4, 4); UFrameBasedMusicMap* MusicMap = NewObject(); MusicMap->SetFrameResolution(FrameResolution); MusicMap->Init(InitTempoBpm, InitTimeSig); if (!TestMusicMapInitialized(MusicMap, FrameResolution, InitTempoBpm, InitTimeSig)) { return; } TArray ExpectedTempoMap; TArray ExpectedBarMap; ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(InitTimeSig, 0, 0, 0.0)); ExpectedTempoMap.Add(FFrameBasedTempoPoint(InitTempoBpm, 0, 0.0f)); // add time signature for time signature changes { int32 AtBar = 2; int32 AtTick = BarsToTick(InitTimeSig, AtBar); FFrameTime FrameTime = FrameResolution.AsFrameTime(TicksToSeconds(InitTempoBpm, AtTick)); FFrameBasedTimeSignature WithTimeSig(3, 4); MusicMap->AddTimeSignature(AtTick, AtBar, WithTimeSig.Numerator, WithTimeSig.Denominator); ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(WithTimeSig, AtBar, AtTick, FrameTime.AsDecimal())); FScopeContext ScopeContext(*this, FString::Format(TEXT("Test Add Time First Signature Change: {0}/{1} @ Bar: {2}, Tick: {3}, Frame: {4}"), {WithTimeSig.Numerator, WithTimeSig.Denominator, AtBar, AtTick, FrameTime.AsDecimal()})); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); } // insert Time Signature Change { int32 AtBar = 1; int32 AtTick = BarsToTick(InitTimeSig, AtBar); double AtMs = TicksToMs(InitTempoBpm, AtTick); FFrameTime FrameTime = FrameResolution.AsFrameTime(AtMs / 1000.0); FFrameBasedTimeSignature WithTimeSig(2, 4); // changing the time signature will cause the bar mapping to change which means we need to update the later time signature position MusicMap->InsertTimeSignature(AtBar, WithTimeSig.Numerator, WithTimeSig.Denominator); ExpectedBarMap.Insert(FFrameBasedTimeSignaturePoint(WithTimeSig, AtBar, AtTick, FrameTime.AsDecimal()), 1); int32 NewTick = AtTick + BarsToTick(WithTimeSig, 1); double NewMs = AtMs + TicksToMs(InitTempoBpm, NewTick - AtTick); FFrameTime NewFrameTime = FrameResolution.AsFrameTime(NewMs / 1000.0); ExpectedBarMap.Last().OnTick = NewTick; ExpectedBarMap.Last().OnFrame = NewFrameTime.AsDecimal(); FScopeContext ScopeContext(*this, FString::Format(TEXT("Test Insert New Time Signature Change: {0}/{1} @ Bar: {2}, Tick: {3}, Frame: {4} "), {WithTimeSig.Numerator, WithTimeSig.Denominator, AtBar, AtTick, FrameTime.AsDecimal()})); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); } // insert Time Signature Change { int32 AtBar = 3; int32 AtTick = BarsToTick(InitTimeSig, 1) + BarsToTick({2, 4}, 1) + BarsToTick({3, 4}, 1); AddInfo(FString::Format(TEXT("{0} + {1} + {2} = {3}"), {BarsToTick(InitTimeSig, 1), BarsToTick({2, 4}, 1), BarsToTick({3, 4}, 1), AtTick})); double AtMs = TicksToMs(InitTempoBpm, AtTick); FFrameTime FrameTime = FrameResolution.AsFrameTime(AtMs / 1000.0); FFrameBasedTimeSignature WithTimeSig(6, 8); // changing the time signature will cause the bar mapping to change which means we need to update the later time signature position MusicMap->InsertTimeSignature(AtBar, WithTimeSig.Numerator, WithTimeSig.Denominator); ExpectedBarMap.Add(FFrameBasedTimeSignaturePoint(WithTimeSig, AtBar, AtTick, FrameTime.AsDecimal())); FScopeContext ScopeContext(*this, FString::Format(TEXT("Test Insert New Time Signature Change: {0}/{1} @ Bar: {2}, Tick: {3}, Frame: {4} "), {WithTimeSig.Numerator, WithTimeSig.Denominator, AtBar, AtTick, FrameTime.AsDecimal()})); TestMusicMapTempoPoints(TEXT("MusicMap->TempoMap"), MusicMap->TempoMap, ExpectedTempoMap); TestMusicMapTimeSignaturePoints(TEXT("MusicMap->BarMap"), MusicMap->BarMap, ExpectedBarMap); } }); }); } }; #endif // WITH_DEV_AUTOMATION_TESTS