#include "ardour/session.h"
#include "evoral/Parameter.hpp"
-#include "evoral/MIDIEvent.hpp"
+#include "evoral/Event.hpp"
#include "evoral/Control.hpp"
#include "evoral/midi_util.h"
#include "sys_ex.h"
#include "ui_config.h"
-#include "i18n.h"
+#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace PBD;
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,
, _step_edit_cursor_width (1.0)
, _step_edit_cursor_position (0.0)
, _channel_selection_scoped_note (0)
- , _temporary_note_group (0)
, _mouse_state(None)
, _pressed_button(0)
, _sort_needed (true)
, _last_event_y (0)
, _grabbed_keyboard (false)
, _entered (false)
- , _note_entered (false)
+ , _entered_note (0)
, _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,
, _step_edit_cursor_width (1.0)
, _step_edit_cursor_position (0.0)
, _channel_selection_scoped_note (0)
- , _temporary_note_group (0)
, _mouse_state(None)
, _pressed_button(0)
, _sort_needed (true)
, _last_event_y (0)
, _grabbed_keyboard (false)
, _entered (false)
- , _note_entered (false)
+ , _entered_note (0)
, _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
, _step_edit_cursor_width (1.0)
, _step_edit_cursor_position (0.0)
, _channel_selection_scoped_note (0)
- , _temporary_note_group (0)
, _mouse_state(None)
, _pressed_button(0)
, _sort_needed (true)
, _last_event_y (0)
, _grabbed_keyboard (false)
, _entered (false)
- , _note_entered (false)
+ , _entered_note (0)
, _mouse_changed_selection (false)
{
init (false);
, _step_edit_cursor_width (1.0)
, _step_edit_cursor_position (0.0)
, _channel_selection_scoped_note (0)
- , _temporary_note_group (0)
, _mouse_state(None)
, _pressed_button(0)
, _sort_needed (true)
, _last_event_y (0)
, _grabbed_keyboard (false)
, _entered (false)
- , _note_entered (false)
+ , _entered_note (0)
, _mouse_changed_selection (false)
{
init (true);
RegionView::init (false);
- set_height (trackview.current_height());
+ //set_height (trackview.current_height());
region_muted ();
region_sync_changed ();
region_resized (ARDOUR::bounds_change);
- region_locked ();
+ //region_locked ();
set_colors ();
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&
bool
MidiRegionView::enter_notify (GdkEventCrossing* ev)
{
- enter_internal();
+ enter_internal (ev->state);
_entered = true;
return false;
// Adjust frame colour (become more transparent for internal tools)
set_frame_color();
- // Only necessary for leave as a mouse_mode_change over a region
- // automatically triggers an enter event.
if (_entered) {
if (!trackview.editor().internal_editing()) {
- // Switched out of internal editing mode while entered
+ /* 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
- hide_verbose_cursor ();
remove_ghost_note ();
+
+ /* XXX This is problematic as the function is executed for every region
+ and only for one region _entered_note 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 (!_entered_note) {
+ hide_verbose_cursor ();
+ }
}
}
}
void
-MidiRegionView::enter_internal()
+MidiRegionView::enter_internal (uint32_t state)
{
if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
// Show ghost note under pencil
- create_ghost_note(_last_event_x, _last_event_y);
+ create_ghost_note(_last_event_x, _last_event_y, state);
}
if (!_selection.empty()) {
{
hide_verbose_cursor ();
remove_ghost_note ();
- _note_entered = false;
+ _entered_note = 0;
if (_grabbed_keyboard) {
Keyboard::magic_widget_drop_focus();
if (_mouse_state != SelectTouchDragging) {
_pressed_button = ev->button;
- _mouse_state = Pressed;
+
+ if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
+
+ if (midi_view()->note_mode() == Percussive) {
+ editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
+ } else {
+ editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
+ }
+
+ _mouse_state = AddDragging;
+ remove_ghost_note ();
+ hide_verbose_cursor ();
+ } else {
+ _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 MouseTimeFX:
{
_mouse_changed_selection = true;
-
- if (Keyboard::is_insert_note_event(ev)) {
-
- double event_x, event_y;
-
- event_x = ev->x;
- event_y = ev->y;
- group->canvas_to_item (event_x, event_y);
-
- Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
- create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
- } else {
- clear_selection ();
- }
+ clear_editor_note_selection ();
break;
}
case MouseDraw:
- {
- Evoral::Beats beats = get_grid_beats(editor.pixel_to_sample(event_x));
- create_note_at (editor.pixel_to_sample (event_x), event_y, beats, true);
- break;
- }
+ break;
+
default:
break;
}
break;
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);
+ /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
+ we don't want one when we were drag-selecting either. */
case SelectRectDragging:
editor.drags()->end_grab ((GdkEvent *) ev);
_mouse_state = None;
{
PublicEditor& editor = trackview.editor ();
- if (!_note_entered) {
+ if (!_entered_note) {
- if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
+ if (_mouse_state == AddDragging) {
+ if (_ghost_note) {
+ remove_ghost_note ();
+ }
+
+ } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
_mouse_state != AddDragging) {
- create_ghost_note (ev->x, ev->y);
+ create_ghost_note (ev->x, ev->y, ev->state);
} else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
- update_ghost_note (ev->x, ev->y);
+ update_ghost_note (ev->x, ev->y, ev->state);
} else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
} else if (editor.current_mouse_mode() == MouseDraw) {
if (_ghost_note) {
- update_ghost_note (ev->x, ev->y);
+ update_ghost_note (ev->x, ev->y, ev->state);
}
else {
- create_ghost_note (ev->x, ev->y);
+ create_ghost_note (ev->x, ev->y, ev->state);
}
}
}
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 ();
- hide_verbose_cursor ();
- return true;
- } else if (m == MouseContent) {
+ if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
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;
detectable auto-repeat is the name of the game and only sends
repeated presses, carry out key actions at key press, not release.
*/
-
bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
- _mouse_state = SelectTouchDragging;
+
+ if (_mouse_state != AddDragging) {
+ _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) {
* \param snap_t true to snap t to the grid, otherwise false.
*/
void
-MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bool snap_t)
+MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap)
{
if (length < 2 * DBL_EPSILON) {
return;
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MidiStreamView* const view = mtv->midi_view();
+ boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
- // Start of note in frames relative to region start
- if (snap_t) {
- framecnt_t grid_frames;
- t = snap_frame_to_grid_underneath (t, grid_frames);
+ if (!mr) {
+ return;
}
- const MidiModel::TimeType beat_time = region_frames_to_region_beats(
- t + _region->start());
+ // Start of note in frames relative to region start
+ const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
+ Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
const double note = view->y_to_note(y);
const uint8_t chan = mtv->get_channel_for_add();
start_note_diff_command(_("add note"));
- clear_selection ();
+ clear_editor_note_selection ();
note_diff_add_note (new_note, true, false);
apply_diff();
}
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();
{
delete _note_diff_command;
_note_diff_command = 0;
- clear_selection();
+ clear_editor_note_selection();
}
NoteBase*
/** This version finds any canvas note matching the supplied note. */
NoteBase*
-MidiRegionView::find_canvas_note (NoteType note)
+MidiRegionView::find_canvas_note (Evoral::event_id_t id)
{
Events::iterator it;
for (it = _events.begin(); it != _events.end(); ++it) {
- if (*((*it)->note()) == note) {
+ if ((*it)->note()->id() == id) {
return *it;
}
}
_optimization_iterator = _events.begin();
bool empty_when_starting = _events.empty();
+ NoteBase* cne;
for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
boost::shared_ptr<NoteType> note (*n);
- NoteBase* cne;
bool visible;
if (note_in_region_range (note, visible)) {
if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
- cne->validate ();
- update_note (cne);
-
if (visible) {
+ cne->validate ();
+ update_note (cne);
cne->show ();
} else {
cne->hide ();
cne = add_note (note, visible);
}
- set<boost::shared_ptr<NoteType> >::iterator it;
+ set<Evoral::event_id_t>::iterator it;
for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
- if (*(*it) == *note) {
+ if ((*it) == note->id()) {
add_to_selection (cne);
}
}
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 =
- 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;
- break;
- }
+ if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
+ have_periodic_system_messages = true;
+ break;
}
}
}
for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
-
- 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();
- if (mev) {
- if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) {
- if (!display_periodic_messages) {
- continue;
- }
+ if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
+ if (!display_periodic_messages) {
+ continue;
}
}
if (_active_notes) {
end_write();
}
-
- _selection_cleared_connection.disconnect ();
-
- _selection.clear();
- clear_events (false);
+ _entered_note = 0;
+ clear_events ();
delete _note_group;
delete _note_diff_command;
delete _step_edit_cursor;
- delete _temporary_note_group;
}
void
MidiRegionView::region_resized (const PropertyChange& what_changed)
{
- RegionView::region_resized(what_changed);
+ RegionView::region_resized(what_changed); // calls RegionView::set_duration()
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();
- }
+ /* reset_width dependent_items() redisplays model */
+
}
if (what_changed.contains (ARDOUR::Properties::start) ||
what_changed.contains (ARDOUR::Properties::position)) {
_source_relative_time_converter.set_origin_b (_region->position() - _region->start());
}
+ /* catch end and start trim so we can update the view*/
+ if (!what_changed.contains (ARDOUR::Properties::start) &&
+ what_changed.contains (ARDOUR::Properties::length)) {
+ enable_display (true);
+ } else if (what_changed.contains (ARDOUR::Properties::start) &&
+ what_changed.contains (ARDOUR::Properties::length)) {
+ enable_display (true);
+ }
}
void
_current_range_min = min;
_current_range_max = max;
- for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
- NoteBase* event = *i;
- boost::shared_ptr<NoteType> note (event->note());
-
- if (note->note() < _current_range_min ||
- note->note() > _current_range_max) {
- event->hide();
- } else {
- event->show();
- }
-
- if (Note* cnote = dynamic_cast<Note*>(event)) {
-
- 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);
-
- } else if (Hit* chit = dynamic_cast<Hit*>(event)) {
- update_hit (chit);
- }
- }
+ redisplay_model ();
}
GhostRegion*
ghost->add_note(*i);
}
+ ghost->set_colors ();
ghost->set_height ();
ghost->set_duration (_region->length() / samples_per_pixel);
ghosts.push_back (ghost);
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());
- const bool outside = ((note_start_frames <= -tick_frames) ||
- (note_start_frames >= _region->length()));
+ const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
+
+ /* must compare double explicitly as Beats::operator< rounds to ppqn */
+ const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
+ note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
- visible = (note->note() >= midi_stream_view()->lowest_note()) &&
- (note->note() <= midi_stream_view()->highest_note());
+ visible = (note->note() >= _current_range_min) &&
+ (note->note() <= _current_range_max);
return !outside;
}
void
MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
{
+ TempoMap& map (trackview.session()->tempo_map());
+ const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
boost::shared_ptr<NoteType> note = ev->note();
- const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
- const double y0 = 1 + floor(midi_stream_view()->note_to_y(note->note()));
- ev->set_x0 (x);
- ev->set_y0 (y0);
+ const double session_source_start = _region->quarter_note() - mr->start_beats();
+ const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
+
+ const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
+ double x1;
+ const double y0 = 1 + floor(note_to_y(note->note()));
+ double y1;
/* trim note display to not overlap the end of its region */
+ if (note->length().to_double() > 0.0) {
+ double note_end_time = note->end_time().to_double();
+
+ if (note_end_time > mr->start_beats() + mr->length_beats()) {
+ note_end_time = mr->start_beats() + mr->length_beats();
+ }
- if (note->length() > 0) {
- const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length());
- ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1);
+ const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
+
+ x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
} else {
- ev->set_x1 (std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1);
+ 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));
+ y1 = y0 + std::max(1., floor(note_height()) - 1);
+
+ ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
if (!note->length()) {
if (_active_notes && note->note() < 128) {
if (old_rect) {
/* There is an active note on this key, so we have a stuck
note. Finish the old rectangle here. */
- old_rect->set_x1 (x);
+ old_rect->set_x1 (x1);
old_rect->set_outline_all ();
}
_active_notes[note->note()] = ev;
}
// Update color in case velocity has changed
- ev->set_fill_color(ev->base_color());
- ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
+ const uint32_t base_col = ev->base_color();
+ ev->set_fill_color(base_col);
+ ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
if (update_ghost_regions) {
for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
{
boost::shared_ptr<NoteType> note = ev->note();
- const framepos_t note_start_frames = source_beats_to_region_frames(note->time());
+ const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
+ const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
+
const double x = trackview.editor().sample_to_pixel(note_start_frames);
- 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;
+ const double diamond_size = std::max(1., floor(note_height()) - 2.);
+ const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
// see DnD note in MidiRegionView::apply_note_range() above
if (y <= 0 || y >= _height) {
ev->set_height (diamond_size);
// Update color in case velocity has changed
- ev->set_fill_color(ev->base_color());
- ev->set_outline_color(ev->calculate_outline(ev->base_color(), ev->selected()));
+ const uint32_t base_col = ev->base_color();
+ ev->set_fill_color(base_col);
+ ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
if (update_ghost_regions) {
for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
if (gr) {
- gr->update_note (ev);
+ gr->update_hit (ev);
}
}
}
} else if (midi_view()->note_mode() == Percussive) {
- const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
+ const double diamond_size = std::max(1., floor(note_height()) - 2.);
Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note);
framepos_t region_end = _region->last_frame();
if (end_frame > region_end) {
- _region->set_length (end_frame - _region->position());
+ /* XX sets length in beats from audio space. make musical */
+ _region->set_length (end_frame - _region->position(), 0);
}
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
start_note_diff_command (_("step add"));
- clear_selection ();
+ clear_editor_note_selection ();
note_diff_add_note (new_note, true, false);
apply_diff();
void
MidiRegionView::note_deleted (NoteBase* cne)
{
+ if (_entered_note && cne == _entered_note) {
+ _entered_note = 0;
+ }
+
if (_selection.empty()) {
return;
}
return;
}
+ if (trackview.editor().drags()->active()) {
+ return;
+ }
+
start_note_diff_command (_("delete selection"));
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_selection.clear();
apply_diff ();
+
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());
The requested notes most likely won't exist in the view until the next model redisplay.
*/
void
-MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
+MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
{
NoteBase* cne;
- list<boost::shared_ptr<NoteType> >::iterator n;
+ list<Evoral::event_id_t>::iterator n;
for (n = notes.begin(); n != notes.end(); ++n) {
- if ((cne = find_canvas_note(*(*n))) != 0) {
+ if ((cne = find_canvas_note(*n)) != 0) {
add_to_selection (cne);
} else {
_pending_note_selection.insert(*n);
}
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) {
if (highest_note_in_selection + dnote > 127) {
highest_note_difference = highest_note_in_selection - 127;
}
+ TempoMap& map (trackview.session()->tempo_map());
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);
-
+ double const start_qn = _region->quarter_note() - midi_region()->start_beats();
+ framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt;
+ Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn);
if (new_time < 0) {
continue;
}
void
MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
{
+ TempoMap& tmap (trackview.session()->tempo_map());
bool cursor_set = false;
bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
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();
+ double snapped_x;
+ int32_t divisions = 0;
+
+ if (with_snap) {
+ snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
+ divisions = trackview.editor().get_grid_music_divisions (0);
+ } else {
+ snapped_x = trackview.editor ().pixel_to_sample (current_x);
+ }
+ const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
+ - midi_region()->beat()) + midi_region()->start_beats();
+
+ Evoral::Beats len = Evoral::Beats();
if (at_front) {
if (beats < canvas_note->note()->end_time()) {
MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
{
_note_diff_command = _model->new_note_diff_command (_("resize notes"));
+ TempoMap& tmap (trackview.session()->tempo_map());
/* XX why doesn't snap_pixel_to_sample() handle this properly? */
bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
sign = -1;
}
+ uint32_t divisions = 0;
/* 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 ();
+ current_fr = snap_pixel_to_sample (current_x, ensure_snap);
+ divisions = trackview.editor().get_grid_music_divisions (0);
} else {
- current_fr = trackview.editor().pixel_to_sample (current_x) + _region->start ();
+ current_fr = trackview.editor().pixel_to_sample (current_x);
}
/* and then to beats */
- const Evoral::Beats x_beats = region_frames_to_region_beats (current_fr);
+ const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
+ const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
+ const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
if (at_front && x_beats < canvas_note->note()->end_time()) {
note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
if (!fine) {
/* non-fine, move by 1 bar regardless of snap */
- delta = Evoral::Beats(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar());
+ delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
} else if (trackview.editor().snap_mode() == Editing::SnapOff) {
void
MidiRegionView::note_entered(NoteBase* ev)
{
- _note_entered = true;
+ _entered_note = ev;
Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
void
MidiRegionView::note_left (NoteBase*)
{
- _note_entered = false;
+ _entered_note = 0;
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
(*i)->hide_velocity ();
MidiRegionView::patch_entered (PatchChange* p)
{
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'
+ << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
<< _("Channel ") << ((int) p->patch()->channel() + 1);
show_verbose_cursor (s.str(), 10, 20);
p->item().grab_focus();
void
MidiRegionView::sysex_entered (SysEx* p)
{
- ostringstream s;
+ // ostringstream s;
// CAIROCANVAS
// need a way to extract text from p->_flag->_text
// s << p->text();
/** This method handles undo */
bool
-MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
+MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
{
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; }
+ 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) {
- if (a->second->paste(pos, selection, ctx)) {
+ if (a->second->paste(pos, selection, ctx, sub_num)) {
+ if(!commit) {
+ trackview.editor().begin_reversible_command (Operations::paste);
+ }
commit = true;
}
}
const Evoral::Beats duration = last_time - first_time;
const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
const Evoral::Beats paste_offset = snap_duration * paste_count;
- const Evoral::Beats pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
+ const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset;
Evoral::Beats end_point = Evoral::Beats();
DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
first_time,
last_time,
duration, pos, _region->position(),
- pos_beats));
+ quarter_note));
- clear_selection ();
+ clear_editor_note_selection ();
for (int n = 0; n < (int) times; ++n) {
for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
- copied_note->set_time (pos_beats + copied_note->time() - first_time);
+ copied_note->set_time (quarter_note + copied_note->time() - first_time);
copied_note->set_id (Evoral::next_event_id());
/* make all newly added notes selected */
DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
_region->clear_changes ();
- _region->set_length (end_frame - _region->position());
+ /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
+ _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
trackview.session()->add_command (new StatefulDiffCommand (_region));
}
}
void
-MidiRegionView::update_ghost_note (double x, double y)
+MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
{
x = std::max(0.0, x);
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);
+
+ const int32_t divisions = editor.get_grid_music_divisions (state);
+ const bool shift_snap = midi_view()->note_mode() != Percussive;
+ const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap);
+
+ /* prevent Percussive mode from displaying a ghost hit at region end */
+ if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
+ _ghost_note->hide();
+ hide_verbose_cursor ();
+ return;
+ }
+
+ /* ghost note may have been snapped before region */
+ if (_ghost_note && snapped_beats.to_double() < 0.0) {
+ _ghost_note->hide();
+ return;
+
+ } else if (_ghost_note) {
+ _ghost_note->show();
+ }
/* calculate time in beats relative to start of source */
- const Evoral::Beats length = get_grid_beats(unsnapped_frame);
- const Evoral::Beats time = std::max(
- Evoral::Beats(),
- absolute_frames_to_source_beats (f + _region->position ()));
+ const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
- _ghost_note->note()->set_time (time);
+ _ghost_note->note()->set_time (snapped_beats);
_ghost_note->note()->set_length (length);
- _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
+ _ghost_note->note()->set_note (y_to_note (y));
_ghost_note->note()->set_channel (mtv->get_channel_for_add ());
- _ghost_note->note()->set_velocity (get_velocity_for_add (time));
-
+ _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
/* the ghost note does not appear in ghost regions, so pass false in here */
update_note (_ghost_note, false);
}
void
-MidiRegionView::create_ghost_note (double x, double y)
+MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
{
remove_ghost_note ();
}
_ghost_note->set_ignore_events (true);
_ghost_note->set_outline_color (0x000000aa);
- update_ghost_note (x, y);
+ update_ghost_note (x, y, state);
_ghost_note->show ();
show_verbose_cursor (_ghost_note->note ());
return;
}
- create_ghost_note (_last_ghost_x, _last_ghost_y);
+ create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
}
void
{
/* XXX: This is dead code. What was it for? */
- double note = midi_stream_view()->y_to_note(y);
+ double note = y_to_note(y);
Events e;
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MidiRegionView::enable_display (bool yn)
{
RegionView::enable_display (yn);
- if (yn) {
- redisplay_model ();
- }
}
void
_step_edit_cursor_width = beats;
if (_step_edit_cursor) {
- _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (region_beats_to_region_frames (beats)));
+ _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
+ region_beats_to_region_frames (_step_edit_cursor_position + beats)
+ - region_beats_to_region_frames (_step_edit_cursor_position)));
}
}
framepos_t back = max_framepos;
for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
- Evoral::MIDIEvent<MidiBuffer::TimeType> const ev (*i, false);
+ const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
if (ev.is_channel_event()) {
if (get_channel_mode() == FilterChannels) {
void
MidiRegionView::trim_front_starting ()
{
- /* Reparent the note group to the region view's parent, so that it doesn't change
- when the region view is trimmed.
+ /* We used to eparent the note group to the region view's parent, so that it didn't change.
+ now we update it.
*/
- _temporary_note_group = new ArdourCanvas::Container (group->parent ());
- _temporary_note_group->move (group->position ());
- _note_group->reparent (_temporary_note_group);
}
void
MidiRegionView::trim_front_ending ()
{
- _note_group->reparent (group);
- delete _temporary_note_group;
- _temporary_note_group = 0;
-
if (_region->start() < 0) {
/* Trim drag made start time -ve; fix this */
midi_region()->fix_negative_start ();
// 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* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
n->channel(),
patch_key.bank(),
patch_key.program(),
- n->note());
+ note_value);
}
- mtv->set_note_highlight (n->note());
}
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() ? ParameterDescriptor::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
}
/** @param p A session framepos.
- * @param grid_frames Filled in with the number of frames that a grid interval is at p.
- * @return p snapped to the grid subdivision underneath it.
+ * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
+ * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
+ * @return beat duration of p snapped to the grid subdivision underneath it.
*/
-framepos_t
-MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const
+Evoral::Beats
+MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
{
- PublicEditor& editor = trackview.editor ();
+ TempoMap& map (trackview.session()->tempo_map());
+ double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
- const Evoral::Beats grid_beats = get_grid_beats(p);
-
- grid_frames = region_beats_to_region_frames (grid_beats);
-
- /* Hack so that we always snap to the note that we are over, instead of snapping
- to the next one if we're more than halfway through the one we're over.
- */
- if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) {
- p -= grid_frames / 2;
- }
-
- 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;
+ if (divisions != 0 && shift_snap) {
+ const double qaf = map.quarter_note_at_frame (p + _region->position());
+ /* Hack so that we always snap to the note that we are over, instead of snapping
+ to the next one if we're more than halfway through the one we're over.
+ */
+ const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
+ const double rem = eqaf - qaf;
+ if (rem >= 0.0) {
+ eqaf -= grid_beats.to_double();
+ }
}
+ const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
- /* Clear our selection in sympathy; but don't signal the fact */
- clear_selection (false);
+ return Evoral::Beats (eqaf - session_start_off);
}
ChannelMode
{
PublicEditor& editor = trackview.editor();
bool success = false;
- Evoral::Beats beats = editor.get_grid_type_as_beats(success, pos);
+ Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
if (!success) {
beats = Evoral::Beats(1);
}
return beats;
}
+uint8_t
+MidiRegionView::y_to_note (double y) const
+{
+ int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
+ + _current_range_min;
+
+ if (n < 0) {
+ return 0;
+ } else if (n > 127) {
+ return 127;
+ }
+
+ /* min due to rounding and/or off-by-one errors */
+ return min ((uint8_t) n, _current_range_max);
+}
+
+double
+MidiRegionView::note_to_y(uint8_t note) const
+{
+ return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
+}