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(
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;
@ -186,8 +185,8 @@ CanvasNoteEvent::base_color()
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:
@ -197,12 +196,12 @@ CanvasNoteEvent::base_color()
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());
@ -221,9 +220,13 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
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:
@ -284,7 +287,7 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
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,
@ -311,12 +314,10 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
}
_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;
@ -356,16 +357,16 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
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();
@ -375,12 +376,9 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
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;

View File

@ -12,18 +12,17 @@ using namespace MIDI::Name;
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,
@ -31,8 +30,7 @@ CanvasProgramChange::CanvasProgramChange(
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)

View File

@ -508,7 +508,7 @@ Editor::popup_ruler_menu (nframes64_t where, ItemType t)
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;
}

View File

@ -64,11 +64,12 @@ using namespace PBD;
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())
@ -82,11 +83,13 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &
_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)
@ -104,7 +107,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other)
: 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)
@ -122,11 +125,11 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other)
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)
@ -151,11 +154,6 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd)
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;
@ -208,14 +206,14 @@ MidiRegionView::canvas_event(GdkEvent* ev)
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;
@ -282,7 +280,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
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;
@ -305,7 +303,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
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;
@ -316,13 +314,14 @@ MidiRegionView::canvas_event(GdkEvent* ev)
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;
@ -385,7 +384,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
switch (_mouse_state) {
case Pressed: // Clicked
switch (trackview.editor().current_midi_edit_mode()) {
switch (midi_edit_mode) {
case MidiEditSelect:
case MidiEditResize:
clear_selection();
@ -408,7 +407,7 @@ MidiRegionView::canvas_event(GdkEvent* ev)
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;
@ -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
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 <= 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");
@ -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;
}
_events.clear();
_pgm_changes.clear();
@ -491,26 +483,29 @@ MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
{
_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
@ -558,14 +553,12 @@ MidiRegionView::redisplay_model()
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())
@ -580,7 +573,7 @@ MidiRegionView::redisplay_model()
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();
@ -619,22 +612,22 @@ MidiRegionView::redisplay_model()
}
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);
@ -651,7 +644,8 @@ MidiRegionView::find_and_insert_program_change_flags()
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);
@ -660,13 +654,13 @@ MidiRegionView::find_and_insert_program_change_flags()
_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];
@ -676,7 +670,8 @@ MidiRegionView::find_and_insert_program_change_flags()
}
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;
}
}
}
@ -705,8 +700,9 @@ MidiRegionView::region_resized (Change what_changed)
RegionView::region_resized(what_changed);
if (what_changed & ARDOUR::PositionChanged) {
if (_enable_display)
if (_enable_display) {
redisplay_model();
}
}
}
@ -716,14 +712,15 @@ MidiRegionView::reset_width_dependent_items (double pixel_width)
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;
@ -811,8 +808,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv)
audio waveforms under it.
*/
ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position);
}
else {
} else {
ghost = new MidiGhostRegion (tv, trackview, unit_position);
}
@ -861,8 +857,9 @@ MidiRegionView::end_write()
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);
@ -898,9 +895,11 @@ MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
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);
@ -912,7 +911,8 @@ MidiRegionView::play_midi_note_off(boost::shared_ptr<NoteType> note)
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;
}
@ -930,11 +930,14 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
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;
}
@ -942,13 +945,12 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
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;
@ -996,10 +998,6 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note)
}
} 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);
@ -1054,7 +1052,6 @@ MidiRegionView::add_pgm_change(ControlEvent& program, string displaytext)
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);
@ -1090,7 +1087,6 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch
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);
@ -1132,8 +1128,7 @@ MidiRegionView::previous_program(CanvasProgramChange& program)
_model_name,
_custom_device_mode,
program.channel(),
key
);
key);
ControlEvent program_change_event(program.event_time(), program.program(), program.channel());
if (patch) {
@ -1152,8 +1147,7 @@ MidiRegionView::next_program(CanvasProgramChange& program)
_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());
@ -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()));
// 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;
}
@ -1391,13 +1381,10 @@ nframes64_t
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
@ -1426,6 +1413,22 @@ MidiRegionView::get_position_pixels()
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)
{
@ -1440,32 +1443,25 @@ 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;
@ -1484,8 +1480,8 @@ void
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();

View File

@ -150,13 +150,13 @@ class MidiRegionView : public RegionView
/** 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);
@ -175,19 +175,16 @@ class MidiRegionView : public RegionView
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
@ -215,7 +212,15 @@ class MidiRegionView : public RegionView
*/
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 {
@ -243,8 +248,13 @@ class MidiRegionView : public RegionView
*/
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.
*/
@ -272,7 +282,7 @@ class MidiRegionView : public RegionView
* (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);
@ -282,7 +292,7 @@ class MidiRegionView : public RegionView
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); }

