Quantifier.hpp
1 #pragma once
2 #include <Engine/Node/SimpleApi.hpp>
3 
4 #include <random>
5 
6 namespace Nodes
7 {
8 namespace Quantifier
9 {
10 using Note = Control::Note;
11 struct Node
12 {
14  {
15  static const constexpr auto prettyName = "Midi quantify";
16  static const constexpr auto objectKey = "Quantifier";
17  static const constexpr auto category = "Midi";
18  static const constexpr auto author = "ossia score";
19  static const constexpr auto kind = Process::ProcessCategory::MidiEffect;
20  static const constexpr auto description = "Quantifies a MIDI input";
21  static const constexpr auto tags = std::array<const char*, 0>{};
22  static const uuid_constexpr auto uuid
23  = make_uuid("b8e2e5ad-17e4-43de-8d79-660a29d5c4f4");
24 
25  static const constexpr midi_in midi_ins[]{"in"};
26  static const constexpr midi_out midi_outs[]{"out"};
27  static const constexpr auto controls = tuplet::make_tuple(
28  Control::Widgets::QuantificationChooser(),
29  Control::FloatSlider{"Tightness", 0.f, 1.f, 0.8f},
30  Control::Widgets::DurationChooser(), Control::Widgets::TempoChooser());
31  };
32 
33  struct NoteIn
34  {
35  Note note{};
36  ossia::time_value date{};
37  };
38  struct State
39  {
40  std::vector<NoteIn> to_start;
41  std::vector<NoteIn> running_notes;
42  };
43 
44  using control_policy = ossia::safe_nodes::default_tick_controls;
45 
46  static void
47  run(const ossia::midi_port& p1, const ossia::timed_vec<float>& startq,
48  const ossia::timed_vec<float>& tightness, const ossia::timed_vec<float>& dur,
49  const ossia::timed_vec<float>& tempo_vec, ossia::midi_port& p2,
50  ossia::token_request tk, ossia::exec_state_facade st, State& self)
51  {
52  auto start = startq.rbegin()->second;
53  double precision = tightness.rbegin()->second;
54  auto duration = dur.rbegin()->second;
55  auto tempo = tempo_vec.rbegin()->second;
56 
57  // how much time does a whole note last at this tempo given the current sr
58  const auto whole_dur = 240.f / tempo; // in seconds
59  const auto whole_samples = whole_dur * st.sampleRate();
60 
61  for(const libremidi::message& in : p1.messages)
62  {
63  if(!in.is_note_on_or_off())
64  {
65  p2.messages.push_back(in);
66  continue;
67  }
68 
69  Note note{in[1], in[2], (uint8_t)in.get_channel()};
70 
71  if(in.get_message_type() == libremidi::message_type::NOTE_ON && note.vel != 0)
72  {
73  if(start == 0.f) // No quantification, start directly
74  {
75  auto no = libremidi::channel_events::note_on(note.chan, note.pitch, note.vel);
76  no.timestamp = in.timestamp;
77 
78  p2.messages.push_back(no);
79  if(duration > 0.f)
80  {
81  auto end
82  = tk.date + (int64_t)no.timestamp + (int64_t)(whole_samples * duration);
83  self.running_notes.push_back({note, end});
84  }
85  else if(duration == 0.f)
86  {
87  // Stop at the next sample
88  auto noff
89  = libremidi::channel_events::note_off(note.chan, note.pitch, note.vel);
90  noff.timestamp = no.timestamp;
91  p2.messages.push_back(noff);
92  }
93  // else do nothing and just wait for a note off
94  }
95  else
96  {
97  // Find next time that matches the requested quantification
98  const auto start_q = whole_samples * start;
99  auto perf_date = int64_t(
100  start_q * int64_t(1 + tk.date.impl * st.modelToSamples() / start_q));
101  int64_t actual_date = (1. - precision) * tk.date.impl * st.modelToSamples()
102  + precision * perf_date;
103  ossia::time_value next_date{actual_date};
104  self.to_start.push_back({note, next_date});
105  }
106  }
107  else
108  {
109  // Just stop
110  auto noff = libremidi::channel_events::note_off(note.chan, note.pitch, note.vel);
111  noff.timestamp = in.timestamp;
112  p2.messages.push_back(noff);
113  }
114  }
115 
116  // TODO : also handle the case where we're quite close from the *previous*
117  // accessible value, eg we played a bit late
118  for(auto it = self.to_start.begin(); it != self.to_start.end();)
119  {
120  auto& note = *it;
121  if(note.date > tk.prev_date && note.date.impl < tk.date.impl)
122  {
123  auto no = libremidi::channel_events::note_on(
124  note.note.chan, note.note.pitch, note.note.vel);
125  no.timestamp = tk.to_physical_time_in_tick(note.date, st.modelToSamples());
126  p2.messages.push_back(no);
127 
128  if(duration > 0.f)
129  {
130  auto end = note.date + (int64_t)(whole_samples * duration);
131  self.running_notes.push_back({note.note, end});
132  }
133  else if(duration == 0.f)
134  {
135  // Stop at the next sample
136  auto noff = libremidi::channel_events::note_off(
137  note.note.chan, note.note.pitch, note.note.vel);
138  noff.timestamp = no.timestamp;
139  p2.messages.push_back(noff);
140  }
141 
142  it = self.to_start.erase(it);
143  }
144  else
145  {
146  ++it;
147  }
148  }
149 
150  for(auto it = self.running_notes.begin(); it != self.running_notes.end();)
151  {
152  auto& note = *it;
153  if(note.date > tk.prev_date && note.date.impl < tk.date.impl)
154  {
155  auto noff = libremidi::channel_events::note_off(
156  note.note.chan, note.note.pitch, note.note.vel);
157  noff.timestamp = tk.to_physical_time_in_tick(note.date, st.modelToSamples());
158  p2.messages.push_back(noff);
159  it = self.running_notes.erase(it);
160  }
161  else
162  {
163  ++it;
164  }
165  }
166  }
167 };
168 }
169 }
Utilities for OSSIA data structures.
Definition: DeviceInterface.hpp:33
Definition: score-lib-process/Control/Widgets.hpp:77
Definition: SimpleApi.hpp:32
Definition: SimpleApi.hpp:81
Definition: Quantifier.hpp:14
Definition: Quantifier.hpp:34
Definition: Quantifier.hpp:12