#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"
#include "streamview.h"
#include "patch_change_dialog.h"
#include "verbose_cursor.h"
-#include "ardour_ui.h"
#include "note.h"
#include "hit.h"
#include "patch_change.h"
#include "sys_ex.h"
+#include "ui_config.h"
#include "i18n.h"
using namespace std;
using Gtkmm2ext::Keyboard;
-PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
-
#define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
, _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)
, _last_event_y (0)
, _grabbed_keyboard (false)
, _entered (false)
+ , _note_entered (false)
, _mouse_changed_selection (false)
{
CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
connect_to_diskstream ();
-
- SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
-
- PublicEditor& editor (trackview.editor());
- editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
}
MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
, _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)
, _last_event_y (0)
, _grabbed_keyboard (false)
, _entered (false)
+ , _note_entered (false)
, _mouse_changed_selection (false)
{
CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
connect_to_diskstream ();
-
- SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
-
- PublicEditor& editor (trackview.editor());
- editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
}
void
, _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)
, _last_event_y (0)
, _grabbed_keyboard (false)
, _entered (false)
+ , _note_entered (false)
, _mouse_changed_selection (false)
{
init (false);
, _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)
, _last_event_y (0)
, _grabbed_keyboard (false)
, _entered (false)
+ , _note_entered (false)
, _mouse_changed_selection (false)
{
init (true);
{
PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
- NoteBase::NoteBaseDeleted.connect (note_delete_connection, MISSING_INVALIDATOR,
- boost::bind (&MidiRegionView::maybe_remove_deleted_note_from_selection, this, _1),
- gui_context());
-
if (wfd) {
Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
midi_region()->midi_source(0)->load_model(lm);
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 ());
Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
connect_to_diskstream ();
-
- SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ());
-
- PublicEditor& editor (trackview.editor());
- editor.get_selection().ClearMidiNoteSelection.connect (_clear_midi_selection_connection, invalidator (*this), boost::bind (&MidiRegionView::clear_midi_selection, this), gui_context ());
}
InstrumentInfo&
case GDK_BUTTON_RELEASE:
r = button_release (&ev->button);
- _note_player.reset();
return r;
case GDK_MOTION_NOTIFY:
set_frame_color();
if (_entered) {
- if (trackview.editor().internal_editing()) {
- // Switched in to internal editing mode while entered
- enter_internal();
- } else {
- // Switched out of internal editing mode while entered
+ if (!trackview.editor().internal_editing()) {
+ /* Switched out of internal editing mode while entered.
+ Only necessary for leave as a mouse_mode_change over a region
+ automatically triggers an enter event. */
leave_internal();
}
+ else if (trackview.editor().current_mouse_mode() == MouseContent) {
+ // hide cursor and ghost note after changing to internal edit mode
+ remove_ghost_note ();
+
+ /* XXX This is problematic as the function is executed for every region
+ and only for one region _note_entered can be true. Still it's
+ necessary as to hide the verbose cursor when we're changing from
+ draw mode to internal edit mode. These lines are the reason why
+ in some situations no verbose cursor is shown when we enter internal
+ edit mode over a note. */
+ if (!_note_entered) {
+ hide_verbose_cursor ();
+ }
+ }
}
}
void
MidiRegionView::leave_internal()
{
- trackview.editor().verbose_cursor()->hide ();
+ hide_verbose_cursor ();
remove_ghost_note ();
+ _note_entered = false;
if (_grabbed_keyboard) {
Keyboard::magic_widget_drop_focus();
}
if (_mouse_state != SelectTouchDragging) {
-
+
_pressed_button = ev->button;
_mouse_state = Pressed;
-
+
return true;
}
switch (editor.current_mouse_mode()) {
case MouseRange:
- /* no motion occured - simple click */
- clear_selection ();
+ /* no motion occurred - simple click */
+ clear_editor_note_selection ();
_mouse_changed_selection = true;
break;
case MouseContent:
case MouseTimeFX:
{
- clear_selection();
_mouse_changed_selection = true;
if (Keyboard::is_insert_note_event(ev)) {
group->canvas_to_item (event_x, event_y);
Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
-
- /* Shorten the length by 1 tick so that we can add a new note at the next
- grid snap without it overlapping this one.
- */
- beats -= Evoral::Beats::tick();
-
create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
+ } else {
+ clear_editor_note_selection ();
}
break;
case MouseDraw:
{
Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
-
- /* Shorten the length by 1 tick so that we can add a new note at the next
- grid snap without it overlapping this one.
- */
- beats -= Evoral::Beats::tick();
-
create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
-
break;
}
default:
_mouse_state = None;
break;
- case SelectRectDragging:
case AddDragging:
+ /* Only create a ghost note when we added a note, not when we were drag-selecting. */
+ create_ghost_note (ev->x, ev->y);
+ case SelectRectDragging:
editor.drags()->end_grab ((GdkEvent *) ev);
_mouse_state = None;
- create_ghost_note (ev->x, ev->y);
break;
}
if (_mouse_changed_selection) {
- trackview.editor().begin_reversible_selection_op (_("Mouse Selection Change"));
+ trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
trackview.editor().commit_reversible_selection_op ();
}
{
PublicEditor& editor = trackview.editor ();
- if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
- Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
- _mouse_state != AddDragging) {
+ if (!_note_entered) {
- create_ghost_note (ev->x, ev->y);
+ if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
+ Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
+ _mouse_state != AddDragging) {
- } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
- Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
+ create_ghost_note (ev->x, ev->y);
- update_ghost_note (ev->x, ev->y);
+ } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
+ Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
- } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
+ update_ghost_note (ev->x, ev->y);
- remove_ghost_note ();
- editor.verbose_cursor()->hide ();
+ } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
+
+ remove_ghost_note ();
+ hide_verbose_cursor ();
- } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) {
+ } else if (editor.current_mouse_mode() == MouseDraw) {
- update_ghost_note (ev->x, ev->y);
+ if (_ghost_note) {
+ update_ghost_note (ev->x, ev->y);
+ }
+ else {
+ create_ghost_note (ev->x, ev->y);
+ }
+ }
}
/* any motion immediately hides velocity text that may have been visible */
case Pressed:
if (_pressed_button == 1) {
-
+
MouseMode m = editor.current_mouse_mode();
-
+
if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
editor.drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (&editor), group, this), (GdkEvent *) ev);
_mouse_state = AddDragging;
remove_ghost_note ();
- editor.verbose_cursor()->hide ();
+ hide_verbose_cursor ();
return true;
} else if (m == MouseContent) {
editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
- clear_selection ();
+ clear_editor_note_selection ();
_mouse_changed_selection = true;
}
_mouse_state = SelectRectDragging;
case AddDragging:
editor.drags()->motion_handler ((GdkEvent *) ev, false);
break;
-
+
case SelectTouchDragging:
return false;
}
- /* we may be dragging some non-note object (eg. patch-change, sysex)
+ /* we may be dragging some non-note object (eg. patch-change, sysex)
*/
return editor.drags()->motion_handler ((GdkEvent *) ev, false);
return false;
}
- if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
- /* XXX: bit of a hack; allow PrimaryModifier scroll through so that
- it still works for zoom.
+ if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
+ Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
+ /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
+ * through so that it still works for navigation.
*/
return false;
}
- trackview.editor().verbose_cursor()->hide ();
+ hide_verbose_cursor ();
bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
- bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
+ Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
+ bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
if (ev->direction == GDK_SCROLL_UP) {
change_velocities (true, fine, false, together);
*/
bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
-
+
if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
_mouse_state = SelectTouchDragging;
return true;
} else if (ev->keyval == GDK_Escape && unmodified) {
- clear_selection();
+ clear_editor_note_selection ();
_mouse_state = None;
} else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
} 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));
if (_selection.empty()) {
return;
}
-
+
/* pick a note somewhat at random (since Selection is a set<>) to
* provide the "current" velocity for the dialog.
*/
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_editor_note_selection ();
+ note_diff_add_note (new_note, true, false);
+
+ apply_diff();
play_midi_note (new_note);
}
void
-MidiRegionView::clear_events (bool with_selection_signal)
+MidiRegionView::clear_events ()
{
- clear_selection (with_selection_signal);
+ // clear selection without signaling
+ clear_selection_internal ();
MidiGhostRegion* gr;
for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
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. */
- clear_events (false);
+ clear_events ();
if (_enable_display) {
redisplay_model();
}
}
+ 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();
{
delete _note_diff_command;
_note_diff_command = 0;
- clear_selection();
+ clear_editor_note_selection();
}
NoteBase*
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;
}
}
bool visible;
if (note_in_region_range (note, visible)) {
-
+
if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
cne->validate ();
}
} else {
-
+
if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
cne->validate ();
cne->hide ();
if (!empty_when_starting) {
for (Events::iterator i = _events.begin(); i != _events.end(); ) {
if (!(*i)->valid ()) {
-
+
for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
if (gr) {
gr->remove_note (*i);
}
}
-
+
delete *i;
i = _events.erase (i);
-
+
} else {
++i;
}
bool have_periodic_system_messages = false;
bool display_periodic_messages = true;
- if (!ARDOUR_UI::config()->get_never_display_periodic_midi()) {
+ if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
- const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
+ const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
-
+
if (mev) {
if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
have_periodic_system_messages = true;
}
}
}
-
+
if (have_periodic_system_messages) {
double zoom = trackview.editor().get_current_zoom (); // frames per pixel
-
+
/* get an approximate value for the number of samples per video frame */
-
+
double video_frame = trackview.session()->frame_rate() * (1.0/30);
-
+
/* if we are zoomed out beyond than the cutoff (i.e. more
* frames per pixel than frames per 4 video frames), don't
* show periodic sysex messages.
*/
-
+
if (zoom > (video_frame*4)) {
display_periodic_messages = false;
- }
+ }
}
} else {
display_periodic_messages = false;
for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
- const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
+ const boost::shared_ptr<const Evoral::MIDIEvent<Evoral::Beats> > mev =
boost::static_pointer_cast<const Evoral::MIDIEvent<Evoral::Beats> > (*i);
Evoral::Beats time = (*i)->time();
{
in_destructor = true;
- trackview.editor().verbose_cursor()->hide ();
-
- note_delete_connection.disconnect ();
+ hide_verbose_cursor ();
delete _list_editor;
end_write();
}
- _selection_cleared_connection.disconnect ();
-
_selection.clear();
- clear_events (false);
+ clear_events ();
delete _note_group;
delete _note_diff_command;
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);
/* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
to allow having midi notes on top of note lines and waveforms.
*/
- ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
+ ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
} else {
- ghost = new MidiGhostRegion (tv, trackview, unit_position);
+ ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
}
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
ghost->set_duration (_region->length() / samples_per_pixel);
ghosts.push_back (ghost);
- GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context());
-
return ghost;
}
void
MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
{
- if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
+ if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
return;
}
void
MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
{
- if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
+ if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
return;
}
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());
if (note->length() > 0) {
const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
- ev->set_x1 (trackview.editor().sample_to_pixel (note_end_frames));
+ ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1);
} else {
- ev->set_x1 (trackview.editor().sample_to_pixel (_region->length()));
+ ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1);
}
ev->set_y1 (y0 + std::max(1., floor(midi_stream_view()->note_height()) - 1));
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_editor_note_selection ();
note_diff_add_note (new_note, true, false);
+
apply_diff();
// last_step_edit_note = new_note;
{
return pc->time() <= time && pc->channel() == channel;
}
-
-void
+
+void
MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
{
// The earliest event not before time
}
void
-MidiRegionView::maybe_remove_deleted_note_from_selection (NoteBase* cne)
+MidiRegionView::note_deleted (NoteBase* cne)
{
if (_selection.empty()) {
return;
_selection.clear();
apply_diff ();
+ hide_verbose_cursor ();
}
void
_note_diff_command->remove (n);
apply_diff ();
- trackview.editor().verbose_cursor()->hide ();
+ hide_verbose_cursor ();
}
void
-MidiRegionView::clear_selection_except (NoteBase* ev, bool signal)
+MidiRegionView::clear_editor_note_selection ()
{
- for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
- if ((*i) != ev) {
- Selection::iterator tmp = i;
- ++tmp;
+ DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
+ PublicEditor& editor(trackview.editor());
+ editor.get_selection().clear_midi_notes();
+}
- (*i)->set_selected (false);
- (*i)->hide_velocity ();
- _selection.erase (i);
+void
+MidiRegionView::clear_selection ()
+{
+ clear_selection_internal();
+ PublicEditor& editor(trackview.editor());
+ editor.get_selection().remove(this);
+}
- i = tmp;
- } else {
- ++i;
- }
+void
+MidiRegionView::clear_selection_internal ()
+{
+ DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ (*i)->set_selected(false);
+ (*i)->hide_velocity();
}
+ _selection.clear();
- if (!ev && _entered) {
+ if (_entered) {
// Clearing selection entirely, ungrab keyboard
Keyboard::magic_widget_drop_focus();
_grabbed_keyboard = false;
}
-
- /* this does not change the status of this regionview w.r.t the editor
- selection.
- */
-
- if (signal) {
- SelectionCleared (this); /* EMIT SIGNAL */
- }
}
void
MidiRegionView::unique_select(NoteBase* ev)
{
- const bool selection_was_empty = _selection.empty();
-
- clear_selection_except (ev);
-
- /* don't bother with checking to see if we should remove this
- regionview from the editor selection, since we're about to add
- another note, and thus put/keep this regionview in the editor
- selection anyway.
- */
-
- if (!ev->selected()) {
- add_to_selection (ev);
- if (selection_was_empty && _entered) {
- // Grab keyboard for moving notes with arrow keys
- Keyboard::magic_widget_grab_focus();
- _grabbed_keyboard = true;
- }
- }
+ clear_editor_note_selection();
+ add_to_selection(ev);
}
void
MidiRegionView::select_all_notes ()
{
- clear_selection ();
+ clear_editor_note_selection ();
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
add_to_selection (*i);
void
MidiRegionView::select_range (framepos_t start, framepos_t end)
{
- clear_selection ();
+ clear_editor_note_selection ();
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
framepos_t t = source_beats_to_absolute_frames((*i)->note()->time());
uint8_t high_note = 0;
MidiModel::Notes& notes (_model->notes());
_optimization_iterator = _events.begin();
-
+
if (extend && !have_selection) {
extend = false;
}
/* scan existing selection to get note range */
-
+
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
if ((*i)->note()->note() < low_note) {
low_note = (*i)->note()->note();
high_note = (*i)->note()->note();
}
}
-
+
if (!add) {
- clear_selection ();
+ clear_editor_note_selection ();
if (!extend && (low_note == high_note) && (high_note == notenum)) {
/* only note previously selected is the one we are
MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
{
if (!add) {
- clear_selection_except (ev);
- if (!_selection.empty()) {
- PublicEditor& editor (trackview.editor());
- editor.get_selection().add (this);
- }
+ clear_editor_note_selection();
+ add_to_selection (ev);
}
if (!extend) {
(*i)->move_event(dx, dy);
}
- if (dy && !_selection.empty() && !_no_sound_notes && ARDOUR_UI::config()->get_sound_midi_notes()) {
+ if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
if (to_play.size() > 1) {
start_note_diff_command (_("move notes"));
for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
-
+
framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt;
Evoral::Beats new_time = absolute_frames_to_source_beats (new_frames);
}
/** @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*/)
{
resize_data->note = note;
// create a new SimpleRect from the note which will be the resize preview
- ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
+ ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
// calculate the colors: get the color settings
uint32_t fill_color = UINT_RGBA_CHANGE_A(
- ARDOUR_UI::config()->color ("midi note selected"),
+ UIConfiguration::instance().color ("midi note selected"),
128);
// make the resize preview notes more transparent and bright
0.85));
resize_rect->set_outline_color (NoteBase::calculate_outline (
- ARDOUR_UI::config()->color ("midi note selected")));
+ UIConfiguration::instance().color ("midi note selected")));
resize_data->resize_rect = resize_rect;
_resize_data.push_back(resize_data);
* 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;
+ bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
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, ensure_snap) - 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, ensure_snap) - 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, ensure_snap) : 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"));
+
+ /* XX why doesn't snap_pixel_to_sample() handle this properly? */
+ bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
Note* canvas_note = (*i)->note;
/* Get the new x position for this resize, which is in pixels relative
* to the region position.
*/
-
+
double current_x;
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, ensure_snap) + _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
void
MidiRegionView::note_entered(NoteBase* ev)
{
+ _note_entered = true;
+
Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
if (_mouse_state == SelectTouchDragging) {
+
note_selected (ev, true);
+
} else if (editor->current_mouse_mode() == MouseContent) {
+
+ remove_ghost_note ();
show_verbose_cursor (ev->note ());
+
} else if (editor->current_mouse_mode() == MouseDraw) {
+
+ remove_ghost_note ();
show_verbose_cursor (ev->note ());
}
}
void
MidiRegionView::note_left (NoteBase*)
{
- Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
+ _note_entered = false;
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
(*i)->hide_velocity ();
}
- editor->verbose_cursor()->hide ();
+ hide_verbose_cursor ();
}
void
{
ostringstream s;
/* XXX should get patch name if we can */
- s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
- << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
+ s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
+ << _("Program ") << ((int) p->patch()->program()) + MIDI_BP_ZERO << '\n'
<< _("Channel ") << ((int) p->patch()->channel() + 1);
show_verbose_cursor (s.str(), 10, 20);
p->item().grab_focus();
void
MidiRegionView::patch_left (PatchChange *)
{
- trackview.editor().verbose_cursor()->hide ();
+ hide_verbose_cursor ();
/* focus will transfer back via the enter-notify event sent to this
* midi region view.
*/
void
MidiRegionView::sysex_left (SysEx *)
{
- trackview.editor().verbose_cursor()->hide ();
+ hide_verbose_cursor ();
/* focus will transfer back via the enter-notify event sent to this
* midi region view.
*/
trackview.editor().internal_editing() ? "editable region" :
"midi frame base");
if (_selected) {
- return ARDOUR_UI::config()->color_mod ("selected region base", mod_name);
- } else if ((!ARDOUR_UI::config()->get_show_name_highlight() || high_enough_for_name) &&
- !ARDOUR_UI::config()->get_color_regions_using_track_color()) {
- return ARDOUR_UI::config()->color_mod ("midi frame base", mod_name);
+ return UIConfiguration::instance().color_mod ("selected region base", mod_name);
+ } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
+ !UIConfiguration::instance().get_color_regions_using_track_color()) {
+ return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
}
- return ARDOUR_UI::config()->color_mod (fill_color, mod_name);
+ return UIConfiguration::instance().color_mod (fill_color, mod_name);
}
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;
}
duration, pos, _region->position(),
pos_beats));
- clear_selection ();
+ clear_editor_note_selection ();
for (int n = 0; n < (int) times; ++n) {
boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
copied_note->set_time (pos_beats + copied_note->time() - first_time);
+ copied_note->set_id (Evoral::next_event_id());
/* make all newly added notes selected */
_note_group->canvas_to_item (x, y);
PublicEditor& editor = trackview.editor ();
-
+
framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
framecnt_t grid_frames;
framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
_ghost_note = 0;
}
+void
+MidiRegionView::hide_verbose_cursor ()
+{
+ trackview.editor().verbose_cursor()->hide ();
+ MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ if (mtv) {
+ mtv->set_note_highlight (NO_MIDI_NOTE);
+ }
+}
+
void
MidiRegionView::snap_changed ()
{
// display_sysexes();
}
-void
-MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
+std::string
+MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
{
using namespace MIDI::Name;
-
std::string name;
- MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
if (mtv) {
boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
if (device_names) {
n->channel(),
patch_key.bank(),
patch_key.program(),
- n->note());
+ note_value);
}
}
char buf[128];
snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
- (int) n->note (),
- name.empty() ? Evoral::midi_note_name (n->note()).c_str() : name.c_str(),
+ (int) note_value,
+ name.empty() ? Evoral::midi_note_name (note_value).c_str() : name.c_str(),
(int) n->channel() + 1,
(int) n->velocity());
- show_verbose_cursor(buf, 10, 20);
+ return buf;
+}
+
+void
+MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
+ uint8_t new_value) const
+{
+ MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ if (mtv) {
+ mtv->set_note_highlight (new_value);
+ }
+
+ show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
+}
+
+void
+MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
+{
+ show_verbose_cursor_for_new_note_value(n, n->note());
}
void
MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
{
PublicEditor& editor = trackview.editor ();
-
+
const Evoral::Beats grid_beats = get_grid_beats(p);
grid_frames = region_beats_to_region_frames (grid_beats);
return snap_frame_to_frame (p);
}
-/** Called when the selection has been cleared in any MidiRegionView.
- * @param rv MidiRegionView that the selection was cleared in.
- */
-void
-MidiRegionView::selection_cleared (MidiRegionView* rv)
-{
- if (rv == this) {
- return;
- }
-
- /* Clear our selection in sympathy; but don't signal the fact */
- clear_selection (false);
-}
-
-void
-MidiRegionView::note_button_release ()
-{
- _note_player.reset();
-}
-
ChannelMode
MidiRegionView::get_channel_mode () const
{