The Big Change: Store time in MidiModel as tempo time, not frame time.

The time stamp of an event is now always tempo, from file to model and
back again.  Frame time is only relevant at playback or recording time,
in the audio thread (MidiModel and MidiBuffer).

I think perhaps we don't need to change the actual time from double (which is
convenient for math), it is the time base conversion that caused problems.
Using a correct equality comparison (i.e.  not == which is not correct for
floating point) should probably make the undo issues go away, in 99.99% of
cases anyway.

There's almost certainly some regressions in here somewhere, but they do not
seem to be time related.  The bugs I'm hitting in testing are old ones that
seem unrelated now, so it's checkpoint time.

This sets us up for fancy things like tempo map import and tempo/meter changes
halfway through MIDI regions, but for now it's still assumed that the tempo
at the start of the region is valid for the duration of the entire region.


git-svn-id: svn://localhost/ardour2/branches/3.0@4582 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard 2009-02-15 17:30:42 +00:00
parent 425966a696
commit ecaf107ed3
14 changed files with 338 additions and 346 deletions

View file

@ -131,11 +131,10 @@ CanvasNoteEvent::show_channel_selector(void)
_channel_selector->channel_selected.connect( _channel_selector->channel_selected.connect(
sigc::mem_fun(this, &CanvasNoteEvent::on_channel_change)); sigc::mem_fun(this, &CanvasNoteEvent::on_channel_change));
_channel_selector_widget = _channel_selector_widget = new Widget(*(_item->property_parent()),
new Widget(*(_item->property_parent()), x1(),
x1(), y2() + 2,
y2() + 2, (Gtk::Widget &) *_channel_selector);
(Gtk::Widget &) *_channel_selector);
_channel_selector_widget->hide(); _channel_selector_widget->hide();
_channel_selector_widget->property_height() = 100; _channel_selector_widget->property_height() = 100;
@ -186,8 +185,8 @@ CanvasNoteEvent::base_color()
ColorMode mode = _region.color_mode(); ColorMode mode = _region.color_mode();
const uint8_t minimal_opaqueness = 15; const uint8_t min_opacity = 15;
uint8_t opaqueness = std::max(minimal_opaqueness, uint8_t(_note->velocity() + _note->velocity())); uint8_t opacity = std::max(min_opacity, uint8_t(_note->velocity() + _note->velocity()));
switch (mode) { switch (mode) {
case TrackColor: case TrackColor:
@ -197,12 +196,12 @@ CanvasNoteEvent::base_color()
SCALE_USHORT_TO_UINT8_T(color.get_red()), SCALE_USHORT_TO_UINT8_T(color.get_red()),
SCALE_USHORT_TO_UINT8_T(color.get_green()), SCALE_USHORT_TO_UINT8_T(color.get_green()),
SCALE_USHORT_TO_UINT8_T(color.get_blue()), SCALE_USHORT_TO_UINT8_T(color.get_blue()),
opaqueness); opacity);
} }
case ChannelColors: case ChannelColors:
return UINT_RGBA_CHANGE_A(CanvasNoteEvent::midi_channel_colors[_note->channel()], return UINT_RGBA_CHANGE_A(CanvasNoteEvent::midi_channel_colors[_note->channel()],
opaqueness); opacity);
default: default:
return meter_style_fill_color(_note->velocity()); return meter_style_fill_color(_note->velocity());
@ -221,9 +220,13 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
double event_x, event_y, dx, dy; double event_x, event_y, dx, dy;
bool select_mod; bool select_mod;
uint8_t d_velocity = 10; 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; return false;
}
const Editing::MidiEditMode midi_edit_mode
= _region.midi_view()->editor().current_midi_edit_mode();
switch (ev->type) { switch (ev->type) {
case GDK_SCROLL: case GDK_SCROLL:
@ -284,7 +287,7 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
switch (_state) { switch (_state) {
case Pressed: // Drag begin 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::SelectTouchDragging
&& _region.mouse_state() != MidiRegionView::EraseTouchDragging) { && _region.mouse_state() != MidiRegionView::EraseTouchDragging) {
_item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
@ -311,12 +314,10 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
} }
_item->property_parent().get_value()->w2i(event_x, event_y); _item->property_parent().get_value()->w2i(event_x, event_y);
// Snap
event_x = _region.snap_to_pixel(event_x); event_x = _region.snap_to_pixel(event_x);
dx = event_x - last_x; dx = event_x - last_x;
dy = event_y - last_y; dy = event_y - last_y;
last_x = event_x; last_x = event_x;
drag_delta_x += dx; drag_delta_x += dx;
@ -356,16 +357,16 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
switch (_state) { switch (_state) {
case Pressed: // Clicked case Pressed: // Clicked
if (_region.midi_view()->editor().current_midi_edit_mode() == Editing::MidiEditSelect) { if (midi_edit_mode == Editing::MidiEditSelect) {
_state = None; _state = None;
if (_selected && !select_mod && _region.selection_size() > 1) {
if (_selected && !select_mod && _region.selection_size() > 1)
_region.unique_select(this); _region.unique_select(this);
else if (_selected) } else if (_selected) {
_region.note_deselected(this, select_mod); _region.note_deselected(this, select_mod);
else } else {
_region.note_selected(this, select_mod); _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.start_delta_command();
_region.command_remove_note(this); _region.command_remove_note(this);
_region.apply_command(); _region.apply_command();
@ -375,12 +376,9 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
case Dragging: // Dropped case Dragging: // Dropped
_item->ungrab(ev->button.time); _item->ungrab(ev->button.time);
_state = None; _state = None;
if (_note) {
if (_note) _region.note_dropped(this, drag_delta_x, drag_delta_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);
return true; return true;
default: default:
break; break;

View file

@ -12,18 +12,17 @@ using namespace MIDI::Name;
using namespace std; using namespace std;
CanvasProgramChange::CanvasProgramChange( CanvasProgramChange::CanvasProgramChange(
MidiRegionView& region, MidiRegionView& region,
Group& parent, Group& parent,
string& text, string& text,
double height, double height,
double x, double x,
double y, double y,
string& model_name, string& model_name,
string& custom_device_mode, string& custom_device_mode,
nframes_t event_time, nframes_t event_time,
uint8_t channel, uint8_t channel,
uint8_t program uint8_t program)
)
: CanvasFlag( : CanvasFlag(
region, region,
parent, parent,
@ -31,8 +30,7 @@ CanvasProgramChange::CanvasProgramChange(
ARDOUR_UI::config()->canvasvar_MidiProgramChangeOutline.get(), ARDOUR_UI::config()->canvasvar_MidiProgramChangeOutline.get(),
ARDOUR_UI::config()->canvasvar_MidiProgramChangeFill.get(), ARDOUR_UI::config()->canvasvar_MidiProgramChangeFill.get(),
x, x,
y y)
)
, _model_name(model_name) , _model_name(model_name)
, _custom_device_mode(custom_device_mode) , _custom_device_mode(custom_device_mode)
, _event_time(event_time) , _event_time(event_time)

View file

@ -508,7 +508,7 @@ Editor::popup_ruler_menu (nframes64_t where, ItemType t)
ruler_items.push_back (MenuElem (*action->create_menu_item())); 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; no_ruler_shown_update = false;
} }

View file

@ -64,11 +64,12 @@ using namespace PBD;
using namespace Editing; using namespace Editing;
using namespace ArdourCanvas; 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) : RegionView (parent, tv, r, spu, basic_color)
, _force_channel(-1) , _force_channel(-1)
, _last_channel_selection(0xFFFF) , _last_channel_selection(0xFFFF)
, _default_note_length(0.0) , _default_note_length(1.0)
, _current_range_min(0) , _current_range_min(0)
, _current_range_max(0) , _current_range_max(0)
, _model_name(string()) , _model_name(string())
@ -82,11 +83,13 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &
_note_group->raise_to_top(); _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) : RegionView (parent, tv, r, spu, basic_color, false, visibility)
, _force_channel(-1) , _force_channel(-1)
, _last_channel_selection(0xFFFF) , _last_channel_selection(0xFFFF)
, _default_note_length(0.0) , _default_note_length(1.0)
, _model_name(string()) , _model_name(string())
, _custom_device_mode(string()) , _custom_device_mode(string())
, _active_notes(0) , _active_notes(0)
@ -104,7 +107,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other)
: RegionView (other) : RegionView (other)
, _force_channel(-1) , _force_channel(-1)
, _last_channel_selection(0xFFFF) , _last_channel_selection(0xFFFF)
, _default_note_length(0.0) , _default_note_length(1.0)
, _model_name(string()) , _model_name(string())
, _custom_device_mode(string()) , _custom_device_mode(string())
, _active_notes(0) , _active_notes(0)
@ -122,11 +125,11 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other)
init (c, false); init (c, false);
} }
MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> other_region) MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
: RegionView (other, boost::shared_ptr<Region> (other_region)) : RegionView (other, boost::shared_ptr<Region> (region))
, _force_channel(-1) , _force_channel(-1)
, _last_channel_selection(0xFFFF) , _last_channel_selection(0xFFFF)
, _default_note_length(0.0) , _default_note_length(1.0)
, _model_name(string()) , _model_name(string())
, _custom_device_mode(string()) , _custom_device_mode(string())
, _active_notes(0) , _active_notes(0)
@ -151,11 +154,6 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
midi_region()->midi_source(0)->load_model(); 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(); _model = midi_region()->midi_source(0)->model();
_enable_display = false; _enable_display = false;
@ -208,14 +206,14 @@ MidiRegionView::canvas_event(GdkEvent* ev)
if (trackview.editor().current_mouse_mode() != MouseNote) if (trackview.editor().current_mouse_mode() != MouseNote)
return false; return false;
// Mmmm, spaghetti const Editing::MidiEditMode midi_edit_mode = trackview.editor().current_midi_edit_mode();
switch (ev->type) { switch (ev->type) {
case GDK_KEY_PRESS: case GDK_KEY_PRESS:
if (ev->key.keyval == GDK_Delete && !delete_mod) { if (ev->key.keyval == GDK_Delete && !delete_mod) {
delete_mod = true; delete_mod = true;
original_mode = trackview.editor().current_midi_edit_mode(); original_mode = midi_edit_mode;
trackview.editor().set_midi_edit_mode(MidiEditErase); trackview.editor().set_midi_edit_mode(MidiEditErase);
start_delta_command(_("erase notes")); start_delta_command(_("erase notes"));
_mouse_state = EraseTouchDragging; _mouse_state = EraseTouchDragging;
@ -282,7 +280,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
case Pressed: // Drag start case Pressed: // Drag start
// Select 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, group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::FLEUR), ev->motion.time); Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
last_x = event_x; last_x = event_x;
@ -305,7 +303,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
return true; return true;
// Add note drag start // 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, group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::FLEUR), ev->motion.time); Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
last_x = event_x; last_x = event_x;
@ -316,13 +314,14 @@ MidiRegionView::canvas_event(GdkEvent* ev)
drag_rect = new ArdourCanvas::SimpleRect(*group); drag_rect = new ArdourCanvas::SimpleRect(*group);
drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame); 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_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_what() = 0xFF;
drag_rect->property_outline_color_rgba() = 0xFFFFFF99; drag_rect->property_outline_color_rgba() = 0xFFFFFF99;
drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
drag_rect->property_fill_color_rgba() = 0xFFFFFF66;
_mouse_state = AddDragging; _mouse_state = AddDragging;
return true; return true;
@ -385,7 +384,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
switch (_mouse_state) { switch (_mouse_state) {
case Pressed: // Clicked case Pressed: // Clicked
switch (trackview.editor().current_midi_edit_mode()) { switch (midi_edit_mode) {
case MidiEditSelect: case MidiEditSelect:
case MidiEditResize: case MidiEditResize:
clear_selection(); clear_selection();
@ -408,7 +407,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
const double length = trackview.editor().pixel_to_frame( const double length = trackview.editor().pixel_to_frame(
drag_rect->property_x2() - drag_rect->property_x1()); 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; delete drag_rect;
@ -423,7 +422,10 @@ MidiRegionView::canvas_event(GdkEvent* ev)
} }
/** 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 void
MidiRegionView::create_note_at(double x, double y, double length) MidiRegionView::create_note_at(double x, double y, double length)
{ {
@ -435,29 +437,18 @@ MidiRegionView::create_note_at(double x, double y, double length)
assert(note >= 0.0); assert(note >= 0.0);
assert(note <= 127.0); assert(note <= 127.0);
nframes64_t new_note_time = trackview.editor().pixel_to_frame (x); // Start of note in frames relative to region start
assert(new_note_time >= 0); nframes64_t start_frames = snap_to_frame(trackview.editor().pixel_to_frame(x));
new_note_time += _region->start(); assert(start_frames >= 0);
/* // Snap length
const Meter& m = trackview.session().tempo_map().meter_at(new_note_time); length = frames_to_beats(
const Tempo& t = trackview.session().tempo_map().tempo_at(new_note_time); snap_to_frame(start_frames + beats_to_frames(length)) - start_frames);
double length = m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar();
*/ const boost::shared_ptr<NoteType> new_note(new NoteType(0,
frames_to_beats(start_frames + _region->start()), length,
// we need to snap here again in nframes64_t in order to be sample accurate (uint8_t)note, 0x40));
// 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;
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()); view->update_note_range(new_note->note());
MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note"); MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
@ -478,8 +469,9 @@ MidiRegionView::clear_events()
} }
} }
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
delete *i; delete *i;
}
_events.clear(); _events.clear();
_pgm_changes.clear(); _pgm_changes.clear();
@ -491,26 +483,29 @@ MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
{ {
_model = model; _model = model;
if (_enable_display) if (_enable_display) {
redisplay_model(); redisplay_model();
}
} }
void void
MidiRegionView::start_delta_command(string name) MidiRegionView::start_delta_command(string name)
{ {
if (!_delta_command) if (!_delta_command) {
_delta_command = _model->new_delta_command(name); _delta_command = _model->new_delta_command(name);
}
} }
void void
MidiRegionView::command_add_note(const boost::shared_ptr<NoteType> note, bool selected) MidiRegionView::command_add_note(const boost::shared_ptr<NoteType> note, bool selected)
{ {
if (_delta_command) if (_delta_command) {
_delta_command->add(note); _delta_command->add(note);
}
if (selected) if (selected) {
_marked_for_selection.insert(note); _marked_for_selection.insert(note);
}
} }
void void
@ -558,14 +553,12 @@ MidiRegionView::redisplay_model()
return; return;
if (_model) { if (_model) {
clear_events(); clear_events();
_model->read_lock(); _model->read_lock();
MidiModel::Notes notes = _model->notes(); 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) { for (MidiModel::Notes::iterator i = notes.begin(); i != notes.end(); ++i) {
cerr << "NOTE time: " << (*i)->time() cerr << "NOTE time: " << (*i)->time()
<< " pitch: " << int((*i)->note()) << " pitch: " << int((*i)->note())
@ -580,7 +573,7 @@ MidiRegionView::redisplay_model()
add_note(_model->note_at(i)); add_note(_model->note_at(i));
} }
find_and_insert_program_change_flags(); display_program_change_flags();
// Is this necessary? // Is this necessary?
/*for (Automatable::Controls::const_iterator i = _model->controls().begin(); /*for (Automatable::Controls::const_iterator i = _model->controls().begin();
@ -619,22 +612,22 @@ MidiRegionView::redisplay_model()
} }
void 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(); for (Automatable::Controls::iterator control = _model->controls().begin();
control != _model->controls().end(); ++control) { control != _model->controls().end(); ++control) {
if (control->first.type() == MidiPgmChangeAutomation) { if (control->first.type() == MidiPgmChangeAutomation) {
Glib::Mutex::Lock list_lock (control->second->list()->lock()); 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(); for (AutomationList::const_iterator event = control->second->list()->begin();
event != control->second->list()->end(); ++event) { event != control->second->list()->end(); ++event) {
double event_time = (*event)->when; double event_time = (*event)->when;
double program_number = floor((*event)->value + 0.5); 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 // find bank select msb and lsb for the program change
Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK); Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
@ -651,7 +644,8 @@ MidiRegionView::find_and_insert_program_change_flags()
lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5)); 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); MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number);
@ -660,13 +654,13 @@ MidiRegionView::find_and_insert_program_change_flags()
_model_name, _model_name,
_custom_device_mode, _custom_device_mode,
channel, channel,
patch_key patch_key);
);
ControlEvent program_change(nframes_t(event_time), uint8_t(program_number), channel); ControlEvent program_change(nframes_t(event_time), uint8_t(program_number), channel);
if (patch != 0) { 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()); add_pgm_change(program_change, patch->name());
} else { } else {
char buf[4]; char buf[4];
@ -676,7 +670,8 @@ MidiRegionView::find_and_insert_program_change_flags()
} }
break; break;
} else if (control->first.type() == MidiCCAutomation) { } 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;
} }
} }
} }
@ -705,8 +700,9 @@ MidiRegionView::region_resized (Change what_changed)
RegionView::region_resized(what_changed); RegionView::region_resized(what_changed);
if (what_changed & ARDOUR::PositionChanged) { if (what_changed & ARDOUR::PositionChanged) {
if (_enable_display) if (_enable_display) {
redisplay_model(); redisplay_model();
}
} }
} }
@ -716,14 +712,15 @@ MidiRegionView::reset_width_dependent_items (double pixel_width)
RegionView::reset_width_dependent_items(pixel_width); RegionView::reset_width_dependent_items(pixel_width);
assert(_pixel_width == pixel_width); assert(_pixel_width == pixel_width);
if (_enable_display) if (_enable_display) {
redisplay_model(); redisplay_model();
}
} }
void 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; const double old_height = _height;
RegionView::set_height(height); RegionView::set_height(height);
_height = height - FUDGE; _height = height - FUDGE;
@ -811,8 +808,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv)
audio waveforms under it. audio waveforms under it.
*/ */
ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position); ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
} } else {
else {
ghost = new MidiGhostRegion (tv, trackview, unit_position); ghost = new MidiGhostRegion (tv, trackview, unit_position);
} }
@ -861,8 +857,9 @@ MidiRegionView::end_write()
void void
MidiRegionView::resolve_note(uint8_t note, double end_time) MidiRegionView::resolve_note(uint8_t note, double end_time)
{ {
if (midi_view()->note_mode() != Sustained) if (midi_view()->note_mode() != Sustained) {
return; return;
}
if (_active_notes && _active_notes[note]) { if (_active_notes && _active_notes[note]) {
_active_notes[note]->property_x2() = trackview.editor().frame_to_pixel((nframes64_t)end_time); _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel((nframes64_t)end_time);
@ -898,9 +895,11 @@ MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview); RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
assert(route_ui); 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()); * (1000 / (double)route_ui->session().nominal_frame_rate());
Glib::signal_timeout().connect(bind(mem_fun(this, &MidiRegionView::play_midi_note_off), note), Glib::signal_timeout().connect(bind(mem_fun(this, &MidiRegionView::play_midi_note_off), note),
note_length_ms, G_PRIORITY_DEFAULT); note_length_ms, G_PRIORITY_DEFAULT);
@ -912,7 +911,8 @@ MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview); RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
assert(route_ui); 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; return false;
} }
@ -930,11 +930,14 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
assert(note->time() >= 0); assert(note->time() >= 0);
assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive); 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 // dont display notes beyond the region bounds
if ( note->time() - _region->start() >= _region->length() || if (note_start_frames - _region->start() >= _region->length() ||
note->time() < _region->start() || note_start_frames < _region->start() ||
note->note() < midi_stream_view()->lowest_note() || note->note() < midi_stream_view()->lowest_note() ||
note->note() > midi_stream_view()->highest_note() ) { note->note() > midi_stream_view()->highest_note() ) {
return; return;
} }
@ -942,13 +945,12 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
CanvasNoteEvent* event = 0; 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) { if (midi_view()->note_mode() == Sustained) {
const double y1 = midi_stream_view()->note_to_y(note->note()); const double y1 = midi_stream_view()->note_to_y(note->note());
const double note_endpixel = 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); CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
ev_rect->property_x1() = x; ev_rect->property_x1() = x;
@ -996,10 +998,6 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
} }
} else if (midi_view()->note_mode() == Percussive) { } 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 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); const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0);
@ -1054,7 +1052,6 @@ MidiRegionView::add_pgm_change(ControlEvent& program, string displaytext)
void void
MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key)
{ {
cerr << "getting patch key at " << time << " for channel " << channel << endl; cerr << "getting patch key at " << time << " for channel " << channel << endl;
Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK); Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK);
boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb); boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
@ -1090,7 +1087,6 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
void void
MidiRegionView::alter_program_change(ControlEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch) 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 // 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); 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); boost::shared_ptr<Evoral::Control> msb_control = _model->control(bank_select_msb);
@ -1132,8 +1128,7 @@ MidiRegionView::previous_program(CanvasProgramChange& program)
_model_name, _model_name,
_custom_device_mode, _custom_device_mode,
program.channel(), program.channel(),
key key);
);
ControlEvent program_change_event(program.event_time(), program.program(), program.channel()); ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
if (patch) { if (patch) {
@ -1152,8 +1147,7 @@ MidiRegionView::next_program(CanvasProgramChange& program)
_model_name, _model_name,
_custom_device_mode, _custom_device_mode,
program.channel(), program.channel(),
key key);
);
ControlEvent program_change_event(program.event_time(), program.program(), program.channel()); ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
if (patch) { if (patch) {
alter_program_change(program_change_event, patch->patch_primary_key()); alter_program_change(program_change_event, patch->patch_primary_key());
@ -1341,28 +1335,24 @@ MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
const boost::shared_ptr<NoteType> copy(new NoteType(*(*i)->note().get())); 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 // we need to snap here again in nframes64_t in order to be sample accurate
double new_note_time = (*i)->note()->time(); double start_frames = snap_to_frame(beats_to_frames((*i)->note()->time()) + dt);
new_note_time += dt;
// keep notes inside region if dragged beyond left region bound // keep notes inside region if dragged beyond left region bound
if (new_note_time < _region->start()) { if (start_frames < _region->start()) {
new_note_time = _region->start(); start_frames = _region->start();
} }
// since note time is region-absolute but snap_to_frame expects position-relative copy->set_time(frames_to_beats(start_frames));
// 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);
uint8_t original_pitch = (*i)->note()->note(); 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 // keep notes in standard midi range
clamp_0_to_127(new_pitch); clamp_0_to_127(new_pitch);
//notes which are dragged beyond the standard midi range snap back to their original place // keep original pitch if note is dragged outside valid midi range
if ((original_pitch != 0 && new_pitch == 0) || (original_pitch != 127 && new_pitch == 127)) { if ((original_pitch != 0 && new_pitch == 0)
|| (original_pitch != 127 && new_pitch == 127)) {
new_pitch = original_pitch; new_pitch = original_pitch;
} }
@ -1391,13 +1381,10 @@ nframes64_t
MidiRegionView::snap_to_frame(double x) MidiRegionView::snap_to_frame(double x)
{ {
PublicEditor &editor = trackview.editor(); PublicEditor &editor = trackview.editor();
// x is region relative // x is region relative, convert it to global absolute frames
// convert x to global frame
nframes64_t frame = editor.pixel_to_frame(x) + _region->position(); nframes64_t frame = editor.pixel_to_frame(x) + _region->position();
editor.snap_to(frame); editor.snap_to(frame);
// convert event_frame back to local coordinates relative to position return frame - _region->position(); // convert back to region relative
frame -= _region->position();
return frame;
} }
nframes64_t nframes64_t
@ -1426,6 +1413,22 @@ MidiRegionView::get_position_pixels()
return trackview.editor().frame_to_pixel(region_frame); 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 void
MidiRegionView::begin_resizing(CanvasNote::NoteEnd note_end) MidiRegionView::begin_resizing(CanvasNote::NoteEnd note_end)
{ {
@ -1440,32 +1443,25 @@ MidiRegionView::begin_resizing(CanvasNote::NoteEnd note_end)
resize_data->canvas_note = note; resize_data->canvas_note = note;
// create a new SimpleRect from the note which will be the resize preview // create a new SimpleRect from the note which will be the resize preview
SimpleRect *resize_rect = SimpleRect *resize_rect = new SimpleRect(
new SimpleRect( *group, note->x1(), note->y1(), note->x2(), note->y2());
*group,
note->x1(),
note->y1(),
note->x2(),
note->y2());
// calculate the colors: get the color settings // calculate the colors: get the color settings
uint32_t fill_color = uint32_t fill_color = UINT_RGBA_CHANGE_A(
UINT_RGBA_CHANGE_A( ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(),
ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(), 128);
128);
// make the resize preview notes more transparent and bright // make the resize preview notes more transparent and bright
fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5); fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
// calculate color based on note velocity // calculate color based on note velocity
resize_rect->property_fill_color_rgba() = resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE(
UINT_INTERPOLATE(
CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()), CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
fill_color, fill_color,
0.85); 0.85);
resize_rect->property_outline_color_rgba() = resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
CanvasNoteEvent::calculate_outline(ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get()); ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
resize_data->resize_rect = resize_rect; resize_data->resize_rect = resize_rect;
@ -1484,8 +1480,8 @@ void
MidiRegionView::update_resizing(CanvasNote::NoteEnd note_end, double x, bool relative) 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) { for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
SimpleRect *resize_rect = (*i)->resize_rect; SimpleRect* resize_rect = (*i)->resize_rect;
CanvasNote *canvas_note = (*i)->canvas_note; CanvasNote* canvas_note = (*i)->canvas_note;
const double region_start = get_position_pixels(); const double region_start = get_position_pixels();

View file

@ -150,13 +150,13 @@ class MidiRegionView : public RegionView
/** Displays all program changed events in the region as flags on the canvas. /** 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 begin_write();
void end_write(); void end_write();
void extend_active_notes(); 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); void display_model(boost::shared_ptr<ARDOUR::MidiModel> model);
@ -175,19 +175,16 @@ class MidiRegionView : public RegionView
size_t selection_size() { return _selection.size(); } size_t selection_size() { return _selection.size(); }
void move_selection(double dx, double dy); 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. /** Get the region position in pixels relative to session. */
* This function is needed to subtract the region start in pixels
* from world coordinates submitted by the mouse
*/
double get_position_pixels(); double get_position_pixels();
/** Begin resizing of some notes. /** Begin resizing of some notes.
* Called by CanvasMidiNote when resizing starts. * Called by CanvasMidiNote when resizing starts.
* @param note_end which end of the note, NOTE_ON or NOTE_OFF * @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. /** Update resizing notes while user drags.
* @param note_end which end of the note, NOTE_ON or NOTE_OFF * @param note_end which end of the note, NOTE_ON or NOTE_OFF
@ -215,7 +212,15 @@ class MidiRegionView : public RegionView
*/ */
void change_channel(uint8_t channel); 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; } MouseState mouse_state() const { return _mouse_state; }
struct NoteResizeData { struct NoteResizeData {
@ -243,8 +248,13 @@ class MidiRegionView : public RegionView
*/ */
nframes64_t snap_to_frame(nframes64_t x); 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: protected:
/** Allows derived types to specify their visibility requirements /** Allows derived types to specify their visibility requirements
* to the TimeAxisViewItem parent class. * to the TimeAxisViewItem parent class.
*/ */
@ -272,7 +282,7 @@ class MidiRegionView : public RegionView
* (scheduled by @ref play_midi_note()). * (scheduled by @ref play_midi_note()).
*/ */
bool play_midi_note_off(boost::shared_ptr<NoteType> note); bool play_midi_note_off(boost::shared_ptr<NoteType> note);
void clear_events(); void clear_events();
void switch_source(boost::shared_ptr<ARDOUR::Source> src); void switch_source(boost::shared_ptr<ARDOUR::Source> src);
@ -282,7 +292,7 @@ class MidiRegionView : public RegionView
void midi_channel_mode_changed(ARDOUR::ChannelMode mode, uint16_t mask); 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 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_except(ArdourCanvas::CanvasNoteEvent* ev);
void clear_selection() { clear_selection_except(NULL); } void clear_selection() { clear_selection_except(NULL); }

View file

@ -57,10 +57,12 @@ class MidiSource : public Source
virtual uint32_t n_channels () const { return 1; } virtual uint32_t n_channels () const { return 1; }
// FIXME: integrate this with the Readable::read interface somehow // 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 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_for_remove() = 0;
virtual void mark_streaming_midi_write_started (NoteMode mode, nframes_t start_time); virtual void mark_streaming_midi_write_started (NoteMode mode, nframes_t start_time);
@ -95,6 +97,7 @@ class MidiSource : public Source
boost::shared_ptr<MidiModel> model() { return _model; } boost::shared_ptr<MidiModel> model() { return _model; }
void set_model(boost::shared_ptr<MidiModel> m) { _model = m; } void set_model(boost::shared_ptr<MidiModel> m) { _model = m; }
void drop_model() { _model.reset(); }
protected: protected:
virtual void flush_midi() = 0; virtual void flush_midi() = 0;

View file

@ -63,8 +63,9 @@ class SMFSource : public MidiSource, public Evoral::SMF {
void set_allow_remove_if_empty (bool yn); void set_allow_remove_if_empty (bool yn);
void mark_for_remove(); 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); int move_to_trash (const string trash_dir_name);
@ -83,8 +84,6 @@ class SMFSource : public MidiSource, public Evoral::SMF {
void load_model(bool lock=true, bool force_reload=false); void load_model(bool lock=true, bool force_reload=false);
void destroy_model(); void destroy_model();
double last_event_time() const { return _last_ev_time; }
void flush_midi(); void flush_midi();
private: private:
@ -111,7 +110,8 @@ class SMFSource : public MidiSource, public Evoral::SMF {
Flag _flags; Flag _flags;
string _take_id; string _take_id;
bool _allow_remove_if_empty; bool _allow_remove_if_empty;
double _last_ev_time; double _last_ev_time_beats;
nframes_t _last_ev_time_frames;
static string _search_path; static string _search_path;
}; };

View file

@ -148,11 +148,6 @@ namespace ARDOUR {
TrackColor TrackColor
}; };
enum EventTimeUnit {
Frames,
Beats
};
struct BBT_Time { struct BBT_Time {
uint32_t bars; uint32_t bars;
uint32_t beats; uint32_t beats;

View file

@ -86,11 +86,11 @@ struct SizedSampleBuffer {
Glib::StaticPrivate<SizedSampleBuffer> thread_interleave_buffer = GLIBMM_STATIC_PRIVATE_INIT; 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) AudioFileSource::AudioFileSource (Session& s, ustring path, Flag flags)
: AudioSource (s, path), _flags (flags), : AudioSource (s, path), _flags (flags),
_channel (0) _channel (0)
{ {
/* constructor used for existing external to session files. file must exist already */
_is_embedded = AudioFileSource::determine_embeddedness (path); _is_embedded = AudioFileSource::determine_embeddedness (path);
if (init (path, true)) { if (init (path, true)) {
@ -99,11 +99,11 @@ AudioFileSource::AudioFileSource (Session& s, ustring path, Flag flags)
} }
/** 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) AudioFileSource::AudioFileSource (Session& s, ustring path, Flag flags, SampleFormat samp_format, HeaderFormat hdr_format)
: AudioSource (s, path), _flags (flags), : AudioSource (s, path), _flags (flags),
_channel (0) _channel (0)
{ {
/* constructor used for new internal-to-session files. file cannot exist */
_is_embedded = false; _is_embedded = false;
if (init (path, false)) { if (init (path, false)) {
@ -111,12 +111,11 @@ AudioFileSource::AudioFileSource (Session& s, ustring path, Flag flags, SampleFo
} }
} }
/** Constructor used for existing internal-to-session files. File must exist. */
AudioFileSource::AudioFileSource (Session& s, const XMLNode& node, bool must_exist) AudioFileSource::AudioFileSource (Session& s, const XMLNode& node, bool must_exist)
: AudioSource (s, node), _flags (Flag (Writable|CanRename)) : 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)) { if (set_state (node)) {
throw failed_constructor (); throw failed_constructor ();
} }
@ -363,31 +362,23 @@ AudioFileSource::move_to_trash (const ustring& trash_dir_name)
return -1; return -1;
} }
ustring newpath;
if (!writable()) { if (!writable()) {
return -1; return -1;
} }
/* don't move the file across filesystems, just /* don't move the file across filesystems, just stick it in the
stick it in the `trash_dir_name' directory trash_dir_name directory on whichever filesystem it was already on
on whichever filesystem it was already on.
*/ */
ustring newpath;
newpath = Glib::path_get_dirname (_path); newpath = Glib::path_get_dirname (_path);
newpath = Glib::path_get_dirname (newpath); newpath = Glib::path_get_dirname (newpath);
cerr << "from " << _path << " dead dir looks like " << newpath << endl; newpath += string("/") + trash_dir_name + "/";
newpath += '/';
newpath += trash_dir_name;
newpath += '/';
newpath += Glib::path_get_basename (_path); newpath += Glib::path_get_basename (_path);
/* the new path already exists, try versioning */
if (access (newpath.c_str(), F_OK) == 0) { if (access (newpath.c_str(), F_OK) == 0) {
/* the new path already exists, try versioning */
char buf[PATH_MAX+1]; char buf[PATH_MAX+1];
int version = 1; int version = 1;
ustring newpath_v; ustring newpath_v;
@ -401,23 +392,19 @@ AudioFileSource::move_to_trash (const ustring& trash_dir_name)
} }
if (version == 999) { if (version == 999) {
error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"), PBD::error << string_compose (
newpath) _("there are already 1000 files with names like %1; versioning discontinued"),
<< endmsg; newpath)
<< endmsg;
} else { } else {
newpath = newpath_v; 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) { if (::rename (_path.c_str(), newpath.c_str()) != 0) {
error << string_compose (_("cannot rename audio file source from %1 to %2 (%3)"), PBD::error << string_compose (
_path, newpath, strerror (errno)) _("cannot rename midi file source from %1 to %2 (%3)"),
<< endmsg; _path, newpath, strerror (errno)) << endmsg;
return -1; return -1;
} }
@ -434,7 +421,6 @@ AudioFileSource::move_to_trash (const ustring& trash_dir_name)
peakpath = ""; peakpath = "";
/* file can not be removed twice, since the operation is not idempotent */ /* file can not be removed twice, since the operation is not idempotent */
_flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty)); _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
return 0; return 0;
@ -467,7 +453,6 @@ AudioFileSource::find (ustring& pathstr, bool must_exist, bool& isnew, uint16_t&
cnt = 0; cnt = 0;
for (vector<ustring>::iterator i = dirs.begin(); i != dirs.end(); ++i) { for (vector<ustring>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
fullpath = *i; fullpath = *i;
if (fullpath[fullpath.length()-1] != '/') { if (fullpath[fullpath.length()-1] != '/') {
fullpath += '/'; fullpath += '/';
@ -544,15 +529,14 @@ AudioFileSource::find (ustring& pathstr, bool must_exist, bool& isnew, uint16_t&
} }
/* Current find() is unable to parse relative path names to yet non-existant /* Current find() is unable to parse relative path names to yet non-existant
sources. QuickFix(tm) */ sources. QuickFix(tm) */
if (keeppath == "") { if (keeppath == "") {
if (must_exist) { if (must_exist) {
error << "AudioFileSource::find(), keeppath = \"\", but the file must exist" << endl; error << "AudioFileSource::find(), keeppath = \"\", but the file must exist" << endl;
} else { } else {
keeppath = pathstr; keeppath = pathstr;
} }
}
}
_name = pathstr; _name = pathstr;
_path = keeppath; _path = keeppath;

View file

@ -320,8 +320,8 @@ write_midi_data_to_new_files (Evoral::SMF* source, Session::import_status& statu
try { try {
for (unsigned i = 1; i <= source->num_tracks(); ++i) { for (unsigned i = 1; i <= source->num_tracks(); ++i) {
boost::shared_ptr<SMFSource> smfs = boost::dynamic_pointer_cast<SMFSource>(newfiles[i-1]); boost::shared_ptr<SMFSource> smfs = boost::dynamic_pointer_cast<SMFSource>(newfiles[i-1]);
smfs->drop_model();
source->seek_to_track(i); source->seek_to_track(i);
@ -346,11 +346,10 @@ write_midi_data_to_new_files (Evoral::SMF* source, Session::import_status& statu
continue; continue;
} }
smfs->append_event_unlocked(Beats, Evoral::Event<double>( smfs->append_event_unlocked_beats(Evoral::Event<double>(0,
0, (double)t / (double)source->ppqn(),
(double)t / (double)source->ppqn(), size,
size, buf));
buf));
if (status.progress < 0.99) if (status.progress < 0.99)
status.progress += 0.01; status.progress += 0.01;

View file

@ -44,7 +44,6 @@ PeakMeter::run_in_place (BufferSet& bufs, nframes_t start_frame, nframes_t end_f
// Meter what we have (midi) // Meter what we have (midi)
for ( ; n < limit; ++n) { for ( ; n < limit; ++n) {
float val = 0; float val = 0;
// GUI needs a better MIDI meter, not much information can be // GUI needs a better MIDI meter, not much information can be

View file

@ -295,9 +295,11 @@ MidiModel::write_to(boost::shared_ptr<MidiSource> source)
const bool old_percussive = percussive(); const bool old_percussive = percussive();
set_percussive(false); set_percussive(false);
source->drop_model();
for (Evoral::Sequence<TimeType>::const_iterator i = begin(); i != end(); ++i) { 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); set_percussive(old_percussive);

View file

@ -32,11 +32,13 @@
#include <pbd/pthread_utils.h> #include <pbd/pthread_utils.h>
#include <pbd/basename.h> #include <pbd/basename.h>
#include <ardour/midi_source.h> #include <ardour/audioengine.h>
#include <ardour/midi_ring_buffer.h> #include <ardour/midi_ring_buffer.h>
#include <ardour/midi_source.h>
#include <ardour/session.h> #include <ardour/session.h>
#include <ardour/session_directory.h> #include <ardour/session_directory.h>
#include <ardour/source_factory.h> #include <ardour/source_factory.h>
#include <ardour/tempo.h>
#include "i18n.h" #include "i18n.h"
@ -105,28 +107,39 @@ MidiSource::set_state (const XMLNode& node)
} }
nframes_t 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); Glib::Mutex::Lock lm (_lock);
if (_model) { 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; Evoral::Sequence<double>::const_iterator& i = _model_iter;
if (_last_read_end == 0 || start != _last_read_end) { if (_last_read_end == 0 || start != _last_read_end) {
i = _model->begin(); cerr << "MidiSource::midi_read seeking to frame " << start << endl;
cerr << "MidiSource::midi_read seeking to " << start << endl; for (i = _model->begin(); i != _model->end(); ++i) {
while (i != _model->end() && i->time() < start) if (BEATS_TO_FRAMES(i->time()) >= start) {
++i; break;
}
}
} }
_last_read_end = start + cnt; _last_read_end = start + cnt;
if (i == _model->end()) { for (; i != _model->end(); ++i) {
return cnt; 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());
while (i->time() < start + cnt && i != _model->end()) { } else {
dst.write(i->time(), i->event_type(), i->size(), i->buffer()); break;
++i; }
} }
return cnt; return cnt;
} else { } else {
@ -148,7 +161,7 @@ MidiSource::file_changed (string path)
int e1 = stat (path.c_str(), &stat_file); int e1 = stat (path.c_str(), &stat_file);
return ( !e1 ); return !e1;
} }
void void

View file

@ -48,15 +48,15 @@ using namespace ARDOUR;
string SMFSource::_search_path; string SMFSource::_search_path;
/** Constructor used for new internal-to-session files. File cannot exist. */
SMFSource::SMFSource(Session& s, std::string path, Flag flags) SMFSource::SMFSource(Session& s, std::string path, Flag flags)
: MidiSource(s, region_name_from_path(path, false)) : MidiSource(s, region_name_from_path(path, false))
, Evoral::SMF() , Evoral::SMF()
, _flags(flags) , _flags(flags)
, _allow_remove_if_empty(true) , _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)) { if (init(path, false)) {
throw failed_constructor (); throw failed_constructor ();
} }
@ -68,14 +68,14 @@ SMFSource::SMFSource(Session& s, std::string path, Flag flags)
assert(_name.find("/") == string::npos); assert(_name.find("/") == string::npos);
} }
/** Constructor used for existing internal-to-session files. File must exist. */
SMFSource::SMFSource(Session& s, const XMLNode& node) SMFSource::SMFSource(Session& s, const XMLNode& node)
: MidiSource(s, node) : MidiSource(s, node)
, _flags(Flag(Writable|CanRename)) , _flags(Flag(Writable|CanRename))
, _allow_remove_if_empty(true) , _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)) { if (set_state(node)) {
throw failed_constructor (); throw failed_constructor ();
} }
@ -146,7 +146,6 @@ SMFSource::read_unlocked (MidiRingBuffer<nframes_t>& dst, nframes_t start, nfram
// FIXME: assumes tempo never changes after start // FIXME: assumes tempo never changes after start
const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position); const Tempo& tempo = _session.tempo_map().tempo_at(_timeline_position);
const double frames_per_beat = tempo.frames_per_beat( const double frames_per_beat = tempo.frames_per_beat(
_session.engine().frame_rate(), _session.engine().frame_rate(),
_session.tempo_map().meter_at(_timeline_position)); _session.tempo_map().meter_at(_timeline_position));
@ -205,7 +204,7 @@ SMFSource::read_unlocked (MidiRingBuffer<nframes_t>& dst, nframes_t start, nfram
/** All stamps in audio frames */ /** All stamps in audio frames */
nframes_t 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; _write_data_count = 0;
@ -220,11 +219,11 @@ SMFSource::write_unlocked (MidiRingBuffer<nframes_t>& src, nframes_t cnt)
_model->start_write(); _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) { while (true) {
bool ret = src.peek_time(&time); bool ret = src.peek_time(&time);
if (!ret || time - _timeline_position > _length + cnt) { if (!ret || time - _timeline_position > _length + dur) {
break; break;
} }
@ -255,11 +254,7 @@ SMFSource::write_unlocked (MidiRingBuffer<nframes_t>& src, nframes_t cnt)
continue; continue;
} }
append_event_unlocked(Frames, ev); append_event_unlocked_frames(ev);
if (_model) {
_model->append(ev);
}
} }
if (_model) { if (_model) {
@ -270,55 +265,83 @@ SMFSource::write_unlocked (MidiRingBuffer<nframes_t>& src, nframes_t cnt)
free(buf); free(buf);
const nframes_t oldlen = _length; const nframes_t oldlen = _length;
update_length(oldlen, cnt); update_length(oldlen, dur);
ViewDataRangeReady(_timeline_position + oldlen, cnt); /* EMIT SIGNAL */ ViewDataRangeReady(_timeline_position + oldlen, dur); /* EMIT SIGNAL */
return cnt; return dur;
} }
/** Append an event with a timestamp in beats (double) */
void 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) { if (ev.size() == 0) {
cerr << "SMFSource: Warning: skipping empty event" << endl;
return; return;
} }
/* /*printf("SMFSource: %s - append_event_unlocked_beats time = %lf, size = %u, data = ",
printf("SMFSource: %s - append_event_unlocked time = %lf, size = %u, data = ",
name().c_str(), ev.time(), ev.size()); name().c_str(), ev.time(), ev.size());
for (size_t i=0; i < ev.size(); ++i) { for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");*/
printf("%X ", ev.buffer()[i]);
} printf("\n");
*/
assert(ev.time() >= 0); assert(ev.time() >= 0);
if (ev.time() < _last_ev_time_beats) {
if (ev.time() < last_event_time()) { cerr << "SMFSource: Warning: Skipping event with non-monotonic time" << endl;
cerr << "SMFSource: Warning: Skipping event with ev.time() < last.time()" << endl;
return; return;
} }
uint32_t delta_time = 0; 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());
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));
delta_time = (uint32_t)((ev.time() - last_event_time()) / frames_per_beat * ppqn()); Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer());
} else { _last_ev_time_beats = ev.time();
assert(unit == Beats);
delta_time = (uint32_t)((ev.time() - last_event_time()) * ppqn());
}
Evoral::SMF::append_event_delta(delta_time, ev.size(), ev.buffer());
_last_ev_time = ev.time();
_write_data_count += ev.size(); _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");*/
assert(ev.time() >= 0);
if (ev.time() < _last_ev_time_frames) {
cerr << "SMFSource: Warning: Skipping event with non-monotonic time" << endl;
return;
}
// 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_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_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);
}
} }
@ -368,7 +391,8 @@ SMFSource::mark_streaming_midi_write_started (NoteMode mode, nframes_t start_fra
{ {
MidiSource::mark_streaming_midi_write_started (mode, start_frame); MidiSource::mark_streaming_midi_write_started (mode, start_frame);
Evoral::SMF::begin_write (); Evoral::SMF::begin_write ();
_last_ev_time = 0; _last_ev_time_beats = 0.0;
_last_ev_time_frames = 0;
} }
void void
@ -395,29 +419,23 @@ SMFSource::mark_take (string id)
int int
SMFSource::move_to_trash (const string trash_dir_name) SMFSource::move_to_trash (const string trash_dir_name)
{ {
string newpath;
if (!writable()) { if (!writable()) {
return -1; return -1;
} }
/* don't move the file across filesystems, just /* don't move the file across filesystems, just stick it in the
stick it in the 'trash_dir_name' directory trash_dir_name directory on whichever filesystem it was already on
on whichever filesystem it was already on.
*/ */
Glib::ustring newpath;
newpath = Glib::path_get_dirname (_path); newpath = Glib::path_get_dirname (_path);
newpath = Glib::path_get_dirname (newpath); newpath = Glib::path_get_dirname (newpath);
newpath += '/'; newpath += string("/") + trash_dir_name + "/";
newpath += trash_dir_name;
newpath += '/';
newpath += Glib::path_get_basename (_path); newpath += Glib::path_get_basename (_path);
/* the new path already exists, try versioning */
if (access (newpath.c_str(), F_OK) == 0) { if (access (newpath.c_str(), F_OK) == 0) {
/* the new path already exists, try versioning */
char buf[PATH_MAX+1]; char buf[PATH_MAX+1];
int version = 1; int version = 1;
string newpath_v; string newpath_v;
@ -431,28 +449,24 @@ SMFSource::move_to_trash (const string trash_dir_name)
} }
if (version == 999) { if (version == 999) {
PBD::error << string_compose (_("there are already 1000 files with names like %1; versioning discontinued"), PBD::error << string_compose (
newpath) _("there are already 1000 files with names like %1; versioning discontinued"),
<< endmsg; newpath) << endmsg;
} else { } else {
newpath = newpath_v; 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) { if (::rename (_path.c_str(), newpath.c_str()) != 0) {
PBD::error << string_compose (_("cannot rename midi file source from %1 to %2 (%3)"), PBD::error << string_compose (
_path, newpath, strerror (errno)) _("cannot rename midi file source from %1 to %2 (%3)"),
<< endmsg; _path, newpath, strerror (errno)) << endmsg;
return -1; return -1;
} }
_path = newpath;
/* file can not be removed twice, since the operation is not idempotent */ /* file can not be removed twice, since the operation is not idempotent */
_flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty)); _flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
return 0; return 0;
@ -468,19 +482,10 @@ SMFSource::safe_file_extension(const Glib::ustring& file)
bool bool
SMFSource::find (string pathstr, bool must_exist, bool& isnew) SMFSource::find (string pathstr, bool must_exist, bool& isnew)
{ {
string::size_type pos;
bool ret = false; bool ret = false;
isnew = 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] != '/') { if (pathstr[0] != '/') {
/* non-absolute pathname: find pathstr in search path */ /* non-absolute pathname: find pathstr in search path */
@ -500,7 +505,6 @@ SMFSource::find (string pathstr, bool must_exist, bool& isnew)
cnt = 0; cnt = 0;
for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) { for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
fullpath = *i; fullpath = *i;
if (fullpath[fullpath.length()-1] != '/') { if (fullpath[fullpath.length()-1] != '/') {
fullpath += '/'; fullpath += '/';
@ -640,24 +644,15 @@ SMFSource::load_model(bool lock, bool force_reload)
size_t scratch_size = 0; // keep track of scratch and minimize reallocs 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 delta_t = 0;
uint32_t size = 0; uint32_t size = 0;
uint8_t* buf = NULL; uint8_t* buf = NULL;
int ret; int ret;
while ((ret = read_event(&delta_t, &size, &buf)) >= 0) { while ((ret = read_event(&delta_t, &size, &buf)) >= 0) {
ev.set(buf, size, 0.0);
time += delta_t; time += delta_t;
ev.set(buf, size, time / (double)ppqn());
if (ret > 0) { // didn't skip (meta) event 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])); ev.set_event_type(EventTypeMap::instance().midi_event_type(buf[0]));
_model->append(ev); _model->append(ev);
} }