Selection of visible note range (full range vs fit contents, selectable from midi track menu).

Added note pencil tool, mock note adding (notes can be added visually but don't yet play).
Reworked MidiModel to be notes w/ duration instead of realtime style MIDI events.
Moved layering (stacked/overlaid) from auto time axis down to route time axis since it applies
to MIDI tracks as well.


git-svn-id: svn://localhost/ardour2/trunk@2128 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard 2007-07-15 01:56:11 +00:00
parent 74eded425a
commit 332a3d9813
25 changed files with 432 additions and 98 deletions

View file

@ -206,17 +206,6 @@ AudioTimeAxisView::append_extra_display_menu_items ()
items.push_back (MenuElem (_("Waveform"), *waveform_menu)); items.push_back (MenuElem (_("Waveform"), *waveform_menu));
Menu *layers_menu = manage(new Menu);
MenuList &layers_items = layers_menu->items();
layers_menu->set_name("ArdourContextMenu");
RadioMenuItem::Group layers_group;
layers_items.push_back(RadioMenuElem (layers_group, _("Overlaid"), bind (mem_fun (*this, &AudioTimeAxisView::set_layer_display), Overlaid)));
layers_items.push_back(RadioMenuElem (layers_group, _("Stacked"), bind (mem_fun (*this, &AudioTimeAxisView::set_layer_display), Stacked)));
items.push_back (MenuElem (_("Layers"), *layers_menu));
} }
Gtk::Menu* Gtk::Menu*
@ -487,11 +476,3 @@ AudioTimeAxisView::update_control_names ()
} }
} }
void
AudioTimeAxisView::set_layer_display (LayerDisplay d)
{
AudioStreamView* asv = audio_view ();
if (asv) {
asv->set_layer_display (d);
}
}

View file

@ -76,7 +76,6 @@ class AudioTimeAxisView : public RouteTimeAxisView
void hide_all_xfades (); void hide_all_xfades ();
void hide_dependent_views (TimeAxisViewItem&); void hide_dependent_views (TimeAxisViewItem&);
void reveal_dependent_views (TimeAxisViewItem&); void reveal_dependent_views (TimeAxisViewItem&);
void set_layer_display (LayerDisplay d);
/* Overridden from parent to store display state */ /* Overridden from parent to store display state */
guint32 show_at (double y, int& nth, Gtk::VBox *parent); guint32 show_at (double y, int& nth, Gtk::VBox *parent);

View file

@ -60,6 +60,7 @@ MOUSEMODE(MouseRange)
MOUSEMODE(MouseTimeFX) MOUSEMODE(MouseTimeFX)
MOUSEMODE(MouseZoom) MOUSEMODE(MouseZoom)
MOUSEMODE(MouseAudition) MOUSEMODE(MouseAudition)
MOUSEMODE(MouseNote)
/* Changing this order will break the menu */ /* Changing this order will break the menu */
ZOOMFOCUS(ZoomFocusLeft) ZOOMFOCUS(ZoomFocusLeft)

View file

