diff --git a/RhythmGameUtilities.Tests/ParsersTest.cs b/RhythmGameUtilities.Tests/ParsersTest.cs index 2365ea9..2337dab 100644 --- a/RhythmGameUtilities.Tests/ParsersTest.cs +++ b/RhythmGameUtilities.Tests/ParsersTest.cs @@ -54,7 +54,7 @@ public void TestParseTimeSignaturesFromChartSection() var timeSignatures = Parsers.ParseTimeSignaturesFromChartSection(sections[NamedSection.SyncTrack]); - Assert.That(timeSignatures.Count, Is.EqualTo(4)); + Assert.That(timeSignatures.Length, Is.EqualTo(4)); } [Test] diff --git a/RhythmGameUtilities.Tests/UtilitiesTest.cs b/RhythmGameUtilities.Tests/UtilitiesTest.cs index e92be1b..1248f85 100644 --- a/RhythmGameUtilities.Tests/UtilitiesTest.cs +++ b/RhythmGameUtilities.Tests/UtilitiesTest.cs @@ -33,8 +33,11 @@ public void TestConvertSecondsToTicks() { 42240, 111980 } }; + var timeSignatureChanges = new[] { new TimeSignature { Position = 0, Numerator = 4, Denominator = 2 } }; + Assert.That( - Utilities.ConvertSecondsToTicks(seconds, resolution, bpmChanges), Is.EqualTo(1408)); + Utilities.ConvertSecondsToTicks(seconds, resolution, bpmChanges, timeSignatureChanges), + Is.EqualTo(1408)); } [Test] diff --git a/RhythmGameUtilities/Scripts/Parsers.cs b/RhythmGameUtilities/Scripts/Parsers.cs index 5f88ac8..ff4503b 100644 --- a/RhythmGameUtilities/Scripts/Parsers.cs +++ b/RhythmGameUtilities/Scripts/Parsers.cs @@ -109,14 +109,17 @@ public static Dictionary ParseMetaDataFromChartSection( return section.ToDictionary(item => item.Key, x => x.Value.First()); } - public static Dictionary ParseTimeSignaturesFromChartSection( + public static TimeSignature[] ParseTimeSignaturesFromChartSection( KeyValuePair[] section) { return section - .Where(item => item.Value[0] == TypeCode.TimeSignatureMarker) - .Select(item => - new KeyValuePair(int.Parse(item.Key), item.Value.Skip(1).Select(int.Parse).ToArray())) - .ToDictionary(item => item.Key, x => x.Value); + .Where(item => item.Value.Length >= 2 && item.Value.First() == TypeCode.TimeSignatureMarker).Select( + item => new TimeSignature + { + Position = int.Parse(item.Key), + Numerator = int.Parse(item.Value.Skip(1).First()), + Denominator = item.Value.Length > 2 ? int.Parse(item.Value.Skip(2).First()) : 2 + }).ToArray(); } public static Dictionary ParseBpmFromChartSection( diff --git a/RhythmGameUtilities/Scripts/Utilities.cs b/RhythmGameUtilities/Scripts/Utilities.cs index 2dde294..a2674b2 100644 --- a/RhythmGameUtilities/Scripts/Utilities.cs +++ b/RhythmGameUtilities/Scripts/Utilities.cs @@ -14,7 +14,7 @@ internal static class UtilitiesInternal [DllImport("libRhythmGameUtilities", CallingConvention = CallingConvention.Cdecl)] public static extern int ConvertSecondsToTicksInternal(float seconds, int resolution, int[] bpmChangesKeys, - int[] bpmChangesValues, int bpmChangesSize); + int[] bpmChangesValues, int bpmChangesSize, TimeSignature[] timeSignatures, int timeSignaturesSize); [DllImport("libRhythmGameUtilities", CallingConvention = CallingConvention.Cdecl)] public static extern bool IsOnTheBeat(int bpm, float currentTime, float delta); @@ -55,10 +55,12 @@ public static float ConvertTickToPosition(int tick, int resolution) /// The seconds to generate ticks with. /// The resolution of the song. /// All BPM changes within the song. - public static int ConvertSecondsToTicks(float seconds, int resolution, Dictionary bpmChanges) + /// All time signature changes within the song. + public static int ConvertSecondsToTicks(float seconds, int resolution, Dictionary bpmChanges, + TimeSignature[] timeSignatureChanges) { return UtilitiesInternal.ConvertSecondsToTicksInternal(seconds, resolution, bpmChanges.Keys.ToArray(), - bpmChanges.Values.ToArray(), bpmChanges.Count); + bpmChanges.Values.ToArray(), bpmChanges.Count, timeSignatureChanges, timeSignatureChanges.Length); } /// diff --git a/RhythmGameUtilities/Structs/TimeSignature.cs b/RhythmGameUtilities/Structs/TimeSignature.cs new file mode 100644 index 0000000..2df6d87 --- /dev/null +++ b/RhythmGameUtilities/Structs/TimeSignature.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace RhythmGameUtilities +{ + + [StructLayout(LayoutKind.Sequential)] + public struct TimeSignature + { + + public int Position; + + public int Numerator; + + public int Denominator; + + } + +} diff --git a/UnityPackage/Editor/Tests/ParsersTest.cs b/UnityPackage/Editor/Tests/ParsersTest.cs index 2365ea9..2337dab 100644 --- a/UnityPackage/Editor/Tests/ParsersTest.cs +++ b/UnityPackage/Editor/Tests/ParsersTest.cs @@ -54,7 +54,7 @@ public void TestParseTimeSignaturesFromChartSection() var timeSignatures = Parsers.ParseTimeSignaturesFromChartSection(sections[NamedSection.SyncTrack]); - Assert.That(timeSignatures.Count, Is.EqualTo(4)); + Assert.That(timeSignatures.Length, Is.EqualTo(4)); } [Test] diff --git a/UnityPackage/Editor/Tests/UtilitiesTest.cs b/UnityPackage/Editor/Tests/UtilitiesTest.cs index e92be1b..1248f85 100644 --- a/UnityPackage/Editor/Tests/UtilitiesTest.cs +++ b/UnityPackage/Editor/Tests/UtilitiesTest.cs @@ -33,8 +33,11 @@ public void TestConvertSecondsToTicks() { 42240, 111980 } }; + var timeSignatureChanges = new[] { new TimeSignature { Position = 0, Numerator = 4, Denominator = 2 } }; + Assert.That( - Utilities.ConvertSecondsToTicks(seconds, resolution, bpmChanges), Is.EqualTo(1408)); + Utilities.ConvertSecondsToTicks(seconds, resolution, bpmChanges, timeSignatureChanges), + Is.EqualTo(1408)); } [Test] diff --git a/UnityPackage/Scripts/Parsers.cs b/UnityPackage/Scripts/Parsers.cs index 5f88ac8..ff4503b 100644 --- a/UnityPackage/Scripts/Parsers.cs +++ b/UnityPackage/Scripts/Parsers.cs @@ -109,14 +109,17 @@ public static Dictionary ParseMetaDataFromChartSection( return section.ToDictionary(item => item.Key, x => x.Value.First()); } - public static Dictionary ParseTimeSignaturesFromChartSection( + public static TimeSignature[] ParseTimeSignaturesFromChartSection( KeyValuePair[] section) { return section - .Where(item => item.Value[0] == TypeCode.TimeSignatureMarker) - .Select(item => - new KeyValuePair(int.Parse(item.Key), item.Value.Skip(1).Select(int.Parse).ToArray())) - .ToDictionary(item => item.Key, x => x.Value); + .Where(item => item.Value.Length >= 2 && item.Value.First() == TypeCode.TimeSignatureMarker).Select( + item => new TimeSignature + { + Position = int.Parse(item.Key), + Numerator = int.Parse(item.Value.Skip(1).First()), + Denominator = item.Value.Length > 2 ? int.Parse(item.Value.Skip(2).First()) : 2 + }).ToArray(); } public static Dictionary ParseBpmFromChartSection( diff --git a/UnityPackage/Scripts/Utilities.cs b/UnityPackage/Scripts/Utilities.cs index 2dde294..a2674b2 100644 --- a/UnityPackage/Scripts/Utilities.cs +++ b/UnityPackage/Scripts/Utilities.cs @@ -14,7 +14,7 @@ internal static class UtilitiesInternal [DllImport("libRhythmGameUtilities", CallingConvention = CallingConvention.Cdecl)] public static extern int ConvertSecondsToTicksInternal(float seconds, int resolution, int[] bpmChangesKeys, - int[] bpmChangesValues, int bpmChangesSize); + int[] bpmChangesValues, int bpmChangesSize, TimeSignature[] timeSignatures, int timeSignaturesSize); [DllImport("libRhythmGameUtilities", CallingConvention = CallingConvention.Cdecl)] public static extern bool IsOnTheBeat(int bpm, float currentTime, float delta); @@ -55,10 +55,12 @@ public static float ConvertTickToPosition(int tick, int resolution) /// The seconds to generate ticks with. /// The resolution of the song. /// All BPM changes within the song. - public static int ConvertSecondsToTicks(float seconds, int resolution, Dictionary bpmChanges) + /// All time signature changes within the song. + public static int ConvertSecondsToTicks(float seconds, int resolution, Dictionary bpmChanges, + TimeSignature[] timeSignatureChanges) { return UtilitiesInternal.ConvertSecondsToTicksInternal(seconds, resolution, bpmChanges.Keys.ToArray(), - bpmChanges.Values.ToArray(), bpmChanges.Count); + bpmChanges.Values.ToArray(), bpmChanges.Count, timeSignatureChanges, timeSignatureChanges.Length); } /// diff --git a/UnityPackage/Structs/TimeSignature.cs b/UnityPackage/Structs/TimeSignature.cs new file mode 100644 index 0000000..2df6d87 --- /dev/null +++ b/UnityPackage/Structs/TimeSignature.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace RhythmGameUtilities +{ + + [StructLayout(LayoutKind.Sequential)] + public struct TimeSignature + { + + public int Position; + + public int Numerator; + + public int Denominator; + + } + +} diff --git a/UnityPackage/Structs/TimeSignature.cs.meta b/UnityPackage/Structs/TimeSignature.cs.meta new file mode 100644 index 0000000..f52378e --- /dev/null +++ b/UnityPackage/Structs/TimeSignature.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 72140cacdcbde4dd8a9aa2a71c201163 \ No newline at end of file diff --git a/include/RhythmGameUtilities/Parsers.hpp b/include/RhythmGameUtilities/Parsers.hpp index cdea95f..5ee41dd 100644 --- a/include/RhythmGameUtilities/Parsers.hpp +++ b/include/RhythmGameUtilities/Parsers.hpp @@ -10,6 +10,7 @@ #include "Enums/TypeCode.h" #include "Structs/Note.h" +#include "Structs/TimeSignature.h" #include "Common.hpp" @@ -104,17 +105,21 @@ std::map ParseMetaDataFromChartSection( return data; } -std::map ParseTimeSignaturesFromChartSection( +std::vector ParseTimeSignaturesFromChartSection( std::vector>> section) { - auto timeSignatures = std::map(); + auto timeSignatures = std::vector(); for (auto &line : section) { if (line.second.front() == ToString(TypeCode::TimeSignatureMarker)) { - timeSignatures.insert( - {std::stoi(line.first), std::stoi(line.second.at(1))}); + auto position = std::stoi(line.first); + auto numerator = std::stoi(line.second.at(1)); + auto denominator = + line.second.size() > 2 ? std::stoi(line.second.at(2)) : 2; + + timeSignatures.push_back({position, numerator, denominator}); } } diff --git a/include/RhythmGameUtilities/Structs/TimeSignature.h b/include/RhythmGameUtilities/Structs/TimeSignature.h new file mode 100644 index 0000000..ee7fb8c --- /dev/null +++ b/include/RhythmGameUtilities/Structs/TimeSignature.h @@ -0,0 +1,15 @@ +#pragma once + +namespace RhythmGameUtilities +{ + +struct TimeSignature +{ + int Position; + + int Numerator; + + int Denominator; +}; + +} // namespace RhythmGameUtilities diff --git a/include/RhythmGameUtilities/Utilities.hpp b/include/RhythmGameUtilities/Utilities.hpp index 6aa458e..fc5060f 100644 --- a/include/RhythmGameUtilities/Utilities.hpp +++ b/include/RhythmGameUtilities/Utilities.hpp @@ -1,13 +1,16 @@ #pragma once +#include #include #include #include #include -#include "Common.hpp" #include "Structs/BeatBar.h" #include "Structs/Note.h" +#include "Structs/TimeSignature.h" + +#include "Common.hpp" #ifdef _WIN32 #define PACKAGE_API __declspec(dllexport) @@ -26,38 +29,65 @@ const float SECONDS_PER_MINUTE = 60.0f; * @param seconds The seconds to generate ticks with. * @param resolution The resolution of the song. * @param bpmChanges All BPM changes within the song. + * @param timeSignatureChanges All time signature changes within the song. * @public */ int ConvertSecondsToTicks(float seconds, int resolution, - std::map bpmChanges) + std::map bpmChanges, + std::vector timeSignatureChanges) { + auto bpmIterator = bpmChanges.begin(); + auto timeSignatureIterator = timeSignatureChanges.begin(); + auto totalTicks = 0; auto remainingSeconds = seconds; auto previousTick = 0; - auto previousBPM = bpmChanges.begin()->second / 1000; + auto previousBPM = bpmIterator->second / 1000.0; + auto previousTimeSignature = timeSignatureIterator->Numerator; - for (auto const &[currentTick, value] : bpmChanges) + while (remainingSeconds > 0) { - auto timeForSegment = (currentTick - previousTick) / - (resolution * previousBPM / SECONDS_PER_MINUTE); + int nextBPMChange = + bpmIterator != bpmChanges.end() ? bpmIterator->first : INT_MAX; + + int nextTimeSignatureChange = + timeSignatureIterator != timeSignatureChanges.end() + ? timeSignatureIterator->Position + : INT_MAX; + + int nextChangeTick = std::min(nextBPMChange, nextTimeSignatureChange); + + float ticksPerSecond = resolution * previousBPM / SECONDS_PER_MINUTE; + float timeForSegment = (nextChangeTick - previousTick) / ticksPerSecond; if (remainingSeconds <= timeForSegment) { - totalTicks += static_cast(remainingSeconds * previousBPM / - SECONDS_PER_MINUTE * resolution); + totalTicks += static_cast(remainingSeconds * ticksPerSecond); return totalTicks; } - totalTicks += currentTick - previousTick; + totalTicks += nextChangeTick - previousTick; remainingSeconds -= timeForSegment; - previousTick = currentTick; - previousBPM = value / 1000; + previousTick = nextChangeTick; + + if (nextChangeTick == nextBPMChange) + { + previousBPM = bpmIterator->second / 1000.0; + ++bpmIterator; + } + + if (nextChangeTick == nextTimeSignatureChange) + { + previousTimeSignature = timeSignatureIterator->Numerator; + ++timeSignatureIterator; + } } - totalTicks += static_cast(remainingSeconds * previousBPM / - SECONDS_PER_MINUTE * resolution); + float ticksPerSecond = resolution * previousBPM / SECONDS_PER_MINUTE; + + totalTicks += static_cast(remainingSeconds * ticksPerSecond); return totalTicks; } diff --git a/include/RhythmGameUtilities/UtilitiesInternal.hpp b/include/RhythmGameUtilities/UtilitiesInternal.hpp index 935c5ac..50aa4b8 100644 --- a/include/RhythmGameUtilities/UtilitiesInternal.hpp +++ b/include/RhythmGameUtilities/UtilitiesInternal.hpp @@ -3,6 +3,7 @@ #include #include "Structs/BeatBar.h" +#include "Structs/TimeSignature.h" #include "Utilities.hpp" @@ -20,7 +21,9 @@ extern "C" PACKAGE_API int ConvertSecondsToTicksInternal(float seconds, int resolution, int *bpmChangesKeys, int *bpmChangesValues, - int bpmChangesSize) + int bpmChangesSize, + TimeSignature *timeSignatures, + int timeSignaturesSize) { std::map bpmChanges; @@ -29,7 +32,15 @@ extern "C" bpmChanges[bpmChangesKeys[i]] = bpmChangesValues[i]; } - return ConvertSecondsToTicks(seconds, resolution, bpmChanges); + std::vector timeSignatureChanges; + + for (auto i = 0; i < timeSignaturesSize; i += 1) + { + timeSignatureChanges.push_back(timeSignatures[i]); + } + + return ConvertSecondsToTicks(seconds, resolution, bpmChanges, + timeSignatureChanges); } PACKAGE_API BeatBar * diff --git a/tests/RhythmGameUtilities/Utilities.cpp b/tests/RhythmGameUtilities/Utilities.cpp index 3a38ad2..64dd5f1 100644 --- a/tests/RhythmGameUtilities/Utilities.cpp +++ b/tests/RhythmGameUtilities/Utilities.cpp @@ -3,6 +3,8 @@ #include #include +#include "RhythmGameUtilities/Structs/TimeSignature.h" + #include "RhythmGameUtilities/Utilities.hpp" using namespace RhythmGameUtilities; @@ -20,7 +22,10 @@ void testConvertSecondsToTicks() {0, 88000}, {3840, 112000}, {9984, 89600}, {22272, 112000}, {33792, 111500}, {34560, 112000}, {42240, 111980}}; - assert(1408 == ConvertSecondsToTicks(5, 192, bpmChanges)); + std::vector timeSignatureChanges = {{0, 4, 2}}; + + assert(1408 == + ConvertSecondsToTicks(5, 192, bpmChanges, timeSignatureChanges)); std::cout << "."; } diff --git a/tests/RhythmGameUtilities/UtilitiesInternal.cpp b/tests/RhythmGameUtilities/UtilitiesInternal.cpp index f8e0b2e..a309aa9 100644 --- a/tests/RhythmGameUtilities/UtilitiesInternal.cpp +++ b/tests/RhythmGameUtilities/UtilitiesInternal.cpp @@ -13,6 +13,8 @@ void testConvertSecondsToTicksInternal() {0, 88000}, {3840, 112000}, {9984, 89600}, {22272, 112000}, {33792, 111500}, {34560, 112000}, {42240, 111980}}; + std::vector timeSignatureChanges = {{0, 4, 2}}; + std::vector bpmChangesKeys; std::vector bpmChangesValues; @@ -22,9 +24,10 @@ void testConvertSecondsToTicksInternal() bpmChangesValues.push_back(value); } - assert(1408 == ConvertSecondsToTicksInternal(5, 192, &bpmChangesKeys[0], - &bpmChangesValues[0], - bpmChanges.size())); + assert(1408 == ConvertSecondsToTicksInternal( + 5, 192, &bpmChangesKeys[0], &bpmChangesValues[0], + bpmChanges.size(), &timeSignatureChanges[0], + timeSignatureChanges.size())); std::cout << "."; }