introduce the notion that note additions and property changes can cause the removal of other notes because of overlaps; merge Diff and Delta commands in MidiModel; fix marshalling of notes to avoid float->int conversion of length+time properties; initial implementation (not tested much so far) of different policies for how to handle note overlaps

git-svn-id: svn://localhost/ardour2/branches/3.0@7254 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2010-06-12 13:55:22 +00:00
parent fc611af4b4
commit fddb377812
8 changed files with 683 additions and 558 deletions

View File

@ -83,7 +83,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &
, _custom_device_mode(string())
, _active_notes(0)
, _note_group(new ArdourCanvas::Group(*parent))
, _delta_command(0)
, _diff_command(0)
, _ghost_note(0)
, _drag_rect (0)
@ -108,7 +107,6 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &
, _custom_device_mode(string())
, _active_notes(0)
, _note_group(new ArdourCanvas::Group(*parent))
, _delta_command(0)
, _diff_command(0)
, _ghost_note(0)
, _drag_rect (0)
@ -132,7 +130,6 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other)
, _custom_device_mode(string())
, _active_notes(0)
, _note_group(new ArdourCanvas::Group(*get_canvas_group()))
, _delta_command(0)
, _diff_command(0)
, _ghost_note(0)
, _drag_rect (0)
@ -160,7 +157,6 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<M
, _custom_device_mode(string())
, _active_notes(0)
, _note_group(new ArdourCanvas::Group(*get_canvas_group()))
, _delta_command(0)
, _diff_command(0)
, _ghost_note(0)
, _drag_rect (0)
@ -680,7 +676,7 @@ MidiRegionView::create_note_at(double x, double y, double length)
view->update_note_range(new_note->note());
MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
MidiModel::DiffCommand* cmd = _model->new_diff_command("add note");
cmd->add(new_note);
_model->apply_command(*trackview.session(), cmd);
@ -724,15 +720,6 @@ MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
}
}
void
MidiRegionView::start_delta_command(string name)
{
if (!_delta_command) {
_delta_command = _model->new_delta_command(name);
}
}
void
MidiRegionView::start_diff_command(string name)
{
@ -742,10 +729,10 @@ MidiRegionView::start_diff_command(string name)
}
void
MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
MidiRegionView::diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
{
if (_delta_command) {
_delta_command->add(note);
if (_diff_command) {
_diff_command->add(note);
}
if (selected) {
_marked_for_selection.insert(note);
@ -756,10 +743,10 @@ MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool sele
}
void
MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
MidiRegionView::diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
{
if (_delta_command && ev->note()) {
_delta_command->remove(ev->note());
if (_diff_command && ev->note()) {
_diff_command->remove(ev->note());
}
}
@ -783,85 +770,64 @@ MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
}
}
void
MidiRegionView::apply_delta()
{
if (!_delta_command) {
return;
}
// Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note());
}
_model->apply_command(*trackview.session(), _delta_command);
_delta_command = 0;
midi_view()->midi_track()->playlist_modified();
_marked_for_selection.clear();
_marked_for_velocity.clear();
}
void
MidiRegionView::apply_diff ()
{
bool add_or_remove;
if (!_diff_command) {
return;
}
if ((add_or_remove = _diff_command->adds_or_removes())) {
// Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note());
}
}
_model->apply_command(*trackview.session(), _diff_command);
_diff_command = 0;
midi_view()->midi_track()->playlist_modified();
_marked_for_velocity.clear();
}
if (add_or_remove) {
_marked_for_selection.clear();
}
void
MidiRegionView::apply_delta_as_subcommand()
{
if (!_delta_command) {
return;
}
// Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note());
}
_model->apply_command_as_subcommand(*trackview.session(), _delta_command);
_delta_command = 0;
midi_view()->midi_track()->playlist_modified();
_marked_for_selection.clear();
_marked_for_velocity.clear();
}
void
MidiRegionView::apply_diff_as_subcommand()
{
bool add_or_remove;
if (!_diff_command) {
return;
}
// Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note());
}
if ((add_or_remove = _diff_command->adds_or_removes())) {
// Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note());
}
}
_model->apply_command_as_subcommand(*trackview.session(), _diff_command);
_diff_command = 0;
midi_view()->midi_track()->playlist_modified();
_marked_for_selection.clear();
if (add_or_remove) {
_marked_for_selection.clear();
}
_marked_for_velocity.clear();
}
void
MidiRegionView::abort_command()
{
delete _delta_command;
_delta_command = 0;
delete _diff_command;
_diff_command = 0;
clear_selection();
@ -1096,7 +1062,7 @@ MidiRegionView::~MidiRegionView ()
_selection.clear();
clear_events();
delete _note_group;
delete _delta_command;
delete _diff_command;
}
void
@ -1457,9 +1423,9 @@ MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
{
boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
start_delta_command (_("step add"));
delta_add_note (new_note, true, false);
apply_delta();
start_diff_command (_("step add"));
diff_add_note (new_note, true, false);
apply_diff();
/* potentially extend region to hold new note */
@ -1627,25 +1593,25 @@ MidiRegionView::delete_selection()
return;
}
start_delta_command (_("delete selection"));
start_diff_command (_("delete selection"));
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
if ((*i)->selected()) {
_delta_command->remove((*i)->note());
_diff_command->remove((*i)->note());
}
}
_selection.clear();
apply_delta ();
apply_diff ();
}
void
MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
{
start_delta_command (_("delete note"));
_delta_command->remove (n);
apply_delta ();
start_diff_command (_("delete note"));
_diff_command->remove (n);
apply_diff ();
trackview.editor().hide_verbose_canvas_cursor ();
}
@ -2622,7 +2588,7 @@ MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
if (op != Copy) {
start_delta_command();
start_diff_command();
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
switch (op) {
@ -2630,12 +2596,12 @@ MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
break;
case Cut:
case Clear:
delta_remove_note (*i);
diff_remove_note (*i);
break;
}
}
apply_delta();
apply_diff();
}
}
@ -2662,7 +2628,7 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
return;
}
start_delta_command (_("paste"));
start_diff_command (_("paste"));
Evoral::MusicalTime beat_delta;
Evoral::MusicalTime paste_pos_beats;
@ -2678,8 +2644,6 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
for (int n = 0; n < (int) times; ++n) {
cerr << "Pasting " << mcb.notes().size() << " for the " << n+1 << "th time\n";
for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
@ -2687,7 +2651,7 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
/* make all newly added notes selected */
delta_add_note (copied_note, true);
diff_add_note (copied_note, true);
end_point = copied_note->end_time();
}
@ -2701,8 +2665,6 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
if (end_frame > region_end) {
cerr << "region end is now " << end_frame << " to extend from " << region_end << endl;
trackview.session()->begin_reversible_command (_("paste"));
_region->clear_history ();
@ -2710,8 +2672,7 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
trackview.session()->add_command (new StatefulDiffCommand (_region));
}
cerr << "region end finally at " << _region->position() + _region->length() - 1;
apply_delta ();
apply_diff ();
}
struct EventNoteTimeEarlyFirstComparator {

View File

@ -173,17 +173,13 @@ class MidiRegionView : public RegionView
void display_model(boost::shared_ptr<ARDOUR::MidiModel> model);
void start_delta_command(std::string name = "midi edit");
void delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity=false);
void delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev);
void start_diff_command(std::string name = "midi edit");
void diff_add_change(ArdourCanvas::CanvasNoteEvent* ev, ARDOUR::MidiModel::DiffCommand::Property, uint8_t val);
void diff_add_change(ArdourCanvas::CanvasNoteEvent* ev, ARDOUR::MidiModel::DiffCommand::Property, Evoral::MusicalTime val);
void diff_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity=false);
void diff_remove_note(ArdourCanvas::CanvasNoteEvent* ev);
void apply_delta();
void apply_diff();
void apply_delta_as_subcommand();
void apply_diff_as_subcommand();
void abort_command();
@ -355,7 +351,6 @@ class MidiRegionView : public RegionView
SysExes _sys_exes;
ArdourCanvas::CanvasNote** _active_notes;
ArdourCanvas::Group* _note_group;
ARDOUR::MidiModel::DeltaCommand* _delta_command;
ARDOUR::MidiModel::DiffCommand* _diff_command;
ArdourCanvas::CanvasNote* _ghost_note;
double _last_ghost_x;

