Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
compiled_tempo_map.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <cstddef>
8#include <cstdint>
9#include <span>
10#include <vector>
11
12namespace pulp::timebase {
13
14
15inline constexpr double kMinimumCompiledTempoBpm = 1.0;
16inline constexpr double kMaximumCompiledTempoBpm = 1'000.0;
17inline constexpr std::uint32_t kMaximumCompiledSampleRate = 768'000;
18
23
24struct TempoPoint {
26 double bpm = 120.0;
28 constexpr auto operator<=>(const TempoPoint&) const = default;
29};
30
41
42// Editable, sample-rate-independent document value. Validation is shared with
43// compilation so malformed maps never reach the playback graph.
44class TempoMap {
45 public:
46 TempoMap() : points_{{{0}, 120.0, TempoCurve::Constant}} {}
47
48 static runtime::Result<TempoMap, TempoMapError>
49 create(std::span<const TempoPoint> points) noexcept;
50
52 replacing_points(std::span<const TempoPoint> points) const noexcept {
53 return create(points);
54 }
55
56 std::span<const TempoPoint> points() const noexcept {
57 return points_;
58 }
59 constexpr auto operator<=>(const TempoMap&) const = default;
60
61 private:
62 explicit TempoMap(std::vector<TempoPoint> points) : points_(std::move(points)) {}
63 std::vector<TempoPoint> points_;
64};
65
72
73// Immutable, sample-rate-specific tempo lookup. compile() validates and owns a
74// copy of the authored points and may allocate, so it belongs on the control
75// thread. After the resulting value is published with a lifetime covering all
76// readers, ticks_to_samples() and resolve_sample() are const, allocation-free
77// lookups suitable for the audio thread.
78//
79// Tempo ramps are linear in
80// musical tick position; their tick-to-sample integral and inverse are
81// analytic. Integer sample anchors at every tempo point prevent cumulative
82// floating-point drift across segments. When the integer tick grid is at least
83// as dense as the sample grid, every sample within TickPosition's representable
84// range has an exact canonical tick. A sparser grid cannot represent every
85// sample; resolve_sample() then returns the nearest tick and reports its
86// explicit sample error. Both position types use their full signed 64-bit
87// range. ticks_to_samples() saturates if its mathematical result exceeds the
88// sample domain. Resolving a sample outside the image of TickPosition returns
89// a nearest canonical edge representation with exact=false and the actual
90// sample error.
91class CompiledTempoMap {
92 public:
94 compile(std::span<const TempoPoint> points, RationalRate sample_rate) noexcept;
96 compile(const TempoMap& map, RationalRate sample_rate) noexcept {
97 return compile(map.points(), sample_rate);
98 }
99
106 long double fractional_ticks_to_samples(long double tick) const noexcept;
110 long double fractional_samples_to_ticks(long double sample) const noexcept;
111 double tempo_at_tick(TickPosition tick) const noexcept;
113 double maximum_tempo_between(TickPosition start, TickPosition end) const noexcept;
114
118 bool matches(std::span<const TempoPoint> points) const noexcept;
119
120 RationalRate sample_rate() const noexcept {
121 return sample_rate_;
122 }
123 std::size_t segment_count() const noexcept {
124 return segments_.size();
125 }
126
127 private:
128 friend class TempoCursor;
129 friend class TempoTickCursor;
130 struct Segment {
131 TickPosition start_tick;
132 TickPosition end_tick;
133 SamplePosition start_sample;
134 double start_bpm = 120.0;
135 double end_bpm = 120.0;
137 };
138
139 long double samples_from_segment_start(const Segment& segment,
140 std::int64_t delta_ticks) const noexcept;
141 long double ticks_from_segment_start(const Segment& segment,
142 long double delta_samples) const noexcept;
143 long double fractional_samples_to_ticks_in_segment(long double sample,
144 std::size_t segment) const noexcept;
145 long double fractional_ticks_to_samples_in_segment(long double tick,
146 std::size_t segment) const noexcept;
147 std::size_t segment_for_tick(TickPosition tick) const noexcept;
148 std::size_t segment_for_sample(SamplePosition sample) const noexcept;
149 SamplePosition ticks_to_samples_in_segment(TickPosition tick,
150 std::size_t segment) const noexcept;
151 SampleToTickResult resolve_sample_in_segment(SamplePosition sample,
152 std::size_t segment) const noexcept;
153
154 CompiledTempoMap(RationalRate sample_rate, std::vector<Segment> segments) noexcept
155 : sample_rate_(sample_rate), segments_(std::move(segments)) {}
156
157 RationalRate sample_rate_;
158 std::vector<Segment> segments_;
159};
160
161// Allocation-free streaming resolver. Forward segment changes are consumed
162// once, giving amortized O(1) segment selection for monotonic playback. A
163// backward sample is an explicit discontinuity and performs one cold seek.
165 public:
166 TempoCursor() = default;
167 explicit TempoCursor(const CompiledTempoMap& map) noexcept {
168 reset(map);
169 }
170
171 void reset(const CompiledTempoMap& map) noexcept;
176 long double advance_fractional(long double sample) noexcept;
177 double tempo_at_tick(TickPosition tick) noexcept;
178
179 std::size_t segment_index() const noexcept {
180 return segment_index_;
181 }
182
183 private:
184 const CompiledTempoMap* map_ = nullptr;
185 std::size_t segment_index_ = 0;
186 SamplePosition sample_{};
187 bool positioned_ = false;
188};
189
190// Allocation-free streaming tick resolver. Forward segment changes are consumed
191// once, giving amortized O(1) segment selection for monotonic host beat mapping.
193 public:
194 TempoTickCursor() = default;
195 explicit TempoTickCursor(const CompiledTempoMap& map) noexcept : map_(&map) {}
196
197 long double advance_fractional(long double tick) noexcept;
198
199 std::size_t segment_index() const noexcept {
200 return segment_index_;
201 }
202
203 private:
204 const CompiledTempoMap* map_ = nullptr;
205 std::size_t segment_index_ = 0;
206 long double tick_ = 0.0L;
207 bool positioned_ = false;
208};
209
210} // namespace pulp::timebase
Result<T, E> — either holds a T or an E, never both.
Definition result.hpp:76
Definition compiled_tempo_map.hpp:91
bool matches(std::span< const TempoPoint > points) const noexcept
True when this compiled map was built from exactly these canonical editable points.
long double fractional_samples_to_ticks(long double sample) const noexcept
Continuous inverse of fractional_ticks_to_samples() for render-time mapping.
static runtime::Result< CompiledTempoMap, TempoMapError > compile(std::span< const TempoPoint > points, RationalRate sample_rate) noexcept
double tempo_at_tick(TickPosition tick) const noexcept
long double fractional_ticks_to_samples(long double tick) const noexcept
Continuous counterpart to ticks_to_samples() for render-time interpolation.
RationalRate sample_rate() const noexcept
Definition compiled_tempo_map.hpp:120
double maximum_tempo_between(TickPosition start, TickPosition end) const noexcept
Maximum tempo over [start, end], including every intersected ramp extremum.
friend class TempoCursor
Definition compiled_tempo_map.hpp:128
SamplePosition ticks_to_samples(TickPosition tick) const noexcept
static runtime::Result< CompiledTempoMap, TempoMapError > compile(const TempoMap &map, RationalRate sample_rate) noexcept
Definition compiled_tempo_map.hpp:96
SampleToTickResult resolve_sample(SamplePosition sample) const noexcept
friend class TempoTickCursor
Definition compiled_tempo_map.hpp:129
TickPosition samples_to_ticks(SamplePosition sample) const noexcept
std::size_t segment_count() const noexcept
Definition compiled_tempo_map.hpp:123
long double advance_fractional(long double sample) noexcept
Continuous monotonic counterpart to advance().
SampleToTickResult seek(SamplePosition sample) noexcept
double tempo_at_tick(TickPosition tick) noexcept
TempoCursor(const CompiledTempoMap &map) noexcept
Definition compiled_tempo_map.hpp:167
SampleToTickResult advance(SamplePosition sample) noexcept
std::size_t segment_index() const noexcept
Definition compiled_tempo_map.hpp:179
void reset(const CompiledTempoMap &map) noexcept
Definition compiled_tempo_map.hpp:44
constexpr auto operator<=>(const TempoMap &) const =default
std::span< const TempoPoint > points() const noexcept
Definition compiled_tempo_map.hpp:56
TempoMap()
Definition compiled_tempo_map.hpp:46
static runtime::Result< TempoMap, TempoMapError > create(std::span< const TempoPoint > points) noexcept
runtime::Result< TempoMap, TempoMapError > replacing_points(std::span< const TempoPoint > points) const noexcept
Definition compiled_tempo_map.hpp:52
TempoTickCursor(const CompiledTempoMap &map) noexcept
Definition compiled_tempo_map.hpp:195
std::size_t segment_index() const noexcept
Definition compiled_tempo_map.hpp:199
long double advance_fractional(long double tick) noexcept
Definition beat_division.hpp:11
constexpr std::uint32_t kMaximumCompiledSampleRate
Definition compiled_tempo_map.hpp:17
TempoCurve
Definition compiled_tempo_map.hpp:19
@ LinearInTicks
Definition compiled_tempo_map.hpp:21
@ Constant
Definition compiled_tempo_map.hpp:20
constexpr double kMinimumCompiledTempoBpm
Definition compiled_tempo_map.hpp:15
constexpr double kMaximumCompiledTempoBpm
Definition compiled_tempo_map.hpp:16
TempoMapError
Definition compiled_tempo_map.hpp:31
@ InvalidFinalCurve
Definition compiled_tempo_map.hpp:36
@ InvalidCurve
Definition compiled_tempo_map.hpp:35
@ InvalidSampleRate
Definition compiled_tempo_map.hpp:38
@ InvalidBpm
Definition compiled_tempo_map.hpp:34
@ SampleRangeExceeded
Definition compiled_tempo_map.hpp:39
@ UnorderedPoints
Definition compiled_meter_map.hpp:41
@ Empty
Definition compiled_meter_map.hpp:38
@ MissingTickZero
Definition compiled_meter_map.hpp:39
STL namespace.
Result<T, E> — success / failure union for fallible operations.
Definition rational_time.hpp:14
Definition rational_time.hpp:9
Definition compiled_tempo_map.hpp:66
bool exact
Definition compiled_tempo_map.hpp:70
TickPosition tick
Definition compiled_tempo_map.hpp:67
SamplePosition represented_sample
Definition compiled_tempo_map.hpp:68
std::uint64_t absolute_error_samples
Definition compiled_tempo_map.hpp:69
Definition compiled_tempo_map.hpp:24
TickPosition tick
Definition compiled_tempo_map.hpp:25
double bpm
Definition compiled_tempo_map.hpp:26
TempoCurve curve_to_next
Definition compiled_tempo_map.hpp:27
constexpr auto operator<=>(const TempoPoint &) const =default
Definition tick.hpp:61