diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2009-09-06 18:11:55 +0000 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2009-09-06 18:11:55 +0000 |
commit | 837bfc9af44c5b6c1eeb14e7af8d9ec62c59aac6 (patch) | |
tree | cb1fc5bf4f58290efd82e95274d33f67e828b0e5 /libs/ardour | |
parent | c3c5c9a559f0dcf63a901f0f99f579fedf64984d (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.h | 54 | ||||
-rw-r--r-- | libs/ardour/enums.cc | 9 | ||||
-rw-r--r-- | libs/ardour/midi_model.cc | 342 | ||||
-rw-r--r-- | libs/ardour/session_state.cc | 10 |
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; } |