Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
spatial_index.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <array>
7#include <cstddef>
8#include <cstdint>
9#include <limits>
10#include <span>
11#include <vector>
12
13namespace pulp::timeline_editor {
14
17 std::vector<timeline::NoteEvent> notes;
18 std::size_t visited_candidates = 0;
19};
20
28 public:
29 explicit TimePitchIndex(const timeline::MidiContent& content)
30 : TimePitchIndex(content.notes()) {}
31
32 [[nodiscard]] std::size_t size() const noexcept {
33 return notes_.size();
34 }
35
38 timebase::TickPosition end, std::uint8_t lowest_pitch,
39 std::uint8_t highest_pitch) const {
41 if (notes_.empty() || start >= end || lowest_pitch > highest_pitch || lowest_pitch > 127)
42 return result;
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);
46 return result;
47 }
48
49 private:
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;
56 });
57 if (!notes_.empty()) {
58 tree_.resize(notes_.size() * 4);
59 build(1, 0, notes_.size());
60 }
61 }
62
63 struct Node {
64 std::int64_t max_end = std::numeric_limits<std::int64_t>::min();
65 std::array<std::uint64_t, 2> pitches{};
66 };
67
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);
73 return mask;
74 }
75
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;
79 }
80
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);
86 return;
87 }
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],
95 };
96 }
97
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))
105 return;
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);
113 return;
114 }
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);
118 }
119
120 std::vector<timeline::NoteEvent> notes_;
121 std::vector<Node> tree_;
122};
123
124} // namespace pulp::timeline_editor
Immutable MIDI clip content: start-then-identity-ordered notes with sparse deterministic modifiers,...
Definition clip.hpp:246
std::size_t size() const noexcept
Definition spatial_index.hpp:32
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
TimePitchIndex(const timeline::MidiContent &content)
Definition spatial_index.hpp:29
@ middle
Definition canvas.hpp:230
@ end
Definition editor_host.hpp:354
ScriptedUiHost is a SequencerUiHost whose playhead is written by the caller instead of an engine,...
Definition edit_intent.hpp:14
Definition tick.hpp:61
Notes intersecting one time-and-pitch query, plus structural work diagnostics.
Definition spatial_index.hpp:16
std::size_t visited_candidates
Definition spatial_index.hpp:18
std::vector< timeline::NoteEvent > notes
Definition spatial_index.hpp:17