43 int base_pitch{}, base_velocity{};
44 std::optional<DecodedNote> operator()()
const noexcept {
return std::nullopt; }
45 std::optional<DecodedNote> operator()(ossia::impulse)
const noexcept
47 return decode_pair(base_pitch, base_velocity,
false);
49 std::optional<DecodedNote> operator()(
int pitch)
const noexcept
51 return decode_pair(pitch, base_velocity,
false);
53 std::optional<DecodedNote> operator()(
char pitch)
const noexcept
55 return decode_pair(pitch, base_velocity,
false);
57 std::optional<DecodedNote> operator()(
float pitch)
const noexcept
59 return decode_pair(pitch, base_velocity,
false);
64 std::optional<DecodedNote> operator()(
const T&)
const noexcept
66 return operator()(ossia::impulse{});
68 std::optional<DecodedNote>
69 operator()(
const std::vector<ossia::value>& list)
const noexcept
72 return operator()(ossia::impulse{});
75 return operator()(ossia::impulse{});
77 return decode_pair(*pitch, base_velocity,
false);
82 return decode_pair(*pitch, *velocity, list[1].get_type() == ossia::val_type::FLOAT);
84 template <std::
size_t N>
85 std::optional<DecodedNote>
86 operator()(
const std::array<float, N>& vector)
const noexcept
89 return decode_pair(vector[0], vector[1],
true);
90 else if constexpr(N == 1)
91 return decode_pair(vector[0], base_velocity,
false);
93 return operator()(ossia::impulse{});
146 halp_meta(name,
"Pulse to Midi")
147 halp_meta(c_name,
"VelToNote")
148 halp_meta(category,
"Midi/Control")
149 halp_meta(author,
"ossia score")
152 "https://ossia.io/score-docs/processes/midi-utilities.html#pulse-to-note")
153 halp_meta(uuid,
"2c6493c3-5449-4e52-ae04-9aee3be5fb6a")
154 halp_meta(process_exec,
true)
160 "Impulse: default pitch and velocity. Scalar number: pitch. "
161 "[pitch, integer velocity]: MIDI velocity; float velocity: normalized 0..1. "
162 "Zero velocity releases owned notes for that source pitch. "
163 "Starts may be quantized; ends use a grid or a duration. "
164 "In quantized mode, min duration extends to the next grid point and, "
165 "when limit duration is on, max duration caps the hold exactly. "
166 "Tightness blends from immediate (0) to the next grid point (1).")
171 ossia_port<
"in", ossia::value_port,
true> port{};
173 halp::hslider_f32<
"Tightness", halp::range{0.f, 1.f, 0.8f}> tightness;
175 midi_spinbox<
"Default pitch"> basenote;
176 midi_spinbox<
"Default vel."> basevel;
177 octave_slider<
"Pitch shift", -5, 5> shift_note;
178 octave_slider<
"Pitch random", 0, 2> note_random;
179 octave_slider<
"Vel. random", 0, 2> vel_random;
180 midi_channel<
"Channel"> channel;
181 halp::toggle<
"Fixed duration"> fixed_duration;
182 halp::time_chooser<
"Duration", halp::range{0., 10., 0.}> duration;
183 halp::enum_t<detail::PitchDirection,
"Pitch direction"> pitch_direction;
185 halp::time_chooser<
"Min duration", halp::range{0., 10., 0.}> min_duration;
186 halp::time_chooser<
"Max duration", halp::range{0., 10., 1.}> max_duration;
188 halp::toggle<
"Limit duration", halp::toggle_setup{.init =
true}> limit_duration;
200 halp_meta(name,
"Pulse to Midi")
201 halp_meta(layout, halp::layouts::tabs)
202 halp_meta(background, halp::colors::background_mid)
206 halp_meta(name,
"Timing")
207 halp_meta(layout, halp::layouts::hbox)
211 halp_meta(name,
"Grid")
212 halp_meta(layout, halp::layouts::vbox)
213 halp::item<&ins::start_quant> start_quant;
214 halp::item<&ins::end_quant> end_quant;
217 halp::spacing sp{.width = 12, .height = 1};
221 halp_meta(name,
"Feel")
222 halp_meta(layout, halp::layouts::vbox)
223 halp::item<&ins::tightness> tightness;
229 halp_meta(name,
"Note")
230 halp_meta(layout, halp::layouts::hbox)
234 halp_meta(name,
"Defaults")
235 halp_meta(layout, halp::layouts::vbox)
236 halp::item<&ins::basenote> basenote;
237 halp::item<&ins::basevel> basevel;
238 halp::item<&ins::channel> channel;
241 halp::spacing sp{.width = 12, .height = 1};
245 halp_meta(name,
"Variation")
246 halp_meta(layout, halp::layouts::vbox)
247 halp::item<&ins::shift_note> shift_note;
248 halp::item<&ins::note_random> note_random;
249 halp::item<&ins::vel_random> vel_random;
250 halp::item<&ins::pitch_direction> pitch_direction;
256 halp_meta(name,
"Duration")
257 halp_meta(layout, halp::layouts::hbox)
261 halp_meta(name,
"Mode")
262 halp_meta(layout, halp::layouts::vbox)
263 halp::item<&ins::fixed_duration> fixed_duration;
264 halp::item<&ins::duration> duration;
267 halp::spacing sp{.width = 12, .height = 1};
271 halp_meta(name,
"Bounds")
272 halp_meta(layout, halp::layouts::vbox)
273 halp::item<&ins::min_duration> min_duration;
274 halp::item<&ins::limit_duration> limit_duration;
275 halp::item<&ins::max_duration> max_duration;
283 using tick = ossia::token_request;
284 ossia::exec_state_facade ossia_state{};
289 std::uint64_t instance{};
292 void prepare(
Setup setup)
298 if(rate_ != setup.rate)
299 engine_.request_reset();
303 engine_.seed(setup.instance ^ 0xa35371f99772b987ULL);
309 void start() noexcept
312 engine_.request_reset();
317 engine_.request_reset();
319 void pause() noexcept { stop(); }
320 void resume() noexcept { start(); }
321 template <
typename Time>
322 void transport(Time)
noexcept
324 engine_.request_reset();
327 [[nodiscard]]
const detail::Statistics& statistics() const noexcept
329 return engine_.statistics();
331 [[nodiscard]]
bool needs_service() const noexcept {
return engine_.needs_service(); }
336 void cleanup_tick(
const tick& tk)
noexcept
338 outputs.midi.midi_messages.clear();
339 engine_.begin_input();
340 engine_.request_reset();
341 if(
const auto span = execution_span(tk); span && span->length > 0)
343 auto sink = [
this](
const detail::MidiEvent& e)
noexcept { emit(e); };
344 engine_.drain_releases(0,
static_cast<int>(span->length), sink);
348 void operator()(
const tick& tk)
noexcept
359 outputs.midi.midi_messages.clear();
360 engine_.begin_input();
361 const auto span = execution_span(tk);
364 engine_.request_reset();
368 const auto total = ossia_state.samplesSinceStart();
369 const auto size = ossia_state.bufferSize();
372 if(total < std::numeric_limits<std::int64_t>::min() + size
373 || total - size > std::numeric_limits<std::int64_t>::max() - span->start_sample)
375 engine_.request_reset();
378 const detail::Block b{
379 .frames =
static_cast<int>(span->length),
380 .first_frame = total - size + span->start_sample,
381 .quarters_begin = tk.musical_start_position,
382 .quarters_end = tk.musical_end_position,
383 .numerator = tk.signature.upper,
384 .denominator = tk.signature.lower,
385 .bar_begin = tk.musical_start_last_bar,
386 .bar_end = tk.musical_end_last_bar,
387 .last_signature = tk.musical_start_last_signature,
388 .model_begin = tk.prev_date.impl,
389 .model_end = tk.date.impl,
390 .discontinuity = tk.start_discontinuous,
391 .end_discontinuity = tk.end_discontinuous};
393 const detail::ValueDecoder decoder{inputs.basenote.value, inputs.basevel.value};
394 if(inputs.port.value && b.frames > 0)
396 const auto& data = inputs.port.value->get_data();
399 if(data.size() > detail::Engine<>::max_inputs)
400 engine_.input_overflow();
402 for(
const auto& input : data)
404 if(input.timestamp < span->start_sample
405 || input.timestamp - span->start_sample >= span->length)
407 if(
const auto note = input.value.apply(decoder))
409 static_cast<int>(input.timestamp - span->start_sample), *note))
413 detail::Settings settings{
414 .start_quant = inputs.start_quant.value,
415 .tightness = inputs.tightness.value,
416 .end_quant = inputs.end_quant.value,
417 .end_mode = inputs.fixed_duration.value ? detail::EndMode::duration
418 : detail::EndMode::quantized,
419 .channel = inputs.channel.value,
420 .pitch_shift = inputs.shift_note.value,
421 .pitch_random = inputs.note_random.value,
422 .velocity_random = inputs.vel_random.value,
423 .pitch_direction = inputs.pitch_direction.value};
424 settings.min_duration = detail::bound_from_time_chooser(
425 inputs.min_duration.value, inputs.min_duration.sync, tk.tempo);
426 settings.max_duration
427 = inputs.limit_duration.value
428 ? detail::bound_from_time_chooser(
429 inputs.max_duration.value, inputs.max_duration.sync, tk.tempo)
431 settings = detail::settings_from_time_chooser(
432 settings, inputs.duration.value, inputs.duration.sync, tk.tempo);
433 auto sink = [
this](
const detail::MidiEvent& e)
noexcept { emit(e); };
434 engine_.process(b, settings, sink);
439 detail::Engine<> engine_{};
444 std::optional<ossia::exec_state_facade::sample_timings>
445 execution_span(
const tick& tk)
const noexcept
447 if(!ossia_state.impl || ossia_state.bufferSize() < 0)
449 const auto size = ossia_state.bufferSize();
452 if(tk.start_sample >= 0 && tk.length_sample >= 0
453 && (tk.start_sample > size || tk.length_sample > size - tk.start_sample))
455 const auto span = ossia_state.timings(tk);
456 if(span.start_sample < 0 || span.length < 0 || span.start_sample > size
457 || span.length > size - span.start_sample)
462 void emit(
const detail::MidiEvent& e)
noexcept
464 auto& msg = outputs.midi.midi_messages.emplace_back();
466 {
static_cast<unsigned char>((e.on ? 0x90 : 0x80) | e.channel), e.pitch,
469 msg.timestamp = e.frame;