13
0

start to provide real functionality in MIDI list editor. far from finished, but then, have *you* read the Logic manual?

git-svn-id: svn://localhost/ardour2/branches/3.0@11415 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2012-02-01 03:33:42 +00:00
parent a09a71d674
commit e39e6196c6
3 changed files with 281 additions and 46 deletions

View File

@ -18,6 +18,8 @@
#include <cmath>
#include <gtkmm/cellrenderercombo.h>
#include "evoral/midi_util.h"
#include "evoral/Note.hpp"
@ -32,6 +34,7 @@
#include "gtkmm2ext/keyboard.h"
#include "midi_list_editor.h"
#include "note_player.h"
#include "i18n.h"
@ -39,10 +42,12 @@ using namespace std;
using namespace Gtk;
using namespace Glib;
using namespace ARDOUR;
using Timecode::BBT_Time;
MidiListEditor::MidiListEditor (Session* s, boost::shared_ptr<MidiRegion> r)
MidiListEditor::MidiListEditor (Session* s, boost::shared_ptr<MidiRegion> r, boost::shared_ptr<MidiTrack> tr)
: ArdourWindow (r->name())
, region (r)
, track (tr)
{
/* We do not handle nested sources/regions. Caller should have tackled this */
@ -52,25 +57,79 @@ MidiListEditor::MidiListEditor (Session* s, boost::shared_ptr<MidiRegion> r)
set_session (s);
edit_column = -1;
editing_renderer = 0;
model = ListStore::create (columns);
view.set_model (model);
view.signal_key_press_event().connect (sigc::mem_fun (*this, &MidiListEditor::key_press));
view.signal_key_release_event().connect (sigc::mem_fun (*this, &MidiListEditor::key_release));
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");
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);
view.append_column (_("Start"), columns.start);
view.append_column (_("Channel"), columns.channel);
view.append_column (_("Num"), columns.note);
view.append_column (_("Name"), columns.note_name);
view.append_column (_("Vel"), columns.velocity);
view.append_column (_("Length"), columns.length);
/* use a combo renderer for length, so that we can offer a selection
of pre-defined note lengths. we still allow edited values with
arbitrary length (in ticks).
*/
Gtk::TreeViewColumn* lenCol = Gtk::manage (new Gtk::TreeViewColumn (_("Length")));
Gtk::CellRendererCombo* comboCell = Gtk::manage(new Gtk::CellRendererCombo);
lenCol->pack_start(*comboCell);
lenCol->add_attribute (comboCell->property_text(), columns.length);
comboCell->property_model() = note_length_model;
comboCell->property_text_column() = 1;
comboCell->property_has_entry() = false;
view.append_column (*lenCol);
view.append_column (_("End"), columns.end);
view.set_headers_visible (true);
view.set_rules_hint (true);
view.get_selection()->set_mode (SELECTION_MULTIPLE);
view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &MidiListEditor::selection_changed));
for (int i = 0; i < 6; ++i) {
for (int i = 0; i < 7; ++i) {
CellRendererText* renderer = dynamic_cast<CellRendererText*>(view.get_column_cell_renderer (i));
renderer->property_editable() = true;
renderer->signal_editing_started().connect (sigc::bind (sigc::mem_fun (*this, &MidiListEditor::editing_started), i));
@ -88,8 +147,13 @@ MidiListEditor::MidiListEditor (Session* s, boost::shared_ptr<MidiRegion> r)
view.show ();
scroller.show ();
buttons.show ();
vbox.show ();
add (scroller);
vbox.pack_start (buttons, false, false);
vbox.pack_start (scroller, true, true);
add (vbox);
set_size_request (-1, 400);
}
@ -100,25 +164,50 @@ MidiListEditor::~MidiListEditor ()
bool
MidiListEditor::key_press (GdkEventKey* ev)
{
bool editing = !_current_edit.empty();
bool ret = false;
TreeModel::Path path;
TreeViewColumn* col;
if (editing) {
switch (ev->keyval) {
case GDK_Tab:
break;
case GDK_Right:
break;
case GDK_Left:
break;
case GDK_Up:
break;
case GDK_Down:
break;
case GDK_Escape:
break;
switch (ev->keyval) {
case GDK_Tab:
if (edit_column > 0) {
if (edit_column >= 6) {
edit_column = 0;
edit_path.next();
} else {
edit_column++;
}
col = view.get_column (edit_column);
path = edit_path;
view.set_cursor (path, *col, true);
ret = true;
}
break;
case GDK_Up:
if (edit_column > 0) {
edit_path.prev ();
col = view.get_column (edit_column);
path = edit_path;
view.set_cursor (path, *col, true);
ret = true;
}
break;
case GDK_Down:
if (edit_column > 0) {
edit_path.next ();
col = view.get_column (edit_column);
path = edit_path;
view.set_cursor (path, *col, true);
ret = true;
}
break;
case GDK_Escape:
stop_editing (true);
break;
}
return ret;
@ -128,22 +217,53 @@ bool
MidiListEditor::key_release (GdkEventKey* ev)
{
bool ret = false;
TreeModel::Path path;
TreeViewColumn* col;
TreeModel::iterator iter;
TreeModel::Row row;
MidiModel::NoteDiffCommand* cmd;
boost::shared_ptr<MidiModel> m (region->midi_source(0)->model());
boost::shared_ptr<NoteType> note;
boost::shared_ptr<NoteType> copy;
switch (ev->keyval) {
case GDK_Insert:
/* add a new note to the model, based on the note at the cursor
* pos
*/
view.get_cursor (path, col);
iter = model->get_iter (path);
cmd = m->new_note_diff_command (_("insert new note"));
note = (*iter)[columns._note];
copy.reset (new NoteType (*note.get()));
cmd->add (copy);
m->apply_command (*_session, cmd);
/* model has been redisplayed by now */
path.next ();
/* select, start editing column 2 (note) */
col = view.get_column (2);
view.set_cursor (path, *col, true);
break;
case GDK_Delete:
case GDK_BackSpace:
delete_selected_note ();
if (edit_column < 0) {
delete_selected_note ();
}
ret = true;
break;
case GDK_z:
if (_session && Gtkmm2ext::Keyboard::modifier_state_contains (ev->state, Gtkmm2ext::Keyboard::PrimaryModifier)) {
_session->undo (1);
ret = true;
}
break;
case GDK_r:
if (_session && Gtkmm2ext::Keyboard::modifier_state_contains (ev->state, Gtkmm2ext::Keyboard::PrimaryModifier)) {
_session->redo (1);
ret = true;
}
break;
@ -186,37 +306,109 @@ MidiListEditor::delete_selected_note ()
m->apply_command (*_session, cmd);
}
void
MidiListEditor::stop_editing (bool cancelled)
{
if (editing_renderer) {
editing_renderer->stop_editing (cancelled);
}
}
void
MidiListEditor::editing_started (CellEditable*, const string& path, int colno)
{
_current_edit = path;
cerr << "Now editing " << _current_edit << " Column " << colno << endl;
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));
}
void
MidiListEditor::editing_canceled ()
{
_current_edit = "";
cerr << "editing cancelled with edit_column = " << edit_column << " path \"" << edit_path.to_string() << "\"\n";
edit_path.clear ();
edit_column = -1;
editing_renderer = 0;
}
void
MidiListEditor::edited (const std::string& path, const std::string& /* text */)
MidiListEditor::edited (const std::string& path, const std::string& text)
{
TreeModel::iterator iter = model->get_iter (path);
cerr << "Edit at " << path << endl;
if (!iter) {
if (!iter || text.empty()) {
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;
cerr << "Edited " << *note << endl;
cmd = m->new_note_diff_command (_("insert new note"));
redisplay_model ();
double fval;
int ival;
bool apply = false;
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);
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);
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);
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;
}
}
break;
case 6: // end
break;
default:
break;
}
if (apply) {
cerr << "Apply change\n";
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);
}
void
@ -252,15 +444,11 @@ MidiListEditor::redisplay_model ()
dur = (*i)->end_time() - (*i)->time();
bbt.beats = floor (dur);
bbt.ticks = (uint32_t) lrint (fmod (dur, 1.0) * Timecode::BBT_Time::ticks_per_beat);
_session->tempo_map().bbt_duration_at (region->position(), bbt, 0);
ss.str ("");
ss << bbt;
row[columns.length] = ss.str();
row[columns.length] = lrint ((*i)->length() * Timecode::BBT_Time::ticks_per_beat);
_session->tempo_map().bbt_time (conv.to ((*i)->end_time()), bbt);
ss.str ("");
ss << bbt;
row[columns.end] = ss.str();
@ -271,3 +459,27 @@ MidiListEditor::redisplay_model ()
view.set_model (model);
}
void
MidiListEditor::selection_changed ()
{
if (!Config->get_sound_midi_notes()) {
return;
}
TreeModel::Path path;
TreeModel::iterator iter;
boost::shared_ptr<NoteType> note;
TreeView::Selection::ListHandle_Path rows = view.get_selection()->get_selected_rows ();
NotePlayer* player = new NotePlayer (track);
for (TreeView::Selection::ListHandle_Path::iterator i = rows.begin(); i != rows.end(); ++i) {
if (iter = model->get_iter (*i)) {
note = (*iter)[columns._note];
player->add (note);
}
}
player->play ();
}

