2 #include <Engine/Node/SimpleApi.hpp>
4 #include <ossia/detail/ssize.hpp>
5 namespace Nodes::MidiUtil
32 constexpr
void constexpr_swap(T& a, T& b)
39 template <
typename Iterator>
46 constexpr_swap(*first++, *next++);
49 else if(first == middle)
54 using scale_array = std::array<bool, 128>;
55 using scales_array = std::array<scale_array, 12>;
56 constexpr scales_array make_scale(std::initializer_list<bool> notes)
58 std::array<scale_array, 12> r{};
59 for(std::size_t octave = 0; octave < 11; octave++)
62 for(
bool note : notes)
64 if(octave * 12 + pos < 128)
66 r[0][octave * 12 + pos] = note;
72 for(std::size_t octave = 1; octave < 12; octave++)
75 constexpr_rotate(r[octave].rbegin(), r[octave].rbegin() + octave, r[octave].rend());
80 constexpr
bool is_same(std::string_view lhs, std::string_view rhs)
82 if(lhs.size() == rhs.size())
84 for(std::size_t i = 0; i < lhs.size(); i++)
94 constexpr
int get_scale(std::string_view s)
96 using namespace std::literals;
97 if(is_same(s, std::string_view(
"all")))
99 else if(is_same(s, std::string_view(
"ionian")))
100 return scale::ionian;
101 else if(is_same(s, std::string_view(
"dorian")))
102 return scale::dorian;
103 else if(is_same(s, std::string_view(
"phyrgian")))
104 return scale::phyrgian;
105 else if(is_same(s, std::string_view(
"lydian")))
106 return scale::lydian;
107 else if(is_same(s, std::string_view(
"mixolydian")))
108 return scale::mixolydian;
109 else if(is_same(s, std::string_view(
"aeolian")))
110 return scale::aeolian;
111 else if(is_same(s, std::string_view(
"locrian")))
112 return scale::locrian;
113 else if(is_same(s, std::string_view(
"I")))
115 else if(is_same(s, std::string_view(
"II")))
117 else if(is_same(s, std::string_view(
"III")))
119 else if(is_same(s, std::string_view(
"IV")))
121 else if(is_same(s, std::string_view(
"V")))
123 else if(is_same(s, std::string_view(
"VI")))
125 else if(is_same(s, std::string_view(
"VII")))
128 return scale::custom;
130 #if defined(_MSC_VER)
131 #define MSVC_CONSTEXPR const
133 #define MSVC_CONSTEXPR constexpr
135 static MSVC_CONSTEXPR std::array<scales_array, scale::SCALES_MAX - 1> scales{
137 make_scale({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) ,
138 make_scale({1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1}) ,
139 make_scale({1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0}) ,
140 make_scale({1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0}) ,
141 make_scale({1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1}) ,
142 make_scale({1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0}) ,
143 make_scale({1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0}) ,
144 make_scale({1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0}) ,
145 make_scale({1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0}) ,
146 make_scale({0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0}) ,
147 make_scale({0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1}) ,
148 make_scale({1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0}) ,
149 make_scale({0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1}) ,
150 make_scale({1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0}) ,
152 make_scale({0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1}) };
154 static std::optional<std::size_t>
155 find_closest_index(
const scale_array& arr, std::size_t i)
182 while((i - r) != 0 && (i + r) != 12)
186 else if(arr[i - r] == 1)
202 static const constexpr
auto prettyName =
"Midi scale";
203 static const constexpr
auto objectKey =
"MidiScale";
204 static const constexpr
auto category =
"Midi";
205 static const constexpr
auto author =
"ossia score";
206 static const constexpr
auto tags = std::array<const char*, 0>{};
207 static const constexpr
auto kind = Process::ProcessCategory::MidiEffect;
208 static const constexpr
auto description =
"Maps a midi input to a given scale";
209 static const uuid_constexpr
auto uuid
210 = make_uuid(
"06b33b83-bb67-4f7a-9980-f5d66e4266c5");
212 static const constexpr midi_in midi_ins[]{
"in"};
213 static const constexpr midi_out midi_outs[]{
"out"};
214 static const constexpr
auto controls = tuplet::make_tuple(
215 Control::make_unvalidated_enum(
218 "all",
"ionian",
"dorian",
"phyrgian",
"lydian",
"mixolydian",
"aeolian",
219 "locrian",
"I",
"II",
"III",
"IV",
"V",
"VI",
"VII")),
220 Control::Widgets::OctaveSlider(
"Base", 0, 1),
221 Control::Widgets::OctaveSlider(
"Transpose", -4, 4));
226 ossia::flat_map<uint8_t, Note> map;
233 const ossia::midi_port& midi_in,
const scale_array& scale,
int transp,
234 ossia::midi_port& midi_out,
const int64_t offset,
State&
self)
236 for(
const auto& msg : midi_in.messages)
238 switch(msg.get_message_type())
240 case libremidi::message_type::NOTE_ON: {
241 if(msg.bytes[1] >= 128)
245 if(
auto index = find_closest_index(scale, msg.bytes[1]))
249 res.bytes[1] = (uint8_t)ossia::clamp(
int(*index + transp), 0, 127);
251 (uint8_t)res.bytes[1], (uint8_t)res.bytes[2],
252 (uint8_t)res.get_channel()};
253 auto it =
self.map.find(msg.bytes[1]);
254 if(it !=
self.map.end())
256 midi_out.messages.push_back(libremidi::channel_events::note_off(
257 res.get_channel(), it->second.pitch, res.bytes[2]));
258 midi_out.messages.back().timestamp = offset;
259 midi_out.messages.push_back(res);
260 midi_out.messages.back().timestamp = offset + 1;
261 const_cast<Note&
>(it->second) = note;
265 midi_out.messages.push_back(res);
266 midi_out.messages.back().timestamp = offset;
267 self.map.insert(std::make_pair((uint8_t)msg.bytes[1], note));
272 case libremidi::message_type::NOTE_OFF: {
273 if(msg.bytes[1] >= 128)
276 auto it =
self.map.find(msg.bytes[1]);
277 if(it !=
self.map.end())
279 midi_out.messages.push_back(libremidi::channel_events::note_off(
280 msg.get_channel(), it->second.pitch, msg.bytes[2]));
281 midi_out.messages.back().timestamp = offset;
287 midi_out.messages.push_back(msg);
294 const ossia::midi_port& midi_in,
const scale_array& scale,
int transp,
295 ossia::midi_port& midi_out,
const int64_t offset,
State&
self)
297 for(
auto& notes :
self.map)
299 Note& note =
const_cast<Note&
>(notes.second);
300 if(
auto index = find_closest_index(scale, notes.first))
302 if((*index + transp) != note.pitch)
304 midi_out.messages.push_back(
305 libremidi::channel_events::note_off(note.chan, note.pitch, note.vel));
306 note.pitch = *index + transp;
307 midi_out.messages.back().timestamp = offset;
308 midi_out.messages.push_back(
309 libremidi::channel_events::note_on(note.chan, note.pitch, note.vel));
310 midi_out.messages.back().timestamp = offset + 1;
316 using control_policy = ossia::safe_nodes::default_tick_controls;
318 run(
const ossia::midi_port& midi_in,
const ossia::timed_vec<std::string>& sc,
319 const ossia::timed_vec<int>& base,
const ossia::timed_vec<int>& transp,
320 ossia::midi_port& midi_out, ossia::token_request tk, ossia::exec_state_facade st,
323 const auto& new_scale = sc.rbegin()->second;
324 const int new_base = base.rbegin()->second;
325 const int new_transpose = transp.rbegin()->second;
326 std::string_view scale{new_scale.data(), new_scale.size()};
328 const auto new_scale_idx = get_scale(scale);
330 auto apply = [&](
auto f) {
331 const auto [tick_start, d] = st.timings(tk);
332 if(new_scale_idx >= 0 && new_scale_idx < scale::custom)
334 f(midi_in, scales[new_scale_idx][new_base], new_transpose, midi_out, tick_start,
340 for(
int oct = 0; oct < 10; oct++)
342 for(
int i = 0; i < ossia::min(std::ssize(scale), 12); i++)
344 arr[oct * 12 + i] = (scale[i] ==
'1');
347 f(midi_in, arr, new_transpose, midi_out, tick_start,
self);
352 && (new_scale !=
self.scale || new_base !=
self.base
353 || new_transpose !=
self.transpose))
360 self.scale = new_scale;
361 self.base = new_base;
362 self.transpose = new_transpose;
368 namespace Nodes::PitchToValue
374 static const constexpr
auto prettyName =
"Midi Pitch";
375 static const constexpr
auto objectKey =
"PitchToValue";
376 static const constexpr
auto category =
"Midi";
377 static const constexpr
auto author =
"ossia score";
378 static const constexpr
auto kind = Process::ProcessCategory::MidiEffect;
379 static const constexpr
auto description =
"Extract a MIDI pitch";
380 static const constexpr
auto tags = std::array<const char*, 0>{};
381 static const uuid_constexpr
auto uuid
382 = make_uuid(
"29ce484f-cb56-4501-af79-88768fa261c3");
384 static const constexpr midi_in midi_ins[]{
"in"};
385 static const constexpr value_out value_outs[]{{
"out",
"midipitch"}};
388 using control_policy = ossia::safe_nodes::default_tick;
390 run(
const ossia::midi_port& in, ossia::value_port& res, ossia::token_request tk,
391 ossia::exec_state_facade st)
393 for(
const auto& note : in.messages)
395 if(note.get_message_type() == libremidi::message_type::NOTE_ON)
396 res.write_value(ossia::value{(int)note.bytes[1]}, note.timestamp);
Utilities for OSSIA data structures.
Definition: DeviceInterface.hpp:33
Definition: SimpleApi.hpp:81
Definition: lv2_atom_helpers.hpp:99
Definition: MidiUtil.hpp:199
Definition: MidiUtil.hpp:371