2013-04-05 17:16:33 -04:00
|
|
|
/*
|
|
|
|
Copyright (C) 2007 Paul Davis
|
|
|
|
Author: David 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 <iostream>
|
|
|
|
|
|
|
|
#include "gtkmm2ext/keyboard.h"
|
|
|
|
|
2014-11-30 19:57:15 -05:00
|
|
|
#include "evoral/Note.hpp"
|
|
|
|
|
2013-04-05 17:16:33 -04:00
|
|
|
#include "canvas/text.h"
|
|
|
|
|
|
|
|
#include "note_base.h"
|
|
|
|
#include "public_editor.h"
|
|
|
|
#include "editing_syms.h"
|
|
|
|
#include "keyboard.h"
|
2014-11-30 19:57:15 -05:00
|
|
|
#include "midi_region_view.h"
|
2013-04-05 17:16:33 -04:00
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace Gtkmm2ext;
|
|
|
|
using ARDOUR::MidiModel;
|
|
|
|
using namespace ArdourCanvas;
|
|
|
|
|
|
|
|
/// dividing the hue circle in 16 parts, hand adjusted for equal look, courtesy Thorsten Wilms
|
|
|
|
const uint32_t NoteBase::midi_channel_colors[16] = {
|
|
|
|
0xd32d2dff, 0xd36b2dff, 0xd3972dff, 0xd3d12dff,
|
|
|
|
0xa0d32dff, 0x7dd32dff, 0x2dd45eff, 0x2dd3c4ff,
|
|
|
|
0x2da5d3ff, 0x2d6fd3ff, 0x432dd3ff, 0x662dd3ff,
|
|
|
|
0x832dd3ff, 0xa92dd3ff, 0xd32dbfff, 0xd32d67ff
|
|
|
|
};
|
|
|
|
|
2016-10-16 12:19:02 -04:00
|
|
|
bool NoteBase::_color_init = false;
|
|
|
|
uint32_t NoteBase::_selected_mod_col = 0;
|
|
|
|
uint32_t NoteBase::_selected_outline_col = 0;
|
|
|
|
uint32_t NoteBase::_selected_col = 0;
|
|
|
|
uint32_t NoteBase::_min_col = 0;
|
|
|
|
uint32_t NoteBase::_mid_col = 0;
|
|
|
|
uint32_t NoteBase::_max_col = 0;
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::set_colors ()
|
|
|
|
{
|
|
|
|
_selected_mod_col = UIConfiguration::instance().color_mod ("midi note selected", "midi note");
|
|
|
|
_selected_outline_col = UIConfiguration::instance().color ("midi note selected outline");
|
|
|
|
_selected_col = UIConfiguration::instance().color ("midi note selected");
|
|
|
|
_min_col = UIConfiguration::instance().color_mod ("midi note min", "midi note");
|
|
|
|
_mid_col = UIConfiguration::instance().color_mod ("midi note mid", "midi note");
|
|
|
|
_max_col = UIConfiguration::instance().color_mod ("midi note max", "midi note");
|
|
|
|
}
|
|
|
|
|
2013-04-05 17:16:33 -04:00
|
|
|
NoteBase::NoteBase(MidiRegionView& region, bool with_events, const boost::shared_ptr<NoteType> note)
|
|
|
|
: _region(region)
|
|
|
|
, _item (0)
|
|
|
|
, _text(0)
|
|
|
|
, _state(None)
|
|
|
|
, _note(note)
|
|
|
|
, _with_events (with_events)
|
|
|
|
, _selected(false)
|
|
|
|
, _valid (true)
|
|
|
|
, _mouse_x_fraction (-1.0)
|
|
|
|
, _mouse_y_fraction (-1.0)
|
|
|
|
{
|
2016-10-16 12:19:02 -04:00
|
|
|
if (!_color_init) {
|
|
|
|
NoteBase::set_colors();
|
|
|
|
_color_init = true;
|
|
|
|
}
|
2013-04-05 17:16:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
NoteBase::~NoteBase()
|
|
|
|
{
|
Replace static PBD::Signal in NoteBase with direct call to MidiRegionView
NoteBaseDeleted signal is static so each MidiRegionView(MRV) gets notified
about the deletion of each NodeBase instance even if it is contained in another
MRV
The NoteBase and MRV classes are currently coupled anyway, so this change uses
the reference to the MRV parent to directly call the parent when the NoteBase
is deleted. This is all in the GUI thread so I'm not sure why a PBD::Signal was
being used?
If the MRV class is the only reference holder to the NoteBase class
then I'm not sure if a callback is needed, perhaps the MRV should just remove
the note from the selection before deleting it but I'm not that familiar with
the code.
Signal emission/calls static NoteBaseDeleted signal vs direct with 10540
NoteBase instances.
static:
After Load Session: 6360638
After Unload Session: 12221026(5860388)
direct:
After load Session: 10540
After unload Session: 21080
Session Load/Unload time in master, debug/release with ~10000 Notes(seconds)
Load Debug: 32, 26
Unload Debug: 83
Load Release 32, 20, 42
Unload Release 26, 25
Session Load/Unload time with direct call debug/release(seconds)
Load Debug: 21.7, 18.1
Unload Debug: 69.4, 71
Load Release: 22.6, 13.4, 17.7
Unload Release: 24, 23.5
This is not a large Session, 1500 regions, 10000 notes so there is probably
some other funky stuff going on that needs fixing.
2015-10-14 06:37:07 -04:00
|
|
|
_region.note_deleted (this);
|
2013-04-05 17:16:33 -04:00
|
|
|
|
|
|
|
delete _text;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::set_item (Item* item)
|
|
|
|
{
|
|
|
|
_item = item;
|
|
|
|
_item->set_data ("notebase", this);
|
|
|
|
|
|
|
|
if (_with_events) {
|
|
|
|
_item->Event.connect (sigc::mem_fun (*this, &NoteBase::event_handler));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::invalidate ()
|
|
|
|
{
|
|
|
|
_valid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::validate ()
|
|
|
|
{
|
|
|
|
_valid = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::show_velocity()
|
|
|
|
{
|
|
|
|
if (!_text) {
|
|
|
|
_text = new Text (_item->parent ());
|
|
|
|
_text->set_ignore_events (true);
|
2015-01-02 09:44:54 -05:00
|
|
|
_text->set_color (UIConfiguration::instance().color_mod ("midi note velocity text", "midi note velocity text"));
|
2013-04-05 17:16:33 -04:00
|
|
|
_text->set_alignment (Pango::ALIGN_CENTER);
|
|
|
|
}
|
|
|
|
|
|
|
|
_text->set_x_position ((x0() + x1()) / 2);
|
|
|
|
_text->set_y_position ((y0() + y1()) / 2);
|
|
|
|
ostringstream velo(ios::ate);
|
|
|
|
velo << int(_note->velocity());
|
|
|
|
_text->set (velo.str ());
|
|
|
|
_text->show();
|
|
|
|
_text->raise_to_top();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::hide_velocity()
|
|
|
|
{
|
|
|
|
delete _text;
|
|
|
|
_text = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::on_channel_selection_change(uint16_t selection)
|
|
|
|
{
|
|
|
|
// make note change its color if its channel is not marked active
|
|
|
|
if ( (selection & (1 << _note->channel())) == 0 ) {
|
2017-07-17 12:34:35 -04:00
|
|
|
const Gtkmm2ext::Color inactive_ch = UIConfiguration::instance().color ("midi note inactive channel");
|
2016-06-19 11:04:23 -04:00
|
|
|
set_fill_color(inactive_ch);
|
|
|
|
set_outline_color(calculate_outline(inactive_ch, _selected));
|
2013-04-05 17:16:33 -04:00
|
|
|
} else {
|
|
|
|
// set the color according to the notes selection state
|
|
|
|
set_selected(_selected);
|
|
|
|
}
|
|
|
|
// this forces the item to update..... maybe slow...
|
|
|
|
_item->hide();
|
|
|
|
_item->show();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::on_channel_change(uint8_t channel)
|
|
|
|
{
|
|
|
|
_region.note_selected(this, true);
|
|
|
|
_region.change_channel(channel);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::set_selected(bool selected)
|
|
|
|
{
|
|
|
|
if (!_note) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_selected = selected;
|
2016-06-19 14:21:33 -04:00
|
|
|
|
|
|
|
const uint32_t base_col = base_color();
|
2016-06-19 11:04:23 -04:00
|
|
|
set_fill_color (base_col);
|
2015-10-04 14:51:05 -04:00
|
|
|
|
2016-06-19 11:04:23 -04:00
|
|
|
set_outline_color(calculate_outline(base_col, _selected));
|
2013-04-05 17:16:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#define SCALE_USHORT_TO_UINT8_T(x) ((x) / 257)
|
|
|
|
|
|
|
|
uint32_t
|
|
|
|
NoteBase::base_color()
|
|
|
|
{
|
|
|
|
using namespace ARDOUR;
|
|
|
|
|
|
|
|
ColorMode mode = _region.color_mode();
|
|
|
|
|
|
|
|
const uint8_t min_opacity = 15;
|
|
|
|
uint8_t opacity = std::max(min_opacity, uint8_t(_note->velocity() + _note->velocity()));
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case TrackColor:
|
|
|
|
{
|
2016-06-19 11:04:23 -04:00
|
|
|
const uint32_t region_color = _region.midi_stream_view()->get_region_color();
|
2016-10-16 12:19:02 -04:00
|
|
|
return UINT_INTERPOLATE (UINT_RGBA_CHANGE_A (region_color, opacity), _selected_col,
|
2014-06-09 23:28:32 -04:00
|
|
|
0.5);
|
2013-04-05 17:16:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
case ChannelColors:
|
2015-10-04 14:51:05 -04:00
|
|
|
return UINT_INTERPOLATE (UINT_RGBA_CHANGE_A (NoteBase::midi_channel_colors[_note->channel()], opacity),
|
2016-10-16 12:19:02 -04:00
|
|
|
_selected_col, 0.5);
|
2013-04-05 17:16:33 -04:00
|
|
|
|
|
|
|
default:
|
|
|
|
return meter_style_fill_color(_note->velocity(), selected());
|
|
|
|
};
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
NoteBase::set_mouse_fractions (GdkEvent* ev)
|
|
|
|
{
|
|
|
|
double ix, iy;
|
|
|
|
bool set_cursor = false;
|
|
|
|
|
|
|
|
switch (ev->type) {
|
|
|
|
case GDK_MOTION_NOTIFY:
|
|
|
|
ix = ev->motion.x;
|
|
|
|
iy = ev->motion.y;
|
|
|
|
set_cursor = true;
|
|
|
|
break;
|
|
|
|
case GDK_ENTER_NOTIFY:
|
|
|
|
ix = ev->crossing.x;
|
|
|
|
iy = ev->crossing.y;
|
|
|
|
set_cursor = true;
|
|
|
|
break;
|
|
|
|
case GDK_BUTTON_PRESS:
|
|
|
|
case GDK_BUTTON_RELEASE:
|
|
|
|
ix = ev->button.x;
|
|
|
|
iy = ev->button.y;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
_mouse_x_fraction = -1.0;
|
|
|
|
_mouse_y_fraction = -1.0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-04-12 21:46:44 -04:00
|
|
|
boost::optional<ArdourCanvas::Rect> bbox = _item->bounding_box ();
|
2013-04-05 17:16:33 -04:00
|
|
|
assert (bbox);
|
|
|
|
|
|
|
|
_item->canvas_to_item (ix, iy);
|
|
|
|
/* XXX: CANVAS */
|
|
|
|
/* hmm, something wrong here. w2i should give item-local coordinates
|
|
|
|
but it doesn't. for now, finesse this.
|
|
|
|
*/
|
|
|
|
ix = ix - bbox.get().x0;
|
|
|
|
iy = iy - bbox.get().y0;
|
|
|
|
|
|
|
|
/* fraction of width/height */
|
|
|
|
double xf;
|
|
|
|
double yf;
|
|
|
|
bool notify = false;
|
|
|
|
|
|
|
|
xf = ix / bbox.get().width ();
|
|
|
|
yf = iy / bbox.get().height ();
|
|
|
|
|
|
|
|
if (xf != _mouse_x_fraction || yf != _mouse_y_fraction) {
|
|
|
|
notify = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
_mouse_x_fraction = xf;
|
|
|
|
_mouse_y_fraction = yf;
|
|
|
|
|
|
|
|
if (notify) {
|
2015-11-29 06:57:04 -05:00
|
|
|
if (big_enough_to_trim()) {
|
|
|
|
_region.note_mouse_position (_mouse_x_fraction, _mouse_y_fraction, set_cursor);
|
|
|
|
} else {
|
|
|
|
/* pretend the mouse is in the middle, because this is not big enough
|
|
|
|
to trim right now.
|
|
|
|
*/
|
|
|
|
_region.note_mouse_position (0.5, 0.5, set_cursor);
|
|
|
|
}
|
2013-04-05 17:16:33 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
NoteBase::event_handler (GdkEvent* ev)
|
|
|
|
{
|
2014-12-08 23:00:00 -05:00
|
|
|
PublicEditor& editor = _region.get_time_axis_view().editor();
|
|
|
|
if (!editor.internal_editing()) {
|
2013-04-05 17:16:33 -04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (ev->type) {
|
|
|
|
case GDK_ENTER_NOTIFY:
|
|
|
|
_region.note_entered (this);
|
2014-11-14 02:28:15 -05:00
|
|
|
set_mouse_fractions (ev);
|
2013-04-05 17:16:33 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case GDK_LEAVE_NOTIFY:
|
|
|
|
set_mouse_fractions (ev);
|
|
|
|
_region.note_left (this);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GDK_MOTION_NOTIFY:
|
|
|
|
set_mouse_fractions (ev);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GDK_BUTTON_PRESS:
|
|
|
|
set_mouse_fractions (ev);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GDK_BUTTON_RELEASE:
|
|
|
|
set_mouse_fractions (ev);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-12-08 23:00:00 -05:00
|
|
|
return editor.canvas_note_event (ev, _item);
|
2013-04-05 17:16:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
NoteBase::mouse_near_ends () const
|
|
|
|
{
|
|
|
|
return (_mouse_x_fraction >= 0.0 && _mouse_x_fraction < 0.25) ||
|
|
|
|
(_mouse_x_fraction >= 0.75 && _mouse_x_fraction < 1.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
NoteBase::big_enough_to_trim () const
|
|
|
|
{
|
2017-07-01 12:42:24 -04:00
|
|
|
return (x1() - x0()) > 10;
|
2013-04-05 17:16:33 -04:00
|
|
|
}
|
|
|
|
|