View File

@ -49,53 +49,13 @@ class MidiSource;
*/
class MidiModel : public AutomatableSequence<Evoral::MusicalTime> {
public:
typedef double TimeType;
typedef Evoral::MusicalTime TimeType;
MidiModel(MidiSource* s);
NoteMode note_mode() const { return (percussive() ? Percussive : Sustained); }
void set_note_mode(NoteMode mode) { set_percussive(mode == Percussive); };
/** Add/Remove notes.
* Technically all note operations can be implemented as one of these, but
* a custom command can be more efficient.
*/
class DeltaCommand : public Command {
public:
DeltaCommand (boost::shared_ptr<MidiModel> m, const std::string& name);
DeltaCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node);
const std::string& name() const { return _name; }
void operator()();
void undo();
int set_state (const XMLNode&, int version);
XMLNode& get_state ();
void add(const boost::shared_ptr< Evoral::Note<TimeType> > note);
void remove(const boost::shared_ptr< Evoral::Note<TimeType> > note);
private:
XMLNode &marshal_note(const boost::shared_ptr< Evoral::Note<TimeType> > note);
boost::shared_ptr< Evoral::Note<TimeType> > unmarshal_note(XMLNode *xml_note);
boost::shared_ptr<MidiModel> _model;
const std::string _name;
typedef std::list< boost::shared_ptr< Evoral::Note<TimeType> > > NoteList;
NoteList _added_notes;
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 {
@ -117,18 +77,23 @@ public:
int set_state (const XMLNode&, int version);
XMLNode& get_state ();
void change (const boost::shared_ptr<Evoral::Note<TimeType> > note,
Property prop, uint8_t new_value);
void change (const boost::shared_ptr<Evoral::Note<TimeType> > note,
Property prop, TimeType new_time);
void add(const NotePtr note);
void remove(const NotePtr note);
private:
void change (const NotePtr note, Property prop, uint8_t new_value);
void change (const NotePtr note, Property prop, TimeType new_time);
bool adds_or_removes() const {
return !_added_notes.empty() || !_removed_notes.empty();
}
private:
boost::shared_ptr<MidiModel> _model;
const std::string _name;
struct NoteChange {
DiffCommand::Property property;
boost::shared_ptr< Evoral::Note<TimeType> > note;
NotePtr note;
union {
uint8_t old_value;
TimeType old_time;
@ -142,11 +107,19 @@ public:
typedef std::list<NoteChange> ChangeList;
ChangeList _changes;
typedef std::list< boost::shared_ptr< Evoral::Note<TimeType> > > NoteList;
NoteList _added_notes;
NoteList _removed_notes;
std::set<NotePtr> side_effect_removals;
XMLNode &marshal_change(const NoteChange&);
NoteChange unmarshal_change(XMLNode *xml_note);
XMLNode &marshal_note(const NotePtr note);
NotePtr unmarshal_note(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);
@ -165,12 +138,18 @@ 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> >);
boost::shared_ptr<Evoral::Note<TimeType> > find_note (NotePtr);
InsertMergePolicy insert_merge_policy () const;
void set_insert_merge_policy (InsertMergePolicy);
protected:
int resolve_overlaps_unlocked (const NotePtr, std::set<NotePtr>* removed = 0);
private:
struct WriteLockImpl : public AutomatableSequence<Evoral::MusicalTime>::WriteLockImpl {
struct WriteLockImpl : public AutomatableSequence<TimeType>::WriteLockImpl {
WriteLockImpl(Glib::Mutex::Lock* source_lock, Glib::RWLock& s, Glib::Mutex& c)
: AutomatableSequence<Evoral::MusicalTime>::WriteLockImpl(s, c)
: AutomatableSequence<TimeType>::WriteLockImpl(s, c)
, source_lock(source_lock)
{}
~WriteLockImpl() {
@ -188,6 +167,7 @@ private:
// We cannot use a boost::shared_ptr here to avoid a retain cycle
MidiSource* _midi_source;
InsertMergePolicy _insert_merge_policy;
};
} /* namespace ARDOUR */

View File

@ -86,6 +86,19 @@ namespace ARDOUR {
ARDOUR::OverlapType coverage (framepos_t sa, framepos_t ea,
framepos_t sb, framepos_t eb);
/* policies for inserting/pasting material where overlaps
might be an issue.
*/
enum InsertMergePolicy {
InsertMergeReject, // no overlaps allowed
InsertMergeRelax, // we just don't care about overlaps
InsertMergeReplace, // replace old with new
InsertMergeTruncateExisting, // shorten existing to avoid overlap
InsertMergeTruncateAddition, // shorten new to avoid overlap
InsertMergeExtend // extend new (or old) to the range of old+new
};
/** See parameter.h
* XXX: I don't think/hope these hex values matter anymore.
*/

View File

@ -19,7 +19,7 @@
*/
#define __STDC_LIMIT_MACROS 1
#include <set>
#include <iostream>
#include <algorithm>
#include <stdexcept>
@ -45,19 +45,6 @@ MidiModel::MidiModel(MidiSource* s)
{
}
/** 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
* formality, until apply_command is called and ownership is taken.
*/
MidiModel::DeltaCommand*
MidiModel::new_delta_command(const string name)
{
DeltaCommand* cmd = new DeltaCommand(_midi_source->model(), name);
return cmd;
}
/** Start a new Diff command.
*
* This has no side-effects on the model or Session, the returned command
@ -98,10 +85,15 @@ MidiModel::apply_command_as_subcommand(Session& session, Command* cmd)
set_edited(true);
}
/************** DIFF COMMAND ********************/
// DeltaCommand
#define DIFF_COMMAND_ELEMENT "DiffCommand"
#define DIFF_NOTES_ELEMENT "ChangedNotes"
#define ADDED_NOTES_ELEMENT "AddedNotes"
#define REMOVED_NOTES_ELEMENT "RemovedNotes"
#define SIDE_EFFECT_REMOVALS_ELEMENT "SideEffectRemovals"
MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
: Command(name)
, _model(m)
, _name(name)
@ -109,7 +101,7 @@ MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const std:
assert(_model);
}
MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const XMLNode& node)
MidiModel::DiffCommand::DiffCommand(boost::shared_ptr<MidiModel> m, const XMLNode& node)
: _model(m)
{
assert(_model);
@ -117,63 +109,242 @@ MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const XMLN
}
void
MidiModel::DeltaCommand::add(const boost::shared_ptr< Evoral::Note<TimeType> > note)
MidiModel::DiffCommand::add(const NotePtr note)
{
_removed_notes.remove(note);
_added_notes.push_back(note);
}
void
MidiModel::DeltaCommand::remove(const boost::shared_ptr< Evoral::Note<TimeType> > note)
MidiModel::DiffCommand::remove(const NotePtr note)
{
_added_notes.remove(note);
_removed_notes.push_back(note);
}
void
MidiModel::DeltaCommand::operator()()
MidiModel::DiffCommand::change(const NotePtr note, Property prop,
uint8_t new_value)
{
// This could be made much faster by using a priority_queue for added and
// removed notes (or sort here), and doing a single iteration over _model
NoteChange change;
MidiModel::WriteLock lock(_model->edit_lock());
switch (prop) {
case NoteNumber:
if (new_value == note->note()) {
return;
}
change.old_value = note->note();
break;
case Velocity:
if (new_value == note->velocity()) {
return;
}
change.old_value = note->velocity();
break;
case Channel:
if (new_value == note->channel()) {
return;
}
change.old_value = note->channel();
break;
for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
_model->add_note_unlocked(*i);
case StartTime:
fatal << "MidiModel::DiffCommand::change() with integer argument called for start time" << endmsg;
/*NOTREACHED*/
break;
case Length:
fatal << "MidiModel::DiffCommand::change() with integer argument called for length" << endmsg;
/*NOTREACHED*/
break;
}
for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
_model->remove_note_unlocked(*i);
change.note = note;
change.property = prop;
change.new_value = new_value;
_changes.push_back (change);
}
void
MidiModel::DiffCommand::change(const NotePtr note, Property prop,
TimeType new_time)
{
NoteChange change;
switch (prop) {
case NoteNumber:
case Channel:
case Velocity:
fatal << "MidiModel::DiffCommand::change() with time argument called for note, channel or velocity" << endmsg;
break;
case StartTime:
if (Evoral::musical_time_equal (note->time(), new_time)) {
return;
}
change.old_time = note->time();
break;
case Length:
if (Evoral::musical_time_equal (note->length(), new_time)) {
return;
}
change.old_time = note->length();
break;
}
lock.reset();
change.note = note;
change.property = prop;
change.new_time = new_time;
_changes.push_back (change);
}
void
MidiModel::DiffCommand::operator()()
{
{
MidiModel::WriteLock lock(_model->edit_lock());
for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
_model->add_note_unlocked(*i);
}
for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
_model->remove_note_unlocked(*i);
}
/* notes we modify in a way that requires remove-then-add to maintain ordering */
set<NotePtr> temporary_removals;
for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
Property prop = i->property;
switch (prop) {
case NoteNumber:
if (temporary_removals.find (i->note) == temporary_removals.end()) {
_model->remove_note_unlocked (i->note);
temporary_removals.insert (i->note);
}
i->note->set_note (i->new_value);
break;
case Velocity:
i->note->set_velocity (i->new_value);
break;
case StartTime:
if (temporary_removals.find (i->note) == temporary_removals.end()) {
_model->remove_note_unlocked (i->note);
temporary_removals.insert (i->note);
}
i->note->set_time (i->new_time);
break;
case Length:
i->note->set_length (i->new_time);
break;
case Channel:
if (temporary_removals.find (i->note) == temporary_removals.end()) {
_model->remove_note_unlocked (i->note);
temporary_removals.insert (i->note);
}
i->note->set_channel (i->new_value);
break;
}
}
for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
_model->add_note_unlocked (*i, &side_effect_removals);
}
if (!side_effect_removals.empty()) {
cerr << "SER: \n";
for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
cerr << "\t" << *i << ' ' << **i << endl;
}
}
}
_model->ContentsChanged(); /* EMIT SIGNAL */
}
void
MidiModel::DeltaCommand::undo()
MidiModel::DiffCommand::undo()
{
// This could be made much faster by using a priority_queue for added and
// removed notes (or sort here), and doing a single iteration over _model
{
MidiModel::WriteLock lock(_model->edit_lock());
for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
_model->remove_note_unlocked(*i);
}
for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
_model->add_note_unlocked(*i);
}
MidiModel::WriteLock lock(_model->edit_lock());;
/* notes we modify in a way that requires remove-then-add to maintain ordering */
set<NotePtr> temporary_removals;
for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) {
_model->remove_note_unlocked(*i);
}
for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
Property prop = i->property;
switch (prop) {
case NoteNumber:
if (temporary_removals.find (i->note) == temporary_removals.end()) {
_model->remove_note_unlocked (i->note);
temporary_removals.insert (i->note);
}
i->note->set_note (i->old_value);
break;
case Velocity:
i->note->set_velocity (i->old_value);
break;
case StartTime:
if (temporary_removals.find (i->note) == temporary_removals.end()) {
_model->remove_note_unlocked (i->note);
temporary_removals.insert (i->note);
}
i->note->set_time (i->old_time);
break;
case Length:
i->note->set_length (i->old_time);
break;
case Channel:
if (temporary_removals.find (i->note) == temporary_removals.end()) {
_model->remove_note_unlocked (i->note);
temporary_removals.insert (i->note);
}
i->note->set_channel (i->old_value);
break;
}
}
for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) {
_model->add_note_unlocked(*i);
}
for (set<NotePtr>::iterator i = temporary_removals.begin(); i != temporary_removals.end(); ++i) {
_model->add_note_unlocked (*i);
}
/* finally add back notes that were removed by the "do". we don't care
about side effects here since the model should be back to its original
state once this is done.
*/
cerr << "This undo has " << side_effect_removals.size() << " SER's\n";
for (set<NotePtr>::iterator i = side_effect_removals.begin(); i != side_effect_removals.end(); ++i) {
_model->add_note_unlocked (*i);
}
}
lock.reset();
_model->ContentsChanged(); /* EMIT SIGNAL */
}
XMLNode&
MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr< Evoral::Note<TimeType> > note)
MidiModel::DiffCommand::marshal_note(const NotePtr note)
{
XMLNode* xml_note = new XMLNode("note");
cerr << "Marshalling note: " << *note << endl;
ostringstream note_str(ios::ate);
note_str << int(note->note());
xml_note->add_property("note", note_str.str());
@ -183,11 +354,11 @@ MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr< Evoral::Note<Time
xml_note->add_property("channel", channel_str.str());
ostringstream time_str(ios::ate);
time_str << int(note->time());
time_str << note->time();
xml_note->add_property("time", time_str.str());
ostringstream length_str(ios::ate);
length_str <<(unsigned int) note->length();
length_str << note->length();
xml_note->add_property("length", length_str.str());
ostringstream velocity_str(ios::ate);
@ -197,8 +368,8 @@ MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr< Evoral::Note<Time
return *xml_note;
}
boost::shared_ptr< Evoral::Note<MidiModel::TimeType> >
MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note)
Evoral::Sequence<MidiModel::TimeType>::NotePtr
MidiModel::DiffCommand::unmarshal_note(XMLNode *xml_note)
{
unsigned int note;
XMLProperty* prop;
@ -247,259 +418,11 @@ MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note)
velocity = 127;
}
boost::shared_ptr< Evoral::Note<TimeType> > note_ptr(new Evoral::Note<TimeType>(
channel, time, length, note, velocity));
NotePtr note_ptr(new Evoral::Note<TimeType>(channel, time, length, note, velocity));
return note_ptr;
}
#define ADDED_NOTES_ELEMENT "AddedNotes"
#define REMOVED_NOTES_ELEMENT "RemovedNotes"
#define DELTA_COMMAND_ELEMENT "DeltaCommand"
int
MidiModel::DeltaCommand::set_state (const XMLNode& delta_command, int /*version*/)
{
if (delta_command.name() != string(DELTA_COMMAND_ELEMENT)) {
return 1;
}
_added_notes.clear();
XMLNode* added_notes = delta_command.child(ADDED_NOTES_ELEMENT);
if (added_notes) {
XMLNodeList notes = added_notes->children();
transform(notes.begin(), notes.end(), back_inserter(_added_notes),
boost::bind (&DeltaCommand::unmarshal_note, this, _1));
}
_removed_notes.clear();
XMLNode* removed_notes = delta_command.child(REMOVED_NOTES_ELEMENT);
if (removed_notes) {
XMLNodeList notes = removed_notes->children();
transform(notes.begin(), notes.end(), back_inserter(_removed_notes),
boost::bind (&DeltaCommand::unmarshal_note, this, _1));
}
return 0;
}
XMLNode&
MidiModel::DeltaCommand::get_state()
{
XMLNode* delta_command = new XMLNode(DELTA_COMMAND_ELEMENT);
delta_command->add_property("midi-source", _model->midi_source()->id().to_s());
XMLNode* added_notes = delta_command->add_child(ADDED_NOTES_ELEMENT);
for_each(_added_notes.begin(), _added_notes.end(),
boost::bind(
boost::bind (&XMLNode::add_child_nocopy, added_notes, _1),
boost::bind (&DeltaCommand::marshal_note, this, _1)));
XMLNode* removed_notes = delta_command->add_child(REMOVED_NOTES_ELEMENT);
for_each(_removed_notes.begin(), _removed_notes.end(),
boost::bind (
boost::bind (&XMLNode::add_child_nocopy, removed_notes, _1),
boost::bind (&DeltaCommand::marshal_note, this, _1)));
return *delta_command;
}
/************** DIFF COMMAND ********************/
#define DIFF_NOTES_ELEMENT "ChangedNotes"
#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, Stateful::loading_state_version);
}
void
MidiModel::DiffCommand::change(const boost::shared_ptr< Evoral::Note<TimeType> > note, Property prop,
uint8_t new_value)
{
NoteChange change;
switch (prop) {
case NoteNumber:
if (new_value == note->note()) {
return;
}
change.old_value = note->note();
break;
case Velocity:
if (new_value == note->velocity()) {
return;
}
change.old_value = note->velocity();
break;
case Channel:
if (new_value == note->channel()) {
return;
}
change.old_value = note->channel();
break;
case StartTime:
fatal << "MidiModel::DiffCommand::change() with integer argument called for start time" << endmsg;
/*NOTREACHED*/
break;
case Length:
fatal << "MidiModel::DiffCommand::change() with integer argument called for length" << endmsg;
/*NOTREACHED*/
break;
}
change.note = note;
change.property = prop;
change.new_value = new_value;
_changes.push_back (change);
}
void
MidiModel::DiffCommand::change(const boost::shared_ptr< Evoral::Note<TimeType> > note, Property prop,
TimeType new_time)
{
NoteChange change;
switch (prop) {
case NoteNumber:
case Channel:
case Velocity:
fatal << "MidiModel::DiffCommand::change() with time argument called for note, channel or velocity" << endmsg;
break;
case StartTime:
if (Evoral::musical_time_equal (note->time(), new_time)) {
return;
}
change.old_time = note->time();
break;
case Length:
if (Evoral::musical_time_equal (note->length(), new_time)) {
return;
}
change.old_time = note->length();
break;
}
change.note = note;
change.property = prop;
change.new_time = new_time;
_changes.push_back (change);
}
void
MidiModel::DiffCommand::operator()()
{
{
MidiModel::WriteLock lock(_model->edit_lock());
set<boost::shared_ptr<Evoral::Note<TimeType> > > removed_notes;
for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
Property prop = i->property;
switch (prop) {
case NoteNumber:
if (removed_notes.find (i->note) == removed_notes.end()) {
_model->remove_note_unlocked (i->note);
removed_notes.insert (i->note);
}
i->note->set_note (i->new_value);
break;
case Velocity:
i->note->set_velocity (i->new_value);
break;
case StartTime:
if (removed_notes.find (i->note) == removed_notes.end()) {
_model->remove_note_unlocked (i->note);
removed_notes.insert (i->note);
}
i->note->set_time (i->new_time);
break;
case Length:
i->note->set_length (i->new_time);
break;
case Channel:
if (removed_notes.find (i->note) == removed_notes.end()) {
_model->remove_note_unlocked (i->note);
removed_notes.insert (i->note);
}
i->note->set_channel (i->new_value);
break;
}
}
for (set<boost::shared_ptr<Evoral::Note<TimeType> > >::iterator i = removed_notes.begin(); i != removed_notes.end(); ++i) {
_model->add_note_unlocked (*i);
}
}
_model->ContentsChanged(); /* EMIT SIGNAL */
}
void
MidiModel::DiffCommand::undo()
{
{
MidiModel::WriteLock lock(_model->edit_lock());
set<boost::shared_ptr<Evoral::Note<TimeType> > > removed_notes;
for (ChangeList::iterator i = _changes.begin(); i != _changes.end(); ++i) {
Property prop = i->property;
switch (prop) {
case NoteNumber:
if (removed_notes.find (i->note) == removed_notes.end()) {
_model->remove_note_unlocked (i->note);
removed_notes.insert (i->note);
}
i->note->set_note (i->old_value);
break;
case Velocity:
i->note->set_velocity (i->old_value);
break;
case StartTime:
if (removed_notes.find (i->note) == removed_notes.end()) {
_model->remove_note_unlocked (i->note);
removed_notes.insert (i->note);
}
i->note->set_time (i->old_time);
break;
case Length:
i->note->set_length (i->old_time);
break;
case Channel:
if (removed_notes.find (i->note) == removed_notes.end()) {
_model->remove_note_unlocked (i->note);
removed_notes.insert (i->note);
}
i->note->set_channel (i->old_value);
break;
}
}
for (set<boost::shared_ptr<Evoral::Note<TimeType> > >::iterator i = removed_notes.begin(); i != removed_notes.end(); ++i) {
_model->add_note_unlocked (*i);
}
}
_model->ContentsChanged(); /* EMIT SIGNAL */
}
XMLNode&
MidiModel::DiffCommand::marshal_change(const NoteChange& change)
{
@ -571,6 +494,8 @@ MidiModel::DiffCommand::marshal_change(const NoteChange& change)
xml_change->add_property("velocity", velocity_str.str());
}
/* and now notes that were remove as a side-effect */
return *xml_change;
}
@ -684,7 +609,7 @@ MidiModel::DiffCommand::unmarshal_change(XMLNode *xml_change)
so go look for it ...
*/
boost::shared_ptr<Evoral::Note<TimeType> > new_note (new Evoral::Note<TimeType> (channel, time, length, note, velocity));
NotePtr new_note (new Evoral::Note<TimeType> (channel, time, length, note, velocity));
change.note = _model->find_note (new_note);
@ -704,6 +629,30 @@ MidiModel::DiffCommand::set_state(const XMLNode& diff_command, int /*version*/)
return 1;
}
/* additions */
_added_notes.clear();
XMLNode* added_notes = diff_command.child(ADDED_NOTES_ELEMENT);
if (added_notes) {
XMLNodeList notes = added_notes->children();
transform(notes.begin(), notes.end(), back_inserter(_added_notes),
boost::bind (&DiffCommand::unmarshal_note, this, _1));
}
/* removals */
_removed_notes.clear();
XMLNode* removed_notes = diff_command.child(REMOVED_NOTES_ELEMENT);
if (removed_notes) {
XMLNodeList notes = removed_notes->children();
transform(notes.begin(), notes.end(), back_inserter(_removed_notes),
boost::bind (&DiffCommand::unmarshal_note, this, _1));
}
/* changes */
_changes.clear();
XMLNode* changed_notes = diff_command.child(DIFF_NOTES_ELEMENT);
@ -715,6 +664,20 @@ MidiModel::DiffCommand::set_state(const XMLNode& diff_command, int /*version*/)
}
/* side effect removals caused by changes */
side_effect_removals.clear();
XMLNode* side_effect_notes = diff_command.child(SIDE_EFFECT_REMOVALS_ELEMENT);
if (side_effect_notes) {
XMLNodeList notes = side_effect_notes->children();
cerr << "Reconstruct DiffCommand with " << notes.size() << " SER's\n";
for (XMLNodeList::iterator n = notes.begin(); n != notes.end(); ++n) {
side_effect_removals.insert (unmarshal_note (*n));
}
}
return 0;
}
@ -730,6 +693,29 @@ MidiModel::DiffCommand::get_state ()
boost::bind (&XMLNode::add_child_nocopy, changes, _1),
boost::bind (&DiffCommand::marshal_change, this, _1)));
XMLNode* added_notes = diff_command->add_child(ADDED_NOTES_ELEMENT);
for_each(_added_notes.begin(), _added_notes.end(),
boost::bind(
boost::bind (&XMLNode::add_child_nocopy, added_notes, _1),
boost::bind (&DiffCommand::marshal_note, this, _1)));
XMLNode* removed_notes = diff_command->add_child(REMOVED_NOTES_ELEMENT);
for_each(_removed_notes.begin(), _removed_notes.end(),
boost::bind (
boost::bind (&XMLNode::add_child_nocopy, removed_notes, _1),
boost::bind (&DiffCommand::marshal_note, this, _1)));
/* if this command had side-effects, store that state too
*/
if (!side_effect_removals.empty()) {
XMLNode* side_effect_notes = diff_command->add_child(SIDE_EFFECT_REMOVALS_ELEMENT);
for_each(side_effect_removals.begin(), side_effect_removals.end(),
boost::bind (
boost::bind (&XMLNode::add_child_nocopy, side_effect_notes, _1),
boost::bind (&DiffCommand::marshal_note, this, _1)));
}
return *diff_command;
}
@ -851,8 +837,8 @@ MidiModel::get_state()
return *node;
}
boost::shared_ptr<Evoral::Note<MidiModel::TimeType> >
MidiModel::find_note (boost::shared_ptr<Evoral::Note<TimeType> > other)
Evoral::Sequence<MidiModel::TimeType>::NotePtr
MidiModel::find_note (NotePtr other)
{
Notes::iterator l = notes().lower_bound(other);
@ -869,7 +855,7 @@ MidiModel::find_note (boost::shared_ptr<Evoral::Note<TimeType> > other)
}
}
return boost::shared_ptr<Evoral::Note<TimeType> >();
return NotePtr();
}
/** Lock and invalidate the source.
@ -892,3 +878,198 @@ MidiModel::write_lock()
assert(!_midi_source->mutex().trylock());
return WriteLock(new WriteLockImpl(NULL, _lock, _control_lock));
}
int
MidiModel::resolve_overlaps_unlocked (const NotePtr note, set<NotePtr>* removed)
{
using namespace Evoral;
if (_writing || insert_merge_policy() == InsertMergeRelax) {
return 0;
}
TimeType sa = note->time();
TimeType ea = note->end_time();
const Pitches& p (pitches (note->channel()));
NotePtr search_note(new Note<TimeType>(0, 0, 0, note->note()));
set<NotePtr> to_be_deleted;
bool set_note_length = false;
bool set_note_time = false;
TimeType note_time = note->time();
TimeType note_length = note->length();
for (Pitches::const_iterator i = p.lower_bound (search_note);
i != p.end() && (*i)->note() == note->note(); ++i) {
TimeType sb = (*i)->time();
TimeType eb = (*i)->end_time();
OverlapType overlap = OverlapNone;
if ((sb > sa) && (eb <= ea)) {
overlap = OverlapInternal;
} else if ((eb >= sa) && (eb <= ea)) {
overlap = OverlapStart;
} else if ((sb > sa) && (sb <= ea)) {
overlap = OverlapEnd;
} else if ((sa >= sb) && (sa <= eb) && (ea <= eb)) {
overlap = OverlapExternal;
} else {
/* no overlap */
continue;
}
if (insert_merge_policy() == InsertMergeReject) {
return -1;
}
switch (overlap) {
case OverlapStart:
/* existing note covers start of new note */
switch (insert_merge_policy()) {
case InsertMergeReplace:
to_be_deleted.insert (*i);
break;
case InsertMergeTruncateExisting:
(*i)->set_length (note->time() - (*i)->time());
break;
case InsertMergeTruncateAddition:
set_note_time = true;
note_time = (*i)->time() + (*i)->length();
break;
case InsertMergeExtend:
(*i)->set_length (note->end_time() - (*i)->time());
return -1; /* do not add the new note */
break;
default:
/*NOTREACHED*/
/* stupid gcc */
break;
}
break;
case OverlapEnd:
/* existing note covers end of new note */
switch (insert_merge_policy()) {
case InsertMergeReplace:
to_be_deleted.insert (*i);
break;
case InsertMergeTruncateExisting:
/* resetting the start time of the existing note
is a problem because of time ordering.
*/
break;
case InsertMergeTruncateAddition:
set_note_length = true;
note_length = min (note_length, ((*i)->time() - note->time()));
break;
case InsertMergeExtend:
/* we can't reset the time of the existing note because
that will corrupt time ordering. So remove the
existing note and change the position/length
of the new note (which has not been added yet)
*/
to_be_deleted.insert (*i);
set_note_length = true;
note_length = min (note_length, (*i)->end_time() - note->time());
break;
default:
/*NOTREACHED*/
/* stupid gcc */
break;
}
break;
case OverlapExternal:
/* existing note overlaps all the new note */
switch (insert_merge_policy()) {
case InsertMergeReplace:
to_be_deleted.insert (*i);
break;
case InsertMergeTruncateExisting:
case InsertMergeTruncateAddition:
case InsertMergeExtend:
/* cannot add in this case */
return -1;
default:
/*NOTREACHED*/
/* stupid gcc */
break;
}
break;
case OverlapInternal:
/* new note fully overlaps an existing note */
switch (insert_merge_policy()) {
case InsertMergeReplace:
case InsertMergeTruncateExisting:
case InsertMergeTruncateAddition:
case InsertMergeExtend:
/* delete the existing note, the new one will cover it */
to_be_deleted.insert (*i);
break;
default:
/*NOTREACHED*/
/* stupid gcc */
break;
}
break;
default:
/*NOTREACHED*/
/* stupid gcc */
break;
}
}
for (set<NotePtr>::iterator i = to_be_deleted.begin(); i != to_be_deleted.end(); ++i) {
remove_note_unlocked (*i);
if (removed) {
removed->insert (*i);
}
}
if (set_note_time) {
note->set_time (note_time);
}
if (set_note_length) {
note->set_length (note_length);
}
return 0;
}
InsertMergePolicy
MidiModel::insert_merge_policy () const
{
char* c = getenv ("AMP");
if (!c || c[0] == 0) {
return InsertMergeReject;
}
switch (c[0]) {
case 'x':
return InsertMergeRelax;
case 'p':
return InsertMergeReplace;
case 't':
return InsertMergeTruncateExisting;
case 'a':
return InsertMergeTruncateAddition;
case 'e':
default:
return InsertMergeExtend;
}
}

