From 15f8e590ddfc91a9a19d45ae0b34c8ad6b958f25 Mon Sep 17 00:00:00 2001 From: lyk Date: Tue, 27 Feb 2024 13:53:14 +0800 Subject: [PATCH] order preserving --- include/symusic/ops.h | 135 ++++++++++++++++++++++++++++++++---------- py_src/core.cpp | 6 +- src/conversion.cpp | 135 +++++++++++++++++++----------------------- 3 files changed, 167 insertions(+), 109 deletions(-) diff --git a/include/symusic/ops.h b/include/symusic/ops.h index c239889..d1a879e 100644 --- a/include/symusic/ops.h +++ b/include/symusic/ops.h @@ -114,6 +114,7 @@ vec clamp_dur(const vec& events, typename T::unit min_dur, typename T::uni return clamp_dur_inplace(new_events, min_dur, max_dur); } +namespace details { template vec adjust_time_sorted( const vec& events, const vec& original_times, const vec& new_times) { @@ -124,14 +125,6 @@ vec adjust_time_sorted( return event.time + event.duration; } return event.time; }; - // assume that all the events, original_times and new_times are sorted - if(original_times.size() != new_times.size()) { - throw std::invalid_argument("symusic::ops::adjust_time: original_times and new_times should have the same size"); - } - // assume that original_times and new_times have at least 2 elements - if(original_times.size() < 2) { - throw std::invalid_argument("symusic::ops::adjust_time: original_times and new_times should have at least 2 elements"); - } // return empty vector if events is empty if(events.empty()) return {}; // find the first event that is after(>=) the first original time @@ -189,52 +182,132 @@ vec adjust_time_sorted( event.duration -= event.time; } } - return new_events; } + template +vec adjust_time_unsorted( + const vec& events, const vec& original_times, const vec& new_times) { + // check if the events have duration + constexpr bool has_dur = HashDuration; + auto get_end = [has_dur](const T &event) { + if constexpr (has_dur) { + return event.time + event.duration; + } return event.time; + }; + // return empty vector if events is empty + if(events.empty()) return {}; + auto get_factor = [&original_times, &new_times](const size_t x) { + return static_cast(new_times[x] - new_times[x - 1]) / + static_cast(original_times[x] - original_times[x - 1]); + }; + const auto range = std::make_pair(original_times.front(), original_times.back()); + auto valid = [range](const T& event) { + if constexpr (has_dur) { + return (event.time >= range.first) & (event.time + event.duration <= range.second); + } return (event.time >= range.first) & (event.time <= range.second); + }; + + auto cur_range = std::make_pair(original_times[0], original_times[1]); + auto cur_factor = get_factor(1); + auto pivot_new = new_times[0]; + + // create new events + vec new_events; + new_events.reserve(events.size()); + for(const auto &event: events) { + if(!valid(event)) continue; + if(event.time < cur_range.first | event.time > cur_range.second) { + auto i = std::lower_bound(original_times.begin() + 1, original_times.end(), event.time) - original_times.begin(); + cur_factor = get_factor(i); + cur_range = std::make_pair(original_times[i - 1], original_times[i]); + pivot_new = new_times[i - 1]; + } + auto start = pivot_new + static_cast( + cur_factor * static_cast(event.time - cur_range.first) + ); + if constexpr (has_dur) { + auto original_end = get_end(event); + if (original_end > cur_range.second) { + auto i = std::lower_bound(original_times.begin() + 1, original_times.end(), original_end) - original_times.begin(); + cur_factor = get_factor(i); + cur_range = std::make_pair(original_times[i - 1], original_times[i]); + pivot_new = new_times[i - 1]; + } + auto end = pivot_new + static_cast( + cur_factor * static_cast(original_end - cur_range.first) + ); + new_events.emplace_back(start, end-start, event); + } else { + new_events.emplace_back(start, event); + } + } return new_events; +} + +template +void check_times(const vec& original_times, const vec& new_times) { + if(original_times.size() != new_times.size()) { + throw std::invalid_argument("symusic::ops::adjust_time: original_times and new_times should have the same size"); + } + if(original_times.size() < 2) { + throw std::invalid_argument("symusic::ops::adjust_time: original_times and new_times should have at least 2 elements"); + } + if(!std::is_sorted(original_times.begin(), original_times.end())) { + throw std::invalid_argument("symusic::ops::adjust_time: original_times should be sorted"); + } + if(!std::is_sorted(new_times.begin(), new_times.end())) { + throw std::invalid_argument("symusic::ops::adjust_time: new_times should be sorted"); + } +} + +} // namespace details + + +template vec adjust_time( const vec& events, const vec& original_times, const vec& new_times, const bool sorted = false) { - if (sorted) { return adjust_time_sorted(events, original_times, new_times); } - - vec _events(events); - sort_by_time(_events); - vec _original_times(original_times); - sort(_original_times, std::less()); - vec _new_times(new_times); - sort(_new_times, std::less()); - - return adjust_time_sorted(_events, _original_times, _new_times); + if constexpr (check_times) { + details::check_times(original_times, new_times); + } + if(sorted || std::is_sorted(events.begin(), events.end(), [](const T & a, const T & b) {return a.time < b.time;})) { + return details::adjust_time_sorted(events, original_times, new_times); + } return details::adjust_time_unsorted(events, original_times, new_times); } -template +template Track adjust_time( const Track & track, const vec& original_times, const vec& new_times, const bool sorted = false) { + if constexpr (check_times) { + details::check_times(original_times, new_times); + } Track new_track{track.name, track.program, track.is_drum}; - new_track.notes = std::move(adjust_time(track.notes, original_times, new_times, sorted)); - new_track.controls = std::move(adjust_time(track.controls, original_times, new_times, sorted)); - new_track.pitch_bends = std::move(adjust_time(track.pitch_bends, original_times, new_times, sorted)); - new_track.pedals = std::move(adjust_time(track.pedals, original_times, new_times, sorted)); + new_track.notes = std::move(adjust_time(track.notes, original_times, new_times, sorted)); + new_track.controls = std::move(adjust_time(track.controls, original_times, new_times, sorted)); + new_track.pitch_bends = std::move(adjust_time(track.pitch_bends, original_times, new_times, sorted)); + new_track.pedals = std::move(adjust_time(track.pedals, original_times, new_times, sorted)); return new_track; } -template +template Score adjust_time( const Score & score, const vec& original_times, const vec& new_times, const bool sorted = false) { + if constexpr (check_times) { + details::check_times(original_times, new_times); + } Score new_score{score.ticks_per_quarter}; new_score.tracks.reserve(score.tracks.size()); for(const auto & track : score.tracks) { - new_score.tracks.push_back(adjust_time(track, original_times, new_times, sorted)); + new_score.tracks.push_back(adjust_time(track, original_times, new_times, sorted)); } - new_score.time_signatures = adjust_time(score.time_signatures, original_times, new_times, sorted); - new_score.key_signatures = adjust_time(score.key_signatures, original_times, new_times, sorted); - new_score.tempos = adjust_time(score.tempos, original_times, new_times, sorted); - new_score.lyrics = adjust_time(score.lyrics, original_times, new_times, sorted); - new_score.markers = adjust_time(score.markers, original_times, new_times, sorted); + new_score.time_signatures = adjust_time(score.time_signatures, original_times, new_times, sorted); + new_score.key_signatures = adjust_time(score.key_signatures, original_times, new_times, sorted); + new_score.tempos = adjust_time(score.tempos, original_times, new_times, sorted); + new_score.lyrics = adjust_time(score.lyrics, original_times, new_times, sorted); + new_score.markers = adjust_time(score.markers, original_times, new_times, sorted); return new_score; } diff --git a/py_src/core.cpp b/py_src/core.cpp index 687060d..17940a8 100644 --- a/py_src/core.cpp +++ b/py_src/core.cpp @@ -140,7 +140,7 @@ std::tuple, py::class_>> time_stamp_base(py::module_ &m, co .def("__getstate__", &py_to_bytes>) .def("__setstate__", &py_from_bytes>) .def("filter", &py_filter, py::arg("func"), py::arg("inplace")=false) - .def("adjust_time", &ops::adjust_time, py::arg("original_times"), py::arg("new_times"), py::arg("is_sorted")=false) + .def("adjust_time", &ops::adjust_time, py::arg("original_times"), py::arg("new_times"), py::arg("is_sorted")=false) .def("start", &ops::start, "Return the start time of the all the events") .def("end", &ops::end, "Return the end time of the all the events"); return std::make_tuple(event, vec_T); @@ -528,7 +528,7 @@ py::class_> bind_track_class(py::module_ &m, const std::string & name_) .def("shift_time", &py_shift_time_track, py::arg("offset"), py::arg("inplace")=false) .def("shift_pitch", &py_shift_pitch_track, py::arg("offset"), py::arg("inplace")=false) .def("shift_velocity", &py_shift_velocity_track, py::arg("offset"), py::arg("inplace")=false) - .def("adjust_time", py::overload_cast&, const vec &, const vec &, bool>(&ops::adjust_time), + .def("adjust_time", py::overload_cast&, const vec &, const vec &, bool>(&ops::adjust_time), py::arg("original_times"), py::arg("new_times"), py::arg("is_sorted") = false) .def("pianoroll", [](const Track &self, const std::vector& modes, @@ -786,7 +786,7 @@ py::class_> bind_score_class(py::module_ &m, const std::string & name_) .def("end", &Score::end) .def("note_num", &Score::note_num) .def("empty", &Score::empty) - .def("adjust_time", py::overload_cast&, const vec &, const vec &, bool>(&ops::adjust_time), + .def("adjust_time", py::overload_cast&, const vec &, const vec &, bool>(&ops::adjust_time), py::arg("original_times"), py::arg("new_times"), py::arg("is_sorted") = false) .def("pianoroll", [](const Score &self, const std::vector& modes, diff --git a/src/conversion.cpp b/src/conversion.cpp index 24ba8d6..55e091c 100644 --- a/src/conversion.cpp +++ b/src/conversion.cpp @@ -103,9 +103,12 @@ struct Quarter2Tick: SimpleConverter { template struct SecondConverter { f64 tpq; - vec> tempos{}; + vec to_times; + vec from_times; + vec factors; explicit SecondConverter(const Score & score): tpq(static_cast(score.ticks_per_quarter)){ + vec> tempos; if(score.tempos.empty()) { // 120 qpm tempos = {{0, 500000}, {std::numeric_limits::max(), 500000}}; @@ -119,28 +122,46 @@ struct SecondConverter { // add a guard at the end tempos.emplace_back(std::numeric_limits::max(), tempos.back().mspq); } - } - template class T> - [[nodiscard]] vec> time_vec(const vec> & data) const { + const auto self = static_cast(this); - // copy and sort origin data - vec> origin(data); ops::sort_by_time(origin); - // reserve space for the result - vec> ans; ans.reserve(origin.size()); - // create an iterator for tempos - auto t_iter = tempos.begin() + 1; + typename To::unit pivot_to = 0; typename From::unit pivot_from = 0; double cur_factor = self->get_factor(tempos[0]); - for(const auto & event : origin) { - // move to next tempo if necessary - while(event.time > t_iter->time) { - pivot_to = self->get_time(t_iter->time, pivot_to, pivot_from, cur_factor); - pivot_from = t_iter->time; - cur_factor = self->get_factor(*t_iter); - ++t_iter; + + to_times.reserve(tempos.size()); + from_times.reserve(tempos.size()); + factors.reserve(tempos.size()); + + to_times.emplace_back(pivot_to); + from_times.emplace_back(pivot_from); + factors.emplace_back(cur_factor); + + for(auto i=1; iget_time(tempos[i].time, pivot_to, pivot_from, cur_factor); + pivot_from = tempos[i].time; + cur_factor = self->get_factor(tempos[i]); + to_times.emplace_back(pivot_to); + from_times.emplace_back(pivot_from); + factors.emplace_back(cur_factor); + } + } + template class T> + [[nodiscard]] vec> time_vec(const vec> & data) const { + const auto self = static_cast(this); + vec> ans; ans.reserve(data.size()); + auto cur_range = std::make_pair(from_times[0], from_times[1]); + auto pivot_to = to_times[0]; + auto cur_factor = factors[0]; + + for (const auto &event : data) { + if(event.time < cur_range.first | event.time >= cur_range.second) { + auto i = std::upper_bound(from_times.begin(), from_times.end(), event.time) - from_times.begin() - 1; + cur_range = std::make_pair(from_times[i], from_times[i+1]); + pivot_to = to_times[i]; + cur_factor = factors[i]; } - ans.emplace_back(self->get_time(event.time, pivot_to, pivot_from, cur_factor), event); + ans.emplace_back(self->get_time(event.time, pivot_to, cur_range.first, cur_factor), event); } return ans; } @@ -149,63 +170,27 @@ struct SecondConverter { [[nodiscard]] vec> duration_vec(const vec> & data, typename To::unit min_dur) const { const auto self = static_cast(this); min_dur = std::max(min_dur, static_cast(0)); - // copy and sort origin data - vec> origin(data); ops::sort_by_time(origin); - // reserve space for the result - vec> ans; ans.reserve(origin.size()); - // create an iterator for tempos - auto t_iter = tempos.begin() + 1; - typename To::unit pivot_to = 0; - typename From::unit pivot_from = 0; - f64 cur_factor = self->get_factor(tempos[0]); - for(const auto & event : origin) { - // move to next tempo if necessary - while(event.time > t_iter->time) { - pivot_to = self->get_time(t_iter->time, pivot_to, pivot_from, cur_factor); - pivot_from = t_iter->time; - cur_factor = self->get_factor(*t_iter); - ++t_iter; + vec> ans; ans.reserve(data.size()); + auto cur_range = std::make_pair(from_times[0], from_times[1]); + auto pivot_to = to_times[0]; + auto cur_factor = factors[0]; + for (const auto &event : data) { + if(event.time < cur_range.first | event.time >= cur_range.second) { + auto i = std::upper_bound(from_times.begin(), from_times.end(), event.time) - from_times.begin() - 1; + cur_range = std::make_pair(from_times[i], from_times[i+1]); + pivot_to = to_times[i]; + cur_factor = factors[i]; } - ans.emplace_back( - self->get_time(event.time, pivot_to, pivot_from, cur_factor), - 0, event - ); - } - // convert duration according to the end time - // reserve space for the end times (end(), index) - vec> end_times; end_times.reserve(origin.size()); - for(size_t i = 0; i < origin.size(); ++i) { - end_times.emplace_back(origin[i].end(), i); - } // sort them according to the end time - pdqsort_detail::insertion_sort(end_times.begin(), end_times.end(), [](const auto & a, const auto & b) { - return a.first < b.first; - }); - // reset pivot - pivot_from = 0; pivot_to = 0; - // reset t_iter - t_iter = tempos.begin() + 1; - // reset cur_factor - cur_factor = self->get_factor(tempos[0]); - for(const auto & [end, idx] : end_times) { - while(end > t_iter->time) { - pivot_to = self->get_time(t_iter->time, pivot_to, pivot_from, cur_factor); - pivot_from = t_iter->time; - cur_factor = self->get_factor(*t_iter); - ++t_iter; + auto start = self->get_time(event.time, pivot_to, cur_range.first, cur_factor); + if(event.end() < cur_range.first | event.end() >= cur_range.second) { + auto i = std::upper_bound(from_times.begin(), from_times.end(), event.end()) - from_times.begin() - 1; + cur_range = std::make_pair(from_times[i], from_times[i+1]); + pivot_to = to_times[i]; + cur_factor = factors[i]; } - ans[idx].duration = std::max( - min_dur, - self->get_time(end, pivot_to, pivot_from, cur_factor) - ans[idx].time - ); - } - if constexpr (std::is_same_v, Note>) { - pdqsort_detail::insertion_sort(ans.begin(), ans.end(), [](const auto & a, const auto & b) { - return std::tie(a.time, a.duration, a.pitch) < std::tie(b.time, b.duration, b.pitch); - }); - } else { - pdqsort_detail::insertion_sort(ans.begin(), ans.end(), [](const auto & a, const auto & b) { - return std::tie(a.time, a.duration) < std::tie(b.time, b.duration); - }); + auto end = self->get_time(event.end(), pivot_to, cur_range.first, cur_factor); + auto duration = std::max(min_dur, end - start); + ans.emplace_back(start, duration, event); } return ans; } @@ -249,7 +234,7 @@ struct Quarter2Second: SecondConverter { explicit Quarter2Second(const Score & score): SecondConverter{score} {} - [[nodiscard]] f64 get_factor(const Tempo & tempo) const { + [[nodiscard]] static f64 get_factor(const Tempo & tempo) { return static_cast(tempo.mspq) / 1000000.; } @@ -265,7 +250,7 @@ struct Second2Quarter: SecondConverter { explicit Second2Quarter(const Score & score): SecondConverter{score} {} - [[nodiscard]] f64 get_factor(const Tempo & tempo) const { + [[nodiscard]] static f64 get_factor(const Tempo & tempo) { return 1000000. / static_cast(tempo.mspq); }