further steps towards MidiRegionView outside the Editor

This commit is contained in:
Paul Davis 2023-11-25 15:25:29 -07:00
parent 1f903e402a
commit 946fe23e60
22 changed files with 530 additions and 365 deletions

View File

@ -22,6 +22,7 @@
#include "pbd/stacktrace.h"
#include "ardour/rc_configuration.h"
#include "ardour/quantize.h"
#include "gtkmm2ext/bindings.h"
@ -30,6 +31,7 @@
#include "editor_drag.h"
#include "keyboard.h"
#include "midi_region_view.h"
#include "quantize_dialog.h"
#include "selection.h"
#include "selection_memento.h"
#include "verbose_cursor.h"
@ -100,10 +102,9 @@ EditingContext::EditingContext ()
, bbt_bar_helper_on (0)
, _visible_canvas_width (0)
, _visible_canvas_height (0)
, quantize_dialog (nullptr)
{
grid_type_strings = I18N (_grid_type_strings);
_verbose_cursor = new VerboseCursor (*this);
}
EditingContext::~EditingContext()
@ -1528,3 +1529,29 @@ EditingContext::compute_bbt_ruler_scale (samplepos_t lower, samplepos_t upper)
bbt_ruler_scale = (EditingContext::BBTRulerScale) suggested_scale;
}
Quantize*
EditingContext::get_quantize_op ()
{
if (!quantize_dialog) {
quantize_dialog = new QuantizeDialog (*this);
}
quantize_dialog->present ();
int r = quantize_dialog->run ();
quantize_dialog->hide ();
if (r != Gtk::RESPONSE_OK) {
return nullptr;
}
return new Quantize (quantize_dialog->snap_start(),
quantize_dialog->snap_end(),
quantize_dialog->start_grid_size(),
quantize_dialog->end_grid_size(),
quantize_dialog->strength(),
quantize_dialog->swing(),
quantize_dialog->threshold());
}

View File

@ -37,6 +37,7 @@
#include "temporal/timeline.h"
#include "ardour/midi_operator.h"
#include "ardour/session_handle.h"
#include "ardour/types.h"
@ -278,6 +279,8 @@ public:
PBD::Signal0<void> MouseModeChanged;
/* MIDI actions, proxied to selected MidiRegionView(s) */
ARDOUR::Quantize* get_quantize_op ();
virtual void apply_midi_note_edit_op (ARDOUR::MidiOperator& op, const RegionSelection& rs) = 0;
void midi_action (void (MidiRegionView::*method)());
virtual std::vector<MidiRegionView*> filter_to_unique_midi_region_views (RegionSelection const & ms) const = 0;
@ -415,6 +418,8 @@ public:
double _visible_canvas_width;
double _visible_canvas_height; ///< height of the visible area of the track canvas
QuantizeDialog* quantize_dialog;
};

View File