View File

@ -3080,16 +3080,6 @@ Session::restore_history (string snapshot_name)
ut->add_command(c);
}
} else if (n->name() == "DeltaCommand") {
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::DeltaCommand(midi_source->model(), *n));
} else {
error << _("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 =
@ -3097,7 +3087,7 @@ Session::restore_history (string snapshot_name)
if (midi_source) {
ut->add_command(new MidiModel::DiffCommand(midi_source->model(), *n));
} else {
error << _("Failed to downcast MidiSource for DeltaCommand") << endmsg;
error << _("Failed to downcast MidiSource for DiffCommand") << endmsg;
}
} else if (n->name() == "StatefulDiffCommand") {

View File

@ -22,6 +22,7 @@
#include <vector>
#include <queue>
#include <set>
#include <list>
#include <utility>
#include <boost/shared_ptr.hpp>
#include <glibmm/thread.h>
@ -78,6 +79,9 @@ protected:
};
public:
typedef typename boost::shared_ptr<Evoral::Note<Time> > NotePtr;
typedef typename boost::shared_ptr<const Evoral::Note<Time> > constNotePtr;
typedef boost::shared_ptr<Glib::RWLock::ReaderLock> ReadLock;
typedef boost::shared_ptr<WriteLockImpl> WriteLock;
@ -133,7 +137,7 @@ public:
}
};
typedef std::multiset<boost::shared_ptr< Note<Time> >, EarlierNoteComparator> Notes;
typedef std::multiset<NotePtr, EarlierNoteComparator> Notes;
inline Notes& notes() { return _notes; }
inline const Notes& notes() const { return _notes; }
@ -173,10 +177,7 @@ public:
inline const SysExes& sysexes() const { return _sysexes; }
private:
typedef std::priority_queue< boost::shared_ptr< Note<Time> >,
std::deque< boost::shared_ptr< Note<Time> > >,
LaterNoteEndComparator >
ActiveNotes;
typedef std::priority_queue<NotePtr, std::deque<NotePtr>, LaterNoteEndComparator> ActiveNotes;
public:
/** Read iterator */
@ -231,12 +232,12 @@ public:
bool edited() const { return _edited; }
void set_edited(bool yn) { _edited = yn; }
bool overlaps (const boost::shared_ptr< Note<Time> >& ev,
const boost::shared_ptr< Note<Time> >& ignore_this_note) const;
bool contains (const boost::shared_ptr< Note<Time> >& ev) const;
bool overlaps (const NotePtr& ev,
const NotePtr& ignore_this_note) const;
bool contains (const NotePtr& ev) const;
bool add_note_unlocked(const boost::shared_ptr< Note<Time> > note);
void remove_note_unlocked(const boost::shared_ptr< const Note<Time> > note);
bool add_note_unlocked (const NotePtr note, std::set<NotePtr>* removed = 0);
void remove_note_unlocked(const constNotePtr note);
uint8_t lowest_note() const { return _lowest_note; }
uint8_t highest_note() const { return _highest_note; }
@ -247,20 +248,24 @@ protected:
bool _overlapping_pitches_accepted;
OverlapPitchResolution _overlap_pitch_resolution;
mutable Glib::RWLock _lock;
bool _writing;
virtual int resolve_overlaps_unlocked (const NotePtr, std::set<NotePtr>* removed = 0) {
return 0;
}
typedef std::multiset<NotePtr, NoteNumberComparator> Pitches;
inline Pitches& pitches(uint8_t chan) { return _pitches[chan&0xf]; }
inline const Pitches& pitches(uint8_t chan) const { return _pitches[chan&0xf]; }
private:
friend class const_iterator;
typedef std::multiset<boost::shared_ptr< Note<Time> >, NoteNumberComparator> Pitches;
inline Pitches& pitches(uint8_t chan) { return _pitches[chan&0xf]; }
inline const Pitches& pitches(uint8_t chan) const { return _pitches[chan&0xf]; }
bool overlaps_unlocked (const NotePtr& ev, const NotePtr& ignore_this_note) const;
bool contains_unlocked (const NotePtr& ev) const;
bool overlaps_unlocked (const boost::shared_ptr< Note<Time> >& ev,
const boost::shared_ptr< Note<Time> >& ignore_this_note) const;
bool contains_unlocked (const boost::shared_ptr< Note<Time> >& ev) const;
void append_note_on_unlocked (boost::shared_ptr< Note<Time> >);
void append_note_off_unlocked(boost::shared_ptr< Note<Time> >);
void append_note_on_unlocked (NotePtr);
void append_note_off_unlocked(NotePtr);
void append_control_unlocked(const Parameter& param, Time time, double value);
void append_sysex_unlocked(const MIDIEvent<Time>& ev);
@ -273,9 +278,8 @@ private:
Pitches _pitches[16]; // notes indexed by channel+pitch
SysExes _sysexes;
typedef std::multiset<boost::shared_ptr< Note<Time> >, EarlierNoteComparator> WriteNotes;
typedef std::multiset<NotePtr, EarlierNoteComparator> WriteNotes;
WriteNotes _write_notes[16];
bool _writing;
typedef std::vector< boost::shared_ptr<const ControlList> > ControlLists;
ControlLists _dirty_controls;

