X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_time_axis.cc;h=79e53a1faf6ebaf98316e18d9125892bc9aece8d;hb=8bdf5cf1d0caa0c6e96c43d19dae756094eb4e2b;hp=9477e5c02e612e5fe1bc6b3dd142cbc6e18aa355;hpb=950ac6ef2b08b460adb16a1a3690077ecf55314c;p=ardour.git diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc index 9477e5c02e..79e53a1faf 100644 --- a/gtk2_ardour/midi_time_axis.cc +++ b/gtk2_ardour/midi_time_axis.cc @@ -33,12 +33,12 @@ #include "pbd/memento_command.h" #include "pbd/stateful_diff_command.h" -#include -#include -#include -#include -#include +#include "gtkmm2ext/gtk_ui.h" +#include "gtkmm2ext/selector.h" +#include "gtkmm2ext/bindable_button.h" +#include "gtkmm2ext/utils.h" +#include "ardour/file_source.h" #include "ardour/midi_playlist.h" #include "ardour/midi_diskstream.h" #include "ardour/midi_patch_manager.h" @@ -81,6 +81,7 @@ #include "rgb_macros.h" #include "selection.h" #include "simplerect.h" +#include "step_entry.h" #include "utils.h" #include "ardour/midi_track.h" @@ -115,6 +116,7 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess, , _midi_thru_item (0) , default_channel_menu (0) , controller_menu (0) + , step_editor (0) { subplugin_menu.set_name ("ArdourContextMenu"); @@ -165,6 +167,12 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess, /* ask for notifications of any new RegionViews */ _view->RegionViewAdded.connect (sigc::mem_fun(*this, &MidiTimeAxisView::region_view_added)); _view->attach (); + + midi_track()->PlaylistChanged.connect (*this, invalidator (*this), + boost::bind (&MidiTimeAxisView::playlist_changed, this), + gui_context()); + playlist_changed (); + } HBox* midi_controls_hbox = manage(new HBox()); @@ -229,6 +237,31 @@ MidiTimeAxisView::~MidiTimeAxisView () delete controller_menu; } +void +MidiTimeAxisView::playlist_changed () +{ + step_edit_region_connection.disconnect (); + midi_track()->playlist()->RegionRemoved.connect (step_edit_region_connection, invalidator (*this), + ui_bind (&MidiTimeAxisView::region_removed, this, _1), + gui_context()); +} + +void +MidiTimeAxisView::region_removed (boost::weak_ptr wr) +{ + boost::shared_ptr r (wr.lock()); + + if (!r) { + return; + } + + if (step_edit_region == r) { + step_edit_region.reset(); + // force a recompute of the insert position + step_edit_beat_pos = -1.0; + } +} + void MidiTimeAxisView::model_changed() { std::list device_modes = MIDI::Name::MidiPatchManager::instance() @@ -282,7 +315,7 @@ MidiTimeAxisView::set_height (uint32_t h) RouteTimeAxisView::set_height (h); if (height >= MIDI_CONTROLS_BOX_MIN_HEIGHT) { - _midi_controls_box.show(); + _midi_controls_box.show_all (); } else { _midi_controls_box.hide(); } @@ -377,6 +410,18 @@ MidiTimeAxisView::build_automation_action_menu () { using namespace Menu_Helpers; + /* If we have a controller menu, we need to detach it before + RouteTimeAxis::build_automation_action_menu destroys the + menu it is attached to. Otherwise GTK destroys + controller_menu's gobj, meaning that it can't be reattached + below. See bug #3134. + */ + + if (controller_menu) { + detach_menu (*controller_menu); + } + + _channel_command_menu_map.clear (); RouteTimeAxisView::build_automation_action_menu (); MenuList& automation_items = automation_action_menu->items(); @@ -393,9 +438,9 @@ MidiTimeAxisView::build_automation_action_menu () something about MIDI (!) would not expect to find them there. */ - add_channel_command_menu_item (automation_items, _("Program PropertyChange"), MidiPgmChangeAutomation, MIDI_CMD_PGM_CHANGE); - add_channel_command_menu_item (automation_items, _("Bender"), MidiPitchBenderAutomation, MIDI_CMD_BENDER); - add_channel_command_menu_item (automation_items, _("Pressure"), MidiChannelPressureAutomation, MIDI_CMD_CHANNEL_PRESSURE); + add_channel_command_menu_item (automation_items, _("Program Change"), MidiPgmChangeAutomation, 0); + add_channel_command_menu_item (automation_items, _("Bender"), MidiPitchBenderAutomation, 0); + add_channel_command_menu_item (automation_items, _("Pressure"), MidiChannelPressureAutomation, 0); /* now all MIDI controllers. Always offer the possibility that we will rebuild the controllers menu since it might need to be updated after a channel mode change or other change. Also detach it @@ -403,7 +448,6 @@ MidiTimeAxisView::build_automation_action_menu () */ build_controller_menu (); - detach_menu (*controller_menu); automation_items.push_back (SeparatorElem()); automation_items.push_back (MenuElem (_("Controllers"), *controller_menu)); @@ -422,10 +466,10 @@ MidiTimeAxisView::change_all_channel_tracks_visibility (bool yn, Evoral::Paramet if (selected_channels & (0x0001 << chn)) { Evoral::Parameter fully_qualified_param (param.type(), chn, param.id()); - RouteAutomationNode* node = automation_track (fully_qualified_param); + Gtk::CheckMenuItem* menu = automation_child_menu_item (fully_qualified_param); - if (node && node->menu_item) { - node->menu_item->set_active (yn); + if (menu) { + menu->set_active (yn); } } } @@ -477,23 +521,18 @@ MidiTimeAxisView::add_channel_command_menu_item (Menu_Helpers::MenuList& items, sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track), fully_qualified_param))); - RouteAutomationNode* node = automation_track (fully_qualified_param); + boost::shared_ptr track = automation_child (fully_qualified_param); bool visible = false; - if (node) { - if (node->track->marked_for_display()) { + if (track) { + if (track->marked_for_display()) { visible = true; } } - - CheckMenuItem* cmi = static_cast(&chn_items.back()); - if (node) { - node->menu_item = cmi; - } + CheckMenuItem* cmi = static_cast(&chn_items.back()); + _channel_command_menu_map[fully_qualified_param] = cmi; cmi->set_active (visible); - - parameter_menu_map[fully_qualified_param] = cmi; } } @@ -513,24 +552,19 @@ MidiTimeAxisView::add_channel_command_menu_item (Menu_Helpers::MenuList& items, sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track), fully_qualified_param))); - RouteAutomationNode* node = automation_track (fully_qualified_param); + boost::shared_ptr track = automation_child (fully_qualified_param); bool visible = false; - if (node) { - if (node->track->marked_for_display()) { + if (track) { + if (track->marked_for_display()) { visible = true; } } CheckMenuItem* cmi = static_cast(&items.back()); - if (node) { - node->menu_item = cmi; - } - + _channel_command_menu_map[fully_qualified_param] = cmi; cmi->set_active (visible); - parameter_menu_map[fully_qualified_param] = cmi; - /* one channel only */ break; } @@ -609,31 +643,25 @@ MidiTimeAxisView::build_controller_menu () sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track), fully_qualified_param))); - RouteAutomationNode* node = automation_track (fully_qualified_param); + boost::shared_ptr track = automation_child (fully_qualified_param); bool visible = false; - if (node) { - if (node->track->marked_for_display()) { + if (track) { + if (track->marked_for_display()) { visible = true; } } CheckMenuItem* cmi = static_cast(&chn_items.back()); - - if (node) { - node->menu_item = cmi; - } - + _controller_menu_map[fully_qualified_param] = cmi; cmi->set_active (visible); - - parameter_menu_map[fully_qualified_param] = cmi; } } /* add the per-channel menu to the list of controllers, with the name of the controller */ - - ctl_items.push_back (MenuElem (midi_name (ctl), *chn_menu)); - + ctl_items.push_back (MenuElem (string_compose ("%1: %2", ctl, midi_name (ctl)), *chn_menu)); + dynamic_cast (ctl_items.back().get_child())->set_use_markup (true); + } else { /* just one channel - create a single menu item for this ctl+channel combination*/ @@ -646,24 +674,19 @@ MidiTimeAxisView::build_controller_menu () sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track), fully_qualified_param))); - RouteAutomationNode* node = automation_track (fully_qualified_param); + boost::shared_ptr track = automation_child (fully_qualified_param); bool visible = false; - if (node) { - if (node->track->marked_for_display()) { + if (track) { + if (track->marked_for_display()) { visible = true; } } CheckMenuItem* cmi = static_cast(&ctl_items.back()); - if (node) { - node->menu_item = cmi; - } - + _controller_menu_map[fully_qualified_param] = cmi; cmi->set_active (visible); - - parameter_menu_map[fully_qualified_param] = cmi; /* one channel only */ break; } @@ -673,7 +696,7 @@ MidiTimeAxisView::build_controller_menu () /* add the menu for this block of controllers to the overall controller menu */ - items.push_back (MenuElem (string_compose (_("Controllers %1-%2"), i+1, i+16), *ctl_menu)); + items.push_back (MenuElem (string_compose (_("Controllers %1-%2"), i, i+15), *ctl_menu)); } } @@ -803,7 +826,7 @@ MidiTimeAxisView::show_existing_automation () RouteTimeAxisView::show_existing_automation (); } -/** Hide an automation track for the given parameter (pitch bend, channel pressure). +/** Create an automation track for the given parameter (pitch bend, channel pressure). */ void MidiTimeAxisView::create_automation_child (const Evoral::Parameter& param, bool show) @@ -872,30 +895,74 @@ void MidiTimeAxisView::start_step_editing () { step_edit_insert_position = _editor.get_preferred_edit_position (); - step_edit_beat_pos = 0; + _step_edit_triplet_countdown = 0; + _step_edit_within_chord = 0; + _step_edit_chord_duration = 0.0; + step_edit_region = playlist()->top_region_at (step_edit_insert_position); if (step_edit_region) { RegionView* rv = view()->find_view (step_edit_region); step_edit_region_view = dynamic_cast (rv); + } else { - step_edit_region_view = 0; - } + step_edit_region = add_region (step_edit_insert_position); + RegionView* rv = view()->find_view (step_edit_region); + step_edit_region_view = dynamic_cast(rv); + } + + assert (step_edit_region); + assert (step_edit_region_view); + + if (step_editor == 0) { + step_editor = new StepEntry (*this); + step_editor->signal_delete_event().connect (sigc::mem_fun (*this, &MidiTimeAxisView::step_editor_hidden)); + step_editor->signal_hide().connect (sigc::mem_fun (*this, &MidiTimeAxisView::step_editor_hide)); + } - midi_track()->set_step_editing (true); + framecnt_t frames_from_start = _editor.get_preferred_edit_position() - step_edit_region->position(); + + assert (frames_from_start >= 0); + + step_edit_beat_pos = step_edit_region_view->frames_to_beats (frames_from_start); + + step_edit_region_view->show_step_edit_cursor (step_edit_beat_pos); + step_edit_region_view->set_step_edit_cursor_width (step_editor->note_length()); + + step_editor->set_position (WIN_POS_MOUSE); + step_editor->present (); +} + +bool +MidiTimeAxisView::step_editor_hidden (GdkEventAny*) +{ + step_editor_hide (); + return true; } void -MidiTimeAxisView::stop_step_editing () +MidiTimeAxisView::step_editor_hide () { + /* everything else will follow the change in the model */ midi_track()->set_step_editing (false); } +void +MidiTimeAxisView::stop_step_editing () +{ + if (step_editor) { + step_editor->hide (); + } + + if (step_edit_region_view) { + step_edit_region_view->hide_step_edit_cursor(); + } +} + void MidiTimeAxisView::check_step_edit () { MidiRingBuffer& incoming (midi_track()->step_edit_ring_buffer()); - Evoral::Note note; uint8_t* buf; uint32_t bufsize = 32; @@ -917,47 +984,161 @@ MidiTimeAxisView::check_step_edit () incoming.read_contents (size, buf); if ((buf[0] & 0xf0) == MIDI_CMD_NOTE_ON) { + step_add_note (buf[0] & 0xf, buf[1], buf[2], 0.0); + } + } +} - if (step_edit_region == 0) { +int +MidiTimeAxisView::step_add_bank_change (uint8_t channel, uint8_t bank) +{ + return 0; +} - step_edit_region = add_region (step_edit_insert_position); - RegionView* rv = view()->find_view (step_edit_region); +int +MidiTimeAxisView::step_add_program_change (uint8_t channel, uint8_t program) +{ + return 0; +} - if (rv) { - step_edit_region_view = dynamic_cast(rv); - } else { - fatal << X_("programming error: no view found for new MIDI region") << endmsg; - /*NOTREACHED*/ - } - } +int +MidiTimeAxisView::step_add_note (uint8_t channel, uint8_t pitch, uint8_t velocity, Evoral::MusicalTime beat_duration) +{ + if (step_edit_region && step_edit_region_view) { + + if (beat_duration == 0.0) { + bool success; + beat_duration = _editor.get_grid_type_as_beats (success, step_edit_insert_position); + + if (!success) { + return -1; + } + } + + MidiStreamView* msv = midi_view(); + + /* make sure its visible on the vertical axis */ + + if (pitch < msv->lowest_note() || pitch > msv->highest_note()) { + msv->update_note_range (pitch); + msv->set_note_range (MidiStreamView::ContentsRange); + } + + /* make sure its visible on the horizontal axis */ + + nframes64_t fpos = step_edit_region->position() + + step_edit_region_view->beats_to_frames (step_edit_beat_pos + beat_duration); + + if (fpos >= (_editor.leftmost_position() + _editor.current_page_frames())) { + _editor.reset_x_origin (fpos - (_editor.current_page_frames()/4)); + } + + step_edit_region_view->step_add_note (channel, pitch, velocity, step_edit_beat_pos, beat_duration); + + if (_step_edit_triplet_countdown > 0) { + _step_edit_triplet_countdown--; + + if (_step_edit_triplet_countdown == 0) { + _step_edit_triplet_countdown = 3; + } + } + + if (!_step_edit_within_chord) { + step_edit_beat_pos += beat_duration; + step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos); + } else { + step_edit_beat_pos += 1.0/Meter::ticks_per_beat; // tiny, but no longer overlapping + _step_edit_chord_duration = max (_step_edit_chord_duration, beat_duration); + } + } + + return 0; +} - if (step_edit_region_view) { +void +MidiTimeAxisView::set_step_edit_cursor_width (Evoral::MusicalTime beats) +{ + if (step_edit_region_view) { + step_edit_region_view->set_step_edit_cursor_width (beats); + } +} - bool success; - Evoral::MusicalTime beats = _editor.get_grid_type_as_beats (success, step_edit_insert_position); +bool +MidiTimeAxisView::step_edit_within_triplet() const +{ + return _step_edit_triplet_countdown > 0; +} - if (!success) { - continue; - } +bool +MidiTimeAxisView::step_edit_within_chord() const +{ + return _step_edit_within_chord; +} - step_edit_region_view->add_note (buf[0] & 0xf, buf[1], buf[2], step_edit_beat_pos, beats); - step_edit_beat_pos += beats; - } - } +void +MidiTimeAxisView::step_edit_toggle_triplet () +{ + if (_step_edit_triplet_countdown == 0) { + _step_edit_within_chord = false; + _step_edit_triplet_countdown = 3; + } else { + _step_edit_triplet_countdown = 0; + } +} - } +void +MidiTimeAxisView::step_edit_toggle_chord () +{ + if (_step_edit_within_chord) { + _step_edit_within_chord = false; + step_edit_beat_pos += _step_edit_chord_duration; + step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos); + } else { + _step_edit_triplet_countdown = 0; + _step_edit_within_chord = true; + } } void -MidiTimeAxisView::step_edit_rest () +MidiTimeAxisView::step_edit_rest (Evoral::MusicalTime beats) { bool success; - Evoral::MusicalTime beats = _editor.get_grid_type_as_beats (success, step_edit_insert_position); - step_edit_beat_pos += beats; + + if (beats == 0.0) { + beats = _editor.get_grid_type_as_beats (success, step_edit_insert_position); + } else { + success = true; + } + + if (success) { + step_edit_beat_pos += beats; + step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos); + } +} + +void +MidiTimeAxisView::step_edit_beat_sync () +{ + step_edit_beat_pos = ceil (step_edit_beat_pos); + step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos); +} + +void +MidiTimeAxisView::step_edit_bar_sync () +{ + if (!_session || !step_edit_region_view || !step_edit_region) { + return; + } + + nframes64_t fpos = step_edit_region->position() + + step_edit_region_view->beats_to_frames (step_edit_beat_pos); + fpos = _session->tempo_map().round_to_bar (fpos, 1); + step_edit_beat_pos = ceil (step_edit_region_view->frames_to_beats (fpos - step_edit_region->position())); + step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos); } boost::shared_ptr -MidiTimeAxisView::add_region (nframes64_t pos) +MidiTimeAxisView::add_region (framepos_t pos) { Editor* real_editor = dynamic_cast (&_editor); @@ -970,8 +1151,8 @@ MidiTimeAxisView::add_region (nframes64_t pos) const Tempo& t = _session->tempo_map().tempo_at(start); double length = floor (m.frames_per_bar(t, _session->frame_rate())); - boost::shared_ptr src = _session->create_midi_source_for_session (view()->trackview().track()->name()); - + boost::shared_ptr src = _session->create_midi_source_for_session (view()->trackview().track().get(), + view()->trackview().track()->name()); PropertyList plist; plist.add (ARDOUR::Properties::start, 0); @@ -979,7 +1160,7 @@ MidiTimeAxisView::add_region (nframes64_t pos) plist.add (ARDOUR::Properties::name, PBD::basename_nosuffix(src->name())); boost::shared_ptr region = (RegionFactory::create (src, plist)); - + playlist()->add_region (region, start); _session->add_command (new StatefulDiffCommand (playlist())); @@ -1070,9 +1251,9 @@ MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t) for (uint32_t chn = 0; chn < 16; ++chn) { Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl); - RouteAutomationNode* node = automation_track (fully_qualified_param); + boost::shared_ptr track = automation_child (fully_qualified_param); - if (!node) { + if (!track) { continue; } @@ -1080,9 +1261,9 @@ MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t) /* channel not in use. hiding it will trigger RouteTimeAxisView::automation_track_hidden() which will cause a redraw. We don't want one per channel, so block that with no_redraw. */ - changed = node->track->set_visibility (false) || changed; + changed = track->set_visibility (false) || changed; } else { - changed = node->track->set_visibility (true) || changed; + changed = track->set_visibility (true) || changed; } } } @@ -1092,6 +1273,7 @@ MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t) /* TODO: Bender, PgmChange, Pressure */ /* invalidate the controller menu, so that we rebuilt it next time */ + _controller_menu_map.clear (); delete controller_menu; controller_menu = 0; @@ -1099,3 +1281,24 @@ MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t) _route->gui_changed ("track_height", this); } } + +Gtk::CheckMenuItem* +MidiTimeAxisView::automation_child_menu_item (Evoral::Parameter param) +{ + Gtk::CheckMenuItem* m = RouteTimeAxisView::automation_child_menu_item (param); + if (m) { + return m; + } + + ParameterMenuMap::iterator i = _controller_menu_map.find (param); + if (i != _controller_menu_map.end()) { + return i->second; + } + + i = _channel_command_menu_map.find (param); + if (i != _channel_command_menu_map.end()) { + return i->second; + } + + return 0; +}