42 int base_pitch{}, base_velocity{};
43 std::optional<DecodedNote> operator()()
const noexcept {
return std::nullopt; }
44 std::optional<DecodedNote> operator()(ossia::impulse)
const noexcept
46 return decode_pair(base_pitch, base_velocity,
false);
48 std::optional<DecodedNote> operator()(
int pitch)
const noexcept
50 return decode_pair(pitch, base_velocity,
false);
52 std::optional<DecodedNote> operator()(
char pitch)
const noexcept
54 return decode_pair(pitch, base_velocity,
false);
56 std::optional<DecodedNote> operator()(
float pitch)
const noexcept
58 return decode_pair(pitch, base_velocity,
false);
61 std::optional<DecodedNote> operator()(
const T&)
const noexcept
65 std::optional<DecodedNote>
66 operator()(
const std::vector<ossia::value>& list)
const noexcept
69 return operator()(ossia::impulse{});
74 return decode_pair(*pitch, base_velocity,
false);
79 return decode_pair(*pitch, *velocity, list[1].get_type() == ossia::val_type::FLOAT);
81 template <std::
size_t N>
82 std::optional<DecodedNote>
83 operator()(
const std::array<float, N>& vector)
const noexcept
86 return decode_pair(vector[0], vector[1],
true);
87 else if constexpr(N == 1)
88 return decode_pair(vector[0], base_velocity,
false);
90 return operator()(ossia::impulse{});
143 halp_meta(name,
"Pulse to Midi")
144 halp_meta(c_name,
"VelToNote")
145 halp_meta(category,
"Midi")
146 halp_meta(author,
"ossia score")
149 "https://ossia.io/score-docs/processes/midi-utilities.html#pulse-to-note")
150 halp_meta(uuid,
"2c6493c3-5449-4e52-ae04-9aee3be5fb6a")
151 halp_meta(process_exec,
true)
154 halp_meta(local_midi_tick_batch,
true)
158 "Impulse: default pitch and velocity. Scalar number: pitch. "
159 "[pitch, integer velocity]: MIDI velocity; float velocity: normalized 0..1. "
160 "Zero velocity releases owned notes for that source pitch. "
161 "Starts may be quantized; ends use a grid or a duration. "
162 "In quantized mode, min duration extends to the next grid point and "
163 "max duration caps the hold exactly. "
164 "Tightness blends from immediate (0) to the next grid point (1).")
169 ossia_port<
"in", ossia::value_port,
true> port{};
171 halp::hslider_f32<
"Tightness", halp::range{0.f, 1.f, 0.8f}> tightness;
173 midi_spinbox<
"Default pitch"> basenote;
174 midi_spinbox<
"Default vel."> basevel;
175 octave_slider<
"Pitch shift", -5, 5> shift_note;
176 octave_slider<
"Pitch random", 0, 2> note_random;
177 octave_slider<
"Vel. random", 0, 2> vel_random;
178 midi_channel<
"Channel"> channel;
179 halp::toggle<
"Fixed duration"> fixed_duration;
180 halp::time_chooser<
"Duration", halp::range{0., 10., 0.}> duration;
181 halp::enum_t<detail::PitchDirection,
"Pitch direction"> pitch_direction;
183 halp::time_chooser<
"Min duration", halp::range{0., 10., 0.}> min_duration;
184 halp::time_chooser<
"Max duration", halp::range{0., 10., 0.}> max_duration;
194 using tick = ossia::token_request;
195 ossia::exec_state_facade ossia_state{};
200 std::uint64_t instance{};
203 void prepare(
Setup setup)
209 if(rate_ != setup.rate)
210 engine_.request_reset();
214 engine_.seed(setup.instance ^ 0xa35371f99772b987ULL);
220 void start() noexcept
223 engine_.request_reset();
228 engine_.request_reset();
230 void pause() noexcept { stop(); }
231 void resume() noexcept { start(); }
232 template <
typename Time>
233 void transport(Time)
noexcept
235 engine_.request_reset();
238 [[nodiscard]]
const detail::Statistics& statistics() const noexcept
240 return engine_.statistics();
242 [[nodiscard]]
bool needs_service() const noexcept {
return engine_.needs_service(); }
247 void cleanup_tick(
const tick& tk)
noexcept
249 outputs.midi.midi_messages.clear();
250 engine_.begin_input();
251 engine_.request_reset();
252 if(
const auto span = execution_span(tk); span && span->length > 0)
254 auto sink = [
this](
const detail::MidiEvent& e)
noexcept { emit(e); };
255 engine_.drain_releases(0,
static_cast<int>(span->length), sink);
259 void operator()(
const tick& tk)
noexcept
270 outputs.midi.midi_messages.clear();
271 engine_.begin_input();
272 const auto span = execution_span(tk);
275 engine_.request_reset();
279 const auto total = ossia_state.samplesSinceStart();
280 const auto size = ossia_state.bufferSize();
283 if(total < std::numeric_limits<std::int64_t>::min() + size
284 || total - size > std::numeric_limits<std::int64_t>::max() - span->start_sample)
286 engine_.request_reset();
289 const detail::Block b{
290 .frames =
static_cast<int>(span->length),
291 .first_frame = total - size + span->start_sample,
292 .quarters_begin = tk.musical_start_position,
293 .quarters_end = tk.musical_end_position,
294 .numerator = tk.signature.upper,
295 .denominator = tk.signature.lower,
296 .bar_begin = tk.musical_start_last_bar,
297 .bar_end = tk.musical_end_last_bar,
298 .last_signature = tk.musical_start_last_signature,
299 .model_begin = tk.prev_date.impl,
300 .model_end = tk.date.impl,
301 .discontinuity = tk.start_discontinuous,
302 .end_discontinuity = tk.end_discontinuous};
304 const detail::ValueDecoder decoder{inputs.basenote.value, inputs.basevel.value};
305 if(inputs.port.value && b.frames > 0)
307 const auto& data = inputs.port.value->get_data();
310 if(data.size() > detail::Engine<>::max_inputs)
311 engine_.input_overflow();
313 for(
const auto& input : data)
315 if(input.timestamp < span->start_sample
316 || input.timestamp - span->start_sample >= span->length)
318 if(
const auto note = input.value.apply(decoder))
320 static_cast<int>(input.timestamp - span->start_sample), *note))
324 detail::Settings settings{
325 .start_quant = inputs.start_quant.value,
326 .tightness = inputs.tightness.value,
327 .end_quant = inputs.end_quant.value,
328 .end_mode = inputs.fixed_duration.value ? detail::EndMode::duration
329 : detail::EndMode::quantized,
330 .channel = inputs.channel.value,
331 .pitch_shift = inputs.shift_note.value,
332 .pitch_random = inputs.note_random.value,
333 .velocity_random = inputs.vel_random.value,
334 .pitch_direction = inputs.pitch_direction.value};
335 settings.min_duration = detail::bound_from_time_chooser(
336 inputs.min_duration.value, inputs.min_duration.sync, tk.tempo);
337 settings.max_duration = detail::bound_from_time_chooser(
338 inputs.max_duration.value, inputs.max_duration.sync, tk.tempo);
339 settings = detail::settings_from_time_chooser(
340 settings, inputs.duration.value, inputs.duration.sync, tk.tempo);
341 auto sink = [
this](
const detail::MidiEvent& e)
noexcept { emit(e); };
342 engine_.process(b, settings, sink);
347 detail::Engine<> engine_{};
352 std::optional<ossia::exec_state_facade::sample_timings>
353 execution_span(
const tick& tk)
const noexcept
355 if(!ossia_state.impl || ossia_state.bufferSize() < 0)
357 const auto size = ossia_state.bufferSize();
360 if(tk.start_sample >= 0 && tk.length_sample >= 0
361 && (tk.start_sample > size || tk.length_sample > size - tk.start_sample))
363 const auto span = ossia_state.timings(tk);
364 if(span.start_sample < 0 || span.length < 0 || span.start_sample > size
365 || span.length > size - span.start_sample)
370 void emit(
const detail::MidiEvent& e)
noexcept
372 auto& msg = outputs.midi.midi_messages.emplace_back();
374 {
static_cast<unsigned char>((e.on ? 0x90 : 0x80) | e.channel), e.pitch,
377 msg.timestamp = e.frame;