summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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;
}