summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2007-07-15 01:56:11 +0000
committerDavid Robillard <d@drobilla.net>2007-07-15 01:56:11 +0000
commit332a3d98138e94903ad9c6d35048b0201ff178c8 (patch)
tree107ac2a68b8c461a534e82d41e47fedfc5ed9178
parent74eded425a5244831c80968c1781c3f81f90e996 (diff)
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
-rw-r--r--gtk2_ardour/audio_time_axis.cc19
-rw-r--r--gtk2_ardour/audio_time_axis.h1
-rw-r--r--gtk2_ardour/editing_syms.h1
-rw-r--r--gtk2_ardour/editor.cc12
-rw-r--r--gtk2_ardour/editor.h2
-rw-r--r--gtk2_ardour/editor_actions.cc1
-rw-r--r--gtk2_ardour/editor_mouse.cc16
-rw-r--r--gtk2_ardour/midi_region_view.cc114
-rw-r--r--gtk2_ardour/midi_region_view.h4
-rw-r--r--gtk2_ardour/midi_streamview.cc27
-rw-r--r--gtk2_ardour/midi_streamview.h19
-rw-r--r--gtk2_ardour/midi_time_axis.cc37
-rw-r--r--gtk2_ardour/midi_time_axis.h5
-rw-r--r--gtk2_ardour/route_time_axis.cc30
-rw-r--r--gtk2_ardour/route_time_axis.h1
-rw-r--r--libs/ardour/ardour/midi_buffer.h1
-rw-r--r--libs/ardour/ardour/midi_event.h49
-rw-r--r--libs/ardour/ardour/midi_model.h46
-rw-r--r--libs/ardour/ardour/midi_source.h3
-rw-r--r--libs/ardour/ardour/midi_track.h2
-rw-r--r--libs/ardour/ardour/smf_source.h2
-rw-r--r--libs/ardour/ardour/types.h15
-rw-r--r--libs/ardour/midi_model.cc100
-rw-r--r--libs/ardour/midi_source.cc2
-rw-r--r--libs/ardour/smf_source.cc17
25 files changed, 430 insertions, 96 deletions
diff --git a/gtk2_ardour/audio_time_axis.cc b/gtk2_ardour/audio_time_axis.cc
index db88982089..437e037836 100644
--- a/gtk2_ardour/audio_time_axis.cc
+++ b/gtk2_ardour/audio_time_axis.cc
@@ -206,17 +206,6 @@ AudioTimeAxisView::append_extra_display_menu_items ()
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*
@@ -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);
- }
-}
diff --git a/gtk2_ardour/audio_time_axis.h b/gtk2_ardour/audio_time_axis.h
index 822fbcadf0..a65bca1a70 100644
--- a/gtk2_ardour/audio_time_axis.h
+++ b/gtk2_ardour/audio_time_axis.h
@@ -76,7 +76,6 @@ class AudioTimeAxisView : public RouteTimeAxisView
void hide_all_xfades ();
void hide_dependent_views (TimeAxisViewItem&);
void reveal_dependent_views (TimeAxisViewItem&);
- void set_layer_display (LayerDisplay d);
/* Overridden from parent to store display state */
guint32 show_at (double y, int& nth, Gtk::VBox *parent);
diff --git a/gtk2_ardour/editing_syms.h b/gtk2_ardour/editing_syms.h
index f415e2c49c..7ae45c4c06 100644
--- a/gtk2_ardour/editing_syms.h
+++ b/gtk2_ardour/editing_syms.h
@@ -60,6 +60,7 @@ MOUSEMODE(MouseRange)
MOUSEMODE(MouseTimeFX)
MOUSEMODE(MouseZoom)
MOUSEMODE(MouseAudition)
+MOUSEMODE(MouseNote)
/* Changing this order will break the menu */
ZOOMFOCUS(ZoomFocusLeft)
diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc
index a771fb51b4..cd1b9e4b7c 100644
--- a/gtk2_ardour/editor.cc
+++ b/gtk2_ardour/editor.cc
@@ -152,6 +152,7 @@ Gdk::Cursor* Editor::zoom_cursor = 0;
Gdk::Cursor* Editor::time_fx_cursor = 0;
Gdk::Cursor* Editor::fader_cursor = 0;
Gdk::Cursor* Editor::speaker_cursor = 0;
+Gdk::Cursor* Editor::note_cursor = 0;
Gdk::Cursor* Editor::wait_cursor = 0;
Gdk::Cursor* Editor::timebar_cursor = 0;
@@ -1210,7 +1211,7 @@ Editor::build_cursors ()
mask = Bitmap::create (speaker_cursor_mask_bits, speaker_cursor_width, speaker_cursor_height);
speaker_cursor = new Gdk::Cursor (source, mask, ffg, fbg, speaker_cursor_x_hot, speaker_cursor_y_hot);
}
-
+
grabber_cursor = new Gdk::Cursor (HAND2);
cross_hair_cursor = new Gdk::Cursor (CROSSHAIR);
trimmer_cursor = new Gdk::Cursor (SB_H_DOUBLE_ARROW);
@@ -1218,6 +1219,7 @@ Editor::build_cursors ()
time_fx_cursor = new Gdk::Cursor (SIZING);
wait_cursor = new Gdk::Cursor (WATCH);
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 */
@@ -2322,6 +2324,9 @@ Editor::setup_toolbar ()
mouse_mode_buttons.push_back (&mouse_timefx_button);
mouse_audition_button.add (*(manage (new Image (::get_icon("tool_audition")))));
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_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_timefx_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);
vector<string> edit_mode_strings;
@@ -2368,6 +2374,7 @@ Editor::setup_toolbar ()
mouse_zoom_button.set_name ("MouseModeButton");
mouse_timefx_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_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_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_note_button, _("Edit MIDI Notes"));
mouse_move_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_timefx_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_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_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_note_button.signal_toggled().connect (bind (mem_fun(*this, &Editor::mouse_mode_toggled), Editing::MouseNote));
// mouse_move_button.set_active (true);
diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h
index b9a4f29cec..b4c0a03f85 100644
--- a/gtk2_ardour/editor.h
+++ b/gtk2_ardour/editor.h
@@ -808,6 +808,7 @@ class Editor : public PublicEditor
static Gdk::Cursor* time_fx_cursor;
static Gdk::Cursor* fader_cursor;
static Gdk::Cursor* speaker_cursor;
+ static Gdk::Cursor* note_cursor;
static Gdk::Cursor* wait_cursor;
static Gdk::Cursor* timebar_cursor;
@@ -1317,6 +1318,7 @@ class Editor : public PublicEditor
Gtk::ToggleButton mouse_zoom_button;
Gtk::ToggleButton mouse_timefx_button;
Gtk::ToggleButton mouse_audition_button;
+ Gtk::ToggleButton mouse_note_button;
GroupedButtons *mouse_mode_button_set;
void mouse_mode_toggled (Editing::MouseMode m);
bool ignore_mouse_mode_toggle;
diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc
index 52f30e7d5d..585c4669e2 100644
--- a/gtk2_ardour/editor_actions.cc
+++ b/gtk2_ardour/editor_actions.cc
@@ -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-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-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_("SnapMode"), _("Snap Mode"));
diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc
index 406eda01db..be2d0bc47d 100644
--- a/gtk2_ardour/editor_mouse.cc
+++ b/gtk2_ardour/editor_mouse.cc
@@ -160,6 +160,12 @@ Editor::mouse_mode_toggled (MouseMode m)
set_mouse_mode (m);
}
break;
+
+ case MouseNote:
+ if (mouse_note_button.get_active()) {
+ set_mouse_mode (m);
+ }
+ break;
default:
break;
@@ -244,6 +250,11 @@ Editor::set_mouse_mode (MouseMode m, bool force)
mouse_audition_button.set_active (true);
current_canvas_cursor = speaker_cursor;
break;
+
+ case MouseNote:
+ mouse_note_button.set_active (true);
+ current_canvas_cursor = note_cursor;
+ break;
}
ignore_mouse_mode_toggle = false;
@@ -286,6 +297,11 @@ Editor::step_mouse_mode (bool next)
if (next) set_mouse_mode (MouseObject);
else set_mouse_mode (MouseTimeFX);
break;
+
+ case MouseNote:
+ if (next) set_mouse_mode (MouseObject);
+ else set_mouse_mode (MouseAudition);
+ break;
}
}
diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc
index e4cb80bca0..81ea4029de 100644
--- a/gtk2_ardour/midi_region_view.cc
+++ b/gtk2_ardour/midi_region_view.cc
@@ -25,10 +25,12 @@
#include <gtkmm2ext/gtk_ui.h>
#include <ardour/playlist.h>
+#include <ardour/tempo.h>
#include <ardour/midi_region.h>
#include <ardour/midi_source.h>
#include <ardour/midi_diskstream.h>
#include <ardour/midi_events.h>
+#include <ardour/midi_model.h>
#include "streamview.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();
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();
- for (size_t i=0; i < midi_region()->midi_source(0)->model()->n_events(); ++i)
- add_event(midi_region()->midi_source(0)->model()->event_at(i));
+ for (size_t i=0; i < midi_region()->midi_source(0)->model()->n_notes(); ++i)
+ add_note(midi_region()->midi_source(0)->model()->note_at(i));
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
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);
+ }
+}
+
diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h
index 88ef43243b..6e37da9f50 100644
--- a/gtk2_ardour/midi_region_view.h
+++ b/gtk2_ardour/midi_region_view.h
@@ -25,6 +25,7 @@
#include <libgnomecanvasmm/polygon.h>
#include <sigc++/signal.h>
#include <ardour/midi_region.h>
+#include <ardour/midi_model.h>
#include <ardour/types.h>
#include "region_view.h"
@@ -64,6 +65,7 @@ class MidiRegionView : public RegionView
GhostRegion* add_ghost (AutomationTimeAxisView&);
void add_event(const ARDOUR::MidiEvent& ev);
+ void add_note(const ARDOUR::MidiModel::Note& note);
void begin_write();
void end_write();
@@ -95,6 +97,8 @@ class MidiRegionView : public RegionView
void display_events();
void clear_events();
+ bool canvas_event(GdkEvent* ev);
+
std::vector<ArdourCanvas::Item*> _events;
ArdourCanvas::SimpleRect** _active_notes;
};
diff --git a/gtk2_ardour/midi_streamview.cc b/gtk2_ardour/midi_streamview.cc
index 862e554cae..30700c8e94 100644
--- a/gtk2_ardour/midi_streamview.cc
+++ b/gtk2_ardour/midi_streamview.cc
@@ -54,8 +54,8 @@ using namespace Editing;
MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
: StreamView (tv)
- , _lowest_note(0)
- , _highest_note(127)
+ , _lowest_note(60)
+ , _highest_note(60)
{
if (tv.is_track())
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));
- for (size_t i=0; i < source->model()->n_events(); ++i) {
- const MidiEvent& ev = source->model()->event_at(i);
+ for (size_t i=0; i < source->model()->n_notes(); ++i) {
+ const MidiModel::Note& note = source->model()->note_at(i);
- // Look at all note on events to find our note range
- 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]);
- }
+ update_bounds(note.note);
if (redisplay_events)
- region_view->add_event(ev);
+ region_view->add_note(note);
}
if (redisplay_events)
@@ -164,8 +160,8 @@ MidiStreamView::redisplay_diskstream ()
(*i)->set_valid (false);
}
- _lowest_note = 60; // middle C
- _highest_note = _lowest_note + 11;
+ //_lowest_note = 60; // middle C
+ //_highest_note = _lowest_note + 11;
if (_trackview.is_midi_track()) {
_trackview.get_diskstream()->playlist()->foreach_region (static_cast<StreamView*>(this), &StreamView::add_region_view);
@@ -191,6 +187,13 @@ MidiStreamView::redisplay_diskstream ()
region_layered (*i);
}
}
+
+void
+MidiStreamView::update_bounds(uint8_t note_num)
+{
+ _lowest_note = min(_lowest_note, note_num);
+ _highest_note = max(_highest_note, note_num);
+}
void
diff --git a/gtk2_ardour/midi_streamview.h b/gtk2_ardour/midi_streamview.h
index ce459e2fad..b8ae33cf46 100644
--- a/gtk2_ardour/midi_streamview.h
+++ b/gtk2_ardour/midi_streamview.h
@@ -58,8 +58,18 @@ class MidiStreamView : public StreamView
void get_selectables (jack_nframes_t start, jack_nframes_t end, list<Selectable* >&);
void get_inverted_selectables (Selection&, list<Selectable* >& results);
- uint8_t lowest_note() const { return _lowest_note; }
- uint8_t highest_note() const { return _highest_note; }
+ enum VisibleNoteRange {
+ 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 ();
@@ -73,8 +83,9 @@ class MidiStreamView : public StreamView
void color_handler ();
- uint8_t _lowest_note;
- uint8_t _highest_note;
+ VisibleNoteRange _range;
+ uint8_t _lowest_note;
+ uint8_t _highest_note;
};
#endif /* __ardour_midi_streamview_h__ */
diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc
index 48aa78dc06..fb3d67913d 100644
--- a/gtk2_ardour/midi_time_axis.cc
+++ b/gtk2_ardour/midi_time_axis.cc
@@ -157,6 +157,31 @@ MidiTimeAxisView::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
MidiTimeAxisView::build_automation_action_menu ()
{
using namespace Menu_Helpers;
@@ -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
*/
void
@@ -285,3 +321,4 @@ MidiTimeAxisView::route_active_changed ()
}
}
+
diff --git a/gtk2_ardour/midi_time_axis.h b/gtk2_ardour/midi_time_axis.h
index 039affd979..a35a332e01 100644
--- a/gtk2_ardour/midi_time_axis.h
+++ b/gtk2_ardour/midi_time_axis.h
@@ -38,6 +38,7 @@
#include "enums.h"
#include "route_time_axis.h"
#include "canvas.h"
+#include "midi_streamview.h"
namespace ARDOUR {
class Session;
@@ -67,13 +68,15 @@ class MidiTimeAxisView : public RouteTimeAxisView
void create_automation_child (ARDOUR::Parameter param, bool show);
ARDOUR::NoteMode note_mode() const { return _note_mode; }
-
+
private:
+ void append_extra_display_menu_items ();
void build_automation_action_menu ();
Gtk::Menu* build_mode_menu();
void set_note_mode(ARDOUR::NoteMode mode);
+ void set_note_range(MidiStreamView::VisibleNoteRange range);
void route_active_changed ();
diff --git a/gtk2_ardour/route_time_axis.cc b/gtk2_ardour/route_time_axis.cc
index e0d93bd41a..dbfb8b029b 100644
--- a/gtk2_ardour/route_time_axis.cc
+++ b/gtk2_ardour/route_time_axis.cc
@@ -500,29 +500,42 @@ RouteTimeAxisView::build_display_menu ()
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);
MenuList& alignment_items = alignment_menu->items();
alignment_menu->set_name ("ArdourContextMenu");
RadioMenuItem::Group align_group;
-
+
alignment_items.push_back (RadioMenuElem (align_group, _("Align with existing material"),
- bind (mem_fun(*this, &RouteTimeAxisView::set_align_style), ExistingMaterial)));
+ bind (mem_fun(*this, &RouteTimeAxisView::set_align_style), ExistingMaterial)));
align_existing_item = dynamic_cast<RadioMenuItem*>(&alignment_items.back());
if (get_diskstream()->alignment_style() == ExistingMaterial)
align_existing_item->set_active();
-
+
alignment_items.push_back (RadioMenuElem (align_group, _("Align with capture time"),
- bind (mem_fun(*this, &RouteTimeAxisView::set_align_style), CaptureTime)));
+ bind (mem_fun(*this, &RouteTimeAxisView::set_align_style), CaptureTime)));
align_capture_item = dynamic_cast<RadioMenuItem*>(&alignment_items.back());
if (get_diskstream()->alignment_style() == CaptureTime)
align_capture_item->set_active();
-
+
items.push_back (MenuElem (_("Alignment"), *alignment_menu));
get_diskstream()->AlignmentStyleChanged.connect (
mem_fun(*this, &RouteTimeAxisView::align_style_changed));
-
+
mode_menu = build_mode_menu();
if (mode_menu)
items.push_back (MenuElem (_("Mode"), *mode_menu));
@@ -1980,3 +1993,8 @@ RouteTimeAxisView::update_rec_display ()
name_entry.set_sensitive (!_route->record_enabled());
}
+void
+RouteTimeAxisView::set_layer_display (LayerDisplay d)
+{
+ _view->set_layer_display (d);
+}
diff --git a/gtk2_ardour/route_time_axis.h b/gtk2_ardour/route_time_axis.h
index 0d42f331a9..cd92f47322 100644
--- a/gtk2_ardour/route_time_axis.h
+++ b/gtk2_ardour/route_time_axis.h
@@ -79,6 +79,7 @@ public:
void get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable *>&);
void get_inverted_selectables (Selection&, list<Selectable*>&);
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);
diff --git a/libs/ardour/ardour/midi_buffer.h b/libs/ardour/ardour/midi_buffer.h
index cf2f92ff79..0a73ac932d 100644
--- a/libs/ardour/ardour/midi_buffer.h
+++ b/libs/ardour/ardour/midi_buffer.h
@@ -21,6 +21,7 @@
#define __ardour_midi_buffer_h__
#include <ardour/buffer.h>
+#include <ardour/midi_event.h>
namespace ARDOUR {
diff --git a/libs/ardour/ardour/midi_event.h b/libs/ardour/ardour/midi_event.h
new file mode 100644
index 0000000000..8aca2d14da
--- /dev/null
+++ b/libs/ardour/ardour/midi_event.h
@@ -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__ */
diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h
index 909603a017..ff52398180 100644
--- a/libs/ardour/ardour/midi_model.h
+++ b/libs/ardour/ardour/midi_model.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2006 Paul Davis
+ Copyright (C) 2007 Paul Davis
Author: Dave Robillard
This program is free software; you can redistribute it and/or modify
@@ -27,14 +27,30 @@
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 {
public:
+ struct Note {
+ Note(double s=0, double d=0, uint8_t n=0, uint8_t v=0)
+ : start(s), duration(d), note(n), velocity(v) {}
+
+ double start;
+ double duration;
+ uint8_t note;
+ uint8_t velocity;
+ };
+
MidiModel(size_t size=0);
- ~MidiModel();
- void clear() { _events.clear(); }
+ void clear() { _notes.clear(); }
+
+ void start_write();
+ void end_write(bool delete_stuck=false);
/** Resizes vector if necessary (NOT realtime safe) */
void append(const MidiBuffer& data);
@@ -42,12 +58,28 @@ public:
/** Resizes vector if necessary (NOT realtime safe) */
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_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 size_t n_events() const { return _events.size(); }
+ inline Notes& notes() { return _notes; }
+ inline const Notes& notes() const { return _notes; }
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 */
diff --git a/libs/ardour/ardour/midi_source.h b/libs/ardour/ardour/midi_source.h
index bc2cc90b19..d95d0fd75a 100644
--- a/libs/ardour/ardour/midi_source.h
+++ b/libs/ardour/ardour/midi_source.h
@@ -69,7 +69,7 @@ class MidiSource : public Source
XMLNode& get_state ();
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;
MidiModel* model() { return _model; }
@@ -84,6 +84,7 @@ class MidiSource : public Source
mutable uint32_t _write_data_count; ///< modified in write()
MidiModel* _model;
+ bool _model_loaded;
private:
bool file_changed (string path);
diff --git a/libs/ardour/ardour/midi_track.h b/libs/ardour/ardour/midi_track.h
index 2317cd0549..bb55454d3b 100644
--- a/libs/ardour/ardour/midi_track.h
+++ b/libs/ardour/ardour/midi_track.h
@@ -57,8 +57,6 @@ public:
int use_diskstream (string name);
int use_diskstream (const PBD::ID& id);
- //int set_mode (TrackMode m);
-
void set_latency_delay (nframes_t);
int export_stuff (BufferSet& bufs,
diff --git a/libs/ardour/ardour/smf_source.h b/libs/ardour/ardour/smf_source.h
index e4d2611271..f8e73c4ec3 100644
--- a/libs/ardour/ardour/smf_source.h
+++ b/libs/ardour/ardour/smf_source.h
@@ -89,7 +89,7 @@ class SMFSource : public MidiSource {
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();
uint16_t ppqn() const { return _ppqn; }
diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h
index ab54b6c26c..4b270f1a7d 100644
--- a/libs/ardour/ardour/types.h
+++ b/libs/ardour/ardour/types.h
@@ -61,21 +61,6 @@ namespace ARDOUR {
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 {
NoChange = 0,
ConfigurationChanged = 0x1,
diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc
index bfc1e4b300..98d29a2729 100644
--- a/libs/ardour/midi_model.cc
+++ b/libs/ardour/midi_model.cc
@@ -1,6 +1,6 @@
/*
- Copyright (C) 2006 Paul Davis
- Written by Dave Robillard, 2006
+ Copyright (C) 2007 Paul Davis
+ Written by Dave Robillard, 2007
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
@@ -20,6 +20,7 @@
#include <iostream>
#include <ardour/midi_model.h>
+#include <ardour/midi_events.h>
#include <ardour/types.h>
using namespace std;
@@ -27,14 +28,45 @@ using namespace ARDOUR;
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)
- delete _events[i].buffer;
+ _write_notes.clear();
+}
+
+
+
+/** 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)
{
for (size_t i=0; i < buf.size(); ++i) {
- const MidiEvent& buf_event = buf[i];
- assert(_events.empty() || buf_event.time >= _events.back().time);
-
- _events.push_back(buf_event);
- MidiEvent& my_event = _events.back();
- assert(my_event.time == buf_event.time);
- assert(my_event.size == buf_event.size);
+ const MidiEvent& ev = buf[i];
- my_event.buffer = new Byte[my_event.size];
- memcpy(my_event.buffer, buf_event.buffer, my_event.size);
+ assert(_write_notes.empty() || ev.time >= _write_notes.back().start);
+
+ if (ev.type() == MIDI_CMD_NOTE_ON)
+ 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.
*/
void
-MidiModel::append(double time, size_t size, Byte* in_buffer)
+MidiModel::append(double time, size_t size, Byte* buf)
+{
+ assert(_write_notes.empty() || time >= _write_notes.back().start);
+
+ if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON)
+ append_note_on(time, buf[1], buf[2]);
+ else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF)
+ append_note_off(time, buf[1]);
+}
+
+
+void
+MidiModel::append_note_on(double time, uint8_t note, uint8_t velocity)
{
- assert(_events.empty() || time >= _events.back().time);
+ _write_notes.push_back(Note(time, 0, note, velocity));
+}
- //cerr << "Model event: time = " << time << endl;
- Byte* my_buffer = new Byte[size];
- memcpy(my_buffer, in_buffer, size);
- _events.push_back(MidiEvent(time, size, my_buffer));
+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;
+ }
+ }
}
diff --git a/libs/ardour/midi_source.cc b/libs/ardour/midi_source.cc
index 2b4f8a8255..a346bfd4f2 100644
--- a/libs/ardour/midi_source.cc
+++ b/libs/ardour/midi_source.cc
@@ -45,6 +45,7 @@ sigc::signal<void,MidiSource *> MidiSource::MidiSourceCreated;
MidiSource::MidiSource (Session& s, string name)
: Source (s, name, DataType::MIDI)
, _model(new MidiModel())
+ , _model_loaded (false)
{
_read_data_count = 0;
_write_data_count = 0;
@@ -53,6 +54,7 @@ MidiSource::MidiSource (Session& s, string name)
MidiSource::MidiSource (Session& s, const XMLNode& node)
: Source (s, node)
, _model(new MidiModel())
+ , _model_loaded (false)
{
_read_data_count = 0;
_write_data_count = 0;
diff --git a/libs/ardour/smf_source.cc b/libs/ardour/smf_source.cc
index c7c3383a44..39b8252a8a 100644
--- a/libs/ardour/smf_source.cc
+++ b/libs/ardour/smf_source.cc
@@ -776,13 +776,20 @@ SMFSource::read_var_len() const
}
void
-SMFSource::load_model(bool lock)
+SMFSource::load_model(bool lock, bool force_reload)
{
if (lock)
Glib::Mutex::Lock lm (_lock);
- destroy_model();
- _model = new MidiModel();
+ if (_model && _model_loaded && ! force_reload) {
+ assert(_model);
+ return;
+ }
+
+ if (! _model)
+ _model = new MidiModel();
+
+ _model->start_write();
fseek(_fd, _header_size, 0);
@@ -807,6 +814,10 @@ SMFSource::load_model(bool lock)
_model->append(ev_time, ev.size, ev.buffer);
}
}
+
+ _model->end_write(false); /* FIXME: delete stuck notes iff percussion? */
+
+ _model_loaded = true;
}