summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <carl@carlh.net>2010-08-05 13:36:38 +0000
committerCarl Hetherington <carl@carlh.net>2010-08-05 13:36:38 +0000
commit5e3ca4db5cc356385e520643cfd279f393db4b51 (patch)
tree2ea31a74d34e37e33bc5b126a5e7f1b58753b4f0
parente7a2b99f3d4f7afe73a30ac85e770e228785c1be (diff)
Support cut / copy / paste of MIDI automation.
git-svn-id: svn://localhost/ardour2/branches/3.0@7545 d708f5d6-7413-0410-9779-e7cbd77b26cf
-rw-r--r--gtk2_ardour/automation_line.cc35
-rw-r--r--gtk2_ardour/automation_line.h6
-rw-r--r--gtk2_ardour/automation_selectable.h46
-rw-r--r--gtk2_ardour/automation_streamview.cc50
-rw-r--r--gtk2_ardour/automation_streamview.h3
-rw-r--r--gtk2_ardour/automation_time_axis.cc42
-rw-r--r--gtk2_ardour/automation_time_axis.h4
-rw-r--r--gtk2_ardour/editor_selection.cc6
-rw-r--r--gtk2_ardour/region_view.cc2
-rw-r--r--gtk2_ardour/route_time_axis.cc2
-rw-r--r--gtk2_ardour/route_time_axis.h2
-rw-r--r--gtk2_ardour/time_axis_view.h2
-rw-r--r--libs/ardour/ardour/beats_frames_converter.h8
-rw-r--r--libs/ardour/beats_frames_converter.cc8
-rw-r--r--libs/evoral/evoral/TimeConverter.hpp12
-rw-r--r--libs/evoral/src/ControlList.cpp8
16 files changed, 169 insertions, 67 deletions
diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc
index dd5f11f042..9ca1c59bb0 100644
--- a/gtk2_ardour/automation_line.cc
+++ b/gtk2_ardour/automation_line.cc
@@ -947,15 +947,25 @@ AutomationLine::remove_point (ControlPoint& cp)
trackview.editor().session()->set_dirty ();
}
+/** Get selectable points within an area.
+ * @param start Start position in session frames.
+ * @param end End position in session frames.
+ * @param botfrac Bottom of area, as a fraction of the line height.
+ * @param topfrac Bottom of area, as a fraction of the line height.
+ */
void
-AutomationLine::get_selectables (nframes_t start, nframes_t end,
- double botfrac, double topfrac, list<Selectable*>& results)
+AutomationLine::get_selectables (
+ framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
+ )
{
double top;
double bot;
- sframes_t nstart;
- sframes_t nend;
+
+ /* these two are in AutomationList model coordinates */
+ double nstart;
+ double nend;
+
bool collecting = false;
/* Curse X11 and its inverted coordinate system! */
@@ -963,21 +973,22 @@ AutomationLine::get_selectables (nframes_t start, nframes_t end,
bot = (1.0 - topfrac) * _height;
top = (1.0 - botfrac) * _height;
- nstart = max_frames;
+ nstart = DBL_MAX;
nend = 0;
for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
- sframes_t const when = _time_converter.to ((*(*i)->model())->when);
+ double const model_when = (*(*i)->model())->when;
+ framepos_t const session_frames_when = _time_converter.to (model_when) + _time_converter.origin_b ();
- if (when >= start && when <= end) {
+ if (session_frames_when >= start && session_frames_when <= end) {
if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
(*i)->show();
(*i)->set_visible(true);
collecting = true;
- nstart = min (nstart, when);
- nend = max (nend, when);
+ nstart = min (nstart, model_when);
+ nend = max (nend, model_when);
} else {
@@ -985,7 +996,7 @@ AutomationLine::get_selectables (nframes_t start, nframes_t end,
results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
collecting = false;
- nstart = max_frames;
+ nstart = DBL_MAX;
nend = 0;
}
}
@@ -1023,8 +1034,8 @@ AutomationLine::point_selection_to_control_points (PointSelection const & s)
for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
- double const rstart = trackview.editor().frame_to_unit (i->start);
- double const rend = trackview.editor().frame_to_unit (i->end);
+ double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start));
+ double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end));
if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
diff --git a/gtk2_ardour/automation_line.h b/gtk2_ardour/automation_line.h
index 0222328634..1464170c34 100644
--- a/gtk2_ardour/automation_line.h
+++ b/gtk2_ardour/automation_line.h
@@ -68,7 +68,7 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible
std::list<ControlPoint*> point_selection_to_control_points (PointSelection const &);
void set_selected_points (PointSelection&);
- void get_selectables (nframes_t start, nframes_t end,
+ void get_selectables (ARDOUR::framepos_t start, ARDOUR::framepos_t end,
double botfrac, double topfrac,
std::list<Selectable*>& results);
void get_inverted_selectables (Selection&, std::list<Selectable*>& results);
@@ -136,6 +136,10 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible
virtual MementoCommandBinder<ARDOUR::AutomationList>* memento_command_binder ();
+ const Evoral::TimeConverter<double, ARDOUR::sframes_t>& time_converter () const {
+ return _time_converter;
+ }
+
protected:
std::string _name;
diff --git a/gtk2_ardour/automation_selectable.h b/gtk2_ardour/automation_selectable.h
index 7104f6adf4..8c897816ba 100644
--- a/gtk2_ardour/automation_selectable.h
+++ b/gtk2_ardour/automation_selectable.h
@@ -20,35 +20,37 @@
#ifndef __ardour_gtk_automation_selectable_h__
#define __ardour_gtk_automation_selectable_h__
-#include "ardour/types.h"
#include "selectable.h"
class TimeAxisView;
-/** A selected automation point, expressed as a rectangle on a track (so that x coordinates
- * are frames and y coordinates are a fraction of track height). This representation falls
- * between the visible GUI control points and the back-end "actual" automation points,
- * some of which may not be visible; it is not trivial to convert from one of these to the other,
- * so the AutomationSelectable is a kind of "best and worst of both worlds".
+/** A selected automation point, expressed as a rectangle.
+ * x coordinates start/end are in AutomationList model coordinates.
+ * y coordinates are a expressed as a fraction of track height.
+ * This representation falls between the visible GUI control points and
+ * the back-end "actual" automation points, some of which may not be
+ * visible; it is not trivial to convert from one of these to the
+ * other, so the AutomationSelectable is a kind of "best and worst of
+ * both worlds".
*/
struct AutomationSelectable : public Selectable
{
- nframes_t start;
- nframes_t end;
- double low_fract;
- double high_fract;
- TimeAxisView* track; // ref would be better, but ARDOUR::SessionHandlePtr is non-assignable
-
- AutomationSelectable (nframes_t s, nframes_t e, double l, double h, TimeAxisView* atv)
- : start (s), end (e), low_fract (l), high_fract (h), track (atv) {}
-
- bool operator== (const AutomationSelectable& other) {
- return start == other.start &&
- end == other.end &&
- low_fract == other.low_fract &&
- high_fract == other.high_fract &&
- track == other.track;
- }
+ double start;
+ double end;
+ double low_fract;
+ double high_fract;
+ TimeAxisView* track; // ref would be better, but ARDOUR::SessionHandlePtr is non-assignable
+
+ AutomationSelectable (double s, double e, double l, double h, TimeAxisView* atv)
+ : start (s), end (e), low_fract (l), high_fract (h), track (atv) {}
+
+ bool operator== (const AutomationSelectable& other) {
+ return start == other.start &&
+ end == other.end &&
+ low_fract == other.low_fract &&
+ high_fract == other.high_fract &&
+ track == other.track;
+ }
};
#endif /* __ardour_gtk_automation_selectable_h__ */
diff --git a/gtk2_ardour/automation_streamview.cc b/gtk2_ardour/automation_streamview.cc
index 6beb439b11..90e1b991fd 100644
--- a/gtk2_ardour/automation_streamview.cc
+++ b/gtk2_ardour/automation_streamview.cc
@@ -274,13 +274,16 @@ AutomationStreamView::clear ()
}
}
+/** @param start Start position in session frames.
+ * @param end End position in session frames.
+ */
void
-AutomationStreamView::get_selectables (nframes_t start, nframes_t end, double botfrac, double topfrac, list<Selectable*>& results)
+AutomationStreamView::get_selectables (framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results)
{
for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*i);
assert (arv);
- arv->line()->get_selectables (start - (*i)->region()->position(), end - (*i)->region()->position(), botfrac, topfrac, results);
+ arv->line()->get_selectables (start, end, botfrac, topfrac, results);
}
}
@@ -307,3 +310,46 @@ AutomationStreamView::get_lines () const
return lines;
}
+
+struct RegionPositionSorter {
+ bool operator() (RegionView* a, RegionView* b) {
+ return a->region()->position() < b->region()->position();
+ }
+};
+
+
+/** @param pos Position, in session frames.
+ * @return AutomationLine to paste to for that position, or 0 if there is none appropriate.
+ */
+boost::shared_ptr<AutomationLine>
+AutomationStreamView::paste_line (framepos_t pos)
+{
+ /* XXX: not sure how best to pick this; for now, just use the last region which starts before pos */
+
+ if (region_views.empty()) {
+ return boost::shared_ptr<AutomationLine> ();
+ }
+
+ region_views.sort (RegionPositionSorter ());
+
+ list<RegionView*>::const_iterator prev = region_views.begin ();
+
+ for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
+ if ((*i)->region()->position() > pos) {
+ break;
+ }
+ prev = i;
+ }
+
+ boost::shared_ptr<Region> r = (*prev)->region ();
+
+ /* If *prev doesn't cover pos, it's no good */
+ if (r->position() > pos || ((r->position() + r->length()) < pos)) {
+ return boost::shared_ptr<AutomationLine> ();
+ }
+
+ AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*prev);
+ assert (arv);
+
+ return arv->line ();
+}
diff --git a/gtk2_ardour/automation_streamview.h b/gtk2_ardour/automation_streamview.h
index 8afacd79cf..257908c9ea 100644
--- a/gtk2_ardour/automation_streamview.h
+++ b/gtk2_ardour/automation_streamview.h
@@ -61,10 +61,11 @@ class AutomationStreamView : public StreamView
void clear ();
- void get_selectables (nframes_t, nframes_t, double, double, std::list<Selectable*> &);
+ void get_selectables (ARDOUR::framepos_t, ARDOUR::framepos_t, double, double, std::list<Selectable*> &);
void set_selected_points (PointSelection &);
std::list<boost::shared_ptr<AutomationLine> > get_lines () const;
+ boost::shared_ptr<AutomationLine> paste_line (ARDOUR::framepos_t);
private:
void setup_rec_box ();
diff --git a/gtk2_ardour/automation_time_axis.cc b/gtk2_ardour/automation_time_axis.cc
index 448cf4e501..cb0c85ae61 100644
--- a/gtk2_ardour/automation_time_axis.cc
+++ b/gtk2_ardour/automation_time_axis.cc
@@ -634,21 +634,27 @@ AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& sel
XMLNode &before = alist->get_state();
+ /* convert time selection to automation list model coordinates */
+ const Evoral::TimeConverter<double, ARDOUR::sframes_t>& tc = line.time_converter ();
+ double const start = tc.from (selection.time.front().start - tc.origin_b ());
+ double const end = tc.from (selection.time.front().end - tc.origin_b ());
+
switch (op) {
case Cut:
- if ((what_we_got = alist->cut (selection.time.front().start, selection.time.front().end)) != 0) {
+
+ if ((what_we_got = alist->cut (start, end)) != 0) {
_editor.get_cut_buffer().add (what_we_got);
_session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
}
break;
case Copy:
- if ((what_we_got = alist->copy (selection.time.front().start, selection.time.front().end)) != 0) {
+ if ((what_we_got = alist->copy (start, end)) != 0) {
_editor.get_cut_buffer().add (what_we_got);
}
break;
case Clear:
- if ((what_we_got = alist->cut (selection.time.front().start, selection.time.front().end)) != 0) {
+ if ((what_we_got = alist->cut (start, end)) != 0) {
_session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
}
break;
@@ -740,8 +746,6 @@ AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointS
delete &before;
- cout << "CCC objects " << what_we_got->size() << "\n";
-
if (what_we_got) {
for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
double when = (*x)->when;
@@ -753,14 +757,32 @@ AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointS
}
}
+/** Paste a selection.
+ * @param pos Position to paste to (session frames).
+ * @param times Number of times to paste.
+ * @param selection Selection to paste.
+ * @param nth Index of the AutomationList within the selection to paste from.
+ */
bool
-AutomationTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
+AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
{
- return paste_one (*_line, pos, times, selection, nth);
+ boost::shared_ptr<AutomationLine> line;
+
+ if (_line) {
+ line = _line;
+ } else if (_view) {
+ line = _view->paste_line (pos);
+ }
+
+ if (!line) {
+ return false;
+ }
+
+ return paste_one (*line, pos, times, selection, nth);
}
bool
-AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float times, Selection& selection, size_t nth)
+AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
{
AutomationSelection::iterator p;
boost::shared_ptr<AutomationList> alist(line.the_list());
@@ -786,8 +808,10 @@ AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float ti
(*x)->value = val;
}
+ double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
+
XMLNode &before = alist->get_state();
- alist->paste (copy, pos, times);
+ alist->paste (copy, model_pos, times);
_session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
return true;
diff --git a/gtk2_ardour/automation_time_axis.h b/gtk2_ardour/automation_time_axis.h
index 0da6c69acd..85ad6ad7d9 100644
--- a/gtk2_ardour/automation_time_axis.h
+++ b/gtk2_ardour/automation_time_axis.h
@@ -88,7 +88,7 @@ class AutomationTimeAxisView : public TimeAxisView {
void cut_copy_clear (Selection&, Editing::CutCopyOp);
void cut_copy_clear_objects (PointSelection&, Editing::CutCopyOp);
- bool paste (nframes_t, float times, Selection&, size_t nth);
+ bool paste (ARDOUR::framepos_t, float times, Selection&, size_t nth);
void reset_objects (PointSelection&);
int set_state (const XMLNode&, int version);
@@ -148,7 +148,7 @@ class AutomationTimeAxisView : public TimeAxisView {
void cut_copy_clear_one (AutomationLine&, Selection&, Editing::CutCopyOp);
void cut_copy_clear_objects_one (AutomationLine&, PointSelection&, Editing::CutCopyOp);
- bool paste_one (AutomationLine&, nframes_t, float times, Selection&, size_t nth);
+ bool paste_one (AutomationLine&, ARDOUR::framepos_t, float times, Selection&, size_t nth);
void reset_objects_one (AutomationLine&, PointSelection&);
void set_automation_state (ARDOUR::AutoState);
diff --git a/gtk2_ardour/editor_selection.cc b/gtk2_ardour/editor_selection.cc
index 6b15f40599..75b4f30d61 100644
--- a/gtk2_ardour/editor_selection.cc
+++ b/gtk2_ardour/editor_selection.cc
@@ -977,11 +977,13 @@ Editor::invert_selection ()
selection->set (touched);
}
-/** @param top Top (lower) y limit in trackview coordinates.
+/** @param start Start time in session frames.
+ * @param end End time in session frames.
+ * @param top Top (lower) y limit in trackview coordinates.
* @param bottom Bottom (higher) y limit in trackview coordinates.
*/
bool
-Editor::select_all_within (nframes64_t start, nframes64_t end, double top, double bot, const TrackViewList& tracklist, Selection::Operation op)
+Editor::select_all_within (framepos_t start, framepos_t end, double top, double bot, const TrackViewList& tracklist, Selection::Operation op)
{
list<Selectable*> found;
diff --git a/gtk2_ardour/region_view.cc b/gtk2_ardour/region_view.cc
index 8a9767bb02..890c16def6 100644
--- a/gtk2_ardour/region_view.cc
+++ b/gtk2_ardour/region_view.cc
@@ -269,7 +269,7 @@ RegionView::region_resized (const PropertyChange& what_changed)
if (what_changed.contains (ARDOUR::Properties::position)) {
set_position (_region->position(), 0);
- _time_converter.set_origin(_region->position());
+ _time_converter.set_origin_b (_region->position());
}
PropertyChange s_and_l;
diff --git a/gtk2_ardour/route_time_axis.cc b/gtk2_ardour/route_time_axis.cc
index 9e50b67fac..b4f56f2ffe 100644
--- a/gtk2_ardour/route_time_axis.cc
+++ b/gtk2_ardour/route_time_axis.cc
@@ -1363,7 +1363,7 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
}
bool
-RouteTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
+RouteTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
{
if (!is_track()) {
return false;
diff --git a/gtk2_ardour/route_time_axis.h b/gtk2_ardour/route_time_axis.h
index 6d832c8077..baef76ee61 100644
--- a/gtk2_ardour/route_time_axis.h
+++ b/gtk2_ardour/route_time_axis.h
@@ -93,7 +93,7 @@ public:
/* Editing operations */
void cut_copy_clear (Selection&, Editing::CutCopyOp);
- bool paste (nframes_t, float times, Selection&, size_t nth);
+ bool paste (ARDOUR::framepos_t, float times, Selection&, size_t nth);
TimeAxisView::Children get_child_list();
diff --git a/gtk2_ardour/time_axis_view.h b/gtk2_ardour/time_axis_view.h
index dfdff2954e..d84075977f 100644
--- a/gtk2_ardour/time_axis_view.h
+++ b/gtk2_ardour/time_axis_view.h
@@ -178,7 +178,7 @@ class TimeAxisView : public virtual AxisView, public PBD::Stateful
/* editing operations */
virtual void cut_copy_clear (Selection&, Editing::CutCopyOp) {}
- virtual bool paste (nframes_t, float /*times*/, Selection&, size_t /*nth*/) { return false; }
+ virtual bool paste (ARDOUR::framepos_t, float /*times*/, Selection&, size_t /*nth*/) { return false; }
virtual void set_selected_regionviews (RegionSelection&) {}
virtual void set_selected_points (PointSelection&) {}
diff --git a/libs/ardour/ardour/beats_frames_converter.h b/libs/ardour/ardour/beats_frames_converter.h
index 9db4448ebb..b1e44adbef 100644
--- a/libs/ardour/ardour/beats_frames_converter.h
+++ b/libs/ardour/ardour/beats_frames_converter.h
@@ -32,19 +32,15 @@ class TempoMap;
class BeatsFramesConverter : public Evoral::TimeConverter<double,sframes_t> {
public:
BeatsFramesConverter(const TempoMap& tempo_map, sframes_t origin)
- : _tempo_map(tempo_map)
- , _origin(origin)
+ : Evoral::TimeConverter<double, sframes_t> (origin)
+ , _tempo_map(tempo_map)
{}
sframes_t to(double beats) const;
double from(sframes_t frames) const;
- sframes_t origin() const { return _origin; }
- void set_origin(sframes_t origin) { _origin = origin; }
-
private:
const TempoMap& _tempo_map;
- sframes_t _origin;
};
} /* namespace ARDOUR */
diff --git a/libs/ardour/beats_frames_converter.cc b/libs/ardour/beats_frames_converter.cc
index f93e5e7224..9d3b8ae4c6 100644
--- a/libs/ardour/beats_frames_converter.cc
+++ b/libs/ardour/beats_frames_converter.cc
@@ -28,10 +28,10 @@ sframes_t
BeatsFramesConverter::to(double beats) const
{
// FIXME: assumes tempo never changes after origin
- const Tempo& tempo = _tempo_map.tempo_at(_origin);
+ const Tempo& tempo = _tempo_map.tempo_at (_origin_b);
const double frames_per_beat = tempo.frames_per_beat(
_tempo_map.frame_rate(),
- _tempo_map.meter_at(_origin));
+ _tempo_map.meter_at (_origin_b));
return lrint(beats * frames_per_beat);
}
@@ -40,10 +40,10 @@ double
BeatsFramesConverter::from(sframes_t frames) const
{
// FIXME: assumes tempo never changes after origin
- const Tempo& tempo = _tempo_map.tempo_at(_origin);
+ const Tempo& tempo = _tempo_map.tempo_at (_origin_b);
const double frames_per_beat = tempo.frames_per_beat(
_tempo_map.frame_rate(),
- _tempo_map.meter_at(_origin));
+ _tempo_map.meter_at (_origin_b));
return frames / frames_per_beat;
}
diff --git a/libs/evoral/evoral/TimeConverter.hpp b/libs/evoral/evoral/TimeConverter.hpp
index 9c53d0370c..25371a18bf 100644
--- a/libs/evoral/evoral/TimeConverter.hpp
+++ b/libs/evoral/evoral/TimeConverter.hpp
@@ -29,6 +29,7 @@ namespace Evoral {
template<typename A, typename B>
class TimeConverter {
public:
+ TimeConverter (B ob = 0) : _origin_b (ob) {}
virtual ~TimeConverter() {}
/** Convert A time to B time (A to B) */
@@ -36,6 +37,17 @@ public:
/** Convert B time to A time (A from B) */
virtual A from(B b) const = 0;
+
+ B origin_b () const {
+ return _origin_b;
+ }
+
+ void set_origin_b (B o) {
+ _origin_b = o;
+ }
+
+protected:
+ B _origin_b;
};
diff --git a/libs/evoral/src/ControlList.cpp b/libs/evoral/src/ControlList.cpp
index 880198dc53..72b032e0cf 100644
--- a/libs/evoral/src/ControlList.cpp
+++ b/libs/evoral/src/ControlList.cpp
@@ -1145,7 +1145,10 @@ ControlList::cut (iterator start, iterator end)
return nal;
}
-/** @param op 0 = cut, 1 = copy, 2 = clear */
+/** @param start Start position in model coordinates.
+ * @param end End position in model coordinates.
+ * @param op 0 = cut, 1 = copy, 2 = clear.
+ */
boost::shared_ptr<ControlList>
ControlList::cut_copy_clear (double start, double end, int op)
{
@@ -1247,9 +1250,10 @@ ControlList::copy (double start, double end)
void
ControlList::clear (double start, double end)
{
- (void) cut_copy_clear (start, end, 2);
+ cut_copy_clear (start, end, 2);
}
+/** @param pos Position in model coordinates */
bool
ControlList::paste (ControlList& alist, double pos, float /*times*/)
{