@ -418,7 +418,6 @@ Editor::Editor ()
, _show_touched_automation (false)
, _control_point_toggled_on_press (false)
, _stepping_axis_view (0)
, quantize_dialog (0)
, _main_menu_disabler (0)
, domain_bounce_info (nullptr)
{

View File

@ -1247,7 +1247,6 @@ private:
void normalize_region ();
void adjust_region_gain (bool up);
void reset_region_gain ();
ARDOUR::Quantize* get_quantize_op ();
void apply_midi_note_edit_op (ARDOUR::MidiOperator& op, const RegionSelection& rs);
void set_tempo_curve_range (double& max, double& min) const;
void quantize_region ();
@ -2327,7 +2326,6 @@ private:
void update_bring_in_message (Gtk::Label* label, uint32_t n, uint32_t total, std::string name);
void bring_all_sources_into_session ();
QuantizeDialog* quantize_dialog;
MainMenuDisabler* _main_menu_disabler;
std::vector<MidiRegionView*> filter_to_unique_midi_region_views (RegionSelection const & ms) const;

View File

@ -89,6 +89,8 @@ Editor::initialize_canvas ()
*/
no_scroll_group = new ArdourCanvas::Container (_track_canvas->root());
_verbose_cursor = new VerboseCursor (*this);
ArdourCanvas::ScrollGroup* hsg;
ArdourCanvas::ScrollGroup* hg;
ArdourCanvas::ScrollGroup* cg;

View File

@ -111,7 +111,6 @@
#include "note.h"
#include "paste_context.h"
#include "patch_change_dialog.h"
#include "quantize_dialog.h"
#include "region_gain_line.h"
#include "route_time_axis.h"
#include "selection.h"
@ -6243,31 +6242,6 @@ Editor::quantize_regions (const RegionSelection& rs)
delete quant;
}
Quantize*
Editor::get_quantize_op ()
{
if (!quantize_dialog) {
quantize_dialog = new QuantizeDialog (*this);
}
quantize_dialog->present ();
int r = quantize_dialog->run ();
quantize_dialog->hide ();
if (r != Gtk::RESPONSE_OK) {
return nullptr;
}
return new Quantize (quantize_dialog->snap_start(),
quantize_dialog->snap_end(),
quantize_dialog->start_grid_size(),
quantize_dialog->end_grid_size(),
quantize_dialog->strength(),
quantize_dialog->swing(),
quantize_dialog->threshold());
}
void
Editor::legatize_region (bool shrink_only)
{

View File

@ -75,6 +75,7 @@ void
MidiClipEditorBox::set_session (Session* s)
{
SessionHandlePtr::set_session (s);
editor->set_session (s);
}
void

View File

@ -24,6 +24,7 @@
#include "midi_cue_editor.h"
#include "ui_config.h"
#include "verbose_cursor.h"
using namespace ARDOUR;
using namespace ArdourCanvas;
@ -34,6 +35,8 @@ MidiCueEditor::MidiCueEditor()
, horizontal_adjustment (0.0, 0.0, 1e16)
{
build_canvas ();
_verbose_cursor = new VerboseCursor (*this);
}
MidiCueEditor::~MidiCueEditor ()
@ -44,10 +47,10 @@ void
MidiCueEditor::build_canvas ()
{
_canvas_viewport = new ArdourCanvas::GtkCanvasViewport (horizontal_adjustment, vertical_adjustment);
_canvas = _canvas_viewport->canvas ();
_canvas->set_background_color (UIConfiguration::instance().color ("arrange base"));
// _canvas->use_nsglview (UIConfiguration::instance().get_nsgl_view_mode () == NSGLHiRes);
_canvas = _canvas_viewport->canvas ();
_canvas->set_background_color (0xff00000a); // UIConfiguration::instance().color ("arrange base"));
dynamic_cast<ArdourCanvas::GtkCanvas*>(_canvas)->use_nsglview (UIConfiguration::instance().get_nsgl_view_mode () == NSGLHiRes);
/* scroll group for items that should not automatically scroll
* (e.g verbose cursor). It shares the canvas coordinate space.
@ -139,3 +142,29 @@ MidiCueEditor::current_page_samples() const
return (samplecnt_t) _visible_canvas_width* samples_per_pixel;
}
void
MidiCueEditor::apply_midi_note_edit_op (ARDOUR::MidiOperator& op, const RegionSelection& rs)
{
}
PBD::Command*
MidiCueEditor::apply_midi_note_edit_op_to_region (ARDOUR::MidiOperator& op, MidiRegionView& mrv)
{
#if 0
Evoral::Sequence<Temporal::Beats>::Notes selected;
mrv.selection_as_notelist (selected, true);
if (selected.empty()) {
return 0;
}
vector<Evoral::Sequence<Temporal::Beats>::Notes> v;
v.push_back (selected);
timepos_t pos = mrv.midi_region()->source_position();
return op (mrv.midi_region()->model(), pos.beats(), v);
#endif
return nullptr;
}

View File

@ -54,6 +54,9 @@ class MidiCueEditor : public CueEditor
int32_t get_grid_beat_divisions (Editing::GridType gt) { return 1; }
int32_t get_grid_music_divisions (Editing::GridType gt, uint32_t event_state) { return 1; }
void apply_midi_note_edit_op (ARDOUR::MidiOperator& op, const RegionSelection& rs);
PBD::Command* apply_midi_note_edit_op_to_region (ARDOUR::MidiOperator& op, MidiRegionView& mrv);
protected:
Temporal::timepos_t snap_to_grid (Temporal::timepos_t const & start,
Temporal::RoundMode direction,

View File

@ -326,6 +326,59 @@ MidiRegionView::connect_to_diskstream ()
gui_context());
}
std::string
MidiRegionView::get_modifier_name () const
{
const bool opaque = _region->opaque() || trackview.layer_display () == Stacked;
std::string mod_name;
if (_dragging) {
mod_name = "dragging region";
} else if (editing_context.internal_editing()) {
if (!opaque || _region->muted ()) {
mod_name = "editable region";
}
} else {
if (!opaque || _region->muted ()) {
mod_name = "transparent region base";
}
}
return mod_name;
}
GhostRegion*
MidiRegionView::add_ghost (TimeAxisView& tv)
{
double unit_position = editing_context.time_to_pixel (_region->position ());
MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
MidiGhostRegion* ghost;
if (mtv && mtv->midi_view()) {
return 0;
} else {
AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*>(&tv);
if (atv && atv->parameter() == Evoral::Parameter (MidiVelocityAutomation)) {
ghost = new VelocityGhostRegion (*this, tv, trackview, unit_position);
} else {
ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
}
}
ghost->set_colors ();
ghost->set_height ();
ghost->set_duration (_region->length().samples() / samples_per_pixel);
for (auto const & i : _events) {
ghost->add_note (i.second);
}
ghosts.push_back (ghost);
return ghost;
}
bool
MidiRegionView::canvas_group_event(GdkEvent* ev)
{
@ -1625,36 +1678,6 @@ MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
view_changed ();
}
GhostRegion*
MidiRegionView::add_ghost (TimeAxisView& tv)
{
double unit_position = editing_context.time_to_pixel (_region->position ());
MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
MidiGhostRegion* ghost;
if (mtv && mtv->midi_view()) {
return 0;
} else {
AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*>(&tv);
if (atv && atv->parameter() == Evoral::Parameter (MidiVelocityAutomation)) {
ghost = new VelocityGhostRegion (*this, tv, trackview, unit_position);
} else {
ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
}
}
ghost->set_colors ();
ghost->set_height ();
ghost->set_duration (_region->length().samples() / samples_per_pixel);
for (auto const & i : _events) {
ghost->add_note (i.second);
}
ghosts.push_back (ghost);
return ghost;
}
/** Begin tracking note state for successive calls to add_event
*/
@ -3805,25 +3828,10 @@ MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, boo
}
}
uint32_t
MidiRegionView::get_fill_color() const
{
const bool opaque = _region->opaque() || trackview.layer_display () == Stacked;
std::string mod_name;
if (_dragging) {
mod_name = "dragging region";
} else if (editing_context.internal_editing()) {
if (!opaque || _region->muted ()) {
mod_name = "editable region";
}
} else {
if (!opaque || _region->muted ()) {
mod_name = "transparent region base";
}
}
Gtkmm2ext::Color c;
if (_selected) {
c = UIConfiguration::instance().color ("selected region base");
@ -3833,6 +3841,8 @@ MidiRegionView::get_fill_color() const
c = fill_color;
}
string mod_name = get_modifier_name();
if (mod_name.empty ()) {
return c;
} else {
@ -4742,7 +4752,7 @@ MidiRegionView::quantize_selected_notes ()
return;
}
trackview.editor().apply_midi_note_edit_op (*quant, rs);
editing_context.apply_midi_note_edit_op (*quant, rs);
delete quant;
}