@ -152,6 +152,7 @@ Gdk::Cursor* Editor::zoom_cursor = 0;
Gdk::Cursor* Editor::time_fx_cursor = 0; Gdk::Cursor* Editor::time_fx_cursor = 0;
Gdk::Cursor* Editor::fader_cursor = 0; Gdk::Cursor* Editor::fader_cursor = 0;
Gdk::Cursor* Editor::speaker_cursor = 0; Gdk::Cursor* Editor::speaker_cursor = 0;
Gdk::Cursor* Editor::note_cursor = 0;
Gdk::Cursor* Editor::wait_cursor = 0; Gdk::Cursor* Editor::wait_cursor = 0;
Gdk::Cursor* Editor::timebar_cursor = 0; Gdk::Cursor* Editor::timebar_cursor = 0;
@ -1218,6 +1219,7 @@ Editor::build_cursors ()
time_fx_cursor = new Gdk::Cursor (SIZING); time_fx_cursor = new Gdk::Cursor (SIZING);
wait_cursor = new Gdk::Cursor (WATCH); wait_cursor = new Gdk::Cursor (WATCH);
timebar_cursor = new Gdk::Cursor(LEFT_PTR); timebar_cursor = new Gdk::Cursor(LEFT_PTR);
note_cursor = new Gdk::Cursor (PENCIL);
} }
/** Pop up a context menu for when the user clicks on a fade in or fade out */ /** Pop up a context menu for when the user clicks on a fade in or fade out */
@ -2322,6 +2324,9 @@ Editor::setup_toolbar ()
mouse_mode_buttons.push_back (&mouse_timefx_button); mouse_mode_buttons.push_back (&mouse_timefx_button);
mouse_audition_button.add (*(manage (new Image (::get_icon("tool_audition"))))); mouse_audition_button.add (*(manage (new Image (::get_icon("tool_audition")))));
mouse_audition_button.set_relief(Gtk::RELIEF_NONE); mouse_audition_button.set_relief(Gtk::RELIEF_NONE);
mouse_note_button.add (*(manage (new Image (::get_icon("tool_note")))));
mouse_note_button.set_relief(Gtk::RELIEF_NONE);
mouse_mode_buttons.push_back (&mouse_note_button);
mouse_mode_buttons.push_back (&mouse_audition_button); mouse_mode_buttons.push_back (&mouse_audition_button);
mouse_mode_button_set = new GroupedButtons (mouse_mode_buttons); mouse_mode_button_set = new GroupedButtons (mouse_mode_buttons);
@ -2336,6 +2341,7 @@ Editor::setup_toolbar ()
mouse_mode_button_box.pack_start(mouse_gain_button, true, true); mouse_mode_button_box.pack_start(mouse_gain_button, true, true);
mouse_mode_button_box.pack_start(mouse_timefx_button, true, true); mouse_mode_button_box.pack_start(mouse_timefx_button, true, true);
mouse_mode_button_box.pack_start(mouse_audition_button, true, true); mouse_mode_button_box.pack_start(mouse_audition_button, true, true);
mouse_mode_button_box.pack_start(mouse_note_button, true, true);
mouse_mode_button_box.set_homogeneous(true); mouse_mode_button_box.set_homogeneous(true);
vector<string> edit_mode_strings; vector<string> edit_mode_strings;
@ -2368,6 +2374,7 @@ Editor::setup_toolbar ()
mouse_zoom_button.set_name ("MouseModeButton"); mouse_zoom_button.set_name ("MouseModeButton");
mouse_timefx_button.set_name ("MouseModeButton"); mouse_timefx_button.set_name ("MouseModeButton");
mouse_audition_button.set_name ("MouseModeButton"); mouse_audition_button.set_name ("MouseModeButton");
mouse_note_button.set_name ("MouseModeButton");
ARDOUR_UI::instance()->tooltips().set_tip (mouse_move_button, _("Select/Move Objects")); ARDOUR_UI::instance()->tooltips().set_tip (mouse_move_button, _("Select/Move Objects"));
ARDOUR_UI::instance()->tooltips().set_tip (mouse_select_button, _("Select/Move Ranges")); ARDOUR_UI::instance()->tooltips().set_tip (mouse_select_button, _("Select/Move Ranges"));
@ -2375,6 +2382,7 @@ Editor::setup_toolbar ()
ARDOUR_UI::instance()->tooltips().set_tip (mouse_zoom_button, _("Select Zoom Range")); ARDOUR_UI::instance()->tooltips().set_tip (mouse_zoom_button, _("Select Zoom Range"));
ARDOUR_UI::instance()->tooltips().set_tip (mouse_timefx_button, _("Stretch/Shrink Regions")); ARDOUR_UI::instance()->tooltips().set_tip (mouse_timefx_button, _("Stretch/Shrink Regions"));
ARDOUR_UI::instance()->tooltips().set_tip (mouse_audition_button, _("Listen to Specific Regions")); ARDOUR_UI::instance()->tooltips().set_tip (mouse_audition_button, _("Listen to Specific Regions"));
ARDOUR_UI::instance()->tooltips().set_tip (mouse_note_button, _("Edit MIDI Notes"));
mouse_move_button.unset_flags (CAN_FOCUS); mouse_move_button.unset_flags (CAN_FOCUS);
mouse_select_button.unset_flags (CAN_FOCUS); mouse_select_button.unset_flags (CAN_FOCUS);
@ -2382,6 +2390,7 @@ Editor::setup_toolbar ()
mouse_zoom_button.unset_flags (CAN_FOCUS); mouse_zoom_button.unset_flags (CAN_FOCUS);
mouse_timefx_button.unset_flags (CAN_FOCUS); mouse_timefx_button.unset_flags (CAN_FOCUS);
mouse_audition_button.unset_flags (CAN_FOCUS); mouse_audition_button.unset_flags (CAN_FOCUS);
mouse_note_button.unset_flags (CAN_FOCUS);
mouse_select_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseRange)); mouse_select_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseRange));
mouse_select_button.signal_button_release_event().connect (mem_fun(*this, &Editor::mouse_select_button_release)); mouse_select_button.signal_button_release_event().connect (mem_fun(*this, &Editor::mouse_select_button_release));
@ -2391,6 +2400,7 @@ Editor::setup_toolbar ()
mouse_zoom_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseZoom)); mouse_zoom_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseZoom));
mouse_timefx_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseTimeFX)); mouse_timefx_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseTimeFX));
mouse_audition_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseAudition)); mouse_audition_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseAudition));
mouse_note_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseNote));
// mouse_move_button.set_active (true); // mouse_move_button.set_active (true);

View file

@ -808,6 +808,7 @@ class Editor : public PublicEditor
static Gdk::Cursor* time_fx_cursor; static Gdk::Cursor* time_fx_cursor;
static Gdk::Cursor* fader_cursor; static Gdk::Cursor* fader_cursor;
static Gdk::Cursor* speaker_cursor; static Gdk::Cursor* speaker_cursor;
static Gdk::Cursor* note_cursor;
static Gdk::Cursor* wait_cursor; static Gdk::Cursor* wait_cursor;
static Gdk::Cursor* timebar_cursor; static Gdk::Cursor* timebar_cursor;
@ -1317,6 +1318,7 @@ class Editor : public PublicEditor
Gtk::ToggleButton mouse_zoom_button; Gtk::ToggleButton mouse_zoom_button;
Gtk::ToggleButton mouse_timefx_button; Gtk::ToggleButton mouse_timefx_button;
Gtk::ToggleButton mouse_audition_button; Gtk::ToggleButton mouse_audition_button;
Gtk::ToggleButton mouse_note_button;
GroupedButtons *mouse_mode_button_set; GroupedButtons *mouse_mode_button_set;
void mouse_mode_toggled (Editing::MouseMode m); void mouse_mode_toggled (Editing::MouseMode m);
bool ignore_mouse_mode_toggle; bool ignore_mouse_mode_toggle;

