#include "pbd/stateful_diff_command.h"
#include "ardour/midi_model.h"
+#include "ardour/midi_playlist.h"
#include "ardour/midi_region.h"
#include "ardour/midi_source.h"
#include "ardour/midi_track.h"
, _current_range_max(0)
, _region_relative_time_converter(r->session().tempo_map(), r->position())
, _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
+ , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
, _active_notes(0)
, _note_group (new ArdourCanvas::Container (group))
, _note_diff_command (0)
, _current_range_max(0)
, _region_relative_time_converter(r->session().tempo_map(), r->position())
, _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
+ , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
, _active_notes(0)
, _note_group (new ArdourCanvas::Container (group))
, _note_diff_command (0)
, _current_range_max(0)
, _region_relative_time_converter(other.region_relative_time_converter())
, _source_relative_time_converter(other.source_relative_time_converter())
+ , _region_relative_time_converter_double(other.region_relative_time_converter_double())
, _active_notes(0)
, _note_group (new ArdourCanvas::Container (get_canvas_group()))
, _note_diff_command (0)
, _current_range_max(0)
, _region_relative_time_converter(other.region_relative_time_converter())
, _source_relative_time_converter(other.source_relative_time_converter())
+ , _region_relative_time_converter_double(other.region_relative_time_converter_double())
, _active_notes(0)
, _note_group (new ArdourCanvas::Container (get_canvas_group()))
, _note_diff_command (0)
group->raise_to_top();
- midi_view()->midi_track()->PlaybackChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
+ midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
gui_context ());
case GDK_BUTTON_RELEASE:
r = button_release (&ev->button);
- _note_player.reset();
return r;
case GDK_MOTION_NOTIFY:
break;
}
- if(_mouse_changed_selection) {
- trackview.editor().begin_reversible_selection_op (_("Mouse Selection Change"));
+ if (_mouse_changed_selection) {
+ trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
trackview.editor().commit_reversible_selection_op ();
}
delete_selection();
return true;
- } else if (ev->keyval == GDK_Tab) {
+ } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
- trackview.editor().begin_reversible_selection_op (_("Select Adjacent Note"));
+ trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
return true;
- } else if (ev->keyval == GDK_ISO_Left_Tab) {
-
- /* Shift-TAB generates ISO Left Tab, for some reason */
-
- trackview.editor().begin_reversible_selection_op (_("Select Adjacent Note"));
-
- if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
- goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
- } else {
- goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
- }
-
- trackview.editor().commit_reversible_selection_op();
-
- return true;
-
-
-
} else if (ev->keyval == GDK_Up) {
bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
view->update_note_range(new_note->note());
- trackview.editor().begin_reversible_command(_("add note"));
- MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
- cmd->add (new_note);
- _model->apply_command(*trackview.session(), cmd);
- trackview.editor().commit_reversible_command();
+ start_note_diff_command(_("add note"));
+
+ clear_selection ();
+ note_diff_add_note (new_note, true, false);
+
+ apply_diff();
play_midi_note (new_note);
}
content_connection.disconnect ();
_model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
- /* Don't signal as nobody else needs to know until selection has been altered.*/
+ /* Don't signal as nobody else needs to know until selection has been altered. */
clear_events (false);
if (_enable_display) {
}
}
+ midi_view()->midi_track()->midi_playlist()->region_edited(
+ _region, _note_diff_command);
+
if (as_subcommand) {
_model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
} else {
}
_note_diff_command = 0;
- midi_view()->midi_track()->playlist_modified();
if (add_or_remove) {
_marked_for_selection.clear();
return 0;
}
-/** This version finds any canvas note matching the supplied note.*/
+/** This version finds any canvas note matching the supplied note. */
NoteBase*
MidiRegionView::find_canvas_note (NoteType note)
{
- if (_optimization_iterator != _events.end()) {
- ++_optimization_iterator;
- }
+ Events::iterator it;
- if (_optimization_iterator != _events.end() && (*(*_optimization_iterator)->note()) == note) {
- return *_optimization_iterator;
- }
-
- for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
- if (*((*_optimization_iterator)->note()) == note) {
- return *_optimization_iterator;
+ for (it = _events.begin(); it != _events.end(); ++it) {
+ if (*((*it)->note()) == note) {
+ return *it;
}
}
if (what_changed.contains (ARDOUR::Properties::position)) {
_region_relative_time_converter.set_origin_b(_region->position());
+ _region_relative_time_converter_double.set_origin_b(_region->position());
set_duration(_region->length(), 0);
if (_enable_display) {
redisplay_model();
const double y0 = 1. + floor (midi_stream_view()->note_to_y(note->note()));
const double y1 = y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1.);
+ if (y0 < 0 || y1 >= _height) {
+ /* During DnD, the region uses the 'old/current'
+ * midi_stream_view()'s range and its position/height calculation.
+ *
+ * Ideally DnD would decouple the midi_stream_view() for the
+ * region(s) being dragged and set it to the target's range
+ * (or in case of the drop-zone, FullRange).
+ * but I don't see how this can be done without major rework.
+ *
+ * For now, just prevent visual bleeding of events in case
+ * the target-track is smaller.
+ */
+ event->hide();
+ continue;
+ }
cnote->set_y0 (y0);
cnote->set_y1 (y1);
return;
}
- _note_player = boost::shared_ptr<NotePlayer>(new NotePlayer(route_ui->midi_track()));
+ NotePlayer* player = new NotePlayer (route_ui->midi_track());
for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
- _note_player->add (*n);
+ player->add (*n);
}
- _note_player->on ();
+ player->play ();
}
bool
MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
{
+ /* This is imprecise due to all the conversion conversion involved, so only
+ hide notes if they seem to start more than one tick before the start. */
+ const framecnt_t tick_frames = Evoral::Beats::tick().to_ticks(trackview.session()->frame_rate());
const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
- bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame());
+ const bool outside = ((note_start_frames <= -tick_frames) ||
+ (note_start_frames >= _region->length()));
visible = (note->note() >= midi_stream_view()->lowest_note()) &&
(note->note() <= midi_stream_view()->highest_note());
const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
const double y = 1.5 + floor(midi_stream_view()->note_to_y(note->note())) + diamond_size * .5;
+ // see DnD note in MidiRegionView::apply_note_range() above
+ if (y <= 0 || y >= _height) {
+ ev->hide();
+ } else {
+ ev->show();
+ }
+
ev->set_position (ArdourCanvas::Duple (x, y));
ev->set_height (diamond_size);
view->update_note_range(new_note->note());
_marked_for_selection.clear ();
- clear_selection ();
start_note_diff_command (_("step add"));
+
+ clear_selection ();
note_diff_add_note (new_note, true, false);
+
apply_diff();
// last_step_edit_note = new_note;
}
/** @param x Pixel relative to the region position.
+ * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
+ * Used for inverting the snap logic with key modifiers and snap delta calculation.
* @return Snapped frame relative to the region position.
*/
framepos_t
-MidiRegionView::snap_pixel_to_sample(double x)
+MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
{
PublicEditor& editor (trackview.editor());
- return snap_frame_to_frame (editor.pixel_to_sample (x));
+ return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
}
/** @param x Pixel relative to the region position.
+ * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
* @return Snapped pixel relative to the region position.
*/
double
-MidiRegionView::snap_to_pixel(double x)
+MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
{
- return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x));
+ return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
}
double
return _region_relative_time_converter.from(frames);
}
+double
+MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
+{
+ return _region_relative_time_converter_double.from(frames);
+}
+
void
MidiRegionView::begin_resizing (bool /*at_front*/)
{
* a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
* amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
* as the \a primary note.
+ * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
+ * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
*/
void
-MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
+MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
{
bool cursor_set = false;
if (at_front) {
if (relative) {
- current_x = canvas_note->x0() + delta_x;
+ current_x = canvas_note->x0() + delta_x + snap_delta;
} else {
- current_x = primary->x0() + delta_x;
+ current_x = primary->x0() + delta_x + snap_delta;
}
} else {
if (relative) {
- current_x = canvas_note->x1() + delta_x;
+ current_x = canvas_note->x1() + delta_x + snap_delta;
} else {
- current_x = primary->x1() + delta_x;
+ current_x = primary->x1() + delta_x + snap_delta;
}
}
}
if (at_front) {
- resize_rect->set_x0 (snap_to_pixel(current_x));
+ if (with_snap) {
+ resize_rect->set_x0 (snap_to_pixel(current_x) - snap_delta);
+ } else {
+ resize_rect->set_x0 (current_x - snap_delta);
+ }
resize_rect->set_x1 (canvas_note->x1());
} else {
- resize_rect->set_x1 (snap_to_pixel(current_x));
+ if (with_snap) {
+ resize_rect->set_x1 (snap_to_pixel(current_x) - snap_delta);
+ } else {
+ resize_rect->set_x1 (current_x - snap_delta);
+ }
resize_rect->set_x0 (canvas_note->x0());
}
if (!cursor_set) {
- const double snapped_x = snap_pixel_to_sample (current_x);
+ /* Convert snap delta from pixels to beats. */
+ framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
+ double snap_delta_beats = 0.0;
+ int sign = 1;
+
+ /* negative beat offsets aren't allowed */
+ if (snap_delta_samps > 0) {
+ snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
+ } else if (snap_delta_samps < 0) {
+ snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
+ sign = -1;
+ }
+
+ const double snapped_x = (with_snap ? snap_pixel_to_sample (current_x) : trackview.editor ().pixel_to_sample (current_x));
Evoral::Beats beats = region_frames_to_region_beats (snapped_x);
Evoral::Beats len = Evoral::Beats();
if (at_front) {
if (beats < canvas_note->note()->end_time()) {
- len = canvas_note->note()->time() - beats;
+ len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
len += canvas_note->note()->length();
}
} else {
if (beats >= canvas_note->note()->time()) {
- len = beats - canvas_note->note()->time();
+ len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
}
}
* Parameters the same as for \a update_resizing().
*/
void
-MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
+MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
{
- start_note_diff_command (_("resize notes"));
+ _note_diff_command = _model->new_note_diff_command (_("resize notes"));
for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
Note* canvas_note = (*i)->note;
if (at_front) {
if (relative) {
- current_x = canvas_note->x0() + delta_x;
+ current_x = canvas_note->x0() + delta_x + snap_delta;
} else {
- current_x = primary->x0() + delta_x;
+ current_x = primary->x0() + delta_x + snap_delta;
}
} else {
if (relative) {
- current_x = canvas_note->x1() + delta_x;
+ current_x = canvas_note->x1() + delta_x + snap_delta;
} else {
- current_x = primary->x1() + delta_x;
+ current_x = primary->x1() + delta_x + snap_delta;
}
}
current_x = trackview.editor().sample_to_pixel(_region->length());
}
- /* Convert that to a frame within the source */
- current_x = snap_pixel_to_sample (current_x) + _region->start ();
+ /* Convert snap delta from pixels to beats with sign. */
+ framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
+ double snap_delta_beats = 0.0;
+ int sign = 1;
+
+ if (snap_delta_samps > 0) {
+ snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
+ } else if (snap_delta_samps < 0) {
+ snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
+ sign = -1;
+ }
+
+ /* Convert the new x position to a frame within the source */
+ framepos_t current_fr;
+ if (with_snap) {
+ current_fr = snap_pixel_to_sample (current_x) + _region->start ();
+ } else {
+ current_fr = trackview.editor().pixel_to_sample (current_x) + _region->start ();
+ }
/* and then to beats */
- const Evoral::Beats x_beats = region_frames_to_region_beats (current_x);
+ const Evoral::Beats x_beats = region_frames_to_region_beats (current_fr);
if (at_front && x_beats < canvas_note->note()->end_time()) {
- note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats);
-
- Evoral::Beats len = canvas_note->note()->time() - x_beats;
+ note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
+ Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
len += canvas_note->note()->length();
if (!!len) {
}
if (!at_front) {
- const Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
- x_beats - canvas_note->note()->time());
+ Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
+ x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
}
}
_resize_data.clear();
- apply_diff();
+ apply_diff(true);
}
void
bool
MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
{
+ bool commit = false;
// Paste notes, if available
MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
if (m != selection.midi_notes.end()) {
ctx.counts.increase_n_notes();
+ if (!(*m)->empty()) { commit = true; }
paste_internal(pos, ctx.count, ctx.times, **m);
}
typedef RouteTimeAxisView::AutomationTracks ATracks;
const ATracks& atracks = midi_view()->automation_tracks();
for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
- a->second->paste(pos, selection, ctx);
+ if (a->second->paste(pos, selection, ctx)) {
+ commit = true;
+ }
}
+ if (commit) {
+ trackview.editor().commit_reversible_command ();
+ }
return true;
}
clear_selection (false);
}
-void
-MidiRegionView::note_button_release ()
-{
- _note_player.reset();
-}
-
ChannelMode
MidiRegionView::get_channel_mode () const
{