2 #include <Fx/Types.hpp>
4 #include <halp/controls.hpp>
5 #include <halp/meta.hpp>
6 #include <halp/midi.hpp>
8 namespace Nodes::MidiUtil
10 enum scale_type : int8_t
35 struct magic_enum::customize::enum_range<Nodes::MidiUtil::scale_type>
37 static constexpr
int min = 0;
38 static constexpr
int max = Nodes::MidiUtil::custom;
41 namespace Nodes::MidiUtil
45 static constexpr
void constexpr_swap(T& a, T& b)
52 template <
typename Iterator>
59 constexpr_swap(*first++, *next++);
62 else if(first == middle)
67 using scale_array = std::array<bool, 128>;
68 using scales_array = std::array<scale_array, 12>;
69 static constexpr scales_array make_scale(std::initializer_list<bool> notes)
71 std::array<scale_array, 12> r{};
72 for(std::size_t octave = 0; octave < 11; octave++)
75 for(
bool note : notes)
77 if(octave * 12 + pos < 128)
79 r[0][octave * 12 + pos] = note;
85 for(std::size_t octave = 1; octave < 12; octave++)
88 constexpr_rotate(r[octave].rbegin(), r[octave].rbegin() + octave, r[octave].rend());
93 static constexpr
bool is_same(std::string_view lhs, std::string_view rhs)
95 if(lhs.size() == rhs.size())
97 for(std::size_t i = 0; i < lhs.size(); i++)
107 static constexpr
int get_scale(std::string_view s)
109 using namespace std::literals;
110 if(is_same(s, std::string_view(
"all")))
111 return scale_type::all;
112 else if(is_same(s, std::string_view(
"ionian")))
113 return scale_type::ionian;
114 else if(is_same(s, std::string_view(
"dorian")))
115 return scale_type::dorian;
116 else if(is_same(s, std::string_view(
"phyrgian")))
117 return scale_type::phyrgian;
118 else if(is_same(s, std::string_view(
"lydian")))
119 return scale_type::lydian;
120 else if(is_same(s, std::string_view(
"mixolydian")))
121 return scale_type::mixolydian;
122 else if(is_same(s, std::string_view(
"aeolian")))
123 return scale_type::aeolian;
124 else if(is_same(s, std::string_view(
"locrian")))
125 return scale_type::locrian;
126 else if(is_same(s, std::string_view(
"I")))
127 return scale_type::I;
128 else if(is_same(s, std::string_view(
"II")))
129 return scale_type::II;
130 else if(is_same(s, std::string_view(
"III")))
131 return scale_type::III;
132 else if(is_same(s, std::string_view(
"IV")))
133 return scale_type::IV;
134 else if(is_same(s, std::string_view(
"V")))
135 return scale_type::V;
136 else if(is_same(s, std::string_view(
"VI")))
137 return scale_type::VI;
138 else if(is_same(s, std::string_view(
"VII")))
139 return scale_type::VII;
141 return scale_type::custom;
145 static constexpr std::array<scales_array, scale_type::SCALES_MAX - 1> scales{
147 make_scale({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) ,
148 make_scale({1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1}) ,
149 make_scale({1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0}) ,
150 make_scale({1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0}) ,
151 make_scale({1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1}) ,
152 make_scale({1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0}) ,
153 make_scale({1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0}) ,
154 make_scale({1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0}) ,
155 make_scale({1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0}) ,
156 make_scale({0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0}) ,
157 make_scale({0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1}) ,
158 make_scale({1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0}) ,
159 make_scale({0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1}) ,
160 make_scale({1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0}) ,
161 make_scale({0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1}) };
163 static std::optional<std::size_t> find_closest_index(
const scale_array& arr, std::size_t i)
190 while((i - r) != 0 && (i + r) != 12)
194 else if(arr[i - r] == 1)
208 halp_meta(name,
"Midi scale")
209 halp_meta(c_name,
"MidiScale")
210 halp_meta(category,
"Midi")
211 halp_meta(author,
"ossia score")
212 halp_meta(manual_url,
"https://ossia.io/score-docs/processes/midi-utilities.html#midi-scale")
213 halp_meta(description,
"Maps a midi input to a given scale")
214 halp_meta(uuid,
"06b33b83-bb67-4f7a-9980-f5d66e4266c5")
218 halp::midi_bus<
"in", libremidi::message> midi;
219 halp::string_enum_t<scale_type,
"Scale"> sc;
220 octave_slider<
"Base", 0, 1> base;
221 octave_slider<
"Transpose", -4, 4> transp;
234 ossia::flat_map<uint8_t, Note> map;
239 void exec(
const scale_array& scale,
int transp)
241 auto& midi_in = inputs.midi;
242 auto& midi_out = outputs.midi;
243 for(
const auto& msg : midi_in)
245 switch(msg.get_message_type())
247 case libremidi::message_type::NOTE_ON: {
248 if(msg.bytes[1] >= 128)
252 if(
auto index = find_closest_index(scale, msg.bytes[1]))
256 res.bytes[1] = (uint8_t)ossia::clamp(
int(*index + transp), 0, 127);
258 (uint8_t)res.bytes[1], (uint8_t)res.bytes[2],
259 (uint8_t)res.get_channel()};
260 auto it = this->map.find(msg.bytes[1]);
261 if(it != this->map.end())
263 midi_out.note_off(res.get_channel(), it->second.pitch, res.bytes[2])
266 midi_out.push_back(res);
267 midi_out.back().timestamp = 1;
268 const_cast<Note&
>(it->second) = note;
272 midi_out.push_back(res);
273 midi_out.back().timestamp = 0;
274 this->map.insert(std::make_pair((uint8_t)msg.bytes[1], note));
279 case libremidi::message_type::NOTE_OFF: {
280 if(msg.bytes[1] >= 128)
283 auto it = this->map.find(msg.bytes[1]);
284 if(it != this->map.end())
286 midi_out.note_off(msg.get_channel(), it->second.pitch, msg.bytes[2])
294 midi_out.push_back(msg);
300 void update(
const scale_array& scale,
int transp)
302 auto& midi_out = outputs.midi;
303 for(
auto& notes : this->map)
305 Note& note =
const_cast<Note&
>(notes.second);
306 if(
auto index = find_closest_index(scale, notes.first))
308 if((*index + transp) != note.pitch)
310 midi_out.note_off(note.chan, note.pitch, note.vel).timestamp = 0;
311 note.pitch = *index + transp;
312 midi_out.note_on(note.chan, note.pitch, note.vel);
313 midi_out.back().timestamp = 1;
319 using tick = halp::tick_flicks;
320 void operator()(
const tick& tk)
322 const auto& new_scale = inputs.sc.value;
323 const int new_base = inputs.base.value;
324 const int new_transpose = inputs.transp.value;
325 std::string_view scale{new_scale.data(), new_scale.size()};
327 const auto new_scale_idx = get_scale(scale);
329 auto apply = [&](
auto f) {
330 if(new_scale_idx >= 0 && new_scale_idx < scale_type::custom)
332 f(scales[new_scale_idx][new_base], new_transpose);
337 for(
int oct = 0; oct < 10; oct++)
339 for(
int i = 0; i < ossia::min(std::ssize(scale), 12); i++)
341 arr[oct * 12 + i] = (scale[i] ==
'1');
344 f(arr, new_transpose);
348 #define forward_to_method(method_name) \
349 [&]<typename... Args>(Args&&... args) { \
350 this->method_name(std::forward<Args>(args)...); \
353 if(!this->map.empty()
354 && (new_scale != this->scale || new_base != this->base
355 || new_transpose != this->transpose))
357 apply(forward_to_method(update));
360 apply(forward_to_method(exec));
361 #undef forward_to_method
363 this->scale = new_scale;
364 this->base = new_base;
365 this->transpose = new_transpose;
Definition: lv2_atom_helpers.hpp:99
Definition: MidiUtil.hpp:229
Definition: MidiUtil.hpp:207