39template <std::
size_t MaximumPendingAttacks = 128>
class Humanize {
53 return spec.minimum_timing_samples >= 0 &&
54 spec.minimum_timing_samples <=
spec.timing_samples &&
spec.velocity_amount <= 127;
66 constexpr bool valid() const noexcept {
79 std::uint8_t note, std::int64_t absolute)
noexcept {
80 if (
spec.timing_samples <= 0)
83 static_cast<std::uint64_t
>(
spec.timing_samples -
spec.minimum_timing_samples) + 1;
84 const auto draw = draw_value(
spec.seed, channel, note, absolute,
kTimingStream, width);
86 static_cast<std::int64_t
>(draw));
93 std::uint8_t note, std::int64_t absolute,
94 std::uint8_t velocity)
noexcept {
95 if (
spec.velocity_amount == 0)
97 const auto span =
static_cast<std::uint64_t
>(
spec.velocity_amount) * 2 + 1;
100 static_cast<std::int32_t
>(draw) -
static_cast<std::int32_t
>(
spec.velocity_amount);
101 return static_cast<std::uint8_t
>(std::clamp(
static_cast<std::int32_t
>(velocity) + offset, 1, 127));
106 std::int32_t block_samples)
noexcept {
108 return {0, input.size(), 0,
false};
116 const std::int64_t block_end =
118 for (
const auto& event : input) {
119 const auto absolute =
121 emit_due_before(absolute, block_start, output, report);
122 const bool is_attack =
event.is_note_on() &&
event.velocity() != 0;
123 const bool is_release =
124 event.is_note_off() || (
event.is_note_on() && event.velocity() == 0);
126 schedule_attack(event, absolute, block_start, output, report);
127 }
else if (is_release) {
133 clamp_pending_against_release(key, absolute, block_start, output, report);
139 emit_due_before(block_end, block_start, output, report);
155 for (
auto& slot : pending_) {
158 if (!emit_pending(slot, slot.scheduled, block_start, output, report)) {
165 if (report.
complete && pending_spec_) {
166 spec_ = *pending_spec_;
168 pending_spec_.reset();
175 return flush(output, block_start);
182 return {0, 0, 0,
false};
184 auto report =
flush(output, block_start);
185 if (report.complete) {
188 pending_spec_.reset();
190 pending_spec_ =
spec;
204 pending_spec_.reset();
209 for (
const auto& slot : pending_)
218 std::int64_t scheduled = 0;
223 Pending* find_pending(
int key)
noexcept {
224 for (
auto& slot : pending_)
225 if (slot.active && slot.key == key)
230 Pending* free_pending() noexcept {
231 for (
auto& slot : pending_)
237 static std::uint64_t draw_value(std::uint64_t seed, std::uint8_t channel, std::uint8_t note,
238 std::int64_t absolute, std::uint64_t stream,
239 std::uint64_t span)
noexcept {
240 const timebase::RandomCoordinate coordinate{
241 timebase::TickPosition{absolute},
247 void schedule_attack(
const MidiEvent& event, std::int64_t absolute,
248 timebase::SamplePosition block_start, MidiBuffer& output,
249 MidiUtilityProcessReport& report)
noexcept {
253 if (
auto* existing = find_pending(key); existing !=
nullptr)
254 emit_pending(*existing, absolute, block_start, output, report);
255 auto* slot = free_pending();
256 if (slot ==
nullptr) {
264 const auto velocity =
265 jittered_velocity(spec_, event.channel(), event.note(), absolute, event.velocity());
267 shaped.timestamp =
event.timestamp;
268 *slot = {shaped,
jittered_position(spec_, event.channel(), event.note(), absolute), key,
272 void clamp_pending_against_release(
int key, std::int64_t release_absolute,
273 timebase::SamplePosition block_start, MidiBuffer& output,
274 MidiUtilityProcessReport& report)
noexcept {
275 auto* slot = find_pending(key);
276 if (slot ==
nullptr || slot->scheduled < release_absolute)
278 const auto latest = release_absolute > 0 ? release_absolute - 1 : release_absolute;
279 emit_pending(*slot, std::min(slot->scheduled, latest), block_start, output, report);
282 void emit_due_before(std::int64_t boundary, timebase::SamplePosition block_start,
283 MidiBuffer& output, MidiUtilityProcessReport& report)
noexcept {
284 for (
auto& slot : pending_)
285 if (slot.active && slot.scheduled < boundary)
286 emit_pending(slot, slot.scheduled, block_start, output, report);
289 bool emit_pending(Pending& slot, std::int64_t at_absolute,
290 timebase::SamplePosition block_start, MidiBuffer& output,
291 MidiUtilityProcessReport& report)
noexcept {
294 const auto offset = at_absolute - block_start.value;
295 const auto clamped =
static_cast<std::int32_t
>(
296 std::clamp<std::int64_t>(offset, 0, std::numeric_limits<std::int32_t>::max()));
303 HumanizeSpec spec_{};
305 std::optional<HumanizeSpec> pending_spec_{};
306 std::array<Pending, MaximumPendingAttacks> pending_{};
MidiUtilityProcessReport flush(MidiBuffer &output, timebase::SamplePosition block_start) noexcept
Emit every attack still waiting in the jitter window.
Definition humanize.hpp:148
static std::int64_t jittered_position(HumanizeSpec spec, std::uint8_t channel, std::uint8_t note, std::int64_t absolute) noexcept
The absolute sample position at which absolute is emitted, derived only from the spec and the event's...
Definition humanize.hpp:78
static constexpr MidiUtilityContract contract() noexcept
Definition humanize.hpp:46
MidiUtilityProcessReport process(const MidiBuffer &input, MidiBuffer &output, timebase::SamplePosition block_start, std::int32_t block_samples) noexcept
Definition humanize.hpp:104
static constexpr bool is_identity(HumanizeSpec spec) noexcept
True when the spec cannot change any event, which is the kernel's declared bypass identity.
Definition humanize.hpp:59
bool update_spec_for_future_attacks(HumanizeSpec spec) noexcept
Change the spec used to schedule future attacks without disturbing attacks already materialized in th...
Definition humanize.hpp:199
constexpr bool valid() const noexcept
Definition humanize.hpp:66
static constexpr bool valid_spec(HumanizeSpec spec) noexcept
Definition humanize.hpp:52
constexpr HumanizeSpec spec() const noexcept
Definition humanize.hpp:70
static constexpr std::uint64_t kTimingStream
Random streams are separated so changing one amount cannot shift the other's draw for the same note.
Definition humanize.hpp:43
bool empty() const noexcept
Definition humanize.hpp:208
static constexpr std::uint64_t kVelocityStream
Definition humanize.hpp:44
MidiUtilityProcessReport replace_spec(HumanizeSpec spec, MidiBuffer &output, timebase::SamplePosition block_start) noexcept
Definition humanize.hpp:178
constexpr Humanize(HumanizeSpec spec={}) noexcept
Definition humanize.hpp:63
MidiUtilityProcessReport reset(MidiBuffer &output, timebase::SamplePosition block_start) noexcept
Definition humanize.hpp:173
static std::uint8_t jittered_velocity(HumanizeSpec spec, std::uint8_t channel, std::uint8_t note, std::int64_t absolute, std::uint8_t velocity) noexcept
The velocity velocity becomes, derived only from the spec and the event's own coordinate.
Definition humanize.hpp:92
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
MidiEvent at(MidiEvent event, std::int32_t sample_offset) noexcept
Definition utility_contract.hpp:140
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
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
int key_index(std::uint8_t channel, std::uint8_t note) noexcept
Definition utility_contract.hpp:55
Definition custom_node_events.hpp:26
@ FlushOnDiscontinuity
Definition utility_contract.hpp:33
@ FailOpenBalanced
Definition utility_contract.hpp:21
@ InputStable
Definition utility_contract.hpp:26
constexpr std::uint64_t multiply_high(std::uint64_t lhs, std::uint64_t rhs) noexcept
Definition coordinate_random.hpp:31
constexpr std::uint64_t coordinate_random(std::uint64_t seed, RandomCoordinate coordinate) noexcept
Definition coordinate_random.hpp:47
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
Seeded timing and velocity jitter over note attacks.
Definition humanize.hpp:26
std::uint64_t seed
Definition humanize.hpp:29
std::uint8_t velocity_amount
Definition humanize.hpp:28
std::int64_t timing_samples
Definition humanize.hpp:27
std::int64_t minimum_timing_samples
Definition humanize.hpp:30
constexpr auto operator<=>(const HumanizeSpec &) const =default
Timestamped MIDI event combining a choc::midi::ShortMessage with timing info.
Definition message.hpp:32
static MidiEvent note_on(uint8_t channel, uint8_t note, uint8_t velocity)
Create a Note On event.
Definition message.hpp:46
Definition utility_contract.hpp:36
Definition utility_contract.hpp:46
bool complete
Definition utility_contract.hpp:50
std::size_t dropped
Definition utility_contract.hpp:48
std::size_t deferred
Definition utility_contract.hpp:49
Definition rational_time.hpp:9