diff options
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; } |