View file

@ -301,6 +301,7 @@ Editor::register_actions ()
ActionManager::register_radio_action (mouse_mode_actions, mouse_mode_group, "set-mouse-mode-gain", _("Gain Tool"), bind (mem_fun(*this, &Editor::set_mouse_mode), Editing::MouseGain, false)); ActionManager::register_radio_action (mouse_mode_actions, mouse_mode_group, "set-mouse-mode-gain", _("Gain Tool"), bind (mem_fun(*this, &Editor::set_mouse_mode), Editing::MouseGain, false));
ActionManager::register_radio_action (mouse_mode_actions, mouse_mode_group, "set-mouse-mode-zoom", _("Zoom Tool"), bind (mem_fun(*this, &Editor::set_mouse_mode), Editing::MouseZoom, false)); ActionManager::register_radio_action (mouse_mode_actions, mouse_mode_group, "set-mouse-mode-zoom", _("Zoom Tool"), bind (mem_fun(*this, &Editor::set_mouse_mode), Editing::MouseZoom, false));
ActionManager::register_radio_action (mouse_mode_actions, mouse_mode_group, "set-mouse-mode-timefx", _("Timefx Tool"), bind (mem_fun(*this, &Editor::set_mouse_mode), Editing::MouseTimeFX, false)); ActionManager::register_radio_action (mouse_mode_actions, mouse_mode_group, "set-mouse-mode-timefx", _("Timefx Tool"), bind (mem_fun(*this, &Editor::set_mouse_mode), Editing::MouseTimeFX, false));
ActionManager::register_radio_action (mouse_mode_actions, mouse_mode_group, "set-mouse-mode-note", _("Note Tool"), bind (mem_fun(*this, &Editor::set_mouse_mode), Editing::MouseNote, false));
ActionManager::register_action (editor_actions, X_("SnapTo"), _("Snap To")); ActionManager::register_action (editor_actions, X_("SnapTo"), _("Snap To"));
ActionManager::register_action (editor_actions, X_("SnapMode"), _("Snap Mode")); ActionManager::register_action (editor_actions, X_("SnapMode"), _("Snap Mode"));

View file

@ -161,6 +161,12 @@ Editor::mouse_mode_toggled (MouseMode m)
} }
break; break;
case MouseNote:
if (mouse_note_button.get_active()) {
set_mouse_mode (m);
}
break;
default: default:
break; break;
} }
@ -244,6 +250,11 @@ Editor::set_mouse_mode (MouseMode m, bool force)
mouse_audition_button.set_active (true); mouse_audition_button.set_active (true);
current_canvas_cursor = speaker_cursor; current_canvas_cursor = speaker_cursor;
break; break;
case MouseNote:
mouse_note_button.set_active (true);
current_canvas_cursor = note_cursor;
break;
} }
ignore_mouse_mode_toggle = false; ignore_mouse_mode_toggle = false;
@ -286,6 +297,11 @@ Editor::step_mouse_mode (bool next)
if (next) set_mouse_mode (MouseObject); if (next) set_mouse_mode (MouseObject);
else set_mouse_mode (MouseTimeFX); else set_mouse_mode (MouseTimeFX);
break; break;
case MouseNote:
if (next) set_mouse_mode (MouseObject);
else set_mouse_mode (MouseAudition);
break;
} }
} }

View file

