Basic canvas note event handling framework.

Note dragging (non-functional).


git-svn-id: svn://localhost/ardour2/trunk@2187 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard 2007-07-30 16:33:10 +00:00
parent 633d9131af
commit 2cbaa2751c
10 changed files with 398 additions and 89 deletions

View file

@ -109,6 +109,7 @@ canvas-simplerect.c
simplerect.cc simplerect.cc
canvas-waveview.c canvas-waveview.c
diamond.cc diamond.cc
canvas-midi-event.cc
crossfade_edit.cc crossfade_edit.cc
crossfade_view.cc crossfade_view.cc
curvetest.cc curvetest.cc

41
gtk2_ardour/canvas-hit.h Normal file
View file

@ -0,0 +1,41 @@
/*
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 __gtk_ardour_canvas_hit_h__
#define __gtk_ardour_canvas_hit_h__
#include <iostream>
#include "simplerect.h"
#include "diamond.h"
namespace Gnome {
namespace Canvas {
class CanvasHit : public Diamond, public CanvasMidiEvent {
public:
CanvasHit(MidiRegionView& region, Group& group, double size)
: Diamond(group, size), CanvasMidiEvent(region, this) {}
bool on_event(GdkEvent* ev) { return CanvasMidiEvent::on_event(ev); }
};
} // namespace Gnome
} // namespace Canvas
#endif /* __gtk_ardour_canvas_hit_h__ */

View file

@ -0,0 +1,122 @@
/*
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.
*/
#include <iostream>
#include "canvas-midi-event.h"
#include "midi_region_view.h"
#include "public_editor.h"
#include "editing_syms.h"
using namespace std;
namespace Gnome {
namespace Canvas {
CanvasMidiEvent::CanvasMidiEvent(MidiRegionView& region, Item* item)
: _region(region)
, _item(item)
, _state(None)
{
}
bool
CanvasMidiEvent::on_event(GdkEvent* ev)
{
static double last_x, last_y;
double event_x, event_y;
if (_region.get_time_axis_view().editor.current_mouse_mode() != Editing::MouseNote)
return false;
switch (ev->type) {
/*case GDK_ENTER_NOTIFY:
cerr << "ENTERED: " << ev->crossing.state << endl;
if ( (ev->crossing.state & GDK_BUTTON2_MASK) ) {
}
break;
*/
case GDK_KEY_PRESS:
cerr << "EVENT KEY PRESS\n"; // doesn't work :/
break;
case GDK_BUTTON_PRESS:
_state = Pressed;
return true;
case GDK_MOTION_NOTIFY:
event_x = ev->motion.x;
event_y = ev->motion.y;
_item->property_parent().get_value()->w2i(event_x, event_y);
switch (_state) {
case Pressed:
_item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
_state = Dragging;
last_x = event_x;
last_y = event_y;
return true;
case Dragging:
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;
}
_item->move(event_x - last_x, event_y - last_y);
last_x = event_x;
last_y = event_y;
return true;
default:
break;
}
break;
case GDK_BUTTON_RELEASE:
switch (_state) {
case Pressed: // Clicked
_state = None;
return true;
case Dragging: // Dropped
_item->ungrab(ev->button.time);
_state = None;
return true;
default:
break;
}
default:
break;
}
return false;
}
} // namespace Canvas
} // namespace Gnome

View file

@ -0,0 +1,59 @@
/*
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 __gtk_ardour_canvas_midi_event_h__
#define __gtk_ardour_canvas_midi_event_h__
#include "simplerect.h"
class Editor;
class MidiRegionView;
namespace Gnome {
namespace Canvas {
/** This manages all the event handling for any MIDI event on the canvas.
*
* This is not actually a canvas item itself to avoid the dreaded diamond,
* since various types of canvas items (Note (rect), Hit (diamond), etc)
* need to share this functionality but can't share an ancestor.
*
* Note: Because of this, derived classes need to manually bounce events to
* on_event, it won't happen automatically.
*/
class CanvasMidiEvent {
public:
CanvasMidiEvent(MidiRegionView& region, Item* item);
virtual ~CanvasMidiEvent() {}
virtual bool on_event(GdkEvent* ev);
private:
enum State { None, Pressed, Dragging };
MidiRegionView& _region;
Item* const _item;
State _state;
};
} // namespace Gnome
} // namespace Canvas
#endif /* __gtk_ardour_canvas_midi_event_h__ */

43
gtk2_ardour/canvas-note.h Normal file
View file

