c68dcff6b6eb8f9c68e184c9ddd7ce29d79ace34
[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 <pbd/enumwriter.h>
24 #include <ardour/midi_model.h>
25 #include <ardour/midi_events.h>
26 #include <ardour/types.h>
27 #include <ardour/session.h>
28
29 using namespace std;
30 using namespace ARDOUR;
31
32 // Note
33
34 MidiModel::Note::Note(double t, double d, uint8_t n, uint8_t v)
35         : _on_event(t, 3, NULL, true)
36         , _off_event(t + d, 3, NULL, true)
37 {
38         _on_event.buffer()[0] = MIDI_CMD_NOTE_ON;
39         _on_event.buffer()[1] = n;
40         _on_event.buffer()[2] = v;
41         
42         _off_event.buffer()[0] = MIDI_CMD_NOTE_OFF;
43         _off_event.buffer()[1] = n;
44         _off_event.buffer()[2] = 0x40;
45         
46         assert(time() == t);
47         assert(duration() == d);
48         assert(note() == n);
49         assert(velocity() == v);
50 }
51
52
53 MidiModel::Note::Note(const Note& copy)
54         : _on_event(copy._on_event, true)
55         , _off_event(copy._off_event, true)
56 {
57         /*
58         assert(copy._on_event.size == 3);
59         _on_event.buffer = _on_event_buffer;
60         memcpy(_on_event_buffer, copy._on_event_buffer, 3);
61         
62         assert(copy._off_event.size == 3);
63         _off_event.buffer = _off_event_buffer;
64         memcpy(_off_event_buffer, copy._off_event_buffer, 3);
65         */
66
67         assert(time() == copy.time());
68         assert(end_time() == copy.end_time());
69         assert(note() == copy.note());
70         assert(velocity() == copy.velocity());
71         assert(duration() == copy.duration());
72 }
73
74
75 const MidiModel::Note&
76 MidiModel::Note::operator=(const Note& copy)
77 {
78         _on_event = copy._on_event;
79         _off_event = copy._off_event;
80         /*_on_event.time = copy._on_event.time;
81         assert(copy._on_event.size == 3);
82         memcpy(_on_event_buffer, copy._on_event_buffer, 3);
83         
84         _off_event.time = copy._off_event.time;
85         assert(copy._off_event.size == 3);
86         memcpy(_off_event_buffer, copy._off_event_buffer, 3);
87         */
88         
89         assert(time() == copy.time());
90         assert(end_time() == copy.end_time());
91         assert(note() == copy.note());
92         assert(velocity() == copy.velocity());
93         assert(duration() == copy.duration());
94
95         return *this;
96 }
97
98 // MidiModel
99
100 MidiModel::MidiModel(Session& s, size_t size)
101         : _session(s)
102         , _notes(size)
103         , _note_mode(Sustained)
104         , _writing(false)
105         , _active_notes(LaterNoteEndComparator())
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
125                 /* FIXME: cache last lookup value to avoid the search */
126                 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
127
128                         //cerr << "MM ON " << n->time() << endl;
129
130                         while ( ! _active_notes.empty() ) {
131                                 const Note* const earliest_off = _active_notes.top();
132                                 const MidiEvent& ev = earliest_off->off_event();
133                                 if (ev.time() < start + nframes && ev.time() <= n->time()) {
134                                         dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
135                                         _active_notes.pop();
136                                         ++read_events;
137                                 } else {
138                                         break;
139                                 }
140                         }
141
142                         if (n->time() >= start + nframes)
143                                 break;
144
145                         // Note on
146                         if (n->time() >= start) {
147                                 const MidiEvent& ev = n->on_event();
148                                 dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
149                                 _active_notes.push(&(*n));
150                                 ++read_events;
151                         }
152
153                 }
154
155         // Percussive
156         } else {
157                 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
158                         // Note on
159                         if (n->time() >= start) {
160                                 if (n->time() < start + nframes) {
161                                         const MidiEvent& ev = n->on_event();
162                                         dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
163                                         ++read_events;
164                                 } else {
165                                         break;
166                                 }
167                         }
168                 }
169         }
170
171         //if (read_events > 0)
172         //      cerr << "MM READ " << read_events << " EVENTS" << endl;
173
174         return read_events;
175 }
176
177
178 /** Begin a write of events to the model.
179  *
180  * If \a mode is Sustained, complete notes with duration are constructed as note
181  * on/off events are received.  Otherwise (Percussive), only note on events are
182  * stored; note off events are discarded entirely and all contained notes will
183  * have duration 0.
184  */
185 void
186 MidiModel::start_write()
187 {
188         //cerr << "MM START WRITE, MODE = " << enum_2_string(_note_mode) << endl;
189         _write_notes.clear();
190         _writing = true;
191 }
192
193
194
195 /** Finish a write of events to the model.
196  *
197  * If \a delete_stuck is true and the current mode is Sustained, note on events
198  * that were never resolved with a corresonding note off will be deleted.
199  * Otherwise they will remain as notes with duration 0.
200  */
201 void
202 MidiModel::end_write(bool delete_stuck)
203 {
204         assert(_writing);
205         
206         //cerr << "MM END WRITE\n";
207
208         if (_note_mode == Sustained && delete_stuck) {
209                 for (Notes::iterator n = _notes.begin(); n != _notes.end() ; ) {
210                         if (n->duration() == 0) {
211                                 cerr << "WARNING: Stuck note lost: " << n->note() << endl;
212                                 n = _notes.erase(n);
213                         } else {
214                                 ++n;
215                         }
216                 }
217         }
218
219         _write_notes.clear();
220         _writing = false;
221 }
222
223
224 /** Append contents of \a buf to model.  NOT realtime safe.
225  *
226  * Timestamps of events in \a buf are expected to be relative to
227  * the start of this model (t=0) and MUST be monotonically increasing
228  * and MUST be >= the latest event currently in the model.
229  *
230  * Events in buf are deep copied.
231  */
232 void
233 MidiModel::append(const MidiBuffer& buf)
234
235         assert(_writing);
236
237         for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
238                 const MidiEvent& ev = *i;
239                 
240                 assert(_notes.empty() || ev.time() >= _notes.back().time());
241
242                 if (ev.type() == MIDI_CMD_NOTE_ON)
243                         append_note_on(ev.time(), ev.note(), ev.velocity());
244                 else if (ev.type() == MIDI_CMD_NOTE_OFF)
245                         append_note_off(ev.time(), ev.note());
246         }
247 }
248
249
250 /** Append \a in_event to model.  NOT realtime safe.
251  *
252  * Timestamps of events in \a buf are expected to be relative to
253  * the start of this model (t=0) and MUST be monotonically increasing
254  * and MUST be >= the latest event currently in the model.
255  */
256 void
257 MidiModel::append(double time, size_t size, const Byte* buf)
258 {
259         assert(_notes.empty() || time >= _notes.back().time());
260         assert(_writing);
261
262         if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON)
263                 append_note_on(time, buf[1], buf[2]);
264         else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF)
265                 append_note_off(time, buf[1]);
266 }
267
268
269 void
270 MidiModel::append_note_on(double time, uint8_t note_num, uint8_t velocity)
271 {
272         assert(_writing);
273         _notes.push_back(Note(time, 0, note_num, velocity));
274         if (_note_mode == Sustained) {
275                 //cerr << "MM Appending note on " << (unsigned)(uint8_t)note_num << endl;
276                 _write_notes.push_back(_notes.size() - 1);
277         } else {
278                 //cerr << "MM NOT appending note on" << endl;
279         }
280 }
281
282
283 void
284 MidiModel::append_note_off(double time, uint8_t note_num)
285 {
286         assert(_writing);
287         if (_note_mode == Percussive) {
288                 //cerr << "MM Ignoring note off (percussive mode)" << endl;
289                 return;
290         } else {
291                 //cerr << "MM Attempting to resolve note off " << (unsigned)(uint8_t)note_num << endl;
292         }
293
294         /* FIXME: make _write_notes fixed size (127 noted) for speed */
295         
296         /* FIXME: note off velocity for that one guy out there who actually has
297          * keys that send it */
298
299         for (WriteNotes::iterator n = _write_notes.begin(); n != _write_notes.end(); ++n) {
300                 Note& note = _notes[*n];
301                 //cerr << (unsigned)(uint8_t)note.note() << " ? " << (unsigned)note_num << endl;
302                 if (note.note() == note_num) {
303                         assert(time > note.time());
304                         note.set_duration(time - note.time());
305                         _write_notes.erase(n);
306                         //cerr << "MidiModel resolved note, duration: " << note.duration() << endl;
307                         break;
308                 }
309         }
310 }
311
312
313 void
314 MidiModel::add_note(const Note& note)
315 {
316         Notes::iterator i = upper_bound(_notes.begin(), _notes.end(), note, note_time_comparator);
317         _notes.insert(i, note);
318 }
319
320
321 void
322 MidiModel::remove_note(const Note& note)
323 {
324         Notes::iterator n = find(_notes.begin(), _notes.end(), note);
325         if (n != _notes.end())
326                 _notes.erase(n);
327 }
328
329 /** Slow!  for debugging only. */
330 bool
331 MidiModel::is_sorted() const
332 {
333         bool t = 0;
334         for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n)
335                 if (n->time() < t)
336                         return false;
337                 else
338                         t = n->time();
339
340         return true;
341 }
342
343
344 /** Start a new command.
345  *
346  * This has no side-effects on the model or Session, the returned command
347  * can be held on to for as long as the caller wishes, or discarded without
348  * formality, until apply_command is called and ownership is taken.
349  */
350 MidiModel::DeltaCommand*
351 MidiModel::new_delta_command(const string name)
352 {
353         DeltaCommand* cmd =  new DeltaCommand(*this, name);
354         return cmd;
355 }
356
357
358 /** Apply a command.
359  *
360  * Ownership of cmd is taken, it must not be deleted by the caller.
361  * The command will constitute one item on the undo stack.
362  */
363 void
364 MidiModel::apply_command(Command* cmd)
365 {
366         _session.begin_reversible_command(cmd->name());
367         (*cmd)();
368         assert(is_sorted());
369         _session.commit_reversible_command(cmd);
370 }
371
372
373 // MidiEditCommand
374
375
376 void
377 MidiModel::DeltaCommand::add(const Note& note)
378 {
379         //cerr << "MEC: apply" << endl;
380
381         _removed_notes.remove(note);
382         _added_notes.push_back(note);
383 }
384
385
386 void
387 MidiModel::DeltaCommand::remove(const Note& note)
388 {
389         //cerr << "MEC: remove" << endl;
390
391         _added_notes.remove(note);
392         _removed_notes.push_back(note);
393 }
394
395                 
396 void 
397 MidiModel::DeltaCommand::operator()()
398 {
399         // This could be made much faster by using a priority_queue for added and
400         // removed notes (or sort here), and doing a single iteration over _model
401         
402         for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
403                 _model.add_note(*i);
404         
405         for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
406                 _model.remove_note(*i);
407         
408         _model.ContentsChanged(); /* EMIT SIGNAL */
409 }
410
411
412 void
413 MidiModel::DeltaCommand::undo()
414 {
415         // This could be made much faster by using a priority_queue for added and
416         // removed notes (or sort here), and doing a single iteration over _model
417
418         for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
419                 _model.remove_note(*i);
420         
421         for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
422                 _model.add_note(*i);
423         
424         _model.ContentsChanged(); /* EMIT SIGNAL */
425 }
426
427
428 bool
429 MidiModel::write_new_source(const std::string& path)
430 {
431         cerr << "Writing model to " << path << endl;
432
433 #if 0
434                 SourceFactory::createWritable (region->data_type(), session, path, false, session.frame_rate());
435
436                 catch (failed_constructor& err) {
437                         error << string_compose (_("filter: error creating new file %1 (%2)"), path, strerror (errno)) << endmsg;
438                         return -1;
439                 }
440         }
441 #endif
442
443         return true;
444 }
445