the basics of step editing, more details to follow

git-svn-id: svn://localhost/ardour2/branches/3.0@5629 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2009-09-03 12:39:50 +00:00
parent c8932292e1
commit b0b584c2a5
15 changed files with 370 additions and 61 deletions

View File

@ -1366,46 +1366,19 @@ void
RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
{
MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (_dest_trackview);
if (!mtv) {
return;
}
const boost::shared_ptr<MidiDiskstream> diskstream =
boost::dynamic_pointer_cast<MidiDiskstream>(mtv->view()->trackview().track()->diskstream());
if (!diskstream) {
warning << "Cannot create non-MIDI region" << endl;
return;
}
if (!movement_occurred) {
_editor->begin_reversible_command (_("create region"));
XMLNode &before = mtv->playlist()->get_state();
nframes64_t start = _grab_frame;
_editor->snap_to (start, -1);
const Meter& m = _editor->session->tempo_map().meter_at(start);
const Tempo& t = _editor->session->tempo_map().tempo_at(start);
double length = floor (m.frames_per_bar(t, _editor->session->frame_rate()));
boost::shared_ptr<Source> src = _editor->session->create_midi_source_for_session(*diskstream.get());
mtv->playlist()->add_region (boost::dynamic_pointer_cast<MidiRegion>
(RegionFactory::create(src, 0, (nframes_t) length,
PBD::basename_nosuffix(src->name()))), start);
XMLNode &after = mtv->playlist()->get_state();
_editor->session->add_command(new MementoCommand<Playlist>(*mtv->playlist().get(), &before, &after));
_editor->commit_reversible_command();
mtv->add_region (_grab_frame);
} else {
motion (event, false);
// TODO: create region-create-drag region here
}
}
void
RegionGainDrag::motion (GdkEvent* /*event*/, bool)
{

View File

@ -32,7 +32,8 @@ ExportFileNotebook::ExportFileNotebook () :
{
/* Last page */
new_file_button.add (*Gtk::manage (new Gtk::Image (::get_icon("add"))));
new_file_button.set_image (*Gtk::manage (new Gtk::Image (::get_icon("add"))));
new_file_button.set_label (_(" Click here to add another format"));
new_file_button.set_alignment (0, 0.5);
new_file_button.set_relief (Gtk::RELIEF_NONE);

View File

@ -19,6 +19,8 @@
#include "evoral/midi_util.h"
#include "ardour/midi_region.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "midi_list_editor.h"
@ -29,20 +31,21 @@ using namespace Gtk;
using namespace Glib;
using namespace ARDOUR;
MidiListEditor::MidiListEditor (boost::shared_ptr<MidiRegion> r)
MidiListEditor::MidiListEditor (Session& s, boost::shared_ptr<MidiRegion> r)
: ArdourDialog (r->name(), false, false)
, session (s)
, 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 (_("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);
view.append_column (_("End"), columns.end);
view.set_headers_visible (true);
view.set_name (X_("MidiListView"));
view.set_rules_hint (true);
@ -70,8 +73,20 @@ MidiListEditor::~MidiListEditor ()
}
void
MidiListEditor::edited (const Glib::ustring& /* path */, const Glib::ustring& /* text */)
MidiListEditor::edited (const Glib::ustring& path, const Glib::ustring& /* text */)
{
TreeModel::iterator iter = model->get_iter (path);
cerr << "Edit at " << path << endl;
if (!iter) {
return;
}
boost::shared_ptr<NoteType> note = (*iter)[columns._note];
cerr << "Edited " << *note << endl;
redisplay_model ();
}
@ -87,12 +102,34 @@ MidiListEditor::redisplay_model ()
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_name] = _("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();
BBT_Time bbt;
BBT_Time dur;
stringstream ss;
session.tempo_map().bbt_time (region->position(), bbt);
dur.bars = 0;
dur.beats = floor ((*i)->time());
dur.ticks = 0;
session.tempo_map().bbt_duration_at (region->position(), dur, 0);
ss << bbt;
row[columns.start] = ss.str();
ss << dur;
row[columns.length] = ss.str();
session.tempo_map().bbt_time (region->position(), bbt);
/* XXX get end point */
ss << bbt;
row[columns.end] = ss.str();
row[columns._note] = (*i);
}
view.set_model (model);

View File

@ -31,12 +31,15 @@
namespace ARDOUR {
class MidiRegion;
class MidiModel;
class Session;
};
class MidiListEditor : public ArdourDialog
{
public:
MidiListEditor(boost::shared_ptr<ARDOUR::MidiRegion>);
typedef Evoral::Note<Evoral::MusicalTime> NoteType;
MidiListEditor(ARDOUR::Session&, boost::shared_ptr<ARDOUR::MidiRegion>);
~MidiListEditor();
private:
@ -49,16 +52,19 @@ class MidiListEditor : public ArdourDialog
add (start);
add (length);
add (end);
add (note);
};
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;
Gtk::TreeModelColumn<std::string> start;
Gtk::TreeModelColumn<std::string> length;
Gtk::TreeModelColumn<std::string> end;
Gtk::TreeModelColumn<boost::shared_ptr<NoteType> > _note;
};
ARDOUR::Session& session;
MidiListModelColumns columns;
Glib::RefPtr<Gtk::ListStore> model;
Gtk::TreeView view;

View File

@ -500,7 +500,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
void
MidiRegionView::show_list_editor ()
{
MidiListEditor* mle = new MidiListEditor (midi_region());
MidiListEditor* mle = new MidiListEditor (trackview.session(), midi_region());
mle->show ();
}
@ -761,6 +761,7 @@ MidiRegionView::display_sysexes()
string text = str.str();
ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
const double x = trackview.editor().frame_to_pixel(beats_to_frames(time));
double height = midi_stream_view()->contents_height();
@ -2235,6 +2236,30 @@ MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb)
apply_command ();
}
void
MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
Evoral::MusicalTime pos, Evoral::MusicalTime len)
{
boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
start_delta_command (_("step add"));
command_add_note (new_note, true, false);
apply_command ();
/* potentially extend region to hold new note */
nframes64_t end_frame = _region->position() + beats_to_frames (new_note->length());
nframes64_t region_end = _region->position() + _region->length() - 1;
if (end_frame > region_end) {
cerr << "Resize region!\n";
_region->set_length (end_frame, this);
} else {
redisplay_model ();
}
}
void
MidiRegionView::goto_next_note ()
{

View File

@ -88,6 +88,9 @@ class MidiRegionView : public RegionView
inline MidiStreamView* midi_stream_view() const
{ return midi_view()->midi_view(); }
void add_note (uint8_t channel, uint8_t number, uint8_t velocity,
Evoral::MusicalTime pos, Evoral::MusicalTime len);
void set_height (double);
void apply_note_range(uint8_t lowest, uint8_t highest, bool force=false);

View File

@ -28,7 +28,9 @@
#include "pbd/error.h"
#include "pbd/stl_delete.h"
#include "pbd/whitespace.h"
#include "pbd/basename.h"
#include "pbd/enumwriter.h"
#include "pbd/memento_command.h"
#include <gtkmm2ext/gtk_ui.h>
#include <gtkmm2ext/selector.h>
@ -39,40 +41,44 @@
#include "ardour/midi_playlist.h"
#include "ardour/midi_diskstream.h"
#include "ardour/midi_patch_manager.h"
#include "ardour/midi_source.h"
#include "ardour/processor.h"
#include "ardour/ladspa_plugin.h"
#include "ardour/location.h"
#include "ardour/playlist.h"
#include "ardour/region_factory.h"
#include "ardour/session.h"
#include "ardour/session_playlist.h"
#include "ardour/tempo.h"
#include "ardour/utils.h"
#include "ardour_ui.h"
#include "midi_time_axis.h"
#include "automation_time_axis.h"
#include "automation_line.h"
#include "add_midi_cc_track_dialog.h"
#include "ardour_ui.h"
#include "automation_line.h"
#include "automation_time_axis.h"
#include "canvas-note-event.h"
#include "canvas_impl.h"
#include "crossfade_view.h"
#include "editor.h"
#include "enums.h"
#include "ghostregion.h"
#include "gui_thread.h"
#include "keyboard.h"
#include "midi_scroomer.h"
#include "midi_streamview.h"
#include "midi_region_view.h"
#include "midi_time_axis.h"
#include "piano_roll_header.h"
#include "playlist_selector.h"
#include "plugin_selector.h"
#include "plugin_ui.h"
#include "point_selection.h"
#include "prompter.h"
#include "public_editor.h"
#include "region_view.h"
#include "rgb_macros.h"
#include "selection.h"
#include "simplerect.h"
#include "midi_streamview.h"
#include "utils.h"
#include "midi_scroomer.h"
#include "piano_roll_header.h"
#include "ghostregion.h"
#include "canvas-note-event.h"
#include "ardour/midi_track.h"
@ -553,3 +559,154 @@ MidiTimeAxisView::route_active_changed ()
}
}
void
MidiTimeAxisView::build_rec_context_menu ()
{
using namespace Menu_Helpers;
if (!is_track()) {
return;
}
rec_context_menu = manage (new Menu);
rec_context_menu->set_name ("ArdourContextMenu");
MenuList& items = rec_context_menu->items();
items.push_back (CheckMenuElem (_("Step Edit"),
(mem_fun (*this, &MidiTimeAxisView::toggle_step_editing))));
_step_edit_item = dynamic_cast<CheckMenuItem*>(&items.back());
_step_edit_item->set_active (midi_track()->step_editing());
}
void
MidiTimeAxisView::toggle_step_editing ()
{
if (!is_track()) {
return;
}
bool yn = _step_edit_item->get_active();
if (yn) {
start_step_editing ();
} else {
stop_step_editing ();
}
midi_track()->set_step_editing (yn);
}
void
MidiTimeAxisView::start_step_editing ()
{
step_edit_connection = Glib::signal_timeout().connect (mem_fun (*this, &MidiTimeAxisView::check_step_edit), 20);
step_edit_insert_position = _editor.get_preferred_edit_position ();
step_edit_beat_pos = 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;
}
}
void
MidiTimeAxisView::stop_step_editing ()
{
step_edit_connection.disconnect ();
}
bool
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;
buf = new uint8_t[bufsize];
while (incoming.read_space()) {
nframes_t time;
Evoral::EventType type;
uint32_t size;
incoming.read_prefix (&time, &type, &size);
if (size > bufsize) {
delete [] buf;
bufsize = size;
buf = new uint8_t[bufsize];
}
incoming.read_contents (size, buf);
if ((buf[0] & 0xf0) == MIDI_CMD_NOTE_ON) {
if (step_edit_region == 0) {
cerr << "Add new region first ..\n";
step_edit_region = add_region (step_edit_insert_position);
RegionView* rv = view()->find_view (step_edit_region);
if (rv) {
step_edit_region_view = dynamic_cast<MidiRegionView*>(rv);
} else {
fatal << X_("programming error: no view found for new MIDI region") << endmsg;
/*NOTREACHED*/
}
}
if (step_edit_region_view) {
bool success;
Evoral::MusicalTime beats = _editor.get_grid_type_as_beats (success, step_edit_insert_position);
if (!success) {
continue;
}
cerr << "will add note at " << step_edit_beat_pos << endl;
step_edit_region_view->add_note (buf[0] & 0xf, buf[1], buf[2], step_edit_beat_pos, beats);
step_edit_beat_pos += beats;
}
}
}
return true; /* keep checking */
}
boost::shared_ptr<Region>
MidiTimeAxisView::add_region (nframes64_t pos)
{
Editor* real_editor = dynamic_cast<Editor*> (&_editor);
real_editor->begin_reversible_command (_("create region"));
XMLNode &before = playlist()->get_state();
nframes64_t start = pos;
real_editor->snap_to (start, -1);
const Meter& m = _session.tempo_map().meter_at(start);
const Tempo& t = _session.tempo_map().tempo_at(start);
double length = floor (m.frames_per_bar(t, _session.frame_rate()));
const boost::shared_ptr<MidiDiskstream> diskstream =
boost::dynamic_pointer_cast<MidiDiskstream>(view()->trackview().track()->diskstream());
boost::shared_ptr<Source> src = _session.create_midi_source_for_session (*diskstream.get());
boost::shared_ptr<Region> region = (RegionFactory::create (src, 0, (nframes_t) length,
PBD::basename_nosuffix(src->name())));
playlist()->add_region (region, start);
XMLNode &after = playlist()->get_state();
_session.add_command (new MementoCommand<Playlist> (*playlist().get(), &before, &after));
real_editor->commit_reversible_command();
return region;
}

