10 return std::clamp(normalized, 0.0f, 1.0f);
28 static_assert(MaximumMappings > 0);
36 if (index >= MaximumMappings)
38 if (rule.input_channel > 15 || rule.output_channel > 15 || rule.input_cc > 127 ||
41 if (!std::isfinite(rule.minimum) || !std::isfinite(rule.maximum) ||
42 !std::isfinite(rule.curve_amount) || !std::isfinite(rule.smoothing_seconds))
44 if (rule.minimum > rule.maximum)
45 std::swap(rule.minimum, rule.maximum);
46 if (rule.smoothing_seconds < 0.0f)
47 rule.smoothing_seconds = 0.0f;
48 if (rule.curve ==
nullptr)
59 return {0, input.
size(), 0,
false};
61 MidiUtilityProcessReport report;
63 return {0, input.
size(), 0,
false};
64 if (!std::isfinite(sample_rate))
65 return {0, input.
size(), 0,
false};
66 for (
const auto& event : input) {
72 for (std::size_t i = 0;
i < MaximumMappings; ++
i) {
73 const auto& rule = rules_[
i];
74 if (!rule.enabled || rule.input_channel != event.channel() ||
75 rule.input_cc != event.cc_number())
78 auto& state = states_[
i];
79 const float normalized =
static_cast<float>(
event.cc_value()) / 127.0f;
80 const float raw_curve = rule.curve(normalized, rule.curve_amount);
81 if (!std::isfinite(raw_curve)) {
83 report.complete =
false;
86 const float curved = std::clamp(raw_curve, 0.0f, 1.0f);
88 std::lerp(
static_cast<double>(rule.minimum),
static_cast<double>(rule.maximum),
89 static_cast<double>(curved));
90 if (!std::isfinite(target)) {
92 report.complete =
false;
95 double current = target;
96 if (!state.initialized || rule.smoothing_seconds <= 0.0f || sample_rate <= 0.0) {
100 block_start.value, event.sample_offset);
102 nonnegative_sample_distance(event_sample, state.last_sample);
103 const double coefficient =
104 std::exp(-
static_cast<double>(elapsed) /
105 (sample_rate *
static_cast<double>(rule.smoothing_seconds)));
106 current = std::lerp(target, state.current, coefficient);
108 if (!std::isfinite(current)) {
110 report.complete =
false;
113 state.target = target;
114 state.current = current;
115 state.initialized =
true;
119 static_cast<int>(std::lround(std::clamp(current, 0.0, 1.0) * 127.0));
120 auto mapped_event =
MidiEvent::cc(rule.output_channel, rule.output_cc,
121 static_cast<std::uint8_t
>(
value));
122 mapped_event.sample_offset =
event.sample_offset;
132 float value(std::size_t index)
const noexcept {
133 return index < MaximumMappings && states_[index].initialized
134 ?
static_cast<float>(states_[index].current)
149 static constexpr std::uint64_t nonnegative_sample_distance(std::int64_t later,
150 std::int64_t earlier)
noexcept {
151 if (later <= earlier)
153 return static_cast<std::uint64_t
>(later) -
static_cast<std::uint64_t
>(earlier);
157 double current = 0.0;
159 std::int64_t last_sample = 0;
160 bool initialized =
false;
162 std::array<ControllerMapRule, MaximumMappings> rules_{};
163 std::array<State, MaximumMappings> states_{};
181 : spec_(sanitize(spec)) {}
184 const int source_note = std::min<int>(note.note, 127);
186 const float finite_bend =
187 std::isfinite(note.pitch_bend_semitones) ? note.pitch_bend_semitones : 0.0f;
188 const float normalized =
189 std::clamp(finite_bend / spec_.input_bend_range_semitones, -1.0f, 1.0f);
190 const float degree_position = normalized * spec_.bend_range_degrees;
191 const int low_degree =
static_cast<int>(std::floor(degree_position));
192 const int high_degree =
static_cast<int>(std::ceil(degree_position));
193 const float fraction = degree_position -
static_cast<float>(low_degree);
194 const int anchor_degree = nearest_degree(anchor, scale);
195 const auto low = scale.degree_to_semitones(anchor_degree + low_degree).value_or(0) -
196 scale.degree_to_semitones(anchor_degree).value_or(0);
197 const auto high = scale.degree_to_semitones(anchor_degree + high_degree).value_or(0) -
198 scale.degree_to_semitones(anchor_degree).value_or(0);
199 const double scale_bend =
200 static_cast<double>(low) +
static_cast<double>(high - low) * fraction;
201 const double computed_cents =
202 static_cast<double>((anchor - source_note) * 100) + scale_bend * 100.0;
203 target_cents_ = std::isfinite(computed_cents) &&
204 std::abs(computed_cents) <=
205 static_cast<double>(std::numeric_limits<float>::max())
206 ?
static_cast<float>(computed_cents)
209 current_cents_ = target_cents_;
212 return target_cents_;
215 float advance(std::uint32_t samples,
double sample_rate)
noexcept {
218 if (spec_.glide_seconds <= 0.0f || sample_rate <= 0.0 || !std::isfinite(sample_rate)) {
219 current_cents_ = target_cents_;
220 return current_cents_;
222 const double coefficient =
223 std::exp(-
static_cast<double>(samples) /
224 (sample_rate *
static_cast<double>(spec_.glide_seconds)));
225 const double next =
static_cast<double>(target_cents_) +
226 (
static_cast<double>(current_cents_) - target_cents_) * coefficient;
227 current_cents_ = std::isfinite(next) ?
static_cast<float>(next) : target_cents_;
228 return current_cents_;
232 return current_cents_;
235 initialized_ =
false;
236 current_cents_ = target_cents_ = 0.0f;
239 spec_ = sanitize(spec);
245 if (!(spec.input_bend_range_semitones > 0.0f) ||
246 !std::isfinite(spec.input_bend_range_semitones))
248 if (!(spec.bend_range_degrees >= 0.0f) || !std::isfinite(spec.bend_range_degrees))
249 spec.bend_range_degrees = 0.0f;
250 spec.bend_range_degrees = std::min(spec.bend_range_degrees, 128.0f);
251 if (!(spec.glide_seconds >= 0.0f) || !std::isfinite(spec.glide_seconds))
252 spec.glide_seconds = 0.0f;
256 static int nearest_degree(
int note,
const music::Scale& scale)
noexcept {
257 const int root =
static_cast<int>(scale.root());
258 const int approximate_octave = (note - root) / 12;
259 const int center = approximate_octave *
static_cast<int>(scale.degree_count());
260 int best_degree = center;
261 int best_distance = std::numeric_limits<int>::max();
262 for (
int degree = center -
static_cast<int>(scale.degree_count());
263 degree <= center +
static_cast<int>(scale.degree_count()); ++degree) {
264 const auto semitones = scale.degree_to_semitones(degree);
267 const int candidate = root + *semitones;
268 const int distance = std::abs(candidate - note);
269 if (distance < best_distance || (distance == best_distance && candidate < note)) {
270 best_degree = degree;
271 best_distance = distance;
277 ScaleAwareMpeSpec spec_{};
278 float current_cents_ = 0.0f;
279 float target_cents_ = 0.0f;
280 bool initialized_ =
false;
Definition controller_utility_kernels.hpp:26
void reset() noexcept
Definition controller_utility_kernels.hpp:138
void hot_swap_reset() noexcept
Definition controller_utility_kernels.hpp:144
bool set_rule(std::size_t index, ControllerMapRule rule) noexcept
Definition controller_utility_kernels.hpp:35
void flush() noexcept
Definition controller_utility_kernels.hpp:141
float value(std::size_t index) const noexcept
Definition controller_utility_kernels.hpp:132
MidiUtilityProcessReport process(const MidiBuffer &input, MidiBuffer &output, double sample_rate, timebase::SamplePosition block_start={}) noexcept
Definition controller_utility_kernels.hpp:55
static constexpr MidiUtilityContract contract() noexcept
Definition controller_utility_kernels.hpp:29
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
std::size_t size() const
Definition buffer.hpp:123
static constexpr float kDefaultMemberBendSemitones
Default member-channel pitch-bend range (MPE spec).
Definition mpe_voice_tracker.hpp:73
float current_cents() const noexcept
Definition controller_utility_kernels.hpp:231
static constexpr MidiUtilityContract contract() noexcept
Definition controller_utility_kernels.hpp:174
void replace_spec(ScaleAwareMpeSpec spec) noexcept
Definition controller_utility_kernels.hpp:238
float advance(std::uint32_t samples, double sample_rate) noexcept
Definition controller_utility_kernels.hpp:215
float target_cents(const MpeNoteState ¬e, const music::Scale &scale) noexcept
Definition controller_utility_kernels.hpp:183
constexpr ScaleAwareMpePitch(ScaleAwareMpeSpec spec={}) noexcept
Definition controller_utility_kernels.hpp:180
void reset() noexcept
Definition controller_utility_kernels.hpp:234
void copy_sidecars(const MidiBuffer &input, MidiBuffer &output, MidiUtilityProcessReport &report) noexcept
Definition utility_contract.hpp:96
void clear_output(MidiBuffer &output) noexcept
Definition utility_contract.hpp:82
bool blocks_alias(const MidiBuffer &first, const MidiBuffer &second) noexcept
Definition utility_contract.hpp:92
constexpr std::int64_t saturating_sample_add(std::int64_t position, std::int64_t offset) noexcept
Definition utility_contract.hpp:59
int nearest_scale_note(int note, const music::Scale &scale) noexcept
Definition utility_contract.hpp:165
bool ready(const MidiBuffer &output) noexcept
Definition utility_contract.hpp:86
bool emit(MidiBuffer &output, const MidiEvent &event, MidiUtilityProcessReport &report)
Definition utility_contract.hpp:67
Definition custom_node_events.hpp:26
@ MonotonicSamples
Definition utility_contract.hpp:32
@ DropUnstarted
Definition utility_contract.hpp:20
float(*)(float normalized, float amount) noexcept ControllerCurve
Definition controller_utility_kernels.hpp:7
@ InputStable
Definition utility_contract.hpp:26
float linear_controller_curve(float normalized, float) noexcept
Definition controller_utility_kernels.hpp:9
@ i
Definition formant_filter_bank.hpp:20
Definition controller_utility_kernels.hpp:13
float minimum
Definition controller_utility_kernels.hpp:18
std::uint8_t output_cc
Definition controller_utility_kernels.hpp:17
float smoothing_seconds
Definition controller_utility_kernels.hpp:21
std::uint8_t input_channel
Definition controller_utility_kernels.hpp:14
ControllerCurve curve
Definition controller_utility_kernels.hpp:22
bool enabled
Definition controller_utility_kernels.hpp:23
std::uint8_t output_channel
Definition controller_utility_kernels.hpp:16
float maximum
Definition controller_utility_kernels.hpp:19
float curve_amount
Definition controller_utility_kernels.hpp:20
std::uint8_t input_cc
Definition controller_utility_kernels.hpp:15
static MidiEvent cc(uint8_t channel, uint8_t controller, uint8_t value)
Create a Control Change event.
Definition message.hpp:64
Definition utility_contract.hpp:36
Definition utility_contract.hpp:46
Per-note state maintained by MpeVoiceTracker.
Definition mpe_voice_tracker.hpp:44
Definition controller_utility_kernels.hpp:166
float input_bend_range_semitones
Definition controller_utility_kernels.hpp:167
float bend_range_degrees
Definition controller_utility_kernels.hpp:168
float glide_seconds
Definition controller_utility_kernels.hpp:169
Definition rational_time.hpp:9