Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
groove_kernel.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <array>
7#include <compare>
8#include <cstddef>
9#include <cstdint>
10#include <limits>
11#include <numeric>
12#include <span>
13
14namespace pulp::timebase {
15
16// These numeric domains intentionally match timeline::GrooveTemplate so its
17// authored values can feed this dependency-lower, allocation-free kernel.
18inline constexpr std::int32_t kGrooveKernelUnitScale = 1'000;
19inline constexpr std::int32_t kMaximumGrooveKernelVelocityScale = 4'000;
20inline constexpr std::size_t kMaximumGrooveKernelSteps = 1'024;
21inline constexpr std::size_t kMaximumGrooveValidationBoundaries = 65'536;
22
26 constexpr auto operator<=>(const GrooveKernelStep&) const = default;
27};
28
30 // Zero disables swing. Swing and table grids are deliberately independent.
33 // Zero exactly when steps is empty.
35 std::span<const GrooveKernelStep> steps{};
38};
39
52
53// This is not the canonical, named, sequence-owned GrooveTemplate. It is a
54// fixed-capacity projection kernel for callers that require the stricter
55// non-reordering subset of that authored model on a realtime path.
56class OrderPreservingGrooveKernel {
57 public:
59 create(GrooveKernelInput input) noexcept {
60 if (input.swing_grid.value < 0 ||
61 (input.swing_grid.value != 0 && !valid_swing_grid(input.swing_grid)))
63 if (!valid_swing_ratio(input.swing))
65 if ((input.table_grid.value != 0) != !input.steps.empty() || input.table_grid.value < 0 ||
66 input.table_grid.value > kMaxSwingGridTicks)
68 if (input.steps.size() > kMaximumGrooveKernelSteps)
70 if (input.timing_strength < 0 || input.timing_strength > kGrooveKernelUnitScale ||
71 input.velocity_strength < 0 || input.velocity_strength > kGrooveKernelUnitScale)
73 for (const auto& step : input.steps) {
74 if (step.timing_offset.value <= -input.table_grid.value ||
75 step.timing_offset.value >= input.table_grid.value)
77 if (step.velocity_scale < 0 || step.velocity_scale > kMaximumGrooveKernelVelocityScale)
79 }
80
81 OrderPreservingGrooveKernel result(input);
82 const auto order = result.validate_order();
83 if (order != GrooveKernelError::None)
84 return runtime::Err(order);
85 return runtime::Ok(result);
86 }
87
88 TickDuration swing_grid() const noexcept {
89 return swing_grid_;
90 }
91 SwingRatio swing() const noexcept {
92 return swing_;
93 }
94 TickDuration table_grid() const noexcept {
95 return table_grid_;
96 }
97 std::span<const GrooveKernelStep> steps() const noexcept {
98 return {steps_.data(), size_};
99 }
100 std::int32_t timing_strength() const noexcept {
101 return timing_strength_;
102 }
103 std::int32_t velocity_strength() const noexcept {
104 return velocity_strength_;
105 }
106
108 apply_timing(TickPosition authored) const noexcept {
109 std::int64_t swing_offset = 0;
110 if (swing_grid_.value != 0)
111 swing_offset = scaled_by_strength(
112 swing_displacement(authored, swing_grid_, swing_).value, timing_strength_);
113 std::int64_t table_offset = 0;
114 if (const auto* step = step_at(authored)) {
115 table_offset = scaled_by_strength(step->timing_offset.value, timing_strength_);
116 }
117 std::int64_t displacement = 0;
118 if (!checked_add(swing_offset, table_offset, displacement))
120 std::int64_t transformed = 0;
121 if (!checked_add(authored.value, displacement, transformed))
123 return runtime::Ok(TickPosition{transformed});
124 }
125
126 std::int32_t velocity_scale_at(TickPosition authored) const noexcept {
127 const auto* step = step_at(authored);
128 if (step == nullptr)
130 const auto deviation =
131 static_cast<std::int64_t>(step->velocity_scale) - kGrooveKernelUnitScale;
132 return static_cast<std::int32_t>(kGrooveKernelUnitScale +
133 scaled_by_strength(deviation, velocity_strength_));
134 }
135
136 private:
137 explicit OrderPreservingGrooveKernel(GrooveKernelInput input) noexcept
138 : swing_grid_(input.swing_grid), swing_(input.swing), table_grid_(input.table_grid),
139 size_(input.steps.size()), timing_strength_(input.timing_strength),
140 velocity_strength_(input.velocity_strength) {
141 for (std::size_t index = 0; index < size_; ++index)
142 steps_[index] = input.steps[index];
143 }
144
145 static constexpr bool checked_add(std::int64_t lhs, std::int64_t rhs,
146 std::int64_t& result) noexcept {
147 constexpr auto min = std::numeric_limits<std::int64_t>::min();
148 constexpr auto max = std::numeric_limits<std::int64_t>::max();
149 if ((rhs > 0 && lhs > max - rhs) || (rhs < 0 && lhs < min - rhs))
150 return false;
151 result = lhs + rhs;
152 return true;
153 }
154
155 static constexpr std::int64_t scaled_by_strength(std::int64_t value,
156 std::int32_t strength) noexcept {
157 if (value == 0 || strength == 0)
158 return 0;
159 const auto negative = value < 0;
160 const auto magnitude = negative ? static_cast<std::uint64_t>(-(value + 1)) + 1U
161 : static_cast<std::uint64_t>(value);
162 const auto whole = magnitude / kGrooveKernelUnitScale;
163 const auto remainder = magnitude % kGrooveKernelUnitScale;
164 const auto scaled =
165 whole * static_cast<std::uint32_t>(strength) +
166 (remainder * static_cast<std::uint32_t>(strength) + kGrooveKernelUnitScale / 2U) /
168 if (!negative)
169 return static_cast<std::int64_t>(scaled);
170 if (scaled == (std::uint64_t{1} << 63U))
171 return std::numeric_limits<std::int64_t>::min();
172 return -static_cast<std::int64_t>(scaled);
173 }
174
175 const GrooveKernelStep* step_at(TickPosition authored) const noexcept {
176 if (size_ == 0)
177 return nullptr;
178 auto index = authored.value / table_grid_.value;
179 if (authored.value % table_grid_.value < 0)
180 --index;
181 auto slot = index % static_cast<std::int64_t>(size_);
182 if (slot < 0)
183 slot += static_cast<std::int64_t>(size_);
184 return &steps_[static_cast<std::size_t>(slot)];
185 }
186
187 GrooveKernelError validate_order() const noexcept {
188 if (timing_strength_ == 0)
190 if (size_ == 0)
192
193 const auto swing_period = swing_grid_.value == 0 ? std::int64_t{1} : 2 * swing_grid_.value;
194 const auto phase_count = swing_period / std::gcd(swing_period, table_grid_.value);
195 const auto step_count = static_cast<std::int64_t>(size_);
196 const auto divisor = std::gcd(phase_count, step_count);
197 if (phase_count >
198 static_cast<std::int64_t>(kMaximumGrooveValidationBoundaries) / (step_count / divisor))
200 const auto boundaries = phase_count * (step_count / divisor);
201
202 for (std::int64_t index = 1; index <= boundaries; ++index) {
203 const auto boundary = index * table_grid_.value;
204 const auto before = apply_timing({boundary - 1});
205 const auto after = apply_timing({boundary});
206 if (!before || !after)
208 if (before.value() > after.value())
210 }
212 }
213
214 TickDuration swing_grid_{};
215 SwingRatio swing_{};
216 TickDuration table_grid_{};
217 std::array<GrooveKernelStep, kMaximumGrooveKernelSteps> steps_{};
218 std::size_t size_ = 0;
219 std::int32_t timing_strength_ = kGrooveKernelUnitScale;
220 std::int32_t velocity_strength_ = kGrooveKernelUnitScale;
221};
222
223} // namespace pulp::timebase
Result<T, E> — either holds a T or an E, never both.
Definition result.hpp:76
Definition groove_kernel.hpp:56
runtime::Result< TickPosition, GrooveKernelError > apply_timing(TickPosition authored) const noexcept
Definition groove_kernel.hpp:108
std::int32_t velocity_scale_at(TickPosition authored) const noexcept
Definition groove_kernel.hpp:126
std::int32_t timing_strength() const noexcept
Definition groove_kernel.hpp:100
std::span< const GrooveKernelStep > steps() const noexcept
Definition groove_kernel.hpp:97
TickDuration swing_grid() const noexcept
Definition groove_kernel.hpp:88
std::int32_t velocity_strength() const noexcept
Definition groove_kernel.hpp:103
static runtime::Result< OrderPreservingGrooveKernel, GrooveKernelError > create(GrooveKernelInput input) noexcept
Definition groove_kernel.hpp:59
SwingRatio swing() const noexcept
Definition groove_kernel.hpp:91
TickDuration table_grid() const noexcept
Definition groove_kernel.hpp:94
@ index
Definition gpu_pass.hpp:140
Err(E) -> Err< E >
@ Ok
No error; bytes is authoritative.
Definition stream.hpp:32
Definition beat_division.hpp:11
constexpr bool valid_swing_ratio(SwingRatio ratio) noexcept
Definition quantize.hpp:79
constexpr bool valid_swing_grid(TickDuration grid) noexcept
Definition quantize.hpp:72
TickDuration swing_displacement(TickPosition position, TickDuration grid, SwingRatio ratio) noexcept
Definition quantize.hpp:114
GrooveKernelError
Definition groove_kernel.hpp:40
@ ReordersEvents
Definition groove_kernel.hpp:48
@ ValidationLimitExceeded
Definition groove_kernel.hpp:49
@ InvalidSwing
Definition groove_kernel.hpp:43
@ None
Definition groove_kernel.hpp:41
@ InvalidTable
Definition groove_kernel.hpp:44
@ TooManySteps
Definition groove_kernel.hpp:45
@ RangeExceeded
Definition groove_kernel.hpp:50
@ InvalidStrength
Definition groove_kernel.hpp:46
@ InvalidSwingGrid
Definition groove_kernel.hpp:42
@ InvalidVelocityScale
Definition groove_kernel.hpp:47
constexpr std::int32_t kMaximumGrooveKernelVelocityScale
Definition groove_kernel.hpp:19
constexpr std::size_t kMaximumGrooveValidationBoundaries
Definition groove_kernel.hpp:21
constexpr std::int64_t kMaxSwingGridTicks
Definition quantize.hpp:69
@ RangeExceeded
Definition beat_division.hpp:49
constexpr std::size_t kMaximumGrooveKernelSteps
Definition groove_kernel.hpp:20
@ None
Definition grid_projection.hpp:97
constexpr std::int32_t kGrooveKernelUnitScale
Definition groove_kernel.hpp:18
constexpr SwingRatio kStraightSwing
Definition quantize.hpp:61
STL namespace.
Result<T, E> — success / failure union for fallible operations.
Definition groove_kernel.hpp:29
std::int32_t timing_strength
Definition groove_kernel.hpp:36
std::int32_t velocity_strength
Definition groove_kernel.hpp:37
std::span< const GrooveKernelStep > steps
Definition groove_kernel.hpp:35
SwingRatio swing
Definition groove_kernel.hpp:32
TickDuration table_grid
Definition groove_kernel.hpp:34
TickDuration swing_grid
Definition groove_kernel.hpp:31
Definition groove_kernel.hpp:23
std::int32_t velocity_scale
Definition groove_kernel.hpp:25
constexpr auto operator<=>(const GrooveKernelStep &) const =default
TickDuration timing_offset
Definition groove_kernel.hpp:24
Definition quantize.hpp:53
Definition tick.hpp:56
Definition tick.hpp:61