Skip to content

Commit

Permalink
order preserving
Browse files Browse the repository at this point in the history
  • Loading branch information
Yikai-Liao committed Feb 27, 2024
1 parent f3a0c73 commit 15f8e59
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 109 deletions.
135 changes: 104 additions & 31 deletions include/symusic/ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ vec<T> clamp_dur(const vec<T>& events, typename T::unit min_dur, typename T::uni
return clamp_dur_inplace(new_events, min_dur, max_dur);
}

namespace details {
template<TimeEvent T>
vec<T> adjust_time_sorted(
const vec<T>& events, const vec<typename T::unit>& original_times, const vec<typename T::unit>& new_times) {
Expand All @@ -124,14 +125,6 @@ vec<T> 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
Expand Down Expand Up @@ -189,52 +182,132 @@ vec<T> adjust_time_sorted(
event.duration -= event.time;
}
}

return new_events;
}


template<TimeEvent T>
vec<T> adjust_time_unsorted(
const vec<T>& events, const vec<typename T::unit>& original_times, const vec<typename T::unit>& new_times) {
// check if the events have duration
constexpr bool has_dur = HashDuration<T>;
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<f64>(new_times[x] - new_times[x - 1]) /
static_cast<f64>(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<T> 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<typename T::unit>(
cur_factor * static_cast<f64>(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<typename T::unit>(
cur_factor * static_cast<f64>(original_end - cur_range.first)
);
new_events.emplace_back(start, end-start, event);
} else {
new_events.emplace_back(start, event);
}
} return new_events;
}

template<typename T>
void check_times(const vec<T>& original_times, const vec<T>& 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<bool check_times = true, TimeEvent T>
vec<T> adjust_time(
const vec<T>& events, const vec<typename T::unit>& original_times,
const vec<typename T::unit>& new_times, const bool sorted = false) {
if (sorted) { return adjust_time_sorted(events, original_times, new_times); }

vec<T> _events(events);
sort_by_time(_events);
vec<typename T::unit> _original_times(original_times);
sort(_original_times, std::less<typename T::unit>());
vec<typename T::unit> _new_times(new_times);
sort(_new_times, std::less<typename T::unit>());

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<TType T>
template<bool check_times = true, TType T>
Track<T> adjust_time(
const Track<T> & track, const vec<typename T::unit>& original_times,
const vec<typename T::unit>& new_times, const bool sorted = false) {
if constexpr (check_times) {
details::check_times(original_times, new_times);
}
Track<T> 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<false>(track.notes, original_times, new_times, sorted));
new_track.controls = std::move(adjust_time<false>(track.controls, original_times, new_times, sorted));
new_track.pitch_bends = std::move(adjust_time<false>(track.pitch_bends, original_times, new_times, sorted));
new_track.pedals = std::move(adjust_time<false>(track.pedals, original_times, new_times, sorted));
return new_track;
}

template<TType T>
template<bool check_times = true, TType T>
Score<T> adjust_time(
const Score<T> & score, const vec<typename T::unit>& original_times,
const vec<typename T::unit>& new_times, const bool sorted = false) {
if constexpr (check_times) {
details::check_times(original_times, new_times);
}
Score<T> 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<false>(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<false>(score.time_signatures, original_times, new_times, sorted);
new_score.key_signatures = adjust_time<false>(score.key_signatures, original_times, new_times, sorted);
new_score.tempos = adjust_time<false>(score.tempos, original_times, new_times, sorted);
new_score.lyrics = adjust_time<false>(score.lyrics, original_times, new_times, sorted);
new_score.markers = adjust_time<false>(score.markers, original_times, new_times, sorted);
return new_score;
}

Expand Down
6 changes: 3 additions & 3 deletions py_src/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ std::tuple<py::class_<T>, py::class_<vec<T>>> time_stamp_base(py::module_ &m, co
.def("__getstate__", &py_to_bytes<vec<T>>)
.def("__setstate__", &py_from_bytes<vec<T>>)
.def("filter", &py_filter<T>, py::arg("func"), py::arg("inplace")=false)
.def("adjust_time", &ops::adjust_time<T>, py::arg("original_times"), py::arg("new_times"), py::arg("is_sorted")=false)
.def("adjust_time", &ops::adjust_time<true, T>, py::arg("original_times"), py::arg("new_times"), py::arg("is_sorted")=false)
.def("start", &ops::start<T>, "Return the start time of the all the events")
.def("end", &ops::end<T>, "Return the end time of the all the events");
return std::make_tuple(event, vec_T);
Expand Down Expand Up @@ -528,7 +528,7 @@ py::class_<Track<T>> bind_track_class(py::module_ &m, const std::string & name_)
.def("shift_time", &py_shift_time_track<T>, py::arg("offset"), py::arg("inplace")=false)
.def("shift_pitch", &py_shift_pitch_track<T>, py::arg("offset"), py::arg("inplace")=false)
.def("shift_velocity", &py_shift_velocity_track<T>, py::arg("offset"), py::arg("inplace")=false)
.def("adjust_time", py::overload_cast<const Track<T>&, const vec<unit> &, const vec<unit> &, bool>(&ops::adjust_time<T>),
.def("adjust_time", py::overload_cast<const Track<T>&, const vec<unit> &, const vec<unit> &, bool>(&ops::adjust_time<true, T>),
py::arg("original_times"), py::arg("new_times"), py::arg("is_sorted") = false)
.def("pianoroll", [](const Track<Tick> &self,
const std::vector<std::string>& modes,
Expand Down Expand Up @@ -786,7 +786,7 @@ py::class_<Score<T>> bind_score_class(py::module_ &m, const std::string & name_)
.def("end", &Score<T>::end)
.def("note_num", &Score<T>::note_num)
.def("empty", &Score<T>::empty)
.def("adjust_time", py::overload_cast<const Score<T>&, const vec<unit> &, const vec<unit> &, bool>(&ops::adjust_time<T>),
.def("adjust_time", py::overload_cast<const Score<T>&, const vec<unit> &, const vec<unit> &, bool>(&ops::adjust_time<true, T>),
py::arg("original_times"), py::arg("new_times"), py::arg("is_sorted") = false)
.def("pianoroll", [](const Score<Tick> &self,
const std::vector<std::string>& modes,
Expand Down
135 changes: 60 additions & 75 deletions src/conversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,12 @@ struct Quarter2Tick: SimpleConverter<Quarter2Tick, Tick, Quarter> {
template<typename Converter, TType To, TType From>
struct SecondConverter {
f64 tpq;
vec<Tempo<From>> tempos{};
vec<typename To::unit> to_times;
vec<typename From::unit> from_times;
vec<f64> factors;

explicit SecondConverter(const Score<From> & score): tpq(static_cast<f64>(score.ticks_per_quarter)){
vec<Tempo<From>> tempos;
if(score.tempos.empty()) {
// 120 qpm
tempos = {{0, 500000}, {std::numeric_limits<typename From::unit>::max(), 500000}};
Expand All @@ -119,28 +122,46 @@ struct SecondConverter {
// add a guard at the end
tempos.emplace_back(std::numeric_limits<typename From::unit>::max(), tempos.back().mspq);
}
}
template<template<class> class T>
[[nodiscard]] vec<T<To>> time_vec(const vec<T<From>> & data) const {

const auto self = static_cast<const Converter*>(this);
// copy and sort origin data
vec<T<From>> origin(data); ops::sort_by_time(origin);
// reserve space for the result
vec<T<To>> 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; i<tempos.size(); ++i) {
pivot_to = self->get_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<template<class> class T>
[[nodiscard]] vec<T<To>> time_vec(const vec<T<From>> & data) const {
const auto self = static_cast<const Converter*>(this);
vec<T<To>> 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;
}
Expand All @@ -149,63 +170,27 @@ struct SecondConverter {
[[nodiscard]] vec<T<To>> duration_vec(const vec<T<From>> & data, typename To::unit min_dur) const {
const auto self = static_cast<const Converter*>(this);
min_dur = std::max(min_dur, static_cast<typename To::unit>(0));
// copy and sort origin data
vec<T<From>> origin(data); ops::sort_by_time(origin);
// reserve space for the result
vec<T<To>> 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<T<To>> 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<std::pair<typename From::unit, u32>> 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<T<From>, Note<From>>) {
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;
}
Expand Down Expand Up @@ -249,7 +234,7 @@ struct Quarter2Second: SecondConverter<Quarter2Second, Second, Quarter> {

explicit Quarter2Second(const Score<From> & score): SecondConverter{score} {}

[[nodiscard]] f64 get_factor(const Tempo<From> & tempo) const {
[[nodiscard]] static f64 get_factor(const Tempo<From> & tempo) {
return static_cast<f64>(tempo.mspq) / 1000000.;
}

Expand All @@ -265,7 +250,7 @@ struct Second2Quarter: SecondConverter<Second2Quarter, Quarter, Second> {

explicit Second2Quarter(const Score<From> & score): SecondConverter{score} {}

[[nodiscard]] f64 get_factor(const Tempo<From> & tempo) const {
[[nodiscard]] static f64 get_factor(const Tempo<From> & tempo) {
return 1000000. / static_cast<f64>(tempo.mspq);
}

Expand Down

0 comments on commit 15f8e59

Please sign in to comment.