@ -25,10 +25,12 @@
#include <gtkmm2ext/gtk_ui.h> #include <gtkmm2ext/gtk_ui.h>
#include <ardour/playlist.h> #include <ardour/playlist.h>
#include <ardour/tempo.h>
#include <ardour/midi_region.h> #include <ardour/midi_region.h>
#include <ardour/midi_source.h> #include <ardour/midi_source.h>
#include <ardour/midi_diskstream.h> #include <ardour/midi_diskstream.h>
#include <ardour/midi_events.h> #include <ardour/midi_events.h>
#include <ardour/midi_model.h>
#include "streamview.h" #include "streamview.h"
#include "midi_region_view.h" #include "midi_region_view.h"
@ -93,6 +95,52 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
midi_region()->midi_source(0)->load_model(); midi_region()->midi_source(0)->load_model();
display_events(); display_events();
} }
group->signal_event().connect (mem_fun (this, &MidiRegionView::canvas_event));
}
bool
MidiRegionView::canvas_event(GdkEvent* ev)
{
if (trackview.editor.current_mouse_mode() == MouseNote) {
if (ev->type == GDK_BUTTON_PRESS) {
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MidiStreamView* const view = mtv->midi_view();
const uint8_t note_range = view->highest_note() - view->lowest_note() + 1;
const double footer_height = name_highlight->property_y2() - name_highlight->property_y1();
const double roll_height = trackview.height - footer_height;
double x = ev->button.x;
double y = ev->button.y;
get_canvas_group()->w2i(x, y);
double note = floor((roll_height - y) / roll_height * (double)note_range) + view->lowest_note();
assert(note >= 0.0);
assert(note <= 127.0);
const nframes_t stamp = trackview.editor.pixel_to_frame (x);
assert(stamp >= 0);
//assert(stamp <= _region->length());
const Meter& m = trackview.session().tempo_map().meter_at(stamp);
const Tempo& t = trackview.session().tempo_map().tempo_at(stamp);
double dur = m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar();
// Add a 1 beat long note (for now)
const MidiModel::Note new_note(stamp, dur, (uint8_t)note, 0x40);
MidiModel::Notes& notes = midi_region()->midi_source(0)->model()->notes();
MidiModel::Notes::iterator i = upper_bound(notes.begin(), notes.end(), new_note,
MidiModel::NoteTimeComparator());
notes.insert(i, new_note);
view->update_bounds(new_note.note);
add_note(new_note);
}
}
return false;
} }
@ -113,8 +161,8 @@ MidiRegionView::display_events()
begin_write(); begin_write();
for (size_t i=0; i < midi_region()->midi_source(0)->model()->n_events(); ++i) for (size_t i=0; i < midi_region()->midi_source(0)->model()->n_notes(); ++i)
add_event(midi_region()->midi_source(0)->model()->event_at(i)); add_note(midi_region()->midi_source(0)->model()->note_at(i));
end_write(); end_write();
} }
@ -222,6 +270,11 @@ MidiRegionView::end_write()
} }
/** Add a MIDI event.
*
* This is used while recording, and handles displaying still-unresolved notes.
* Displaying an existing model is simpler, and done with add_note.
*/
void void
MidiRegionView::add_event (const MidiEvent& ev) MidiRegionView::add_event (const MidiEvent& ev)
{ {
@ -300,4 +353,61 @@ MidiRegionView::extend_active_notes()
} }
/** Add a MIDI note (with duration).
*
* This does no 'realtime' note resolution, notes from a MidiModel have a
* duration so they can be drawn in full immediately.
*/
void
MidiRegionView::add_note (const MidiModel::Note& note)
{
assert(note.start >= 0);
assert(note.start < _region->length());
//assert(note.start + note.duration < _region->length());
/*printf("Event, time = %f, size = %zu, data = ", ev.time, ev.size);
for (size_t i=0; i < ev.size; ++i) {
printf("%X ", ev.buffer[i]);
}
printf("\n\n");*/
MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MidiStreamView* const view = mtv->midi_view();
ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
const uint8_t note_range = view->highest_note() - view->lowest_note() + 1;
const double footer_height = name_highlight->property_y2() - name_highlight->property_y1();
const double pixel_range = (trackview.height - footer_height - 5.0) / (double)note_range;
if (mtv->note_mode() == Note) {
const double y1 = trackview.height - (pixel_range * (note.note - view->lowest_note() + 1))
- footer_height - 3.0;
ArdourCanvas::SimpleRect * ev_rect = new Gnome::Canvas::SimpleRect(*group);
ev_rect->property_x1() = trackview.editor.frame_to_pixel((nframes_t)note.start);
ev_rect->property_y1() = y1;
ev_rect->property_x2() = trackview.editor.frame_to_pixel((nframes_t)(note.start + note.duration));
ev_rect->property_y2() = y1 + ceil(pixel_range);
ev_rect->property_fill_color_rgba() = 0xFFFFFF66;
ev_rect->property_outline_color_rgba() = 0xFFFFFFAA;
ev_rect->property_outline_what() = (guint32) 0xF; // all edges
ev_rect->show();
_events.push_back(ev_rect);
} else if (mtv->note_mode() == Percussion) {
const double x = trackview.editor.frame_to_pixel((nframes_t)note.start);
const double y = trackview.height - (pixel_range * (note.note - view->lowest_note() + 1))
- footer_height - 3.0;
Diamond* ev_diamond = new Diamond(*group, std::min(pixel_range, 5.0));
ev_diamond->move(x, y);
ev_diamond->show();
ev_diamond->property_outline_color_rgba() = 0xFFFFFFDD;
ev_diamond->property_fill_color_rgba() = 0xFFFFFF66;
_events.push_back(ev_diamond);
}
}

View file

@ -25,6 +25,7 @@
#include <libgnomecanvasmm/polygon.h> #include <libgnomecanvasmm/polygon.h>
#include <sigc++/signal.h> #include <sigc++/signal.h>
#include <ardour/midi_region.h> #include <ardour/midi_region.h>
#include <ardour/midi_model.h>
#include <ardour/types.h> #include <ardour/types.h>
#include "region_view.h" #include "region_view.h"
@ -64,6 +65,7 @@ class MidiRegionView : public RegionView
GhostRegion* add_ghost (AutomationTimeAxisView&); GhostRegion* add_ghost (AutomationTimeAxisView&);
void add_event(const ARDOUR::MidiEvent& ev); void add_event(const ARDOUR::MidiEvent& ev);
void add_note(const ARDOUR::MidiModel::Note& note);
void begin_write(); void begin_write();
void end_write(); void end_write();
@ -95,6 +97,8 @@ class MidiRegionView : public RegionView
void display_events(); void display_events();
void clear_events(); void clear_events();
bool canvas_event(GdkEvent* ev);
std::vector<ArdourCanvas::Item*> _events; std::vector<ArdourCanvas::Item*> _events;
ArdourCanvas::SimpleRect** _active_notes; ArdourCanvas::SimpleRect** _active_notes;
}; };

