Little MidiEvent prettification additions, ifdef'd non-realtime aspects (for future...
[ardour.git] / libs / ardour / midi_model.cc
1 /*
2     Copyright (C) 2007 Paul Davis 
3         Written by Dave Robillard, 2007
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <iostream>
22 #include <algorithm>
23 #include <queue>
24 #include <pbd/enumwriter.h>
25 #include <ardour/midi_model.h>
26 #include <ardour/midi_events.h>
27 #include <ardour/types.h>
28 #include <ardour/session.h>
29
30 using namespace std;
31 using namespace ARDOUR;
32
33 // Note
34
35 MidiModel::Note::Note(double t, double d, uint8_t n, uint8_t v)
36         : _on_event(t, 3, NULL, true)
37         , _off_event(t + d, 3, NULL, true)
38 {
39         _on_event.buffer()[0] = MIDI_CMD_NOTE_ON;
40         _on_event.buffer()[1] = n;
41         _on_event.buffer()[2] = v;
42         
43         _off_event.buffer()[0] = MIDI_CMD_NOTE_OFF;
44         _off_event.buffer()[1] = n;
45         _off_event.buffer()[2] = 0x40;
46         
47         assert(time() == t);
48         assert(duration() == d);
49         assert(note() == n);
50         assert(velocity() == v);
51 }
52
53
54 MidiModel::Note::Note(const Note& copy)
55         : _on_event(copy._on_event, true)
56         , _off_event(copy._off_event, true)
57 {
58         /*
59         assert(copy._on_event.size == 3);
60         _on_event.buffer = _on_event_buffer;
61         memcpy(_on_event_buffer, copy._on_event_buffer, 3);
62         
63         assert(copy._off_event.size == 3);
64         _off_event.buffer = _off_event_buffer;
65         memcpy(_off_event_buffer, copy._off_event_buffer, 3);
66         */
67
68         assert(time() == copy.time());
69         assert(end_time() == copy.end_time());
70         assert(note() == copy.note());
71         assert(velocity() == copy.velocity());
72         assert(duration() == copy.duration());
73 }
74
75
76 const MidiModel::Note&
77 MidiModel::Note::operator=(const Note& copy)
78 {
79         _on_event = copy._on_event;
80         _off_event = copy._off_event;
81         /*_on_event.time = copy._on_event.time;
82         assert(copy._on_event.size == 3);
83         memcpy(_on_event_buffer, copy._on_event_buffer, 3);
84         
85         _off_event.time = copy._off_event.time;
86         assert(copy._off_event.size == 3);
87         memcpy(_off_event_buffer, copy._off_event_buffer, 3);
88         */
89         
90         assert(time() == copy.time());
91         assert(end_time() == copy.end_time());
92         assert(note() == copy.note());
93         assert(velocity() == copy.velocity());
94         assert(duration() == copy.duration());
95
96         return *this;
97 }
98
99 // MidiModel
100
101 MidiModel::MidiModel(Session& s, size_t size)
102         : _session(s)
103         , _notes(size)
104         , _note_mode(Sustained)
105         , _writing(false)
106 {
107 }
108
109
110 /** Read events in frame range \a start .. \a start+cnt into \a dst,
111  * adding \a stamp_offset to each event's timestamp.
112  * \return number of events written to \a dst
113  */
114 size_t
115 MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframes_t stamp_offset) const
116 {
117         size_t read_events = 0;
118
119         //cerr << "MM READ @ " << start << " + " << nframes << endl;
120
121         /* FIXME: cache last lookup value to avoid the search */
122
123         if (_note_mode == Sustained) {
124                 LaterNoteEndComparator cmp;
125                 priority_queue<const Note*,vector<const Note*>,LaterNoteEndComparator> active_notes(cmp);
126
127                 /* FIXME: cache last lookup value to avoid the search */
128                 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
129         
130                         //cerr << "MM ON " << n->time() << endl;
131
132                         if (n->time() >= start + nframes)
133                                 break;
134
135                         while ( ! active_notes.empty() ) {
136                                 const Note* const earliest_off = active_notes.top();
137                                 const MidiEvent& ev = earliest_off->off_event();
138                                 if (ev.time() < start + nframes && ev.time() <= n->time()) {
139                                         dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
140                                         active_notes.pop();
141                                         ++read_events;
142                                 } else {
143                                         break;
144                                 }
145                         }
146
147                         // Note on
148                         if (n->time() >= start) {
149                                 const MidiEvent& ev = n->on_event();
150                                 dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
151                                 active_notes.push(&(*n));
152                                 ++read_events;
153                         }
154
155                 }
156
157         // Percussive
158         } else {
159                 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
160                         // Note on
161                         if (n->time() >= start) {
162                                 if (n->time() < start + nframes) {
163                                         const MidiEvent& ev = n->on_event();
164                                         dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
165                                         ++read_events;
166                                 } else {
167                                         break;
168                                 }
169                         }
170                 }
171         }
172
173         //if (read_events > 0)
174         //      cerr << "MM READ " << read_events << " EVENTS" << endl;
175
176         return read_events;
177 }
178
179
180 /** Begin a write of events to the model.
181  *
182  * If \a mode is Sustained, complete notes with duration are constructed as note
183  * on/off events are received.  Otherwise (Percussive), only note on events are
184  * stored; note off events are discarded entirely and all contained notes will
185  * have duration 0.
186  */
187 void
188 MidiModel::start_write()
189 {
190         //cerr << "MM START WRITE, MODE = " << enum_2_string(_note_mode) << endl;
191         _write_notes.clear();
192         _writing = true;
193 }
194
195
196
197 /** Finish a write of events to the model.
198  *
199  * If \a delete_stuck is true and the current mode is Sustained, note on events
200  * that were never resolved with a corresonding note off will be deleted.
201  * Otherwise they will remain as notes with duration 0.
202  */
203 void
204 MidiModel::end_write(bool delete_stuck)
205 {
206         assert(_writing);
207         
208         //cerr << "MM END WRITE\n";
209
210         if (_note_mode == Sustained && delete_stuck) {
211                 for (Notes::iterator n = _notes.begin(); n != _notes.end() ; ) {
212                         if (n->duration() == 0) {
213                                 cerr << "WARNING: Stuck note lost: " << n->note() << endl;
214                                 n = _notes.erase(n);
215                         } else {
216                                 ++n;
217                         }
218                 }
219         }
220
221         _write_notes.clear();
222         _writing = false;
223 }
224
225
226 /** Append contents of \a buf to model.  NOT realtime safe.
227  *
228  * Timestamps of events in \a buf are expected to be relative to
229  * the start of this model (t=0) and MUST be monotonically increasing
230  * and MUST be >= the latest event currently in the model.
231  *
232  * Events in buf are deep copied.
233  */
234 void
235 MidiModel::append(const MidiBuffer& buf)
236
237         assert(_writing);
238
239         for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
240                 const MidiEvent& ev = *i;
241                 
242                 assert(_notes.empty() || ev.time() >= _notes.back().time());
243
244                 if (ev.type() == MIDI_CMD_NOTE_ON)
245                         append_note_on(ev.time(), ev.note(), ev.velocity());
246                 else if (ev.type() == MIDI_CMD_NOTE_OFF)
247                         append_note_off(ev.time(), ev.note());
248         }
249 }
250
251
252 /** Append \a in_event to model.  NOT realtime safe.
253  *
254  * Timestamps of events in \a buf are expected to be relative to
255  * the start of this model (t=0) and MUST be monotonically increasing
256  * and MUST be >= the latest event currently in the model.
257  */
258 void
259 MidiModel::append(double time, size_t size, const Byte* buf)
260 {
261         assert(_notes.empty() || time >= _notes.back().time());
262         assert(_writing);
263
264         if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON)
265                 append_note_on(time, buf[1], buf[2]);
266         else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF)
267                 append_note_off(time, buf[1]);
268 }
269
270
271 void
272 MidiModel::append_note_on(double time, uint8_t note_num, uint8_t velocity)
273 {
274         assert(_writing);
275         _notes.push_back(Note(time, 0, note_num, velocity));
276         if (_note_mode == Sustained) {
277                 //cerr << "MM Appending note on " << (unsigned)(uint8_t)note_num << endl;
278                 _write_notes.push_back(_notes.size() - 1);
279         } else {
280                 //cerr << "MM NOT appending note on" << endl;
281         }
282 }
283
284
285 void
286 MidiModel::append_note_off(double time, uint8_t note_num)
287 {
288         assert(_writing);
289         if (_note_mode == Percussive) {
290                 //cerr << "MM Ignoring note off (percussive mode)" << endl;
291                 return;
292         } else {
293                 //cerr << "MM Attempting to resolve note off " << (unsigned)(uint8_t)note_num << endl;
294         }
295
296         /* FIXME: make _write_notes fixed size (127 noted) for speed */
297         
298         /* FIXME: note off velocity for that one guy out there who actually has
299          * keys that send it */
300
301         for (WriteNotes::iterator n = _write_notes.begin(); n != _write_notes.end(); ++n) {
302                 Note& note = _notes[*n];
303                 //cerr << (unsigned)(uint8_t)note.note() << " ? " << (unsigned)note_num << endl;
304                 if (note.note() == note_num) {
305                         assert(time > note.time());
306                         note.set_duration(time - note.time());
307                         _write_notes.erase(n);
308                         //cerr << "MidiModel resolved note, duration: " << note.duration() << endl;
309                         break;
310                 }
311         }
312 }
313
314
315 void
316 MidiModel::add_note(const Note& note)
317 {
318         Notes::iterator i = upper_bound(_notes.begin(), _notes.end(), note, note_time_comparator);
319         _notes.insert(i, note);
320 }
321
322
323 void
324 MidiModel::remove_note(const Note& note)
325 {
326         Notes::iterator n = find(_notes.begin(), _notes.end(), note);
327         if (n != _notes.end())
328                 _notes.erase(n);
329 }
330
331 /** Slow!  for debugging only. */
332 bool
333 MidiModel::is_sorted() const
334 {
335         bool t = 0;
336         for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n)
337                 if (n->time() < t)
338                         return false;
339                 else
340                         t = n->time();
341
342         return true;
343 }
344
345
346 /** Start a new command.
347  *
348  * This has no side-effects on the model or Session, the returned command
349  * can be held on to for as long as the caller wishes, or discarded without
350  * formality, until apply_command is called and ownership is taken.
351  */
352 MidiModel::DeltaCommand*
353 MidiModel::new_delta_command(const string name)
354 {
355         DeltaCommand* cmd =  new DeltaCommand(*this, name);
356         return cmd;
357 }
358
359
360 /** Apply a command.
361  *
362  * Ownership of cmd is taken, it must not be deleted by the caller.
363  * The command will constitute one item on the undo stack.
364  */
365 void
366 MidiModel::apply_command(Command* cmd)
367 {
368         _session.begin_reversible_command(cmd->name());
369         (*cmd)();
370         assert(is_sorted());
371         _session.commit_reversible_command(cmd);
372 }
373
374
375 // MidiEditCommand
376
377
378 void
379 MidiModel::DeltaCommand::add(const Note& note)
380 {
381         //cerr << "MEC: apply" << endl;
382
383         _removed_notes.remove(note);
384         _added_notes.push_back(note);
385 }
386
387
388 void
389 MidiModel::DeltaCommand::remove(const Note& note)
390 {
391         //cerr << "MEC: remove" << endl;
392
393         _added_notes.remove(note);
394         _removed_notes.push_back(note);
395 }
396
397                 
398 void 
399 MidiModel::DeltaCommand::operator()()
400 {
401         // This could be made much faster by using a priority_queue for added and
402         // removed notes (or sort here), and doing a single iteration over _model
403         
404         for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
405                 _model.add_note(*i);
406         
407         for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
408                 _model.remove_note(*i);
409         
410         _model.ContentsChanged(); /* EMIT SIGNAL */
411 }
412
413
414 void
415 MidiModel::DeltaCommand::undo()
416 {
417         // This could be made much faster by using a priority_queue for added and
418         // removed notes (or sort here), and doing a single iteration over _model
419
420         for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
421                 _model.remove_note(*i);
422         
423         for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
424                 _model.add_note(*i);
425         
426         _model.ContentsChanged(); /* EMIT SIGNAL */
427 }
428