diff --git a/src/better_chart.cpp b/src/better_chart.cpp index 71f8731..97c84c9 100644 --- a/src/better_chart.cpp +++ b/src/better_chart.cpp @@ -8,6 +8,17 @@ #include "json_decimal_handling.hpp" namespace better { + bool Chart::operator==(const Chart& other) const { + return ( + level == other.level + and ( + ((not timing.has_value()) and (not other.timing.has_value())) + or (**timing == **other.timing) + ) and hakus == other.hakus + and *notes == *other.notes + ); + } + nlohmann::ordered_json Chart::dump_to_memon_1_0_0( const nlohmann::ordered_json& fallback_timing_object ) const { diff --git a/src/better_chart.hpp b/src/better_chart.hpp index c2c3708..527321b 100644 --- a/src/better_chart.hpp +++ b/src/better_chart.hpp @@ -20,7 +20,7 @@ namespace better { std::optional hakus; std::shared_ptr notes = std::make_shared(); - bool operator==(const Chart&) const = default; + bool operator==(const Chart&) const; nlohmann::ordered_json dump_to_memon_1_0_0( const nlohmann::ordered_json& fallback_timing_object diff --git a/src/better_song.cpp b/src/better_song.cpp index 69ddc9c..15170f5 100644 --- a/src/better_song.cpp +++ b/src/better_song.cpp @@ -1,5 +1,6 @@ #include "better_song.hpp" +#include #include #include @@ -193,6 +194,15 @@ namespace better { return song; }; + bool Song::operator==(const Song& other) const { + return ( + charts == other.charts + and metadata == other.metadata + and *timing == *other.timing + and hakus == other.hakus + ); + } + std::ostream& operator<<(std::ostream& out, const Song& s) { out << fmt::format("{}", s); return out; diff --git a/src/better_song.hpp b/src/better_song.hpp index cadd88c..c0aa08b 100644 --- a/src/better_song.hpp +++ b/src/better_song.hpp @@ -77,7 +77,7 @@ namespace better { */ static Song load_from_memon_legacy(const nlohmann::json& memon); - bool operator==(const Song&) const = default; + bool operator==(const Song&) const; friend std::ostream& operator<<(std::ostream& out, const Song& s); }; diff --git a/src/better_timing.cpp b/src/better_timing.cpp index 0b7948f..f086627 100644 --- a/src/better_timing.cpp +++ b/src/better_timing.cpp @@ -89,15 +89,16 @@ namespace better { change */ double Timing::seconds_at(Fraction beats) const { + return offset_as_double + seconds_without_offset_at(beats); + }; + + double Timing::seconds_without_offset_at(Fraction beats) const { const auto& bpm_change = bpm_event_in_effect_at(beats); const Fraction beats_since_previous_event = beats - bpm_change.get_beats(); double seconds_since_previous_event = static_cast(beats_since_previous_event) * 60 / bpm_change.get_bpm_as_double(); - return ( - offset_as_double - + bpm_change.get_seconds() - + seconds_since_previous_event - ); - }; + const auto previous_event_seconds = bpm_change.get_seconds(); + return previous_event_seconds + seconds_since_previous_event; + } double Timing::seconds_between(Fraction beat_a, Fraction beat_b) const { return seconds_at(beat_b) - seconds_at(beat_a); @@ -118,8 +119,9 @@ namespace better { Fraction Timing::beats_at(double seconds) const { const auto& bpm_change = bpm_event_in_effect_at(seconds); - auto seconds_since_previous_event = seconds - bpm_change.get_seconds(); - auto beats_since_previous_event = ( + const auto previous_event_seconds = bpm_change.get_seconds() + offset_as_double; + const auto seconds_since_previous_event = seconds - previous_event_seconds; + const auto beats_since_previous_event = ( convert_to_fraction(bpm_change.get_bpm()) * Fraction{seconds_since_previous_event} / 60 @@ -182,7 +184,8 @@ namespace better { } void Timing::set_offset(const Decimal& new_offset) { - shift_to_match(new_offset); + offset = new_offset; + offset_as_double = std::stod(new_offset.format("f")); } @@ -242,7 +245,7 @@ namespace better { void Timing::reconstruct(const std::vector& events, const Decimal& offset) { reload_events_from(events); - shift_to_match(offset); + set_offset(offset); } void Timing::reload_events_from(const std::vector& events) { @@ -274,9 +277,8 @@ namespace better { } } - // Compute seconds offsets as if the first event happened at second 0 - // then shift - // Bootstrap the alg by computing the first one out of the loop + // Compute everything as if the first BPM change happened at zero + // seconds auto first_event = filtered_events.begin(); double current_second = 0; events_by_beats.clear(); @@ -298,19 +300,17 @@ namespace better { current->get_bpm() ); } - } - void Timing::shift_to_match(const Decimal& offset_) { - offset = offset_; - offset_as_double = std::stod(offset_.format("f")); - const auto shift = offset_as_double - seconds_at(0); + // Shift events so their precomputed "seconds" put beat zero + // at zero seconds + const auto shift = seconds_without_offset_at(0); Timing::keys_by_beats_type shifted_events; seconds_to_beats.clear(); std::for_each( events_by_beats.cbegin(), events_by_beats.cend(), [&](const auto& event) { - const auto seconds = event.get_seconds() + shift; + const auto seconds = event.get_seconds() - shift; const auto beats = event.get_beats(); shifted_events.emplace(beats, seconds, event.get_bpm()); seconds_to_beats.emplace(seconds, beats); @@ -325,7 +325,7 @@ namespace better { } const Timing::key_type& Timing::bpm_event_in_effect_at(double seconds) const { - auto it = seconds_to_beats.upper_bound(seconds); + auto it = seconds_to_beats.upper_bound(seconds - offset_as_double); if (it != seconds_to_beats.begin()) { it = std::prev(it); } diff --git a/src/better_timing.hpp b/src/better_timing.hpp index 7a1b060..84d57d5 100644 --- a/src/better_timing.hpp +++ b/src/better_timing.hpp @@ -69,6 +69,9 @@ namespace better { Timing(const std::vector& events, const Decimal& offset); double seconds_at(Fraction beats) const; + private: + double seconds_without_offset_at(Fraction beats) const; + public: double seconds_between(Fraction beat_a, Fraction beat_b) const; sf::Time time_at(Fraction beats) const; sf::Time time_between(Fraction beat_a, Fraction beat_b) const; @@ -106,14 +109,17 @@ namespace better { Decimal offset = 0; double offset_as_double = 0; - // These containers hold shared pointers to the same objects + // holds the bpm changes with seconds precomputed, seconds are synced + // as if beat zero was happening at zero seconds, ignoring any offset keys_by_beats_type events_by_beats = {}; + // holds a pair for each event to allow a quick search + // when querying by seconds, seconds held in this are synced as if beat + // zero was happenning at zero seconds std::map seconds_to_beats = {}; void reconstruct(const std::vector& events, const Decimal& offset); - /* Reload the timing object assuming the first event in the given - vector happens at second zero */ + /* Reload using the given events */ void reload_events_from(const std::vector& events); /* Shift all events in the timing object to make beat zero happen diff --git a/src/tests/doctest/long_note_dummy.cpp b/src/tests/doctest/long_note_dummy.cpp index 85ba648..9f61a38 100644 --- a/src/tests/doctest/long_note_dummy.cpp +++ b/src/tests/doctest/long_note_dummy.cpp @@ -10,7 +10,7 @@ TEST_CASE("make_long_note works with any input") { better::Position pos_b{index_b}; better::TapNote a{0, pos_a}; better::TapNote b{0, pos_b}; - REQUIRE_NOTHROW(make_linear_view_long_note_dummy({a, b}, 1)); + REQUIRE_NOTHROW(make_long_note_dummy_for_linear_view({a, b}, 1)); } } } \ No newline at end of file diff --git a/src/tests/rapidcheck/generators.hpp b/src/tests/rapidcheck/generators.hpp index 30681ff..1ebfa7c 100644 --- a/src/tests/rapidcheck/generators.hpp +++ b/src/tests/rapidcheck/generators.hpp @@ -20,6 +20,7 @@ #include "../../better_song.hpp" #include "../../better_timing.hpp" #include "../../variant_visitor.hpp" +#include "rapidcheck/gen/Arbitrary.hpp" namespace rc { @@ -153,10 +154,15 @@ namespace rc { } }; + struct TimingParams { + std::vector events; + Decimal offset; + }; + template<> - struct Arbitrary { - static Gen arbitrary() { - return gen::construct( + struct Arbitrary { + static Gen arbitrary() { + return gen::construct( gen::withSize([](std::size_t size) { return gen::uniqueBy>( std::max(std::size_t(1), size), @@ -169,6 +175,18 @@ namespace rc { } }; + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map( + gen::arbitrary(), + [](const TimingParams& t){ + return better::Timing{t.events, t.offset}; + } + ); + } + }; + template<> struct Arbitrary { static Gen arbitrary() { @@ -197,9 +215,13 @@ namespace rc { static Gen arbitrary() { return gen::construct( gen::arbitrary>(), - gen::construct>(gen::arbitrary()), + gen::construct>>( + gen::makeShared(gen::arbitrary()) + ), gen::arbitrary>(), - gen::arbitrary() + gen::construct>( + gen::makeShared(gen::arbitrary()) + ) ); } }; @@ -254,7 +276,7 @@ namespace rc { return gen::construct( gen::arbitrary>(), gen::arbitrary(), - gen::arbitrary(), + gen::makeShared(gen::arbitrary()), gen::arbitrary>() ); } diff --git a/src/widgets/linear_view.cpp b/src/widgets/linear_view.cpp index 30d00dd..3b1ff8a 100644 --- a/src/widgets/linear_view.cpp +++ b/src/widgets/linear_view.cpp @@ -312,6 +312,7 @@ void LinearView::draw( ImGui::IsMouseClicked(ImGuiMouseButton_Left) and current_window->InnerClipRect.Contains(ImGui::GetMousePos()) and not ImGui::IsAnyItemHovered() + and ImGui::IsWindowFocused() ) { started_selection_inside_window = true; }