View file

@ -54,8 +54,8 @@ using namespace Editing;
MidiStreamView::MidiStreamView (MidiTimeAxisView& tv) MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
: StreamView (tv) : StreamView (tv)
, _lowest_note(0) , _lowest_note(60)
, _highest_note(127) , _highest_note(60)
{ {
if (tv.is_track()) if (tv.is_track())
stream_base_color = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get(); stream_base_color = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
@ -137,17 +137,13 @@ MidiStreamView::display_region(MidiRegionView* region_view, bool redisplay_event
boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0)); boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
for (size_t i=0; i < source->model()->n_events(); ++i) { for (size_t i=0; i < source->model()->n_notes(); ++i) {
const MidiEvent& ev = source->model()->event_at(i); const MidiModel::Note& note = source->model()->note_at(i);
// Look at all note on events to find our note range update_bounds(note.note);
if ((ev.buffer[0] & 0xF0) == MIDI_CMD_NOTE_ON) {
_lowest_note = min(_lowest_note, ev.buffer[1]);
_highest_note = max(_highest_note, ev.buffer[1]);
}
if (redisplay_events) if (redisplay_events)
region_view->add_event(ev); region_view->add_note(note);
} }
if (redisplay_events) if (redisplay_events)
@ -164,8 +160,8 @@ MidiStreamView::redisplay_diskstream ()
(*i)->set_valid (false); (*i)->set_valid (false);
} }
_lowest_note = 60; // middle C //_lowest_note = 60; // middle C
_highest_note = _lowest_note + 11; //_highest_note = _lowest_note + 11;
if (_trackview.is_midi_track()) { if (_trackview.is_midi_track()) {
_trackview.get_diskstream()->playlist()->foreach_region (static_cast<StreamView*>(this), &StreamView::add_region_view); _trackview.get_diskstream()->playlist()->foreach_region (static_cast<StreamView*>(this), &StreamView::add_region_view);
@ -192,6 +188,13 @@ MidiStreamView::redisplay_diskstream ()
} }
} }
void
MidiStreamView::update_bounds(uint8_t note_num)
{
_lowest_note = min(_lowest_note, note_num);
_highest_note = max(_highest_note, note_num);
}
void void
MidiStreamView::setup_rec_box () MidiStreamView::setup_rec_box ()

View file

@ -58,8 +58,18 @@ class MidiStreamView : public StreamView
void get_selectables (jack_nframes_t start, jack_nframes_t end, list<Selectable* >&); void get_selectables (jack_nframes_t start, jack_nframes_t end, list<Selectable* >&);
void get_inverted_selectables (Selection&, list<Selectable* >& results); void get_inverted_selectables (Selection&, list<Selectable* >& results);
uint8_t lowest_note() const { return _lowest_note; } enum VisibleNoteRange {
uint8_t highest_note() const { return _highest_note; } FullRange,
ContentsRange
};
VisibleNoteRange note_range() { return _range; }
void set_note_range(VisibleNoteRange r) { _range = r; }
uint8_t lowest_note() const { return (_range == FullRange) ? 0 : _lowest_note; }
uint8_t highest_note() const { return (_range == FullRange) ? 127 : _highest_note; }
void update_bounds(uint8_t note_num);
void redisplay_diskstream (); void redisplay_diskstream ();
@ -73,6 +83,7 @@ class MidiStreamView : public StreamView
void color_handler (); void color_handler ();
VisibleNoteRange _range;
uint8_t _lowest_note; uint8_t _lowest_note;
uint8_t _highest_note; uint8_t _highest_note;
}; };

View file

@ -156,6 +156,31 @@ MidiTimeAxisView::hide ()
TimeAxisView::hide (); TimeAxisView::hide ();
} }
void
MidiTimeAxisView::append_extra_display_menu_items ()
{
using namespace Menu_Helpers;
MenuList& items = display_menu->items();
// Note range
Menu *range_menu = manage(new Menu);
MenuList& range_items = range_menu->items();
range_menu->set_name ("ArdourContextMenu");
RadioMenuItem::Group range_group;
range_items.push_back (RadioMenuElem (range_group, _("Show Full Range"), bind (
mem_fun(*this, &MidiTimeAxisView::set_note_range),
MidiStreamView::FullRange)));
range_items.push_back (RadioMenuElem (range_group, _("Fit Contents"), bind (
mem_fun(*this, &MidiTimeAxisView::set_note_range),
MidiStreamView::ContentsRange)));
items.push_back (MenuElem (_("Note range"), *range_menu));
}
void void
MidiTimeAxisView::build_automation_action_menu () MidiTimeAxisView::build_automation_action_menu ()
{ {
@ -204,6 +229,17 @@ MidiTimeAxisView::set_note_mode(NoteMode mode)
} }
} }
void
MidiTimeAxisView::set_note_range(MidiStreamView::VisibleNoteRange range)
{
//if (midi_view()->note_range() != range) {
midi_view()->set_note_range(range);
midi_view()->redisplay_diskstream();
//}
}
/** Prompt for a controller with a dialog and add an automation track for it /** Prompt for a controller with a dialog and add an automation track for it
*/ */
void void
@ -285,3 +321,4 @@ MidiTimeAxisView::route_active_changed ()
} }
} }

