13
0
livetrax/gtk2_ardour/midi_region_view.cc
David Robillard 214a31bb98 Fix various MIDI control and installation issues:
* Install ardour3_ui_default.conf to system config dir

 * Set -DDATA_DIR etc. defines to proper absolute paths

 * Set default MIDI control port name to "control"
   (it was "control" some places, "default" other, so the generic MIDI
   control surface didn't work.  The real problem here is probably that
   the name is hardcoded in the surface code, ick)

 * Install surfaces to correct system directory

 * Generate and install ardour_system.rc

User POV:

 * Installed versions not run from the source directory discover configuration
   files and surfaces, and generally work

 * Building and/or starting a fresh copy of ardour3 with no pre-existing
   configuration will run an ardour with a single MIDI "control" port, which
   you can plug a surface into and control MMC and controllers and such
   (after turning on the generic MIDI surface, which IMO should be loaded
    by default anyway, especially since it's no longer in a menu)


git-svn-id: svn://localhost/ardour2/branches/3.0@5833 d708f5d6-7413-0410-9779-e7cbd77b26cf
2009-10-20 23:43:19 +00:00

2524 lines
63 KiB
C++

/*
Copyright (C) 2001-2007 Paul Davis
Author: Dave Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <cmath>
#include <cassert>
#include <algorithm>
#include <ostream>
#include <gtkmm.h>
#include <gtkmm2ext/gtk_ui.h>
#include <sigc++/signal.h>
#include "pbd/memento_command.h"
#include "ardour/playlist.h"
#include "ardour/tempo.h"
#include "ardour/midi_region.h"
#include "ardour/midi_source.h"
#include "ardour/midi_diskstream.h"
#include "ardour/midi_model.h"
#include "ardour/midi_patch_manager.h"
#include "evoral/Parameter.hpp"
#include "evoral/Control.hpp"
#include "automation_region_view.h"
#include "automation_time_axis.h"
#include "canvas-hit.h"
#include "canvas-note.h"
#include "canvas-program-change.h"
#include "ghostregion.h"
#include "gui_thread.h"
#include "keyboard.h"
#include "midi_cut_buffer.h"
#include "midi_list_editor.h"
#include "midi_region_view.h"
#include "midi_streamview.h"
#include "midi_time_axis.h"
#include "midi_time_axis.h"
#include "midi_util.h"
#include "public_editor.h"
#include "selection.h"
#include "simpleline.h"
#include "streamview.h"
#include "utils.h"
#include "i18n.h"
using namespace sigc;
using namespace ARDOUR;
using namespace PBD;
using namespace Editing;
using namespace ArdourCanvas;
MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color const & basic_color)
: RegionView (parent, tv, r, spu, basic_color)
, _force_channel(-1)
, _last_channel_selection(0xFFFF)
, _default_note_length(1.0)
, _current_range_min(0)
, _current_range_max(0)
, _model_name(string())
, _custom_device_mode(string())
, _active_notes(0)
, _note_group(new ArdourCanvas::Group(*parent))
, _delta_command(0)
, _diff_command(0)
, _mouse_state(None)
, _pressed_button(0)
, _sort_needed (true)
, _optimization_iterator (_events.end())
{
_note_group->raise_to_top();
}
MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
TimeAxisViewItem::Visibility visibility)
: RegionView (parent, tv, r, spu, basic_color, false, visibility)
, _force_channel(-1)
, _last_channel_selection(0xFFFF)
, _default_note_length(1.0)
, _model_name(string())
, _custom_device_mode(string())
, _active_notes(0)
, _note_group(new ArdourCanvas::Group(*parent))
, _delta_command(0)
, _diff_command(0)
, _mouse_state(None)
, _pressed_button(0)
, _sort_needed (true)
, _optimization_iterator (_events.end())
{
_note_group->raise_to_top();
}
MidiRegionView::MidiRegionView (const MidiRegionView& other)
: sigc::trackable(other)
, RegionView (other)
, _force_channel(-1)
, _last_channel_selection(0xFFFF)
, _default_note_length(1.0)
, _model_name(string())
, _custom_device_mode(string())
, _active_notes(0)
, _note_group(new ArdourCanvas::Group(*get_canvas_group()))
, _delta_command(0)
, _diff_command(0)
, _mouse_state(None)
, _pressed_button(0)
, _sort_needed (true)
, _optimization_iterator (_events.end())
{
Gdk::Color c;
int r,g,b,a;
UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
c.set_rgb_p (r/255.0, g/255.0, b/255.0);
init (c, false);
}
MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
: RegionView (other, boost::shared_ptr<Region> (region))
, _force_channel(-1)
, _last_channel_selection(0xFFFF)
, _default_note_length(1.0)
, _model_name(string())
, _custom_device_mode(string())
, _active_notes(0)
, _note_group(new ArdourCanvas::Group(*get_canvas_group()))
, _delta_command(0)
, _diff_command(0)
, _mouse_state(None)
, _pressed_button(0)
, _sort_needed (true)
, _optimization_iterator (_events.end())
{
Gdk::Color c;
int r,g,b,a;
UINT_TO_RGBA (other.fill_color, &r, &g, &b, &a);
c.set_rgb_p (r/255.0, g/255.0, b/255.0);
init (c, true);
}
void
MidiRegionView::init (Gdk::Color const & basic_color, bool wfd)
{
if (wfd) {
midi_region()->midi_source(0)->load_model();
}
_model = midi_region()->midi_source(0)->model();
_enable_display = false;
RegionView::init (basic_color, false);
compute_colors (basic_color);
set_height (trackview.current_height());
region_muted ();
region_sync_changed ();
region_resized (BoundsChanged);
region_locked ();
reset_width_dependent_items (_pixel_width);
set_colors ();
_enable_display = true;
if (_model) {
if (wfd) {
display_model (_model);
}
}
group->raise_to_top();
group->signal_event().connect (mem_fun (this, &MidiRegionView::canvas_event), false);
midi_view()->signal_channel_mode_changed().connect(
mem_fun(this, &MidiRegionView::midi_channel_mode_changed));
midi_view()->signal_midi_patch_settings_changed().connect(
mem_fun(this, &MidiRegionView::midi_patch_settings_changed));
}
bool
MidiRegionView::canvas_event(GdkEvent* ev)
{
PublicEditor& editor (trackview.editor());
if (!editor.internal_editing()) {
return false;
}
static double drag_start_x, drag_start_y;
static double last_x, last_y;
double event_x, event_y;
nframes64_t event_frame = 0;
bool fine;
static ArdourCanvas::SimpleRect* drag_rect = 0;
/* XXX: note that as of August 2009, the GnomeCanvas does not propagate scroll events
to its items, which means that ev->type == GDK_SCROLL will never be seen
*/
switch (ev->type) {
case GDK_SCROLL:
fine = Keyboard::modifier_state_equals (ev->scroll.state, Keyboard::Level4Modifier);
if (ev->scroll.direction == GDK_SCROLL_UP) {
change_velocities (true, fine, false);
return true;
} else if (ev->scroll.direction == GDK_SCROLL_DOWN) {
change_velocities (false, fine, false);
return true;
} else {
return false;
}
break;
case GDK_KEY_PRESS:
/* since GTK bindings are generally activated on press, and since
detectable auto-repeat is the name of the game and only sends
repeated presses, carry out key actions at key press, not release.
*/
if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R){
_mouse_state = SelectTouchDragging;
return true;
} else if (ev->key.keyval == GDK_Escape) {
clear_selection();
_mouse_state = None;
} else if (ev->key.keyval == GDK_comma || ev->key.keyval == GDK_period) {
bool start = (ev->key.keyval == GDK_comma);
bool end = (ev->key.keyval == GDK_period);
bool shorter = Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier);
fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
change_note_lengths (fine, shorter, start, end);
return true;
} else if (ev->key.keyval == GDK_Delete) {
delete_selection();
return true;
} else if (ev->key.keyval == GDK_Tab) {
if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) {
goto_previous_note ();
} else {
goto_next_note ();
}
return true;
} else if (ev->key.keyval == GDK_Up) {
bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
bool fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
change_velocities (true, fine, allow_smush);
} else {
transpose (true, fine, allow_smush);
}
return true;
} else if (ev->key.keyval == GDK_Down) {
bool allow_smush = Keyboard::modifier_state_contains (ev->key.state, Keyboard::SecondaryModifier);
fine = Keyboard::modifier_state_contains (ev->key.state, Keyboard::TertiaryModifier);
if (Keyboard::modifier_state_contains (ev->key.state, Keyboard::PrimaryModifier)) {
change_velocities (false, fine, allow_smush);
} else {
transpose (false, fine, allow_smush);
}
return true;
} else if (ev->key.keyval == GDK_Left) {
nudge_notes (false);
return true;
} else if (ev->key.keyval == GDK_Right) {
nudge_notes (true);
return true;
} else if (ev->key.keyval == GDK_Control_L) {
return true;
} else if (ev->key.keyval == GDK_r) {
/* if we're not step editing, this really doesn't matter */
midi_view()->step_edit_rest ();
return true;
}
return false;
case GDK_KEY_RELEASE:
if (ev->key.keyval == GDK_Alt_L || ev->key.keyval == GDK_Alt_R) {
_mouse_state = None;
return true;
}
return false;
case GDK_BUTTON_PRESS:
if (_mouse_state != SelectTouchDragging && ev->button.button == 1) {
_pressed_button = ev->button.button;
_mouse_state = Pressed;
return true;
}
_pressed_button = ev->button.button;
return true;
case GDK_2BUTTON_PRESS:
return true;
case GDK_ENTER_NOTIFY:
/* FIXME: do this on switch to note tool, too, if the pointer is already in */
Keyboard::magic_widget_grab_focus();
group->grab_focus();
break;
case GDK_MOTION_NOTIFY:
event_x = ev->motion.x;
event_y = ev->motion.y;
group->w2i(event_x, event_y);
// convert event_x to global frame
event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position();
trackview.editor().snap_to(event_frame);
// convert event_frame back to local coordinates relative to position
event_frame -= _region->position();
switch (_mouse_state) {
case Pressed: // Drag start
// Select drag start
if (_pressed_button == 1 && editor.current_mouse_mode() == MouseObject) {
group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
last_x = event_x;
last_y = event_y;
drag_start_x = event_x;
drag_start_y = event_y;
drag_rect = new ArdourCanvas::SimpleRect(*group);
drag_rect->property_x1() = event_x;
drag_rect->property_y1() = event_y;
drag_rect->property_x2() = event_x;
drag_rect->property_y2() = event_y;
drag_rect->property_outline_what() = 0xFF;
drag_rect->property_outline_color_rgba()
= ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get();
drag_rect->property_fill_color_rgba()
= ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get();
_mouse_state = SelectRectDragging;
return true;
// Add note drag start
} else if (editor.current_mouse_mode() == MouseRange) {
group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
last_x = event_x;
last_y = event_y;
drag_start_x = event_x;
drag_start_y = event_y;
drag_rect = new ArdourCanvas::SimpleRect(*group);
drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame);
drag_rect->property_y1() = midi_stream_view()->note_to_y(
midi_stream_view()->y_to_note(event_y));
drag_rect->property_x2() = event_x;
drag_rect->property_y2() = drag_rect->property_y1()
+ floor(midi_stream_view()->note_height());
drag_rect->property_outline_what() = 0xFF;
drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
_mouse_state = AddDragging;
return true;
}
return false;
case SelectRectDragging: // Select drag motion
case AddDragging: // Add note drag motion
if (ev->motion.is_hint) {
int t_x;
int t_y;
GdkModifierType state;
gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state);
event_x = t_x;
event_y = t_y;
}
if (_mouse_state == AddDragging)
event_x = trackview.editor().frame_to_pixel(event_frame);
if (drag_rect) {
if (event_x > drag_start_x)
drag_rect->property_x2() = event_x;
else
drag_rect->property_x1() = event_x;
}
if (drag_rect && _mouse_state == SelectRectDragging) {
if (event_y > drag_start_y)
drag_rect->property_y2() = event_y;
else
drag_rect->property_y1() = event_y;
update_drag_selection(drag_start_x, event_x, drag_start_y, event_y);
}
last_x = event_x;
last_y = event_y;
case SelectTouchDragging:
return false;
default:
break;
}
break;
case GDK_BUTTON_RELEASE:
event_x = ev->motion.x;
event_y = ev->motion.y;
group->w2i(event_x, event_y);
group->ungrab(ev->button.time);
event_frame = trackview.editor().pixel_to_frame(event_x);
if (ev->button.button == 3) {
return false;
} else if (_pressed_button != 1) {
return false;
}
switch (_mouse_state) {
case Pressed: // Clicked
switch (editor.current_mouse_mode()) {
case MouseObject:
case MouseTimeFX:
clear_selection();
break;
case MouseRange:
create_note_at(event_x, event_y, _default_note_length);
break;
default:
break;
}
_mouse_state = None;
break;
case SelectRectDragging: // Select drag done
_mouse_state = None;
delete drag_rect;
drag_rect = 0;
break;
case AddDragging: // Add drag done
_mouse_state = None;
if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
const double x = drag_rect->property_x1();
const double length = trackview.editor().pixel_to_frame(
drag_rect->property_x2() - drag_rect->property_x1());
create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
}
delete drag_rect;
drag_rect = 0;
default: break;
}
default: break;
}
return false;
}
void
MidiRegionView::show_list_editor ()
{
MidiListEditor* mle = new MidiListEditor (trackview.session(), midi_region());
mle->show ();
}
/** Add a note to the model, and the view, at a canvas (click) coordinate.
* \param x horizontal position in pixels
* \param y vertical position in pixels
* \param length duration of the note in beats */
void
MidiRegionView::create_note_at(double x, double y, double length)
{
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MidiStreamView* const view = mtv->midi_view();
double note = midi_stream_view()->y_to_note(y);
assert(note >= 0.0);
assert(note <= 127.0);
// Start of note in frames relative to region start
nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x));
assert(start_frames >= 0);
// Snap length
length = frames_to_beats(
snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames);
const boost::shared_ptr<NoteType> new_note(new NoteType(0,
frames_to_beats(start_frames + _region->start()), length,
(uint8_t)note, 0x40));
view->update_note_range(new_note->note());
MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
cmd->add(new_note);
_model->apply_command(trackview.session(), cmd);
play_midi_note (new_note);
}
void
MidiRegionView::clear_events()
{
clear_selection();
MidiGhostRegion* gr;
for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
gr->clear_events();
}
}
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
delete *i;
}
_events.clear();
_pgm_changes.clear();
_sys_exes.clear();
_optimization_iterator = _events.end();
}
void
MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
{
_model = model;
content_connection.disconnect ();
content_connection = _model->ContentsChanged.connect(sigc::mem_fun(this, &MidiRegionView::redisplay_model));
clear_events ();
if (_enable_display) {
redisplay_model();
}
}
void
MidiRegionView::start_delta_command(string name)
{
if (!_delta_command) {
_delta_command = _model->new_delta_command(name);
}
}
void
MidiRegionView::start_diff_command(string name)
{
if (!_diff_command) {
_diff_command = _model->new_diff_command(name);
}
}
void
MidiRegionView::delta_add_note(const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
{
if (_delta_command) {
_delta_command->add(note);
}
if (selected) {
_marked_for_selection.insert(note);
}
if (show_velocity) {
_marked_for_velocity.insert(note);
}
}
void
MidiRegionView::delta_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
{
if (_delta_command && ev->note()) {
_delta_command->remove(ev->note());
}
}
void
MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
MidiModel::DiffCommand::Property property,
uint8_t val)
{
if (_diff_command) {
_diff_command->change (ev->note(), property, val);
}
}
void
MidiRegionView::diff_add_change (ArdourCanvas::CanvasNoteEvent* ev,
MidiModel::DiffCommand::Property property,
Evoral::MusicalTime val)
{
if (_diff_command) {
_diff_command->change (ev->note(), property, val);
}
}
void
MidiRegionView::apply_delta()
{
if (!_delta_command) {
return;
}
// Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note());
}
_model->apply_command(trackview.session(), _delta_command);
_delta_command = 0;
midi_view()->midi_track()->diskstream()->playlist_modified();
_marked_for_selection.clear();
_marked_for_velocity.clear();
}
void
MidiRegionView::apply_diff ()
{
if (!_diff_command) {
return;
}
_model->apply_command(trackview.session(), _diff_command);
_diff_command = 0;
midi_view()->midi_track()->diskstream()->playlist_modified();
_marked_for_velocity.clear();
}
void
MidiRegionView::apply_delta_as_subcommand()
{
if (!_delta_command) {
return;
}
// Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note());
}
_model->apply_command_as_subcommand(trackview.session(), _delta_command);
_delta_command = 0;
midi_view()->midi_track()->diskstream()->playlist_modified();
_marked_for_selection.clear();
_marked_for_velocity.clear();
}
void
MidiRegionView::apply_diff_as_subcommand()
{
if (!_diff_command) {
return;
}
// Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note());
}
_model->apply_command_as_subcommand(trackview.session(), _diff_command);
_diff_command = 0;
midi_view()->midi_track()->diskstream()->playlist_modified();
_marked_for_selection.clear();
_marked_for_velocity.clear();
}
void
MidiRegionView::abort_command()
{
delete _delta_command;
_delta_command = 0;
delete _diff_command;
_diff_command = 0;
clear_selection();
}
CanvasNoteEvent*
MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
{
if (_optimization_iterator != _events.end()) {
++_optimization_iterator;
}
if (_optimization_iterator != _events.end() && (*_optimization_iterator)->note() == note) {
return *_optimization_iterator;
}
for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
if ((*_optimization_iterator)->note() == note) {
return *_optimization_iterator;
}
}
return 0;
}
void
MidiRegionView::redisplay_model()
{
// Don't redisplay the model if we're currently recording and displaying that
if (_active_notes) {
return;
}
if (!_model) {
cerr << "MidiRegionView::redisplay_model called without a model" << endmsg;
return;
}
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
(*i)->invalidate ();
}
_model->read_lock();
MidiModel::Notes& notes (_model->notes());
_optimization_iterator = _events.begin();
for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
boost::shared_ptr<NoteType> note (*n);
CanvasNoteEvent* cne;
bool visible;
if (note_in_region_range (note, visible)) {
if ((cne = find_canvas_note (note)) != 0) {
cne->validate ();
CanvasNote* cn;
CanvasHit* ch;
if ((cn = dynamic_cast<CanvasNote*>(cne)) != 0) {
update_note (cn);
} else if ((ch = dynamic_cast<CanvasHit*>(cne)) != 0) {
update_hit (ch);
}
if (visible) {
cne->show ();
} else {
cne->hide ();
}
} else {
add_note (note, visible);
}
} else {
if ((cne = find_canvas_note (note)) != 0) {
cne->validate ();
cne->hide ();
}
}
}
/* remove note items that are no longer valid */
for (Events::iterator i = _events.begin(); i != _events.end(); ) {
if (!(*i)->valid ()) {
delete *i;
i = _events.erase (i);
} else {
++i;
}
}
display_sysexes();
display_program_changes();
_model->read_unlock();
_marked_for_selection.clear ();
_marked_for_velocity.clear ();
/* we may have caused _events to contain things out of order (e.g. if a note
moved earlier or later). we don't generally need them in time order, but
make a note that a sort is required for those cases that require it.
*/
_sort_needed = true;
}
void
MidiRegionView::display_program_changes()
{
boost::shared_ptr<Evoral::Control> control = _model->control(MidiPgmChangeAutomation);
if (!control) {
return;
}
Glib::Mutex::Lock lock (control->list()->lock());
uint8_t channel = control->parameter().channel();
for (AutomationList::const_iterator event = control->list()->begin();
event != control->list()->end(); ++event) {
double event_time = (*event)->when;
double program_number = floor((*event)->value + 0.5);
// Get current value of bank select MSB at time of the program change
Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
uint8_t msb = 0;
if (msb_control != 0) {
msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5));
}
// Get current value of bank select LSB at time of the program change
Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
uint8_t lsb = 0;
if (lsb_control != 0) {
lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
}
MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
boost::shared_ptr<MIDI::Name::Patch> patch =
MIDI::Name::MidiPatchManager::instance().find_patch(
_model_name, _custom_device_mode, channel, patch_key);
PCEvent program_change(event_time, uint8_t(program_number), channel);
if (patch != 0) {
add_pgm_change(program_change, patch->name());
} else {
char buf[4];
snprintf(buf, 4, "%d", int(program_number));
add_pgm_change(program_change, buf);
}
}
}
void
MidiRegionView::display_sysexes()
{
for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
Evoral::MusicalTime time = (*i)->time();
assert(time >= 0);
ostringstream str;
str << hex;
for (uint32_t b = 0; b < (*i)->size(); ++b) {
str << int((*i)->buffer()[b]);
if (b != (*i)->size() -1) {
str << " ";
}
}
string text = str.str();
ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
double height = midi_stream_view()->contents_height();
boost::shared_ptr<CanvasSysEx> sysex = boost::shared_ptr<CanvasSysEx>(
new CanvasSysEx(*this, *group, text, height, x, 1.0));
// Show unless program change is beyond the region bounds
if (time - _region->start() >= _region->length() || time < _region->start()) {
sysex->hide();
} else {
sysex->show();
}
_sys_exes.push_back(sysex);
}
}
MidiRegionView::~MidiRegionView ()
{
in_destructor = true;
RegionViewGoingAway (this); /* EMIT_SIGNAL */
if (_active_notes) {
end_write();
}
_selection.clear();
clear_events();
delete _note_group;
delete _delta_command;
}
void
MidiRegionView::region_resized (Change what_changed)
{
RegionView::region_resized(what_changed);
if (what_changed & ARDOUR::PositionChanged) {
set_duration(_region->length(), 0);
if (_enable_display) {
redisplay_model();
}
}
}
void
MidiRegionView::reset_width_dependent_items (double pixel_width)
{
RegionView::reset_width_dependent_items(pixel_width);
assert(_pixel_width == pixel_width);
if (_enable_display) {
redisplay_model();
}
}
void
MidiRegionView::set_height (double height)
{
static const double FUDGE = 2.0;
const double old_height = _height;
RegionView::set_height(height);
_height = height - FUDGE;
apply_note_range(midi_stream_view()->lowest_note(),
midi_stream_view()->highest_note(),
height != old_height + FUDGE);
if (name_pixbuf) {
name_pixbuf->raise_to_top();
}
}
/** Apply the current note range from the stream view
* by repositioning/hiding notes as necessary
*/
void
MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
{
if (!_enable_display) {
return;
}
if (!force && _current_range_min == min && _current_range_max == max) {
return;
}
_current_range_min = min;
_current_range_max = max;
for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) {
CanvasNoteEvent* 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 (CanvasNote* cnote = dynamic_cast<CanvasNote*>(event)) {
const double y1 = midi_stream_view()->note_to_y(note->note());
const double y2 = y1 + floor(midi_stream_view()->note_height());
cnote->property_y1() = y1;
cnote->property_y2() = y2;
} else if (CanvasHit* chit = dynamic_cast<CanvasHit*>(event)) {
double x = trackview.editor().frame_to_pixel(
beats_to_frames(note->time()) - _region->start());
const double diamond_size = midi_stream_view()->note_height() / 2.0;
double y = midi_stream_view()->note_to_y(event->note()->note())
+ ((diamond_size-2.0) / 4.0);
chit->set_height (diamond_size);
chit->move (x - chit->x1(), y - chit->y1());
chit->show ();
}
}
}
GhostRegion*
MidiRegionView::add_ghost (TimeAxisView& tv)
{
CanvasNote* note;
double unit_position = _region->position () / samples_per_unit;
MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
MidiGhostRegion* ghost;
if (mtv && mtv->midi_view()) {
/* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
to allow having midi notes on top of note lines and waveforms.
*/
ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
} else {
ghost = new MidiGhostRegion (tv, trackview, unit_position);
}
ghost->set_height ();
ghost->set_duration (_region->length() / samples_per_unit);
ghosts.push_back (ghost);
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
if ((note = dynamic_cast<CanvasNote*>(*i)) != 0) {
ghost->add_note(note);
}
}
ghost->GoingAway.connect (mem_fun(*this, &MidiRegionView::remove_ghost));
return ghost;
}
/** Begin tracking note state for successive calls to add_event
*/
void
MidiRegionView::begin_write()
{
assert(!_active_notes);
_active_notes = new CanvasNote*[128];
for (unsigned i=0; i < 128; ++i) {
_active_notes[i] = 0;
}
}
/** Destroy note state for add_event
*/
void
MidiRegionView::end_write()
{
delete[] _active_notes;
_active_notes = 0;
_marked_for_selection.clear();
_marked_for_velocity.clear();
}
/** Resolve an active MIDI note (while recording).
*/
void
MidiRegionView::resolve_note(uint8_t note, double end_time)
{
if (midi_view()->note_mode() != Sustained) {
return;
}
if (_active_notes && _active_notes[note]) {
const nframes64_t end_time_frames = beats_to_frames(end_time);
_active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames);
_active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges
_active_notes[note] = 0;
}
}
/** Extend active notes to rightmost edge of region (if length is changed)
*/
void
MidiRegionView::extend_active_notes()
{
if (!_active_notes) {
return;
}
for (unsigned i=0; i < 128; ++i) {
if (_active_notes[i]) {
_active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length());
}
}
}
void
MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
{
if (!trackview.editor().sound_notes()) {
return;
}
RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
assert(route_ui);
route_ui->midi_track()->write_immediate_event(
note->on_event().size(), note->on_event().buffer());
const double note_length_beats = (note->off_event().time() - note->on_event().time());
nframes_t note_length_ms = beats_to_frames(note_length_beats)
* (1000 / (double)route_ui->session().nominal_frame_rate());
Glib::signal_timeout().connect(bind(mem_fun(this, &MidiRegionView::play_midi_note_off), note),
note_length_ms, G_PRIORITY_DEFAULT);
}
bool
MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
{
RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
assert(route_ui);
route_ui->midi_track()->write_immediate_event(
note->off_event().size(), note->off_event().buffer());
return false;
}
bool
MidiRegionView::note_in_region_range(const boost::shared_ptr<NoteType> note, bool& visible) const
{
const nframes64_t note_start_frames = beats_to_frames(note->time());
bool outside = (note_start_frames - _region->start() >= _region->length()) ||
(note_start_frames < _region->start());
visible = (note->note() >= midi_stream_view()->lowest_note()) &&
(note->note() <= midi_stream_view()->highest_note());
return !outside;
}
void
MidiRegionView::update_note (CanvasNote* ev)
{
boost::shared_ptr<NoteType> note = ev->note();
const nframes64_t note_start_frames = beats_to_frames(note->time());
const nframes64_t note_end_frames = beats_to_frames(note->end_time());
const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
const double y1 = midi_stream_view()->note_to_y(note->note());
const double note_endpixel =
trackview.editor().frame_to_pixel(note_end_frames - _region->start());
ev->property_x1() = x;
ev->property_y1() = y1;
if (note->length() > 0) {
ev->property_x2() = note_endpixel;
} else {
ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
}
ev->property_y2() = y1 + floor(midi_stream_view()->note_height());
if (note->length() == 0) {
if (_active_notes) {
assert(note->note() < 128);
// If this note is already active there's a stuck note,
// finish the old note rectangle
if (_active_notes[note->note()]) {
CanvasNote* const old_rect = _active_notes[note->note()];
boost::shared_ptr<NoteType> old_note = old_rect->note();
old_rect->property_x2() = x;
old_rect->property_outline_what() = (guint32) 0xF;
}
_active_notes[note->note()] = ev;
}
/* outline all but right edge */
ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
} else {
/* outline all edges */
ev->property_outline_what() = (guint32) 0xF;
}
}
void
MidiRegionView::update_hit (CanvasHit* ev)
{
boost::shared_ptr<NoteType> note = ev->note();
const nframes64_t note_start_frames = beats_to_frames(note->time());
const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
const double diamond_size = midi_stream_view()->note_height() / 2.0;
const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
ev->move_to (x, y);
}
/** Add a MIDI note to the view (with length).
*
* If in sustained mode, notes with length 0 will be considered active
* notes, and resolve_note should be called when the corresponding note off
* event arrives, to properly display the note.
*/
void
MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
{
CanvasNoteEvent* event = 0;
assert(note->time() >= 0);
assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
if (midi_view()->note_mode() == Sustained) {
CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
update_note (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 = midi_stream_view()->note_height() / 2.0;
CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
update_hit (ev_diamond);
event = ev_diamond;
} else {
event = 0;
}
if (event) {
if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
note_selected(event, true);
}
if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
event->show_velocity();
}
event->on_channel_selection_change(_last_channel_selection);
_events.push_back(event);
if (visible) {
event->show();
} else {
event->hide ();
}
}
}
void
MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
Evoral::MusicalTime pos, Evoral::MusicalTime len)
{
boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
start_delta_command (_("step add"));
delta_add_note (new_note, true, false);
apply_delta();
/* potentially extend region to hold new note */
nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
nframes64_t region_end = _region->position() + _region->length() - 1;
if (end_frame > region_end) {
_region->set_length (end_frame, this);
} else {
redisplay_model ();
}
}
void
MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
{
assert(program.time >= 0);
ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
double height = midi_stream_view()->contents_height();
boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
new CanvasProgramChange(*this, *group,
displaytext,
height,
x, 1.0,
_model_name,
_custom_device_mode,
program.time, program.channel, program.value));
// Show unless program change is beyond the region bounds
if (program.time - _region->start() >= _region->length() || program.time < _region->start()) {
pgm_change->hide();
} else {
pgm_change->show();
}
_pgm_changes.push_back(pgm_change);
}
void
MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
{
cerr << "getting patch key at " << time << " for channel " << channel << endl;
Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
float msb = -1.0;
if (msb_control != 0) {
msb = int(msb_control->get_float(true, time));
cerr << "got msb " << msb;
}
Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
float lsb = -1.0;
if (lsb_control != 0) {
lsb = lsb_control->get_float(true, time);
cerr << " got lsb " << lsb;
}
Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
float program_number = -1.0;
if (program_control != 0) {
program_number = program_control->get_float(true, time);
cerr << " got program " << program_number << endl;
}
key.msb = (int) floor(msb + 0.5);
key.lsb = (int) floor(lsb + 0.5);
key.program_number = (int) floor(program_number + 0.5);
assert(key.is_sane());
}
void
MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
{
// TODO: Get the real event here and alter them at the original times
Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
if (msb_control != 0) {
msb_control->set_float(float(new_patch.msb), true, old_program.time);
}
// TODO: Get the real event here and alter them at the original times
Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK);
boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
if (lsb_control != 0) {
lsb_control->set_float(float(new_patch.lsb), true, old_program.time);
}
Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0);
boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
assert(program_control != 0);
program_control->set_float(float(new_patch.program_number), true, old_program.time);
redisplay_model();
}
void
MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch)
{
PCEvent program_change_event(program.event_time(), program.program(), program.channel());
alter_program_change(program_change_event, new_patch);
}
void
MidiRegionView::previous_program(CanvasProgramChange& program)
{
MIDI::Name::PatchPrimaryKey key;
get_patch_key_at(program.event_time(), program.channel(), key);
boost::shared_ptr<MIDI::Name::Patch> patch =
MIDI::Name::MidiPatchManager::instance().previous_patch(
_model_name,
_custom_device_mode,
program.channel(),
key);
PCEvent program_change_event(program.event_time(), program.program(), program.channel());
if (patch) {
alter_program_change(program_change_event, patch->patch_primary_key());
}
}
void
MidiRegionView::next_program(CanvasProgramChange& program)
{
MIDI::Name::PatchPrimaryKey key;
get_patch_key_at(program.event_time(), program.channel(), key);
boost::shared_ptr<MIDI::Name::Patch> patch =
MIDI::Name::MidiPatchManager::instance().next_patch(
_model_name,
_custom_device_mode,
program.channel(),
key);
PCEvent program_change_event(program.event_time(), program.program(), program.channel());
if (patch) {
alter_program_change(program_change_event, patch->patch_primary_key());
}
}
void
MidiRegionView::delete_selection()
{
if (_selection.empty()) {
return;
}
start_delta_command (_("delete selection"));
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
if ((*i)->selected()) {
_delta_command->remove((*i)->note());
}
}
_selection.clear();
apply_delta ();
}
void
MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev)
{
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
if ((*i)->selected() && (*i) != ev) {
(*i)->selected(false);
(*i)->hide_velocity();
}
}
_selection.clear();
}
void
MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
{
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
if ((*i) != ev) {
Selection::iterator tmp = i;
++tmp;
(*i)->selected (false);
_selection.erase (i);
i = tmp;
} else {
++i;
}
}
/* don't bother with removing this regionview from the editor selection,
since we're about to add another note, and thus put/keep this
regionview in the editor selection.
*/
if (!ev->selected()) {
add_to_selection (ev);
}
}
void
MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
{
if (!add) {
clear_selection_except(ev);
}
if (!extend) {
if (!ev->selected()) {
add_to_selection (ev);
}
} else {
/* find end of latest note selected, select all between that and the start of "ev" */
Evoral::MusicalTime earliest = DBL_MAX;
Evoral::MusicalTime latest = 0;
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
if ((*i)->note()->end_time() > latest) {
latest = (*i)->note()->end_time();
}
if ((*i)->note()->time() < earliest) {
earliest = (*i)->note()->time();
}
}
if (ev->note()->end_time() > latest) {
latest = ev->note()->end_time();
}
if (ev->note()->time() < earliest) {
earliest = ev->note()->time();
}
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
/* find notes entirely within OR spanning the earliest..latest range */
if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
add_to_selection (*i);
}
#if 0
/* if events were guaranteed to be time sorted, we could do this.
but as of sept 10th 2009, they no longer are.
*/
if ((*i)->note()->time() > latest) {
break;
}
#endif
}
}
}
void
MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
{
remove_from_selection (ev);
}
void
MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
{
if (x1 > x2) {
swap (x1, x2);
}
if (y1 > y2) {
swap (y1, y2);
}
// TODO: Make this faster by storing the last updated selection rect, and only
// adjusting things that are in the area that appears/disappeared.
// We probably need a tree to be able to find events in O(log(n)) time.
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
/* check if any corner of the note is inside the rect
Notes:
1) this is computing "touched by", not "contained by" the rect.
2) this does not require that events be sorted in time.
*/
const double ix1 = (*i)->x1();
const double ix2 = (*i)->x2();
const double iy1 = (*i)->y1();
const double iy2 = (*i)->y2();
if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
(ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
(ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
(ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
// Inside rectangle
if (!(*i)->selected()) {
add_to_selection (*i);
}
} else if ((*i)->selected()) {
// Not inside rectangle
remove_from_selection (*i);
}
}
}
void
MidiRegionView::remove_from_selection (CanvasNoteEvent* ev)
{
Selection::iterator i = _selection.find (ev);
if (i != _selection.end()) {
_selection.erase (i);
}
ev->selected (false);
ev->hide_velocity ();
if (_selection.empty()) {
PublicEditor& editor (trackview.editor());
editor.get_selection().remove (this);
}
}
void
MidiRegionView::add_to_selection (CanvasNoteEvent* ev)
{
bool add_mrv_selection = false;
if (_selection.empty()) {
add_mrv_selection = true;
}
if (_selection.insert (ev).second) {
ev->selected (true);
play_midi_note ((ev)->note());
}
if (add_mrv_selection) {
PublicEditor& editor (trackview.editor());
editor.get_selection().add (this);
}
}
void
MidiRegionView::move_selection(double dx, double dy)
{
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
(*i)->move_event(dx, dy);
}
}
void
MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
{
assert (!_selection.empty());
uint8_t lowest_note_in_selection = 127;
uint8_t highest_note_in_selection = 0;
uint8_t highest_note_difference = 0;
// find highest and lowest notes first
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
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);
}
/*
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 (highest_note_in_selection + dnote > 127) {
highest_note_difference = highest_note_in_selection - 127;
}
start_diff_command(_("move notes"));
for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
nframes64_t start_frames = beats_to_frames((*i)->note()->time());
if (dt >= 0) {
start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt));
} else {
start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt));
}
Evoral::MusicalTime new_time = frames_to_beats(start_frames);
if (new_time < 0) {
continue;
}
diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
uint8_t original_pitch = (*i)->note()->note();
uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
// keep notes in standard midi range
clamp_to_0_127(new_pitch);
// keep original pitch if note is dragged outside valid midi range
if ((original_pitch != 0 && new_pitch == 0)
|| (original_pitch != 127 && new_pitch == 127)) {
new_pitch = original_pitch;
}
lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
}
apply_diff();
// 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()) {
midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
}
}
nframes64_t
MidiRegionView::snap_pixel_to_frame(double x)
{
PublicEditor& editor = trackview.editor();
// x is region relative, convert it to global absolute frames
nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
editor.snap_to(frame);
return frame - _region->position(); // convert back to region relative
}
nframes64_t
MidiRegionView::snap_frame_to_frame(nframes64_t x)
{
PublicEditor& editor = trackview.editor();
// x is region relative, convert it to global absolute frames
nframes64_t frame = x + _region->position();
editor.snap_to(frame);
return frame - _region->position(); // convert back to region relative
}
double
MidiRegionView::snap_to_pixel(double x)
{
return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x));
}
double
MidiRegionView::get_position_pixels()
{
nframes64_t region_frame = get_position();
return trackview.editor().frame_to_pixel(region_frame);
}
double
MidiRegionView::get_end_position_pixels()
{
nframes64_t frame = get_position() + get_duration ();
return trackview.editor().frame_to_pixel(frame);
}
nframes64_t
MidiRegionView::beats_to_frames(double beats) const
{
return _time_converter.to(beats);
}
double
MidiRegionView::frames_to_beats(nframes64_t frames) const
{
return _time_converter.from(frames);
}
void
MidiRegionView::begin_resizing (bool /*at_front*/)
{
_resize_data.clear();
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
// only insert CanvasNotes into the map
if (note) {
NoteResizeData *resize_data = new NoteResizeData();
resize_data->canvas_note = note;
// 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
uint32_t fill_color = UINT_RGBA_CHANGE_A(
ARDOUR_UI::config()->canvasvar_MidiNoteSelected.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(
CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
fill_color,
0.85);
resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
resize_data->resize_rect = resize_rect;
_resize_data.push_back(resize_data);
}
}
}
void
MidiRegionView::update_resizing (bool at_front, double delta_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;
double current_x;
if (at_front) {
if (relative) {
current_x = canvas_note->x1() + delta_x;
} else {
// x is in track relative, transform it to region relative
current_x = delta_x - get_position_pixels();
}
} else {
if (relative) {
current_x = canvas_note->x2() + delta_x;
} else {
// x is in track relative, transform it to region relative
current_x = delta_x - get_end_position_pixels ();
}
}
if (at_front) {
resize_rect->property_x1() = snap_to_pixel(current_x);
resize_rect->property_x2() = canvas_note->x2();
} else {
resize_rect->property_x2() = snap_to_pixel(current_x);
resize_rect->property_x1() = canvas_note->x1();
}
}
}
void
MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
{
start_diff_command(_("resize notes"));
for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
CanvasNote* canvas_note = (*i)->canvas_note;
SimpleRect* resize_rect = (*i)->resize_rect;
const double region_start = get_position_pixels();
double current_x;
if (at_front) {
if (relative) {
current_x = canvas_note->x1() + delta_x;
} else {
// x is in track relative, transform it to region relative
current_x = region_start + delta_x;
}
} else {
if (relative) {
current_x = canvas_note->x2() + delta_x;
} else {
// x is in track relative, transform it to region relative
current_x = region_start + delta_x;
}
}
current_x = snap_pixel_to_frame (current_x);
current_x = frames_to_beats (current_x);
if (at_front && current_x < canvas_note->note()->end_time()) {
diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
double len = canvas_note->note()->time() - current_x;
len += canvas_note->note()->length();
if (len > 0) {
/* XXX convert to beats */
diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
}
}
if (!at_front) {
double len = current_x - canvas_note->note()->time();
if (len > 0) {
/* XXX convert to beats */
diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
}
}
delete resize_rect;
delete (*i);
}
_resize_data.clear();
apply_diff();
}
void
MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
{
uint8_t new_velocity;
if (relative) {
new_velocity = event->note()->velocity() + velocity;
clamp_to_0_127(new_velocity);
} else {
new_velocity = velocity;
}
diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
}
void
MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
{
uint8_t new_note;
if (relative) {
new_note = event->note()->note() + note;
} else {
new_note = note;
}
clamp_to_0_127 (new_note);
diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
}
void
MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
{
bool change_start = false;
bool change_length = false;
Evoral::MusicalTime new_start;
Evoral::MusicalTime new_length;
/* NOTE: the semantics of the two delta arguments are slightly subtle:
front_delta: if positive - move the start of the note later in time (shortening it)
if negative - move the start of the note earlier in time (lengthening it)
end_delta: if positive - move the end of the note later in time (lengthening it)
if negative - move the end of the note earlier in time (shortening it)
*/
if (front_delta) {
if (front_delta < 0) {
if (event->note()->time() < -front_delta) {
new_start = 0;
} else {
new_start = event->note()->time() + front_delta; // moves earlier
}
/* start moved toward zero, so move the end point out to where it used to be.
Note that front_delta is negative, so this increases the length.
*/
new_length = event->note()->length() - front_delta;
change_start = true;
change_length = true;
} else {
Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
if (new_pos < event->note()->end_time()) {
new_start = event->note()->time() + front_delta;
/* start moved toward the end, so move the end point back to where it used to be */
new_length = event->note()->length() - front_delta;
change_start = true;
change_length = true;
}
}
}
if (end_delta) {
bool can_change = true;
if (end_delta < 0) {
if (event->note()->length() < -end_delta) {
can_change = false;
}
}
if (can_change) {
new_length = event->note()->length() + end_delta;
change_length = true;
}
}
if (change_start) {
diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
}
if (change_length) {
diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
}
}
void
MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
{
Evoral::MusicalTime new_time;
if (relative) {
if (delta < 0.0) {
if (event->note()->time() < -delta) {
new_time = 0;
} else {
new_time = event->note()->time() + delta;
}
} else {
new_time = event->note()->time() + delta;
}
} else {
new_time = delta;
}
diff_add_change (event, MidiModel::DiffCommand::StartTime, new_time);
}
void
MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush)
{
int8_t delta;
if (_selection.empty()) {
return;
}
if (fine) {
delta = 1;
} else {
delta = 10;
}
if (!up) {
delta = -delta;
}
if (!allow_smush) {
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
if ((*i)->note()->velocity() + delta == 0 || (*i)->note()->velocity() + delta == 127) {
return;
}
}
}
start_diff_command(_("change velocities"));
for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
Selection::iterator next = i;
++next;
change_note_velocity (*i, delta, true);
i = next;
}
apply_diff();
}
void
MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
{
if (_selection.empty()) {
return;
}
int8_t delta;
if (fine) {
delta = 1;
} else {
delta = 12;
}
if (!up) {
delta = -delta;
}
if (!allow_smush) {
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
if (!up) {
if ((int8_t) (*i)->note()->note() + delta <= 0) {
return;
}
} else {
if ((int8_t) (*i)->note()->note() + delta > 127) {
return;
}
}
}
}
start_diff_command (_("transpose"));
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
Selection::iterator next = i;
++next;
change_note_note (*i, delta, true);
i = next;
}
apply_diff ();
}
void
MidiRegionView::change_note_lengths (bool fine, bool shorter, bool start, bool end)
{
Evoral::MusicalTime delta;
if (fine) {
delta = 1.0/128.0;
} else {
/* grab the current grid distance */
bool success;
delta = trackview.editor().get_grid_type_as_beats (success, _region->position());
if (!success) {
/* XXX cannot get grid type as beats ... should always be possible ... FIX ME */
cerr << "Grid type not available as beats - TO BE FIXED\n";
return;
}
}
if (shorter) {
delta = -delta;
}
start_diff_command (_("change note lengths"));
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
Selection::iterator next = i;
++next;
/* note the negation of the delta for start */
trim_note (*i, (start ? -delta : 0), (end ? delta : 0));
i = next;
}
apply_diff ();
}
void
MidiRegionView::nudge_notes (bool forward)
{
if (_selection.empty()) {
return;
}
/* pick a note as the point along the timeline to get the nudge distance.
its not necessarily the earliest note, so we may want to pull the notes out
into a vector and sort before using the first one.
*/
nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time());
nframes64_t unused;
nframes64_t distance;
if ((distance = trackview.editor().get_nudge_distance (ref_point, unused)) == 0) {
/* no nudge distance set - use grid */
nframes64_t next_pos = ref_point;
if (forward) {
/* XXX need check on max_frames, but that needs max_frames64 or something */
next_pos += 1;
} else {
if (next_pos == 0) {
return;
}
next_pos -= 1;
}
cerr << "ref point was " << ref_point << " next was " << next_pos;
trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false);
distance = ref_point - next_pos;
cerr << " final is " << next_pos << " distance = " << distance << endl;
}
if (distance == 0) {
return;
}
Evoral::MusicalTime delta = frames_to_beats (fabs (distance));
if (!forward) {
delta = -delta;
}
start_diff_command (_("nudge"));
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
Selection::iterator next = i;
++next;
change_note_time (*i, delta, true);
i = next;
}
apply_diff ();
}
void
MidiRegionView::change_channel(uint8_t channel)
{
start_diff_command(_("change channel"));
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
diff_add_change (*i, MidiModel::DiffCommand::Channel, channel);
}
apply_diff();
}
void
MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev)
{
if (_mouse_state == SelectTouchDragging) {
note_selected(ev, true);
}
PublicEditor& editor (trackview.editor());
char buf[4];
snprintf (buf, sizeof (buf), "%d", (int) ev->note()->note());
//editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
editor.show_verbose_canvas_cursor_with (buf);
}
void
MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*)
{
PublicEditor& editor (trackview.editor());
editor.hide_verbose_canvas_cursor ();
}
void
MidiRegionView::switch_source(boost::shared_ptr<Source> src)
{
boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
if (msrc)
display_model(msrc->model());
}
void
MidiRegionView::set_frame_color()
{
if (frame) {
if (_selected && should_show_selection) {
frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_SelectedFrameBase.get();
} else {
frame->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiFrameBase.get();
}
}
}
void
MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask)
{
switch (mode) {
case AllChannels:
case FilterChannels:
_force_channel = -1;
break;
case ForceChannel:
_force_channel = mask;
mask = 0xFFFF; // Show all notes as active (below)
};
// Update notes for selection
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
(*i)->on_channel_selection_change(mask);
}
_last_channel_selection = mask;
}
void
MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode)
{
_model_name = model;
_custom_device_mode = custom_device_mode;
redisplay_model();
}
void
MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
{
if (_selection.empty()) {
return;
}
PublicEditor& editor (trackview.editor());
switch (op) {
case Cut:
case Copy:
editor.get_cut_buffer().add (selection_as_cut_buffer());
break;
default:
break;
}
start_delta_command();
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
switch (op) {
case Copy:
break;
case Cut:
delta_remove_note (*i);
break;
case Clear:
break;
}
}
apply_delta();
}
MidiCutBuffer*
MidiRegionView::selection_as_cut_buffer () const
{
Notes notes;
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
notes.insert (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get()))));
}
MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
cb->set (notes);
return cb;
}
void
MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
{
if (mcb.empty()) {
return;
}
start_delta_command (_("paste"));
Evoral::MusicalTime beat_delta;
Evoral::MusicalTime paste_pos_beats;
Evoral::MusicalTime duration;
Evoral::MusicalTime end_point;
duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time();
paste_pos_beats = frames_to_beats (pos - _region->position());
beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats;
paste_pos_beats = 0;
_selection.clear ();
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 (paste_pos_beats + copied_note->time() - beat_delta);
/* make all newly added notes selected */
delta_add_note (copied_note, true);
end_point = copied_note->end_time();
}
paste_pos_beats += duration;
}
/* if we pasted past the current end of the region, extend the region */
nframes64_t end_frame = _region->position() + beats_to_frames (end_point);
nframes64_t region_end = _region->position() + _region->length() - 1;
if (end_frame > region_end) {
trackview.session().begin_reversible_command (_("paste"));
XMLNode& before (_region->get_state());
_region->set_length (end_frame, this);
trackview.session().add_command (new MementoCommand<Region>(*_region, &before, &_region->get_state()));
}
apply_delta ();
}
struct EventNoteTimeEarlyFirstComparator {
bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) {
return a->note()->time() < b->note()->time();
}
};
void
MidiRegionView::time_sort_events ()
{
if (!_sort_needed) {
return;
}
EventNoteTimeEarlyFirstComparator cmp;
_events.sort (cmp);
_sort_needed = false;
}
void
MidiRegionView::goto_next_note ()
{
// nframes64_t pos = -1;
bool use_next = false;
if (_events.back()->selected()) {
return;
}
time_sort_events ();
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
if ((*i)->selected()) {
use_next = true;
continue;
} else if (use_next) {
unique_select (*i);
// pos = _region->position() + beats_to_frames ((*i)->note()->time());
return;
}
}
/* use the first one */
unique_select (_events.front());
}
void
MidiRegionView::goto_previous_note ()
{
// nframes64_t pos = -1;
bool use_next = false;
if (_events.front()->selected()) {
return;
}
time_sort_events ();
for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) {
if ((*i)->selected()) {
use_next = true;
continue;
} else if (use_next) {
unique_select (*i);
// pos = _region->position() + beats_to_frames ((*i)->note()->time());
return;
}
}
/* use the last one */
unique_select (*(_events.rbegin()));
}
void
MidiRegionView::selection_as_notelist (Notes& selected)
{
time_sort_events ();
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
if ((*i)->selected()) {
selected.insert ((*i)->note());
}
}
}