View File

@ -57,10 +57,12 @@ class MidiSource : public Source
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);
@ -95,6 +97,7 @@ class MidiSource : public Source
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;

View File

@ -63,8 +63,9 @@ class SMFSource : public MidiSource, public Evoral::SMF {
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);
@ -83,8 +84,6 @@ class SMFSource : public MidiSource, public Evoral::SMF {
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:
@ -111,7 +110,8 @@ class SMFSource : public MidiSource, public Evoral::SMF {
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;
};

View File

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

View File

@ -86,11 +86,11 @@ struct SizedSampleBuffer {
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)) {
@ -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)
: 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)) {
@ -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)
: 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 ();
}
@ -363,31 +362,23 @@ AudioFileSource::move_to_trash (const ustring& trash_dir_name)
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;
@ -401,23 +392,19 @@ AudioFileSource::move_to_trash (const ustring& trash_dir_name)
}
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;
}
@ -434,7 +421,6 @@ AudioFileSource::move_to_trash (const ustring& trash_dir_name)
peakpath = "";
/* file can not be removed twice, since the operation is not idempotent */
_flags = Flag (_flags & ~(RemoveAtDestroy|Removable|RemovableIfEmpty));
return 0;
@ -467,7 +453,6 @@ AudioFileSource::find (ustring& pathstr, bool must_exist, bool& isnew, uint16_t&
cnt = 0;
for (vector<ustring>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
fullpath = *i;
if (fullpath[fullpath.length()-1] != '/') {
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
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;

View File

@ -320,8 +320,8 @@ write_midi_data_to_new_files (Evoral::SMF* source, Session::import_status& statu
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);
@ -346,11 +346,10 @@ write_midi_data_to_new_files (Evoral::SMF* source, Session::import_status& statu
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;

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)
for ( ; n < limit; ++n) {
float val = 0;
// 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();
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);

View File

@ -32,11 +32,13 @@
#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"
@ -105,28 +107,39 @@ MidiSource::set_state (const XMLNode& node)
}
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 {
@ -148,7 +161,7 @@ MidiSource::file_changed (string path)
int e1 = stat (path.c_str(), &stat_file);
return ( !e1 );
return !e1;
}
void

View File

@ -48,15 +48,15 @@ using namespace ARDOUR;
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 ();
}
@ -68,14 +68,14 @@ SMFSource::SMFSource(Session& s, std::string path, Flag flags)
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 ();
}
@ -146,7 +146,6 @@ SMFSource::read_unlocked (MidiRingBuffer<nframes_t>& dst, nframes_t start, nfram
// 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));
@ -205,7 +204,7 @@ SMFSource::read_unlocked (MidiRingBuffer<nframes_t>& dst, nframes_t start, nfram
/** 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;
@ -220,11 +219,11 @@ SMFSource::write_unlocked (MidiRingBuffer<nframes_t>& src, nframes_t cnt)
_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;
}
@ -255,11 +254,7 @@ SMFSource::write_unlocked (MidiRingBuffer<nframes_t>& src, nframes_t cnt)
continue;
}
append_event_unlocked(Frames, ev);
if (_model) {
_model->append(ev);
}
append_event_unlocked_frames(ev);
}
if (_model) {
@ -270,55 +265,83 @@ SMFSource::write_unlocked (MidiRingBuffer<nframes_t>& src, nframes_t cnt)
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_event_time()) {
cerr << "SMFSource: Warning: Skipping event with ev.time() < last.time()" << endl;
if (ev.time() < _last_ev_time_beats) {
cerr << "SMFSource: Warning: Skipping event with non-monotonic time" << endl;
return;
}
uint32_t delta_time = 0;
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));
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());
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());
}
Evoral::SMF::append_event_delta(delta_time, ev.size(), ev.buffer());
_last_ev_time = ev.time();
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");*/
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);
Evoral::SMF::begin_write ();
_last_ev_time = 0;
_last_ev_time_beats = 0.0;
_last_ev_time_frames = 0;
}
void
@ -395,29 +419,23 @@ SMFSource::mark_take (string id)
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;
@ -431,28 +449,24 @@ SMFSource::move_to_trash (const string trash_dir_name)
}
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;
@ -468,19 +482,10 @@ SMFSource::safe_file_extension(const Glib::ustring& file)
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 */
@ -500,7 +505,6 @@ SMFSource::find (string pathstr, bool must_exist, bool& isnew)
cnt = 0;
for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {
fullpath = *i;
if (fullpath[fullpath.length()-1] != '/') {
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
// 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);
}