View file

@ -38,6 +38,7 @@
#include "enums.h" #include "enums.h"
#include "route_time_axis.h" #include "route_time_axis.h"
#include "canvas.h" #include "canvas.h"
#include "midi_streamview.h"
namespace ARDOUR { namespace ARDOUR {
class Session; class Session;
@ -70,10 +71,12 @@ class MidiTimeAxisView : public RouteTimeAxisView
private: private:
void append_extra_display_menu_items ();
void build_automation_action_menu (); void build_automation_action_menu ();
Gtk::Menu* build_mode_menu(); Gtk::Menu* build_mode_menu();
void set_note_mode(ARDOUR::NoteMode mode); void set_note_mode(ARDOUR::NoteMode mode);
void set_note_range(MidiStreamView::VisibleNoteRange range);
void route_active_changed (); void route_active_changed ();

View file

@ -500,6 +500,19 @@ RouteTimeAxisView::build_display_menu ()
if (is_track()) { if (is_track()) {
Menu *layers_menu = manage(new Menu);
MenuList &layers_items = layers_menu->items();
layers_menu->set_name("ArdourContextMenu");
RadioMenuItem::Group layers_group;
layers_items.push_back(RadioMenuElem (layers_group, _("Overlaid"),
bind (mem_fun (*this, &RouteTimeAxisView::set_layer_display), Overlaid)));
layers_items.push_back(RadioMenuElem (layers_group, _("Stacked"),
bind (mem_fun (*this, &RouteTimeAxisView::set_layer_display), Stacked)));
items.push_back (MenuElem (_("Layers"), *layers_menu));
Menu* alignment_menu = manage (new Menu); Menu* alignment_menu = manage (new Menu);
MenuList& alignment_items = alignment_menu->items(); MenuList& alignment_items = alignment_menu->items();
alignment_menu->set_name ("ArdourContextMenu"); alignment_menu->set_name ("ArdourContextMenu");
@ -1980,3 +1993,8 @@ RouteTimeAxisView::update_rec_display ()
name_entry.set_sensitive (!_route->record_enabled()); name_entry.set_sensitive (!_route->record_enabled());
} }
void
RouteTimeAxisView::set_layer_display (LayerDisplay d)
{
_view->set_layer_display (d);
}

View file

@ -79,6 +79,7 @@ public:
void get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable *>&); void get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable *>&);
void get_inverted_selectables (Selection&, list<Selectable*>&); void get_inverted_selectables (Selection&, list<Selectable*>&);
bool show_automation(ARDOUR::Parameter param); bool show_automation(ARDOUR::Parameter param);
void set_layer_display (LayerDisplay d);
boost::shared_ptr<ARDOUR::Region> find_next_region (nframes_t pos, ARDOUR::RegionPoint, int32_t dir); boost::shared_ptr<ARDOUR::Region> find_next_region (nframes_t pos, ARDOUR::RegionPoint, int32_t dir);

View file