@ -0,0 +1,43 @@
/*
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 __gtk_ardour_canvas_note_h__
#define __gtk_ardour_canvas_note_h__
#include <iostream>
#include "simplerect.h"
#include "canvas-midi-event.h"
namespace Gnome {
namespace Canvas {
class CanvasNote : public SimpleRect, public CanvasMidiEvent {
public:
CanvasNote(MidiRegionView& region, Group& group)
: SimpleRect(group), CanvasMidiEvent(region, this)
{
}
bool on_event(GdkEvent* ev) { return CanvasMidiEvent::on_event(ev); }
};
} // namespace Gnome
} // namespace Canvas
#endif /* __gtk_ardour_canvas_note_h__ */

View file

@ -21,14 +21,15 @@
#define __ardour_diamond_h__ #define __ardour_diamond_h__
#include <libgnomecanvasmm/polygon.h> #include <libgnomecanvasmm/polygon.h>
#include "canvas-midi-event.h"
namespace Gnome { namespace Gnome {
namespace Canvas { namespace Canvas {
class Diamond : public Gnome::Canvas::Polygon { class Diamond : public Polygon {
public: public:
Diamond(Gnome::Canvas::Group& group, double height); Diamond(Group& group, double height);
}; };

View file

@ -41,7 +41,7 @@
#include "midi_time_axis.h" #include "midi_time_axis.h"
#include "simplerect.h" #include "simplerect.h"
#include "simpleline.h" #include "simpleline.h"
#include "diamond.h" #include "canvas-hit.h"
#include "public_editor.h" #include "public_editor.h"
#include "ghostregion.h" #include "ghostregion.h"
#include "midi_time_axis.h" #include "midi_time_axis.h"
@ -108,56 +108,124 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
bool bool
MidiRegionView::canvas_event(GdkEvent* ev) MidiRegionView::canvas_event(GdkEvent* ev)
{ {
if (trackview.editor.current_mouse_mode() == MouseNote) { enum State { None, Pressed, Dragging };
if (ev->type == GDK_BUTTON_PRESS) { static State _state;
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(); static double last_x, last_y;
assert(note >= 0.0); double event_x, event_y;
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();
MidiModel* model = midi_region()->midi_source(0)->model();
// Add a 1 beat long note (for now)
const MidiModel::Note new_note(stamp, dur, (uint8_t)note, 0x40);
model->begin_command();
model->add_note(new_note);
model->finish_command();
view->update_bounds(new_note.note());
add_note(new_note);
}
}
if (trackview.editor.current_mouse_mode() != MouseNote)
return false;
switch (ev->type) {
case GDK_BUTTON_PRESS:
//group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, ev->button.time);
// This should happen on release...
if (ev->button.button == 1)
create_note_at(ev->button.x, ev->button.y);
_state = Pressed;
return true;
case GDK_MOTION_NOTIFY:
cerr << "MOTION\n";
event_x = ev->motion.x;
event_y = ev->motion.y;
group->w2i(event_x, event_y);
switch (_state) {
case Pressed:
cerr << "SELECT DRAG START\n";
//group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
// Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
_state = Dragging;
last_x = event_x;
last_y = event_y;
return true;
case Dragging:
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;
}
cerr << "SELECT DRAG" << endl;
//move(event_x - last_x, event_y - last_y);
last_x = event_x;
last_y = event_y;
return true;
default:
break;
}
break;
case GDK_BUTTON_RELEASE:
cerr << "RELEASE\n";
//group->ungrab(ev->button.time);
switch (_state) {
case Pressed:
cerr << "CLICK\n";
//create_note_at(ev->button.x, ev->button.y);
_state = None;
return true;
case Dragging:
cerr << "SELECT RECT DONE\n";
_state = None;
return true;
default:
break;
}
default:
break;
}
return false; return false;
} }
bool /** Add a note to the model, and the view, at a canvas (click) coordinate */
MidiRegionView::note_canvas_event(GdkEvent* ev) void
MidiRegionView::create_note_at(double x, double y)
{ {
cerr << "NOTE CANVAS EVENT" << endl; MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
MidiStreamView* const view = mtv->midi_view();
return true; 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;
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();
MidiModel* model = midi_region()->midi_source(0)->model();
// Add a 1 beat long note (for now)
const MidiModel::Note new_note(stamp, dur, (uint8_t)note, 0x40);
model->begin_command();
model->add_note(new_note);
model->finish_command();
view->update_bounds(new_note.note());
add_note(new_note);
} }
@ -279,7 +347,7 @@ MidiRegionView::add_ghost (AutomationTimeAxisView& atv)
void void
MidiRegionView::begin_write() MidiRegionView::begin_write()
{ {
_active_notes = new ArdourCanvas::SimpleRect*[128]; _active_notes = new CanvasNote*[128];
for (unsigned i=0; i < 128; ++i) for (unsigned i=0; i < 128; ++i)
_active_notes[i] = NULL; _active_notes[i] = NULL;
} }
@ -323,7 +391,7 @@ MidiRegionView::add_event (const MidiEvent& ev)
const double y1 = trackview.height - (pixel_range * (note - view->lowest_note() + 1)) const double y1 = trackview.height - (pixel_range * (note - view->lowest_note() + 1))
- footer_height - 3.0; - footer_height - 3.0;
ArdourCanvas::SimpleRect * ev_rect = new Gnome::Canvas::SimpleRect(*group); CanvasNote* ev_rect = new CanvasNote(*this, *group);
ev_rect->property_x1() = trackview.editor.frame_to_pixel ( ev_rect->property_x1() = trackview.editor.frame_to_pixel (
(nframes_t)ev.time); (nframes_t)ev.time);
ev_rect->property_y1() = y1; ev_rect->property_y1() = y1;
@ -335,8 +403,6 @@ MidiRegionView::add_event (const MidiEvent& ev)
ev_rect->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8); ev_rect->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
ev_rect->property_fill_color_rgba() = 0xFFFFFF66; ev_rect->property_fill_color_rgba() = 0xFFFFFF66;
ev_rect->signal_event().connect(sigc::mem_fun(this, &MidiRegionView::note_canvas_event));
ev_rect->raise_to_top(); ev_rect->raise_to_top();
_events.push_back(ev_rect); _events.push_back(ev_rect);
@ -358,14 +424,12 @@ MidiRegionView::add_event (const MidiEvent& ev)
const double y = trackview.height - (pixel_range * (note - view->lowest_note() + 1)) const double y = trackview.height - (pixel_range * (note - view->lowest_note() + 1))
- footer_height - 3.0; - footer_height - 3.0;
Diamond* ev_diamond = new Diamond(*group, std::min(pixel_range, 5.0)); CanvasHit* ev_diamond = new CanvasHit(*this, *group, std::min(pixel_range, 5.0));
ev_diamond->move(x, y); ev_diamond->move(x, y);
ev_diamond->show(); ev_diamond->show();
ev_diamond->property_outline_color_rgba() = 0xFFFFFFDD; ev_diamond->property_outline_color_rgba() = 0xFFFFFFDD;
ev_diamond->property_fill_color_rgba() = 0xFFFFFF66; ev_diamond->property_fill_color_rgba() = 0xFFFFFF66;
ev_diamond->signal_event().connect(sigc::mem_fun(this, &MidiRegionView::note_canvas_event));
_events.push_back(ev_diamond); _events.push_back(ev_diamond);
} }
} }
@ -385,7 +449,7 @@ MidiRegionView::extend_active_notes()
} }
/** Add a MIDI note (with duration). /** Add a MIDI note to the view (with duration).
* *
* This does no 'realtime' note resolution, notes from a MidiModel have a * This does no 'realtime' note resolution, notes from a MidiModel have a
* duration so they can be drawn in full immediately. * duration so they can be drawn in full immediately.
@ -419,7 +483,7 @@ MidiRegionView::add_note (const MidiModel::Note& note)
const double y1 = trackview.height - (pixel_range * (note.note() - view->lowest_note() + 1)) const double y1 = trackview.height - (pixel_range * (note.note() - view->lowest_note() + 1))
- footer_height - 3.0; - footer_height - 3.0;
ArdourCanvas::SimpleRect * ev_rect = new Gnome::Canvas::SimpleRect(*group); ArdourCanvas::SimpleRect * ev_rect = new CanvasNote(*this, *group);
ev_rect->property_x1() = trackview.editor.frame_to_pixel((nframes_t)note.time()); ev_rect->property_x1() = trackview.editor.frame_to_pixel((nframes_t)note.time());
ev_rect->property_y1() = y1; ev_rect->property_y1() = y1;
ev_rect->property_x2() = trackview.editor.frame_to_pixel((nframes_t)(note.end_time())); ev_rect->property_x2() = trackview.editor.frame_to_pixel((nframes_t)(note.end_time()));
@ -437,7 +501,7 @@ MidiRegionView::add_note (const MidiModel::Note& note)
const double y = trackview.height - (pixel_range * (note.note() - view->lowest_note() + 1)) const double y = trackview.height - (pixel_range * (note.note() - view->lowest_note() + 1))
- footer_height - 3.0; - footer_height - 3.0;
Diamond* ev_diamond = new Diamond(*group, std::min(pixel_range, 5.0)); CanvasHit* ev_diamond = new CanvasHit(*this, *group, std::min(pixel_range, 5.0));
ev_diamond->move(x, y); ev_diamond->move(x, y);
ev_diamond->show(); ev_diamond->show();
ev_diamond->property_fill_color_rgba() = fill; ev_diamond->property_fill_color_rgba() = fill;

View file

@ -33,6 +33,7 @@
#include "automation_line.h" #include "automation_line.h"
#include "enums.h" #include "enums.h"
#include "canvas.h" #include "canvas.h"
#include "canvas-note.h"
namespace ARDOUR { namespace ARDOUR {
class MidiRegion; class MidiRegion;
@ -70,6 +71,8 @@ class MidiRegionView : public RegionView
void end_write(); void end_write();
void extend_active_notes(); void extend_active_notes();
void create_note_at(double x, double y);
protected: protected:
/* this constructor allows derived types /* this constructor allows derived types
@ -101,7 +104,7 @@ class MidiRegionView : public RegionView
bool note_canvas_event(GdkEvent* ev); bool note_canvas_event(GdkEvent* ev);
std::vector<ArdourCanvas::Item*> _events; std::vector<ArdourCanvas::Item*> _events;
ArdourCanvas::SimpleRect** _active_notes; ArdourCanvas::CanvasNote** _active_notes;
}; };
#endif /* __gtk_ardour_midi_region_view_h__ */ #endif /* __gtk_ardour_midi_region_view_h__ */

View file

@ -827,18 +827,6 @@ MidiDiskstream::do_refill ()
return 0; return 0;
} }
/* if there are 2+ chunks of disk i/o possible for
this track, let the caller know so that it can arrange
for us to be called again, ASAP.
*/
// FIXME: using disk_io_chunk_frames as an event count, not good
// count vs duration semantic differences are nonexistant for audio,
// which makes translating for MIDI code confusing...
if (_playback_buf->write_space() >= (_slaved?3:2) * disk_io_chunk_frames) {
ret = 1;
}
/* if we're running close to normal speed and there isn't enough /* if we're running close to normal speed and there isn't enough
space to do disk_io_chunk_frames of I/O, then don't bother. space to do disk_io_chunk_frames of I/O, then don't bother.
@ -847,7 +835,7 @@ MidiDiskstream::do_refill ()
*/ */
if ((write_space < disk_io_chunk_frames) && fabs (_actual_speed) < 2.0f) { if ((write_space < disk_io_chunk_frames) && fabs (_actual_speed) < 2.0f) {
cerr << "No refill 1\n"; //cerr << "No refill 1\n";
return 0; return 0;
} }
@ -857,12 +845,12 @@ MidiDiskstream::do_refill ()
*/ */
if (_slaved && write_space < (_playback_buf->capacity() / 2)) { if (_slaved && write_space < (_playback_buf->capacity() / 2)) {
cerr << "No refill 2\n"; //cerr << "No refill 2\n";
return 0; return 0;
} }
if (reversed) { if (reversed) {
cerr << "No refill 3 (reverse)\n"; //cerr << "No refill 3 (reverse)\n";
return 0; return 0;
} }
@ -874,26 +862,10 @@ MidiDiskstream::do_refill ()
return 0; return 0;
} }
#if 0 // At this point we...
// or this
if (file_frame > max_frames - total_space) {
/* to close to the end: read what we can, and zero fill the rest */
zero_fill = total_space - (max_frames - file_frame);
total_space = max_frames - file_frame;
} else {
zero_fill = 0;
}
#endif
// At this point we:
assert(_playback_buf->write_space() > 0); // ... have something to write to, and assert(_playback_buf->write_space() > 0); // ... have something to write to, and
assert(file_frame <= max_frames); // ... something to write assert(file_frame <= max_frames); // ... something to write
// So (read it, then) write it:
nframes_t file_frame_tmp = file_frame; nframes_t file_frame_tmp = file_frame;
nframes_t to_read = min(disk_io_chunk_frames, (max_frames - file_frame)); nframes_t to_read = min(disk_io_chunk_frames, (max_frames - file_frame));

View file

@ -86,7 +86,7 @@ MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframe
{ {
size_t read_events = 0; size_t read_events = 0;
//cerr << "MM READ " << start << " .. " << nframes << endl; cerr << "MM READ @ " << start << " + " << nframes << endl;
/* FIXME: cache last lookup value to avoid the search */ /* FIXME: cache last lookup value to avoid the search */
@ -140,6 +140,9 @@ MidiModel::read (MidiRingBuffer& dst, nframes_t start, nframes_t nframes, nframe
} }
} }
if (read_events > 0)
cerr << "MM READ " << read_events << " EVENTS" << endl;
return read_events; return read_events;
} }