mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-15 19:16:40 +01:00
* persistent undo for MIDI edits works now
* fixed bug: dragging of notes beyond left region bounds made it disappear (unsigned int wrap around) git-svn-id: svn://localhost/ardour2/branches/3.0@3249 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
cb41314642
commit
aae8262a36
8 changed files with 82 additions and 66 deletions
|
|
@ -154,7 +154,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
|
|||
delete_mod = true;
|
||||
original_mode = trackview.editor.current_midi_edit_mode();
|
||||
trackview.editor.set_midi_edit_mode(MidiEditErase);
|
||||
start_delta_command();
|
||||
start_delta_command(_("erase notes"));
|
||||
_mouse_state = EraseTouchDragging;
|
||||
return true;
|
||||
} else if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) {
|
||||
|
|
@ -899,7 +899,7 @@ MidiRegionView::note_dropped(CanvasMidiEvent* ev, double dt, uint8_t dnote)
|
|||
highest_note_difference = highest_note_in_selection - 127;
|
||||
}
|
||||
|
||||
start_delta_command();
|
||||
start_delta_command(_("move notes"));
|
||||
|
||||
for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) {
|
||||
Selection::iterator next = i;
|
||||
|
|
@ -909,15 +909,17 @@ MidiRegionView::note_dropped(CanvasMidiEvent* ev, double dt, uint8_t dnote)
|
|||
const boost::shared_ptr<Note> copy(new Note(*(*i)->note().get()));
|
||||
|
||||
// we need to snap here again in nframes_t in order to be sample accurate
|
||||
nframes_t new_note_time = nframes_t((*i)->note()->time());
|
||||
new_note_time += nframes_t(dt);
|
||||
double new_note_time = (*i)->note()->time();
|
||||
new_note_time += dt;
|
||||
|
||||
// keep notes inside region if dragged beyond left region bound
|
||||
if(new_note_time < _region->start()) {
|
||||
new_note_time = _region->start();
|
||||
}
|
||||
|
||||
// since note time is region-absolute but snap_to_frame expects position-relative
|
||||
// time we have to coordinate transform back and forth here.
|
||||
new_note_time = snap_to_frame(new_note_time - _region->start()) + _region->start();
|
||||
|
||||
if(new_note_time < 0) {
|
||||
new_note_time = 0;
|
||||
}
|
||||
new_note_time = snap_to_frame(nframes_t(new_note_time) - _region->start()) + _region->start();
|
||||
|
||||
copy->set_time(new_note_time);
|
||||
|
||||
|
|
@ -1078,7 +1080,7 @@ MidiRegionView::update_resizing(CanvasNote::NoteEnd note_end, double x, bool rel
|
|||
void
|
||||
MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bool relative)
|
||||
{
|
||||
start_delta_command();
|
||||
start_delta_command(_("resize notes"));
|
||||
|
||||
for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
|
||||
CanvasNote *canvas_note = (*i)->canvas_note;
|
||||
|
|
@ -1127,7 +1129,7 @@ MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bo
|
|||
void
|
||||
MidiRegionView::change_velocity(uint8_t velocity, bool relative)
|
||||
{
|
||||
start_delta_command();
|
||||
start_delta_command(_("change velocity"));
|
||||
for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
|
||||
Selection::iterator next = i;
|
||||
++next;
|
||||
|
|
@ -1164,7 +1166,7 @@ void
|
|||
MidiRegionView::note_entered(ArdourCanvas::CanvasMidiEvent* ev)
|
||||
{
|
||||
if (ev->note() && _mouse_state == EraseTouchDragging) {
|
||||
start_delta_command();
|
||||
start_delta_command(_("note entered"));
|
||||
ev->selected(true);
|
||||
_delta_command->remove(ev->note());
|
||||
} else if (_mouse_state == SelectTouchDragging) {
|
||||
|
|
|
|||
|
|
@ -91,9 +91,9 @@ class MidiRegionView : public RegionView
|
|||
|
||||
/* This stuff is a bit boilerplatey ATM. Work in progress. */
|
||||
|
||||
inline void start_delta_command() {
|
||||
inline void start_delta_command(string name = "midi edit") {
|
||||
if (!_delta_command)
|
||||
_delta_command = _model->new_delta_command();
|
||||
_delta_command = _model->new_delta_command(name);
|
||||
}
|
||||
|
||||
void command_add_note(const boost::shared_ptr<ARDOUR::Note> note) {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ typedef std::pair<boost::shared_ptr<const AutomationList>, std::pair<double,doub
|
|||
*/
|
||||
class MidiModel : public boost::noncopyable, public Automatable {
|
||||
public:
|
||||
MidiModel(MidiSource& s, size_t size=0);
|
||||
MidiModel(MidiSource *s, size_t size=0);
|
||||
|
||||
// This is crap.
|
||||
void write_lock();
|
||||
|
|
@ -108,9 +108,8 @@ public:
|
|||
class DeltaCommand : public Command
|
||||
{
|
||||
public:
|
||||
DeltaCommand (MidiModel& m, const std::string& name)
|
||||
: Command(name), _model(m), _name(name) {}
|
||||
DeltaCommand (MidiModel&, const XMLNode& node);
|
||||
DeltaCommand (boost::shared_ptr<MidiModel> m, const std::string& name);
|
||||
DeltaCommand (boost::shared_ptr<MidiModel>, const XMLNode& node);
|
||||
|
||||
const std::string& name() const { return _name; }
|
||||
|
||||
|
|
@ -127,7 +126,7 @@ public:
|
|||
XMLNode &marshal_note(const boost::shared_ptr<Note> note);
|
||||
boost::shared_ptr<Note> unmarshal_note(XMLNode *xml_note);
|
||||
|
||||
MidiModel& _model;
|
||||
boost::shared_ptr<MidiModel> _model;
|
||||
const std::string _name;
|
||||
|
||||
typedef std::list< boost::shared_ptr<Note> > NoteList;
|
||||
|
|
@ -189,7 +188,8 @@ public:
|
|||
const_iterator begin() const { return const_iterator(*this, 0); }
|
||||
const const_iterator& end() const { return _end_iter; }
|
||||
|
||||
const MidiSource& midi_source() const { return _midi_source; }
|
||||
const MidiSource *midi_source() const { return _midi_source; }
|
||||
void set_midi_source(MidiSource *source) { _midi_source = source; }
|
||||
|
||||
private:
|
||||
friend class DeltaCommand;
|
||||
|
|
@ -227,7 +227,8 @@ private:
|
|||
LaterNoteEndComparator>
|
||||
ActiveNotes;
|
||||
|
||||
MidiSource& _midi_source;
|
||||
// We cannot use a boost::shared_ptr here to avoid a retain cycle
|
||||
MidiSource *_midi_source;
|
||||
};
|
||||
|
||||
} /* namespace ARDOUR */
|
||||
|
|
|
|||
|
|
@ -40,14 +40,22 @@ public:
|
|||
const Note& operator=(const Note& copy);
|
||||
|
||||
inline bool operator==(const Note& other)
|
||||
{ return time() == other.time() && note() == other.note() && duration() == other.duration(); }
|
||||
{ return time() == other.time() &&
|
||||
note() == other.note() &&
|
||||
duration() == other.duration() &&
|
||||
velocity() == other.velocity() &&
|
||||
channel() == other.channel();
|
||||
}
|
||||
|
||||
inline double time() const { return _on_event.time(); }
|
||||
inline double end_time() const { return _off_event.time(); }
|
||||
inline uint8_t note() const { return _on_event.note(); }
|
||||
inline uint8_t velocity() const { return _on_event.velocity(); }
|
||||
inline double duration() const { return _off_event.time() - _on_event.time(); }
|
||||
inline uint8_t channel() const { return _on_event.channel(); }
|
||||
inline uint8_t channel() const {
|
||||
assert(_on_event.channel() == _off_event.channel());
|
||||
return _on_event.channel();
|
||||
}
|
||||
|
||||
inline void set_time(double t) { _off_event.time() = t + duration(); _on_event.time() = t; }
|
||||
inline void set_note(uint8_t n) { _on_event.buffer()[1] = n; _off_event.buffer()[1] = n; }
|
||||
|
|
|
|||
|
|
@ -272,8 +272,8 @@ MidiModel::const_iterator::operator=(const const_iterator& other)
|
|||
|
||||
// MidiModel
|
||||
|
||||
MidiModel::MidiModel(MidiSource& s, size_t size)
|
||||
: Automatable(s.session(), "midi model")
|
||||
MidiModel::MidiModel(MidiSource *s, size_t size)
|
||||
: Automatable(s->session(), "midi model")
|
||||
, _notes(size)
|
||||
, _note_mode(Sustained)
|
||||
, _writing(false)
|
||||
|
|
@ -287,7 +287,6 @@ MidiModel::MidiModel(MidiSource& s, size_t size)
|
|||
assert( ! _end_iter._locked);
|
||||
}
|
||||
|
||||
|
||||
/** Read events in frame range \a start .. \a start+cnt into \a dst,
|
||||
* adding \a stamp_offset to each event's timestamp.
|
||||
* \return number of events written to \a dst
|
||||
|
|
@ -521,9 +520,12 @@ void
|
|||
MidiModel::remove_note_unlocked(const boost::shared_ptr<const Note> note)
|
||||
{
|
||||
//cerr << "MidiModel " << this << " remove note " << (int)note.note() << " @ " << note.time() << endl;
|
||||
Notes::iterator n = find(_notes.begin(), _notes.end(), note);
|
||||
if (n != _notes.end())
|
||||
_notes.erase(n);
|
||||
for(Notes::iterator n = _notes.begin(); n != _notes.end(); ++n) {
|
||||
if(**n == *note) {
|
||||
_notes.erase(n);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Slow! for debugging only. */
|
||||
|
|
@ -551,7 +553,7 @@ MidiModel::is_sorted() const
|
|||
MidiModel::DeltaCommand*
|
||||
MidiModel::new_delta_command(const string name)
|
||||
{
|
||||
DeltaCommand* cmd = new DeltaCommand(*this, name);
|
||||
DeltaCommand* cmd = new DeltaCommand(_midi_source->model(), name);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
|
|
@ -574,6 +576,17 @@ MidiModel::apply_command(Command* cmd)
|
|||
|
||||
// MidiEditCommand
|
||||
|
||||
MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
|
||||
: Command(name), _model(m), _name(name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const XMLNode& node)
|
||||
: _model(m)
|
||||
{
|
||||
set_state(node);
|
||||
}
|
||||
|
||||
void
|
||||
MidiModel::DeltaCommand::add(const boost::shared_ptr<Note> note)
|
||||
|
|
@ -602,28 +615,28 @@ MidiModel::DeltaCommand::operator()()
|
|||
// removed notes (or sort here), and doing a single iteration over _model
|
||||
|
||||
// Need to reset iterator to drop the read lock it holds, or we'll deadlock
|
||||
const bool reset_iter = (_model._read_iter.locked());
|
||||
const double iter_time = _model._read_iter->time();
|
||||
const bool reset_iter = (_model->_read_iter.locked());
|
||||
const double iter_time = _model->_read_iter->time();
|
||||
|
||||
if (reset_iter)
|
||||
_model._read_iter = _model.end(); // drop read lock
|
||||
_model->_read_iter = _model->end(); // drop read lock
|
||||
|
||||
assert( ! _model._read_iter.locked());
|
||||
assert( ! _model->_read_iter.locked());
|
||||
|
||||
_model.write_lock();
|
||||
_model->write_lock();
|
||||
|
||||
for (std::list< boost::shared_ptr<Note> >::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
|
||||
_model.add_note_unlocked(*i);
|
||||
_model->add_note_unlocked(*i);
|
||||
|
||||
for (std::list< boost::shared_ptr<Note> >::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
|
||||
_model.remove_note_unlocked(*i);
|
||||
_model->remove_note_unlocked(*i);
|
||||
|
||||
_model.write_unlock();
|
||||
_model->write_unlock();
|
||||
|
||||
if (reset_iter)
|
||||
_model._read_iter = const_iterator(_model, iter_time);
|
||||
_model->_read_iter = const_iterator(*_model.get(), iter_time);
|
||||
|
||||
_model.ContentsChanged(); /* EMIT SIGNAL */
|
||||
_model->ContentsChanged(); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -634,28 +647,28 @@ MidiModel::DeltaCommand::undo()
|
|||
// removed notes (or sort here), and doing a single iteration over _model
|
||||
|
||||
// Need to reset iterator to drop the read lock it holds, or we'll deadlock
|
||||
const bool reset_iter = (_model._read_iter.locked());
|
||||
const double iter_time = _model._read_iter->time();
|
||||
const bool reset_iter = (_model->_read_iter.locked());
|
||||
const double iter_time = _model->_read_iter->time();
|
||||
|
||||
if (reset_iter)
|
||||
_model._read_iter = _model.end(); // drop read lock
|
||||
_model->_read_iter = _model->end(); // drop read lock
|
||||
|
||||
assert( ! _model._read_iter.locked());
|
||||
assert( ! _model->_read_iter.locked());
|
||||
|
||||
_model.write_lock();
|
||||
_model->write_lock();
|
||||
|
||||
for (std::list< boost::shared_ptr<Note> >::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
|
||||
_model.remove_note_unlocked(*i);
|
||||
_model->remove_note_unlocked(*i);
|
||||
|
||||
for (std::list< boost::shared_ptr<Note> >::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i)
|
||||
_model.add_note_unlocked(*i);
|
||||
_model->add_note_unlocked(*i);
|
||||
|
||||
_model.write_unlock();
|
||||
_model->write_unlock();
|
||||
|
||||
if (reset_iter)
|
||||
_model._read_iter = const_iterator(_model, iter_time);
|
||||
_model->_read_iter = const_iterator(*_model.get(), iter_time);
|
||||
|
||||
_model.ContentsChanged(); /* EMIT SIGNAL */
|
||||
_model->ContentsChanged(); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
XMLNode &
|
||||
|
|
@ -708,9 +721,6 @@ MidiModel::DeltaCommand::unmarshal_note(XMLNode *xml_note)
|
|||
istringstream velocity_str(xml_note->property("velocity")->value());
|
||||
velocity_str >> velocity;
|
||||
|
||||
cerr << "creating note channel: " << channel_str.str() << " time " << time_str.str() << " duration " << duration_str.str() << " pitch " << note_str.str() << " velo " << velocity_str.str() <<endl;
|
||||
cerr << "creating note channel: " << channel << " time " << time << " duration " << duration << " pitch " << note << " velo " << velocity <<endl;
|
||||
|
||||
boost::shared_ptr<Note> note_ptr(new Note(channel, time, duration, note, velocity));
|
||||
return note_ptr;
|
||||
}
|
||||
|
|
@ -745,8 +755,7 @@ 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());
|
||||
delta_command->add_property("midi_source_name", _model.midi_source().name());
|
||||
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(),
|
||||
|
|
@ -761,11 +770,6 @@ MidiModel::DeltaCommand::get_state ()
|
|||
return *delta_command;
|
||||
}
|
||||
|
||||
MidiModel::DeltaCommand::DeltaCommand (MidiModel& m, const XMLNode& node)
|
||||
: _model(m)
|
||||
{
|
||||
set_state(node);
|
||||
}
|
||||
|
||||
bool
|
||||
MidiModel::write_to(boost::shared_ptr<MidiSource> source)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ sigc::signal<void,MidiSource *> MidiSource::MidiSourceCreated;
|
|||
MidiSource::MidiSource (Session& s, string name)
|
||||
: Source (s, name, DataType::MIDI)
|
||||
, _timeline_position(0)
|
||||
, _model(new MidiModel(*this))
|
||||
, _model(new MidiModel(this))
|
||||
, _writing (false)
|
||||
{
|
||||
_read_data_count = 0;
|
||||
|
|
@ -59,7 +59,7 @@ MidiSource::MidiSource (Session& s, string name)
|
|||
MidiSource::MidiSource (Session& s, const XMLNode& node)
|
||||
: Source (s, node)
|
||||
, _timeline_position(0)
|
||||
, _model(new MidiModel(*this))
|
||||
, _model(new MidiModel(this))
|
||||
, _writing (false)
|
||||
{
|
||||
_read_data_count = 0;
|
||||
|
|
@ -192,12 +192,13 @@ MidiSource::session_saved()
|
|||
newsrc->set_timeline_position(_timeline_position);
|
||||
_model->write_to(newsrc);
|
||||
|
||||
// cyclic dependency here, ugly :(
|
||||
newsrc->set_model(_model);
|
||||
_model.reset();
|
||||
_model->set_midi_source(newsrc.get());
|
||||
|
||||
newsrc->flush_header();
|
||||
newsrc->flush_footer();
|
||||
|
||||
|
||||
Switched.emit(newsrc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2994,7 +2994,7 @@ Session::restore_history (string snapshot_name)
|
|||
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));
|
||||
ut->add_command(new MidiModel::DeltaCommand(midi_source->model(), *n));
|
||||
} else {
|
||||
error << "FIXME: Failed to downcast MidiSource for DeltaCommand" << endmsg;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -872,7 +872,7 @@ SMFSource::load_model(bool lock, bool force_reload)
|
|||
}
|
||||
|
||||
if (! _model) {
|
||||
_model = boost::shared_ptr<MidiModel>(new MidiModel(*this));
|
||||
_model = boost::shared_ptr<MidiModel>(new MidiModel(this));
|
||||
cerr << _name << " loaded new model " << _model.get() << endl;
|
||||
} else {
|
||||
cerr << _name << " reloading model " << _model.get()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue