Midi pencil undo (not yet serializable).
[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 <ardour/midi_model.h>
23 #include <ardour/midi_events.h>
24 #include <ardour/types.h>
25 #include <ardour/session.h>
26
27 using namespace std;
28 using namespace ARDOUR;
29
30
31 MidiModel::MidiModel(Session& s, size_t size)
32         : _session(s)
33         , _notes(size)
34         , _command(NULL)
35 {
36 }
37
38
39 /** Begin a write of events to the model.
40  *
41  * As note on and off events are written, complete notes with duration are
42  * constructed
43  */
44 void
45 MidiModel::start_write()
46 {
47         _write_notes.clear();
48 }
49
50
51
52 /** Finish a write of events to the model.
53  *
54  * If \a delete_stuck is true, note on events that were never resolved with
55  * a corresonding note off will be deleted.  Otherwise they will remain as
56  * notes with duration 0.
57  */
58 void
59 MidiModel::end_write(bool delete_stuck)
60 {
61         if (delete_stuck) {
62                 _write_notes.clear();
63         } else {
64                 cerr << "FIXME: Stuck notes lost" << endl;
65                 _write_notes.clear();
66                 /* Merge _write_events into _events */
67                 /*size_t ev_index = 0
68                 size_t write_index = 0;
69                 while ( ! _write_events.empty()) {
70                         // do stuff
71                 }*/
72         }
73 }
74
75
76 /** Append contents of \a buf to model.  NOT realtime safe.
77  *
78  * Timestamps of events in \a buf are expected to be relative to
79  * the start of this model (t=0) and MUST be monotonically increasing
80  * and MUST be >= the latest event currently in the model.
81  *
82  * Events in buf are deep copied.
83  */
84 void
85 MidiModel::append(const MidiBuffer& buf)
86 {
87         for (size_t i=0; i < buf.size(); ++i) {
88                 const MidiEvent& ev = buf[i];
89                 
90                 assert(_write_notes.empty() || ev.time >= _write_notes.back().start);
91
92                 if (ev.type() == MIDI_CMD_NOTE_ON)
93                         append_note_on(ev.time, ev.note(), ev.velocity());
94                 else if (ev.type() == MIDI_CMD_NOTE_OFF)
95                         append_note_off(ev.time, ev.note());
96         }
97 }
98
99
100 /** Append \a in_event to model.  NOT realtime safe.
101  *
102  * Timestamps of events in \a buf are expected to be relative to
103  * the start of this model (t=0) and MUST be monotonically increasing
104  * and MUST be >= the latest event currently in the model.
105  */
106 void
107 MidiModel::append(double time, size_t size, Byte* buf)
108 {
109         assert(_write_notes.empty() || time >= _write_notes.back().start);
110
111         if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON)
112                 append_note_on(time, buf[1], buf[2]);
113         else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF)
114                 append_note_off(time, buf[1]);
115 }
116
117
118 void
119 MidiModel::append_note_on(double time, uint8_t note, uint8_t velocity)
120 {
121         _write_notes.push_back(Note(time, 0, note, velocity));
122 }
123
124
125 void
126 MidiModel::append_note_off(double time, uint8_t note_num)
127 {
128         /* _write_notes (active notes) is presumably small enough for linear
129          * search to be a good idea.  maybe not with instruments (percussion)
130          * that don't send note off at all though.... FIXME? */
131
132         /* FIXME: note off velocity for that one guy out there who actually has
133          * keys that send it */
134
135         for (size_t i=0; i < _write_notes.size(); ++i) {
136                 Note& note = _write_notes[i];
137                 if (note.note == note_num) {
138                         assert(time > note.start);
139                         note.duration = time - note.start;
140                         cerr << "MidiModel resolved note, duration: " << note.duration << endl;
141                         break;
142                 }
143         }
144 }
145
146
147 void
148 MidiModel::add_note(const Note& note)
149 {
150         Notes::iterator i = upper_bound(_notes.begin(), _notes.end(), note, NoteTimeComparator());
151         _notes.insert(i, note);
152         if (_command)
153                 _command->add_note(note);
154 }
155
156
157 void
158 MidiModel::remove_note(const Note& note)
159 {
160         Notes::iterator n = find(_notes.begin(), _notes.end(), note);
161         if (n != _notes.end())
162                 _notes.erase(n);
163         
164         if (_command)
165                 _command->remove_note(note);
166 }
167
168
169
170 void
171 MidiModel::begin_command()
172 {
173         assert(!_command);
174         _session.begin_reversible_command("midi edit");
175         _command = new MidiEditCommand(*this);
176 }
177
178
179 void
180 MidiModel::finish_command()
181 {
182         _session.commit_reversible_command(_command);
183         _command = NULL;
184 }
185
186
187 // MidiEditCommand
188
189
190 void
191 MidiModel::MidiEditCommand::add_note(const Note& note)
192 {
193         //cerr << "MEC: apply" << endl;
194
195         _removed_notes.remove(note);
196         _added_notes.push_back(note);
197 }
198
199
200 void
201 MidiModel::MidiEditCommand::remove_note(const Note& note)
202 {
203         //cerr << "MEC: remove" << endl;
204
205         _added_notes.remove(note);
206         _removed_notes.push_back(note);
207 }
208
209                 
210 void 
211 MidiModel::MidiEditCommand::operator()()
212 {
213         //cerr << "MEC: apply" << endl;
214         assert(!_model.current_command());
215         
216         for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
217                 _model.add_note(*i);
218         
219         for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
220                 _model.remove_note(*i);
221
222         _model.ContentsChanged(); /* EMIT SIGNAL */
223 }
224
225
226 void
227 MidiModel::MidiEditCommand::undo()
228 {
229         //cerr << "MEC: undo" << endl;
230         assert(!_model.current_command());
231
232         for (std::list<Note>::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
233                 _model.remove_note(*i);
234         
235         for (std::list<Note>::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
236                 _model.add_note(*i);
237         
238         _model.ContentsChanged(); /* EMIT SIGNAL */
239 }
240