Loading...
Searching...
No Matches
MidiTake.hpp
1#pragma once
2#include <Fx/Looper/MidiLoop.hpp>
3#include <Fx/Looper/TakeWriter.hpp>
4
5#include <libremidi/writer.hpp>
6
7#include <algorithm>
8#include <array>
9#include <atomic>
10#include <cmath>
11#include <filesystem>
12#include <fstream>
13#include <memory>
14#include <sstream>
15#include <vector>
16
17namespace Nodes::Looper
18{
22{
23 std::array<midi_note, midi_loop::max_notes> notes{};
24 std::array<midi_control, midi_loop::max_controls> controls{};
25 int note_count{};
26 int control_count{};
27 int64_t length{};
28 double rate{48000.};
29 double tempo{120.};
30 int meter_numerator{4};
31 int meter_denominator{4};
32 bool wrap{true};
35 std::atomic_bool in_use{};
36
37 static constexpr int ticks_per_quarter = 480;
38
43 std::string to_smf() const
44 {
45 const double bpm = std::max(1., tempo);
46 const double frames_per_quarter = rate * 60. / bpm;
47 auto to_tick = [&](int64_t frames) {
48 return int64_t(std::llround(frames / frames_per_quarter * ticks_per_quarter));
49 };
50 const int64_t end = std::max<int64_t>(1, to_tick(length));
51
52 struct event
53 {
54 int64_t tick;
55 int order;
56 libremidi::message m;
57 };
58 std::vector<event> events;
59 events.reserve(std::size_t(note_count) * 4 + std::size_t(control_count) + 4);
60
61 // A tempo event holds microseconds per quarter on 24 bits.
62 const int us_per_quarter
63 = int(std::clamp<int64_t>(std::llround(60'000'000. / bpm), 1, 0xFFFFFF));
64 events.push_back({0, 1, libremidi::meta_events::tempo(us_per_quarter)});
65 // 24 MIDI clocks per metronome click and 8 thirty-seconds per quarter are
66 // what every sequencer writes.
67 events.push_back(
68 {0, 1, libremidi::meta_events::time_signature(
69 std::clamp(meter_numerator, 1, 255),
70 std::clamp(meter_denominator, 1, 128), 24, 8)});
71
72 for(int i = 0; i < note_count; i++)
73 {
74 const auto& n = notes[i];
75 const int64_t on = std::clamp<int64_t>(to_tick(n.start), 0, end - 1);
76 const int64_t len = std::max<int64_t>(1, to_tick(std::max<int64_t>(1, n.length)));
77 int64_t off = on + std::min(len, end - 1);
78 const int ch = std::clamp<int>(n.channel, 1, 16);
79 events.push_back(
80 {on, 2, libremidi::channel_events::note_on(ch, n.pitch, std::max<int>(1, n.velocity))});
81 if(off <= end)
82 {
83 events.push_back({off, 0, libremidi::channel_events::note_off(ch, n.pitch, 0)});
84 }
85 else if(wrap)
86 {
87 // The note carries on from the loop's start: its tail is a note of
88 // its own there.
89 events.push_back({end, 0, libremidi::channel_events::note_off(ch, n.pitch, 0)});
90 events.push_back(
91 {0, 2, libremidi::channel_events::note_on(ch, n.pitch, std::max<int>(1, n.velocity))});
92 events.push_back(
93 {std::max<int64_t>(1, off - end), 0, libremidi::channel_events::note_off(ch, n.pitch, 0)});
94 }
95 else
96 {
97 events.push_back({end, 0, libremidi::channel_events::note_off(ch, n.pitch, 0)});
98 }
99 }
100
101 for(int i = 0; i < control_count; i++)
102 {
103 const auto& c = controls[i];
104 const int64_t t = std::clamp<int64_t>(to_tick(c.pos), 0, end - 1);
105 if((c.status & 0xF0) == 0xD0)
106 events.push_back({t, 1, libremidi::message{c.status, c.data1}});
107 else
108 events.push_back({t, 1, libremidi::message{c.status, c.data1, c.data2}});
109 }
110
111 // A marker on the loop's end: the end of the track is the end of the
112 // loop even when the last note ends earlier.
113 events.push_back({end, 3, libremidi::message{0xFF, 0x06, 3, 'e', 'n', 'd'}});
114
115 std::stable_sort(events.begin(), events.end(), [](const event& a, const event& b) {
116 return a.tick != b.tick ? a.tick < b.tick : a.order < b.order;
117 });
118
119 libremidi::writer w;
120 w.ticksPerQuarterNote = ticks_per_quarter;
121 int64_t prev = 0;
122 for(const auto& e : events)
123 {
124 w.add_event(int(e.tick - prev), 0, e.m);
125 prev = e.tick;
126 }
127
128 std::ostringstream out;
129 w.write(out);
130 return std::move(out).str();
131 }
132};
133
137template <typename Place>
139 const midi_loop& snap, int item, int64_t pos, OverdubMode mode, Place&& place,
140 midi_export& e)
141{
142 e.note_count = 0;
143 e.control_count = 0;
144 for(int i = 0; i < snap.note_count; i++)
145 {
146 // A muted pass has its file, as it would be heard.
147 auto n = snap.notes[i];
148 if(!snap.heard(n, mode, item < 0) || (item >= 0 && n.pass != item))
149 continue;
150 if(n.length < 0)
151 n.length = snap.distance(n.start, pos);
152 // A pass shorter than the loop is in the file as many times as it plays.
153 const int64_t period = std::clamp<int64_t>(snap.period_of(n.pass), 1, snap.length);
154 const auto placed = place(n, period);
155 for(int64_t on = placed.start; on < snap.length; on += period)
156 {
157 if(e.note_count >= midi_loop::max_notes)
158 break;
159 n.start = on;
160 n.length = placed.length;
161 e.notes[e.note_count++] = n;
162 }
163 }
164 for(int i = 0; i < snap.control_count; i++)
165 {
166 const auto& c = snap.controls[i];
167 if(!snap.heard(c, item < 0) || (item >= 0 && c.pass != item))
168 continue;
169 const int64_t period
170 = std::max<int64_t>(1, std::min<int64_t>(snap.period_of(c.pass), snap.length));
171 for(int64_t q = c.pos % period; q < snap.length; q += period)
172 {
173 if(e.control_count >= midi_loop::max_controls)
174 break;
175 e.controls[e.control_count] = c;
176 e.controls[e.control_count++].pos = q;
177 }
178 }
179 e.length = snap.length;
180}
181
187{
188 midi_loop loop;
189 OverdubMode mode{};
190 double rate{48000.};
191 double tempo{120.};
192 int meter_numerator{4};
193 int meter_denominator{4};
194 bool wrap{true};
195 stop_take_notice notice;
196 std::atomic_bool in_use{};
197};
198
200{
201 std::shared_ptr<take_writer> writer;
202 std::shared_ptr<midi_export> data;
205 std::shared_ptr<midi_stop_take> stop;
206 int slot{};
208 int number{};
209 int layer{-1};
210 bool alone{};
211
212 bool write(const midi_export& e)
213 {
214 const std::string bytes = e.to_smf();
215 const auto& path = writer->next_path(slot, number, layer);
216 std::ofstream f{
217 std::filesystem::path{std::u8string_view{
218 reinterpret_cast<const char8_t*>(path.data()), path.size()}},
219 std::ios::binary | std::ios::trunc};
220 f.write(bytes.data(), std::streamsize(bytes.size()));
221 return bool(f);
222 }
223
224 bool run()
225 {
226 if(stop)
227 return run_stop();
228 if(!data)
229 return false;
230 struct release
231 {
232 midi_export& e;
233 ~release() { e.in_use = false; }
234 } done{*data};
235 if(!writer || data->length <= 0)
236 return false;
237 return write(*data);
238 }
239
241 bool run_stop()
242 {
243 auto& s = *stop;
244 struct release
245 {
247 ~release() { s.in_use = false; }
248 } done{s};
249 bool ok = writer && s.loop.length > 0;
250 if(ok)
251 {
252 auto e = std::make_unique<midi_export>();
253 e->rate = s.rate;
254 e->tempo = s.tempo;
255 e->meter_numerator = s.meter_numerator;
256 e->meter_denominator = s.meter_denominator;
257 e->wrap = s.wrap;
258 const auto where = [](const midi_note& n, int64_t) {
259 struct
260 {
261 int64_t start, length;
262 } placed{n.start, n.length};
263 return placed;
264 };
265 number = 0;
266 for(layer = -1; ok && layer < s.loop.count; layer++)
267 {
268 fill_midi_export(s.loop, layer, 0, s.mode, where, *e);
269 ok = write(*e);
270 }
271 }
272 if(writer)
273 s.notice.written(*writer, slot, ok, s.loop.count, true);
274 return ok;
275 }
276};
277}
Definition AudioLayers.hpp:14
void fill_midi_export(const midi_loop &snap, int item, int64_t pos, OverdubMode mode, Place &&place, midi_export &e)
Definition MidiTake.hpp:138
OverdubMode
What the MIDI looper does with notes of earlier passes under an overdub.
Definition MidiLoop.hpp:17
Definition MidiTake.hpp:22
std::string to_smf() const
Definition MidiTake.hpp:43
std::atomic_bool in_use
Definition MidiTake.hpp:35
Definition MidiLoop.hpp:80
bool heard(const midi_note &n, OverdubMode mode, bool own_mute=true) const noexcept
own_mute false hears a muted pass's events as if it were not muted.
Definition MidiLoop.hpp:128
Definition MidiLoop.hpp:31
Definition MidiTake.hpp:187
Definition MidiTake.hpp:200
bool run_stop()
The mix, then each pass, under one take number.
Definition MidiTake.hpp:241
std::shared_ptr< midi_stop_take > stop
Definition MidiTake.hpp:205
int number
Number of the take, 0 for a new one; -1 for the mix, else the pass.
Definition MidiTake.hpp:208
int64_t length
Frames in the loop.
Definition PassStack.hpp:79
Definition TakeWriter.hpp:153
int64_t distance(int64_t from, int64_t to) const noexcept
Definition PassStack.hpp:736