View File

@ -67,6 +67,8 @@ class MidiTimeAxisView : public RouteTimeAxisView
void set_height (uint32_t);
void hide ();
boost::shared_ptr<ARDOUR::Region> add_region (nframes64_t pos);
void show_all_automation ();
void show_existing_automation ();
void add_cc_track ();
@ -102,6 +104,7 @@ class MidiTimeAxisView : public RouteTimeAxisView
void set_note_range(MidiStreamView::VisibleNoteRange range);
void route_active_changed ();
void build_rec_context_menu ();
void add_insert_to_subplugin_menu (ARDOUR::Processor *);
@ -120,6 +123,19 @@ class MidiTimeAxisView : public RouteTimeAxisView
MidiMultipleChannelSelector _channel_selector;
Gtk::ComboBoxText _model_selector;
Gtk::ComboBoxText _custom_device_mode_selector;
Gtk::CheckMenuItem* _step_edit_item;
sigc::connection step_edit_connection;
nframes64_t step_edit_insert_position;
Evoral::MusicalTime step_edit_beat_pos;
boost::shared_ptr<ARDOUR::Region> step_edit_region;
MidiRegionView* step_edit_region_view;
void toggle_step_editing ();
void start_step_editing ();
void stop_step_editing ();
bool check_step_edit ();
};
#endif /* __ardour_midi_time_axis_h__ */

