Loading...
Searching...
No Matches
Chord.hpp
1#pragma once
2#include <ossia/detail/string_map.hpp>
3
4#include <halp/audio.hpp>
5#include <halp/controls.hpp>
6#include <halp/meta.hpp>
7#include <halp/midi.hpp>
8#include <libremidi/message.hpp>
9
10namespace Nodes
11{
12namespace Chord
13{
14struct Node
15{
16 halp_meta(name, "Chord")
17 halp_meta(c_name, "Chord")
18 halp_meta(category, "Midi")
19 halp_meta(author, "ossia score")
20 halp_meta(manual_url, "https://ossia.io/score-docs/processes/midi-utilities.html#chord")
21 halp_meta(description, "Generate a chord from a single note")
22 halp_meta(uuid, "F0904279-EA26-48DB-B0DF-F68FE3091DA1");
23
24 enum Chord
25 {
26 Maj,
27 Min,
28 Sus2,
29 Sus4,
30 Dim,
31 Aug
32 };
33 using midi_out = halp::midi_out_bus<"out", libremidi::message>;
34 struct
35 {
36 halp::midi_bus<"in", libremidi::message> midi;
37 halp::hslider_i32<"Num. Notes", halp::irange{1, 5, 3}> num;
38 halp::enum_t<Chord, "Chord"> chord;
39 } inputs;
40 struct
41 {
42 midi_out midi;
43 } outputs;
45 {
46 std::string_view ch{};
47 int notes{};
48 };
49 ossia::flat_map<uint8_t, std::vector<chord_type>> chords;
50
51 // C C# D D# E F F# G G# A A# B
52 // 1 . . . 1 . . 1 . 1 . .
53 static const constexpr std::array<int, 5> major7{0, 4, 7, 11, 12};
54 static const constexpr std::array<int, 5> minor7{0, 3, 7, 10, 12};
55 static const constexpr std::array<int, 5> sus2{0, 2, 7, 9, 12};
56 static const constexpr std::array<int, 5> sus4{0, 5, 7, 9, 12};
57 static const constexpr std::array<int, 5> dim{0, 3, 6, 9, 12};
58 static const constexpr std::array<int, 5> aug{0, 4, 8, 10, 12};
59
60 template <typename T>
61 static void startChord(const T& chord, const libremidi::message& m, const std::size_t num, midi_out& op)
62 {
63 for(std::size_t i = 0; i < std::min(num, chord.size()); i++)
64 {
65 auto new_note = m.bytes[1] + chord[i];
66 if(new_note > 127)
67 break;
68
69 op.note_on(m.get_channel(), new_note, m.bytes[2]).timestamp = m.timestamp;
70 }
71 }
72
73 template <typename T>
74 static void stopChord(const T& chord, const libremidi::message& m, const std::size_t num, midi_out& op)
75 {
76 for(std::size_t i = 0; i < std::min(num, chord.size()); i++)
77 {
78 auto new_note = m.bytes[1] + chord[i];
79 if(new_note > 127)
80 break;
81
82 op.note_off(m.get_channel(), new_note, m.bytes[2]).timestamp = m.timestamp;
83 }
84 }
85
86 template <typename F>
87 static void dispatchChord(std::string_view chord, const libremidi::message& m, int num, midi_out& op, F&& f)
88 {
89 static const ossia::string_view_map<std::array<int, 5>> chords{{"Maj", major7}, {"Min", minor7}, {"Sus2", sus2}, {"Sus4", sus4}, {"Dim", dim}, {"Aug", aug}};
90 auto it = chords.find(chord);
91 if(it != chords.end())
92 f(it->second, m, num, op);
93 }
94
95 // FIXME here we want precise ticking, e.g. call for each change in any parameter
96 using tick = halp::tick_musical;
97 void operator()(const halp::tick_musical& tk)
98 {
99 for(const libremidi::message& m : inputs.midi)
100 {
101 int lastNum = inputs.num;
102 std::string_view lastCh = magic_enum::enum_name<Chord>(inputs.chord);
103 if(m.get_message_type() == libremidi::message_type::NOTE_ON)
104 {
105 auto cur = m.bytes[1];
106 this->chords[cur].push_back({lastCh, lastNum});
107 dispatchChord(lastCh, m, lastNum, outputs.midi, [](auto&&... args) { startChord(args...); });
108 }
109 else if(m.get_message_type() == libremidi::message_type::NOTE_OFF)
110 {
111 auto it = this->chords.find(m.bytes[1]);
112 if(it != this->chords.end())
113 {
114 for(const auto& chord : it->second)
115 {
116 dispatchChord(chord.ch, m, chord.notes, outputs.midi, [](auto&&... args) { stopChord(args...); });
117 }
118 const_cast<std::vector<chord_type>&>(it->second).clear();
119 }
120 }
121 else
122 {
123 // just forward
124 outputs.midi.push_back(m);
125 }
126 }
127 }
128};
129}
130}
Definition Chord.hpp:45
Definition Chord.hpp:15