summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2012-02-01 20:11:57 +0000
committerPaul Davis <paul@linuxaudiosystems.com>2012-02-01 20:11:57 +0000
commit2d015c06104186af6711f6040a82448723971481 (patch)
tree07f334d17d543b924c7ae78bbdbb090ece8a8d5c
parente39e6196c69adb51cab9283aac08777edf4698de (diff)
MIDI event list editor improvements, mostly to do with nagivation, but also edits are now applied across all selected notes. getting close to calling this done for 3.0
git-svn-id: svn://localhost/ardour2/branches/3.0@11418 d708f5d6-7413-0410-9779-e7cbd77b26cf
-rw-r--r--gtk2_ardour/editor.h2
-rw-r--r--gtk2_ardour/editor_actions.cc28
-rw-r--r--gtk2_ardour/midi_list_editor.cc294
-rw-r--r--gtk2_ardour/midi_list_editor.h5
4 files changed, 249 insertions, 80 deletions
diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h
index 7147b6378d..a654a83b9f 100644
--- a/gtk2_ardour/editor.h
+++ b/gtk2_ardour/editor.h
@@ -2091,6 +2091,8 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT;
}
+ void toggle_sound_midi_notes ();
+
friend class Drag;
friend class RegionDrag;
friend class RegionMoveDrag;
diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc
index 37dbf30ab7..d071377899 100644
--- a/gtk2_ardour/editor_actions.cc
+++ b/gtk2_ardour/editor_actions.cc
@@ -111,6 +111,7 @@ Editor::register_actions ()
ActionManager::register_action (editor_actions, X_("MarkerMenu"), _("Markers"));
ActionManager::register_action (editor_actions, X_("MeterFalloff"), _("Meter falloff"));
ActionManager::register_action (editor_actions, X_("MeterHold"), _("Meter hold"));
+ ActionManager::register_action (editor_actions, X_("MIDI"), _("MIDI Options"));
ActionManager::register_action (editor_actions, X_("MiscOptions"), _("Misc Options"));
ActionManager::register_action (editor_actions, X_("Monitoring"), _("Monitoring"));
ActionManager::register_action (editor_actions, X_("MoveActiveMarkMenu"), _("Active Mark"));
@@ -238,7 +239,6 @@ Editor::register_actions ()
reg_sens (editor_actions, "playhead-forward-to-grid", _("Forward to Grid"), sigc::mem_fun(*this, &Editor::playhead_forward_to_grid));
reg_sens (editor_actions, "playhead-backward-to-grid", _("Backward to Grid"), sigc::mem_fun(*this, &Editor::playhead_backward_to_grid));
-
reg_sens (editor_actions, "temporal-zoom-out", _("Zoom Out"), sigc::bind (sigc::mem_fun(*this, &Editor::temporal_zoom_step), true));
reg_sens (editor_actions, "temporal-zoom-in", _("Zoom In"), sigc::bind (sigc::mem_fun(*this, &Editor::temporal_zoom_step), false));
reg_sens (editor_actions, "zoom-to-session", _("Zoom to Session"), sigc::mem_fun(*this, &Editor::temporal_zoom_session));
@@ -390,6 +390,8 @@ Editor::register_actions ()
sigc::mem_fun(*this, &Editor::set_track_height), HeightSmall));
ActionManager::track_selection_sensitive_actions.push_back (act);
+ toggle_reg_sens (editor_actions, "sound-midi-notes", _("Sound Selected MIDI Notes"), sigc::mem_fun (*this, &Editor::toggle_sound_midi_notes));
+
Glib::RefPtr<ActionGroup> zoom_actions = ActionGroup::create (X_("Zoom"));
RadioAction::Group zoom_group;
@@ -1114,6 +1116,20 @@ Editor::zoom_focus_action (ZoomFocus focus)
}
void
+Editor::toggle_sound_midi_notes ()
+{
+ Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("sound-midi-notes"));
+
+ if (act) {
+ bool s = Config->get_sound_midi_notes();
+ Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
+ if (tact->get_active () != s) {
+ Config->set_sound_midi_notes (tact->get_active());
+ }
+ }
+}
+
+void
Editor::zoom_focus_chosen (ZoomFocus focus)
{
/* this is driven by a toggle on a radio group, and so is invoked twice,
@@ -1192,6 +1208,16 @@ Editor::parameter_changed (std::string p)
update_just_timecode ();
} else if (p == "show-zoom-tools") {
_zoom_tearoff->set_visible (Config->get_show_zoom_tools(), true);
+ } else if (p == "sound-midi-notes") {
+ Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("sound-midi-notes"));
+
+ if (act) {
+ bool s = Config->get_sound_midi_notes();
+ Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic (act);
+ if (tact->get_active () != s) {
+ tact->set_active (s);
+ }
+ }
}
}
diff --git a/gtk2_ardour/midi_list_editor.cc b/gtk2_ardour/midi_list_editor.cc
index 236f3dda7a..b18b57bab7 100644
--- a/gtk2_ardour/midi_list_editor.cc
+++ b/gtk2_ardour/midi_list_editor.cc
@@ -17,6 +17,7 @@
*/
#include <cmath>
+#include <map>
#include <gtkmm/cellrenderercombo.h>
@@ -32,6 +33,7 @@
#include "gtkmm2ext/gui_thread.h"
#include "gtkmm2ext/keyboard.h"
+#include "gtkmm2ext/actions.h"
#include "midi_list_editor.h"
#include "note_player.h"
@@ -44,11 +46,31 @@ using namespace Glib;
using namespace ARDOUR;
using Timecode::BBT_Time;
+static map<int,std::string> note_length_map;
+
+static void
+fill_note_length_map ()
+{
+ note_length_map.insert (make_pair<int,string> (BBT_Time::ticks_per_beat, _("Whole")));
+ note_length_map.insert (make_pair<int,string> (BBT_Time::ticks_per_beat/2, _("Half")));
+ note_length_map.insert (make_pair<int,string> (BBT_Time::ticks_per_beat/3, _("Triplet")));
+ note_length_map.insert (make_pair<int,string> (BBT_Time::ticks_per_beat/4, _("Quarter")));
+ note_length_map.insert (make_pair<int,string> (BBT_Time::ticks_per_beat/8, _("Eighth")));
+ note_length_map.insert (make_pair<int,string> (BBT_Time::ticks_per_beat/16, _("Sixteenth")));
+ note_length_map.insert (make_pair<int,string> (BBT_Time::ticks_per_beat/32, _("Thirty-second")));
+ note_length_map.insert (make_pair<int,string> (BBT_Time::ticks_per_beat/64, _("Sixty-fourth")));
+}
+
MidiListEditor::MidiListEditor (Session* s, boost::shared_ptr<MidiRegion> r, boost::shared_ptr<MidiTrack> tr)
: ArdourWindow (r->name())
+ , buttons (1, 1)
, region (r)
, track (tr)
{
+ if (note_length_map.empty()) {
+ fill_note_length_map ();
+ }
+
/* We do not handle nested sources/regions. Caller should have tackled this */
if (r->max_source_level() > 0) {
@@ -59,43 +81,19 @@ MidiListEditor::MidiListEditor (Session* s, boost::shared_ptr<MidiRegion> r, boo
edit_column = -1;
editing_renderer = 0;
+ editing_editable = 0;
model = ListStore::create (columns);
view.set_model (model);
note_length_model = ListStore::create (note_length_columns);
TreeModel::Row row;
- row = *(note_length_model->append());
- row[note_length_columns.ticks] = BBT_Time::ticks_per_beat;
- row[note_length_columns.name] = _("Whole");
-
- row = *(note_length_model->append());
- row[note_length_columns.ticks] = BBT_Time::ticks_per_beat/2;
- row[note_length_columns.name] = _("Half");
-
- row = *(note_length_model->append());
- row[note_length_columns.ticks] = BBT_Time::ticks_per_beat/3;
- row[note_length_columns.name] = _("Triplet");
-
- row = *(note_length_model->append());
- row[note_length_columns.ticks] = BBT_Time::ticks_per_beat/4;
- row[note_length_columns.name] = _("Quarter");
-
- row = *(note_length_model->append());
- row[note_length_columns.ticks] = BBT_Time::ticks_per_beat/8;
- row[note_length_columns.name] = _("Eighth");
-
- row = *(note_length_model->append());
- row[note_length_columns.ticks] = BBT_Time::ticks_per_beat;
- row[note_length_columns.name] = _("Sixteenth");
-
- row = *(note_length_model->append());
- row[note_length_columns.ticks] = BBT_Time::ticks_per_beat/32;
- row[note_length_columns.name] = _("Thirty-second");
-
- row = *(note_length_model->append());
- row[note_length_columns.ticks] = BBT_Time::ticks_per_beat/64;
- row[note_length_columns.name] = _("Sixty-fourth");
+
+ for (std::map<int,string>::iterator i = note_length_map.begin(); i != note_length_map.end(); ++i) {
+ row = *(note_length_model->append());
+ row[note_length_columns.ticks] = i->first;
+ row[note_length_columns.name] = i->second;
+ }
view.signal_key_press_event().connect (sigc::mem_fun (*this, &MidiListEditor::key_press), false);
view.signal_key_release_event().connect (sigc::mem_fun (*this, &MidiListEditor::key_release), false);
@@ -145,11 +143,20 @@ MidiListEditor::MidiListEditor (Session* s, boost::shared_ptr<MidiRegion> r, boo
region->midi_source(0)->model()->ContentsChanged.connect (content_connection, invalidator (*this),
boost::bind (&MidiListEditor::redisplay_model, this), gui_context());
+ buttons.attach (sound_notes_button, 0, 1, 0, 1);
+ Glib::RefPtr<Gtk::Action> act = ActionManager::get_action ("Editor", "sound-midi-notes");
+ if (act) {
+ gtk_activatable_set_related_action (GTK_ACTIVATABLE (sound_notes_button.gobj()), act->gobj());
+ }
+
view.show ();
scroller.show ();
buttons.show ();
vbox.show ();
+ sound_notes_button.show ();
+ vbox.set_spacing (6);
+ vbox.set_border_width (6);
vbox.pack_start (buttons, false, false);
vbox.pack_start (scroller, true, true);
@@ -167,38 +174,54 @@ MidiListEditor::key_press (GdkEventKey* ev)
bool ret = false;
TreeModel::Path path;
TreeViewColumn* col;
+ int colnum;
switch (ev->keyval) {
case GDK_Tab:
if (edit_column > 0) {
- if (edit_column >= 6) {
- edit_column = 0;
- edit_path.next();
+ colnum = edit_column;
+ path = edit_path;
+ if (editing_editable) {
+ editing_editable->editing_done ();
+ }
+ if (colnum >= 6) {
+ /* wrap to next line */
+ colnum = 0;
+ path.next();
} else {
- edit_column++;
+ colnum++;
}
- col = view.get_column (edit_column);
- path = edit_path;
+ col = view.get_column (colnum);
view.set_cursor (path, *col, true);
ret = true;
}
break;
case GDK_Up:
+ case GDK_uparrow:
if (edit_column > 0) {
- edit_path.prev ();
- col = view.get_column (edit_column);
+ colnum = edit_column;
path = edit_path;
+ if (editing_editable) {
+ editing_editable->editing_done ();
+ }
+ path.prev ();
+ col = view.get_column (colnum);
view.set_cursor (path, *col, true);
ret = true;
}
break;
case GDK_Down:
+ case GDK_downarrow:
if (edit_column > 0) {
- edit_path.next ();
- col = view.get_column (edit_column);
+ colnum = edit_column;
path = edit_path;
+ if (editing_editable) {
+ editing_editable->editing_done ();
+ }
+ path.next ();
+ col = view.get_column (colnum);
view.set_cursor (path, *col, true);
ret = true;
}
@@ -309,27 +332,41 @@ MidiListEditor::delete_selected_note ()
void
MidiListEditor::stop_editing (bool cancelled)
{
- if (editing_renderer) {
- editing_renderer->stop_editing (cancelled);
+ if (!cancelled) {
+ if (editing_editable) {
+ editing_editable->editing_done ();
+ }
+ } else {
+ if (editing_renderer) {
+ editing_renderer->stop_editing (cancelled);
+ }
}
}
void
-MidiListEditor::editing_started (CellEditable*, const string& path, int colno)
+MidiListEditor::editing_started (CellEditable* ed, const string& path, int colno)
{
- cerr << "start editing at [" << path << "] col " << colno << endl;
edit_path = TreePath (path);
edit_column = colno;
editing_renderer = dynamic_cast<CellRendererText*>(view.get_column_cell_renderer (colno));
+ editing_editable = ed;
+
+ if (ed) {
+ Gtk::Entry *e = dynamic_cast<Gtk::Entry*> (ed);
+ if (e) {
+ e->signal_key_press_event().connect (sigc::mem_fun (*this, &MidiListEditor::key_press), false);
+ e->signal_key_release_event().connect (sigc::mem_fun (*this, &MidiListEditor::key_release), false);
+ }
+ }
}
void
MidiListEditor::editing_canceled ()
{
- cerr << "editing cancelled with edit_column = " << edit_column << " path \"" << edit_path.to_string() << "\"\n";
edit_path.clear ();
edit_column = -1;
editing_renderer = 0;
+ editing_editable = 0;
}
void
@@ -341,56 +378,116 @@ MidiListEditor::edited (const std::string& path, const std::string& text)
return;
}
- cerr << "Edited " << path << " col " << edit_column << " to \"" << text << "\"\n";
-
boost::shared_ptr<NoteType> note = (*iter)[columns._note];
- boost::shared_ptr<MidiModel> m (region->midi_source(0)->model());
- MidiModel::NoteDiffCommand* cmd;
-
- cmd = m->new_note_diff_command (_("insert new note"));
+ MidiModel::NoteDiffCommand::Property prop (MidiModel::NoteDiffCommand::NoteNumber);
double fval;
int ival;
bool apply = false;
-
+ int idelta;
+ double fdelta;
+ char* opname;
switch (edit_column) {
case 0: // start
break;
case 1: // channel
// correct ival for zero-based counting after scan
- if (sscanf (text.c_str(), "%d", &ival) == 1 && --ival != note->note()) {
- cmd->change (note, MidiModel::NoteDiffCommand::NoteNumber, (uint8_t) ival);
+ if (sscanf (text.c_str(), "%d", &ival) == 1 && --ival != note->channel()) {
+ idelta = ival - note->channel();
+ prop = MidiModel::NoteDiffCommand::Channel;
+ opname = _("change note channel");
apply = true;
- cerr << "channel differs " << (int) ival << " vs. " << (int) note->channel() << endl;
}
break;
case 2: // note
if (sscanf (text.c_str(), "%d", &ival) == 1 && ival != note->note()) {
- cmd->change (note, MidiModel::NoteDiffCommand::NoteNumber, (uint8_t) ival);
+ idelta = ival - note->note();
+ prop = MidiModel::NoteDiffCommand::NoteNumber;
+ opname = _("change note number");
apply = true;
- cerr << "note number differs " << (int) ival << " vs. " << (int) note->note() << endl;
}
break;
case 3: // name
break;
case 4: // velocity
if (sscanf (text.c_str(), "%d", &ival) == 1 && ival != note->velocity()) {
- cmd->change (note, MidiModel::NoteDiffCommand::Velocity, (uint8_t) ival);
+ idelta = ival - note->velocity();
+ prop = MidiModel::NoteDiffCommand::Velocity;
+ opname = _("change note velocity");
apply = true;
- cerr << "velocity differs " << (int) ival << " vs. " << (int) note->velocity() << endl;
}
break;
case 5: // length
- if (sscanf (text.c_str(), "%d", &ival) == 1) {
- fval = (double) ival / Timecode::BBT_Time::ticks_per_beat;
-
- if (fval != note->length()) {
- cmd->change (note, MidiModel::NoteDiffCommand::Length, fval);
- apply = true;
- cerr << "length differs: " << fval << " vs. " << note->length() << endl;
+
+ if (sscanf (text.c_str(), "%lf", &fval) == 1) {
+
+ /* numeric value entered */
+
+ if (text.find ('.') == string::npos && text.find (',') == string::npos) {
+ /* integral => units are ticks */
+ fval = fval / BBT_Time::ticks_per_beat;
+ } else {
+ /* non-integral => beats, so use as-is */
+ }
+
+ } else {
+
+ /* assume its text from the combo. look for the map
+ * entry for the actual note ticks
+ */
+
+ int len_ticks = lrint (note->length() * Timecode::BBT_Time::ticks_per_beat);
+ std::map<int,string>::iterator x = note_length_map.find (len_ticks);
+
+ if (x == note_length_map.end()) {
+
+ /* tick length not in map - was
+ * displaying numeric value ... use new value
+ * from note length map, and convert to beats.
+ */
+
+ for (x = note_length_map.begin(); x != note_length_map.end(); ++x) {
+ if (x->second == text) {
+ break;
+ }
+ }
+
+ if (x != note_length_map.end()) {
+ fval = x->first / BBT_Time::ticks_per_beat;
+ }
+
+ } else {
+
+ fval = -1.0;
+
+ if (text != x->second) {
+
+ /* get ticks for the newly selected
+ * note length
+ */
+
+ for (x = note_length_map.begin(); x != note_length_map.end(); ++x) {
+ if (x->second == text) {
+ break;
+ }
+ }
+
+ if (x != note_length_map.end()) {
+ /* convert to beats */
+ fval = (double) x->first / BBT_Time::ticks_per_beat;
+ }
+ }
}
}
+
+ if (fval > 0.0) {
+ fdelta = fval - note->length();
+ prop = MidiModel::NoteDiffCommand::Length;
+ opname = _("change note length");
+ apply = true;
+ }
break;
+
case 6: // end
break;
default:
@@ -398,17 +495,51 @@ MidiListEditor::edited (const std::string& path, const std::string& text)
}
if (apply) {
- cerr << "Apply change\n";
+
+ boost::shared_ptr<MidiModel> m (region->midi_source(0)->model());
+ MidiModel::NoteDiffCommand* cmd = m->new_note_diff_command (opname);
+
+ TreeView::Selection::ListHandle_Path rows = view.get_selection()->get_selected_rows ();
+
+ for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) {
+ if (iter = model->get_iter (*i)) {
+
+ note = (*iter)[columns._note];
+
+ switch (prop) {
+ case MidiModel::NoteDiffCommand::Velocity:
+ cmd->change (note, prop, (uint8_t) (note->velocity() + idelta));
+ break;
+ case MidiModel::NoteDiffCommand::Length:
+ cmd->change (note, prop, note->length() + fdelta);
+ break;
+ case MidiModel::NoteDiffCommand::Channel:
+ cmd->change (note, prop, (uint8_t) (note->channel() + idelta));
+ break;
+ case MidiModel::NoteDiffCommand::NoteNumber:
+ cmd->change (note, prop, (uint8_t) (note->note() + idelta));
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+
m->apply_command (*_session, cmd);
- } else {
- cerr << "No change\n";
- }
-
- /* model has been redisplayed by now */
- /* keep selected row(s), move cursor there, to allow us to continue editing */
- TreeViewColumn* col = view.get_column (edit_column);
- view.set_cursor (edit_path, *col, 0);
+ /* model has been redisplayed by now */
+ /* keep selected row(s), move cursor there, don't continue editing */
+
+ TreeViewColumn* col = view.get_column (edit_column);
+ view.set_cursor (edit_path, *col, 0);
+
+ /* reset edit info, since we're done */
+
+ edit_path.clear ();
+ edit_column = -1;
+ editing_renderer = 0;
+ editing_editable = 0;
+ }
}
void
@@ -445,7 +576,16 @@ MidiListEditor::redisplay_model ()
bbt.beats = floor (dur);
bbt.ticks = (uint32_t) lrint (fmod (dur, 1.0) * Timecode::BBT_Time::ticks_per_beat);
- row[columns.length] = lrint ((*i)->length() * Timecode::BBT_Time::ticks_per_beat);
+ int len_ticks = lrint ((*i)->length() * Timecode::BBT_Time::ticks_per_beat);
+ std::map<int,string>::iterator x = note_length_map.find (len_ticks);
+
+ if (x != note_length_map.end()) {
+ row[columns.length] = x->second;
+ } else {
+ ss.str ("");
+ ss << len_ticks;
+ row[columns.length] = ss.str();
+ }
_session->tempo_map().bbt_time (conv.to ((*i)->end_time()), bbt);
diff --git a/gtk2_ardour/midi_list_editor.h b/gtk2_ardour/midi_list_editor.h
index faaec51f30..6061f54db6 100644
--- a/gtk2_ardour/midi_list_editor.h
+++ b/gtk2_ardour/midi_list_editor.h
@@ -68,7 +68,7 @@ class MidiListEditor : public ArdourWindow
Gtk::TreeModelColumn<std::string> note_name;
Gtk::TreeModelColumn<uint8_t> velocity;
Gtk::TreeModelColumn<std::string> start;
- Gtk::TreeModelColumn<int> length;
+ Gtk::TreeModelColumn<std::string> length;
Gtk::TreeModelColumn<std::string> end;
Gtk::TreeModelColumn<boost::shared_ptr<NoteType> > _note;
};
@@ -91,9 +91,10 @@ class MidiListEditor : public ArdourWindow
Gtk::TreeModel::Path edit_path;
int edit_column;
Gtk::CellRendererText* editing_renderer;
+ Gtk::CellEditable* editing_editable;
Gtk::Table buttons;
Gtk::VBox vbox;
- Gtk::ToggleButton additional_info_button;
+ Gtk::ToggleButton sound_notes_button;
boost::shared_ptr<ARDOUR::MidiRegion> region;
boost::shared_ptr<ARDOUR::MidiTrack> track;