summaryrefslogtreecommitdiff
path: root/libs/ardour
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2009-09-06 18:11:55 +0000
committerPaul Davis <paul@linuxaudiosystems.com>2009-09-06 18:11:55 +0000
commit837bfc9af44c5b6c1eeb14e7af8d9ec62c59aac6 (patch)
treecb1fc5bf4f58290efd82e95274d33f67e828b0e5 /libs/ardour
parentc3c5c9a559f0dcf63a901f0f99f579fedf64984d (diff)
the start (only the start) of MIDI diff commands
git-svn-id: svn://localhost/ardour2/branches/3.0@5637 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/ardour')
-rw-r--r--libs/ardour/ardour/midi_model.h54
-rw-r--r--libs/ardour/enums.cc9
-rw-r--r--libs/ardour/midi_model.cc342
-rw-r--r--libs/ardour/session_state.cc10
4 files changed, 413 insertions, 2 deletions
diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h
index 967372fa9a..46d4cf794c 100644
--- a/libs/ardour/ardour/midi_model.h
+++ b/libs/ardour/ardour/midi_model.h
@@ -89,10 +89,62 @@ public:
NoteList _removed_notes;
};
+
+ /** Change note properties.
+ * More efficient than DeltaCommand and has the important property that
+ * it leaves the objects in the MidiModel (Notes) the same, thus
+ * enabling selection and other state to persist across command
+ * do/undo/redo.
+ */
+ class DiffCommand : public Command {
+ public:
+ enum Property {
+ NoteNumber,
+ Velocity,
+ StartTime,
+ Length,
+ Channel
+ };
+
+ DiffCommand (boost::shared_ptr<MidiModel> m, const std::string& name);
+ DiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node);
+
+ const std::string& name() const { return _name; }
+
+ void operator()();
+ void undo();
+
+ int set_state (const XMLNode&);
+ XMLNode& get_state ();
+
+ void change (const boost::shared_ptr<Evoral::Note<TimeType> > note,
+ Property prop, uint8_t new_value);
+
+ private:
+ boost::shared_ptr<MidiModel> _model;
+ const std::string _name;
+
+ struct NotePropertyChange {
+ DiffCommand::Property property;
+ boost::shared_ptr<Evoral::Note<TimeType> > note;
+ uint8_t old_value;
+ uint8_t new_value;
+ };
+
+ typedef std::list<NotePropertyChange> ChangeList;
+ ChangeList _changes;
+
+ XMLNode &marshal_change(const NotePropertyChange&);
+ NotePropertyChange unmarshal_change(XMLNode *xml_note);
+ };
+
MidiModel::DeltaCommand* new_delta_command(const std::string name="midi edit");
+ MidiModel::DiffCommand* new_diff_command(const std::string name="midi edit");
void apply_command(Session& session, Command* cmd);
void apply_command_as_subcommand(Session& session, Command* cmd);
+
+
bool write_to(boost::shared_ptr<MidiSource> source);
// MidiModel doesn't use the normal AutomationList serialisation code
@@ -104,6 +156,8 @@ public:
const MidiSource* midi_source() const { return _midi_source; }
void set_midi_source(MidiSource* source) { _midi_source = source; }
+
+ boost::shared_ptr<Evoral::Note<TimeType> > find_note (boost::shared_ptr<Evoral::Note<TimeType> >);
private:
friend class DeltaCommand;
diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc
index efe72ddb4b..92810534ee 100644
--- a/libs/ardour/enums.cc
+++ b/libs/ardour/enums.cc
@@ -28,6 +28,7 @@
#include "ardour/export_profile_manager.h"
#include "ardour/io.h"
#include "ardour/location.h"
+#include "ardour/midi_model.h"
#include "ardour/midi_track.h"
#include "ardour/mute_master.h"
#include "ardour/panner.h"
@@ -109,6 +110,7 @@ setup_enum_writer ()
Delivery::Role _Delivery_Role;
IO::Direction _IO_Direction;
MuteMaster::MutePoint _MuteMaster_MutePoint;
+ MidiModel::DiffCommand::Property _MidiModel_DiffCommand_Property;
#define REGISTER(e) enum_writer->register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
#define REGISTER_BITS(e) enum_writer->register_bits (typeid(e).name(), i, s); i.clear(); s.clear()
@@ -522,4 +524,11 @@ setup_enum_writer ()
REGISTER_CLASS_ENUM (IO, Input);
REGISTER_CLASS_ENUM (IO, Output);
REGISTER (_IO_Direction);
+
+ REGISTER_CLASS_ENUM (MidiModel::DiffCommand, NoteNumber);
+ REGISTER_CLASS_ENUM (MidiModel::DiffCommand, Channel);
+ REGISTER_CLASS_ENUM (MidiModel::DiffCommand, Velocity);
+ REGISTER_CLASS_ENUM (MidiModel::DiffCommand, StartTime);
+ REGISTER_CLASS_ENUM (MidiModel::DiffCommand, Length);
+ REGISTER (_MidiModel_DiffCommand_Property);
}
diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc
index da524307f6..60c8829b61 100644
--- a/libs/ardour/midi_model.cc
+++ b/libs/ardour/midi_model.cc
@@ -43,7 +43,7 @@ MidiModel::MidiModel(MidiSource* s, size_t size)
{
}
-/** Start a new command.
+/** Start a new Delta command.
*
* This has no side-effects on the model or Session, the returned command
* can be held on to for as long as the caller wishes, or discarded without
@@ -56,6 +56,19 @@ MidiModel::new_delta_command(const string name)
return cmd;
}
+/** Start a new Diff command.
+ *
+ * This has no side-effects on the model or Session, the returned command
+ * can be held on to for as long as the caller wishes, or discarded without
+ * formality, until apply_command is called and ownership is taken.
+ */
+MidiModel::DiffCommand*
+MidiModel::new_diff_command(const string name)
+{
+ DiffCommand* cmd = new DiffCommand(_midi_source->model(), name);
+ return cmd;
+}
+
/** Apply a command.
*
* Ownership of cmd is taken, it must not be deleted by the caller.
@@ -225,7 +238,7 @@ MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note)
length_str >> length;
} else {
warning << "note information missing length" << endmsg;
- note = 1;
+ length = 1;
}
if ((prop = xml_note->property("velocity")) != 0) {
@@ -286,6 +299,320 @@ MidiModel::DeltaCommand::get_state()
return *delta_command;
}
+/************** DIFF COMMAND ********************/
+
+#define DIFF_NOTES_ELEMENT "changed_notes"
+#define DIFF_COMMAND_ELEMENT "DiffCommand"
+
+MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
+ : Command(name)
+ , _model(m)
+ , _name(name)
+{
+ assert(_model);
+}
+
+MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const XMLNode& node)
+ : _model(m)
+{
+ assert(_model);
+ set_state(node);
+}
+
+void
+MidiModel::DiffCommand::change(const boost::shared_ptr< Evoral::Note<TimeType> > note, Property prop,
+ uint8_t new_value)
+{
+ NotePropertyChange change;
+
+ change.note = note;
+ change.property = prop;
+ change.new_value = new_value;
+
+ switch (prop) {
+ case NoteNumber:
+ change.old_value = note->note();
+ break;
+ case Velocity:
+ change.old_value = note->velocity();
+ break;
+ case StartTime:
+ change.old_value = note->time();
+ break;
+ case Length:
+ change.old_value = note->length();
+ break;
+ case Channel:
+ change.old_value = note->channel();
+ break;
+ }
+
+ _changes.push_back (change);
+}
+
+void
+MidiModel::DiffCommand::operator()()
+{
+ Glib::Mutex::Lock lm (_model->_midi_source->mutex());
+ _model->_midi_source->invalidate(); // release model read lock
+ _model->write_lock();
+
+ for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
+ Property prop = i->property;
+ switch (prop) {
+ case NoteNumber:
+ i->note->set_note (i->new_value);
+ break;
+ case Velocity:
+ i->note->set_velocity (i->new_value);
+ break;
+ case StartTime:
+ i->note->set_time (i->new_value);
+ break;
+ case Length:
+ i->note->set_length (i->new_value);
+ break;
+ case Channel:
+ i->note->set_channel (i->new_value);
+ break;
+ }
+ }
+
+ _model->write_unlock();
+ _model->ContentsChanged(); /* EMIT SIGNAL */
+}
+
+void
+MidiModel::DiffCommand::undo()
+{
+ Glib::Mutex::Lock lm (_model->_midi_source->mutex());
+ _model->_midi_source->invalidate(); // release model read lock
+ _model->write_lock();
+
+ for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
+ Property prop = i->property;
+ switch (prop) {
+ case NoteNumber:
+ i->note->set_note (i->old_value);
+ break;
+ case Velocity:
+ i->note->set_velocity (i->old_value);
+ break;
+ case StartTime:
+ i->note->set_time (i->old_value);
+ break;
+ case Length:
+ i->note->set_length (i->old_value);
+ break;
+ case Channel:
+ i->note->set_channel (i->old_value);
+ break;
+ }
+ }
+
+ _model->write_unlock();
+ _model->ContentsChanged(); /* EMIT SIGNAL */
+}
+
+XMLNode&
+MidiModel::DiffCommand::marshal_change(const NotePropertyChange& change)
+{
+ XMLNode* xml_change = new XMLNode("change");
+
+ /* first, the change itself */
+
+ xml_change->add_property ("property", enum_2_string (change.property));
+
+ {
+ ostringstream old_value_str (ios::ate);
+ old_value_str << (unsigned int) change.old_value;
+ xml_change->add_property ("old", old_value_str.str());
+ }
+
+ {
+ ostringstream new_value_str (ios::ate);
+ new_value_str << (unsigned int) change.old_value;
+ xml_change->add_property ("new", new_value_str.str());
+ }
+
+ /* now the rest of the note */
+
+ if (change.property != NoteNumber) {
+ ostringstream note_str(ios::ate);
+ note_str << int(change.note->note());
+ xml_change->add_property("note", note_str.str());
+ }
+
+ if (change.property != Channel) {
+ ostringstream channel_str(ios::ate);
+ channel_str << int(change.note->channel());
+ xml_change->add_property("channel", channel_str.str());
+ }
+
+ if (change.property != StartTime) {
+ ostringstream time_str(ios::ate);
+ time_str << int(change.note->time());
+ xml_change->add_property("time", time_str.str());
+ }
+
+ if (change.property != Length) {
+ ostringstream length_str(ios::ate);
+ length_str <<(unsigned int) change.note->length();
+ xml_change->add_property("length", length_str.str());
+ }
+
+ if (change.property != Velocity) {
+ ostringstream velocity_str(ios::ate);
+ velocity_str << (unsigned int) change.note->velocity();
+ xml_change->add_property("velocity", velocity_str.str());
+ }
+
+ return *xml_change;
+}
+
+MidiModel::DiffCommand::NotePropertyChange
+MidiModel::DiffCommand::unmarshal_change(XMLNode *xml_change)
+{
+ XMLProperty* prop;
+ NotePropertyChange change;
+ unsigned int note;
+ unsigned int channel;
+ unsigned int time;
+ unsigned int length;
+ unsigned int velocity;
+
+ if ((prop = xml_change->property("property")) != 0) {
+ change.property = (Property) string_2_enum (prop->value(), change.property);
+ } else {
+ fatal << "!!!" << endmsg;
+ /*NOTREACHED*/
+ }
+
+ if ((prop = xml_change->property ("old")) != 0) {
+ istringstream old_str (prop->value());
+ old_str >> change.old_value;
+ } else {
+ fatal << "!!!" << endmsg;
+ /*NOTREACHED*/
+ }
+
+ if ((prop = xml_change->property ("new")) == 0) {
+ istringstream new_str (prop->value());
+ new_str >> change.new_value;
+ } else {
+ fatal << "!!!" << endmsg;
+ /*NOTREACHED*/
+ }
+
+ if (change.property != NoteNumber) {
+ if ((prop = xml_change->property("note")) != 0) {
+ istringstream note_str(prop->value());
+ note_str >> note;
+ } else {
+ warning << "note information missing note value" << endmsg;
+ note = 127;
+ }
+ } else {
+ note = change.new_value;
+ }
+
+ if (change.property != Channel) {
+ if ((prop = xml_change->property("channel")) != 0) {
+ istringstream channel_str(prop->value());
+ channel_str >> channel;
+ } else {
+ warning << "note information missing channel" << endmsg;
+ channel = 0;
+ }
+ } else {
+ channel = change.new_value;
+ }
+
+ if (change.property != StartTime) {
+ if ((prop = xml_change->property("time")) != 0) {
+ istringstream time_str(prop->value());
+ time_str >> time;
+ } else {
+ warning << "note information missing time" << endmsg;
+ time = 0;
+ }
+ } else {
+ time = change.new_value;
+ }
+
+ if (change.property != Length) {
+ if ((prop = xml_change->property("length")) != 0) {
+ istringstream length_str(prop->value());
+ length_str >> length;
+ } else {
+ warning << "note information missing length" << endmsg;
+ length = 1;
+ }
+ } else {
+ length = change.new_value;
+ }
+
+ if (change.property != Velocity) {
+ if ((prop = xml_change->property("velocity")) != 0) {
+ istringstream velocity_str(prop->value());
+ velocity_str >> velocity;
+ } else {
+ warning << "note information missing velocity" << endmsg;
+ velocity = 127;
+ }
+ } else {
+ velocity = change.new_value;
+ }
+
+ /* we must point at the instance of the note that is actually in the model.
+ so go look for it ...
+ */
+
+ boost::shared_ptr<Evoral::Note<TimeType> > new_note (new Evoral::Note<TimeType> (channel, time, length, note, velocity));
+
+ change.note = _model->find_note (new_note);
+
+ if (!change.note) {
+ warning << "MIDI note not found in model - programmers should investigate this" << endmsg;
+ /* use the actual new note */
+ change.note = new_note;
+ }
+
+ return change;
+}
+
+int
+MidiModel::DiffCommand::set_state(const XMLNode& diff_command)
+{
+ if (diff_command.name() != string(DIFF_COMMAND_ELEMENT)) {
+ return 1;
+ }
+
+ _changes.clear();
+
+ XMLNode* changed_notes = diff_command.child(DIFF_NOTES_ELEMENT);
+ XMLNodeList notes = changed_notes->children();
+
+ transform (notes.begin(), notes.end(), back_inserter(_changes),
+ sigc::mem_fun(*this, &DiffCommand::unmarshal_change));
+
+ return 0;
+}
+
+XMLNode&
+MidiModel::DiffCommand::get_state ()
+{
+ XMLNode* diff_command = new XMLNode(DIFF_COMMAND_ELEMENT);
+ diff_command->add_property("midi-source", _model->midi_source()->id().to_s());
+
+ XMLNode* changes = diff_command->add_child(DIFF_NOTES_ELEMENT);
+ for_each(_changes.begin(), _changes.end(), sigc::compose(
+ sigc::mem_fun(*changes, &XMLNode::add_child_nocopy),
+ sigc::mem_fun(*this, &DiffCommand::marshal_change)));
+
+ return *diff_command;
+}
+
/** Write the model to a MidiSource (i.e. save the model).
* This is different from manually using read to write to a source in that
* note off events are written regardless of the track mode. This is so the
@@ -322,3 +649,14 @@ MidiModel::get_state()
return *node;
}
+boost::shared_ptr<Evoral::Note<MidiModel::TimeType> >
+MidiModel::find_note (boost::shared_ptr<Evoral::Note<TimeType> > other)
+{
+ Notes::iterator i = find (notes().begin(), notes().end(), other);
+
+ if (i == notes().end()) {
+ return boost::shared_ptr<Evoral::Note<TimeType> > ();
+ }
+
+ return *i;
+}
diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc
index c782882c0f..32a4a50cb4 100644
--- a/libs/ardour/session_state.cc
+++ b/libs/ardour/session_state.cc
@@ -2932,6 +2932,16 @@ Session::restore_history (string snapshot_name)
} else {
error << "FIXME: Failed to downcast MidiSource for DeltaCommand" << endmsg;
}
+ } else if (n->name() == "DiffCommand") {
+ PBD::ID id(n->property("midi-source")->value());
+ boost::shared_ptr<MidiSource> midi_source =
+ boost::dynamic_pointer_cast<MidiSource, Source>(source_by_id(id));
+ if(midi_source) {
+ ut->add_command(new MidiModel::DiffCommand(midi_source->model(), *n));
+ } else {
+ error << "FIXME: Failed to downcast MidiSource for DeltaCommand" << endmsg;
+ }
+
} else {
error << string_compose(_("Couldn't figure out how to make a Command out of a %1 XMLNode."), n->name()) << endmsg;
}