View File

@ -184,7 +184,7 @@ RouteTimeAxisView::RouteTimeAxisView (PublicEditor& ed, Session& sess, boost::sh
rec_enable_button->show_all ();
rec_enable_button->signal_button_press_event().connect (mem_fun(*this, &RouteUI::rec_enable_press), false);
rec_enable_button->signal_button_release_event().connect (mem_fun(*this, &RouteUI::rec_enable_release));
rec_enable_button->signal_button_release_event().connect (mem_fun(*this, &RouteUI::rec_enable_release), false);
controls_table.attach (*rec_enable_button, 5, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
ARDOUR_UI::instance()->tooltips().set_tip(*rec_enable_button, _("Record"));

View File

@ -95,6 +95,7 @@ RouteUI::init ()
mute_menu = 0;
solo_menu = 0;
sends_menu = 0;
rec_context_menu = 0;
ignore_toggle = false;
wait_for_release = false;
route_active_menu_item = 0;
@ -498,6 +499,10 @@ RouteUI::rec_enable_press(GdkEventButton* ev)
set_route_group_rec_enable (_route, !_route->record_enabled());
} else if (Keyboard::is_context_menu_event (ev)) {
/* do this on release */
} else {
reversibly_apply_track_boolean ("rec-enable change", &Track::set_record_enable, !track()->record_enabled(), this);
check_rec_enable_sensitivity ();
@ -507,9 +512,31 @@ RouteUI::rec_enable_press(GdkEventButton* ev)
return true;
}
bool
RouteUI::rec_enable_release (GdkEventButton*)
void
RouteUI::show_rec_context_menu ()
{
if (!rec_context_menu) {
cerr << "build menu\n";
build_rec_context_menu ();
}
if (rec_context_menu) {
/* only do this if build_rec_context_menu() actually did something */
cerr << "show menu\n";
rec_context_menu->popup (1, gtk_get_current_event_time());
}
}
bool
RouteUI::rec_enable_release (GdkEventButton* ev)
{
cerr << "release\n";
if (Keyboard::is_context_menu_event(ev)) {
cerr << "context\n";
show_rec_context_menu ();
}
return true;
}

View File

@ -98,6 +98,10 @@ class RouteUI : public virtual AxisView
Gtk::Menu* mute_menu;
Gtk::Menu* solo_menu;
Gtk::Menu* sends_menu;
Gtk::Menu* rec_context_menu;
virtual void build_rec_context_menu () { }
void show_rec_context_menu ();
XMLNode *xml_node;
void ensure_xml_node ();

View File

@ -77,6 +77,10 @@ public:
NoteMode note_mode() const { return _note_mode; }
void set_note_mode (NoteMode m);
bool step_editing() const { return _step_editing; }
void set_step_editing (bool yn);
MidiRingBuffer<nframes_t>& step_edit_ring_buffer() { return _step_edit_ring_buffer; }
protected:
XMLNode& state (bool full);
@ -93,7 +97,13 @@ private:
void set_state_part_three ();
MidiRingBuffer<nframes_t> _immediate_events;
MidiRingBuffer<nframes_t> _step_edit_ring_buffer;
NoteMode _note_mode;
bool _step_editing;
int no_roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame,
bool state_changing, bool can_record, bool rec_monitors_input);
void push_midi_input_to_step_edit_ringbuffer (nframes_t nframes);
};
} /* namespace ARDOUR*/

