bool
CanvasMidiEvent::on_event(GdkEvent* ev)
{
+ MidiStreamView *streamview = _region.midi_stream_view();
static uint8_t drag_delta_note = 0;
static double drag_delta_x = 0;
static double last_x, last_y;
drag_delta_x += dx;
// Snap to note rows
- if (abs(dy) < _region.midi_stream_view()->note_height()) {
+ if (abs(dy) < streamview->note_height()) {
dy = 0.0;
} else {
int8_t this_delta_note;
- if (dy > 0)
- this_delta_note = (int8_t)ceil(dy / _region.midi_stream_view()->note_height() / 2.0);
- else
- this_delta_note = (int8_t)floor(dy / _region.midi_stream_view()->note_height() / 2.0);
+ if (dy > 0) {
+ this_delta_note = (int8_t)ceil(dy / streamview->note_height() / 2.0);
+ } else {
+ this_delta_note = (int8_t)floor(dy / streamview->note_height() / 2.0);
+ }
drag_delta_note -= this_delta_note;
- dy = _region.midi_stream_view()->note_height() * this_delta_note;
+ dy = streamview->note_height() * this_delta_note;
last_y = last_y + dy;
}
CanvasNote::on_event(GdkEvent* ev)
{
double event_x;
- static double middle_point, pressed_x, last_x;
+ static double middle_point, last_x;
Gdk::Cursor cursor;
static NoteEnd note_end;
+ Editing::MidiEditMode edit_mode = _region.get_trackview().editor.current_midi_edit_mode();
switch(ev->type) {
case GDK_BUTTON_PRESS:
- if (ev->button.button == 2) {
+ if (ev->button.button == 2 ||
+ (ev->button.button == 1 &&
+ edit_mode == Editing::MidiEditResize)) {
+ double region_start = _region.get_position_pixels();
event_x = ev->button.x;
- middle_point = x1() + (x2() - x1()) / 2.0L;
+ middle_point = region_start + x1() + (x2() - x1()) / 2.0L;
if(event_x <= middle_point) {
cursor = Gdk::Cursor(Gdk::LEFT_SIDE);
- last_x = x1();
note_end = NOTE_ON;
} else {
cursor = Gdk::Cursor(Gdk::RIGHT_SIDE);
- last_x = x2();
note_end = NOTE_OFF;
}
_item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, cursor, ev->motion.time);
if (_region.mouse_state() == MidiRegionView::SelectTouchDragging) {
- _mouse2_state = AbsoluteResize;
+ _note_state = AbsoluteResize;
} else {
- _mouse2_state = RelativeResize;
+ _note_state = RelativeResize;
}
- pressed_x = event_x;
-
_region.note_selected(this, true);
_region.begin_resizing(note_end);
+ last_x = event_x;
return true;
}
case GDK_MOTION_NOTIFY:
event_x = ev->motion.x;
- if (_mouse2_state == RelativeResize) {
+ if (_note_state == RelativeResize) {
_region.update_resizing(note_end, event_x - last_x, true);
last_x = event_x;
return true;
}
- if (_mouse2_state == AbsoluteResize) {
+ if (_note_state == AbsoluteResize) {
_region.update_resizing(note_end, event_x, false);
return true;
}
case GDK_BUTTON_RELEASE:
event_x = ev->button.x;
- switch (_mouse2_state) {
+ switch (_note_state) {
case RelativeResize: // Clicked
_item->ungrab(ev->button.time);
_region.commit_resizing(note_end, event_x, true);
- _mouse2_state = None;
+ _note_state = None;
return true;
case AbsoluteResize: // Clicked
_item->ungrab(ev->button.time);
_region.commit_resizing(note_end, event_x, false);
- _mouse2_state = None;
+ _note_state = None;
return true;
default:
NOTE_OFF
};
- enum Mouse2State {
+ enum NoteState {
None,
RelativeResize,
AbsoluteResize
};
protected:
- Mouse2State _mouse2_state;
+ NoteState _note_state;
private:
- // single click resizing with mouse-2
- void resize_note(double pressed_x, double event_x, double middle_point);
+
};
} // namespace Gnome
MIDIEDITMODE(MidiEditPencil)
MIDIEDITMODE(MidiEditSelect)
+MIDIEDITMODE(MidiEditResize)
MIDIEDITMODE(MidiEditErase)
/* Changing this order will break the menu */
Gdk::Cursor* Editor::speaker_cursor = 0;
Gdk::Cursor* Editor::midi_pencil_cursor = 0;
Gdk::Cursor* Editor::midi_select_cursor = 0;
+Gdk::Cursor* Editor::midi_resize_cursor = 0;
Gdk::Cursor* Editor::midi_erase_cursor = 0;
Gdk::Cursor* Editor::wait_cursor = 0;
Gdk::Cursor* Editor::timebar_cursor = 0;
timebar_cursor = new Gdk::Cursor(LEFT_PTR);
midi_pencil_cursor = new Gdk::Cursor (PENCIL);
midi_select_cursor = new Gdk::Cursor (CENTER_PTR);
+ midi_resize_cursor = new Gdk::Cursor (SIZING);
midi_erase_cursor = new Gdk::Cursor (DRAPED_BOX);
}
midi_tool_select_button.add (*(manage (new Image (::get_icon("midi_tool_select")))));
midi_tool_select_button.set_relief(Gtk::RELIEF_NONE);
midi_tool_buttons.push_back (&midi_tool_select_button);
+ midi_tool_resize_button.add (*(manage (new Image (::get_icon("strip_width")))));
+ midi_tool_resize_button.set_relief(Gtk::RELIEF_NONE);
+ midi_tool_buttons.push_back (&midi_tool_resize_button);
midi_tool_erase_button.add (*(manage (new Image (::get_icon("midi_tool_erase")))));
midi_tool_erase_button.set_relief(Gtk::RELIEF_NONE);
midi_tool_buttons.push_back (&midi_tool_erase_button);
midi_tool_button_set = new GroupedButtons (midi_tool_buttons);
midi_tool_button_box.set_border_width (2);
- midi_tool_button_box.set_spacing(4);
midi_tool_button_box.set_spacing(1);
midi_tool_button_box.pack_start(midi_tool_pencil_button, true, true);
midi_tool_button_box.pack_start(midi_tool_select_button, true, true);
+ midi_tool_button_box.pack_start(midi_tool_resize_button, true, true);
midi_tool_button_box.pack_start(midi_tool_erase_button, true, true);
midi_tool_button_box.set_homogeneous(true);
midi_tool_pencil_button.set_name ("MouseModeButton");
midi_tool_select_button.set_name ("MouseModeButton");
+ midi_tool_resize_button.set_name ("MouseModeButton");
midi_tool_erase_button.set_name ("MouseModeButton");
ARDOUR_UI::instance()->tooltips().set_tip (midi_tool_pencil_button, _("Add/Move/Stretch Notes"));
ARDOUR_UI::instance()->tooltips().set_tip (midi_tool_select_button, _("Select/Move Notes"));
+ ARDOUR_UI::instance()->tooltips().set_tip (midi_tool_resize_button, _("Resize Notes"));
ARDOUR_UI::instance()->tooltips().set_tip (midi_tool_erase_button, _("Erase Notes"));
midi_tool_pencil_button.unset_flags (CAN_FOCUS);
midi_tool_select_button.unset_flags (CAN_FOCUS);
+ midi_tool_resize_button.unset_flags (CAN_FOCUS);
midi_tool_erase_button.unset_flags (CAN_FOCUS);
midi_tool_pencil_button.signal_toggled().connect (bind (mem_fun(*this,
&Editor::midi_edit_mode_toggled), Editing::MidiEditPencil));
midi_tool_select_button.signal_toggled().connect (bind (mem_fun(*this,
&Editor::midi_edit_mode_toggled), Editing::MidiEditSelect));
+ midi_tool_resize_button.signal_toggled().connect (bind (mem_fun(*this,
+ &Editor::midi_edit_mode_toggled), Editing::MidiEditResize));
midi_tool_erase_button.signal_toggled().connect (bind (mem_fun(*this,
&Editor::midi_edit_mode_toggled), Editing::MidiEditErase));
static Gdk::Cursor* speaker_cursor;
static Gdk::Cursor* midi_pencil_cursor;
static Gdk::Cursor* midi_select_cursor;
+ static Gdk::Cursor* midi_resize_cursor;
static Gdk::Cursor* midi_erase_cursor;
static Gdk::Cursor* wait_cursor;
static Gdk::Cursor* timebar_cursor;
Gtkmm2ext::TearOff* midi_tool_tearoff;
Gtk::ToggleButton midi_tool_pencil_button;
Gtk::ToggleButton midi_tool_select_button;
+ Gtk::ToggleButton midi_tool_resize_button;
Gtk::ToggleButton midi_tool_erase_button;
GroupedButtons *midi_tool_button_set;
void midi_edit_mode_toggled (Editing::MidiEditMode m);
set_midi_edit_mode (m);
break;
+ case MidiEditResize:
+ if (midi_tool_resize_button.get_active())
+ set_midi_edit_mode (m);
+ break;
+
case MidiEditErase:
if (midi_tool_erase_button.get_active())
set_midi_edit_mode (m);
midi_tool_select_button.set_active (true);
break;
+ case MidiEditResize:
+ midi_tool_resize_button.set_active (true);
+ break;
+
case MidiEditErase:
midi_tool_erase_button.set_active (true);
break;
current_canvas_cursor = midi_select_cursor;
break;
+ case MidiEditResize:
+ current_canvas_cursor = midi_resize_cursor;
+ break;
+
case MidiEditErase:
current_canvas_cursor = midi_erase_cursor;
break;
} else if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) {
_mouse_state = SelectTouchDragging;
return true;
+ } else if (ev->key.keyval == GDK_Escape) {
+ clear_selection();
+ _mouse_state = None;
}
return false;
case Pressed: // Clicked
switch (trackview.editor.current_midi_edit_mode()) {
case MidiEditSelect:
+ case MidiEditResize:
clear_selection();
break;
case MidiEditPencil:
clear_selection();
MidiGhostRegion* gr;
- for (vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
+ for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
gr->clear_events();
}
MidiGhostRegion* gr;
- for (vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
+ for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
gr->add_note(ev_rect);
}
{
// TODO: This would be faster/nicer with a MoveCommand that doesn't need to copy...
if (_selection.find(ev) != _selection.end()) {
+ uint8_t lowest_note_in_selection = midi_stream_view()->lowest_note();
+ uint8_t highest_note_in_selection = midi_stream_view()->highest_note();
+ uint8_t highest_note_difference = 0;
+
+ // find highest and lowest notes first
+ for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) {
+ Selection::iterator next = i;
+ ++next;
+
+ uint8_t pitch = (*i)->note()->note();
+ lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
+ highest_note_in_selection = std::max(highest_note_in_selection, pitch);
+
+ i = next;
+ }
+
+ /*
+ cerr << "dnote: " << (int) dnote << endl;
+ cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
+ << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
+ cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
+ << int(highest_note_in_selection) << endl;
+ cerr << "selection size: " << _selection.size() << endl;
+ cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
+ */
+
+ // Make sure the note pitch does not exceed the MIDI standard range
+ if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) {
+ highest_note_difference = highest_note_in_selection - 127;
+ cerr << "Highest note difference: " << (int) highest_note_difference;
+ }
+
start_delta_command();
for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) {
const boost::shared_ptr<Note> copy(new Note(*(*i)->note().get()));
copy->set_time((*i)->note()->time() + dt);
- copy->set_note((*i)->note()->note() + dnote);
+ if(copy->time() < 0) {
+ copy->set_time(0);
+ }
+
+ uint8_t new_pitch = (*i)->note()->note() + dnote - highest_note_difference;
+ if(new_pitch > 127) {
+ new_pitch = 127;
+ }
+ lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
+ highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+
+ copy->set_note(new_pitch);
+
command_add_note(copy);
_selection.erase(i);
i = next;
}
+
apply_command();
+
+ //cerr << "new lowest note (selection): " << int(lowest_note_in_selection) << " new highest note(selection): " << int(highest_note_in_selection) << endl;
+
+ // care about notes being moved beyond the upper/lower bounds on the canvas
+ if(lowest_note_in_selection < midi_stream_view()->lowest_note() ||
+ highest_note_in_selection > midi_stream_view()->highest_note()
+ ) {
+ //cerr << "resetting note range" << endl;
+ midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
}
}
+}
+
+
+double
+MidiRegionView::snap_to(double x)
+{
+ PublicEditor &editor = trackview.editor;
+
+ nframes_t frame = editor.pixel_to_frame(x);
+ editor.snap_to(frame);
+ return (double) editor.frame_to_pixel(frame);
+}
+
+double
+MidiRegionView::get_position_pixels(void)
+{
+ nframes_t region_frame = get_position();
+ return trackview.editor.frame_to_pixel(region_frame);
+}
void
MidiRegionView::begin_resizing(CanvasNote::NoteEnd note_end)
NoteResizeData *resize_data = new NoteResizeData();
resize_data->canvas_note = note;
- SimpleRect *resize_rect = new SimpleRect(*group, note->x1(), note->y1(), note->x2(), note->y2());
-
- uint fill_color = UINT_RGBA_CHANGE_A(ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get(), 128);
+ // create a new SimpleRect from the note which will be the resize preview
+ SimpleRect *resize_rect =
+ new SimpleRect(
+ *group,
+ note->x1(),
+ note->y1(),
+ note->x2(),
+ note->y2());
+
+ // calculate the colors: get the color settings
+ uint fill_color =
+ UINT_RGBA_CHANGE_A(
+ ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get(),
+ 128);
+
+ // make the resize preview notes more transparent and bright
fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
+ // calculate color based on note velocity
resize_rect->property_fill_color_rgba() =
UINT_INTERPOLATE(
note_fill_color(note->note()->velocity()),
fill_color,
0.85);
- resize_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get();
+ resize_rect->property_outline_color_rgba() =
+ ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get();
+
resize_data->resize_rect = resize_rect;
if(note_end == CanvasNote::NOTE_ON) {
}
}
-double
-MidiRegionView::snap_to(double x)
-{
- PublicEditor &editor = trackview.editor;
-
- nframes_t frame = editor.pixel_to_frame(x);
- editor.snap_to(frame);
- return (double) editor.frame_to_pixel(frame);
-}
-
void
-MidiRegionView::update_resizing(CanvasNote::NoteEnd note_end, double dx, bool relative)
+MidiRegionView::update_resizing(CanvasNote::NoteEnd note_end, double x, bool relative)
{
-
-
for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
SimpleRect *resize_rect = (*i)->resize_rect;
CanvasNote *canvas_note = (*i)->canvas_note;
+ const double region_start = get_position_pixels();
+
if(relative) {
- (*i)->current_x = (*i)->current_x + dx;
+ (*i)->current_x = (*i)->current_x + x;
} else {
- (*i)->current_x = dx;
+ // x is in track relative, transform it to region relative
+ (*i)->current_x = x - region_start;
}
double current_x = (*i)->current_x;
if(note_end == CanvasNote::NOTE_ON) {
- resize_rect->property_x1() = snap_to(current_x);
+ // because snapping works on world coordinates we have to transform current_x
+ // to world coordinates before snapping and transform it back afterwards
+ resize_rect->property_x1() = snap_to(region_start + current_x) - region_start;
resize_rect->property_x2() = canvas_note->x2();
} else {
- resize_rect->property_x2() = snap_to(current_x);
+ // because snapping works on world coordinates we have to transform current_x
+ // to world coordinates before snapping and transform it back afterwards
+ resize_rect->property_x2() = snap_to(region_start + current_x) - region_start;
resize_rect->property_x1() = canvas_note->x1();
}
}
CanvasNote *canvas_note = (*i)->canvas_note;
SimpleRect *resize_rect = (*i)->resize_rect;
double current_x = (*i)->current_x;
-
+ const double region_start = get_position_pixels();
if(!relative) {
- current_x = event_x;
+ // event_x is in track relative, transform it to region relative
+ current_x = event_x - region_start;
}
- nframes_t current_frame = trackview.editor.pixel_to_frame(current_x);
+ // because snapping works on world coordinates we have to transform current_x
+ // to world coordinates before snapping and transform it back afterwards
+ nframes_t current_frame = trackview.editor.pixel_to_frame(current_x + region_start);
trackview.editor.snap_to(current_frame);
+ current_frame -= get_position();
const boost::shared_ptr<Note> copy(new Note(*(canvas_note->note().get())));
void move_selection(double dx, double dy);
void note_dropped(ArdourCanvas::CanvasMidiEvent* ev, double dt, uint8_t dnote);
+ /**
+ * This function is needed to subtract the region start in pixels
+ * from world coordinates submitted by the mouse
+ */
+ double get_position_pixels(void);
+
+ /**
+ * This function is called by CanvasMidiNote when resizing starts,
+ * i.e. when the user presses mouse-2 on the note
+ * @param note_end which end of the note, NOTE_ON or NOTE_OFF
+ */
void begin_resizing(ArdourCanvas::CanvasNote::NoteEnd note_end);
- void update_resizing(ArdourCanvas::CanvasNote::NoteEnd note_end, double dx, bool relative);
+
+ /**
+ * This function is called while the user moves the mouse when resizing notes
+ * @param note_end which end of the note, NOTE_ON or NOTE_OFF
+ * @param x the difference in mouse motion, ie the motion difference if relative=true
+ * or the absolute mouse position (track-relative) if relative is false
+ * @param relative true if relative resizing is taking place, false if absolute resizing
+ */
+ void update_resizing(ArdourCanvas::CanvasNote::NoteEnd note_end, double x, bool relative);
+
+ /**
+ * This function is called while the user releases the mouse button when resizing notes
+ * @param note_end which end of the note, NOTE_ON or NOTE_OFF
+ * @param event_x the absolute mouse position (track-relative)
+ * @param relative true if relative resizing is taking place, false if absolute resizing
+ */
void commit_resizing(ArdourCanvas::CanvasNote::NoteEnd note_end, double event_x, bool relative);
enum MouseState { None, Pressed, SelectTouchDragging, SelectRectDragging, AddDragging, EraseTouchDragging };
protected:
- /* this constructor allows derived types
- to specify their visibility requirements
- to the TimeAxisViewItem parent class
+ /**
+ * this constructor allows derived types
+ * to specify their visibility requirements
+ * to the TimeAxisViewItem parent class
*/
-
MidiRegionView (ArdourCanvas::Group *,
RouteTimeAxisView&,
boost::shared_ptr<ARDOUR::MidiRegion>,
void clear_selection_except(ArdourCanvas::CanvasMidiEvent* ev);
void clear_selection() { clear_selection_except(NULL); }
void update_drag_selection(double last_x, double x, double last_y, double y);
+
+ /**
+ * This function provides the snap function for pixel units (double)
+ * instead of nframes_t
+ */
double snap_to(double x);
double _default_note_length;
eventType type;
ostream *o;
- cerr << "TRACE\n";
-
if ((o = trace_stream) == NULL) { /* can be asynchronously removed */
return;
}