2 Copyright (C) 2007 Paul Davis
3 Written by Dave Robillard, 2007
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.
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.
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.
23 #include <pbd/enumwriter.h>
24 #include <ardour/midi_model.h>
25 #include <ardour/midi_events.h>
26 #include <ardour/midi_source.h>
27 #include <ardour/types.h>
28 #include <ardour/session.h>
31 using namespace ARDOUR;
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)
39 _on_event.buffer()[0] = MIDI_CMD_NOTE_ON;
40 _on_event.buffer()[1] = n;
41 _on_event.buffer()[2] = v;
43 _off_event.buffer()[0] = MIDI_CMD_NOTE_OFF;
44 _off_event.buffer()[1] = n;
45 _off_event.buffer()[2] = 0x40;
48 assert(duration() == d);
50 assert(velocity() == v);
54 MidiModel::Note::Note(const Note& copy)
55 : _on_event(copy._on_event, true)
56 , _off_event(copy._off_event, true)
59 assert(copy._on_event.size == 3);
60 _on_event.buffer = _on_event_buffer;
61 memcpy(_on_event_buffer, copy._on_event_buffer, 3);
63 assert(copy._off_event.size == 3);
64 _off_event.buffer = _off_event_buffer;
65 memcpy(_off_event_buffer, copy._off_event_buffer, 3);
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());
76 const MidiModel::Note&
77 MidiModel::Note::operator=(const Note& copy)
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);
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);
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());
101 MidiModel::MidiModel(Session& s, size_t size)
102 : Automatable(s, "midi model")
104 , _note_mode(Sustained)
107 , _active_notes(LaterNoteEndComparator())
112 /** Read events in frame range \a start .. \a start+cnt into \a dst,
113 * adding \a stamp_offset to each event's timestamp.
114 * \return number of events written to \a dst
117 MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframes_t stamp_offset) const
119 size_t read_events = 0;
121 /* FIXME: cache last lookup value to avoid O(n) search every time */
123 if (_note_mode == Sustained) {
125 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
127 while ( ! _active_notes.empty() ) {
128 const Note* const earliest_off = _active_notes.top();
129 const MidiEvent& off_ev = earliest_off->off_event();
130 if (off_ev.time() < start + nframes && off_ev.time() <= n->time()) {
131 dst.write(off_ev.time() + stamp_offset, off_ev.size(), off_ev.buffer());
139 if (n->time() >= start + nframes)
143 if (n->time() >= start) {
144 const MidiEvent& on_ev = n->on_event();
145 dst.write(on_ev.time() + stamp_offset, on_ev.size(), on_ev.buffer());
146 _active_notes.push(&(*n));
152 // Write any trailing note offs
153 while ( ! _active_notes.empty() ) {
154 const Note* const earliest_off = _active_notes.top();
155 const MidiEvent& off_ev = earliest_off->off_event();
156 if (off_ev.time() < start + nframes) {
157 dst.write(off_ev.time() + stamp_offset, off_ev.size(), off_ev.buffer());
167 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
169 if (n->time() >= start) {
170 if (n->time() < start + nframes) {
171 const MidiEvent& ev = n->on_event();
172 dst.write(ev.time() + stamp_offset, ev.size(), ev.buffer());
185 /** Begin a write of events to the model.
187 * If \a mode is Sustained, complete notes with duration are constructed as note
188 * on/off events are received. Otherwise (Percussive), only note on events are
189 * stored; note off events are discarded entirely and all contained notes will
193 MidiModel::start_write()
195 //cerr << "MM " << this << " START WRITE, MODE = " << enum_2_string(_note_mode) << endl;
198 _write_notes.clear();
203 /** Finish a write of events to the model.
205 * If \a delete_stuck is true and the current mode is Sustained, note on events
206 * that were never resolved with a corresonding note off will be deleted.
207 * Otherwise they will remain as notes with duration 0.
210 MidiModel::end_write(bool delete_stuck)
214 //cerr << "MM " << this << " END WRITE: " << _notes.size() << " NOTES\n";
216 if (_note_mode == Sustained && delete_stuck) {
217 for (Notes::iterator n = _notes.begin(); n != _notes.end() ; ) {
218 if (n->duration() == 0) {
219 cerr << "WARNING: Stuck note lost: " << n->note() << endl;
227 _write_notes.clear();
229 _lock.writer_unlock();
233 /** Append contents of \a buf to model. NOT realtime safe.
235 * Timestamps of events in \a buf are expected to be relative to
236 * the start of this model (t=0) and MUST be monotonically increasing
237 * and MUST be >= the latest event currently in the model.
239 * Events in buf are deep copied.
242 MidiModel::append(const MidiBuffer& buf)
246 for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
247 assert(_notes.empty() || (*i).time() >= _notes.back().time());
253 /** Append \a in_event to model. NOT realtime safe.
255 * Timestamps of events in \a buf are expected to be relative to
256 * the start of this model (t=0) and MUST be monotonically increasing
257 * and MUST be >= the latest event currently in the model.
260 MidiModel::append(const MidiEvent& ev)
262 assert(_notes.empty() || ev.time() >= _notes.back().time());
266 append_note_on(ev.time(), ev.note(), ev.velocity());
267 else if (ev.is_note_off())
268 append_note_off(ev.time(), ev.note());
270 append_cc(ev.time(), ev.cc_number(), ev.cc_value());
272 printf("MM Unknown event type %X\n", ev.type());
277 MidiModel::append_note_on(double time, uint8_t note_num, uint8_t velocity)
279 //cerr << "MidiModel " << this << " note " << (int)note_num << " on @ " << time << endl;
282 _notes.push_back(Note(time, 0, note_num, velocity));
283 if (_note_mode == Sustained) {
284 //cerr << "MM Sustained: Appending active note on " << (unsigned)(uint8_t)note_num << endl;
285 _write_notes.push_back(_notes.size() - 1);
287 //cerr << "MM Percussive: NOT appending active note on" << endl;
293 MidiModel::append_note_off(double time, uint8_t note_num)
295 //cerr << "MidiModel " << this << " note " << (int)note_num << " off @ " << time << endl;
298 if (_note_mode == Percussive) {
299 //cerr << "MM Ignoring note off (percussive mode)" << endl;
302 //cerr << "MM Attempting to resolve note off " << (unsigned)(uint8_t)note_num << endl;
305 /* FIXME: make _write_notes fixed size (127 noted) for speed */
307 /* FIXME: note off velocity for that one guy out there who actually has
308 * keys that send it */
310 for (WriteNotes::iterator n = _write_notes.begin(); n != _write_notes.end(); ++n) {
311 Note& note = _notes[*n];
312 //cerr << (unsigned)(uint8_t)note.note() << " ? " << (unsigned)note_num << endl;
313 if (note.note() == note_num) {
314 assert(time > note.time());
315 note.set_duration(time - note.time());
316 _write_notes.erase(n);
317 //cerr << "MM resolved note, duration: " << note.duration() << endl;
325 MidiModel::append_cc(double time, uint8_t number, uint8_t value)
327 Parameter param(MidiCCAutomation, number);
329 //cerr << "MidiModel " << this << " add CC " << (int)number << " = " << (int)value
330 // << " @ " << time << endl;
332 boost::shared_ptr<AutomationControl> control = Automatable::control(param, true);
333 control->list()->fast_simple_add(time, (double)value);
338 MidiModel::add_note_unlocked(const Note& note)
340 //cerr << "MidiModel " << this << " add note " << (int)note.note() << " @ " << note.time() << endl;
341 Notes::iterator i = upper_bound(_notes.begin(), _notes.end(), note, note_time_comparator);
342 _notes.insert(i, note);
347 MidiModel::remove_note_unlocked(const Note& note)
349 //cerr << "MidiModel " << this << " remove note " << (int)note.note() << " @ " << note.time() << endl;
350 Notes::iterator n = find(_notes.begin(), _notes.end(), note);
351 if (n != _notes.end())
355 /** Slow! for debugging only. */
358 MidiModel::is_sorted() const
361 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n)
371 /** Start a new command.
373 * This has no side-effects on the model or Session, the returned command
374 * can be held on to for as long as the caller wishes, or discarded without
375 * formality, until apply_command is called and ownership is taken.
377 MidiModel::DeltaCommand*
378 MidiModel::new_delta_command(const string name)
380 DeltaCommand* cmd = new DeltaCommand(*this, name);
387 * Ownership of cmd is taken, it must not be deleted by the caller.
388 * The command will constitute one item on the undo stack.
391 MidiModel::apply_command(Command* cmd)
393 _session.begin_reversible_command(cmd->name());
396 _session.commit_reversible_command(cmd);
405 MidiModel::DeltaCommand::add(const Note& note)
407 //cerr << "MEC: apply" << endl;
409 _removed_notes.remove(note);
410 _added_notes.push_back(note);
415 MidiModel::DeltaCommand::remove(const Note& note)
417 //cerr << "MEC: remove" << endl;
419 _added_notes.remove(note);
420 _removed_notes.push_back(note);
425 MidiModel::DeltaCommand::operator()()
427 // This could be made much faster by using a priority_queue for added and
428 // removed notes (or sort here), and doing a single iteration over _model
430 _model._lock.writer_lock();
432 for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
433 _model.add_note_unlocked(*i);
435 for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
436 _model.remove_note_unlocked(*i);
438 _model._lock.writer_unlock();
440 _model.ContentsChanged(); /* EMIT SIGNAL */
445 MidiModel::DeltaCommand::undo()
447 // This could be made much faster by using a priority_queue for added and
448 // removed notes (or sort here), and doing a single iteration over _model
450 _model._lock.writer_lock();
452 for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
453 _model.remove_note_unlocked(*i);
455 for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
456 _model.add_note_unlocked(*i);
458 _model._lock.writer_unlock();
460 _model.ContentsChanged(); /* EMIT SIGNAL */
465 MidiModel::write_to(boost::shared_ptr<MidiSource> source)
467 //cerr << "Writing model to " << source->name() << endl;
469 /* This could be done using a temporary MidiRingBuffer and using
470 * MidiModel::read and MidiSource::write, but this is more efficient
471 * and doesn't require any buffer size assumptions (ie it's worth
472 * the code duplication).
474 * This is also different from read in that note off events are written
475 * regardless of the track mode. This is so the user can switch a
476 * recorded track (with note durations from some instrument) to percussive,
477 * save, reload, then switch it back to sustained preserving the original
482 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
483 const MidiEvent& ev = n->on_event();
484 source->append_event_unlocked(ev);
489 LaterNoteEndComparator cmp;
490 ActiveNotes active_notes(cmp);
493 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
495 // Write any pending note offs earlier than this note on
496 while ( ! active_notes.empty() ) {
497 const Note* const earliest_off = active_notes.top();
498 const MidiEvent& off_ev = earliest_off->off_event();
499 if (off_ev.time() <= n->time()) {
500 source->append_event_unlocked(off_ev);
507 // Write this note on
508 source->append_event_unlocked(n->on_event());
509 if (n->duration() > 0)
510 active_notes.push(&(*n));
513 // Write any trailing note offs
514 while ( ! active_notes.empty() ) {
515 source->append_event_unlocked(active_notes.top()->off_event());
521 _lock.reader_unlock();
527 MidiModel::get_state()
529 XMLNode *node = new XMLNode("MidiModel");