X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=fde477ffc05c734e99cab7478f3513c44b953a31;hb=a5abcd306ba589411aa8b4e5b2729621bdc77d3a;hp=fe2947bc42261678e787604ec1a56d597c519ae4;hpb=41c128155a5eec412f138bcb90b8eb65bd1c5b3d;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index fe2947bc42..fde477ffc0 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -1,5 +1,6 @@ /* - Copyright (C) 2001-2006 Paul Davis + Copyright (C) 2001-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 @@ -24,17 +25,23 @@ #include +#include + #include +#include #include #include #include #include +#include #include "streamview.h" #include "midi_region_view.h" +#include "midi_streamview.h" #include "midi_time_axis.h" #include "simplerect.h" #include "simpleline.h" +#include "canvas-hit.h" #include "public_editor.h" #include "ghostregion.h" #include "midi_time_axis.h" @@ -53,25 +60,38 @@ using namespace ArdourCanvas; MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr r, double spu, Gdk::Color& basic_color) : RegionView (parent, tv, r, spu, basic_color) + , _default_note_length(0.0) , _active_notes(0) + , _delta_command(NULL) + , _command_mode(None) { } MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr r, double spu, Gdk::Color& basic_color, TimeAxisViewItem::Visibility visibility) : RegionView (parent, tv, r, spu, basic_color, visibility) + , _default_note_length(0.0) , _active_notes(0) + , _delta_command(NULL) + , _command_mode(None) { } void MidiRegionView::init (Gdk::Color& basic_color, bool wfd) { - // FIXME: Some redundancy here with RegionView::init. Need to figure out - // where order is important and where it isn't... + if (wfd) + midi_region()->midi_source(0)->load_model(); + + const Meter& m = trackview.session().tempo_map().meter_at(_region->start()); + const Tempo& t = trackview.session().tempo_map().tempo_at(_region->start()); + _default_note_length = m.frames_per_bar(t, trackview.session().frame_rate()) + / m.beats_per_bar(); + + _model = midi_region()->midi_source(0)->model(); + _enable_display = false; - // FIXME - RegionView::init(basic_color, /*wfd*/false); + RegionView::init(basic_color, false); compute_colors (basic_color); @@ -86,8 +106,256 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd) _region->StateChanged.connect (mem_fun(*this, &MidiRegionView::region_changed)); set_colors (); + + _enable_display = true; + + if (_model) { + if (wfd) { + redisplay_model(); + } + _model->ContentsChanged.connect(sigc::mem_fun(this, &MidiRegionView::redisplay_model)); + } + + group->signal_event().connect (mem_fun (this, &MidiRegionView::canvas_event), false); + } +bool +MidiRegionView::canvas_event(GdkEvent* ev) +{ + enum State { None, Pressed, SelectDragging, AddDragging }; + static int press_button = 0; + static State _state; + + static double drag_start_x, drag_start_y; + static double last_x, last_y; + double event_x, event_y; + + static ArdourCanvas::SimpleRect* drag_rect = NULL; + + if (trackview.editor.current_mouse_mode() != MouseNote) + return false; + + switch (ev->type) { + case GDK_KEY_PRESS: + if (ev->key.keyval == GDK_Delete) + start_remove_command(); + break; + + case GDK_KEY_RELEASE: + if (_command_mode == Remove && ev->key.keyval == GDK_Delete) + apply_command(); + break; + + case GDK_BUTTON_PRESS: + //group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, ev->button.time); + _state = Pressed; + press_button = ev->button.button; + //cerr << "PRESSED: " << press_button << endl; + return true; + + case GDK_MOTION_NOTIFY: + event_x = ev->motion.x; + event_y = ev->motion.y; + group->w2i(event_x, event_y); + + switch (_state) { + case Pressed: // Drag start + + if (press_button == 1 && _command_mode != Remove) { // Select rect start + group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, + Gdk::Cursor(Gdk::FLEUR), ev->motion.time); + last_x = event_x; + last_y = event_y; + drag_start_x = event_x; + drag_start_y = event_y; + + drag_rect = new ArdourCanvas::SimpleRect(*group); + drag_rect->property_x1() = event_x; + drag_rect->property_y1() = event_y; + drag_rect->property_x2() = event_x; + drag_rect->property_y2() = event_y; + drag_rect->property_outline_what() = 0xFF; + drag_rect->property_outline_color_rgba() = 0xFF000099; + drag_rect->property_fill_color_rgba() = 0xFFDDDD33; + + _state = SelectDragging; + return true; + + } else if (press_button == 3) { // Add note drag start + group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, + Gdk::Cursor(Gdk::FLEUR), ev->motion.time); + last_x = event_x; + last_y = event_y; + drag_start_x = event_x; + drag_start_y = event_y; + + nframes_t event_frame = midi_view()->editor.pixel_to_frame(event_x); + midi_view()->editor.snap_to(event_frame); + + drag_rect = new ArdourCanvas::SimpleRect(*group); + drag_rect->property_x1() = midi_view()->editor.frame_to_pixel(event_frame); + + drag_rect->property_y1() = midi_stream_view()->note_to_y(midi_stream_view()->y_to_note(event_y)); + drag_rect->property_x2() = event_x; + drag_rect->property_y2() = drag_rect->property_y1() + floor(midi_stream_view()->note_height()); + drag_rect->property_outline_what() = 0xFF; + drag_rect->property_outline_color_rgba() = 0xFFFFFF99; + drag_rect->property_fill_color_rgba() = 0xFFFFFF66; + + _state = AddDragging; + return true; + } + + break; + + case SelectDragging: // Select rect motion + case AddDragging: // Add note rect motion + if (ev->motion.is_hint) { + int t_x; + int t_y; + GdkModifierType state; + gdk_window_get_pointer(ev->motion.window, &t_x, &t_y, &state); + event_x = t_x; + event_y = t_y; + } + + if (_state == AddDragging) { + nframes_t event_frame = midi_view()->editor.pixel_to_frame(event_x); + midi_view()->editor.snap_to(event_frame); + event_x = midi_view()->editor.frame_to_pixel(event_frame); + } + + if (drag_rect) + drag_rect->property_x2() = event_x; + + if (drag_rect && _state == SelectDragging) + drag_rect->property_y2() = event_y; + + last_x = event_x; + last_y = event_y; + + return true; + default: + _state = None; + break; + } + break; + + case GDK_BUTTON_RELEASE: + event_x = ev->motion.x; + event_y = ev->motion.y; + group->w2i(event_x, event_y); + group->ungrab(ev->button.time); + switch (_state) { + case Pressed: // Clicked + if (ev->button.button == 3) { + nframes_t event_frame = midi_view()->editor.pixel_to_frame(event_x); + midi_view()->editor.snap_to(event_frame); + event_x = midi_view()->editor.frame_to_pixel(event_frame); + create_note_at(event_x, event_y, _default_note_length); + } + _state = None; + return true; + case SelectDragging: // Select drag done + _state = None; + delete drag_rect; + drag_rect = NULL; + return true; + case AddDragging: // Add drag done + _state = None; + if (drag_rect->property_x2() > drag_rect->property_x1() + 2) { + create_note_at(drag_rect->property_x1(), drag_rect->property_y1(), + trackview.editor.pixel_to_frame( + drag_rect->property_x2() - drag_rect->property_x1())); + } + + delete drag_rect; + drag_rect = NULL; + return true; + default: + break; + } + + default: + break; + } + + return false; +} + + +/** Add a note to the model, and the view, at a canvas (click) coordinate */ +void +MidiRegionView::create_note_at(double x, double y, double dur) +{ + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + MidiStreamView* const view = mtv->midi_view(); + + double note = midi_stream_view()->y_to_note(y); + + 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); + + view->update_bounds(new_note.note()); + + MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note"); + cmd->add(new_note); + _model->apply_command(cmd); + + //add_note(new_note); +} + + +void +MidiRegionView::clear_events() +{ + for (std::vector::iterator i = _events.begin(); i != _events.end(); ++i) + delete *i; + + _events.clear(); +} + + +void +MidiRegionView::display_model(boost::shared_ptr model) +{ + _model = model; + + if (_enable_display) + redisplay_model(); +} + + +void +MidiRegionView::redisplay_model() +{ + clear_events(); + + if (_model) { + begin_write(); + + for (size_t i=0; i < _model->n_notes(); ++i) + add_note(_model->note_at(i)); + + end_write(); + } else { + warning << "MidiRegionView::redisplay_model called without a model" << endmsg; + } +} + + MidiRegionView::~MidiRegionView () { in_destructor = true; @@ -96,11 +364,49 @@ MidiRegionView::~MidiRegionView () RegionViewGoingAway (this); /* EMIT_SIGNAL */ } -boost::shared_ptr -MidiRegionView::midi_region() const + +void +MidiRegionView::region_resized (Change what_changed) +{ + RegionView::region_resized(what_changed); + + if (what_changed & ARDOUR::PositionChanged) { + + if (_enable_display) + redisplay_model(); + + } else if (what_changed & Change (StartChanged)) { + + //cerr << "MIDI RV START CHANGED" << endl; + + } else if (what_changed & Change (LengthChanged)) { + + //cerr << "MIDI RV LENGTH CHANGED" << endl; + + } +} + +void +MidiRegionView::reset_width_dependent_items (double pixel_width) { - // "Guaranteed" to succeed... - return boost::dynamic_pointer_cast(_region); + RegionView::reset_width_dependent_items(pixel_width); + assert(_pixel_width == pixel_width); + + if (_enable_display) + redisplay_model(); +} + +void +MidiRegionView::set_y_position_and_height (double y, double h) +{ + RegionView::set_y_position_and_height(y, h - 1); + + if (_enable_display) + redisplay_model(); + + if (name_text) { + name_text->raise_to_top(); + } } void @@ -112,8 +418,19 @@ MidiRegionView::show_region_editor () GhostRegion* MidiRegionView::add_ghost (AutomationTimeAxisView& atv) { - throw; // FIXME - return NULL; + RouteTimeAxisView* rtv = dynamic_cast(&trackview); + assert(rtv); + + double unit_position = _region->position () / samples_per_unit; + GhostRegion* ghost = new GhostRegion (atv, unit_position); + + ghost->set_height (); + ghost->set_duration (_region->length() / samples_per_unit); + ghosts.push_back (ghost); + + ghost->GoingAway.connect (mem_fun(*this, &MidiRegionView::remove_ghost)); + + return ghost; } @@ -122,7 +439,7 @@ MidiRegionView::add_ghost (AutomationTimeAxisView& atv) void MidiRegionView::begin_write() { - _active_notes = new ArdourCanvas::SimpleRect*[128]; + _active_notes = new CanvasNote*[128]; for (unsigned i=0; i < 128; ++i) _active_notes[i] = NULL; } @@ -138,46 +455,68 @@ 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) { - /*printf("Event, time = %u, size = %zu, data = ", - ev.time, ev.size); - for (size_t i=0; i < ev.size; ++i) { - printf("%X ", ev.buffer[i]); - } - printf("\n");*/ - double y1 = trackview.height / 2.0; - if ((ev.buffer[0] & 0xF0) == MIDI_CMD_NOTE_ON) { - const Byte& note = ev.buffer[1]; - y1 = trackview.height - ((trackview.height / 127.0) * note); - - ArdourCanvas::SimpleRect * ev_rect = new Gnome::Canvas::SimpleRect( - *(ArdourCanvas::Group*)get_canvas_group()); - ev_rect->property_x1() = trackview.editor.frame_to_pixel ( - ev.time); - ev_rect->property_y1() = y1; - ev_rect->property_x2() = trackview.editor.frame_to_pixel ( - _region->length()); - ev_rect->property_y2() = y1 + ceil(trackview.height / 127.0); - ev_rect->property_outline_color_rgba() = 0xFFFFFFAA; - /* outline all but right edge */ - ev_rect->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8); - ev_rect->property_fill_color_rgba() = 0xFFFFFF66; + /*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");*/ - _events.push_back(ev_rect); - if (_active_notes) - _active_notes[note] = ev_rect; - - } else if ((ev.buffer[0] & 0xF0) == MIDI_CMD_NOTE_OFF) { - const Byte& note = ev.buffer[1]; - if (_active_notes && _active_notes[note]) { - _active_notes[note]->property_x2() = trackview.editor.frame_to_pixel(ev.time); - _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges - _active_notes[note] = NULL; + ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group(); + + if (midi_view()->note_mode() == Sustained) { + if ((ev.buffer()[0] & 0xF0) == MIDI_CMD_NOTE_ON) { + const Byte& note = ev.buffer()[1]; + const double y1 = midi_stream_view()->note_to_y(note); + + CanvasNote* ev_rect = new CanvasNote(*this, *group); + ev_rect->property_x1() = trackview.editor.frame_to_pixel ( + (nframes_t)ev.time()); + ev_rect->property_y1() = y1; + ev_rect->property_x2() = trackview.editor.frame_to_pixel ( + _region->length()); + ev_rect->property_y2() = y1 + floor(midi_stream_view()->note_height()); + ev_rect->property_outline_color_rgba() = 0xFFFFFFAA; + /* outline all but right edge */ + ev_rect->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8); + ev_rect->property_fill_color_rgba() = 0xFFFFFF66; + + ev_rect->raise_to_top(); + + _events.push_back(ev_rect); + if (_active_notes) + _active_notes[note] = ev_rect; + + } else if ((ev.buffer()[0] & 0xF0) == MIDI_CMD_NOTE_OFF) { + const Byte& note = ev.buffer()[1]; + if (_active_notes && _active_notes[note]) { + _active_notes[note]->property_x2() = trackview.editor.frame_to_pixel((nframes_t)ev.time()); + _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges + _active_notes[note] = NULL; + } } + + } else if (midi_view()->note_mode() == Percussive) { + const Byte& note = ev.buffer()[1]; + const double diamond_size = midi_stream_view()->note_height() / 2.0; + const double x = trackview.editor.frame_to_pixel((nframes_t)ev.time()); + const double y = midi_stream_view()->note_to_y(note) + (diamond_size / 2.0); + + CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size); + 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); } - } @@ -195,4 +534,55 @@ MidiRegionView::extend_active_notes() } +/** Add a MIDI note to the view (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.time() >= 0); + assert(note.time() < _region->length()); + //assert(note.time() + note.duration < _region->length()); + + ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group(); + + const uint8_t fill_alpha = 0x20 + (uint8_t)(note.velocity() * 1.5); + const uint32_t fill = RGBA_TO_UINT(0xE0 + note.velocity()/127.0 * 0x10, 0xE0, 0xE0, fill_alpha); + const uint8_t outline_alpha = 0x80 + (uint8_t)(note.velocity()); + const uint32_t outline = RGBA_TO_UINT(0xE0 + note.velocity()/127.0 * 0x10, 0xE0, 0xE0, outline_alpha); + + //printf("Event, time = %f, note = %d\n", note.time(), note.note()); + + if (midi_view()->note_mode() == Sustained) { + const double y1 = midi_stream_view()->note_to_y(note.note()); + + ArdourCanvas::SimpleRect * ev_rect = new CanvasNote(*this, *group, ¬e); + ev_rect->property_x1() = trackview.editor.frame_to_pixel((nframes_t)note.time()); + ev_rect->property_y1() = y1; + ev_rect->property_x2() = trackview.editor.frame_to_pixel((nframes_t)(note.end_time())); + ev_rect->property_y2() = y1 + floor(midi_stream_view()->note_height()); + + ev_rect->property_fill_color_rgba() = fill; + ev_rect->property_outline_color_rgba() = outline; + ev_rect->property_outline_what() = (guint32) 0xF; // all edges + + ev_rect->show(); + _events.push_back(ev_rect); + + } else if (midi_view()->note_mode() == Percussive) { + const double diamond_size = midi_stream_view()->note_height() / 2.0; + const double x = trackview.editor.frame_to_pixel((nframes_t)note.time()); + const double y = midi_stream_view()->note_to_y(note.note()) + (diamond_size / 2.0) - 2; + + CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size); + ev_diamond->move(x, y); + ev_diamond->show(); + ev_diamond->property_fill_color_rgba() = fill; + ev_diamond->property_outline_color_rgba() = outline; + _events.push_back(ev_diamond); + } +} +