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/types.h>
27 #include <ardour/session.h>
30 using namespace ARDOUR;
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)
38 _on_event.buffer()[0] = MIDI_CMD_NOTE_ON;
39 _on_event.buffer()[1] = n;
40 _on_event.buffer()[2] = v;
42 _off_event.buffer()[0] = MIDI_CMD_NOTE_OFF;
43 _off_event.buffer()[1] = n;
44 _off_event.buffer()[2] = 0x40;
47 assert(duration() == d);
49 assert(velocity() == v);
53 MidiModel::Note::Note(const Note& copy)
54 : _on_event(copy._on_event, true)
55 , _off_event(copy._off_event, true)
58 assert(copy._on_event.size == 3);
59 _on_event.buffer = _on_event_buffer;
60 memcpy(_on_event_buffer, copy._on_event_buffer, 3);
62 assert(copy._off_event.size == 3);
63 _off_event.buffer = _off_event_buffer;
64 memcpy(_off_event_buffer, copy._off_event_buffer, 3);
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());
75 const MidiModel::Note&
76 MidiModel::Note::operator=(const Note& copy)
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);
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);
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());
100 MidiModel::MidiModel(Session& s, size_t size)
103 , _note_mode(Sustained)
105 , _active_notes(LaterNoteEndComparator())
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
115 MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframes_t stamp_offset) const
117 size_t read_events = 0;
119 //cerr << "MM READ @ " << start << " + " << nframes << endl;
121 /* FIXME: cache last lookup value to avoid the search */
123 if (_note_mode == Sustained) {
125 /* FIXME: cache last lookup value to avoid the search */
126 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
128 //cerr << "MM ON " << n->time() << endl;
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());
142 if (n->time() >= start + nframes)
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));
157 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n) {
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());
171 //if (read_events > 0)
172 // cerr << "MM READ " << read_events << " EVENTS" << endl;
178 /** Begin a write of events to the model.
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
186 MidiModel::start_write()
188 //cerr << "MM START WRITE, MODE = " << enum_2_string(_note_mode) << endl;
189 _write_notes.clear();
195 /** Finish a write of events to the model.
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.
202 MidiModel::end_write(bool delete_stuck)
206 //cerr << "MM END WRITE\n";
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;
219 _write_notes.clear();
224 /** Append contents of \a buf to model. NOT realtime safe.
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.
230 * Events in buf are deep copied.
233 MidiModel::append(const MidiBuffer& buf)
237 for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) {
238 const MidiEvent& ev = *i;
240 assert(_notes.empty() || ev.time() >= _notes.back().time());
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());
250 /** Append \a in_event to model. NOT realtime safe.
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.
257 MidiModel::append(double time, size_t size, const Byte* buf)
259 assert(_notes.empty() || time >= _notes.back().time());
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]);
270 MidiModel::append_note_on(double time, uint8_t note_num, uint8_t velocity)
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);
278 //cerr << "MM NOT appending note on" << endl;
284 MidiModel::append_note_off(double time, uint8_t note_num)
287 if (_note_mode == Percussive) {
288 //cerr << "MM Ignoring note off (percussive mode)" << endl;
291 //cerr << "MM Attempting to resolve note off " << (unsigned)(uint8_t)note_num << endl;
294 /* FIXME: make _write_notes fixed size (127 noted) for speed */
296 /* FIXME: note off velocity for that one guy out there who actually has
297 * keys that send it */
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;
314 MidiModel::add_note(const Note& note)
316 Notes::iterator i = upper_bound(_notes.begin(), _notes.end(), note, note_time_comparator);
317 _notes.insert(i, note);
322 MidiModel::remove_note(const Note& note)
324 Notes::iterator n = find(_notes.begin(), _notes.end(), note);
325 if (n != _notes.end())
329 /** Slow! for debugging only. */
331 MidiModel::is_sorted() const
334 for (Notes::const_iterator n = _notes.begin(); n != _notes.end(); ++n)
344 /** Start a new command.
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.
350 MidiModel::DeltaCommand*
351 MidiModel::new_delta_command(const string name)
353 DeltaCommand* cmd = new DeltaCommand(*this, name);
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.
364 MidiModel::apply_command(Command* cmd)
366 _session.begin_reversible_command(cmd->name());
369 _session.commit_reversible_command(cmd);
377 MidiModel::DeltaCommand::add(const Note& note)
379 //cerr << "MEC: apply" << endl;
381 _removed_notes.remove(note);
382 _added_notes.push_back(note);
387 MidiModel::DeltaCommand::remove(const Note& note)
389 //cerr << "MEC: remove" << endl;
391 _added_notes.remove(note);
392 _removed_notes.push_back(note);
397 MidiModel::DeltaCommand::operator()()
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
402 for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
405 for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
406 _model.remove_note(*i);
408 _model.ContentsChanged(); /* EMIT SIGNAL */
413 MidiModel::DeltaCommand::undo()
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
418 for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
419 _model.remove_note(*i);
421 for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
424 _model.ContentsChanged(); /* EMIT SIGNAL */
429 MidiModel::write_new_source(const std::string& path)
431 cerr << "Writing model to " << path << endl;
434 SourceFactory::createWritable (region->data_type(), session, path, false, session.frame_rate());
436 catch (failed_constructor& err) {
437 error << string_compose (_("filter: error creating new file %1 (%2)"), path, strerror (errno)) << endmsg;