diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2009-08-27 03:09:30 +0000 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2009-08-27 03:09:30 +0000 |
commit | 3845af6ce92ef15637ffb09410f442e7b4a104c3 (patch) | |
tree | 218a29f23c83c3ac57c857d3b1f599f1e6d97a14 | |
parent | c6be9b688802198e04a07dc902c49d1d6b66340e (diff) |
lots of MIDI editing stuff. to be explained on the website when its done
git-svn-id: svn://localhost/ardour2/branches/3.0@5596 d708f5d6-7413-0410-9779-e7cbd77b26cf
49 files changed, 1112 insertions, 222 deletions
diff --git a/gtk2_ardour/ardour3_ui_dark.rc.in b/gtk2_ardour/ardour3_ui_dark.rc.in index bac7baae8c..8046d4d075 100644 --- a/gtk2_ardour/ardour3_ui_dark.rc.in +++ b/gtk2_ardour/ardour3_ui_dark.rc.in @@ -81,9 +81,51 @@ style "time_axis_view_item_name" font_name = "%FONT_SMALLER%" } -style "default_base" = "medium_text" +style "white_tree_view" { + GtkButton::default_border = { 0, 0, 0, 0 } + GtkButton::default_outside_border = { 0, 0, 0, 0 } + GtkButton::button_relief = GTK_RELIEF_NONE + GtkTreeView::vertical-padding = 0 + GtkTreeView::horizontal-padding = 0 + GtkTreeView::even-row-color = { 0.70, 0.70, 0.70 } + GtkTreeView::odd-row-color = { 0.64, 0.64, 0.64 } + + fg[NORMAL] = { 0.30, 0.30, 0.40 } + fg[ACTIVE] = { 0.30, 0.30, 0.40 } + fg[PRELIGHT] = { 1.0, 1.0, 1.0 } + fg[INSENSITIVE] = { 0.30, 0.30, 0.40 } + fg[SELECTED] = { 0.30, 0.30, 0.40 } + + bg[NORMAL] = { 0.80, 0.80, 0.80 } + bg[ACTIVE] = { 0.80, 0.80, 0.80 } + bg[PRELIGHT] = { 0.80, 0.80, 0.80 } + bg[INSENSITIVE] = { 0.80, 0.80, 0.80 } + bg[SELECTED] = { 0.80, 0.80, 0.80 } + + text[NORMAL] = { 0.30, 0.30, 0.40 } + text[ACTIVE] = { 0.30, 0.30, 0.40 } + text[PRELIGHT] = { 0.30, 0.30, 0.40 } + text[INSENSITIVE] = { 0.30, 0.30, 0.40 } + text[SELECTED] = { 0, 0, 0 } + base[ACTIVE] = { 0.80, 0.80, 0.80 } + base[NORMAL] = { 0.80, 0.80, 0.80 } + base[PRELIGHT] = { 0.90, 0.90, 0.90 } + base[INSENSITIVE] = "#4c5159" + base[SELECTED] = { 0.60, 0.60, 0.80 } + + engine "clearlooks" + { + menubarstyle = 0 # 0 = flat, 1 = sunken, 2 = flat gradient + menuitemstyle = 0 # 0 = flat, 1 = 3d-ish (gradient), 2 = 3d-ish (button) + listviewitemstyle = 0 # 0 = flat, 1 = 3d-ish (gradient) + progressbarstyle = 1 # 0 = candy bar, 1 = fancy candy bar, 2 = flat + } +} + +style "default_base" = "medium_text" +{ GtkWidget::cursor_color = {1.0, 1.0, 1.0 } GtkButton::default_border = { 0, 0, 0, 0 } GtkButton::default_outside_border = { 0, 0, 0, 0 } @@ -1594,4 +1636,5 @@ widget "*RegionListWholeFile" style:highest "treeview_parent_node" widget "*EditorHScrollbar" style:highest "editor_hscrollbar" widget "*OddPortGroups" style:highest "odd_port_groups" widget "*EvenPortGroups" style:highest "even_port_groups" - +Widget "*MidiListView" style:highest "white_tree_view" +Widget "*MidiListView*" style:highest "white_tree_view" diff --git a/gtk2_ardour/ardour3_ui_default.conf b/gtk2_ardour/ardour3_ui_default.conf index 222b409c3f..94a2b62830 100644 --- a/gtk2_ardour/ardour3_ui_default.conf +++ b/gtk2_ardour/ardour3_ui_default.conf @@ -62,7 +62,7 @@ <Option name="midi note meter color max" value="ee3333aa"/> <Option name="midi note meter color mid" value="eeee33aa"/> <Option name="midi note meter color min" value="33ee33aa"/> - <Option name="midi note selected" value="8888ffb0"/> + <Option name="midi note selected" value="ff0000ff"/> <Option name="midi note velocity text" value="000000ff"/> <Option name="midi program change fill" value="0000ffa0"/> <Option name="midi program change outline" value="a7a7d4ff"/> diff --git a/gtk2_ardour/ardour_ui2.cc b/gtk2_ardour/ardour_ui2.cc index 1b16c6a761..0394d8be8c 100644 --- a/gtk2_ardour/ardour_ui2.cc +++ b/gtk2_ardour/ardour_ui2.cc @@ -191,6 +191,7 @@ ARDOUR_UI::setup_transport () { transport_tearoff = manage (new TearOff (transport_tearoff_hbox)); transport_tearoff->set_name ("TransportBase"); + transport_tearoff->tearoff_window().signal_key_press_event().connect (bind (sigc::ptr_fun (relay_key_press), &transport_tearoff->tearoff_window()), false); if (Profile->get_sae()) { transport_tearoff->set_can_be_torn_off (false); diff --git a/gtk2_ardour/ardour_ui_ed.cc b/gtk2_ardour/ardour_ui_ed.cc index dcc7a91ff1..b827625db2 100644 --- a/gtk2_ardour/ardour_ui_ed.cc +++ b/gtk2_ardour/ardour_ui_ed.cc @@ -39,6 +39,7 @@ #include "editor.h" #include "actions.h" #include "mixer_ui.h" +#include "utils.h" #ifdef GTKOSX #include <gtkmm2ext/sync-menu.h> @@ -567,7 +568,6 @@ ARDOUR_UI::use_menubar_as_top_menubar () #endif } - void ARDOUR_UI::setup_clock () { @@ -580,11 +580,12 @@ ARDOUR_UI::setup_clock () big_clock_window->add (big_clock); WindowTitle title(Glib::get_application_name()); - title += _("Clock"); + title += _("Big Clock"); big_clock_window->set_title (title.get_string()); - big_clock_window->set_type_hint (Gdk::WINDOW_TYPE_HINT_MENU); + big_clock_window->set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY); big_clock_window->signal_realize().connect (bind (sigc::ptr_fun (set_decoration), big_clock_window, (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH))); big_clock_window->signal_unmap().connect (bind (sigc::ptr_fun(&ActionManager::uncheck_toggleaction), X_("<Actions>/Common/ToggleBigClock"))); + big_clock_window->signal_key_press_event().connect (bind (sigc::ptr_fun (relay_key_press), big_clock_window), false); manage_window (*big_clock_window); } diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index c18d70868b..34f258a348 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -1704,7 +1704,11 @@ Editor::add_region_context_items (StreamView* sv, boost::shared_ptr<Region> regi bind (mem_fun(*this, &Editor::set_selected_regionview_from_map_event), sv, boost::weak_ptr<Region>(region))); items.push_back (MenuElem (_("Rename"), mem_fun(*this, &Editor::rename_region))); - items.push_back (MenuElem (_("Popup region editor"), mem_fun(*this, &Editor::edit_region))); + if (mr && internal_editing()) { + items.push_back (MenuElem (_("Popup list editor"), mem_fun(*this, &Editor::show_midi_list_editor))); + } else { + items.push_back (MenuElem (_("Popup region editor"), mem_fun(*this, &Editor::edit_region))); + } } items.push_back (MenuElem (_("Raise to top layer"), mem_fun(*this, &Editor::raise_region_to_top))); @@ -2643,23 +2647,23 @@ Editor::snap_to_internal (nframes64_t& start, int32_t direction, bool for_mark) break; case SnapToAThirtysecondBeat: - start = session->tempo_map().round_to_beat_subdivision (start, 32); + start = session->tempo_map().round_to_beat_subdivision (start, 32, direction); break; case SnapToASixteenthBeat: - start = session->tempo_map().round_to_beat_subdivision (start, 16); + start = session->tempo_map().round_to_beat_subdivision (start, 16, direction); break; case SnapToAEighthBeat: - start = session->tempo_map().round_to_beat_subdivision (start, 8); + start = session->tempo_map().round_to_beat_subdivision (start, 8, direction); break; case SnapToAQuarterBeat: - start = session->tempo_map().round_to_beat_subdivision (start, 4); + start = session->tempo_map().round_to_beat_subdivision (start, 4, direction); break; case SnapToAThirdBeat: - start = session->tempo_map().round_to_beat_subdivision (start, 3); + start = session->tempo_map().round_to_beat_subdivision (start, 3, direction); break; case SnapToMark: @@ -2758,45 +2762,6 @@ Editor::snap_to_internal (nframes64_t& start, int32_t direction, bool for_mark) } } -double -Editor::snap_length_beats (nframes64_t start) -{ - if (!session) { - return 1.0; - } - - /* FIXME: This could/should also work with non-tempo based snap settings (ie seconds) */ - - switch (snap_type) { - case SnapToBar: - return session->tempo_map().meter_at(start).beats_per_bar(); - - case SnapToBeat: - return 1.0; - - case SnapToAThirtysecondBeat: - return 1.0 / (double)32.0; - break; - - case SnapToASixteenthBeat: - return 1.0 / (double)16.0; - break; - - case SnapToAEighthBeat: - return 1.0 / (double)8.0; - break; - - case SnapToAQuarterBeat: - return 1.0 / (double)4.0; - break; - - case SnapToAThirdBeat: - return 1.0 / (double)3.0; - - default: - return 1.0; - } -} void Editor::setup_toolbar () @@ -2846,6 +2811,7 @@ Editor::setup_toolbar () mouse_mode_tearoff = manage (new TearOff (*mode_box)); mouse_mode_tearoff->set_name ("MouseModeBase"); + mouse_mode_tearoff->tearoff_window().signal_key_press_event().connect (bind (sigc::ptr_fun (relay_key_press), &mouse_mode_tearoff->tearoff_window()), false); if (Profile->get_sae()) { mouse_mode_tearoff->set_can_be_torn_off (false); @@ -2985,6 +2951,7 @@ Editor::setup_toolbar () tools_tearoff = manage (new TearOff (*hbox)); tools_tearoff->set_name ("MouseModeBase"); + tools_tearoff->tearoff_window().signal_key_press_event().connect (bind (sigc::ptr_fun (relay_key_press), &tools_tearoff->tearoff_window()), false); if (Profile->get_sae()) { tools_tearoff->set_can_be_torn_off (false); @@ -3846,6 +3813,60 @@ Editor::playlist_selector () const return *_playlist_selector; } +Evoral::MusicalTime +Editor::get_grid_type_as_beats (bool& success, nframes64_t position) +{ + success = true; + + switch (snap_type) { + case SnapToBeat: + return 1.0; + break; + + case SnapToAThirtysecondBeat: + return 1.0/32.0; + break; + + case SnapToASixteenthBeat: + return 1.0/16.0; + break; + + case SnapToAEighthBeat: + return 1.0/8.0; + break; + + case SnapToAQuarterBeat: + return 1.0/4.0; + break; + + case SnapToAThirdBeat: + return 1.0/3.0; + break; + + case SnapToBar: + if (session) { + return session->tempo_map().meter_at (position).beats_per_bar(); + } + break; + + case SnapToCDFrame: + case SnapToSMPTEFrame: + case SnapToSMPTESeconds: + case SnapToSMPTEMinutes: + case SnapToSeconds: + case SnapToMinutes: + case SnapToRegionStart: + case SnapToRegionEnd: + case SnapToRegionSync: + case SnapToRegionBoundary: + default: + success = false; + break; + } + + return 0.0; +} + nframes64_t Editor::get_nudge_distance (nframes64_t pos, nframes64_t& next) { diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 5f5147a677..66e6f2c5f8 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -82,6 +82,7 @@ namespace ARDOUR { class Filter; class Crossfade; class ChanCount; + class MidiOperator; } namespace LADSPA { @@ -102,6 +103,7 @@ class Drag; class GlobalPortMatrixWindow; class GroupedButtons; class Marker; +class MidiRegionView; class MixerStrip; class PlaylistSelector; class PluginSelector; @@ -333,6 +335,7 @@ class Editor : public PublicEditor /* nudge is initiated by transport controls owned by ARDOUR_UI */ nframes64_t get_nudge_distance (nframes64_t pos, nframes64_t& next); + Evoral::MusicalTime get_grid_type_as_beats (bool& success, nframes64_t position); void nudge_forward (bool next, bool force_playhead); void nudge_backward (bool next, bool force_playhead); @@ -1060,6 +1063,7 @@ class Editor : public PublicEditor void remove_selected_regions (); void remove_clicked_region (); void edit_region (); + void show_midi_list_editor (); void rename_region (); void duplicate_some_regions (RegionSelection&, float times); void duplicate_selection (float times); @@ -1493,8 +1497,6 @@ public: void snap_to_with_modifier (nframes64_t& first, GdkEvent const *, int32_t direction = 0, bool for_mark = false); void snap_to (nframes64_t& first, nframes64_t& last, int32_t direction = 0, bool for_mark = false); - double snap_length_beats (nframes64_t start); - uint32_t bbt_beat_subdivision; /* toolbar */ @@ -1915,6 +1917,9 @@ public: void apply_filter (ARDOUR::Filter&, std::string cmd); + void apply_midi_note_edit_op_to_region (ARDOUR::MidiOperator& op, MidiRegionView& mrv); + void apply_midi_note_edit_op (ARDOUR::MidiOperator& op); + /* handling cleanup */ int playlist_deletion_dialog (boost::shared_ptr<ARDOUR::Playlist>); diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index d9491a21b0..feeae881f0 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -79,6 +79,7 @@ #include "strip_silence_dialog.h" #include "editor_routes.h" #include "editor_regions.h" +#include "quantize_dialog.h" #include "i18n.h" @@ -2535,6 +2536,13 @@ Editor::edit_region () selection->foreach_regionview (&RegionView::show_region_editor); } +/** Show the midi list editor for the selected MIDI regions */ +void +Editor::show_midi_list_editor () +{ + selection->foreach_midi_regionview (&MidiRegionView::show_list_editor); +} + void Editor::rename_region() { @@ -4801,6 +4809,48 @@ Editor::strip_region_silence () } } +void +Editor::apply_midi_note_edit_op_to_region (MidiOperator& op, MidiRegionView& mrv) +{ + vector<Evoral::Sequence<Evoral::MusicalTime>::Notes> v; + Evoral::Sequence<Evoral::MusicalTime>::Notes selected; + + v.push_back (selected); + + mrv.selection_as_notelist (v.front()); + op (v); + mrv.replace_selected (v.front()); +} + +void +Editor::apply_midi_note_edit_op (MidiOperator& op) +{ + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; + } + + begin_reversible_command (op.name ()); + + for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ) { + RegionSelection::iterator tmp = r; + ++tmp; + + MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (*r); + + if (mrv) { + apply_midi_note_edit_op_to_region (op, *mrv); + } + + r = tmp; + } + + commit_reversible_command (); + rs.clear (); +} void Editor::quantize_region () @@ -4809,9 +4859,18 @@ Editor::quantize_region () return; } - // FIXME: varying meter? - Quantize quant (*session, snap_length_beats(0)); - apply_filter (quant, _("quantize regions")); + QuantizeDialog* qd = new QuantizeDialog (*this); + + qd->present (); + qd->run (); + qd->hide (); + + Quantize quant (*session, Plain, + qd->snap_start(), qd->snap_end(), + qd->start_grid_size(), qd->end_grid_size(), + qd->strength(), qd->swing(), qd->threshold()); + + apply_midi_note_edit_op (quant); } void @@ -4834,13 +4893,6 @@ Editor::apply_filter (Filter& filter, string command) RegionSelection::iterator tmp = r; ++tmp; - MidiRegionView* const mrv = dynamic_cast<MidiRegionView*>(*r); - if (mrv) { - if (mrv->midi_region()->apply(filter) == 0) { - mrv->redisplay_model(); - } - } - AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*r); if (arv) { boost::shared_ptr<Playlist> playlist = arv->region()->playlist(); diff --git a/gtk2_ardour/keyboard.cc b/gtk2_ardour/keyboard.cc index f3542054aa..157cfffd01 100644 --- a/gtk2_ardour/keyboard.cc +++ b/gtk2_ardour/keyboard.cc @@ -237,6 +237,7 @@ Keyboard::snooper (GtkWidget *widget, GdkEventKey *event) const AccelKey& ak (k->first); if (keyval == ak.get_key() && (Gdk::ModifierType)(event->state | Gdk::RELEASE_MASK) == ak.get_mod()) { + cerr << "Suppress auto repeat\n"; ret = true; break; } @@ -261,6 +262,7 @@ Keyboard::snooper (GtkWidget *widget, GdkEventKey *event) Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (ts.first.c_str(), ts.second.c_str()); if (act) { act->activate(); + cerr << "use repeat, suppress other\n"; ret = true; } break; diff --git a/gtk2_ardour/midi_list_editor.cc b/gtk2_ardour/midi_list_editor.cc new file mode 100644 index 0000000000..2db6cbd25e --- /dev/null +++ b/gtk2_ardour/midi_list_editor.cc @@ -0,0 +1,99 @@ +/* + Copyright (C) 2009 Paul Davis + + 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 "evoral/midi_util.h" +#include "ardour/midi_region.h" + +#include "midi_list_editor.h" + +#include "i18n.h" + +using namespace std; +using namespace Gtk; +using namespace Glib; +using namespace ARDOUR; + +MidiListEditor::MidiListEditor (boost::shared_ptr<MidiRegion> r) + : ArdourDialog (r->name(), false, false) + , region (r) +{ + model = ListStore::create (columns); + view.set_model (model); + + view.append_column (_("Channel"), columns.channel); + view.append_column (_("Note"), columns.note); + view.append_column (_("Name"), columns.note_name); + view.append_column (_("Velocity"), columns.velocity); + view.append_column (_("Start"), columns.start); + view.append_column (_("End"), columns.end); + view.append_column (_("Length"), columns.length); + view.set_headers_visible (true); + view.set_name (X_("MidiListView")); + view.set_rules_hint (true); + + for (int i = 0; i < 6; ++i) { + CellRendererText* renderer = dynamic_cast<CellRendererText*>(view.get_column_cell_renderer (i)); + renderer->property_editable() = true; + renderer->signal_edited().connect (mem_fun (*this, &MidiListEditor::edited)); + } + + scroller.add (view); + scroller.set_policy (POLICY_NEVER, POLICY_AUTOMATIC); + + redisplay_model (); + + view.show (); + scroller.show (); + + get_vbox()->pack_start (scroller); + set_size_request (400, 400); +} + +MidiListEditor::~MidiListEditor () +{ +} + +void +MidiListEditor::edited (const Glib::ustring& /* path */, const Glib::ustring& /* text */) +{ + redisplay_model (); +} + +void +MidiListEditor::redisplay_model () +{ + view.set_model (Glib::RefPtr<Gtk::ListStore>(0)); + model->clear (); + + MidiModel::Notes notes = region->midi_source(0)->model()->notes(); + TreeModel::Row row; + + for (MidiModel::Notes::iterator i = notes.begin(); i != notes.end(); ++i) { + row = *(model->append()); + row[columns.channel] = (*i)->channel(); + row[columns.note_name] = Evoral::midi_note_name ((*i)->note()); + row[columns.note] = (*i)->note(); + row[columns.velocity] = (*i)->velocity(); + row[columns.start] = (*i)->time(); + row[columns.length] = (*i)->length(); + row[columns.end] = (*i)->end_time(); + } + + view.set_model (model); +} diff --git a/gtk2_ardour/midi_list_editor.h b/gtk2_ardour/midi_list_editor.h new file mode 100644 index 0000000000..ac931dcca6 --- /dev/null +++ b/gtk2_ardour/midi_list_editor.h @@ -0,0 +1,73 @@ +/* + Copyright (C) 2009 Paul Davis + + 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. + +*/ + +#ifndef __ardour_gtk2_midi_list_editor_h_ +#define __ardour_gtk2_midi_list_editor_h_ + +#include <gtkmm/treeview.h> +#include <gtkmm/liststore.h> +#include <gtkmm/scrolledwindow.h> + +#include "evoral/types.hpp" + +#include "ardour_dialog.h" + +namespace ARDOUR { + class MidiRegion; + class MidiModel; +}; + +class MidiListEditor : public ArdourDialog +{ + public: + MidiListEditor(boost::shared_ptr<ARDOUR::MidiRegion>); + ~MidiListEditor(); + + private: + struct MidiListModelColumns : public Gtk::TreeModel::ColumnRecord { + MidiListModelColumns() { + add (channel); + add (note); + add (note_name); + add (velocity); + add (start); + add (length); + add (end); + }; + Gtk::TreeModelColumn<uint8_t> channel; + Gtk::TreeModelColumn<uint8_t> note; + Gtk::TreeModelColumn<std::string> note_name; + Gtk::TreeModelColumn<uint8_t> velocity; + Gtk::TreeModelColumn<Evoral::MusicalTime> start; + Gtk::TreeModelColumn<Evoral::MusicalTime> length; + Gtk::TreeModelColumn<Evoral::MusicalTime> end; + }; + + MidiListModelColumns columns; + Glib::RefPtr<Gtk::ListStore> model; + Gtk::TreeView view; + Gtk::ScrolledWindow scroller; + + boost::shared_ptr<ARDOUR::MidiRegion> region; + + void edited (const Glib::ustring&, const Glib::ustring&); + void redisplay_model (); +}; + +#endif /* __ardour_gtk2_midi_list_editor_h_ */ diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 4efaa2eed2..95b1ffad93 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -50,6 +50,7 @@ #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" @@ -160,9 +161,7 @@ MidiRegionView::init (Gdk::Color const & basic_color, bool wfd) midi_region()->midi_source(0)->load_model(); } - cerr << "Looking up model for midi region\n"; _model = midi_region()->midi_source(0)->model(); - cerr << " model = " << _model << endl; _enable_display = false; RegionView::init (basic_color, false); @@ -236,53 +235,84 @@ MidiRegionView::canvas_event(GdkEvent* ev) break; case GDK_KEY_PRESS: - if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) { + + /* 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; - } - return false; - case GDK_KEY_RELEASE: - if (ev->key.keyval == GDK_Delete) { + } 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); + bool 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(); apply_command(); return true; - } else if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) { - _mouse_state = None; - 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) { if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) { change_velocities (1, true); } else { - transpose (true, false); + transpose (true, Keyboard::modifier_state_equals (ev->key.state, Keyboard::SecondaryModifier)); } + return true; } else if (ev->key.keyval == GDK_Down) { - + if (Keyboard::modifier_state_equals (ev->key.state, Keyboard::PrimaryModifier)) { change_velocities (-1, true); } else { - transpose (false, false); + transpose (false, Keyboard::modifier_state_equals (ev->key.state, Keyboard::SecondaryModifier)); } - } else if (ev->key.keyval == GDK_Left) { + 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; + } + 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; @@ -416,7 +446,9 @@ MidiRegionView::canvas_event(GdkEvent* ev) group->ungrab(ev->button.time); event_frame = trackview.editor().pixel_to_frame(event_x); - if (_pressed_button != 1) { + if (ev->button.button == 3) { + return false; + } else if (_pressed_button != 1) { return false; } @@ -429,7 +461,9 @@ MidiRegionView::canvas_event(GdkEvent* ev) break; case MouseObject: create_note_at(event_x, event_y, _default_note_length); - default: break; + break; + default: + break; } _mouse_state = None; break; @@ -459,6 +493,12 @@ MidiRegionView::canvas_event(GdkEvent* ev) return false; } +void +MidiRegionView::show_list_editor () +{ + MidiListEditor* mle = new MidiListEditor (midi_region()); + mle->show (); +} /** Add a note to the model, and the view, at a canvas (click) coordinate. * \param x horizontal position in pixels @@ -578,7 +618,26 @@ MidiRegionView::apply_command() _marked_for_selection.clear(); _marked_for_velocity.clear(); } + +void +MidiRegionView::apply_command_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 = NULL; + midi_view()->midi_track()->diskstream()->playlist_modified(); + + _marked_for_selection.clear(); + _marked_for_velocity.clear(); +} void MidiRegionView::abort_command() @@ -673,7 +732,7 @@ void MidiRegionView::display_sysexes() { for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) { - ARDOUR::MidiModel::TimeType time = (*i)->time(); + Evoral::MusicalTime time = (*i)->time(); assert(time >= 0); ostringstream str; @@ -1033,8 +1092,12 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note) if (event) { if (_marked_for_selection.find(note) != _marked_for_selection.end()) { + cerr << "Added note " << *note << " was marked for selection\n"; note_selected(event, true); + } else { + cerr << "Added note " << *note << " not selected\n"; } + if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) { event->show_velocity(); } @@ -1190,9 +1253,7 @@ MidiRegionView::delete_selection() return; } - if (!_delta_command) { - _delta_command = _model->new_delta_command("delete selection"); - } + start_delta_command (_("delete selection")); for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { if ((*i)->selected()) { @@ -1201,6 +1262,8 @@ MidiRegionView::delete_selection() } _selection.clear(); + + apply_command (); } void @@ -1252,8 +1315,8 @@ MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool } else { /* find end of latest note selected, select all between that and the start of "ev" */ - MidiModel::TimeType earliest = DBL_MAX; - MidiModel::TimeType latest = 0; + 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) { @@ -1265,18 +1328,24 @@ MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool } if (ev->note()->end_time() > latest) { - earliest = latest; latest = ev->note()->end_time(); - } else { + } + + if (ev->note()->time() < earliest) { earliest = ev->note()->time(); } + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - if ((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) { - add_to_selection (*i); - } - if ((*i)->note()->end_time() > latest) { + /* 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 ((*i)->note()->time() > latest) { break; } } @@ -1431,13 +1500,32 @@ MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote) const boost::shared_ptr<NoteType> copy(new NoteType(*(*i)->note().get())); nframes64_t start_frames = beats_to_frames((*i)->note()->time()); + + cerr << "starting at " << (*i)->note()->time() + << " (" << start_frames << ") delta on drag = " << dt << endl; + if (dt >= 0) { + cerr << "Motion was " << snap_frame_to_frame(trackview.editor().pixel_to_frame(dt)) << endl; start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt)); } else { + cerr << "rev Motion was " << snap_frame_to_frame(trackview.editor().pixel_to_frame(dt)) << endl; start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt)); } - copy->set_time(frames_to_beats(start_frames)); + cerr << "start frame will be " << start_frames << " vs. region " + << _region->position () + << endl; + + Evoral::MusicalTime new_time = frames_to_beats(start_frames); + + if (new_time < 0) { + i = next; + continue; + } + + copy->set_time (new_time); + + cerr << "copy time = " << copy->time() << endl; uint8_t original_pitch = (*i)->note()->note(); uint8_t new_pitch = original_pitch + dnote - highest_note_difference; @@ -1457,6 +1545,7 @@ MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote) copy->set_note(new_pitch); command_remove_note(*i); + cerr << "Adding note: " << *copy << endl; command_add_note(copy, (*i)->selected()); i = next; @@ -1671,7 +1760,63 @@ MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool rela } void -MidiRegionView::change_note_time (CanvasNoteEvent* event, MidiModel::TimeType delta, bool relative) +MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta) +{ + /* 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) + */ + + const boost::shared_ptr<NoteType> copy(new NoteType(*(event->note().get()))); + + cerr << "Trim front by " << front_delta << " end by " << end_delta << endl; + + if (front_delta) { + if (front_delta < 0) { + if (copy->time() < -front_delta) { + copy->set_time (0); + } else { + copy->set_time (copy->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. + */ + copy->set_length (copy->length() - front_delta); + } else { + Evoral::MusicalTime new_pos = copy->time() + front_delta; + + if (new_pos >= copy->end_time()) { + return; + } + + copy->set_time (copy->time() + front_delta); + + /* start moved toward the end, so move the end point back to where it used to be */ + copy->set_length (copy->length() - front_delta); + } + + } + + if (end_delta) { + if (end_delta < 0) { + if (copy->length() < -end_delta) { + return; + } + } + + copy->set_length (copy->length() + end_delta); + } + + command_remove_note(event); + command_add_note(copy, event->selected(), false); +} + +void +MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative) { const boost::shared_ptr<NoteType> copy(new NoteType(*(event->note().get()))); @@ -1745,6 +1890,44 @@ MidiRegionView::transpose (bool up, bool fine) } 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_delta_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_command (); + +} + +void MidiRegionView::nudge_notes (bool forward) { if (_selection.empty()) { @@ -1757,23 +1940,36 @@ MidiRegionView::nudge_notes (bool forward) */ nframes64_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time()); - nframes64_t next_pos = ref_point; + nframes64_t unused; + nframes64_t distance; - if (forward) { - next_pos += 1; - } else { - if (next_pos == 0) { - return; - } - next_pos -= 1; - } + if ((distance = trackview.editor().get_nudge_distance (ref_point, unused)) == 0) { - trackview.editor().snap_to (next_pos, (forward ? 1 : -1), false); - nframes64_t distance = ref_point - next_pos; + /* no nudge distance set - use grid */ - cerr << "ref was " << ref_point << " next is " << next_pos << endl; + 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; + } - MidiModel::TimeType delta = frames_to_beats (fabs (distance)); + Evoral::MusicalTime delta = frames_to_beats (fabs (distance)); if (!forward) { delta = -delta; @@ -1911,7 +2107,7 @@ MidiRegionView::cut_copy_clear (Editing::CutCopyOp op) MidiCutBuffer* MidiRegionView::selection_as_cut_buffer () const { - Evoral::Sequence<MidiModel::TimeType>::Notes notes; + NoteList notes; for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { notes.push_back (boost::shared_ptr<NoteType> (new NoteType (*((*i)->note().get())))); @@ -1919,7 +2115,8 @@ MidiRegionView::selection_as_cut_buffer () const /* sort them into time order */ - sort (notes.begin(), notes.end(), Evoral::Sequence<MidiModel::TimeType>::note_time_comparator); + Evoral::Sequence<Evoral::MusicalTime>::LaterNoteComparator cmp; + sort (notes.begin(), notes.end(), cmp); MidiCutBuffer* cb = new MidiCutBuffer (trackview.session()); cb->set (notes); @@ -1936,10 +2133,10 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb) start_delta_command (_("paste")); - MidiModel::TimeType beat_delta; - MidiModel::TimeType paste_pos_beats; - MidiModel::TimeType duration; - MidiModel::TimeType end_point; + Evoral::MusicalTime beat_delta; + Evoral::MusicalTime paste_pos_beats; + Evoral::MusicalTime duration; + Evoral::MusicalTime end_point; duration = mcb.notes().back()->end_time() - mcb.notes().front()->time(); paste_pos_beats = frames_to_beats (pos - _region->position()); @@ -1950,7 +2147,7 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb) for (int n = 0; n < (int) times; ++n) { - for (Evoral::Sequence<MidiModel::TimeType>::Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) { + for (NoteList::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); @@ -2033,3 +2230,66 @@ MidiRegionView::goto_previous_note () unique_select (*(_events.rbegin())); } + +MidiModel::DeltaCommand* +MidiRegionView::apply (Filter& filter, const std::string& name) +{ + MidiModel::Notes before = _model->notes(); + MidiModel::Notes after; + + _region->apply (filter); + + redisplay_model (); + + after = _model->notes(); + + start_delta_command (name); + + for (MidiModel::Notes::iterator i = before.begin(); i != before.end(); ++i) { + _delta_command->add (*i); + } + + for (MidiModel::Notes::iterator i = after.begin(); i != after.end(); ++i) { + _delta_command->remove (*i); + } + + return _delta_command; +} + +void +MidiRegionView::selection_as_notelist (NoteList& selected) +{ + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { + if ((*i)->selected()) { + /* make a copy of the original */ + selected.push_back (boost::shared_ptr<Evoral::Note<Evoral::MusicalTime> > + (new Evoral::Note<Evoral::MusicalTime> (*((*i)->note())))); + } + } +} + +void +MidiRegionView::replace_selected (NoteList& replacements) +{ + start_delta_command ("whatever"); + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { + Selection::iterator tmp; + tmp = i; + ++tmp; + + command_remove_note (*i); + remove_from_selection (*i); + + i = tmp; + } + + _selection.clear (); + + for (NoteList::iterator i = replacements.begin(); i != replacements.end(); ++i) { + command_add_note (*i, true, false); + } + + apply_command_as_subcommand (); +} + diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h index 073b78ad1a..7edc4cd835 100644 --- a/gtk2_ardour/midi_region_view.h +++ b/gtk2_ardour/midi_region_view.h @@ -45,6 +45,7 @@ namespace ARDOUR { class MidiRegion; class MidiModel; + class Filter; }; namespace MIDI { @@ -62,7 +63,8 @@ class MidiCutBuffer; class MidiRegionView : public RegionView { public: - typedef Evoral::Note<ARDOUR::MidiModel::TimeType> NoteType; + typedef Evoral::Note<Evoral::MusicalTime> NoteType; + typedef Evoral::Sequence<Evoral::MusicalTime>::Notes NoteList; MidiRegionView (ArdourCanvas::Group *, RouteTimeAxisView&, @@ -86,6 +88,8 @@ class MidiRegionView : public RegionView inline MidiStreamView* midi_stream_view() const { return midi_view()->midi_view(); } + ARDOUR::MidiModel::DeltaCommand* apply (ARDOUR::Filter&, const std::string& name); + void set_height (double); void apply_note_range(uint8_t lowest, uint8_t highest, bool force=false); @@ -168,6 +172,7 @@ class MidiRegionView : public RegionView void command_remove_note(ArdourCanvas::CanvasNoteEvent* ev); void apply_command(); + void apply_command_as_subcommand(); void abort_command(); void note_entered(ArdourCanvas::CanvasNoteEvent* ev); @@ -264,9 +269,15 @@ class MidiRegionView : public RegionView void goto_previous_note (); void goto_next_note (); void change_velocities (int8_t velocity, bool relative); + void change_note_lengths (bool, bool, bool start, bool end); void transpose (bool up, bool fine); void nudge_notes (bool forward); + void show_list_editor (); + + void selection_as_notelist (NoteList& selected); + void replace_selected (NoteList& replacements); + protected: /** Allows derived types to specify their visibility requirements * to the TimeAxisViewItem parent class. @@ -308,6 +319,8 @@ class MidiRegionView : public RegionView void change_note_velocity(ArdourCanvas::CanvasNoteEvent* ev, int8_t vel, bool relative=false); void change_note_note(ArdourCanvas::CanvasNoteEvent* ev, int8_t note, bool relative=false); void change_note_time(ArdourCanvas::CanvasNoteEvent* ev, ARDOUR::MidiModel::TimeType, bool relative=false); + void trim_note(ArdourCanvas::CanvasNoteEvent* ev, ARDOUR::MidiModel::TimeType start_delta, + ARDOUR::MidiModel::TimeType end_delta); void clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev); void clear_selection() { clear_selection_except(NULL); } @@ -361,6 +374,7 @@ class MidiRegionView : public RegionView /* connection used to connect to model's ContentChanged signal */ sigc::connection content_connection; + }; #endif /* __gtk_ardour_midi_region_view_h__ */ diff --git a/gtk2_ardour/plugin_ui.cc b/gtk2_ardour/plugin_ui.cc index 1c5a56f531..86dbcd829b 100644 --- a/gtk2_ardour/plugin_ui.cc +++ b/gtk2_ardour/plugin_ui.cc @@ -331,11 +331,7 @@ PluginUIWindow::create_lv2_editor(boost::shared_ptr<PluginInsert> insert) bool PluginUIWindow::on_key_press_event (GdkEventKey* event) { - if (!key_press_focus_accelerator_handler (*this, event)) { - return PublicEditor::instance().on_key_press_event(event); - } else { - return true; - } + return relay_key_press (event, this); } bool diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index f2d57a72a2..ee7f303884 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -31,6 +31,7 @@ #include <jack/types.h> #include <sigc++/signal.h> +#include "evoral/types.hpp" #include "ardour/route_group.h" #include "pbd/statefuldestructible.h" @@ -121,9 +122,6 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulThingWithGoingAway /** Snap a value according to the current snap setting. */ virtual void snap_to (nframes64_t& first, int32_t direction = 0, bool for_mark = false) = 0; - /** Get the current snap value in beats */ - virtual double snap_length_beats (nframes64_t start) = 0; - /** Undo some transactions. * @param n Number of transactions to undo. */ @@ -264,7 +262,7 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulThingWithGoingAway virtual void foreach_time_axis_view (sigc::slot<void,TimeAxisView&>) = 0; virtual void add_to_idle_resize (TimeAxisView*, int32_t) = 0; virtual nframes64_t get_nudge_distance (nframes64_t pos, nframes64_t& next) = 0; - + virtual Evoral::MusicalTime get_grid_type_as_beats (bool& success, nframes64_t position) = 0; #ifdef WITH_CMT virtual void add_imageframe_time_axis(const std::string & track_name, void*) = 0; @@ -350,7 +348,7 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulThingWithGoingAway static PublicEditor* _instance; - friend class PluginUIWindow; + friend bool relay_key_press (GdkEventKey*, Gtk::Window*); }; #endif // __gtk_ardour_public_editor_h__ diff --git a/gtk2_ardour/selection.h b/gtk2_ardour/selection.h index 288c832178..a898705d23 100644 --- a/gtk2_ardour/selection.h +++ b/gtk2_ardour/selection.h @@ -181,6 +181,7 @@ class Selection : public sigc::trackable void foreach_region (void (ARDOUR::Region::*method)(void)); void foreach_regionview (void (RegionView::*method)(void)); + void foreach_midi_regionview (void (MidiRegionView::*method)(void)); template<class A> void foreach_region (void (ARDOUR::Region::*method)(A), A arg); private: diff --git a/gtk2_ardour/selection_templates.h b/gtk2_ardour/selection_templates.h index cc7933d879..cc2585c17e 100644 --- a/gtk2_ardour/selection_templates.h +++ b/gtk2_ardour/selection_templates.h @@ -29,6 +29,7 @@ #include "selection.h" #include "region_view.h" +#include "midi_region_view.h" inline void Selection::foreach_region (void (ARDOUR::Region::*method)(void)) { @@ -45,6 +46,16 @@ Selection::foreach_regionview (void (RegionView::*method)(void)) { } } +inline void +Selection::foreach_midi_regionview (void (MidiRegionView::*method)(void)) { + for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { + MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*i); + if (mrv) { + (mrv->*(method))(); + } + } +} + template<class A> inline void Selection::foreach_region (void (ARDOUR::Region::*method)(A), A arg) { for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { diff --git a/gtk2_ardour/utils.cc b/gtk2_ardour/utils.cc index 7a1c9c6968..5e5534222e 100644 --- a/gtk2_ardour/utils.cc +++ b/gtk2_ardour/utils.cc @@ -41,6 +41,7 @@ #include "ardour/filesystem_paths.h" #include "ardour_ui.h" +#include "public_editor.h" #include "keyboard.h" #include "utils.h" #include "i18n.h" @@ -509,6 +510,16 @@ extern "C" { #endif bool +relay_key_press (GdkEventKey* ev, Gtk::Window* win) +{ + if (!key_press_focus_accelerator_handler (*win, ev)) { + return PublicEditor::instance().on_key_press_event(ev); + } else { + return true; + } +} + +bool key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev) { GtkWindow* win = window.gobj(); diff --git a/gtk2_ardour/utils.h b/gtk2_ardour/utils.h index 4cf54f6c91..5e0c3b53c2 100644 --- a/gtk2_ardour/utils.h +++ b/gtk2_ardour/utils.h @@ -82,6 +82,7 @@ bool canvas_item_visible (ArdourCanvas::Item* item); void set_color (Gdk::Color&, int); +bool relay_key_press (GdkEventKey* ev, Gtk::Window* win); bool key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey* ev); bool possibly_translate_keyval_to_make_legal_accelerator (uint32_t& keyval); diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 87503ff2d3..17d9096dc9 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -127,6 +127,7 @@ gtk2_ardour_sources = [ 'marker.cc', 'midi_channel_selector.cc', 'midi_cut_buffer.cc', + 'midi_list_editor.cc', 'midi_port_dialog.cc', 'midi_region_view.cc', 'midi_scroomer.cc', @@ -157,6 +158,7 @@ gtk2_ardour_sources = [ 'processor_box.cc', 'prompter.cc', 'public_editor.cc', + 'quantize_dialog.cc', 'rc_option_editor.cc', 'region_gain_line.cc', 'region_selection.cc', diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h index 3feda1a22c..967372fa9a 100644 --- a/libs/ardour/ardour/midi_model.h +++ b/libs/ardour/ardour/midi_model.h @@ -47,7 +47,7 @@ class MidiSource; * Because of this MIDI controllers and automatable controllers/widgets/etc * are easily interchangeable. */ -class MidiModel : public AutomatableSequence<double> { +class MidiModel : public AutomatableSequence<Evoral::MusicalTime> { public: typedef double TimeType; @@ -91,6 +91,7 @@ public: MidiModel::DeltaCommand* new_delta_command(const std::string name="midi edit"); void apply_command(Session& session, Command* cmd); + void apply_command_as_subcommand(Session& session, Command* cmd); bool write_to(boost::shared_ptr<MidiSource> source); diff --git a/libs/ardour/ardour/midi_operator.h b/libs/ardour/ardour/midi_operator.h new file mode 100644 index 0000000000..e3ed6aabfd --- /dev/null +++ b/libs/ardour/ardour/midi_operator.h @@ -0,0 +1,42 @@ +/* + Copyright (C) 2009 Paul Davis + + 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. + +*/ + +#ifndef __libardour_midi_operator_h__ +#define __libardour_midi_operator_h__ + +#include <vector> +#include <string> + +#include "evoral/types.hpp" +#include "evoral/Sequence.hpp" + +namespace ARDOUR { + +class MidiOperator { + public: + MidiOperator() {} + virtual ~MidiOperator() {} + + virtual int operator() (std::vector<Evoral::Sequence<Evoral::MusicalTime>::Notes>&) = 0; + virtual std::string name() const = 0; +}; + +} /* namespace */ + +#endif /* __libardour_midi_operator_h__ */ diff --git a/libs/ardour/ardour/midi_source.h b/libs/ardour/ardour/midi_source.h index cf49e59458..a479b4a8a8 100644 --- a/libs/ardour/ardour/midi_source.h +++ b/libs/ardour/ardour/midi_source.h @@ -65,7 +65,7 @@ class MidiSource : virtual public Source sframes_t source_start, nframes_t cnt); - virtual void append_event_unlocked_beats(const Evoral::Event<double>& ev) = 0; + virtual void append_event_unlocked_beats(const Evoral::Event<Evoral::MusicalTime>& ev) = 0; virtual void append_event_unlocked_frames(const Evoral::Event<nframes_t>& ev, sframes_t source_start) = 0; @@ -128,7 +128,7 @@ class MidiSource : virtual public Source boost::shared_ptr<MidiModel> _model; bool _writing; - mutable Evoral::Sequence<double>::const_iterator _model_iter; + mutable Evoral::Sequence<Evoral::MusicalTime>::const_iterator _model_iter; mutable double _length_beats; mutable sframes_t _last_read_end; diff --git a/libs/ardour/ardour/midi_track.h b/libs/ardour/ardour/midi_track.h index e7ffd40d67..5c5ef5c26c 100644 --- a/libs/ardour/ardour/midi_track.h +++ b/libs/ardour/ardour/midi_track.h @@ -84,7 +84,8 @@ protected: int _set_state (const XMLNode&, bool call_base); private: - void write_controller_messages(MidiBuffer& buf, sframes_t start_frame, sframes_t end_frame, nframes_t nframes); + void write_out_of_band_data (BufferSet& bufs, sframes_t start_frame, sframes_t end_frame, + nframes_t nframes); int set_diskstream (boost::shared_ptr<MidiDiskstream> ds); void use_new_diskstream (); diff --git a/libs/ardour/ardour/quantize.h b/libs/ardour/ardour/quantize.h index f7307c194c..57e5467294 100644 --- a/libs/ardour/ardour/quantize.h +++ b/libs/ardour/ardour/quantize.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2007 Paul Davis + Copyright (C) 2007-2009 Paul Davis Author: Dave Robillard This program is free software; you can redistribute it and/or modify @@ -21,19 +21,33 @@ #ifndef __ardour_quantize_h__ #define __ardour_quantize_h__ -#include "ardour/filter.h" +#include "ardour/types.h" +#include "ardour/midi_operator.h" namespace ARDOUR { -class Quantize : public Filter { +class Session; + +class Quantize : public MidiOperator { public: - Quantize (ARDOUR::Session&, double q); - ~Quantize (); + Quantize (ARDOUR::Session&, QuantizeType type, + bool snap_start, bool snap_end, + double start_grid, double end_grid, + float strength, float swing, float threshold); + ~Quantize (); - int run (boost::shared_ptr<ARDOUR::Region>); + int operator() (std::vector<Evoral::Sequence<Evoral::MusicalTime>::Notes>&); + std::string name() const { return std::string ("quantize"); } private: - double _q; + ARDOUR::Session& session; + bool _snap_start; + bool _snap_end; + double _start_grid; + double _end_grid; + float _strength; + float _swing; + float _threshold; }; } /* namespace */ diff --git a/libs/ardour/ardour/route.h b/libs/ardour/ardour/route.h index 9195a50060..34bf9e8d9f 100644 --- a/libs/ardour/ardour/route.h +++ b/libs/ardour/ardour/route.h @@ -326,6 +326,9 @@ class Route : public SessionObject, public AutomatableControls void passthru (sframes_t start_frame, sframes_t end_frame, nframes_t nframes, int declick); + virtual void write_out_of_band_data (BufferSet& /* bufs */, sframes_t /* start_frame */, sframes_t /* end_frame */, + nframes_t /* nframes */) {} + virtual void process_output_buffers (BufferSet& bufs, sframes_t start_frame, sframes_t end_frame, nframes_t nframes, bool with_processors, int declick); diff --git a/libs/ardour/ardour/smf_source.h b/libs/ardour/ardour/smf_source.h index dc9cbeee55..73bef5480a 100644 --- a/libs/ardour/ardour/smf_source.h +++ b/libs/ardour/ardour/smf_source.h @@ -51,7 +51,7 @@ public: bool set_name (const std::string& newname) { return (set_source_name(newname, false) == 0); } - void append_event_unlocked_beats (const Evoral::Event<double>& ev); + void append_event_unlocked_beats (const Evoral::Event<Evoral::MusicalTime>& ev); void append_event_unlocked_frames (const Evoral::Event<nframes_t>& ev, sframes_t source_start); void mark_streaming_midi_write_started (NoteMode mode, sframes_t start_time); diff --git a/libs/ardour/ardour/tempo.h b/libs/ardour/ardour/tempo.h index fcda463fe1..843437dab7 100644 --- a/libs/ardour/ardour/tempo.h +++ b/libs/ardour/ardour/tempo.h @@ -204,7 +204,7 @@ class TempoMap : public PBD::StatefulDestructible nframes_t round_to_bar (nframes_t frame, int dir); nframes_t round_to_beat (nframes_t frame, int dir); - nframes_t round_to_beat_subdivision (nframes_t fr, int sub_num); + nframes_t round_to_beat_subdivision (nframes_t fr, int sub_num, int dir); nframes_t round_to_tick (nframes_t frame, int dir); void set_length (nframes_t frames); diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h index 809eb5b2b2..1a30cfd769 100644 --- a/libs/ardour/ardour/types.h +++ b/libs/ardour/ardour/types.h @@ -446,6 +446,12 @@ namespace ARDOUR { Rectified }; + enum QuantizeType { + Plain, + Legato, + Groove + }; + } // namespace ARDOUR std::istream& operator>>(std::istream& o, ARDOUR::SampleFormat& sf); diff --git a/libs/ardour/delivery.cc b/libs/ardour/delivery.cc index 01ff226b5e..1b5f96d36c 100644 --- a/libs/ardour/delivery.cc +++ b/libs/ardour/delivery.cc @@ -22,6 +22,8 @@ #include "pbd/enumwriter.h" #include "pbd/convert.h" +#include "ardour/midi_buffer.h" + #include "ardour/delivery.h" #include "ardour/audio_buffer.h" #include "ardour/amp.h" diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc index 0c0c0ead5b..da524307f6 100644 --- a/libs/ardour/midi_model.cc +++ b/libs/ardour/midi_model.cc @@ -70,6 +70,19 @@ MidiModel::apply_command(Session& session, Command* cmd) set_edited(true); } +/** Apply a command as part of a larger reversible transaction + * + * Ownership of cmd is taken, it must not be deleted by the caller. + * The command will constitute one item on the undo stack. + */ +void +MidiModel::apply_command_as_subcommand(Session& session, Command* cmd) +{ + (*cmd)(); + session.add_command(cmd); + set_edited(true); +} + // DeltaCommand @@ -107,17 +120,19 @@ MidiModel::DeltaCommand::operator()() { // This could be made much faster by using a priority_queue for added and // removed notes (or sort here), and doing a single iteration over _model - + Glib::Mutex::Lock lm (_model->_midi_source->mutex()); _model->_midi_source->invalidate(); // release model read lock _model->write_lock(); - for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) + for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) { _model->add_note_unlocked(*i); + } - for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) + for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) { _model->remove_note_unlocked(*i); - + } + _model->write_unlock(); _model->ContentsChanged(); /* EMIT SIGNAL */ } @@ -132,11 +147,13 @@ MidiModel::DeltaCommand::undo() _model->_midi_source->invalidate(); // release model read lock _model->write_lock(); - for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) + for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) { _model->remove_note_unlocked(*i); + } - for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) + for (NoteList::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) { _model->add_note_unlocked(*i); + } _model->write_unlock(); _model->ContentsChanged(); /* EMIT SIGNAL */ diff --git a/libs/ardour/midi_ring_buffer.cc b/libs/ardour/midi_ring_buffer.cc index 746dc3b3eb..007d5271c8 100644 --- a/libs/ardour/midi_ring_buffer.cc +++ b/libs/ardour/midi_ring_buffer.cc @@ -24,7 +24,7 @@ using namespace std; namespace ARDOUR { -/** Read a block of MIDI events from buffer. +/** Read a block of MIDI events from buffer into a MidiBuffer. * * Timestamps of events returned are relative to start (i.e. event with stamp 0 * occurred at start), with offset added. @@ -106,11 +106,13 @@ MidiRingBuffer<T>::read(MidiBuffer& dst, nframes_t start, nframes_t end, nframes // write MIDI buffer contents success = Evoral::EventRingBuffer<T>::full_read(ev_size, write_loc); - /*cerr << "wrote MidiEvent to Buffer: "; +#if 0 + cerr << "wrote MidiEvent to Buffer: " << hex; for (size_t i=0; i < ev_size; ++i) { - printf("%X ", write_loc[i]); + cerr << (int) write_loc[i] << ' '; } - printf("\n");*/ + cerr << dec << endl; +#endif if (success) { if (is_channel_event(status) && get_channel_mode() == ForceChannel) { diff --git a/libs/ardour/midi_track.cc b/libs/ardour/midi_track.cc index 5a53cecc3c..4c575e8cc4 100644 --- a/libs/ardour/midi_track.cc +++ b/libs/ardour/midi_track.cc @@ -401,7 +401,6 @@ MidiTrack::roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame, _silent = false; if ((dret = diskstream->process (transport_frame, nframes, can_record, rec_monitors_input)) != 0) { - silence (nframes); return dret; } @@ -416,10 +415,10 @@ MidiTrack::roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame, /* not actually recording, but we want to hear the input material anyway, at least potentially (depending on monitoring options) - */ + */ passthru (start_frame, end_frame, nframes, 0); - + } else { /* XXX is it true that the earlier test on n_outputs() @@ -434,12 +433,15 @@ MidiTrack::roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame, //const size_t limit = n_process_buffers().n_audio(); BufferSet& bufs = _session.get_scratch_buffers (n_process_buffers()); + + diskstream->get_playback (bufs.get_midi(0), start_frame, end_frame); + + /* append immediate messages to the first MIDI buffer (thus sending it to the first output port) */ - diskstream->get_playback(bufs.get_midi(0), start_frame, end_frame); + write_out_of_band_data (bufs, start_frame, end_frame, nframes); process_output_buffers (bufs, start_frame, end_frame, nframes, (!_session.get_record_enabled() || !Config->get_do_not_record_plugins()), declick); - } _main_outs->flush (nframes); @@ -448,13 +450,12 @@ MidiTrack::roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame, } void -MidiTrack::write_controller_messages(MidiBuffer& output_buf, sframes_t /*start*/, sframes_t /*end*/, nframes_t nframes) +MidiTrack::write_out_of_band_data (BufferSet& bufs, sframes_t /*start*/, sframes_t /*end*/, nframes_t nframes) { - // Append immediate events (UI controls) - - // XXX TAKE _port_offset in Port into account??? + // Append immediate events - _immediate_events.read (output_buf, 0, 0, nframes - 1); // all stamps = 0 + MidiBuffer& buf (bufs.get_midi (0)); + _immediate_events.read (buf, 0, 0, nframes - 1); // all stamps = 0 } int diff --git a/libs/ardour/quantize.cc b/libs/ardour/quantize.cc index e5f4abeccf..becb3bd491 100644 --- a/libs/ardour/quantize.cc +++ b/libs/ardour/quantize.cc @@ -16,6 +16,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include <cmath> #include "pbd/basename.h" @@ -31,15 +32,24 @@ using namespace std; using namespace ARDOUR; - -/** Quantize notes, valid for MIDI regions only. +/** Quantize notes * - * Q is the quantize value in beats, ie 1.0 = quantize to beats, + * grid parameters are the quantize value in beats, ie 1.0 = quantize to beats, * 0.25 = quantize to beats/4, etc. */ -Quantize::Quantize (Session& s, double q) - : Filter (s) - , _q(q) + +Quantize::Quantize (Session& s, QuantizeType /* type */, + bool snap_start, bool snap_end, + double start_grid, double end_grid, + float strength, float swing, float threshold) + : session (s) + , _snap_start (snap_start) + , _snap_end (snap_end) + , _start_grid(start_grid) + , _end_grid(end_grid) + , _strength (strength/100.0) + , _swing (swing/100.0) + , _threshold (threshold) { } @@ -48,32 +58,70 @@ Quantize::~Quantize () } int -Quantize::run (boost::shared_ptr<Region> r) +Quantize::operator () (std::vector<Evoral::Sequence<Evoral::MusicalTime>::Notes>& seqs) { - boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion>(r); - if (!region) - return -1; - - // FIXME: how to make a whole file region if it isn't? - //assert(region->whole_file()); - - boost::shared_ptr<MidiSource> src = region->midi_source(0); - src->load_model(); - - boost::shared_ptr<MidiModel> model = src->model(); - - for (Evoral::Sequence<MidiModel::TimeType>::Notes::iterator i = model->notes().begin(); - i != model->notes().end(); ++i) { - const double new_time = lrint((*i)->time() / _q) * _q; - double new_dur = lrint((*i)->length() / _q) * _q; - if (new_dur == 0.0) - new_dur = _q; - - (*i)->set_time(new_time); - (*i)->set_length(new_dur); + bool even; + + for (std::vector<Evoral::Sequence<Evoral::MusicalTime>::Notes>::iterator s = seqs.begin(); + s != seqs.end(); ++s) { + + even = false; + + for (Evoral::Sequence<MidiModel::TimeType>::Notes::iterator i = (*s).begin(); + i != (*s).end(); ++i) { + + double new_start = round ((*i)->time() / _start_grid) * _start_grid; + double new_end = round ((*i)->end_time() / _end_grid) * _end_grid; + double delta; + + if (_swing > 0.0 && !even) { + + double next_grid = new_start + _start_grid; + + /* find a spot 2/3 (* swing factor) of the way between the grid point + we would put this note at, and the nominal position of the next note. + */ + + new_start = new_start + (2.0/3.0 * _swing * (next_grid - new_start)); + + } else if (_swing < 0.0 && !even) { + + double prev_grid = new_start - _start_grid; + + /* find a spot 2/3 (* swing factor) of the way between the grid point + we would put this note at, and the nominal position of the previous note. + */ + + new_start = new_start - (2.0/3.0 * _swing * (new_start - prev_grid)); + + } + + delta = new_start - (*i)->time(); + + if (fabs (delta) >= _threshold) { + if (_snap_start) { + delta *= _strength; + (*i)->set_time ((*i)->time() + delta); + } + } + + if (_snap_end) { + delta = new_end - (*i)->end_time(); + + if (fabs (delta) >= _threshold) { + double new_dur = new_end - new_start; + + if (new_dur == 0.0) { + new_dur = _end_grid; + } + + (*i)->set_length (new_dur); + } + } + + even = !even; + } } - model->set_edited(true); - return 0; } diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index 989cecdd6b..e922a2f33f 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -479,13 +479,16 @@ Route::passthru (sframes_t start_frame, sframes_t end_frame, nframes_t nframes, } } + write_out_of_band_data (bufs, start_frame, end_frame, nframes); process_output_buffers (bufs, start_frame, end_frame, nframes, true, declick); } void Route::passthru_silence (sframes_t start_frame, sframes_t end_frame, nframes_t nframes, int declick) { - process_output_buffers (_session.get_silent_buffers (n_process_buffers()), start_frame, end_frame, nframes, true, declick); + BufferSet& bufs (_session.get_silent_buffers (n_process_buffers())); + write_out_of_band_data (bufs, start_frame, end_frame, nframes); + process_output_buffers (bufs, start_frame, end_frame, nframes, true, declick); } void diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 3056016752..a2307901bc 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -1099,30 +1099,91 @@ TempoMap::round_to_beat (nframes_t fr, int dir) } nframes_t - -TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num) +TempoMap::round_to_beat_subdivision (nframes_t fr, int sub_num, int dir) { BBT_Time the_beat; uint32_t ticks_one_half_subdivisions_worth; uint32_t ticks_one_subdivisions_worth; + uint32_t difference; bbt_time(fr, the_beat); ticks_one_subdivisions_worth = (uint32_t)Meter::ticks_per_beat / sub_num; ticks_one_half_subdivisions_worth = ticks_one_subdivisions_worth / 2; + + if (dir > 0) { + + /* round to next */ - if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) { - uint32_t difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth); - if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) { - the_beat.beats++; - the_beat.ticks += difference; - the_beat.ticks -= (uint32_t)Meter::ticks_per_beat; - } else { - the_beat.ticks += difference; - } - } else { - the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth; + uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth; + + if (mod == 0) { + /* right on the subdivision, so the difference is just the subdivision ticks */ + difference = ticks_one_subdivisions_worth; + + } else { + /* not on subdivision, compute distance to next subdivision */ + + difference = ticks_one_subdivisions_worth - mod; + } + + if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) { + the_beat.beats++; + the_beat.ticks += difference; + the_beat.ticks -= (uint32_t)Meter::ticks_per_beat; + } else { + the_beat.ticks += difference; + } + + } else if (dir < 0) { + + /* round to previous */ + + uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth; + + if (mod == 0) { + /* right on the subdivision, so the difference is just the subdivision ticks */ + difference = ticks_one_subdivisions_worth; + cerr << "On the sub, move by 1 sub = " << difference << endl; + } else { + /* not on subdivision, compute distance to previous subdivision, which + is just the modulus. + */ + + difference = mod; + cerr << "off the sub, move by 1 sub = " << difference << endl; + } + + + cerr << "ticks = " << the_beat.ticks << endl; + + if (the_beat.ticks < difference) { + cerr << "backup beats, set ticks to " + << (uint32_t)Meter::ticks_per_beat - difference << endl; + the_beat.beats--; + the_beat.ticks = (uint32_t)Meter::ticks_per_beat - difference; + } else { + cerr << " reduce ticks\n"; + the_beat.ticks -= difference; + } + + } else { + /* round to nearest */ + + if (the_beat.ticks % ticks_one_subdivisions_worth > ticks_one_half_subdivisions_worth) { + difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth); + if (the_beat.ticks + difference >= (uint32_t)Meter::ticks_per_beat) { + the_beat.beats++; + the_beat.ticks += difference; + the_beat.ticks -= (uint32_t)Meter::ticks_per_beat; + } else { + the_beat.ticks += difference; + } + } else { + // difference = ticks_one_subdivisions_worth - (the_beat.ticks % ticks_one_subdivisions_worth); + the_beat.ticks -= the_beat.ticks % ticks_one_subdivisions_worth; + } } return frame_time (the_beat); @@ -1139,7 +1200,9 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type) switch (type) { case Bar: if (dir < 0) { - /* relax */ + if (bbt.bars > 1) { + bbt.bars--; + } } else if (dir > 0) { if (bbt.beats > 0) { bbt.bars++; @@ -1157,7 +1220,9 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type) case Beat: if (dir < 0) { - /* relax */ + if (bbt.beats > 1) { + bbt.beats--; + } } else if (dir > 0) { if (bbt.ticks > 0) { bbt.beats++; @@ -1178,11 +1243,12 @@ TempoMap::round_to_type (nframes_t frame, int dir, BBTPointType type) } - /* + /* cerr << "for " << frame << " round to " << bbt << " using " << metric.start() << endl; */ + return metric.frame() + count_frames_between (metric.start(), bbt); } diff --git a/libs/ardour/track.cc b/libs/ardour/track.cc index 64ec77b683..23da42f86e 100644 --- a/libs/ardour/track.cc +++ b/libs/ardour/track.cc @@ -24,6 +24,7 @@ #include "ardour/audioplaylist.h" #include "ardour/audioregion.h" #include "ardour/audiosource.h" +#include "ardour/delivery.h" #include "ardour/diskstream.h" #include "ardour/io_processor.h" #include "ardour/meter.h" @@ -316,6 +317,8 @@ Track::no_roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame, passthru (start_frame, end_frame, nframes, false); } + _main_outs->flush (nframes); + return 0; } diff --git a/libs/evoral/evoral/Event.hpp b/libs/evoral/evoral/Event.hpp index 7703963b95..096d00de35 100644 --- a/libs/evoral/evoral/Event.hpp +++ b/libs/evoral/evoral/Event.hpp @@ -57,7 +57,8 @@ struct Event { inline const Event& operator=(const Event& copy) { _type = copy._type; - _time = copy._time; + _original_time = copy._original_time; + _nominal_time = copy._nominal_time; if (_owns_buf) { if (copy._buf) { if (copy._size > _size) { @@ -84,7 +85,8 @@ struct Event { } _type = copy._type; - _time = copy._time; + _original_time = copy._nominal_time; + _nominal_time = copy._nominal_time; _size = copy._size; _buf = copy._buf; } @@ -99,7 +101,8 @@ struct Event { _buf = buf; } - _time = t; + _original_time = t; + _nominal_time = t; _size = size; } @@ -107,7 +110,10 @@ struct Event { if (_type != other._type) return false; - if (_time != other._time) + if (_nominal_time != other._nominal_time) + return false; + + if (_original_time != other._original_time) return false; if (_size != other._size) @@ -151,7 +157,8 @@ struct Event { inline void clear() { _type = 0; - _time = 0; + _original_time = 0; + _nominal_time = 0; _size = 0; _buf = NULL; } @@ -164,8 +171,10 @@ struct Event { inline EventType event_type() const { return _type; } inline void set_event_type(EventType t) { _type = t; } - inline Time time() const { return _time; } - inline Time& time() { return _time; } + inline Time time() const { return _nominal_time; } + inline Time& time() { return _nominal_time; } + inline Time original_time() const { return _original_time; } + inline Time& original_time() { return _original_time; } inline uint32_t size() const { return _size; } inline uint32_t& size() { return _size; } @@ -174,7 +183,8 @@ struct Event { protected: EventType _type; /**< Type of event (application relative, NOT MIDI 'type') */ - Time _time; /**< Sample index (or beat time) at which event is valid */ + Time _original_time; /**< Sample index (or beat time) at which event is valid */ + Time _nominal_time; /**< quantized version of _time, used in preference */ uint32_t _size; /**< Number of uint8_ts of data in \a buffer */ uint8_t* _buf; /**< Raw MIDI data */ diff --git a/libs/evoral/evoral/Note.hpp b/libs/evoral/evoral/Note.hpp index c8365588ee..b891c2096a 100644 --- a/libs/evoral/evoral/Note.hpp +++ b/libs/evoral/evoral/Note.hpp @@ -24,7 +24,6 @@ namespace Evoral { - /** An abstract (protocol agnostic) note. * * Currently a note is defined as (on event, length, off event). @@ -76,5 +75,14 @@ private: } // namespace Evoral +template<typename Time> +std::ostream& operator<<(std::ostream& o, const Evoral::Note<Time>& n) { + o << "Note: pitch = " << (int) n.note() + << " @ " << n.time() << " .. " << n.end_time() + << " velocity " << (int) n.velocity() + << " chn " << (int) n.channel(); + return o; +} + #endif // EVORAL_NOTE_HPP diff --git a/libs/evoral/evoral/Sequence.hpp b/libs/evoral/evoral/Sequence.hpp index b059f4e231..df8c48a1b6 100644 --- a/libs/evoral/evoral/Sequence.hpp +++ b/libs/evoral/evoral/Sequence.hpp @@ -92,6 +92,14 @@ public: return a->time() < b->time(); } + struct LaterNoteComparator { + typedef const Note<Time>* value_type; + inline bool operator()(const boost::shared_ptr< const Note<Time> > a, + const boost::shared_ptr< const Note<Time> > b) const { + return a->time() > b->time(); + } + }; + struct LaterNoteEndComparator { typedef const Note<Time>* value_type; inline bool operator()(const boost::shared_ptr< const Note<Time> > a, diff --git a/libs/evoral/evoral/midi_util.h b/libs/evoral/evoral/midi_util.h index fd4526d4a8..6dee894546 100644 --- a/libs/evoral/evoral/midi_util.h +++ b/libs/evoral/evoral/midi_util.h @@ -21,6 +21,8 @@ #include <stdint.h> #include <stdbool.h> +#include <string> +#include <sys/types.h> #include <assert.h> #include "evoral/midi_events.h" @@ -113,6 +115,7 @@ midi_event_is_valid(const uint8_t* buffer, size_t len) return true; } +std::string midi_note_name (uint8_t noteval); } // namespace Evoral diff --git a/libs/evoral/evoral/types.hpp b/libs/evoral/evoral/types.hpp index 2643b82439..e9ce503068 100644 --- a/libs/evoral/evoral/types.hpp +++ b/libs/evoral/evoral/types.hpp @@ -27,6 +27,9 @@ namespace Evoral { /** Frame count (i.e. length of time in audio frames) */ typedef uint32_t FrameTime; +/** Musical time: beats relative to some defined origin */ +typedef double MusicalTime; + /** Type of an event (opaque, mapped by application) */ typedef uint32_t EventType; diff --git a/libs/evoral/src/Event.cpp b/libs/evoral/src/Event.cpp index dc13a070ee..d70ba1a04b 100644 --- a/libs/evoral/src/Event.cpp +++ b/libs/evoral/src/Event.cpp @@ -25,7 +25,8 @@ namespace Evoral { template<typename Timestamp> Event<Timestamp>::Event(EventType type, Timestamp time, uint32_t size, uint8_t* buf, bool alloc) : _type(type) - , _time(time) + , _original_time(time) + , _nominal_time(time) , _size(size) , _buf(buf) , _owns_buf(alloc) @@ -43,7 +44,8 @@ Event<Timestamp>::Event(EventType type, Timestamp time, uint32_t size, uint8_t* template<typename Timestamp> Event<Timestamp>::Event(const Event& copy, bool owns_buf) : _type(copy._type) - , _time(copy._time) + , _original_time(copy._original_time) + , _nominal_time(copy._nominal_time) , _size(copy._size) , _buf(copy._buf) , _owns_buf(owns_buf) @@ -67,7 +69,7 @@ Event<Timestamp>::~Event() { #endif // EVORAL_EVENT_ALLOC -template class Event<double>; +template class Event<Evoral::MusicalTime>; template class Event<uint32_t>; } // namespace Evoral diff --git a/libs/evoral/src/MIDIEvent.cpp b/libs/evoral/src/MIDIEvent.cpp index ee71ca55d6..4af451a3b1 100644 --- a/libs/evoral/src/MIDIEvent.cpp +++ b/libs/evoral/src/MIDIEvent.cpp @@ -81,7 +81,7 @@ MIDIEvent<Time>::to_xml() const #endif // EVORAL_MIDI_XML -template class MIDIEvent<double>; +template class MIDIEvent<Evoral::MusicalTime>; } // namespace Evoral diff --git a/libs/evoral/src/Note.cpp b/libs/evoral/src/Note.cpp index d86f7b8948..cc3d4fee62 100644 --- a/libs/evoral/src/Note.cpp +++ b/libs/evoral/src/Note.cpp @@ -73,7 +73,6 @@ Note<Time>::Note(const Note<Time>& copy) assert(channel() == copy.channel()); } - template<typename Time> Note<Time>::~Note() { @@ -98,6 +97,7 @@ Note<Time>::operator=(const Note<Time>& copy) return *this; } -template class Note<double>; +template class Note<Evoral::MusicalTime>; } // namespace Evoral + diff --git a/libs/evoral/src/OldSMF.cpp b/libs/evoral/src/OldSMF.cpp index 1440e3e128..7fc079aaed 100644 --- a/libs/evoral/src/OldSMF.cpp +++ b/libs/evoral/src/OldSMF.cpp @@ -364,6 +364,6 @@ SMF<Time>::write_var_len(uint32_t value) return ret; } -template class SMF<double>; +template class SMF<Evoral::MusicalTime>; } // namespace Evoral diff --git a/libs/evoral/src/Sequence.cpp b/libs/evoral/src/Sequence.cpp index 39772e6a6e..3b1729cfe8 100644 --- a/libs/evoral/src/Sequence.cpp +++ b/libs/evoral/src/Sequence.cpp @@ -787,7 +787,7 @@ Sequence<Time>::set_notes (const Sequence<Time>::Notes& n) _notes = n; } -template class Sequence<double>; +template class Sequence<Evoral::MusicalTime>; } // namespace Evoral diff --git a/libs/evoral/src/midi_util.cpp b/libs/evoral/src/midi_util.cpp new file mode 100644 index 0000000000..5f088cb925 --- /dev/null +++ b/libs/evoral/src/midi_util.cpp @@ -0,0 +1,55 @@ +/* This file is part of Evoral. + * Copyright (C) 2008 Dave Robillard <http://drobilla.net> + * Copyright (C) 2009 Paul Davis + * + * Evoral 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. + * + * Evoral 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 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "evoral/midi_util.h" +#include <cstdio> + +namespace Evoral { + +std::string +midi_note_name (uint8_t val) +{ + if (val > 127) { + return "???"; + } + + static const char* notes[] = { + "c", + "c#", + "d", + "d#", + "e", + "f", + "f#", + "g", + "a", + "a#", + "b", + "b#" + }; + + int octave = val/12; + static char buf[8]; + + val -= octave*12; + + snprintf (buf, sizeof (buf), "%s%d", notes[val], octave); + return buf; +} + +} diff --git a/libs/evoral/wscript b/libs/evoral/wscript index dd5b6f6db9..a625e4d1f4 100644 --- a/libs/evoral/wscript +++ b/libs/evoral/wscript @@ -68,6 +68,7 @@ def build(bld): src/ControlSet.cpp src/Curve.cpp src/Event.cpp + src/midi_util.cpp src/MIDIEvent.cpp src/Note.cpp src/SMF.cpp diff --git a/libs/gtkmm2ext/tearoff.cc b/libs/gtkmm2ext/tearoff.cc index eb5dc8f7ef..c33663e8f7 100644 --- a/libs/gtkmm2ext/tearoff.cc +++ b/libs/gtkmm2ext/tearoff.cc @@ -47,7 +47,7 @@ TearOff::TearOff (Widget& c, bool allow_resize) close_event_box.set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK); close_event_box.signal_button_release_event().connect (mem_fun (*this, &TearOff::close_click)); - own_window.add_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK|POINTER_MOTION_MASK|POINTER_MOTION_HINT_MASK); + own_window.add_events (KEY_PRESS_MASK|KEY_RELEASE_MASK|BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK|POINTER_MOTION_MASK|POINTER_MOTION_HINT_MASK); own_window.set_resizable (allow_resize); own_window.set_type_hint (WINDOW_TYPE_HINT_TOOLBAR); |