View File

@ -118,6 +118,7 @@ public:
inline ARDOUR::ColorMode color_mode() const { return midi_view()->color_mode(); }
std::string get_modifier_name() const;
uint32_t get_fill_color() const;
void color_handler ();

View File

@ -29,8 +29,8 @@
#include <gtkmm2ext/gtk_ui.h>
#include "canvas/line_set.h"
#include "canvas/rectangle.h"
#include "canvas/line_set.h"
#include "ardour/midi_region.h"
#include "ardour/midi_source.h"
@ -64,14 +64,7 @@ using namespace Editing;
MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
: StreamView (tv)
, note_range_adjustment(0.0f, 0.0f, 0.0f)
, _range_dirty(false)
, _range_sum_cache(-1.0)
, _lowest_note(UIConfiguration::instance().get_default_lower_midi_note())
, _highest_note(UIConfiguration::instance().get_default_upper_midi_note())
, _data_note_min(60)
, _data_note_max(71)
, _note_lines (0)
, MidiViewBackground (_canvas_group)
, _updates_suspended (false)
{
/* use a dedicated group for MIDI regions (on top of the grid and lines) */
@ -82,25 +75,16 @@ MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
/* put the note lines in the timeaxisview's group, so it
can be put below ghost regions from MIDI underlays
*/
_note_lines = new ArdourCanvas::LineSet (_canvas_group, ArdourCanvas::LineSet::Horizontal);
_note_lines->Event.connect(
sigc::bind(sigc::mem_fun(_trackview.editor(),
&PublicEditor::canvas_stream_view_event),
_note_lines, &_trackview));
_note_lines->lower_to_bottom();
color_handler ();
UIConfiguration::instance().ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
UIConfiguration::instance().ParameterChanged.connect(sigc::mem_fun(*this, &MidiStreamView::parameter_changed));
note_range_adjustment.set_page_size(_highest_note - _lowest_note);
note_range_adjustment.set_value(_lowest_note);
note_range_adjustment.signal_value_changed().connect(
sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
}
MidiStreamView::~MidiStreamView ()
@ -110,11 +94,7 @@ MidiStreamView::~MidiStreamView ()
void
MidiStreamView::parameter_changed (string const & param)
{
if (param == X_("max-note-height")) {
apply_note_range (_lowest_note, _highest_note, true);
} else {
StreamView::parameter_changed (param);
}
StreamView::parameter_changed (param);
}
RegionView*
@ -243,21 +223,6 @@ MidiStreamView::update_contents_metrics(std::shared_ptr<Region> r)
}
}
bool
MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
{
bool dirty = false;
if (min < _data_note_min) {
_data_note_min = min;
dirty = true;
}
if (max > _data_note_max) {
_data_note_max = max;
dirty = true;
}
return dirty;
}
void
MidiStreamView::set_layer_display (LayerDisplay d)
{
@ -314,140 +279,7 @@ MidiStreamView::redisplay_track ()
}
void
MidiStreamView::update_contents_height ()
{
StreamView::update_contents_height();
_note_lines->set_extent (ArdourCanvas::COORD_MAX);
apply_note_range (lowest_note(), highest_note(), true);
}
void
MidiStreamView::draw_note_lines()
{
if (!_note_lines || _updates_suspended) {
return;
}
double y;
double prev_y = 0.;
Gtkmm2ext::Color black = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
Gtkmm2ext::Color white = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
Gtkmm2ext::Color outline = UIConfiguration::instance().color ("piano roll black outline");
Gtkmm2ext::Color color;
ArdourCanvas::LineSet::ResetRAII lr (*_note_lines);
if (child_height() < 140 || note_height() < 3) {
/* track is too small for note lines, or there are too many */
return;
}
/* do this is order of highest ... lowest since that matches the
* coordinate system in which y=0 is at the top
*/
for (int i = highest_note() + 1; i >= lowest_note(); --i) {
y = floor (note_to_y (i));
/* add a thicker line/bar which covers the entire vertical height of this note. */
switch (i % 12) {
case 1:
case 3:
case 6:
case 8:
case 10:
color = black;
break;
case 4:
case 11:
/* this is the line actually corresponding to the division between B & C and E & F */
_note_lines->add_coord (y, 1.0, outline);
/* fallthrough */
default:
color = white;
break;
}
double h = y - prev_y;
double middle = y + (h/2.0);
if (!fmod (h, 2.) && !fmod (middle, 1.)) {
middle += 0.5;
}
if (middle >= 0 && h > 1.0) {
_note_lines->add_coord (middle, h, color);
}
prev_y = y;
}
}
void
MidiStreamView::set_note_range(VisibleNoteRange r)
{
if (r == FullRange) {
_lowest_note = 0;
_highest_note = 127;
} else {
_lowest_note = _data_note_min;
_highest_note = _data_note_max;
}
apply_note_range(_lowest_note, _highest_note, true);
}
void
MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
{
_highest_note = highest;
_lowest_note = lowest;
float uiscale = UIConfiguration::instance().get_ui_scale();
uiscale = expf (uiscale) / expf (1.f);
const int mnh = UIConfiguration::instance().get_max_note_height();
int const max_note_height = std::max<int> (mnh, mnh * uiscale);
int const range = _highest_note - _lowest_note;
int const available_note_range = floor (child_height() / max_note_height);
int additional_notes = available_note_range - range;
/* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
for (int i = 0; i < additional_notes; i++){
if (i % 2 && _highest_note < 127){
_highest_note++;
}
else if (i % 2) {
_lowest_note--;
}
else if (_lowest_note > 0){
_lowest_note--;
}
else {
_highest_note++;
}
}
note_range_adjustment.set_page_size (_highest_note - _lowest_note);
note_range_adjustment.set_value (_lowest_note);
draw_note_lines();
if (to_region_views) {
apply_note_range_to_regions ();
}
NoteRangeChanged(); /* EMIT SIGNAL*/
}
void
MidiStreamView::apply_note_range_to_regions ()
MidiStreamView::apply_note_range_to_children ()
{
if (!_updates_suspended) {
for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
@ -456,13 +288,6 @@ MidiStreamView::apply_note_range_to_regions ()
}
}
void
MidiStreamView::update_note_range(uint8_t note_num)
{
_data_note_min = min(_data_note_min, note_num);
_data_note_max = max(_data_note_max, note_num);
}
void
MidiStreamView::setup_rec_box ()
{
@ -559,8 +384,9 @@ MidiStreamView::setup_rec_box ()
void
MidiStreamView::color_handler ()
{
MidiViewBackground::color_handler ();
_region_group->set_render_with_alpha (UIConfiguration::instance().modifier ("region alpha").a());
draw_note_lines ();
if (_trackview.is_midi_track()) {
canvas_rect->set_fill_color (UIConfiguration::instance().color_mod ("midi track base", "midi track base"));
@ -573,34 +399,6 @@ MidiStreamView::color_handler ()
}
}
void
MidiStreamView::note_range_adjustment_changed()
{
double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
int lowest = (int) floor(note_range_adjustment.get_value());
int highest;
if (sum == _range_sum_cache) {
//cerr << "cached" << endl;
highest = (int) floor(sum);
} else {
//cerr << "recalc" << endl;
highest = lowest + (int) floor(note_range_adjustment.get_page_size());
_range_sum_cache = sum;
}
if (lowest == _lowest_note && highest == _highest_note) {
return;
}
//cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
//cerr << " val=" << v_zoom_adjustment.get_value() << " page=" << v_zoom_adjustment.get_page_size() << " sum=" << v_zoom_adjustment.get_value() + v_zoom_adjustment.get_page_size() << endl;
_lowest_note = lowest;
_highest_note = highest;
apply_note_range(lowest, highest, true);
}
void
MidiStreamView::update_rec_box ()
{
@ -618,21 +416,6 @@ MidiStreamView::update_rec_box ()
mrv->extend_active_notes ();
}
uint8_t
MidiStreamView::y_to_note (double y) const
{
int const n = ((contents_height() - y) / contents_height() * (double)contents_note_range())
+ lowest_note();
if (n < 0) {
return 0;
} else if (n > 127) {
return 127;
}
/* min due to rounding and/or off-by-one errors */
return min ((uint8_t) n, highest_note());
}
/** Suspend updates to the regions' note ranges and our
* note lines until resume_updates() is called.
@ -652,7 +435,7 @@ MidiStreamView::resume_updates ()
_updates_suspended = false;
draw_note_lines ();
apply_note_range_to_regions ();
apply_note_range_to_children ();
_canvas_group->redraw ();
}
@ -710,3 +493,4 @@ MidiStreamView::get_regions_with_selected_data (RegionSelection& rs)
}
}
}

View File

@ -28,6 +28,7 @@
#include "ardour/location.h"
#include "enums.h"
#include "midi_view_background.h"
#include "streamview.h"
#include "time_axis_view_item.h"
#include "route_time_axis.h"
@ -57,7 +58,7 @@ class RegionSelection;
class CrossfadeView;
class Selection;
class MidiStreamView : public StreamView
class MidiStreamView : public StreamView, public MidiViewBackground
{
public:
MidiStreamView (MidiTimeAxisView&);
@ -66,50 +67,18 @@ public:
void get_inverted_selectables (Selection&, std::list<Selectable* >& results);
void get_regions_with_selected_data (RegionSelection&);
enum VisibleNoteRange {
FullRange,
ContentsRange
};
Gtk::Adjustment note_range_adjustment;
void set_note_range(VisibleNoteRange r);
inline uint8_t lowest_note() const { return _lowest_note; }
inline uint8_t highest_note() const { return _highest_note; }
void update_note_range(uint8_t note_num);
void set_layer_display (LayerDisplay);
//bool can_change_layer_display() const { return false; } // revert this change for now. Although stacked view is weirdly implemented wrt the "scroomer", it is still necessary to be able to manage layered regions.
void redisplay_track ();
inline double contents_height() const {
double contents_height() const {
return (child_height() - TimeAxisViewItem::NAME_HIGHLIGHT_SIZE - 2);
}
inline double note_to_y(uint8_t note) const {
return contents_height() - (note + 1 - lowest_note()) * note_height() + 1;
}
uint8_t y_to_note(double y) const;
inline double note_height() const {
return contents_height() / (double)contents_note_range();
}
inline uint8_t contents_note_range() const {
return highest_note() - lowest_note() + 1;
}
sigc::signal<void> NoteRangeChanged;
RegionView* create_region_view (std::shared_ptr<ARDOUR::Region>, bool, bool);
bool paste (Temporal::timepos_t const & pos, const Selection& selection, PasteContext& ctx);
void apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views);
void suspend_updates ();
void resume_updates ();
@ -120,11 +89,11 @@ public:
protected:
void setup_rec_box ();
void update_rec_box ();
bool updates_suspended() const { return _updates_suspended; }
ArdourCanvas::Container* _region_group;
private:
RegionView* add_region_view_internal (
std::shared_ptr<ARDOUR::Region>,
bool wait_for_waves,
@ -133,24 +102,12 @@ private:
void display_region(MidiRegionView* region_view, bool load_model);
void display_track (std::shared_ptr<ARDOUR::Track> tr);
void update_contents_height ();
void draw_note_lines();
bool update_data_note_range(uint8_t min, uint8_t max);
void update_contents_metrics(std::shared_ptr<ARDOUR::Region> r);
void update_contents_metrics (std::shared_ptr<ARDOUR::Region> r);
void color_handler ();
void note_range_adjustment_changed();
void apply_note_range_to_regions ();
void apply_note_range_to_children ();
bool _range_dirty;
double _range_sum_cache;
uint8_t _lowest_note; ///< currently visible
uint8_t _highest_note; ///< currently visible
uint8_t _data_note_min; ///< in data
uint8_t _data_note_max; ///< in data
ArdourCanvas::LineSet* _note_lines;
/** true if updates to the note lines and regions are currently suspended */
bool _updates_suspended;
};

