32 [[nodiscard]] std::size_t
size() const noexcept {
39 std::uint8_t highest_pitch)
const {
41 if (notes_.empty() || start >= end || lowest_pitch > highest_pitch || lowest_pitch > 127)
43 highest_pitch = std::min<std::uint8_t>(highest_pitch, 127);
44 const auto pitch_mask = mask_for_range(lowest_pitch, highest_pitch);
45 query_node(1, 0, notes_.size(), start, end, pitch_mask, result);
50 explicit TimePitchIndex(std::span<const timeline::NoteEvent> notes)
51 : notes_(notes.begin(), notes.end()) {
52 std::sort(notes_.begin(), notes_.end(), [](
const auto& lhs,
const auto& rhs) {
53 if (lhs.start != rhs.start)
54 return lhs.start < rhs.start;
55 return lhs.id < rhs.id;
57 if (!notes_.empty()) {
58 tree_.resize(notes_.size() * 4);
59 build(1, 0, notes_.size());
64 std::int64_t max_end = std::numeric_limits<std::int64_t>::min();
65 std::array<std::uint64_t, 2> pitches{};
68 static std::array<std::uint64_t, 2> mask_for_range(std::uint8_t lowest,
69 std::uint8_t highest)
noexcept {
70 std::array<std::uint64_t, 2> mask{};
71 for (std::uint16_t pitch = lowest; pitch <= highest; ++pitch)
72 mask[pitch / 64] |= std::uint64_t{1} << (pitch % 64);
76 static bool intersects(
const std::array<std::uint64_t, 2>& lhs,
77 const std::array<std::uint64_t, 2>& rhs)
noexcept {
78 return (lhs[0] & rhs[0]) != 0 || (lhs[1] & rhs[1]) != 0;
81 void build(std::size_t node, std::size_t begin, std::size_t end) {
82 if (end - begin == 1) {
83 const auto& note = notes_[begin];
84 tree_[node].max_end = (note.start + note.duration).value;
85 tree_[node].pitches[note.pitch / 64] |= std::uint64_t{1} << (note.pitch % 64);
88 const auto middle = begin + (
end - begin) / 2;
89 build(node * 2, begin, middle);
90 build(node * 2 + 1, middle, end);
91 tree_[node].max_end = std::max(tree_[node * 2].max_end, tree_[node * 2 + 1].max_end);
92 tree_[node].pitches = {
93 tree_[node * 2].pitches[0] | tree_[node * 2 + 1].pitches[0],
94 tree_[node * 2].pitches[1] | tree_[node * 2 + 1].pitches[1],
98 void query_node(std::size_t node, std::size_t begin, std::size_t end,
99 timebase::TickPosition query_start, timebase::TickPosition query_end,
100 const std::array<std::uint64_t, 2>& pitch_mask,
101 TimePitchQueryResult& result)
const {
102 const auto& summary = tree_[node];
103 if (summary.max_end <= query_start.value || notes_[begin].start >= query_end ||
104 !intersects(summary.pitches, pitch_mask))
106 if (end - begin == 1) {
107 ++result.visited_candidates;
108 const auto& note = notes_[begin];
109 const auto note_end = note.start + note.duration;
110 if (note.start < query_end && note_end > query_start &&
111 (pitch_mask[note.pitch / 64] & (std::uint64_t{1} << (note.pitch % 64))) != 0)
112 result.notes.push_back(note);
115 const auto middle = begin + (
end - begin) / 2;
116 query_node(node * 2, begin, middle, query_start, query_end, pitch_mask, result);
117 query_node(node * 2 + 1, middle, end, query_start, query_end, pitch_mask, result);
120 std::vector<timeline::NoteEvent> notes_;
121 std::vector<Node> tree_;
Immutable MIDI clip content: start-then-identity-ordered notes with sparse deterministic modifiers,...
Definition clip.hpp:246
TimePitchQueryResult query(timebase::TickPosition start, timebase::TickPosition end, std::uint8_t lowest_pitch, std::uint8_t highest_pitch) const
Finds notes intersecting [start, end) within [lowest_pitch, highest_pitch].
Definition spatial_index.hpp:37