@ -21,6 +21,7 @@
#define __ardour_midi_buffer_h__ #define __ardour_midi_buffer_h__
#include <ardour/buffer.h> #include <ardour/buffer.h>
#include <ardour/midi_event.h>
namespace ARDOUR { namespace ARDOUR {

View file

@ -0,0 +1,49 @@
/*
Copyright (C) 2007 Paul Davis
Author: Dave Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_midi_event_h__
#define __ardour_midi_event_h__
namespace ARDOUR {
/** Identical to jack_midi_event_t, but with double timestamp
*
* time is either a frame time (from/to Jack) or a beat time (internal
* tempo time, used in MidiModel) depending on context.
*/
struct MidiEvent {
MidiEvent(double t=0, size_t s=0, Byte* b=NULL)
: time(t), size(s), buffer(b)
{}
inline uint8_t type() const { return (buffer[0] & 0xF0); }
inline uint8_t note() const { return (buffer[1]); }
inline uint8_t velocity() const { return (buffer[2]); }
double time; /**< Sample index (or beat time) at which event is valid */
size_t size; /**< Number of bytes of data in \a buffer */
Byte* buffer; /**< Raw MIDI data */
};
} // namespace ARDOUR
#endif /* __ardour_midi_event_h__ */

View file

@ -1,5 +1,5 @@
/* /*
Copyright (C) 2006 Paul Davis Copyright (C) 2007 Paul Davis
Author: Dave Robillard Author: Dave Robillard
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
@ -27,14 +27,30 @@
namespace ARDOUR { namespace ARDOUR {
/** A dynamically resizable collection of MIDI events, sorted by time /** This is a slightly higher level (than MidiBuffer) model of MIDI note data.
* Currently it only represents note data, which is represented as complete
* note events (ie with a start time and a duration) rather than separate
* note on and off events (controller data is not here since it's represented
* as an AutomationList)
*/ */
class MidiModel : public boost::noncopyable { class MidiModel : public boost::noncopyable {
public: public:
MidiModel(size_t size=0); struct Note {
~MidiModel(); Note(double s=0, double d=0, uint8_t n=0, uint8_t v=0)
: start(s), duration(d), note(n), velocity(v) {}
void clear() { _events.clear(); } double start;
double duration;
uint8_t note;
uint8_t velocity;
};
MidiModel(size_t size=0);
void clear() { _notes.clear(); }
void start_write();
void end_write(bool delete_stuck=false);
/** Resizes vector if necessary (NOT realtime safe) */ /** Resizes vector if necessary (NOT realtime safe) */
void append(const MidiBuffer& data); void append(const MidiBuffer& data);
@ -42,12 +58,28 @@ public:
/** Resizes vector if necessary (NOT realtime safe) */ /** Resizes vector if necessary (NOT realtime safe) */
void append(double time, size_t size, Byte* in_buffer); void append(double time, size_t size, Byte* in_buffer);
inline const MidiEvent& event_at(unsigned i) const { return _events[i]; } inline const Note& note_at(unsigned i) const { return _notes[i]; }
inline size_t n_events() const { return _events.size(); } inline size_t n_notes() const { return _notes.size(); }
typedef std::vector<Note> Notes;
struct NoteTimeComparator {
inline bool operator() (const Note& a, const Note& b) const {
return a.start < b.start;
}
};
inline Notes& notes() { return _notes; }
inline const Notes& notes() const { return _notes; }
private: private:
std::vector<MidiEvent> _events;
void append_note_on(double time, uint8_t note, uint8_t velocity);
void append_note_off(double time, uint8_t note);
Notes _notes;
Notes _write_notes;
}; };
} /* namespace ARDOUR */ } /* namespace ARDOUR */

View file

@ -69,7 +69,7 @@ class MidiSource : public Source
XMLNode& get_state (); XMLNode& get_state ();
int set_state (const XMLNode&); int set_state (const XMLNode&);
virtual void load_model(bool lock=true) = 0; virtual void load_model(bool lock=true, bool force_reload=false) = 0;
virtual void destroy_model() = 0; virtual void destroy_model() = 0;
MidiModel* model() { return _model; } MidiModel* model() { return _model; }
@ -84,6 +84,7 @@ class MidiSource : public Source
mutable uint32_t _write_data_count; ///< modified in write() mutable uint32_t _write_data_count; ///< modified in write()
MidiModel* _model; MidiModel* _model;
bool _model_loaded;
private: private:
bool file_changed (string path); bool file_changed (string path);

View file

