#include "pbd/memento_command.h"
#include "pbd/stateful_diff_command.h"
-#include <gtkmm2ext/gtk_ui.h>
-#include <gtkmm2ext/selector.h>
-#include <gtkmm2ext/stop_signal.h>
-#include <gtkmm2ext/bindable_button.h>
-#include <gtkmm2ext/utils.h>
+#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"
#include "rgb_macros.h"
#include "selection.h"
#include "simplerect.h"
+#include "step_entry.h"
#include "utils.h"
#include "ardour/midi_track.h"
, _midi_thru_item (0)
, default_channel_menu (0)
, controller_menu (0)
+ , step_editor (0)
{
subplugin_menu.set_name ("ArdourContextMenu");
/* 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());
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<Region> wr)
+{
+ boost::shared_ptr<Region> 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<std::string> device_modes = MIDI::Name::MidiPatchManager::instance()
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();
}
{
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();
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
*/
build_controller_menu ();
- detach_menu (*controller_menu);
automation_items.push_back (SeparatorElem());
automation_items.push_back (MenuElem (_("Controllers"), *controller_menu));
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);
}
}
}
sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
fully_qualified_param)));
- RouteAutomationNode* node = automation_track (fully_qualified_param);
+ boost::shared_ptr<AutomationTimeAxisView> 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<CheckMenuItem*>(&chn_items.back());
- if (node) {
- node->menu_item = cmi;
- }
+ CheckMenuItem* cmi = static_cast<CheckMenuItem*>(&chn_items.back());
+ _channel_command_menu_map[fully_qualified_param] = cmi;
cmi->set_active (visible);
-
- parameter_menu_map[fully_qualified_param] = cmi;
}
}
sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
fully_qualified_param)));
- RouteAutomationNode* node = automation_track (fully_qualified_param);
+ boost::shared_ptr<AutomationTimeAxisView> 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<CheckMenuItem*>(&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;
}
sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
fully_qualified_param)));
- RouteAutomationNode* node = automation_track (fully_qualified_param);
+ boost::shared_ptr<AutomationTimeAxisView> 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<CheckMenuItem*>(&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 ("<b>%1</b>: %2", ctl, midi_name (ctl)), *chn_menu));
+ dynamic_cast<Label*> (ctl_items.back().get_child())->set_use_markup (true);
+
} else {
/* just one channel - create a single menu item for this ctl+channel combination*/
sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::toggle_automation_track),
fully_qualified_param)));
- RouteAutomationNode* node = automation_track (fully_qualified_param);
+ boost::shared_ptr<AutomationTimeAxisView> 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<CheckMenuItem*>(&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;
}
/* 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));
}
}
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)
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<MidiRegionView*> (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<MidiRegionView*>(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<nframes_t>& incoming (midi_track()->step_edit_ring_buffer());
- Evoral::Note<Evoral::MusicalTime> note;
uint8_t* buf;
uint32_t bufsize = 32;
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<MidiRegionView*>(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<Region>
-MidiTimeAxisView::add_region (nframes64_t pos)
+MidiTimeAxisView::add_region (framepos_t pos)
{
Editor* real_editor = dynamic_cast<Editor*> (&_editor);
const Tempo& t = _session->tempo_map().tempo_at(start);
double length = floor (m.frames_per_bar(t, _session->frame_rate()));
- boost::shared_ptr<Source> src = _session->create_midi_source_for_session (view()->trackview().track()->name());
-
+ boost::shared_ptr<Source> src = _session->create_midi_source_for_session (view()->trackview().track().get(),
+ view()->trackview().track()->name());
PropertyList plist;
plist.add (ARDOUR::Properties::start, 0);
plist.add (ARDOUR::Properties::name, PBD::basename_nosuffix(src->name()));
boost::shared_ptr<Region> region = (RegionFactory::create (src, plist));
-
+
playlist()->add_region (region, start);
_session->add_command (new StatefulDiffCommand (playlist()));
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<AutomationTimeAxisView> track = automation_child (fully_qualified_param);
- if (!node) {
+ if (!track) {
continue;
}
/* 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;
}
}
}
/* 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;
_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;
+}