mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-17 12:16:30 +01:00
Selection of visible note range (full range vs fit contents, selectable from midi track menu).
Added note pencil tool, mock note adding (notes can be added visually but don't yet play). Reworked MidiModel to be notes w/ duration instead of realtime style MIDI events. Moved layering (stacked/overlaid) from auto time axis down to route time axis since it applies to MIDI tracks as well. git-svn-id: svn://localhost/ardour2/trunk@2128 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
74eded425a
commit
332a3d9813
25 changed files with 432 additions and 98 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ MOUSEMODE(MouseRange)
|
|||
MOUSEMODE(MouseTimeFX)
|
||||
MOUSEMODE(MouseZoom)
|
||||
MOUSEMODE(MouseAudition)
|
||||
MOUSEMODE(MouseNote)
|
||||
|
||||
/* Changing this order will break the menu */
|
||||
ZOOMFOCUS(ZoomFocusLeft)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -161,6 +161,12 @@ Editor::mouse_mode_toggled (MouseMode 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -192,6 +188,13 @@ MidiStreamView::redisplay_diskstream ()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
MidiStreamView::update_bounds(uint8_t note_num)
|
||||
{
|
||||
_lowest_note = min(_lowest_note, note_num);
|
||||
_highest_note = max(_highest_note, note_num);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MidiStreamView::setup_rec_box ()
|
||||
|
|
|
|||
|
|
@ -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,6 +83,7 @@ class MidiStreamView : public StreamView
|
|||
|
||||
void color_handler ();
|
||||
|
||||
VisibleNoteRange _range;
|
||||
uint8_t _lowest_note;
|
||||
uint8_t _highest_note;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -156,6 +156,31 @@ MidiTimeAxisView::hide ()
|
|||
TimeAxisView::hide ();
|
||||
}
|
||||
|
||||
void
|
||||
MidiTimeAxisView::append_extra_display_menu_items ()
|
||||
{
|
||||
using namespace Menu_Helpers;
|
||||
|
||||
MenuList& items = display_menu->items();
|
||||
|
||||
// Note range
|
||||
Menu *range_menu = manage(new Menu);
|
||||
MenuList& range_items = range_menu->items();
|
||||
range_menu->set_name ("ArdourContextMenu");
|
||||
|
||||
RadioMenuItem::Group range_group;
|
||||
|
||||
range_items.push_back (RadioMenuElem (range_group, _("Show Full Range"), bind (
|
||||
mem_fun(*this, &MidiTimeAxisView::set_note_range),
|
||||
MidiStreamView::FullRange)));
|
||||
|
||||
range_items.push_back (RadioMenuElem (range_group, _("Fit Contents"), bind (
|
||||
mem_fun(*this, &MidiTimeAxisView::set_note_range),
|
||||
MidiStreamView::ContentsRange)));
|
||||
|
||||
items.push_back (MenuElem (_("Note range"), *range_menu));
|
||||
}
|
||||
|
||||
void
|
||||
MidiTimeAxisView::build_automation_action_menu ()
|
||||
{
|
||||
|
|
@ -204,6 +229,17 @@ MidiTimeAxisView::set_note_mode(NoteMode mode)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MidiTimeAxisView::set_note_range(MidiStreamView::VisibleNoteRange range)
|
||||
{
|
||||
//if (midi_view()->note_range() != range) {
|
||||
midi_view()->set_note_range(range);
|
||||
midi_view()->redisplay_diskstream();
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
/** Prompt for a controller with a dialog and add an automation track for it
|
||||
*/
|
||||
void
|
||||
|
|
@ -285,3 +321,4 @@ MidiTimeAxisView::route_active_changed ()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
#include "enums.h"
|
||||
#include "route_time_axis.h"
|
||||
#include "canvas.h"
|
||||
#include "midi_streamview.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
class Session;
|
||||
|
|
@ -70,10 +71,12 @@ class MidiTimeAxisView : public RouteTimeAxisView
|
|||
|
||||
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 ();
|
||||
|
||||
|
|
|
|||
|
|
@ -500,6 +500,19 @@ 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");
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#define __ardour_midi_buffer_h__
|
||||
|
||||
#include <ardour/buffer.h>
|
||||
#include <ardour/midi_event.h>
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
|
|
|
|||
49
libs/ardour/ardour/midi_event.h
Normal file
49
libs/ardour/ardour/midi_event.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright (C) 2007 Paul Davis
|
||||
Author: Dave Robillard
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __ardour_midi_event_h__
|
||||
#define __ardour_midi_event_h__
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
|
||||
/** Identical to jack_midi_event_t, but with double timestamp
|
||||
*
|
||||
* time is either a frame time (from/to Jack) or a beat time (internal
|
||||
* tempo time, used in MidiModel) depending on context.
|
||||
*/
|
||||
struct MidiEvent {
|
||||
MidiEvent(double t=0, size_t s=0, Byte* b=NULL)
|
||||
: time(t), size(s), buffer(b)
|
||||
{}
|
||||
|
||||
inline uint8_t type() const { return (buffer[0] & 0xF0); }
|
||||
inline uint8_t note() const { return (buffer[1]); }
|
||||
inline uint8_t velocity() const { return (buffer[2]); }
|
||||
|
||||
double time; /**< Sample index (or beat time) at which event is valid */
|
||||
size_t size; /**< Number of bytes of data in \a buffer */
|
||||
Byte* buffer; /**< Raw MIDI data */
|
||||
};
|
||||
|
||||
|
||||
} // namespace ARDOUR
|
||||
|
||||
#endif /* __ardour_midi_event_h__ */
|
||||
|
|
@ -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:
|
||||
MidiModel(size_t size=0);
|
||||
~MidiModel();
|
||||
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) {}
|
||||
|
||||
void clear() { _events.clear(); }
|
||||
double start;
|
||||
double duration;
|
||||
uint8_t note;
|
||||
uint8_t velocity;
|
||||
};
|
||||
|
||||
MidiModel(size_t size=0);
|
||||
|
||||
void clear() { _notes.clear(); }
|
||||
|
||||
void start_write();
|
||||
void end_write(bool delete_stuck=false);
|
||||
|
||||
/** Resizes vector if necessary (NOT realtime safe) */
|
||||
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_events() const { return _events.size(); }
|
||||
inline size_t n_notes() const { return _notes.size(); }
|
||||
|
||||
typedef std::vector<Note> Notes;
|
||||
|
||||
struct NoteTimeComparator {
|
||||
inline bool operator() (const Note& a, const Note& b) const {
|
||||
return a.start < b.start;
|
||||
}
|
||||
};
|
||||
|
||||
inline Notes& notes() { return _notes; }
|
||||
inline const Notes& notes() const { return _notes; }
|
||||
|
||||
private:
|
||||
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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
const MidiEvent& ev = buf[i];
|
||||
|
||||
_events.push_back(buf_event);
|
||||
MidiEvent& my_event = _events.back();
|
||||
assert(my_event.time == buf_event.time);
|
||||
assert(my_event.size == buf_event.size);
|
||||
assert(_write_notes.empty() || ev.time >= _write_notes.back().start);
|
||||
|
||||
my_event.buffer = new Byte[my_event.size];
|
||||
memcpy(my_event.buffer, buf_event.buffer, my_event.size);
|
||||
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(_events.empty() || time >= _events.back().time);
|
||||
assert(_write_notes.empty() || time >= _write_notes.back().start);
|
||||
|
||||
//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));
|
||||
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)
|
||||
{
|
||||
_write_notes.push_back(Note(time, 0, note, velocity));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MidiModel::append_note_off(double time, uint8_t note_num)
|
||||
{
|
||||
/* _write_notes (active notes) is presumably small enough for linear
|
||||
* search to be a good idea. maybe not with instruments (percussion)
|
||||
* that don't send note off at all though.... FIXME? */
|
||||
|
||||
/* FIXME: note off velocity for that one guy out there who actually has
|
||||
* keys that send it */
|
||||
|
||||
for (size_t i=0; i < _write_notes.size(); ++i) {
|
||||
Note& note = _write_notes[i];
|
||||
if (note.note == note_num) {
|
||||
assert(time > note.start);
|
||||
note.duration = time - note.start;
|
||||
cerr << "MidiModel resolved note, duration: " << note.duration << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -776,14 +776,21 @@ 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();
|
||||
if (_model && _model_loaded && ! force_reload) {
|
||||
assert(_model);
|
||||
return;
|
||||
}
|
||||
|
||||
if (! _model)
|
||||
_model = new MidiModel();
|
||||
|
||||
_model->start_write();
|
||||
|
||||
fseek(_fd, _header_size, 0);
|
||||
|
||||
uint64_t time = 0; /* in SMF ticks */
|
||||
|
|
@ -807,6 +814,10 @@ SMFSource::load_model(bool lock)
|
|||
_model->append(ev_time, ev.size, ev.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
_model->end_write(false); /* FIXME: delete stuck notes iff percussion? */
|
||||
|
||||
_model_loaded = true;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue