#include <sigc++/signal.h>
+#include "midi++/midnam_patch.h"
+
#include "pbd/memento_command.h"
#include "pbd/stateful_diff_command.h"
#include "ardour/midi_model.h"
-#include "ardour/midi_patch_manager.h"
#include "ardour/midi_region.h"
#include "ardour/midi_source.h"
#include "ardour/midi_track.h"
#include "ardour/session.h"
#include "evoral/Parameter.hpp"
-#include "evoral/MIDIParameters.hpp"
#include "evoral/MIDIEvent.hpp"
#include "evoral/Control.hpp"
#include "evoral/midi_util.h"
#include "midi_velocity_dialog.h"
#include "mouse_cursors.h"
#include "note_player.h"
+#include "paste_context.h"
#include "public_editor.h"
#include "route_time_axis.h"
#include "rgb_macros.h"
using namespace ARDOUR;
using namespace PBD;
using namespace Editing;
+using namespace std;
using Gtkmm2ext::Keyboard;
PBD::Signal1<void, MidiRegionView *> MidiRegionView::SelectionCleared;
, _optimization_iterator (_events.end())
, _list_editor (0)
, _no_sound_notes (false)
+ , _last_display_zoom (0)
, _last_event_x (0)
, _last_event_y (0)
- , pre_enter_cursor (0)
- , pre_press_cursor (0)
- , pre_note_enter_cursor (0)
- , _note_player (0)
+ , _grabbed_keyboard (false)
+ , _entered (false)
{
CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
_note_group->raise_to_top();
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,
boost::shared_ptr<MidiRegion> r,
double spu,
uint32_t basic_color,
+ bool recording,
TimeAxisViewItem::Visibility visibility)
- : RegionView (parent, tv, r, spu, basic_color, false, visibility)
+ : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
, _current_range_min(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())
, _active_notes(0)
- , _note_group (new ArdourCanvas::Container (parent))
+ , _note_group (new ArdourCanvas::Container (group))
, _note_diff_command (0)
, _ghost_note(0)
, _step_edit_cursor (0)
, _optimization_iterator (_events.end())
, _list_editor (0)
, _no_sound_notes (false)
+ , _last_display_zoom (0)
, _last_event_x (0)
, _last_event_y (0)
- , pre_enter_cursor (0)
- , pre_press_cursor (0)
- , pre_note_enter_cursor (0)
- , _note_player (0)
+ , _grabbed_keyboard (false)
+ , _entered (false)
{
CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
_note_group->raise_to_top();
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
, _optimization_iterator (_events.end())
, _list_editor (0)
, _no_sound_notes (false)
+ , _last_display_zoom (0)
, _last_event_x (0)
, _last_event_y (0)
- , pre_enter_cursor (0)
- , pre_press_cursor (0)
- , pre_note_enter_cursor (0)
- , _note_player (0)
+ , _grabbed_keyboard (false)
+ , _entered (false)
{
init (false);
}
, _optimization_iterator (_events.end())
, _list_editor (0)
, _no_sound_notes (false)
+ , _last_display_zoom (0)
, _last_event_x (0)
, _last_event_y (0)
- , pre_enter_cursor (0)
- , pre_press_cursor (0)
- , pre_note_enter_cursor (0)
- , _note_player (0)
+ , _grabbed_keyboard (false)
+ , _entered (false)
{
init (true);
}
gui_context());
if (wfd) {
- midi_region()->midi_source(0)->load_model();
+ Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
+ midi_region()->midi_source(0)->load_model(lm);
}
_model = midi_region()->midi_source(0)->model();
_enable_display = false;
+ fill_color_name = "midi frame base";
RegionView::init (false);
boost::bind (&MidiRegionView::snap_changed, this),
gui_context());
+ trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
+ boost::bind (&MidiRegionView::mouse_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&
bool
MidiRegionView::canvas_group_event(GdkEvent* ev)
{
- if (in_destructor) {
+ if (in_destructor || _recregion) {
return false;
}
+ if (!trackview.editor().internal_editing()) {
+ // not in internal edit mode, so just act like a normal region
+ return RegionView::canvas_group_event (ev);
+ }
+
bool r;
switch (ev->type) {
case GDK_ENTER_NOTIFY:
- case GDK_LEAVE_NOTIFY:
_last_event_x = ev->crossing.x;
_last_event_y = ev->crossing.y;
- break;
- case GDK_MOTION_NOTIFY:
- _last_event_x = ev->motion.x;
- _last_event_y = ev->motion.y;
- break;
- default:
- break;
- }
-
- if (ev->type == GDK_2BUTTON_PRESS) {
- // cannot use double-click to exit internal mode if single-click is being used
- MouseMode m = trackview.editor().current_mouse_mode();
-
- if ((m != MouseObject || !Keyboard::modifier_state_contains (ev->button.state, Keyboard::insert_note_modifier())) && (m != MouseDraw)) {
- return trackview.editor().toggle_internal_editing_from_double_click (ev);
- }
- }
+ enter_notify(&ev->crossing);
+ // set entered_regionview (among other things)
+ return RegionView::canvas_group_event (ev);
- if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) ||
- (trackview.editor().current_mouse_mode() == MouseTimeFX)) {
- // handle non-internal-edit/non-draw modes elsewhere
+ case GDK_LEAVE_NOTIFY:
+ _last_event_x = ev->crossing.x;
+ _last_event_y = ev->crossing.y;
+ leave_notify(&ev->crossing);
+ // reset entered_regionview (among other things)
return RegionView::canvas_group_event (ev);
- }
- switch (ev->type) {
case GDK_SCROLL:
if (scroll (&ev->scroll)) {
return true;
case GDK_BUTTON_RELEASE:
r = button_release (&ev->button);
- delete _note_player;
- _note_player = 0;
+ _note_player.reset();
return r;
- case GDK_ENTER_NOTIFY:
- // set entered_regionview (among other things)
- trackview.editor().canvas_region_view_event (ev, group, this);
- return enter_notify (&ev->crossing);
-
- case GDK_LEAVE_NOTIFY:
- // reset entered_regionview (among other things)
- trackview.editor().canvas_region_view_event (ev, group, this);
- return leave_notify (&ev->crossing);
-
case GDK_MOTION_NOTIFY:
+ _last_event_x = ev->motion.x;
+ _last_event_y = ev->motion.y;
return motion (&ev->motion);
default:
break;
}
- return trackview.editor().canvas_region_view_event (ev, group, this);
+ return RegionView::canvas_group_event (ev);
}
bool
MidiRegionView::enter_notify (GdkEventCrossing* ev)
{
- trackview.editor().MouseModeChanged.connect (
- _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context ()
- );
-
- if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
- create_ghost_note (ev->x, ev->y);
- }
-
- if (!trackview.editor().internal_editing()) {
- Keyboard::magic_widget_drop_focus();
- } else {
- Keyboard::magic_widget_grab_focus();
- group->grab_focus();
- }
-
- // if current operation is non-operational in a midi region, change the cursor to so indicate
- if (trackview.editor().current_mouse_mode() == MouseGain) {
- Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
- pre_enter_cursor = editor->get_canvas_cursor();
- editor->set_canvas_cursor(editor->cursors()->timebar);
- }
+ enter_internal();
+ _entered = true;
return false;
}
bool
MidiRegionView::leave_notify (GdkEventCrossing*)
{
- _mouse_mode_connection.disconnect ();
+ leave_internal();
- trackview.editor().verbose_cursor()->hide ();
- remove_ghost_note ();
+ _entered = false;
+ return false;
+}
- if (trackview.editor().internal_editing()) {
- Keyboard::magic_widget_drop_focus();
+void
+MidiRegionView::mouse_mode_changed ()
+{
+ // Adjust frame colour (become more transparent for internal tools)
+ 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
+ leave_internal();
+ }
}
+}
- if (pre_enter_cursor) {
- Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
- editor->set_canvas_cursor(pre_enter_cursor);
- pre_enter_cursor = 0;
+void
+MidiRegionView::enter_internal()
+{
+ if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
+ // Show ghost note under pencil
+ create_ghost_note(_last_event_x, _last_event_y);
}
- return false;
+ if (!_selection.empty()) {
+ // Grab keyboard for moving selected notes with arrow keys
+ Keyboard::magic_widget_grab_focus();
+ _grabbed_keyboard = true;
+ }
+
+ // Lower frame handles below notes so they don't steal events
+ if (frame_handle_start) {
+ frame_handle_start->lower_to_bottom();
+ }
+ if (frame_handle_end) {
+ frame_handle_end->lower_to_bottom();
+ }
}
void
-MidiRegionView::mouse_mode_changed ()
+MidiRegionView::leave_internal()
{
- if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) {
- create_ghost_note (_last_event_x, _last_event_y);
- } else {
- remove_ghost_note ();
- trackview.editor().verbose_cursor()->hide ();
- }
+ trackview.editor().verbose_cursor()->hide ();
+ remove_ghost_note ();
- if (!trackview.editor().internal_editing()) {
+ if (_grabbed_keyboard) {
Keyboard::magic_widget_drop_focus();
- } else {
- Keyboard::magic_widget_grab_focus();
- group->grab_focus();
+ _grabbed_keyboard = false;
+ }
+
+ // Raise frame handles above notes so they catch events
+ if (frame_handle_start) {
+ frame_handle_start->raise_to_top();
+ }
+ if (frame_handle_end) {
+ frame_handle_end->raise_to_top();
}
}
Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
MouseMode m = editor->current_mouse_mode();
- if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
- pre_press_cursor = editor->get_canvas_cursor ();
- editor->set_canvas_cursor (editor->cursors()->midi_pencil);
+ if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
+ _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
}
if (_mouse_state != SelectTouchDragging) {
PublicEditor& editor = trackview.editor ();
- if (pre_press_cursor) {
- dynamic_cast<Editor*>(&editor)->set_canvas_cursor (pre_press_cursor, false);
- pre_press_cursor = 0;
- }
+ _press_cursor_ctx.reset();
switch (_mouse_state) {
case Pressed: // Clicked
clear_selection ();
break;
- case MouseObject:
+ case MouseContent:
case MouseTimeFX:
{
clear_selection();
{
PublicEditor& editor = trackview.editor ();
- if (!_ghost_note && editor.current_mouse_mode() == MouseObject &&
+ 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);
- } else if (_ghost_note && editor.current_mouse_mode() == MouseObject &&
+ } 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);
- } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) {
+ } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
remove_ghost_note ();
editor.verbose_cursor()->hide ();
MouseMode m = editor.current_mouse_mode();
- if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
+ 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 ();
return true;
- } else if (m == MouseObject) {
+ } else if (m == MouseContent) {
editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
- clear_selection ();
+ if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
+ clear_selection ();
+ }
_mouse_state = SelectRectDragging;
return true;
} else if (m == MouseRange) {
clear_selection();
_mouse_state = None;
- } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) {
+ } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
bool start = (ev->keyval == GDK_comma);
bool end = (ev->keyval == GDK_period);
}
return true;
- } else if (ev->keyval == GDK_Left && unmodified) {
+ } else if (ev->keyval == GDK_Left) {
- nudge_notes (false);
+ bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
+ nudge_notes (false, fine);
return true;
- } else if (ev->keyval == GDK_Right && unmodified) {
+ } else if (ev->keyval == GDK_Right) {
- nudge_notes (true);
+ bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
+ nudge_notes (true, fine);
return true;
} else if (ev->keyval == GDK_c && unmodified) {
return;
}
- MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
- MidiStreamView* const view = mtv->midi_view();
-
- const double note = view->y_to_note(y);
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ MidiStreamView* const view = mtv->midi_view();
// Start of note in frames relative to region start
if (snap_t) {
t = snap_frame_to_grid_underneath (t, grid_frames);
}
- const boost::shared_ptr<NoteType> new_note (
- new NoteType (mtv->get_channel_for_add (),
- region_frames_to_region_beats(t + _region->start()),
- length,
- (uint8_t)note, 0x40));
+ const MidiModel::TimeType beat_time = region_frames_to_region_beats(
+ t + _region->start());
+
+ const double note = view->y_to_note(y);
+ const uint8_t chan = mtv->get_channel_for_add();
+ const uint8_t velocity = get_velocity_for_add(beat_time);
+
+ const boost::shared_ptr<NoteType> new_note(
+ new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
if (_model->contains (new_note)) {
return;
void
MidiRegionView::redisplay_model()
{
- // Don't redisplay the model if we're currently recording and displaying that
if (_active_notes) {
+ // Currently recording
+ const framecnt_t zoom = trackview.editor().get_current_zoom();
+ if (zoom != _last_display_zoom) {
+ /* Update resolved canvas notes to reflect changes in zoom without
+ touching model. Leave active notes (with length 0) alone since
+ they are being extended. */
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ if ((*i)->note()->length() > 0) {
+ update_note(*i);
+ }
+ }
+ _last_display_zoom = zoom;
+ }
return;
}
if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
cne->validate ();
-
- Note* cn;
- Hit* ch;
-
- if ((cn = dynamic_cast<Note*>(cne)) != 0) {
- update_note (cn);
- } else if ((ch = dynamic_cast<Hit*>(cne)) != 0) {
- update_hit (ch);
- }
+ update_note (cne);
if (visible) {
cne->show ();
bool have_periodic_system_messages = false;
bool display_periodic_messages = true;
- if (!Config->get_never_display_periodic_midi()) {
+ if (!ARDOUR_UI::config()->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::MusicalTime> > mev =
GhostRegion*
MidiRegionView::add_ghost (TimeAxisView& tv)
{
- Note* note;
-
double unit_position = _region->position () / samples_per_pixel;
MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
MidiGhostRegion* ghost;
}
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
- if ((note = dynamic_cast<Note*>(*i)) != 0) {
- ghost->add_note(note);
- }
+ ghost->add_note(*i);
}
ghost->set_height ();
}
if (_active_notes && _active_notes[note]) {
+ /* Set note length so update_note() works. Note this is a local note
+ for recording, not from a model, so we can safely mess with it. */
+ _active_notes[note]->note()->set_length(
+ end_time - _active_notes[note]->note()->time());
- /* XXX is end_time really region-centric? I think so, because
- this is a new region that we're recording, so source zero is
- the same as region zero
- */
+ /* End time is relative to the region being recorded. */
const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
_active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
_active_notes[note]->set_outline_all ();
_active_notes[note] = 0;
-
}
}
return;
}
- for (unsigned i=0; i < 128; ++i) {
+ for (unsigned i = 0; i < 128; ++i) {
if (_active_notes[i]) {
- _active_notes[i]->set_x1 (trackview.editor().sample_to_pixel(_region->length()));
+ _active_notes[i]->set_x1(
+ trackview.editor().sample_to_pixel(_region->length()));
}
}
}
-
void
MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
{
- if (_no_sound_notes || !Config->get_sound_midi_notes()) {
+ if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
return;
}
void
MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
{
- if (_no_sound_notes || !Config->get_sound_midi_notes()) {
- return;
- }
-
- RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
-
- if (!route_ui || !route_ui->midi_track()) {
- return;
- }
-
- delete _note_player;
- _note_player = new NotePlayer (route_ui->midi_track ());
- _note_player->add (note);
- _note_player->on ();
+ const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
+ start_playing_midi_chord(notes);
}
void
MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
{
- if (_no_sound_notes || !Config->get_sound_midi_notes()) {
+ if (_no_sound_notes || !ARDOUR_UI::config()->get_sound_midi_notes()) {
return;
}
return;
}
- delete _note_player;
- _note_player = new NotePlayer (route_ui->midi_track());
+ _note_player = boost::shared_ptr<NotePlayer>(new NotePlayer(route_ui->midi_track()));
for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
_note_player->add (*n);
return !outside;
}
+void
+MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
+{
+ Note* sus = NULL;
+ Hit* hit = NULL;
+ if ((sus = dynamic_cast<Note*>(note))) {
+ update_sustained(sus, update_ghost_regions);
+ } else if ((hit = dynamic_cast<Hit*>(note))) {
+ update_hit(hit, update_ghost_regions);
+ }
+}
+
/** Update a canvas note's size from its model note.
* @param ev Canvas note to update.
* @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
*/
void
-MidiRegionView::update_note (Note* ev, bool update_ghost_regions)
+MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
{
boost::shared_ptr<NoteType> note = ev->note();
const double x = trackview.editor().sample_to_pixel (source_beats_to_region_frames (note->time()));
if (!note->length()) {
if (_active_notes && note->note() < 128) {
- // If this note is already active there's a stuck note,
- // finish the old note rectangle
- if (_active_notes[note->note()]) {
- Note* const old_rect = _active_notes[note->note()];
- boost::shared_ptr<NoteType> old_note = old_rect->note();
+ Note* const old_rect = _active_notes[note->note()];
+ 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_outline_all ();
}
/* outline all edges */
ev->set_outline_all ();
}
-
+
+ // 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()));
+
if (update_ghost_regions) {
for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
}
void
-MidiRegionView::update_hit (Hit* ev)
+MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
{
boost::shared_ptr<NoteType> note = ev->note();
ev->set_position (ArdourCanvas::Duple (x, y));
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()));
+
+ 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);
+ }
+ }
+ }
}
/** Add a MIDI note to the view (with length).
Note* ev_rect = new Note (*this, _note_group, note);
- update_note (ev_rect);
+ update_sustained (ev_rect);
event = ev_rect;
- MidiGhostRegion* gr;
-
- for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
- if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
- gr->add_note(ev_rect);
- }
- }
-
} else if (midi_view()->note_mode() == Percussive) {
const double diamond_size = std::max(1., floor(midi_stream_view()->note_height()) - 2.);
}
if (event) {
+ MidiGhostRegion* gr;
+
+ for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
+ if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
+ gr->add_note(event);
+ }
+ }
+
if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
note_selected(event, true);
}
}
if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
- key.bank_number = (*i)->bank();
- key.program_number = (*i)->program ();
+ key.set_bank((*i)->bank());
+ key.set_program((*i)->program ());
} else {
- key.bank_number = key.program_number = 0;
- }
-
- if (!key.is_sane()) {
- error << string_compose(_("insane MIDI patch key %1:%2"),
- key.bank_number, key.program_number) << endmsg;
+ key.set_bank(0);
+ key.set_program(0);
}
}
{
MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
- if (pc.patch()->program() != new_patch.program_number) {
- c->change_program (pc.patch (), new_patch.program_number);
+ if (pc.patch()->program() != new_patch.program()) {
+ c->change_program (pc.patch (), new_patch.program());
}
- int const new_bank = new_patch.bank_number;
+ int const new_bank = new_patch.bank();
if (pc.patch()->bank() != new_bank) {
c->change_bank (pc.patch (), new_bank);
}
}
void
-MidiRegionView::previous_patch (PatchChange& patch)
-{
- if (patch.patch()->program() < 127) {
- MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
- key.program_number++;
- change_patch_change (patch, key);
- }
-}
-
-void
-MidiRegionView::next_patch (PatchChange& patch)
+MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
{
- if (patch.patch()->program() > 0) {
- MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
- key.program_number--;
- change_patch_change (patch, key);
- }
-}
-
-void
-MidiRegionView::next_bank (PatchChange& patch)
-{
- if (patch.patch()->program() < 127) {
- MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
- if (key.bank_number > 0) {
- key.bank_number--;
- change_patch_change (patch, key);
- }
- }
-}
-
-void
-MidiRegionView::previous_bank (PatchChange& patch)
-{
- if (patch.patch()->program() > 0) {
- MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch());
- if (key.bank_number < 127) {
- key.bank_number++;
- change_patch_change (patch, key);
- }
+ MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
+ if (bank) {
+ key.set_bank(key.bank() + delta);
+ } else {
+ key.set_program(key.program() + delta);
}
+ change_patch_change(patch, key);
}
void
}
}
+ if (!ev && _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.
*/
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
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;
+ }
}
}
if (i != _selection.end()) {
_selection.erase (i);
+ if (_selection.empty() && _grabbed_keyboard) {
+ // Ungrab keyboard
+ Keyboard::magic_widget_drop_focus();
+ _grabbed_keyboard = false;
+ }
}
ev->set_selected (false);
void
MidiRegionView::add_to_selection (NoteBase* ev)
{
- bool add_mrv_selection = false;
-
- if (_selection.empty()) {
- add_mrv_selection = true;
- }
+ const bool selection_was_empty = _selection.empty();
if (_selection.insert (ev).second) {
ev->set_selected (true);
start_playing_midi_note ((ev)->note());
+ if (selection_was_empty && _entered) {
+ // Grab keyboard for moving notes with arrow keys
+ Keyboard::magic_widget_grab_focus();
+ _grabbed_keyboard = true;
+ }
}
- if (add_mrv_selection) {
+ if (selection_was_empty) {
PublicEditor& editor (trackview.editor());
editor.get_selection().add (this);
}
(*i)->move_event(dx, dy);
}
- if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) {
+ if (dy && !_selection.empty() && !_no_sound_notes && ARDOUR_UI::config()->get_sound_midi_notes()) {
if (to_play.size() > 1) {
// calculate the colors: get the color settings
uint32_t fill_color = UINT_RGBA_CHANGE_A(
- ARDOUR_UI::config()->get_MidiNoteSelected(),
+ ARDOUR_UI::config()->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()->get_MidiNoteSelected()));
+ ARDOUR_UI::config()->color ("midi note selected")));
resize_data->resize_rect = resize_rect;
_resize_data.push_back(resize_data);
}
void
-MidiRegionView::nudge_notes (bool forward)
+MidiRegionView::nudge_notes (bool forward, bool fine)
{
if (_selection.empty()) {
return;
into a vector and sort before using the first one.
*/
- framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
- framepos_t unused;
- framecnt_t distance;
+ const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
+ Evoral::MusicalTime delta;
- if (trackview.editor().snap_mode() == Editing::SnapOff) {
+ if (!fine) {
+
+ /* non-fine, move by 1 bar regardless of snap */
+ delta = Evoral::MusicalTime(trackview.session()->tempo_map().meter_at(ref_point).divisions_per_bar());
+
+ } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
/* grid is off - use nudge distance */
- distance = trackview.editor().get_nudge_distance (ref_point, unused);
+ framepos_t unused;
+ const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
+ delta = region_frames_to_region_beats (fabs ((double)distance));
} else {
}
trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
- distance = ref_point - next_pos;
+ const framecnt_t distance = ref_point - next_pos;
+ delta = region_frames_to_region_beats (fabs ((double)distance));
}
- if (distance == 0) {
+ if (!delta) {
return;
}
- Evoral::MusicalTime delta = region_frames_to_region_beats (fabs ((double)distance));
-
if (!forward) {
delta = -delta;
}
{
Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
- pre_note_enter_cursor = editor->get_canvas_cursor ();
-
if (_mouse_state == SelectTouchDragging) {
note_selected (ev, true);
+ } else if (editor->current_mouse_mode() == MouseContent) {
+ show_verbose_cursor (ev->note ());
+ } else if (editor->current_mouse_mode() == MouseDraw) {
+ show_verbose_cursor (ev->note ());
}
-
- show_verbose_cursor (ev->note ());
}
void
}
editor->verbose_cursor()->hide ();
-
- if (pre_note_enter_cursor) {
- editor->set_canvas_cursor (pre_note_enter_cursor);
- pre_note_enter_cursor = 0;
- }
}
void
{
Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
Editing::MouseMode mm = editor->current_mouse_mode();
- bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw);
+ bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
- if (can_set_cursor) {
+ Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
+ if (can_set_cursor && ctx) {
if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
- editor->set_canvas_cursor (editor->cursors()->left_side_trim);
+ ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
} else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
- editor->set_canvas_cursor (editor->cursors()->right_side_trim);
- } else if (pre_note_enter_cursor) {
- editor->set_canvas_cursor (pre_note_enter_cursor);
+ ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
+ } else {
+ ctx->cursor_ctx->change(editor->cursors()->grabber_note);
}
}
}
-void
-MidiRegionView::set_frame_color()
+uint32_t
+MidiRegionView::get_fill_color() const
{
- uint32_t f;
-
- TimeAxisViewItem::set_frame_color ();
-
- if (!frame) {
- return;
- }
-
+ const std::string mod_name = (_dragging ? "dragging region" :
+ trackview.editor().internal_editing() ? "editable region" :
+ "midi frame base");
if (_selected) {
- f = ARDOUR_UI::config()->get_SelectedFrameBase();
- } else if (high_enough_for_name) {
- f= ARDOUR_UI::config()->get_MidiFrameBase();
- } else {
- f = fill_color;
+ return ARDOUR_UI::config()->color_mod ("selected region base", mod_name);
+ } else if (high_enough_for_name || !ARDOUR_UI::config()->get_color_regions_using_track_color()) {
+ return ARDOUR_UI::config()->color_mod ("midi frame base", mod_name);
}
-
- if (!rect_visible) {
- f = UINT_RGBA_CHANGE_A (f, 80);
- }
-
- frame->set_fill_color (f);
+ return ARDOUR_UI::config()->color_mod (fill_color, mod_name);
}
void
/** This method handles undo */
bool
-MidiRegionView::paste (framepos_t pos, unsigned paste_count, float times, const ::Selection& selection, ItemCounts& counts)
+MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
{
- // Get our set of notes from the selection
- MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(counts.n_notes());
- if (m == selection.midi_notes.end()) {
- return 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();
+ paste_internal(pos, ctx.count, ctx.times, **m);
}
- counts.increase_n_notes();
-
- trackview.session()->begin_reversible_command (Operations::paste);
- // Paste notes
- paste_internal(pos, paste_count, times, **m);
-
- // Paste control points to automation children
+ // Paste control points to automation children, if available
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, paste_count, times, selection, counts);
+ a->second->paste(pos, selection, ctx);
}
- trackview.session()->commit_reversible_command ();
-
return true;
}
void
MidiRegionView::update_ghost_note (double x, double y)
{
+ x = std::max(0.0, x);
+
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
_last_ghost_x = x;
framecnt_t grid_frames;
framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames);
- /* use region_frames... because we are converting a delta within the region
- */
-
+ /* calculate time in beats relative to start of source */
const Evoral::MusicalTime length = get_grid_beats(unsnapped_frame);
+ const Evoral::MusicalTime time = std::max(
+ Evoral::MusicalTime(),
+ absolute_frames_to_source_beats (f + _region->position ()));
- /* note that this sets the time of the ghost note in beats relative to
- the start of the source; that is how all note times are stored.
- */
- _ghost_note->note()->set_time (
- std::max(Evoral::MusicalTime(),
- absolute_frames_to_source_beats (f + _region->position ())));
+ _ghost_note->note()->set_time (time);
_ghost_note->note()->set_length (length);
_ghost_note->note()->set_note (midi_stream_view()->y_to_note (y));
_ghost_note->note()->set_channel (mtv->get_channel_for_add ());
+ _ghost_note->note()->set_velocity (get_velocity_for_add (time));
/* the ghost note does not appear in ghost regions, so pass false in here */
update_note (_ghost_note, false);
remove_ghost_note ();
boost::shared_ptr<NoteType> g (new NoteType);
- _ghost_note = new Note (*this, _note_group, g);
+ if (midi_view()->note_mode() == Sustained) {
+ _ghost_note = new Note (*this, _note_group, g);
+ } else {
+ _ghost_note = new Hit (*this, _note_group, 10, g);
+ }
_ghost_note->set_ignore_events (true);
_ghost_note->set_outline_color (0x000000aa);
- if (x < 0) { x = 0; }
update_ghost_note (x, y);
_ghost_note->show ();
- _last_ghost_x = x;
- _last_ghost_y = y;
-
show_verbose_cursor (_ghost_note->note ());
}
void
MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
{
+ /* XXX: This is dead code. What was it for? */
+
double note = midi_stream_view()->y_to_note(y);
Events e;
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
- BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0));
-
framepos_t back = max_framepos;
for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
}
}
- /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is
- frames from the start of the source, and so time_beats is in terms of the
- source.
- */
-
- Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ());
+ /* convert from session frames to source beats */
+ Evoral::MusicalTime const time_beats = _source_relative_time_converter.from(
+ ev.time() - src->timeline_position() + _region->start());
if (ev.type() == MIDI_CMD_NOTE_ON) {
boost::shared_ptr<NoteType> note (
get_patch_key_at(n->time(), n->channel(), patch_key);
name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
n->channel(),
- patch_key.bank_number,
- patch_key.program_number,
+ patch_key.bank(),
+ patch_key.program(),
n->note());
}
}
trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
}
+uint8_t
+MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
+{
+ if (_model->notes().empty()) {
+ return 0x40; // No notes, use default
+ }
+
+ MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
+ if (m == _model->notes().begin()) {
+ // Before the start, use the velocity of the first note
+ return (*m)->velocity();
+ } else if (m == _model->notes().end()) {
+ // Past the end, use the velocity of the last note
+ --m;
+ return (*m)->velocity();
+ }
+
+ // Interpolate velocity of surrounding notes
+ MidiModel::Notes::const_iterator n = m;
+ --n;
+
+ const double frac = ((time - (*n)->time()).to_double() /
+ ((*m)->time() - (*n)->time()).to_double());
+
+ return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
+}
+
/** @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.
void
MidiRegionView::note_button_release ()
{
- delete _note_player;
- _note_player = 0;
+ _note_player.reset();
}
ChannelMode