more MIDI editing tweaks ; flip mouse mode buttons around for MIDI so that "object...
[ardour.git] / libs / ardour / midi_model.cc
1 /*
2     Copyright (C) 2007 Paul Davis
3     Author: Dave Robillard
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 #define __STDC_LIMIT_MACROS 1
22
23 #include <iostream>
24 #include <algorithm>
25 #include <stdexcept>
26 #include <stdint.h>
27 #include "pbd/error.h"
28 #include "pbd/enumwriter.h"
29 #include "midi++/events.h"
30
31 #include "ardour/midi_model.h"
32 #include "ardour/midi_source.h"
33 #include "ardour/types.h"
34 #include "ardour/session.h"
35
36 using namespace std;
37 using namespace ARDOUR;
38 using namespace PBD;
39
40 MidiModel::MidiModel(MidiSource* s, size_t size)
41         : AutomatableSequence<TimeType>(s->session(), size)
42         , _midi_source(s)
43 {
44 }
45
46 /** Start a new command.
47  *
48  * This has no side-effects on the model or Session, the returned command
49  * can be held on to for as long as the caller wishes, or discarded without
50  * formality, until apply_command is called and ownership is taken.
51  */
52 MidiModel::DeltaCommand*
53 MidiModel::new_delta_command(const string name)
54 {
55         DeltaCommand* cmd = new DeltaCommand(_midi_source->model(), name);
56         return cmd;
57 }
58
59 /** Apply a command.
60  *
61  * Ownership of cmd is taken, it must not be deleted by the caller.
62  * The command will constitute one item on the undo stack.
63  */
64 void
65 MidiModel::apply_command(Session& session, Command* cmd)
66 {
67         session.begin_reversible_command(cmd->name());
68         (*cmd)();
69         session.commit_reversible_command(cmd);
70         set_edited(true);
71 }
72
73 /** Apply a command as part of a larger reversible transaction
74  *
75  * Ownership of cmd is taken, it must not be deleted by the caller.
76  * The command will constitute one item on the undo stack.
77  */
78 void
79 MidiModel::apply_command_as_subcommand(Session& session, Command* cmd)
80 {
81         (*cmd)();
82         session.add_command(cmd);
83         set_edited(true);
84 }
85
86
87 // DeltaCommand
88
89 MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
90         : Command(name)
91         , _model(m)
92         , _name(name)
93 {
94         assert(_model);
95 }
96
97 MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const XMLNode& node)
98         : _model(m)
99 {
100         assert(_model);
101         set_state(node);
102 }
103
104 void
105 MidiModel::DeltaCommand::add(const boost::shared_ptr< Evoral::Note<TimeType> > note)
106 {
107         _removed_notes.remove(note);
108         _added_notes.push_back(note);
109 }
110
111 void
112 MidiModel::DeltaCommand::remove(const boost::shared_ptr< Evoral::Note<TimeType> > note)
113 {
114         _added_notes.remove(note);
115         _removed_notes.push_back(note);
116 }
117
118 void
119 MidiModel::DeltaCommand::operator()()
120 {
121         // This could be made much faster by using a priority_queue for added and
122         // removed notes (or sort here), and doing a single iteration over _model
123
124         Glib::Mutex::Lock lm (_model->_midi_source->mutex());
125         _model->_midi_source->invalidate(); // release model read lock
126         _model->write_lock();
127
128         for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
129                 _model->add_note_unlocked(*i);
130         }
131
132         for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
133                 _model->remove_note_unlocked(*i);
134         }
135         
136         _model->write_unlock();
137         _model->ContentsChanged(); /* EMIT SIGNAL */
138 }
139
140 void
141 MidiModel::DeltaCommand::undo()
142 {
143         // This could be made much faster by using a priority_queue for added and
144         // removed notes (or sort here), and doing a single iteration over _model
145         
146         Glib::Mutex::Lock lm (_model->_midi_source->mutex());
147         _model->_midi_source->invalidate(); // release model read lock
148         _model->write_lock();
149
150         for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
151                 _model->remove_note_unlocked(*i);
152         }
153
154         for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
155                 _model->add_note_unlocked(*i);
156         }
157
158         _model->write_unlock();
159         _model->ContentsChanged(); /* EMIT SIGNAL */
160 }
161
162 XMLNode&
163 MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr< Evoral::Note<TimeType> > note)
164 {
165         XMLNode* xml_note = new XMLNode("note");
166         ostringstream note_str(ios::ate);
167         note_str << int(note->note());
168         xml_note->add_property("note", note_str.str());
169
170         ostringstream channel_str(ios::ate);
171         channel_str << int(note->channel());
172         xml_note->add_property("channel", channel_str.str());
173
174         ostringstream time_str(ios::ate);
175         time_str << int(note->time());
176         xml_note->add_property("time", time_str.str());
177
178         ostringstream length_str(ios::ate);
179         length_str <<(unsigned int) note->length();
180         xml_note->add_property("length", length_str.str());
181
182         ostringstream velocity_str(ios::ate);
183         velocity_str << (unsigned int) note->velocity();
184         xml_note->add_property("velocity", velocity_str.str());
185
186         return *xml_note;
187 }
188
189 boost::shared_ptr< Evoral::Note<MidiModel::TimeType> >
190 MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note)
191 {
192         unsigned int note;
193         XMLProperty* prop;
194         unsigned int channel;
195         unsigned int time;
196         unsigned int length;
197         unsigned int velocity;
198
199         if ((prop = xml_note->property("note")) != 0) {
200                 istringstream note_str(prop->value());
201                 note_str >> note;
202         } else {
203                 warning << "note information missing note value" << endmsg;
204                 note = 127;
205         }
206
207         if ((prop = xml_note->property("channel")) != 0) {
208                 istringstream channel_str(prop->value());
209                 channel_str >> channel;
210         } else {
211                 warning << "note information missing channel" << endmsg;
212                 channel = 0;
213         }
214
215         if ((prop = xml_note->property("time")) != 0) {
216                 istringstream time_str(prop->value());
217                 time_str >> time;
218         } else {
219                 warning << "note information missing time" << endmsg;
220                 time = 0;
221         }
222
223         if ((prop = xml_note->property("length")) != 0) {
224                 istringstream length_str(prop->value());
225                 length_str >> length;
226         } else {
227                 warning << "note information missing length" << endmsg;
228                 note = 1;
229         }
230
231         if ((prop = xml_note->property("velocity")) != 0) {
232                 istringstream velocity_str(prop->value());
233                 velocity_str >> velocity;
234         } else {
235                 warning << "note information missing velocity" << endmsg;
236                 velocity = 127;
237         }
238
239         boost::shared_ptr< Evoral::Note<TimeType> > note_ptr(new Evoral::Note<TimeType>(
240                         channel, time, length, note, velocity));
241         return note_ptr;
242 }
243
244 #define ADDED_NOTES_ELEMENT "added_notes"
245 #define REMOVED_NOTES_ELEMENT "removed_notes"
246 #define DELTA_COMMAND_ELEMENT "DeltaCommand"
247
248 int
249 MidiModel::DeltaCommand::set_state(const XMLNode& delta_command)
250 {
251         if (delta_command.name() != string(DELTA_COMMAND_ELEMENT)) {
252                 return 1;
253         }
254
255         _added_notes.clear();
256         XMLNode* added_notes = delta_command.child(ADDED_NOTES_ELEMENT);
257         XMLNodeList notes = added_notes->children();
258         transform(notes.begin(), notes.end(), back_inserter(_added_notes),
259                         sigc::mem_fun(*this, &DeltaCommand::unmarshal_note));
260
261         _removed_notes.clear();
262         XMLNode* removed_notes = delta_command.child(REMOVED_NOTES_ELEMENT);
263         notes = removed_notes->children();
264         transform(notes.begin(), notes.end(), back_inserter(_removed_notes),
265                         sigc::mem_fun(*this, &DeltaCommand::unmarshal_note));
266
267         return 0;
268 }
269
270 XMLNode&
271 MidiModel::DeltaCommand::get_state()
272 {
273         XMLNode* delta_command = new XMLNode(DELTA_COMMAND_ELEMENT);
274         delta_command->add_property("midi-source", _model->midi_source()->id().to_s());
275
276         XMLNode* added_notes = delta_command->add_child(ADDED_NOTES_ELEMENT);
277         for_each(_added_notes.begin(), _added_notes.end(), sigc::compose(
278                         sigc::mem_fun(*added_notes, &XMLNode::add_child_nocopy),
279                         sigc::mem_fun(*this, &DeltaCommand::marshal_note)));
280
281         XMLNode* removed_notes = delta_command->add_child(REMOVED_NOTES_ELEMENT);
282         for_each(_removed_notes.begin(), _removed_notes.end(), sigc::compose(
283                         sigc::mem_fun(*removed_notes, &XMLNode::add_child_nocopy),
284                         sigc::mem_fun(*this, &DeltaCommand::marshal_note)));
285
286         return *delta_command;
287 }
288
289 /** Write the model to a MidiSource (i.e. save the model).
290  * This is different from manually using read to write to a source in that
291  * note off events are written regardless of the track mode.  This is so the
292  * user can switch a recorded track (with note durations from some instrument)
293  * to percussive, save, reload, then switch it back to sustained without
294  * destroying the original note durations.
295  */
296 bool
297 MidiModel::write_to(boost::shared_ptr<MidiSource> source)
298 {
299         read_lock();
300
301         const bool old_percussive = percussive();
302         set_percussive(false);
303
304         source->drop_model();
305         
306         for (Evoral::Sequence<TimeType>::const_iterator i = begin(); i != end(); ++i) {
307                 source->append_event_unlocked_beats(*i);
308         }
309                 
310         set_percussive(old_percussive);
311         
312         read_unlock();
313         set_edited(false);
314
315         return true;
316 }
317
318 XMLNode&
319 MidiModel::get_state()
320 {
321         XMLNode *node = new XMLNode("MidiModel");
322         return *node;
323 }
324