View File

@ -179,6 +179,8 @@ class TempoMap : public PBD::StatefulDestructible
nframes_t frame_time (const BBT_Time&) const;
nframes_t bbt_duration_at (nframes_t, const BBT_Time&, int dir) const;
void bbt_time_add (nframes64_t origin, BBT_Time& start, const BBT_Time& shift);
static const Tempo& default_tempo() { return _default_tempo; }
static const Meter& default_meter() { return _default_meter; }

View File

@ -45,8 +45,8 @@ class Track : public Route
virtual bool can_use_mode (TrackMode /*m*/, bool& /*bounce_required*/) { return false; }
sigc::signal<void> TrackModeChanged;
int no_roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame,
bool state_changing, bool can_record, bool rec_monitors_input);
virtual int no_roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame,
bool state_changing, bool can_record, bool rec_monitors_input);
int silent_roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame,
bool can_record, bool rec_monitors_input);

View File

@ -36,6 +36,7 @@
#include "ardour/midi_source.h"
#include "ardour/midi_track.h"
#include "ardour/panner.h"
#include "ardour/port.h"
#include "ardour/processor.h"
#include "ardour/route_group_specialized.h"
#include "ardour/session.h"
@ -50,7 +51,9 @@ using namespace PBD;
MidiTrack::MidiTrack (Session& sess, string name, Route::Flag flag, TrackMode mode)
: Track (sess, name, flag, mode, DataType::MIDI)
, _immediate_events(1024) // FIXME: size?
, _step_edit_ring_buffer(64) // FIXME: size?
, _note_mode(Sustained)
, _step_editing (false)
{
use_new_diskstream ();
@ -63,7 +66,9 @@ MidiTrack::MidiTrack (Session& sess, string name, Route::Flag flag, TrackMode mo
MidiTrack::MidiTrack (Session& sess, const XMLNode& node)
: Track (sess, node, DataType::MIDI )
, _immediate_events(1024) // FIXME: size?
, _step_edit_ring_buffer(64) // FIXME: size?
, _note_mode(Sustained)
, _step_editing (false)
{
_set_state(node, false);
}
@ -276,6 +281,9 @@ MidiTrack::state(bool full_state)
root.add_child_nocopy (_rec_enable_control->get_state());
root.add_property ("step-editing", (_step_editing ? "yes" : "no"));
root.add_property ("note-mode", enum_2_string (_note_mode));
return root;
}
@ -449,6 +457,41 @@ MidiTrack::roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame,
return 0;
}
int
MidiTrack::no_roll (nframes_t nframes, sframes_t start_frame, sframes_t end_frame,
bool state_changing, bool can_record, bool rec_monitors_input)
{
int ret = Track::no_roll (nframes, start_frame, end_frame, state_changing, can_record, rec_monitors_input);
if (ret == 0 && _step_editing) {
push_midi_input_to_step_edit_ringbuffer (nframes);
}
return ret;
}
void
MidiTrack::push_midi_input_to_step_edit_ringbuffer (nframes_t nframes)
{
PortSet& ports (_input->ports());
for (PortSet::iterator p = ports.begin(DataType::MIDI); p != ports.end(DataType::MIDI); ++p) {
Buffer& b (p->get_buffer (nframes));
const MidiBuffer* const mb = dynamic_cast<MidiBuffer*>(&b);
assert (mb);
for (MidiBuffer::const_iterator e = mb->begin(); e != mb->end(); ++e) {
const Evoral::MIDIEvent<nframes_t> ev(*e, false);
/* we don't care about the time for this purpose */
_step_edit_ring_buffer.write (0, ev.type(), ev.size(), ev.buffer());
}
}
}
void
MidiTrack::write_out_of_band_data (BufferSet& bufs, sframes_t /*start*/, sframes_t /*end*/, nframes_t nframes)
{
@ -594,3 +637,8 @@ MidiTrack::MidiControl::set_value(float val)
AutomationControl::set_value(val);
}
void
MidiTrack::set_step_editing (bool yn)
{
_step_editing = yn;
}