View File

@ -383,8 +383,8 @@ Sequence<Time>::Sequence(const TypeMap& type_map)
: _edited(false)
, _overlapping_pitches_accepted (true)
, _overlap_pitch_resolution (FirstOnFirstOff)
, _type_map(type_map)
, _writing(false)
, _type_map(type_map)
, _end_iter(*this, DBL_MAX)
, _percussive(false)
, _lowest_note(127)
@ -401,15 +401,15 @@ Sequence<Time>::Sequence(const Sequence<Time>& other)
, _edited(false)
, _overlapping_pitches_accepted (other._overlapping_pitches_accepted)
, _overlap_pitch_resolution (other._overlap_pitch_resolution)
, _type_map(other._type_map)
, _writing(false)
, _type_map(other._type_map)
, _end_iter(*this, DBL_MAX)
, _percussive(other._percussive)
, _lowest_note(other._lowest_note)
, _highest_note(other._highest_note)
{
for (typename Notes::const_iterator i = other._notes.begin(); i != other._notes.end(); ++i) {
boost::shared_ptr<Note<Time> > n (new Note<Time> (**i));
NotePtr n (new Note<Time> (**i));
_notes.insert (n);
}
@ -580,14 +580,15 @@ Sequence<Time>::end_write (bool delete_stuck)
template<typename Time>
bool
Sequence<Time>::add_note_unlocked(const boost::shared_ptr< Note<Time> > note)
Sequence<Time>::add_note_unlocked(const NotePtr note,
set<NotePtr >* removed)
{
/* This is the core method to add notes to a Sequence
*/
DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 add note %2 @ %3\n", this, (int)note->note(), note->time()));
if (!_overlapping_pitches_accepted && overlaps_unlocked (note, boost::shared_ptr<Note<Time> >())) {
if (resolve_overlaps_unlocked (note, removed)) {
return false;
}
@ -606,7 +607,7 @@ Sequence<Time>::add_note_unlocked(const boost::shared_ptr< Note<Time> > note)
template<typename Time>
void
Sequence<Time>::remove_note_unlocked(const boost::shared_ptr< const Note<Time> > note)
Sequence<Time>::remove_note_unlocked(const constNotePtr note)
{
bool erased = false;
@ -641,7 +642,7 @@ Sequence<Time>::remove_note_unlocked(const boost::shared_ptr< const Note<Time> >
Pitches& p (pitches (note->channel()));
boost::shared_ptr< Note<Time> > search_note(new Note<Time>(0, 0, 0, note->note(), 0));
NotePtr search_note(new Note<Time>(0, 0, 0, note->note(), 0));
for (typename Pitches::iterator i = p.lower_bound (search_note);
i != p.end() && (*i)->note() == note->note(); ++i) {
@ -650,74 +651,74 @@ Sequence<Time>::remove_note_unlocked(const boost::shared_ptr< const Note<Time> >
p.erase (i);
}
}
if (!erased) {
cerr << "Unable to find note to erase" << endl;
}
}
if (!erased) {
cerr << "Unable to find note to erase" << endl;
}
}
/** Append \a ev to model. NOT realtime safe.
*
* The timestamp of event is expected to be relative to
* the start of this model (t=0) and MUST be monotonically increasing
* and MUST be >= the latest event currently in the model.
*/
template<typename Time>
void
Sequence<Time>::append(const Event<Time>& event)
{
WriteLock lock(write_lock());
_edited = true;
/** Append \a ev to model. NOT realtime safe.
*
* The timestamp of event is expected to be relative to
* the start of this model (t=0) and MUST be monotonically increasing
* and MUST be >= the latest event currently in the model.
*/
template<typename Time>
const MIDIEvent<Time>& ev = (const MIDIEvent<Time>&)event;
assert(_notes.empty() || ev.time() >= (*_notes.rbegin())->time());
assert(_writing);
if (!midi_event_is_valid(ev.buffer(), ev.size())) {
cerr << "WARNING: Sequence ignoring illegal MIDI event" << endl;
return;
}
if (ev.is_note_on()) {
NotePtr note(new Note<Time>(ev.channel(), ev.time(), 0, ev.note(), ev.velocity()));
append_note_on_unlocked (note);
} else if (ev.is_note_off()) {
NotePtr note(new Note<Time>(ev.channel(), ev.time(), 0, ev.note(), ev.velocity()));
append_note_off_unlocked (note);
} else if (ev.is_sysex()) {
append_sysex_unlocked(ev);
} else if (!_type_map.type_is_midi(ev.event_type())) {
printf("WARNING: Sequence: Unknown event type %X: ", ev.event_type());
for (size_t i=0; i < ev.size(); ++i) {
printf("%X ", ev.buffer()[i]);
}
printf("\n");
} else if (ev.is_cc()) {
append_control_unlocked(
Evoral::MIDI::ContinuousController(ev.event_type(), ev.channel(), ev.cc_number()),
ev.time(), ev.cc_value());
} else if (ev.is_pgm_change()) {
append_control_unlocked(
Evoral::MIDI::ProgramChange(ev.event_type(), ev.channel()),
ev.time(), ev.pgm_number());
} else if (ev.is_pitch_bender()) {
append_control_unlocked(
Evoral::MIDI::PitchBender(ev.event_type(), ev.channel()),
ev.time(), double( (0x7F & ev.pitch_bender_msb()) << 7
| (0x7F & ev.pitch_bender_lsb()) ));
} else if (ev.is_channel_pressure()) {
append_control_unlocked(
Evoral::MIDI::ChannelPressure(ev.event_type(), ev.channel()),
ev.time(), ev.channel_pressure());
} else {
printf("WARNING: Sequence: Unknown MIDI event type %X\n", ev.type());
}
}
template<typename Time>
void
Sequence<Time>::append(const Event<Time>& event)
{
WriteLock lock(write_lock());
_edited = true;
const MIDIEvent<Time>& ev = (const MIDIEvent<Time>&)event;
assert(_notes.empty() || ev.time() >= (*_notes.rbegin())->time());
assert(_writing);
if (!midi_event_is_valid(ev.buffer(), ev.size())) {
cerr << "WARNING: Sequence ignoring illegal MIDI event" << endl;
return;
}
if (ev.is_note_on()) {
boost::shared_ptr< Note<Time> > note(new Note<Time>(ev.channel(), ev.time(), 0, ev.note(), ev.velocity()));
append_note_on_unlocked (note);
} else if (ev.is_note_off()) {
boost::shared_ptr< Note<Time> > note(new Note<Time>(ev.channel(), ev.time(), 0, ev.note(), ev.velocity()));
append_note_off_unlocked (note);
} else if (ev.is_sysex()) {
append_sysex_unlocked(ev);
} else if (!_type_map.type_is_midi(ev.event_type())) {
printf("WARNING: Sequence: Unknown event type %X: ", ev.event_type());
for (size_t i=0; i < ev.size(); ++i) {
printf("%X ", ev.buffer()[i]);
}
printf("\n");
} else if (ev.is_cc()) {
append_control_unlocked(
Evoral::MIDI::ContinuousController(ev.event_type(), ev.channel(), ev.cc_number()),
ev.time(), ev.cc_value());
} else if (ev.is_pgm_change()) {
append_control_unlocked(
Evoral::MIDI::ProgramChange(ev.event_type(), ev.channel()),
ev.time(), ev.pgm_number());
} else if (ev.is_pitch_bender()) {
append_control_unlocked(
Evoral::MIDI::PitchBender(ev.event_type(), ev.channel()),
ev.time(), double( (0x7F & ev.pitch_bender_msb()) << 7
| (0x7F & ev.pitch_bender_lsb()) ));
} else if (ev.is_channel_pressure()) {
append_control_unlocked(
Evoral::MIDI::ChannelPressure(ev.event_type(), ev.channel()),
ev.time(), ev.channel_pressure());
} else {
printf("WARNING: Sequence: Unknown MIDI event type %X\n", ev.type());
}
}
template<typename Time>
void
Sequence<Time>::append_note_on_unlocked (boost::shared_ptr< Note<Time> > note)
Sequence<Time>::append_note_on_unlocked (NotePtr note)
{
DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 on @ %4 v=%5\n", this,
(int) note->channel(), (int) note->note(),
@ -744,7 +745,7 @@ Sequence<Time>::remove_note_unlocked(const boost::shared_ptr< const Note<Time> >
template<typename Time>
void
Sequence<Time>::append_note_off_unlocked (boost::shared_ptr< Note<Time> > note)
Sequence<Time>::append_note_off_unlocked (NotePtr note)
{
DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 c=%2 note %3 on @ %4 v=%5\n",
this, (int)note->channel(),
@ -770,7 +771,7 @@ Sequence<Time>::remove_note_unlocked(const boost::shared_ptr< const Note<Time> >
/* XXX use _overlap_pitch_resolution to determine FIFO/LIFO ... */
for (typename WriteNotes::iterator n = _write_notes[note->channel()].begin(); n != _write_notes[note->channel()].end(); ++n) {
boost::shared_ptr< Note<Time> > nn = *n;
NotePtr nn = *n;
if (note->note() == nn->note() && nn->channel() == note->channel()) {
assert(note->time() >= nn->time());
@ -817,17 +818,17 @@ Sequence<Time>::remove_note_unlocked(const boost::shared_ptr< const Note<Time> >
template<typename Time>
bool
Sequence<Time>::contains (const boost::shared_ptr< Note<Time> >& note) const
Sequence<Time>::contains (const NotePtr& note) const
{
return contains_unlocked (note);
}
template<typename Time>
bool
Sequence<Time>::contains_unlocked (const boost::shared_ptr< Note<Time> >& note) const
Sequence<Time>::contains_unlocked (const NotePtr& note) const
{
const Pitches& p (pitches (note->channel()));
boost::shared_ptr< Note<Time> > search_note(new Note<Time>(0, 0, 0, note->note()));
NotePtr search_note(new Note<Time>(0, 0, 0, note->note()));
for (typename Pitches::const_iterator i = p.lower_bound (search_note);
i != p.end() && (*i)->note() == note->note(); ++i) {
@ -843,7 +844,7 @@ Sequence<Time>::remove_note_unlocked(const boost::shared_ptr< const Note<Time> >
template<typename Time>
bool
Sequence<Time>::overlaps (const boost::shared_ptr< Note<Time> >& note, const boost::shared_ptr<Note<Time> >& without) const
Sequence<Time>::overlaps (const NotePtr& note, const NotePtr& without) const
{
ReadLock lock (read_lock());
return overlaps_unlocked (note, without);
@ -851,13 +852,13 @@ Sequence<Time>::remove_note_unlocked(const boost::shared_ptr< const Note<Time> >
template<typename Time>
bool
Sequence<Time>::overlaps_unlocked (const boost::shared_ptr< Note<Time> >& note, const boost::shared_ptr<Note<Time> >& without) const
Sequence<Time>::overlaps_unlocked (const NotePtr& note, const NotePtr& without) const
{
Time sa = note->time();
Time ea = note->end_time();
const Pitches& p (pitches (note->channel()));
boost::shared_ptr< Note<Time> > search_note(new Note<Time>(0, 0, 0, note->note()));
NotePtr search_note(new Note<Time>(0, 0, 0, note->note()));
for (typename Pitches::const_iterator i = p.lower_bound (search_note);
i != p.end() && (*i)->note() == note->note(); ++i) {
@ -892,7 +893,7 @@ Sequence<Time>::remove_note_unlocked(const boost::shared_ptr< const Note<Time> >
typename Sequence<Time>::Notes::const_iterator
Sequence<Time>::note_lower_bound (Time t) const
{
boost::shared_ptr< Note<Time> > search_note(new Note<Time>(0, t, 0, 0, 0));
NotePtr search_note(new Note<Time>(0, t, 0, 0, 0));
typename Sequence<Time>::Notes::const_iterator i = _notes.lower_bound(search_note);
assert(i == _notes.end() || (*i)->time() >= t);
return i;
@ -932,7 +933,7 @@ Sequence<Time>::get_notes_by_pitch (Notes& n, NoteOperator op, uint8_t val, int
}
const Pitches& p (pitches (c));
boost::shared_ptr< Note<Time> > search_note(new Note<Time>(0, 0, 0, val, 0));
NotePtr search_note(new Note<Time>(0, 0, 0, val, 0));
typename Pitches::const_iterator i;
switch (op) {
case PitchEqual: