_channel_selector->channel_selected.connect(
sigc::mem_fun(this, &CanvasNoteEvent::on_channel_change));
- _channel_selector_widget =
- new Widget(*(_item->property_parent()),
- x1(),
- y2() + 2,
- (Gtk::Widget &) *_channel_selector);
+ _channel_selector_widget = new Widget(*(_item->property_parent()),
+ x1(),
+ y2() + 2,
+ (Gtk::Widget &) *_channel_selector);
_channel_selector_widget->hide();
_channel_selector_widget->property_height() = 100;
ColorMode mode = _region.color_mode();
- const uint8_t minimal_opaqueness = 15;
- uint8_t opaqueness = std::max(minimal_opaqueness, uint8_t(_note->velocity() + _note->velocity()));
+ const uint8_t min_opacity = 15;
+ uint8_t opacity = std::max(min_opacity, uint8_t(_note->velocity() + _note->velocity()));
switch (mode) {
case TrackColor:
SCALE_USHORT_TO_UINT8_T(color.get_red()),
SCALE_USHORT_TO_UINT8_T(color.get_green()),
SCALE_USHORT_TO_UINT8_T(color.get_blue()),
- opaqueness);
+ opacity);
}
case ChannelColors:
return UINT_RGBA_CHANGE_A(CanvasNoteEvent::midi_channel_colors[_note->channel()],
- opaqueness);
+ opacity);
default:
return meter_style_fill_color(_note->velocity());
double event_x, event_y, dx, dy;
bool select_mod;
uint8_t d_velocity = 10;
-
- if (_region.get_time_axis_view().editor().current_mouse_mode() != Editing::MouseNote)
+
+ if (_region.get_time_axis_view().editor().current_mouse_mode() != Editing::MouseNote) {
return false;
+ }
+
+ const Editing::MidiEditMode midi_edit_mode
+ = _region.midi_view()->editor().current_midi_edit_mode();
switch (ev->type) {
case GDK_SCROLL:
switch (_state) {
case Pressed: // Drag begin
- if (_region.midi_view()->editor().current_midi_edit_mode() == Editing::MidiEditSelect
+ if (midi_edit_mode == Editing::MidiEditSelect
&& _region.mouse_state() != MidiRegionView::SelectTouchDragging
&& _region.mouse_state() != MidiRegionView::EraseTouchDragging) {
_item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
}
_item->property_parent().get_value()->w2i(event_x, event_y);
- // Snap
event_x = _region.snap_to_pixel(event_x);
- dx = event_x - last_x;
- dy = event_y - last_y;
-
+ dx = event_x - last_x;
+ dy = event_y - last_y;
last_x = event_x;
drag_delta_x += dx;
switch (_state) {
case Pressed: // Clicked
- if (_region.midi_view()->editor().current_midi_edit_mode() == Editing::MidiEditSelect) {
+ if (midi_edit_mode == Editing::MidiEditSelect) {
_state = None;
-
- if (_selected && !select_mod && _region.selection_size() > 1)
+ if (_selected && !select_mod && _region.selection_size() > 1) {
_region.unique_select(this);
- else if (_selected)
+ } else if (_selected) {
_region.note_deselected(this, select_mod);
- else
+ } else {
_region.note_selected(this, select_mod);
- } else if (_region.midi_view()->editor().current_midi_edit_mode() == Editing::MidiEditErase) {
+ }
+ } else if (midi_edit_mode == Editing::MidiEditErase) {
_region.start_delta_command();
_region.command_remove_note(this);
_region.apply_command();
case Dragging: // Dropped
_item->ungrab(ev->button.time);
_state = None;
-
- if (_note)
- _region.note_dropped(this,
- _region.midi_view()->editor().pixel_to_frame(abs(drag_delta_x))
- * ((drag_delta_x < 0.0) ? -1 : 1),
- drag_delta_note);
+ if (_note) {
+ _region.note_dropped(this, drag_delta_x, drag_delta_note);
+ }
return true;
default:
break;
using namespace std;
CanvasProgramChange::CanvasProgramChange(
- MidiRegionView& region,
- Group& parent,
- string& text,
- double height,
- double x,
- double y,
- string& model_name,
- string& custom_device_mode,
- nframes_t event_time,
- uint8_t channel,
- uint8_t program
- )
+ MidiRegionView& region,
+ Group& parent,
+ string& text,
+ double height,
+ double x,
+ double y,
+ string& model_name,
+ string& custom_device_mode,
+ nframes_t event_time,
+ uint8_t channel,
+ uint8_t program)
: CanvasFlag(
region,
parent,
ARDOUR_UI::config()->canvasvar_MidiProgramChangeOutline.get(),
ARDOUR_UI::config()->canvasvar_MidiProgramChangeFill.get(),
x,
- y
- )
+ y)
, _model_name(model_name)
, _custom_device_mode(custom_device_mode)
, _event_time(event_time)
ruler_items.push_back (MenuElem (*action->create_menu_item()));
}
- editor_ruler_menu->popup (1, gtk_get_current_event_time());
+ editor_ruler_menu->popup (1, gtk_get_current_event_time());
no_ruler_shown_update = false;
}
using namespace Editing;
using namespace ArdourCanvas;
-MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color)
+MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
+ boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color)
: RegionView (parent, tv, r, spu, basic_color)
, _force_channel(-1)
, _last_channel_selection(0xFFFF)
- , _default_note_length(0.0)
+ , _default_note_length(1.0)
, _current_range_min(0)
, _current_range_max(0)
, _model_name(string())
_note_group->raise_to_top();
}
-MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color, TimeAxisViewItem::Visibility visibility)
+MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv,
+ boost::shared_ptr<MidiRegion> r, double spu, Gdk::Color& basic_color,
+ TimeAxisViewItem::Visibility visibility)
: RegionView (parent, tv, r, spu, basic_color, false, visibility)
, _force_channel(-1)
, _last_channel_selection(0xFFFF)
- , _default_note_length(0.0)
+ , _default_note_length(1.0)
, _model_name(string())
, _custom_device_mode(string())
, _active_notes(0)
: RegionView (other)
, _force_channel(-1)
, _last_channel_selection(0xFFFF)
- , _default_note_length(0.0)
+ , _default_note_length(1.0)
, _model_name(string())
, _custom_device_mode(string())
, _active_notes(0)
init (c, false);
}
-MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> other_region)
- : RegionView (other, boost::shared_ptr<Region> (other_region))
+MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
+ : RegionView (other, boost::shared_ptr<Region> (region))
, _force_channel(-1)
, _last_channel_selection(0xFFFF)
- , _default_note_length(0.0)
+ , _default_note_length(1.0)
, _model_name(string())
, _custom_device_mode(string())
, _active_notes(0)
midi_region()->midi_source(0)->load_model();
}
- const Meter& m = trackview.session().tempo_map().meter_at(_region->position());
- const Tempo& t = trackview.session().tempo_map().tempo_at(_region->position());
- _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;
if (trackview.editor().current_mouse_mode() != MouseNote)
return false;
-
- // Mmmm, spaghetti
+
+ const Editing::MidiEditMode midi_edit_mode = trackview.editor().current_midi_edit_mode();
switch (ev->type) {
case GDK_KEY_PRESS:
if (ev->key.keyval == GDK_Delete && !delete_mod) {
delete_mod = true;
- original_mode = trackview.editor().current_midi_edit_mode();
+ original_mode = midi_edit_mode;
trackview.editor().set_midi_edit_mode(MidiEditErase);
start_delta_command(_("erase notes"));
_mouse_state = EraseTouchDragging;
case Pressed: // Drag start
// Select drag start
- if (_pressed_button == 1 && trackview.editor().current_midi_edit_mode() == MidiEditSelect) {
+ if (_pressed_button == 1 && midi_edit_mode == MidiEditSelect) {
group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
last_x = event_x;
return true;
// Add note drag start
- } else if (trackview.editor().current_midi_edit_mode() == MidiEditPencil) {
+ } else if (midi_edit_mode == MidiEditPencil) {
group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
last_x = event_x;
drag_rect = new ArdourCanvas::SimpleRect(*group);
drag_rect->property_x1() = trackview.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_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_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;
+ drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
_mouse_state = AddDragging;
return true;
switch (_mouse_state) {
case Pressed: // Clicked
- switch (trackview.editor().current_midi_edit_mode()) {
+ switch (midi_edit_mode) {
case MidiEditSelect:
case MidiEditResize:
clear_selection();
const double length = trackview.editor().pixel_to_frame(
drag_rect->property_x2() - drag_rect->property_x1());
- create_note_at(x, drag_rect->property_y1(), length);
+ create_note_at(x, drag_rect->property_y1(), frames_to_beats(length));
}
delete drag_rect;
}
-/** Add a note to the model, and the view, at a canvas (click) coordinate */
+/** Add a note to the model, and the view, at a canvas (click) coordinate.
+ * \param x horizontal position in pixels
+ * \param y vertical position in pixels
+ * \param length duration of the note in beats */
void
MidiRegionView::create_note_at(double x, double y, double length)
{
assert(note >= 0.0);
assert(note <= 127.0);
- nframes64_t new_note_time = trackview.editor().pixel_to_frame (x);
- assert(new_note_time >= 0);
- new_note_time += _region->start();
+ // Start of note in frames relative to region start
+ nframes64_t start_frames = snap_to_frame(trackview.editor().pixel_to_frame(x));
+ assert(start_frames >= 0);
- /*
- const Meter& m = trackview.session().tempo_map().meter_at(new_note_time);
- const Tempo& t = trackview.session().tempo_map().tempo_at(new_note_time);
- double length = m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar();
- */
-
- // we need to snap here again in nframes64_t in order to be sample accurate
- // since note time is region-absolute but snap_to_frame expects position-relative
- // time we have to coordinate transform back and forth here.
- nframes64_t new_note_time_position_relative = new_note_time - _region->start();
- new_note_time = snap_to_frame(new_note_time_position_relative) + _region->start();
-
- // we need to snap the length too to be sample accurate
- nframes64_t new_note_length = nframes_t(length);
- new_note_length = snap_to_frame(new_note_time_position_relative + new_note_length) + _region->start()
- - new_note_time;
+ // Snap length
+ length = frames_to_beats(
+ snap_to_frame(start_frames + beats_to_frames(length)) - start_frames);
+
+ const boost::shared_ptr<NoteType> new_note(new NoteType(0,
+ frames_to_beats(start_frames + _region->start()), length,
+ (uint8_t)note, 0x40));
- const boost::shared_ptr<NoteType> new_note(new NoteType(
- 0, new_note_time, new_note_length, (uint8_t)note, 0x40));
view->update_note_range(new_note->note());
MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
}
}
- for (Events::iterator i = _events.begin(); i != _events.end(); ++i)
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
delete *i;
+ }
_events.clear();
_pgm_changes.clear();
{
_model = model;
- if (_enable_display)
+ if (_enable_display) {
redisplay_model();
+ }
}
void
MidiRegionView::start_delta_command(string name)
{
- if (!_delta_command)
+ if (!_delta_command) {
_delta_command = _model->new_delta_command(name);
+ }
}
void
MidiRegionView::command_add_note(const boost::shared_ptr<NoteType> note, bool selected)
{
- if (_delta_command)
+ if (_delta_command) {
_delta_command->add(note);
-
- if (selected)
+ }
+ if (selected) {
_marked_for_selection.insert(note);
+ }
}
void
return;
if (_model) {
-
clear_events();
_model->read_lock();
-
MidiModel::Notes notes = _model->notes();
/*
- cerr << endl << _model->midi_source()->name() << " : redisplaying " << notes.size() << " notes:" << endl;
+ cerr << _model->midi_source()->name() << " : redisplaying " << notes.size() << endl;
for (MidiModel::Notes::iterator i = notes.begin(); i != notes.end(); ++i) {
cerr << "NOTE time: " << (*i)->time()
<< " pitch: " << int((*i)->note())
add_note(_model->note_at(i));
}
- find_and_insert_program_change_flags();
+ display_program_change_flags();
// Is this necessary?
/*for (Automatable::Controls::const_iterator i = _model->controls().begin();
}
void
-MidiRegionView::find_and_insert_program_change_flags()
+MidiRegionView::display_program_change_flags()
{
- // Draw program change 'flags'
for (Automatable::Controls::iterator control = _model->controls().begin();
control != _model->controls().end(); ++control) {
if (control->first.type() == MidiPgmChangeAutomation) {
Glib::Mutex::Lock list_lock (control->second->list()->lock());
- uint8_t channel = control->first.channel();
+ uint8_t channel = control->first.channel();
for (AutomationList::const_iterator event = control->second->list()->begin();
event != control->second->list()->end(); ++event) {
double event_time = (*event)->when;
double program_number = floor((*event)->value + 0.5);
- //cerr << " got program change on channel " << int(channel) << " time: " << event_time << " number: " << program_number << endl;
+ //cerr << " got program change on channel " << int(channel)
+ // << " time: " << event_time << " number: " << program_number << endl;
// find bank select msb and lsb for the program change
Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5));
}
- //cerr << " got msb " << int(msb) << " and lsb " << int(lsb) << " thread_id: " << pthread_self() << endl;
+ //cerr << " got msb " << int(msb) << " and lsb " << int(lsb)
+ // << " thread_id: " << pthread_self() << endl;
MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
_model_name,
_custom_device_mode,
channel,
- patch_key
- );
+ patch_key);
ControlEvent program_change(nframes_t(event_time), uint8_t(program_number), channel);
if (patch != 0) {
- //cerr << " got patch with name " << patch->name() << " number " << patch->number() << endl;
+ //cerr << " got patch with name " << patch->name()
+ // << " number " << patch->number() << endl;
add_pgm_change(program_change, patch->name());
} else {
char buf[4];
}
break;
} else if (control->first.type() == MidiCCAutomation) {
- //cerr << " found CC Automation of channel " << int(control->first.channel()) << " and id " << control->first.id() << endl;
+ //cerr << " found CC Automation of channel " << int(control->first.channel())
+ // << " and id " << control->first.id() << endl;
}
}
}
RegionView::region_resized(what_changed);
if (what_changed & ARDOUR::PositionChanged) {
- if (_enable_display)
+ if (_enable_display) {
redisplay_model();
+ }
}
}
RegionView::reset_width_dependent_items(pixel_width);
assert(_pixel_width == pixel_width);
- if (_enable_display)
+ if (_enable_display) {
redisplay_model();
+ }
}
void
-MidiRegionView::set_height (gdouble height)
+MidiRegionView::set_height (double height)
{
- static const double FUDGE = 2;
+ static const double FUDGE = 2.0;
const double old_height = _height;
RegionView::set_height(height);
_height = height - FUDGE;
audio waveforms under it.
*/
ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
- }
- else {
+ } else {
ghost = new MidiGhostRegion (tv, trackview, unit_position);
}
void
MidiRegionView::resolve_note(uint8_t note, double end_time)
{
- if (midi_view()->note_mode() != Sustained)
+ if (midi_view()->note_mode() != Sustained) {
return;
+ }
if (_active_notes && _active_notes[note]) {
_active_notes[note]->property_x2() = trackview.editor().frame_to_pixel((nframes64_t)end_time);
RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
assert(route_ui);
- route_ui->midi_track()->write_immediate_event(note->on_event().size(), note->on_event().buffer());
+ route_ui->midi_track()->write_immediate_event(
+ note->on_event().size(), note->on_event().buffer());
- nframes_t note_length_ms = (note->off_event().time() - note->on_event().time())
+ const double note_length_beats = (note->off_event().time() - note->on_event().time());
+ nframes_t note_length_ms = beats_to_frames(note_length_beats)
* (1000 / (double)route_ui->session().nominal_frame_rate());
Glib::signal_timeout().connect(bind(mem_fun(this, &MidiRegionView::play_midi_note_off), note),
note_length_ms, G_PRIORITY_DEFAULT);
RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
assert(route_ui);
- route_ui->midi_track()->write_immediate_event(note->off_event().size(), note->off_event().buffer());
+ route_ui->midi_track()->write_immediate_event(
+ note->off_event().size(), note->off_event().buffer());
return false;
}
assert(note->time() >= 0);
assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
+ const nframes64_t note_start_frames = beats_to_frames(note->time());
+ const nframes64_t note_end_frames = beats_to_frames(note->end_time());
+
// dont display notes beyond the region bounds
- if ( note->time() - _region->start() >= _region->length() ||
- note->time() < _region->start() ||
- note->note() < midi_stream_view()->lowest_note() ||
- note->note() > midi_stream_view()->highest_note() ) {
+ if (note_start_frames - _region->start() >= _region->length() ||
+ note_start_frames < _region->start() ||
+ note->note() < midi_stream_view()->lowest_note() ||
+ note->note() > midi_stream_view()->highest_note() ) {
return;
}
CanvasNoteEvent* event = 0;
- const double x = trackview.editor().frame_to_pixel((nframes64_t)note->time() - _region->start());
+ const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
if (midi_view()->note_mode() == Sustained) {
-
const double y1 = midi_stream_view()->note_to_y(note->note());
const double note_endpixel =
- trackview.editor().frame_to_pixel((nframes64_t)note->end_time() - _region->start());
+ trackview.editor().frame_to_pixel(note_end_frames - _region->start());
CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
ev_rect->property_x1() = x;
}
} else if (midi_view()->note_mode() == Percussive) {
-
- //cerr << "MRV::add_note percussive " << note->note() << " @ " << note->time()
- // << " .. " << note->end_time() << endl;
-
const double diamond_size = midi_stream_view()->note_height() / 2.0;
const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
void
MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
{
-
cerr << "getting patch key at " << time << " for channel " << channel << endl;
Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
void
MidiRegionView::alter_program_change(ControlEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch)
{
-
// TODO: Get the real event here and alter them at the original times
Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK);
boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
_model_name,
_custom_device_mode,
program.channel(),
- key
- );
+ key);
ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
if (patch) {
_model_name,
_custom_device_mode,
program.channel(),
- key
- );
+ key);
ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
if (patch) {
alter_program_change(program_change_event, patch->patch_primary_key());
const boost::shared_ptr<NoteType> copy(new NoteType(*(*i)->note().get()));
// we need to snap here again in nframes64_t in order to be sample accurate
- double new_note_time = (*i)->note()->time();
- new_note_time += dt;
+ double start_frames = snap_to_frame(beats_to_frames((*i)->note()->time()) + dt);
// keep notes inside region if dragged beyond left region bound
- if (new_note_time < _region->start()) {
- new_note_time = _region->start();
+ if (start_frames < _region->start()) {
+ start_frames = _region->start();
}
- // since note time is region-absolute but snap_to_frame expects position-relative
- // time we have to coordinate transform back and forth here.
- new_note_time = snap_to_frame(nframes64_t(new_note_time) - _region->start()) + _region->start();
-
- copy->set_time(new_note_time);
+ copy->set_time(frames_to_beats(start_frames));
uint8_t original_pitch = (*i)->note()->note();
- uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
+ uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
// keep notes in standard midi range
clamp_0_to_127(new_pitch);
- //notes which are dragged beyond the standard midi range snap back to their original place
- if ((original_pitch != 0 && new_pitch == 0) || (original_pitch != 127 && new_pitch == 127)) {
+ // keep original pitch if note is dragged outside valid midi range
+ if ((original_pitch != 0 && new_pitch == 0)
+ || (original_pitch != 127 && new_pitch == 127)) {
new_pitch = original_pitch;
}
MidiRegionView::snap_to_frame(double x)
{
PublicEditor &editor = trackview.editor();
- // x is region relative
- // convert x to global frame
+ // x is region relative, convert it to global absolute frames
nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
editor.snap_to(frame);
- // convert event_frame back to local coordinates relative to position
- frame -= _region->position();
- return frame;
+ return frame - _region->position(); // convert back to region relative
}
nframes64_t
return trackview.editor().frame_to_pixel(region_frame);
}
+nframes64_t
+MidiRegionView::beats_to_frames(double beats) const
+{
+ const Meter& m = trackview.session().tempo_map().meter_at(_region->position());
+ const Tempo& t = trackview.session().tempo_map().tempo_at(_region->position());
+ return lrint(beats * m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar());
+}
+
+double
+MidiRegionView::frames_to_beats(nframes64_t frames) const
+{
+ const Meter& m = trackview.session().tempo_map().meter_at(_region->position());
+ const Tempo& t = trackview.session().tempo_map().tempo_at(_region->position());
+ return frames / m.frames_per_bar(t, trackview.session().frame_rate()) * m.beats_per_bar();
+}
+
void
MidiRegionView::begin_resizing(CanvasNote::NoteEnd note_end)
{
resize_data->canvas_note = note;
// create a new SimpleRect from the note which will be the resize preview
- SimpleRect *resize_rect =
- new SimpleRect(
- *group,
- note->x1(),
- note->y1(),
- note->x2(),
- note->y2());
+ SimpleRect *resize_rect = new SimpleRect(
+ *group, note->x1(), note->y1(), note->x2(), note->y2());
// calculate the colors: get the color settings
- uint32_t fill_color =
- UINT_RGBA_CHANGE_A(
- ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
- 128);
+ uint32_t fill_color = UINT_RGBA_CHANGE_A(
+ ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
+ 128);
// make the resize preview notes more transparent and bright
fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
// calculate color based on note velocity
- resize_rect->property_fill_color_rgba() =
- UINT_INTERPOLATE(
+ resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
fill_color,
0.85);
- resize_rect->property_outline_color_rgba() =
- CanvasNoteEvent::calculate_outline(ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
+ resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
+ ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
resize_data->resize_rect = resize_rect;
MidiRegionView::update_resizing(CanvasNote::NoteEnd note_end, double x, bool relative)
{
for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
- SimpleRect *resize_rect = (*i)->resize_rect;
- CanvasNote *canvas_note = (*i)->canvas_note;
+ SimpleRect* resize_rect = (*i)->resize_rect;
+ CanvasNote* canvas_note = (*i)->canvas_note;
const double region_start = get_position_pixels();
/** Displays all program changed events in the region as flags on the canvas.
*/
- void find_and_insert_program_change_flags();
+ void display_program_change_flags();
void begin_write();
void end_write();
void extend_active_notes();
- void create_note_at(double x, double y, double duration);
+ void create_note_at(double x, double y, double length);
void display_model(boost::shared_ptr<ARDOUR::MidiModel> model);
size_t selection_size() { return _selection.size(); }
void move_selection(double dx, double dy);
- void note_dropped(ArdourCanvas::CanvasNoteEvent* ev, double dt, uint8_t dnote);
+ void note_dropped(ArdourCanvas::CanvasNoteEvent* ev, double d_frames, uint8_t d_note);
- /** Get the region position in pixels.
- * This function is needed to subtract the region start in pixels
- * from world coordinates submitted by the mouse
- */
+ /** Get the region position in pixels relative to session. */
double get_position_pixels();
/** Begin resizing of some notes.
* Called by CanvasMidiNote when resizing starts.
* @param note_end which end of the note, NOTE_ON or NOTE_OFF
*/
- void begin_resizing(ArdourCanvas::CanvasNote::NoteEnd note_end);
+ void begin_resizing(ArdourCanvas::CanvasNote::NoteEnd note_end);
/** Update resizing notes while user drags.
* @param note_end which end of the note, NOTE_ON or NOTE_OFF
*/
void change_channel(uint8_t channel);
- enum MouseState { None, Pressed, SelectTouchDragging, SelectRectDragging, AddDragging, EraseTouchDragging };
+ enum MouseState {
+ None,
+ Pressed,
+ SelectTouchDragging,
+ SelectRectDragging,
+ AddDragging,
+ EraseTouchDragging
+ };
+
MouseState mouse_state() const { return _mouse_state; }
struct NoteResizeData {
*/
nframes64_t snap_to_frame(nframes64_t x);
+ /** Convert a timestamp in beats to frames (both relative to region start) */
+ nframes64_t beats_to_frames(double beats) const;
+
+ /** Convert a timestamp in frames to beats (both relative to region start) */
+ double frames_to_beats(nframes64_t beats) const;
+
protected:
-
/** Allows derived types to specify their visibility requirements
* to the TimeAxisViewItem parent class.
*/
* (scheduled by @ref play_midi_note()).
*/
bool play_midi_note_off(boost::shared_ptr<NoteType> note);
-
+
void clear_events();
void switch_source(boost::shared_ptr<ARDOUR::Source> src);
void midi_channel_mode_changed(ARDOUR::ChannelMode mode, uint16_t mask);
void midi_patch_settings_changed(std::string model, std::string custom_device_mode);
- void change_note_velocity(ArdourCanvas::CanvasNoteEvent* ev, int8_t velocity, bool relative=false);
+ void change_note_velocity(ArdourCanvas::CanvasNoteEvent* ev, int8_t vel, bool relative=false);
void clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev);
void clear_selection() { clear_selection_except(NULL); }
virtual uint32_t n_channels () const { return 1; }
// FIXME: integrate this with the Readable::read interface somehow
- virtual nframes_t midi_read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset, nframes_t negative_stamp_offset) const;
+ virtual nframes_t midi_read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_t cnt,
+ nframes_t stamp_offset, nframes_t negative_stamp_offset) const;
virtual nframes_t midi_write (MidiRingBuffer<nframes_t>& src, nframes_t cnt);
- virtual void append_event_unlocked(EventTimeUnit unit, const Evoral::Event<TimeType>& ev) = 0;
+ virtual void append_event_unlocked_beats(const Evoral::Event<double>& ev) = 0;
+ virtual void append_event_unlocked_frames(const Evoral::Event<nframes_t>& ev) = 0;
virtual void mark_for_remove() = 0;
virtual void mark_streaming_midi_write_started (NoteMode mode, nframes_t start_time);
boost::shared_ptr<MidiModel> model() { return _model; }
void set_model(boost::shared_ptr<MidiModel> m) { _model = m; }
+ void drop_model() { _model.reset(); }
protected:
virtual void flush_midi() = 0;
void set_allow_remove_if_empty (bool yn);
void mark_for_remove();
-
- void append_event_unlocked(EventTimeUnit unit, const Evoral::Event<double>& ev);
+
+ void append_event_unlocked_beats(const Evoral::Event<double>& ev);
+ void append_event_unlocked_frames(const Evoral::Event<nframes_t>& ev);
int move_to_trash (const string trash_dir_name);
void load_model(bool lock=true, bool force_reload=false);
void destroy_model();
- double last_event_time() const { return _last_ev_time; }
-
void flush_midi();
private:
Flag _flags;
string _take_id;
bool _allow_remove_if_empty;
- double _last_ev_time;
+ double _last_ev_time_beats;
+ nframes_t _last_ev_time_frames;
static string _search_path;
};
TrackColor
};
- enum EventTimeUnit {
- Frames,
- Beats
- };
-
struct BBT_Time {
uint32_t bars;
uint32_t beats;
Glib::StaticPrivate<SizedSampleBuffer> thread_interleave_buffer = GLIBMM_STATIC_PRIVATE_INIT;
+/** Constructor used for existing internal-to-session files. File must exist. */
AudioFileSource::AudioFileSource (Session& s, ustring path, Flag flags)
: AudioSource (s, path), _flags (flags),
_channel (0)
{
- /* constructor used for existing external to session files. file must exist already */
_is_embedded = AudioFileSource::determine_embeddedness (path);
if (init (path, true)) {
}
+/** Constructor used for new internal-to-session files. File cannot exist. */
AudioFileSource::AudioFileSource (Session& s, ustring path, Flag flags, SampleFormat samp_format, HeaderFormat hdr_format)
: AudioSource (s, path), _flags (flags),
_channel (0)
{
- /* constructor used for new internal-to-session files. file cannot exist */
_is_embedded = false;
if (init (path, false)) {
}
}
+/** Constructor used for existing internal-to-session files. File must exist. */
AudioFileSource::AudioFileSource (Session& s, const XMLNode& node, bool must_exist)
: AudioSource (s, node), _flags (Flag (Writable|CanRename))
- /* _channel is set in set_state() or init() */
+ /* _channel is set in set_state() or init() */
{
- /* constructor used for existing internal-to-session files. file must exist */
-
if (set_state (node)) {
throw failed_constructor ();
}
return -1;
}
- ustring newpath;
-
if (!writable()) {
return -1;
}
- /* don't move the file across filesystems, just
- stick it in the `trash_dir_name' directory
- on whichever filesystem it was already on.
+ /* don't move the file across filesystems, just stick it in the
+ trash_dir_name directory on whichever filesystem it was already on
*/
+ ustring newpath;
newpath = Glib::path_get_dirname (_path);
newpath = Glib::path_get_dirname (newpath);
- cerr << "from " << _path << " dead dir looks like " << newpath << endl;
-
- newpath += '/';
- newpath += trash_dir_name;
- newpath += '/';
+ newpath += string("/") + trash_dir_name + "/";
newpath += Glib::path_get_basename (_path);
+ /* the new path already exists, try versioning */
if (access (newpath.c_str(), F_OK) == 0) {
-
- /* the new path already exists, try versioning */
-
char buf[PATH_MAX+1];
int version = 1;
ustring newpath_v;
}
if (version == 999) {
- error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
- newpath)
- << endmsg;
+ PBD::error << string_compose (
+ _("there are already 1000 files with names like %1; versioning discontinued"),
+ newpath)
+ << endmsg;
} else {
newpath = newpath_v;
}
-
- } else {
-
- /* it doesn't exist, or we can't read it or something */
-
}
if (::rename (_path.c_str(), newpath.c_str()) != 0) {
- error << string_compose (_("cannot rename audio file source from %1 to %2 (%3)"),
- _path, newpath, strerror (errno))
- << endmsg;
+ PBD::error << string_compose (
+ _("cannot rename midi file source from %1 to %2 (%3)"),
+ _path, newpath, strerror (errno)) << endmsg;
return -1;
}
peakpath = "";
/* file can not be removed twice, since the operation is not idempotent */
-
_flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
return 0;
cnt = 0;
for (vector<ustring>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
-
fullpath = *i;
if (fullpath[fullpath.length()-1] != '/') {
fullpath += '/';
}
/* Current find() is unable to parse relative path names to yet non-existant
- sources. QuickFix(tm) */
- if (keeppath == "") {
- if (must_exist) {
- error << "AudioFileSource::find(), keeppath = \"\", but the file must exist" << endl;
- } else {
- keeppath = pathstr;
- }
-
- }
+ sources. QuickFix(tm) */
+ if (keeppath == "") {
+ if (must_exist) {
+ error << "AudioFileSource::find(), keeppath = \"\", but the file must exist" << endl;
+ } else {
+ keeppath = pathstr;
+ }
+ }
_name = pathstr;
_path = keeppath;
try {
for (unsigned i = 1; i <= source->num_tracks(); ++i) {
-
boost::shared_ptr<SMFSource> smfs = boost::dynamic_pointer_cast<SMFSource>(newfiles[i-1]);
+ smfs->drop_model();
source->seek_to_track(i);
continue;
}
- smfs->append_event_unlocked(Beats, Evoral::Event<double>(
- 0,
- (double)t / (double)source->ppqn(),
- size,
- buf));
+ smfs->append_event_unlocked_beats(Evoral::Event<double>(0,
+ (double)t / (double)source->ppqn(),
+ size,
+ buf));
if (status.progress < 0.99)
status.progress += 0.01;
// Meter what we have (midi)
for ( ; n < limit; ++n) {
-
float val = 0;
// GUI needs a better MIDI meter, not much information can be
const bool old_percussive = percussive();
set_percussive(false);
+
+ source->drop_model();
for (Evoral::Sequence<TimeType>::const_iterator i = begin(); i != end(); ++i) {
- source->append_event_unlocked(Frames, *i);
+ source->append_event_unlocked_beats(*i);
}
set_percussive(old_percussive);
#include <pbd/pthread_utils.h>
#include <pbd/basename.h>
-#include <ardour/midi_source.h>
+#include <ardour/audioengine.h>
#include <ardour/midi_ring_buffer.h>
+#include <ardour/midi_source.h>
#include <ardour/session.h>
#include <ardour/session_directory.h>
#include <ardour/source_factory.h>
+#include <ardour/tempo.h>
#include "i18n.h"
}
nframes_t
-MidiSource::midi_read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset, nframes_t negative_stamp_offset) const
+MidiSource::midi_read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_t cnt,
+ nframes_t stamp_offset, nframes_t negative_stamp_offset) const
{
+
Glib::Mutex::Lock lm (_lock);
if (_model) {
+ // FIXME: assumes tempo never changes after start
+ const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position);
+ const double frames_per_beat = tempo.frames_per_beat(
+ _session.engine().frame_rate(),
+ _session.tempo_map().meter_at(_timeline_position));
+#define BEATS_TO_FRAMES(t) (((t) * frames_per_beat) + stamp_offset - negative_stamp_offset)
+
Evoral::Sequence<double>::const_iterator& i = _model_iter;
if (_last_read_end == 0 || start != _last_read_end) {
- i = _model->begin();
- cerr << "MidiSource::midi_read seeking to " << start << endl;
- while (i != _model->end() && i->time() < start)
- ++i;
+ cerr << "MidiSource::midi_read seeking to frame " << start << endl;
+ for (i = _model->begin(); i != _model->end(); ++i) {
+ if (BEATS_TO_FRAMES(i->time()) >= start) {
+ break;
+ }
+ }
}
_last_read_end = start + cnt;
- if (i == _model->end()) {
- return cnt;
- }
-
- while (i->time() < start + cnt && i != _model->end()) {
- dst.write(i->time(), i->event_type(), i->size(), i->buffer());
- ++i;
+ for (; i != _model->end(); ++i) {
+ const nframes_t time_frames = BEATS_TO_FRAMES(i->time());
+ if (time_frames < start + cnt) {
+ dst.write(time_frames, i->event_type(), i->size(), i->buffer());
+ } else {
+ break;
+ }
}
return cnt;
} else {
int e1 = stat (path.c_str(), &stat_file);
- return ( !e1 );
+ return !e1;
}
void
string SMFSource::_search_path;
+/** Constructor used for new internal-to-session files. File cannot exist. */
SMFSource::SMFSource(Session& s, std::string path, Flag flags)
: MidiSource(s, region_name_from_path(path, false))
, Evoral::SMF()
, _flags(flags)
, _allow_remove_if_empty(true)
- , _last_ev_time(0)
+ , _last_ev_time_beats(0.0)
+ , _last_ev_time_frames(0)
{
- /* Constructor used for new internal-to-session files. File cannot exist. */
-
if (init(path, false)) {
throw failed_constructor ();
}
assert(_name.find("/") == string::npos);
}
+/** Constructor used for existing internal-to-session files. File must exist. */
SMFSource::SMFSource(Session& s, const XMLNode& node)
: MidiSource(s, node)
, _flags(Flag(Writable|CanRename))
, _allow_remove_if_empty(true)
- , _last_ev_time(0)
+ , _last_ev_time_beats(0.0)
+ , _last_ev_time_frames(0)
{
- /* Constructor used for existing internal-to-session files. File must exist. */
-
if (set_state(node)) {
throw failed_constructor ();
}
// FIXME: assumes tempo never changes after start
const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position);
-
const double frames_per_beat = tempo.frames_per_beat(
_session.engine().frame_rate(),
_session.tempo_map().meter_at(_timeline_position));
/** All stamps in audio frames */
nframes_t
-SMFSource::write_unlocked (MidiRingBuffer<nframes_t>& src, nframes_t cnt)
+SMFSource::write_unlocked (MidiRingBuffer<nframes_t>& src, nframes_t dur)
{
_write_data_count = 0;
_model->start_write();
}
- Evoral::MIDIEvent<double> ev(0, 0.0, 4, NULL, true);
+ Evoral::MIDIEvent<nframes_t> ev(0, 0.0, 4, NULL, true);
while (true) {
bool ret = src.peek_time(&time);
- if (!ret || time - _timeline_position > _length + cnt) {
+ if (!ret || time - _timeline_position > _length + dur) {
break;
}
continue;
}
- append_event_unlocked(Frames, ev);
-
- if (_model) {
- _model->append(ev);
- }
+ append_event_unlocked_frames(ev);
}
if (_model) {
free(buf);
const nframes_t oldlen = _length;
- update_length(oldlen, cnt);
+ update_length(oldlen, dur);
- ViewDataRangeReady(_timeline_position + oldlen, cnt); /* EMIT SIGNAL */
-
- return cnt;
+ ViewDataRangeReady(_timeline_position + oldlen, dur); /* EMIT SIGNAL */
+
+ return dur;
}
+/** Append an event with a timestamp in beats (double) */
void
-SMFSource::append_event_unlocked(EventTimeUnit unit, const Evoral::Event<double>& ev)
+SMFSource::append_event_unlocked_beats(const Evoral::Event<double>& ev)
{
if (ev.size() == 0) {
- cerr << "SMFSource: Warning: skipping empty event" << endl;
return;
}
- /*
- printf("SMFSource: %s - append_event_unlocked time = %lf, size = %u, data = ",
+ /*printf("SMFSource: %s - append_event_unlocked_beats time = %lf, size = %u, data = ",
name().c_str(), ev.time(), ev.size());
- for (size_t i=0; i < ev.size(); ++i) {
- printf("%X ", ev.buffer()[i]);
- } printf("\n");
- */
+ for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");*/
assert(ev.time() >= 0);
+ if (ev.time() < _last_ev_time_beats) {
+ cerr << "SMFSource: Warning: Skipping event with non-monotonic time" << endl;
+ return;
+ }
- if (ev.time() < last_event_time()) {
- cerr << "SMFSource: Warning: Skipping event with ev.time() < last.time()" << endl;
+ const double delta_time_beats = ev.time() - _last_ev_time_beats;
+ const uint32_t delta_time_ticks = (uint32_t)lrint(delta_time_beats * (double)ppqn());
+
+ Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer());
+ _last_ev_time_beats = ev.time();
+
+ _write_data_count += ev.size();
+
+ if (_model) {
+ _model->append(ev);
+ }
+}
+
+/** Append an event with a timestamp in frames (nframes_t) */
+void
+SMFSource::append_event_unlocked_frames(const Evoral::Event<nframes_t>& ev)
+{
+ if (ev.size() == 0) {
return;
}
+
+ /*printf("SMFSource: %s - append_event_unlocked_frames time = %u, size = %u, data = ",
+ name().c_str(), ev.time(), ev.size());
+ for (size_t i=0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");*/
- uint32_t delta_time = 0;
+ assert(ev.time() >= 0);
+ if (ev.time() < _last_ev_time_frames) {
+ cerr << "SMFSource: Warning: Skipping event with non-monotonic time" << endl;
+ return;
+ }
- if (unit == Frames) {
- // FIXME: assumes tempo never changes after start
- const double frames_per_beat = _session.tempo_map().tempo_at(_timeline_position).frames_per_beat(
- _session.engine().frame_rate(),
- _session.tempo_map().meter_at(_timeline_position));
+ // FIXME: assumes tempo never changes after start
+ const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position);
+ const double frames_per_beat = tempo.frames_per_beat(
+ _session.engine().frame_rate(),
+ _session.tempo_map().meter_at(_timeline_position));
- delta_time = (uint32_t)((ev.time() - last_event_time()) / frames_per_beat * ppqn());
- } else {
- assert(unit == Beats);
- delta_time = (uint32_t)((ev.time() - last_event_time()) * ppqn());
- }
+ uint32_t delta_time = (uint32_t)((ev.time() - _last_ev_time_frames)
+ / frames_per_beat * (double)ppqn());
Evoral::SMF::append_event_delta(delta_time, ev.size(), ev.buffer());
- _last_ev_time = ev.time();
+ _last_ev_time_frames = ev.time();
_write_data_count += ev.size();
+
+ if (_model) {
+ double beat_time = ev.time() / frames_per_beat;
+ const Evoral::Event<double> beat_ev(
+ ev.event_type(), beat_time, ev.size(), (uint8_t*)ev.buffer());
+ _model->append(beat_ev);
+ }
}
{
MidiSource::mark_streaming_midi_write_started (mode, start_frame);
Evoral::SMF::begin_write ();
- _last_ev_time = 0;
+ _last_ev_time_beats = 0.0;
+ _last_ev_time_frames = 0;
}
void
int
SMFSource::move_to_trash (const string trash_dir_name)
{
- string newpath;
-
if (!writable()) {
return -1;
}
- /* don't move the file across filesystems, just
- stick it in the 'trash_dir_name' directory
- on whichever filesystem it was already on.
+ /* don't move the file across filesystems, just stick it in the
+ trash_dir_name directory on whichever filesystem it was already on
*/
-
+
+ Glib::ustring newpath;
newpath = Glib::path_get_dirname (_path);
- newpath = Glib::path_get_dirname (newpath);
+ newpath = Glib::path_get_dirname (newpath);
- newpath += '/';
- newpath += trash_dir_name;
- newpath += '/';
+ newpath += string("/") + trash_dir_name + "/";
newpath += Glib::path_get_basename (_path);
+ /* the new path already exists, try versioning */
if (access (newpath.c_str(), F_OK) == 0) {
-
- /* the new path already exists, try versioning */
-
char buf[PATH_MAX+1];
int version = 1;
string newpath_v;
}
if (version == 999) {
- PBD::error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"),
- newpath)
- << endmsg;
+ PBD::error << string_compose (
+ _("there are already 1000 files with names like %1; versioning discontinued"),
+ newpath) << endmsg;
} else {
newpath = newpath_v;
}
-
- } else {
-
- /* it doesn't exist, or we can't read it or something */
-
}
if (::rename (_path.c_str(), newpath.c_str()) != 0) {
- PBD::error << string_compose (_("cannot rename midi file source from %1 to %2 (%3)"),
- _path, newpath, strerror (errno))
- << endmsg;
+ PBD::error << string_compose (
+ _("cannot rename midi file source from %1 to %2 (%3)"),
+ _path, newpath, strerror (errno)) << endmsg;
return -1;
}
+ _path = newpath;
+
/* file can not be removed twice, since the operation is not idempotent */
-
_flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
return 0;
bool
SMFSource::find (string pathstr, bool must_exist, bool& isnew)
{
- string::size_type pos;
bool ret = false;
isnew = false;
- /* clean up PATH:CHANNEL notation so that we are looking for the correct path */
-
- if ((pos = pathstr.find_last_of (':')) == string::npos) {
- pathstr = pathstr;
- } else {
- pathstr = pathstr.substr (0, pos);
- }
-
if (pathstr[0] != '/') {
/* non-absolute pathname: find pathstr in search path */
cnt = 0;
for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
-
fullpath = *i;
if (fullpath[fullpath.length()-1] != '/') {
fullpath += '/';
size_t scratch_size = 0; // keep track of scratch and minimize reallocs
- // FIXME: assumes tempo never changes after start
- const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position);
-
- const double frames_per_beat = tempo.frames_per_beat(
- _session.engine().frame_rate(),
- _session.tempo_map().meter_at(_timeline_position));
-
uint32_t delta_t = 0;
uint32_t size = 0;
uint8_t* buf = NULL;
int ret;
while ((ret = read_event(&delta_t, &size, &buf)) >= 0) {
- ev.set(buf, size, 0.0);
time += delta_t;
+ ev.set(buf, size, time / (double)ppqn());
if (ret > 0) { // didn't skip (meta) event
- // make ev.time absolute time in frames
- ev.time() = time * frames_per_beat / (double)ppqn();
ev.set_event_type(EventTypeMap::instance().midi_event_type(buf[0]));
_model->append(ev);
}