View File

@ -20,6 +20,8 @@
#define __ardour_gtk2_midi_list_editor_h_
#include <gtkmm/treeview.h>
#include <gtkmm/table.h>
#include <gtkmm/box.h>
#include <gtkmm/liststore.h>
#include <gtkmm/scrolledwindow.h>
@ -36,6 +38,7 @@ namespace Evoral {
namespace ARDOUR {
class MidiRegion;
class MidiModel;
class MidiTrack;
class Session;
};
@ -44,7 +47,8 @@ class MidiListEditor : public ArdourWindow
public:
typedef Evoral::Note<Evoral::MusicalTime> NoteType;
MidiListEditor(ARDOUR::Session*, boost::shared_ptr<ARDOUR::MidiRegion>);
MidiListEditor(ARDOUR::Session*, boost::shared_ptr<ARDOUR::MidiRegion>,
boost::shared_ptr<ARDOUR::MidiTrack>);
~MidiListEditor();
private:
@ -59,23 +63,40 @@ class MidiListEditor : public ArdourWindow
add (end);
add (_note);
};
Gtk::TreeModelColumn<uint8_t> channel;
Gtk::TreeModelColumn<uint8_t> note;
Gtk::TreeModelColumn<uint8_t> channel;
Gtk::TreeModelColumn<uint8_t> note;
Gtk::TreeModelColumn<std::string> note_name;
Gtk::TreeModelColumn<uint8_t> velocity;
Gtk::TreeModelColumn<uint8_t> velocity;
Gtk::TreeModelColumn<std::string> start;
Gtk::TreeModelColumn<std::string> length;
Gtk::TreeModelColumn<int> length;
Gtk::TreeModelColumn<std::string> end;
Gtk::TreeModelColumn<boost::shared_ptr<NoteType> > _note;
};
struct NoteLengthColumns : public Gtk::TreeModel::ColumnRecord {
NoteLengthColumns() {
add (ticks);
add (name);
}
Gtk::TreeModelColumn<int> ticks;
Gtk::TreeModelColumn<std::string> name;
};
MidiListModelColumns columns;
Glib::RefPtr<Gtk::ListStore> model;
NoteLengthColumns note_length_columns;
Glib::RefPtr<Gtk::ListStore> note_length_model;
Gtk::TreeView view;
Gtk::ScrolledWindow scroller;
std::string _current_edit;
Gtk::TreeModel::Path edit_path;
int edit_column;
Gtk::CellRendererText* editing_renderer;
Gtk::Table buttons;
Gtk::VBox vbox;
Gtk::ToggleButton additional_info_button;
boost::shared_ptr<ARDOUR::MidiRegion> region;
boost::shared_ptr<ARDOUR::MidiTrack> track;
/** connection used to connect to model's ContentChanged signal */
PBD::ScopedConnection content_connection;
@ -83,6 +104,7 @@ class MidiListEditor : public ArdourWindow
void edited (const std::string&, const std::string&);
void editing_started (Gtk::CellEditable*, const std::string& path, int);
void editing_canceled ();
void stop_editing (bool cancelled = false);
void redisplay_model ();
@ -90,6 +112,7 @@ class MidiListEditor : public ArdourWindow
bool key_release (GdkEventKey* ev);
void delete_selected_note ();
void selection_changed ();
};
#endif /* __ardour_gtk2_midi_list_editor_h_ */

View File

@ -811,7 +811,7 @@ void
MidiRegionView::show_list_editor ()
{
if (!_list_editor) {
_list_editor = new MidiListEditor (trackview.session(), midi_region());
_list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
}
_list_editor->present ();
}