@ -57,8 +57,6 @@ public:
int use_diskstream (string name); int use_diskstream (string name);
int use_diskstream (const PBD::ID& id); int use_diskstream (const PBD::ID& id);
//int set_mode (TrackMode m);
void set_latency_delay (nframes_t); void set_latency_delay (nframes_t);
int export_stuff (BufferSet& bufs, int export_stuff (BufferSet& bufs,

View file

@ -89,7 +89,7 @@ class SMFSource : public MidiSource {
void seek_to(nframes_t time); void seek_to(nframes_t time);
void load_model(bool lock=true); void load_model(bool lock=true, bool force_reload=false);
void destroy_model(); void destroy_model();
uint16_t ppqn() const { return _ppqn; } uint16_t ppqn() const { return _ppqn; }

View file

@ -61,21 +61,6 @@ namespace ARDOUR {
typedef unsigned char Byte; typedef unsigned char Byte;
/** Identical to jack_midi_event_t, but with double timestamp
*
* time is either a frame time (from/to Jack) or a beat time (internal
* tempo time, used in MidiModel) depending on context.
*/
struct MidiEvent {
MidiEvent(double t=0, size_t s=0, Byte* b=NULL)
: time(t), size(s), buffer(b)
{}
double time; /**< Sample index (or beat time) at which event is valid */
size_t size; /**< Number of bytes of data in \a buffer */
Byte* buffer; /**< Raw MIDI data */
};
enum IOChange { enum IOChange {
NoChange = 0, NoChange = 0,
ConfigurationChanged = 0x1, ConfigurationChanged = 0x1,

View file

@ -1,6 +1,6 @@
/* /*
Copyright (C) 2006 Paul Davis Copyright (C) 2007 Paul Davis
Written by Dave Robillard, 2006 Written by Dave Robillard, 2007
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -20,6 +20,7 @@
#include <iostream> #include <iostream>
#include <ardour/midi_model.h> #include <ardour/midi_model.h>
#include <ardour/midi_events.h>
#include <ardour/types.h> #include <ardour/types.h>
using namespace std; using namespace std;
@ -27,14 +28,45 @@ using namespace ARDOUR;
MidiModel::MidiModel(size_t size) MidiModel::MidiModel(size_t size)
: _events(size) : _notes(size)
{ {
} }
MidiModel::~MidiModel()
/** Begin a write of events to the model.
*
* As note on and off events are written, complete notes with duration are
* constructed
*/
void
MidiModel::start_write()
{ {
for (size_t i=0; i < _events.size(); ++i) _write_notes.clear();
delete _events[i].buffer; }
/** Finish a write of events to the model.
*
* If \a delete_stuck is true, note on events that were never resolved with
* a corresonding note off will be deleted. Otherwise they will remain as
* notes with duration 0.
*/
void
MidiModel::end_write(bool delete_stuck)
{
if (delete_stuck) {
_write_notes.clear();
} else {
cerr << "FIXME: Stuck notes lost" << endl;
_write_notes.clear();
/* Merge _write_events into _events */
/*size_t ev_index = 0
size_t write_index = 0;
while ( ! _write_events.empty()) {
// do stuff
}*/
}
} }
@ -50,16 +82,14 @@ void
MidiModel::append(const MidiBuffer& buf) MidiModel::append(const MidiBuffer& buf)
{ {
for (size_t i=0; i < buf.size(); ++i) { for (size_t i=0; i < buf.size(); ++i) {
const MidiEvent& buf_event = buf[i]; const MidiEvent& ev = buf[i];
assert(_events.empty() || buf_event.time >= _events.back().time);
_events.push_back(buf_event); assert(_write_notes.empty() || ev.time >= _write_notes.back().start);
MidiEvent& my_event = _events.back();
assert(my_event.time == buf_event.time);
assert(my_event.size == buf_event.size);
my_event.buffer = new Byte[my_event.size]; if (ev.type() == MIDI_CMD_NOTE_ON)
memcpy(my_event.buffer, buf_event.buffer, my_event.size); append_note_on(ev.time, ev.note(), ev.velocity());
else if (ev.type() == MIDI_CMD_NOTE_OFF)
append_note_off(ev.time, ev.note());
} }
} }
@ -71,14 +101,42 @@ MidiModel::append(const MidiBuffer& buf)
* and MUST be >= the latest event currently in the model. * and MUST be >= the latest event currently in the model.
*/ */
void void
MidiModel::append(double time, size_t size, Byte* in_buffer) MidiModel::append(double time, size_t size, Byte* buf)
{ {
assert(_events.empty() || time >= _events.back().time); assert(_write_notes.empty() || time >= _write_notes.back().start);
//cerr << "Model event: time = " << time << endl; if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON)
append_note_on(time, buf[1], buf[2]);
Byte* my_buffer = new Byte[size]; else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF)
memcpy(my_buffer, in_buffer, size); append_note_off(time, buf[1]);
_events.push_back(MidiEvent(time, size, my_buffer)); }
void
MidiModel::append_note_on(double time, uint8_t note, uint8_t velocity)
{
_write_notes.push_back(Note(time, 0, note, velocity));
}
void
MidiModel::append_note_off(double time, uint8_t note_num)
{
/* _write_notes (active notes) is presumably small enough for linear
* search to be a good idea. maybe not with instruments (percussion)
* that don't send note off at all though.... FIXME? */
/* FIXME: note off velocity for that one guy out there who actually has
* keys that send it */
for (size_t i=0; i < _write_notes.size(); ++i) {
Note& note = _write_notes[i];
if (note.note == note_num) {
assert(time > note.start);
note.duration = time - note.start;
cerr << "MidiModel resolved note, duration: " << note.duration << endl;
break;
}
}
} }

View file

@ -45,6 +45,7 @@ sigc::signal<void,MidiSource *> MidiSource::MidiSourceCreated;
MidiSource::MidiSource (Session& s, string name) MidiSource::MidiSource (Session& s, string name)
: Source (s, name, DataType::MIDI) : Source (s, name, DataType::MIDI)
, _model(new MidiModel()) , _model(new MidiModel())
, _model_loaded (false)
{ {
_read_data_count = 0; _read_data_count = 0;
_write_data_count = 0; _write_data_count = 0;
@ -53,6 +54,7 @@ MidiSource::MidiSource (Session& s, string name)
MidiSource::MidiSource (Session& s, const XMLNode& node) MidiSource::MidiSource (Session& s, const XMLNode& node)
: Source (s, node) : Source (s, node)
, _model(new MidiModel()) , _model(new MidiModel())
, _model_loaded (false)
{ {
_read_data_count = 0; _read_data_count = 0;
_write_data_count = 0; _write_data_count = 0;

View file

@ -776,14 +776,21 @@ SMFSource::read_var_len() const
} }
void void
SMFSource::load_model(bool lock) SMFSource::load_model(bool lock, bool force_reload)
{ {
if (lock) if (lock)
Glib::Mutex::Lock lm (_lock); Glib::Mutex::Lock lm (_lock);
destroy_model(); if (_model && _model_loaded && ! force_reload) {
assert(_model);
return;
}
if (! _model)
_model = new MidiModel(); _model = new MidiModel();
_model->start_write();
fseek(_fd, _header_size, 0); fseek(_fd, _header_size, 0);
uint64_t time = 0; /* in SMF ticks */ uint64_t time = 0; /* in SMF ticks */
@ -807,6 +814,10 @@ SMFSource::load_model(bool lock)
_model->append(ev_time, ev.size, ev.buffer); _model->append(ev_time, ev.size, ev.buffer);
} }
} }
_model->end_write(false); /* FIXME: delete stuck notes iff percussion? */
_model_loaded = true;
} }