View File

@ -0,0 +1,272 @@
/*
* Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
* Copyright (C) 2007 Doug McLain <doug@nostar.net>
* Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
* Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
* Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
* Copyright (C) 2016 Nick Mainsbridge <mainsbridge@gmail.com>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "canvas/line_set.h"
#include "midi_view_background.h"
#include "ui_config.h"
#include "pbd/i18n.h"
using namespace std;
MidiViewBackground::MidiViewBackground (ArdourCanvas::Item* parent)
: note_range_adjustment (0.0f, 0.0f, 0.0f)
, _range_dirty (false)
, _range_sum_cache (-1.0)
, _lowest_note (UIConfiguration::instance().get_default_lower_midi_note())
, _highest_note (UIConfiguration::instance().get_default_upper_midi_note())
, _data_note_min (60)
, _data_note_max (71)
, _note_lines (new ArdourCanvas::LineSet (parent, ArdourCanvas::LineSet::Horizontal))
{
_note_lines->lower_to_bottom();
color_handler ();
UIConfiguration::instance().ColorsChanged.connect(sigc::mem_fun(*this, &MidiViewBackground::color_handler));
UIConfiguration::instance().ParameterChanged.connect(sigc::mem_fun(*this, &MidiViewBackground::parameter_changed));
note_range_adjustment.set_page_size(_highest_note - _lowest_note);
note_range_adjustment.set_value(_lowest_note);
note_range_adjustment.signal_value_changed().connect(sigc::mem_fun(*this, &MidiViewBackground::note_range_adjustment_changed));
}
MidiViewBackground::~MidiViewBackground()
{
}
void
MidiViewBackground::parameter_changed (std::string const & param)
{
if (param == X_("max-note-height")) {
apply_note_range (_lowest_note, _highest_note, true);
}
}
void
MidiViewBackground::color_handler ()
{
draw_note_lines ();
}
void
MidiViewBackground::note_range_adjustment_changed()
{
double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
int lowest = (int) floor(note_range_adjustment.get_value());
int highest;
if (sum == _range_sum_cache) {
//cerr << "cached" << endl;
highest = (int) floor(sum);
} else {
//cerr << "recalc" << endl;
highest = lowest + (int) floor(note_range_adjustment.get_page_size());
_range_sum_cache = sum;
}
if (lowest == _lowest_note && highest == _highest_note) {
return;
}
//cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
//cerr << " val=" << v_zoom_adjustment.get_value() << " page=" << v_zoom_adjustment.get_page_size() << " sum=" << v_zoom_adjustment.get_value() + v_zoom_adjustment.get_page_size() << endl;
_lowest_note = lowest;
_highest_note = highest;
apply_note_range (lowest, highest, true);
}
uint8_t
MidiViewBackground::y_to_note (double y) const
{
int const n = ((contents_height() - y) / contents_height() * (double)contents_note_range())
+ lowest_note();
if (n < 0) {
return 0;
} else if (n > 127) {
return 127;
}
/* min due to rounding and/or off-by-one errors */
return min ((uint8_t) n, highest_note());
}
void
MidiViewBackground::update_note_range(uint8_t note_num)
{
_data_note_min = min(_data_note_min, note_num);
_data_note_max = max(_data_note_max, note_num);
}
void
MidiViewBackground::update_contents_height ()
{
_note_lines->set_extent (ArdourCanvas::COORD_MAX);
apply_note_range (lowest_note(), highest_note(), true);
}
void
MidiViewBackground::draw_note_lines()
{
if (updates_suspended()) {
return;
}
double y;
double prev_y = 0.;
Gtkmm2ext::Color black = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
Gtkmm2ext::Color white = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
Gtkmm2ext::Color outline = UIConfiguration::instance().color ("piano roll black outline");
Gtkmm2ext::Color color;
ArdourCanvas::LineSet::ResetRAII lr (*_note_lines);
if (contents_height() < 140 || note_height() < 3) {
/* context is too small for note lines, or there are too many */
return;
}
/* do this is order of highest ... lowest since that matches the
* coordinate system in which y=0 is at the top
*/
for (int i = highest_note() + 1; i >= lowest_note(); --i) {
y = floor (note_to_y (i));
/* add a thicker line/bar which covers the entire vertical height of this note. */
switch (i % 12) {
case 1:
case 3:
case 6:
case 8:
case 10:
color = black;
break;
case 4:
case 11:
/* this is the line actually corresponding to the division between B & C and E & F */
_note_lines->add_coord (y, 1.0, outline);
/* fallthrough */
default:
color = white;
break;
}
double h = y - prev_y;
double middle = y + (h/2.0);
if (!fmod (h, 2.) && !fmod (middle, 1.)) {
middle += 0.5;
}
if (middle >= 0 && h > 1.0) {
_note_lines->add_coord (middle, h, color);
}
prev_y = y;
}
}
void
MidiViewBackground::set_note_range(VisibleNoteRange r)
{
if (r == FullRange) {
_lowest_note = 0;
_highest_note = 127;
} else {
_lowest_note = _data_note_min;
_highest_note = _data_note_max;
}
apply_note_range(_lowest_note, _highest_note, true);
}
void
MidiViewBackground::apply_note_range(uint8_t lowest, uint8_t highest, bool to_children)
{
_highest_note = highest;
_lowest_note = lowest;
float uiscale = UIConfiguration::instance().get_ui_scale();
uiscale = expf (uiscale) / expf (1.f);
const int mnh = UIConfiguration::instance().get_max_note_height();
int const max_note_height = std::max<int> (mnh, mnh * uiscale);
int const range = _highest_note - _lowest_note;
int const available_note_range = floor (contents_height() / max_note_height);
int additional_notes = available_note_range - range;
/* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
for (int i = 0; i < additional_notes; i++){
if (i % 2 && _highest_note < 127){
_highest_note++;
}
else if (i % 2) {
_lowest_note--;
}
else if (_lowest_note > 0){
_lowest_note--;
}
else {
_highest_note++;
}
}
note_range_adjustment.set_page_size (_highest_note - _lowest_note);
note_range_adjustment.set_value (_lowest_note);
draw_note_lines();
if (to_children) {
apply_note_range_to_children ();
}
NoteRangeChanged(); /* EMIT SIGNAL*/
}
bool
MidiViewBackground::update_data_note_range (uint8_t min, uint8_t max)
{
bool dirty = false;
if (min < _data_note_min) {
_data_note_min = min;
dirty = true;
}
if (max > _data_note_max) {
_data_note_max = max;
dirty = true;
}
return dirty;
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
* Copyright (C) 2007 Doug McLain <doug@nostar.net>
* Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
* Copyright (C) 2016-2017 Robin Gareus <robin@gareus.org>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __gtk2_ardour_midi_view_background_h__
#define __gtk2_ardour_midi_view_background_h__
#include <cstdint>
#include <gtkmm/adjustment.h>
namespace ArdourCanvas {
class Item;
class LineSet;
}
class MidiViewBackground
{
public:
MidiViewBackground (ArdourCanvas::Item* parent);
virtual ~MidiViewBackground ();
Gtk::Adjustment note_range_adjustment;
enum VisibleNoteRange {
FullRange,
ContentsRange
};
void set_note_range (VisibleNoteRange r);
inline uint8_t lowest_note() const { return _lowest_note; }
inline uint8_t highest_note() const { return _highest_note; }
void update_note_range(uint8_t note_num);
virtual double contents_height() const = 0;
double note_to_y (uint8_t note) const {
return contents_height() - (note + 1 - lowest_note()) * note_height() + 1;
}
uint8_t y_to_note(double y) const;
uint8_t contents_note_range() const {
return highest_note() - lowest_note() + 1;
}
double note_height() const {
return contents_height() / (double)contents_note_range();
}
sigc::signal<void> NoteRangeChanged;
void apply_note_range (uint8_t lowest, uint8_t highest, bool to_children);
protected:
bool _range_dirty;
double _range_sum_cache;
uint8_t _lowest_note; ///< currently visible
uint8_t _highest_note; ///< currently visible
uint8_t _data_note_min; ///< in data
uint8_t _data_note_max; ///< in data
ArdourCanvas::LineSet* _note_lines;
void color_handler ();
void parameter_changed (std::string const &);
void note_range_adjustment_changed();
void update_contents_height ();
void draw_note_lines();
bool update_data_note_range(uint8_t min, uint8_t max);
virtual void apply_note_range_to_children () = 0;
virtual bool updates_suspended() const { return false; }
};
#endif /* __gtk2_ardour_midi_view_background_h__ */

View File

@ -460,9 +460,6 @@ public:
virtual void queue_redisplay_track_views () = 0;
virtual ARDOUR::Quantize* get_quantize_op () = 0;
virtual void apply_midi_note_edit_op (ARDOUR::MidiOperator& op, const RegionSelection& rs) = 0;
virtual void set_tempo_curve_range (double& max, double& min) const = 0;
/// Singleton instance, set up by Editor::Editor()

View File

@ -26,7 +26,7 @@
#include "pbd/convert.h"
#include "quantize_dialog.h"
#include "public_editor.h"
#include "editing_context.h"
#include "pbd/i18n.h"
#include "pbd/integer_division.h"
@ -71,7 +71,7 @@ static const int _grid_beats[] = {
std::vector<std::string> QuantizeDialog::grid_strings;
QuantizeDialog::QuantizeDialog (PublicEditor& e)
QuantizeDialog::QuantizeDialog (EditingContext& e)
: ArdourDialog (_("Quantize"), false, false)
, editor (e)
, strength_adjustment (100.0, 0.0, 100.0, 1.0, 10.0)

View File

@ -38,12 +38,12 @@ namespace ARDOUR {
class MidiModel;
};
class PublicEditor;
class EditingContext;
class QuantizeDialog : public ArdourDialog
{
public:
QuantizeDialog (PublicEditor&);
QuantizeDialog (EditingContext&);
~QuantizeDialog ();
Temporal::Beats start_grid_size() const;
@ -55,7 +55,7 @@ public:
float swing () const;
private:
PublicEditor& editor;
EditingContext& editor;
Gtk::ComboBoxText start_grid_combo;
Gtk::ComboBoxText end_grid_combo;

View File

@ -147,11 +147,13 @@ TriggerPage::TriggerPage ()
table->attach (_audio_trig_box, col, col + 1, 0, 1, Gtk::FILL, Gtk::SHRINK | Gtk::FILL);
++col;
#ifdef MIDI_PROPERTIES_BOX_IMPLEMENTED
col = 2;
table->attach (_midi_trig_box, col, col + 1, 0, 1, Gtk::FILL, Gtk::SHRINK);
++col;
#endif
col = 3;
table->attach (_midi_trim_box, col, col + 1, 0, 1, Gtk::EXPAND, Gtk::SHRINK);
++col;
_parameter_box.pack_start (*table);
@ -282,6 +284,7 @@ TriggerPage::set_session (Session* s)
_audio_trig_box.set_session (s);
_midi_trig_box.set_session (s);
_midi_trim_box.set_session (s);
update_title ();
start_updating ();
@ -386,6 +389,7 @@ TriggerPage::selection_changed ()
_audio_trig_box.hide ();
_midi_trig_box.hide ();
_midi_trim_box.hide ();
_parameter_box.hide ();
@ -404,6 +408,9 @@ TriggerPage::selection_changed ()
} else {
_midi_trig_box.set_trigger (ref);
_midi_trig_box.show ();
// _midi_trim_box.set_trigger (ref);
_midi_trim_box.show ();
}
}
_parameter_box.show ();

View File

@ -133,10 +133,10 @@ private:
#if REGION_PROPERTIES_BOX_TODO
AudioRegionOperationsBox _audio_ops_box;
AudioClipEditorBox _audio_trim_box;
#endif
MidiRegionOperationsBox _midi_ops_box;
MidiClipEditorBox _midi_trim_box;
#endif
RouteProcessorSelection _selection;
std::list<TriggerStrip*> _strips;

View File

@ -173,6 +173,7 @@ gtk2_ardour_sources = [
'midi_time_axis.cc',
'midi_tracer.cc',
'midi_velocity_dialog.cc',
'midi_view_background.cc',
'mini_timeline.cc',
'missing_file_dialog.cc',
'missing_filesource_dialog.cc',

View File

@ -26,6 +26,8 @@
#include "temporal/beats.h"
#include "evoral/Sequence.h"
#include "ardour/libardour_visibility.h"
namespace PBD {
class Command;
}