mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-09 16:24:57 +01:00
monster commit: transport mgmt changes from 2.X (omnibus edition); make slave use nframes64_t ; avoid crashes in Drags when commiting reversible transactions that do not exist
git-svn-id: svn://localhost/ardour2/branches/3.0@6034 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
660fd702af
commit
ff122d0fe8
24 changed files with 492 additions and 404 deletions
|
|
@ -1549,7 +1549,7 @@ ARDOUR_UI::transport_roll ()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ARDOUR_UI::toggle_roll (bool with_abort)
|
ARDOUR_UI::toggle_roll (bool with_abort, bool roll_out_of_bounded_mode)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
|
@ -1573,27 +1573,35 @@ ARDOUR_UI::toggle_roll (bool with_abort)
|
||||||
bool rolling = session->transport_rolling();
|
bool rolling = session->transport_rolling();
|
||||||
bool affect_transport = true;
|
bool affect_transport = true;
|
||||||
|
|
||||||
if (rolling) {
|
if (rolling && roll_out_of_bounded_mode) {
|
||||||
/* drop out of loop/range playback but leave transport rolling */
|
/* drop out of loop/range playback but leave transport rolling */
|
||||||
if (session->get_play_loop()) {
|
if (session->get_play_loop()) {
|
||||||
affect_transport = false;
|
if (Config->get_seamless_loop()) {
|
||||||
|
/* the disk buffers contain copies of the loop - we can't
|
||||||
|
just keep playing, so stop the transport. the user
|
||||||
|
can restart as they wish.
|
||||||
|
*/
|
||||||
|
affect_transport = true;
|
||||||
|
} else {
|
||||||
|
/* disk buffers are normal, so we can keep playing */
|
||||||
|
affect_transport = false;
|
||||||
|
}
|
||||||
session->request_play_loop (false, true);
|
session->request_play_loop (false, true);
|
||||||
} else if (session->get_play_range ()) {
|
} else if (session->get_play_range ()) {
|
||||||
affect_transport = false;
|
affect_transport = false;
|
||||||
session->request_play_range (false, true);
|
session->request_play_range (0, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (affect_transport) {
|
if (affect_transport) {
|
||||||
|
|
||||||
if (rolling) {
|
if (rolling) {
|
||||||
session->request_stop (with_abort);
|
session->request_stop (with_abort, true);
|
||||||
} else {
|
} else {
|
||||||
session->request_transport_speed (1.0f);
|
session->request_transport_speed (1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
map_transport_state ();
|
map_transport_state ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
||||||
|
|
@ -547,7 +547,7 @@ class ARDOUR_UI : public Gtkmm2ext::UI
|
||||||
void transport_forward (int option);
|
void transport_forward (int option);
|
||||||
void transport_rewind (int option);
|
void transport_rewind (int option);
|
||||||
void transport_loop ();
|
void transport_loop ();
|
||||||
void toggle_roll (bool with_abort);
|
void toggle_roll (bool with_abort, bool roll_out_of_bounded_mode);
|
||||||
|
|
||||||
bool _session_is_new;
|
bool _session_is_new;
|
||||||
void connect_to_session (ARDOUR::Session *);
|
void connect_to_session (ARDOUR::Session *);
|
||||||
|
|
|
||||||
|
|
@ -251,10 +251,13 @@ ARDOUR_UI::install_actions ()
|
||||||
ActionManager::session_sensitive_actions.push_back (act);
|
ActionManager::session_sensitive_actions.push_back (act);
|
||||||
ActionManager::transport_sensitive_actions.push_back (act);
|
ActionManager::transport_sensitive_actions.push_back (act);
|
||||||
|
|
||||||
ActionManager::register_action (transport_actions, X_("ToggleRoll"), _("Start/Stop"), bind (mem_fun (*this, &ARDOUR_UI::toggle_roll), false));
|
act = ActionManager::register_action (transport_actions, X_("ToggleRoll"), _("Start/Stop"), bind (mem_fun (*this, &ARDOUR_UI::toggle_roll), false, false));
|
||||||
ActionManager::session_sensitive_actions.push_back (act);
|
ActionManager::session_sensitive_actions.push_back (act);
|
||||||
ActionManager::transport_sensitive_actions.push_back (act);
|
ActionManager::transport_sensitive_actions.push_back (act);
|
||||||
ActionManager::register_action (transport_actions, X_("ToggleRollForgetCapture"), _("Stop and Forget Capture"), bind (mem_fun(*this, &ARDOUR_UI::toggle_roll), true));
|
act = ActionManager::register_action (transport_actions, X_("ToggleRollMaybe"), _("Start/Continue/Stop"), bind (mem_fun (*this, &ARDOUR_UI::toggle_roll), false, true));
|
||||||
|
ActionManager::session_sensitive_actions.push_back (act);
|
||||||
|
ActionManager::transport_sensitive_actions.push_back (act);
|
||||||
|
act = ActionManager::register_action (transport_actions, X_("ToggleRollForgetCapture"), _("Stop + Forget Capture"), bind (mem_fun(*this, &ARDOUR_UI::toggle_roll), true, false));
|
||||||
ActionManager::session_sensitive_actions.push_back (act);
|
ActionManager::session_sensitive_actions.push_back (act);
|
||||||
ActionManager::transport_sensitive_actions.push_back (act);
|
ActionManager::transport_sensitive_actions.push_back (act);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,15 +50,16 @@ using namespace ArdourCanvas;
|
||||||
|
|
||||||
double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));
|
double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));
|
||||||
|
|
||||||
Drag::Drag (Editor* e, ArdourCanvas::Item* i) :
|
Drag::Drag (Editor* e, ArdourCanvas::Item* i)
|
||||||
_editor (e),
|
: _editor (e)
|
||||||
_item (i),
|
, _item (i)
|
||||||
_pointer_frame_offset (0),
|
, _pointer_frame_offset (0)
|
||||||
_grab_frame (0),
|
, _grab_frame (0)
|
||||||
_last_pointer_frame (0),
|
, _last_pointer_frame (0)
|
||||||
_current_pointer_frame (0),
|
, _current_pointer_frame (0)
|
||||||
_had_movement (false),
|
, _had_movement (false)
|
||||||
_move_threshold_passed (false)
|
, _have_transaction (false)
|
||||||
|
, _move_threshold_passed (false)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -778,6 +779,8 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_have_transaction = true;
|
||||||
|
|
||||||
changed_position = (_last_frame_position != (nframes64_t) (_primary->region()->position()));
|
changed_position = (_last_frame_position != (nframes64_t) (_primary->region()->position()));
|
||||||
changed_tracks = (_dest_trackview != &_primary->get_time_axis_view());
|
changed_tracks = (_dest_trackview != &_primary->get_time_axis_view());
|
||||||
|
|
||||||
|
|
@ -1551,6 +1554,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
|
||||||
}
|
}
|
||||||
|
|
||||||
_editor->begin_reversible_command (trim_type);
|
_editor->begin_reversible_command (trim_type);
|
||||||
|
_have_transaction = true;
|
||||||
|
|
||||||
for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
|
for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
|
||||||
(*i)->fake_set_opaque(false);
|
(*i)->fake_set_opaque(false);
|
||||||
|
|
@ -1576,6 +1580,9 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* XXX i hope to god that we can really conclude this ... */
|
||||||
|
_have_transaction = true;
|
||||||
|
|
||||||
if (left_direction) {
|
if (left_direction) {
|
||||||
frame_delta = (_last_pointer_frame - _current_pointer_frame);
|
frame_delta = (_last_pointer_frame - _current_pointer_frame);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1658,15 +1665,19 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
|
||||||
(*i)->fake_set_opaque (true);
|
(*i)->fake_set_opaque (true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
|
for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
|
||||||
(*p)->thaw ();
|
(*p)->thaw ();
|
||||||
_editor->session->add_command (new MementoCommand<Playlist>(*(*p).get(), 0, &(*p)->get_state()));
|
if (_have_transaction) {
|
||||||
|
_editor->session->add_command (new MementoCommand<Playlist>(*(*p).get(), 0, &(*p)->get_state()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_editor->motion_frozen_playlists.clear ();
|
_editor->motion_frozen_playlists.clear ();
|
||||||
|
|
||||||
_editor->commit_reversible_command();
|
if (_have_transaction) {
|
||||||
|
_editor->commit_reversible_command();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
/* no mouse movement */
|
/* no mouse movement */
|
||||||
_editor->point_trim (event);
|
_editor->point_trim (event);
|
||||||
|
|
@ -2853,9 +2864,9 @@ ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
|
SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
|
||||||
: Drag (e, i),
|
: Drag (e, i)
|
||||||
_operation (o),
|
, _operation (o)
|
||||||
_copy (false)
|
, _copy (false)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -2924,6 +2935,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
|
||||||
nframes64_t end = 0;
|
nframes64_t end = 0;
|
||||||
nframes64_t length;
|
nframes64_t length;
|
||||||
|
|
||||||
|
|
||||||
nframes64_t const pending_position = adjusted_current_frame (event);
|
nframes64_t const pending_position = adjusted_current_frame (event);
|
||||||
|
|
||||||
/* only alter selection if the current frame is
|
/* only alter selection if the current frame is
|
||||||
|
|
@ -2956,6 +2968,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
|
||||||
if (first_move) {
|
if (first_move) {
|
||||||
|
|
||||||
_editor->begin_reversible_command (_("range selection"));
|
_editor->begin_reversible_command (_("range selection"));
|
||||||
|
_have_transaction = true;
|
||||||
|
|
||||||
if (_copy) {
|
if (_copy) {
|
||||||
/* adding to the selection */
|
/* adding to the selection */
|
||||||
|
|
@ -2972,8 +2985,9 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
|
||||||
|
|
||||||
if (first_move) {
|
if (first_move) {
|
||||||
_editor->begin_reversible_command (_("trim selection start"));
|
_editor->begin_reversible_command (_("trim selection start"));
|
||||||
|
_have_transaction = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
start = _editor->selection->time[_editor->clicked_selection].start;
|
start = _editor->selection->time[_editor->clicked_selection].start;
|
||||||
end = _editor->selection->time[_editor->clicked_selection].end;
|
end = _editor->selection->time[_editor->clicked_selection].end;
|
||||||
|
|
||||||
|
|
@ -2988,6 +3002,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
|
||||||
|
|
||||||
if (first_move) {
|
if (first_move) {
|
||||||
_editor->begin_reversible_command (_("trim selection end"));
|
_editor->begin_reversible_command (_("trim selection end"));
|
||||||
|
_have_transaction = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
start = _editor->selection->time[_editor->clicked_selection].start;
|
start = _editor->selection->time[_editor->clicked_selection].start;
|
||||||
|
|
@ -3005,6 +3020,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
|
||||||
|
|
||||||
if (first_move) {
|
if (first_move) {
|
||||||
_editor->begin_reversible_command (_("move selection"));
|
_editor->begin_reversible_command (_("move selection"));
|
||||||
|
_have_transaction = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
start = _editor->selection->time[_editor->clicked_selection].start;
|
start = _editor->selection->time[_editor->clicked_selection].start;
|
||||||
|
|
@ -3040,13 +3056,25 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
|
||||||
void
|
void
|
||||||
SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
|
SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
|
||||||
{
|
{
|
||||||
|
Session* s = _editor->session;
|
||||||
|
|
||||||
if (movement_occurred) {
|
if (movement_occurred) {
|
||||||
motion (event, false);
|
motion (event, false);
|
||||||
/* XXX this is not object-oriented programming at all. ick */
|
/* XXX this is not object-oriented programming at all. ick */
|
||||||
if (_editor->selection->time.consolidate()) {
|
if (_editor->selection->time.consolidate()) {
|
||||||
_editor->selection->TimeChanged ();
|
_editor->selection->TimeChanged ();
|
||||||
}
|
}
|
||||||
_editor->commit_reversible_command ();
|
|
||||||
|
if (_have_transaction) {
|
||||||
|
_editor->commit_reversible_command ();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XXX what if its a music time selection? */
|
||||||
|
if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
|
||||||
|
s->request_play_range (&_editor->selection->time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
/* just a click, no pointer movement.*/
|
/* just a click, no pointer movement.*/
|
||||||
|
|
||||||
|
|
@ -3055,10 +3083,13 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
|
||||||
_editor->selection->clear_time();
|
_editor->selection->clear_time();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (s && s->get_play_range () && s->transport_rolling()) {
|
||||||
|
s->request_stop (false, false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* XXX what happens if its a music selection? */
|
|
||||||
_editor->session->set_audio_range (_editor->selection->time);
|
|
||||||
_editor->stop_canvas_autoscroll ();
|
_editor->stop_canvas_autoscroll ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ protected:
|
||||||
bool _x_constrained; ///< true if x motion is constrained, otherwise false
|
bool _x_constrained; ///< true if x motion is constrained, otherwise false
|
||||||
bool _y_constrained; ///< true if y motion is constrained, otherwise false
|
bool _y_constrained; ///< true if y motion is constrained, otherwise false
|
||||||
bool _was_rolling; ///< true if the session was rolling before the drag started, otherwise false
|
bool _was_rolling; ///< true if the session was rolling before the drag started, otherwise false
|
||||||
|
bool _have_transaction; ///< true if a transaction has been started, false otherwise. Must be set true by derived class.
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2410,7 +2410,7 @@ Editor::play_selection ()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
session->request_play_range (true);
|
session->request_play_range (&selection->time, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
||||||
|
|
@ -132,8 +132,8 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
Recording = 2
|
Recording = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Event {
|
struct Event {
|
||||||
enum Type {
|
enum Type {
|
||||||
SetTransportSpeed,
|
SetTransportSpeed,
|
||||||
SetDiskstreamSpeed,
|
SetDiskstreamSpeed,
|
||||||
Locate,
|
Locate,
|
||||||
|
|
@ -148,78 +148,82 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
SetSlaveSource,
|
SetSlaveSource,
|
||||||
Audition,
|
Audition,
|
||||||
InputConfigurationChange,
|
InputConfigurationChange,
|
||||||
SetAudioRange,
|
SetPlayAudioRange,
|
||||||
SetPlayRange,
|
|
||||||
|
|
||||||
/* only one of each of these events can be queued at any one time */
|
/* only one of each of these events can be queued at any one time */
|
||||||
|
|
||||||
StopOnce,
|
StopOnce,
|
||||||
AutoLoop
|
AutoLoop
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum Action {
|
||||||
|
Add,
|
||||||
|
Remove,
|
||||||
|
Replace,
|
||||||
|
Clear
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
Action action;
|
||||||
|
nframes64_t action_frame;
|
||||||
|
nframes64_t target_frame;
|
||||||
|
double speed;
|
||||||
|
|
||||||
|
union {
|
||||||
|
void* ptr;
|
||||||
|
bool yes_or_no;
|
||||||
|
nframes64_t target2_frame;
|
||||||
|
SlaveSource slave;
|
||||||
|
Route* route;
|
||||||
|
};
|
||||||
|
|
||||||
enum Action {
|
union {
|
||||||
Add,
|
bool second_yes_or_no;
|
||||||
Remove,
|
};
|
||||||
Replace,
|
|
||||||
Clear
|
std::list<AudioRange> audio_range;
|
||||||
};
|
std::list<MusicRange> music_range;
|
||||||
|
|
||||||
Type type;
|
boost::shared_ptr<Region> region;
|
||||||
Action action;
|
|
||||||
nframes_t action_frame;
|
Event(Type t, Action a, nframes_t when, nframes_t where, double spd, bool yn = false, bool yn2 = false)
|
||||||
nframes_t target_frame;
|
|
||||||
double speed;
|
|
||||||
|
|
||||||
union {
|
|
||||||
void* ptr;
|
|
||||||
bool yes_or_no;
|
|
||||||
nframes_t target2_frame;
|
|
||||||
SlaveSource slave;
|
|
||||||
Route* route;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::list<AudioRange> audio_range;
|
|
||||||
std::list<MusicRange> music_range;
|
|
||||||
|
|
||||||
boost::shared_ptr<Region> region;
|
|
||||||
|
|
||||||
Event(Type t, Action a, nframes_t when, nframes_t where, double spd, bool yn = false)
|
|
||||||
: type (t)
|
: type (t)
|
||||||
, action (a)
|
, action (a)
|
||||||
, action_frame (when)
|
, action_frame (when)
|
||||||
, target_frame (where)
|
, target_frame (where)
|
||||||
, speed (spd)
|
, speed (spd)
|
||||||
, yes_or_no (yn)
|
, yes_or_no (yn)
|
||||||
|
, second_yes_or_no (yn2)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void set_ptr (void* p) {
|
void set_ptr (void* p) {
|
||||||
ptr = p;
|
ptr = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool before (const Event& other) const {
|
bool before (const Event& other) const {
|
||||||
return action_frame < other.action_frame;
|
return action_frame < other.action_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool after (const Event& other) const {
|
bool after (const Event& other) const {
|
||||||
return action_frame > other.action_frame;
|
return action_frame > other.action_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool compare (const Event *e1, const Event *e2) {
|
static bool compare (const Event *e1, const Event *e2) {
|
||||||
return e1->before (*e2);
|
return e1->before (*e2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *operator new (size_t) {
|
void *operator new (size_t) {
|
||||||
return pool.alloc ();
|
return pool.alloc ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator delete (void *ptr, size_t /*size*/) {
|
void operator delete (void *ptr, size_t /*size*/) {
|
||||||
pool.release (ptr);
|
pool.release (ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const nframes_t Immediate = 0;
|
static const nframes_t Immediate = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static MultiAllocSingleReleasePool pool;
|
static MultiAllocSingleReleasePool pool;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* creating from an XML file */
|
/* creating from an XML file */
|
||||||
|
|
@ -375,9 +379,9 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
/* Transport mechanism signals */
|
/* Transport mechanism signals */
|
||||||
|
|
||||||
sigc::signal<void> TransportStateChange; /* generic */
|
sigc::signal<void> TransportStateChange; /* generic */
|
||||||
sigc::signal<void,nframes_t> PositionChanged; /* sent after any non-sequential motion */
|
sigc::signal<void,nframes64_t> PositionChanged; /* sent after any non-sequential motion */
|
||||||
sigc::signal<void> DurationChanged;
|
sigc::signal<void> DurationChanged;
|
||||||
sigc::signal<void,nframes_t> Xrun;
|
sigc::signal<void,nframes64_t> Xrun;
|
||||||
sigc::signal<void> TransportLooped;
|
sigc::signal<void> TransportLooped;
|
||||||
|
|
||||||
/** emitted when a locate has occurred */
|
/** emitted when a locate has occurred */
|
||||||
|
|
@ -388,7 +392,7 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
|
|
||||||
void request_roll_at_and_return (nframes_t start, nframes_t return_to);
|
void request_roll_at_and_return (nframes_t start, nframes_t return_to);
|
||||||
void request_bounded_roll (nframes_t start, nframes_t end);
|
void request_bounded_roll (nframes_t start, nframes_t end);
|
||||||
void request_stop (bool abort = false);
|
void request_stop (bool abort = false, bool clear_state = false);
|
||||||
void request_locate (nframes_t frame, bool with_roll = false);
|
void request_locate (nframes_t frame, bool with_roll = false);
|
||||||
|
|
||||||
void request_play_loop (bool yn, bool leave_rolling = false);
|
void request_play_loop (bool yn, bool leave_rolling = false);
|
||||||
|
|
@ -406,7 +410,7 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
void request_diskstream_speed (Diskstream&, double speed);
|
void request_diskstream_speed (Diskstream&, double speed);
|
||||||
void request_input_change_handling ();
|
void request_input_change_handling ();
|
||||||
|
|
||||||
bool locate_pending() const { return static_cast<bool>(post_transport_work&PostTransportLocate); }
|
bool locate_pending() const { return static_cast<bool>(post_transport_work()&PostTransportLocate); }
|
||||||
bool transport_locked () const;
|
bool transport_locked () const;
|
||||||
|
|
||||||
int wipe ();
|
int wipe ();
|
||||||
|
|
@ -533,8 +537,8 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
|
|
||||||
/* Time */
|
/* Time */
|
||||||
|
|
||||||
nframes_t transport_frame () const {return _transport_frame; }
|
nframes64_t transport_frame () const {return _transport_frame; }
|
||||||
nframes_t audible_frame () const;
|
nframes64_t audible_frame () const;
|
||||||
nframes64_t requested_return_frame() const { return _requested_return_frame; }
|
nframes64_t requested_return_frame() const { return _requested_return_frame; }
|
||||||
|
|
||||||
enum PullupFormat {
|
enum PullupFormat {
|
||||||
|
|
@ -914,10 +918,7 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
|
|
||||||
/* ranges */
|
/* ranges */
|
||||||
|
|
||||||
void set_audio_range (std::list<AudioRange>&);
|
void request_play_range (std::list<AudioRange>*, bool leave_rolling = false);
|
||||||
void set_music_range (std::list<MusicRange>&);
|
|
||||||
|
|
||||||
void request_play_range (bool yn, bool leave_rolling = false);
|
|
||||||
bool get_play_range () const { return _play_range; }
|
bool get_play_range () const { return _play_range; }
|
||||||
|
|
||||||
/* buffers for gain and pan */
|
/* buffers for gain and pan */
|
||||||
|
|
@ -997,7 +998,7 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
nframes_t _nominal_frame_rate; //ignores audioengine setting, "native" SR
|
nframes_t _nominal_frame_rate; //ignores audioengine setting, "native" SR
|
||||||
int transport_sub_state;
|
int transport_sub_state;
|
||||||
mutable gint _record_status;
|
mutable gint _record_status;
|
||||||
volatile nframes_t _transport_frame;
|
volatile nframes64_t _transport_frame;
|
||||||
Location* end_location;
|
Location* end_location;
|
||||||
Location* start_location;
|
Location* start_location;
|
||||||
Slave* _slave;
|
Slave* _slave;
|
||||||
|
|
@ -1010,7 +1011,7 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
CubicInterpolation interpolation;
|
CubicInterpolation interpolation;
|
||||||
|
|
||||||
bool auto_play_legal;
|
bool auto_play_legal;
|
||||||
nframes_t _last_slave_transport_frame;
|
nframes64_t _last_slave_transport_frame;
|
||||||
nframes_t maximum_output_latency;
|
nframes_t maximum_output_latency;
|
||||||
volatile nframes64_t _requested_return_frame;
|
volatile nframes64_t _requested_return_frame;
|
||||||
BufferSet* _scratch_buffers;
|
BufferSet* _scratch_buffers;
|
||||||
|
|
@ -1026,6 +1027,7 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
bool _non_soloed_outs_muted;
|
bool _non_soloed_outs_muted;
|
||||||
uint32_t _listen_cnt;
|
uint32_t _listen_cnt;
|
||||||
bool _writable;
|
bool _writable;
|
||||||
|
bool _was_seamless;
|
||||||
|
|
||||||
void set_worst_io_latencies ();
|
void set_worst_io_latencies ();
|
||||||
void set_worst_io_latencies_x (IOChange, void *) {
|
void set_worst_io_latencies_x (IOChange, void *) {
|
||||||
|
|
@ -1180,7 +1182,8 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
PostTransportScrub = 0x8000,
|
PostTransportScrub = 0x8000,
|
||||||
PostTransportReverse = 0x10000,
|
PostTransportReverse = 0x10000,
|
||||||
PostTransportInputChange = 0x20000,
|
PostTransportInputChange = 0x20000,
|
||||||
PostTransportCurveRealloc = 0x40000
|
PostTransportCurveRealloc = 0x40000,
|
||||||
|
PostTransportClearSubstate = 0x80000
|
||||||
};
|
};
|
||||||
|
|
||||||
static const PostTransportWork ProcessCannotProceedMask =
|
static const PostTransportWork ProcessCannotProceedMask =
|
||||||
|
|
@ -1192,9 +1195,13 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
PostTransportScrub|
|
PostTransportScrub|
|
||||||
PostTransportAudition|
|
PostTransportAudition|
|
||||||
PostTransportLocate|
|
PostTransportLocate|
|
||||||
PostTransportStop);
|
PostTransportStop|
|
||||||
|
PostTransportClearSubstate);
|
||||||
|
|
||||||
PostTransportWork post_transport_work;
|
gint _post_transport_work; /* accessed only atomic ops */
|
||||||
|
PostTransportWork post_transport_work() const { return (PostTransportWork) g_atomic_int_get (&_post_transport_work); }
|
||||||
|
void set_post_transport_work (PostTransportWork ptw) { g_atomic_int_set (&_post_transport_work, (gint) ptw); }
|
||||||
|
void add_post_transport_work (PostTransportWork ptw);
|
||||||
|
|
||||||
uint32_t cumulative_rf_motion;
|
uint32_t cumulative_rf_motion;
|
||||||
uint32_t rf_scale;
|
uint32_t rf_scale;
|
||||||
|
|
@ -1337,8 +1344,8 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
int no_roll (nframes_t nframes);
|
int no_roll (nframes_t nframes);
|
||||||
int fail_roll (nframes_t nframes);
|
int fail_roll (nframes_t nframes);
|
||||||
|
|
||||||
bool non_realtime_work_pending() const { return static_cast<bool>(post_transport_work); }
|
bool non_realtime_work_pending() const { return static_cast<bool>(post_transport_work()); }
|
||||||
bool process_can_proceed() const { return !(post_transport_work & ProcessCannotProceedMask); }
|
bool process_can_proceed() const { return !(post_transport_work() & ProcessCannotProceedMask); }
|
||||||
|
|
||||||
struct MIDIRequest {
|
struct MIDIRequest {
|
||||||
enum Type {
|
enum Type {
|
||||||
|
|
@ -1361,18 +1368,19 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
void change_midi_ports ();
|
void change_midi_ports ();
|
||||||
int use_config_midi_ports ();
|
int use_config_midi_ports ();
|
||||||
|
|
||||||
void set_play_loop (bool yn, bool leave_rolling);
|
void set_play_loop (bool yn);
|
||||||
|
void unset_play_loop ();
|
||||||
void overwrite_some_buffers (Diskstream*);
|
void overwrite_some_buffers (Diskstream*);
|
||||||
void flush_all_inserts ();
|
void flush_all_inserts ();
|
||||||
int micro_locate (nframes_t distance);
|
int micro_locate (nframes_t distance);
|
||||||
void locate (nframes_t, bool with_roll, bool with_flush, bool with_loop=false);
|
void locate (nframes64_t, bool with_roll, bool with_flush, bool with_loop=false, bool force=false);
|
||||||
void start_locate (nframes_t, bool with_roll, bool with_flush, bool with_loop=false);
|
void start_locate (nframes64_t, bool with_roll, bool with_flush, bool with_loop=false, bool force=false);
|
||||||
void force_locate (nframes_t frame, bool with_roll = false);
|
void force_locate (nframes64_t frame, bool with_roll = false);
|
||||||
void set_diskstream_speed (Diskstream*, double speed);
|
void set_diskstream_speed (Diskstream*, double speed);
|
||||||
void set_transport_speed (double speed, bool abort = false);
|
void set_transport_speed (double speed, bool abort = false, bool clear_state = false);
|
||||||
void stop_transport (bool abort = false);
|
void stop_transport (bool abort = false, bool clear_state = false);
|
||||||
void start_transport ();
|
void start_transport ();
|
||||||
void realtime_stop (bool abort);
|
void realtime_stop (bool abort, bool clear_state);
|
||||||
void non_realtime_start_scrub ();
|
void non_realtime_start_scrub ();
|
||||||
void non_realtime_set_speed ();
|
void non_realtime_set_speed ();
|
||||||
void non_realtime_locate ();
|
void non_realtime_locate ();
|
||||||
|
|
@ -1619,8 +1627,8 @@ class Session : public PBD::StatefulDestructible, public boost::noncopyable
|
||||||
|
|
||||||
std::list<AudioRange> current_audio_range;
|
std::list<AudioRange> current_audio_range;
|
||||||
bool _play_range;
|
bool _play_range;
|
||||||
void set_play_range (bool yn, bool leave_rolling);
|
void set_play_range (std::list<AudioRange>&, bool leave_rolling);
|
||||||
void setup_auto_play ();
|
void unset_play_range ();
|
||||||
|
|
||||||
/* main outs */
|
/* main outs */
|
||||||
uint32_t main_outs;
|
uint32_t main_outs;
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ class Slave {
|
||||||
* @param position - The transport position requested
|
* @param position - The transport position requested
|
||||||
* @return - The return value is currently ignored (see Session::follow_slave)
|
* @return - The return value is currently ignored (see Session::follow_slave)
|
||||||
*/
|
*/
|
||||||
virtual bool speed_and_position (double& speed, nframes_t& position) = 0;
|
virtual bool speed_and_position (double& speed, nframes64_t& position) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* reports to ARDOUR whether the Slave is currently synced to its external
|
* reports to ARDOUR whether the Slave is currently synced to its external
|
||||||
|
|
@ -161,13 +161,13 @@ class Slave {
|
||||||
class ISlaveSessionProxy {
|
class ISlaveSessionProxy {
|
||||||
public:
|
public:
|
||||||
virtual TempoMap& tempo_map() const { return *((TempoMap *) 0); }
|
virtual TempoMap& tempo_map() const { return *((TempoMap *) 0); }
|
||||||
virtual nframes_t frame_rate() const { return 0; }
|
virtual nframes_t frame_rate() const { return 0; }
|
||||||
virtual nframes_t audible_frame () const { return 0; }
|
virtual nframes64_t audible_frame () const { return 0; }
|
||||||
virtual nframes_t transport_frame () const { return 0; }
|
virtual nframes64_t transport_frame () const { return 0; }
|
||||||
virtual nframes_t frames_since_cycle_start () const { return 0; }
|
virtual nframes_t frames_since_cycle_start () const { return 0; }
|
||||||
virtual nframes_t frame_time () const { return 0; }
|
virtual nframes64_t frame_time () const { return 0; }
|
||||||
|
|
||||||
virtual void request_locate (nframes_t /*frame*/, bool with_roll = false) {
|
virtual void request_locate (nframes64_t /*frame*/, bool with_roll = false) {
|
||||||
(void) with_roll;
|
(void) with_roll;
|
||||||
}
|
}
|
||||||
virtual void request_transport_speed (double /*speed*/) {}
|
virtual void request_transport_speed (double /*speed*/) {}
|
||||||
|
|
@ -181,14 +181,14 @@ class SlaveSessionProxy : public ISlaveSessionProxy {
|
||||||
public:
|
public:
|
||||||
SlaveSessionProxy(Session &s) : session(s) {}
|
SlaveSessionProxy(Session &s) : session(s) {}
|
||||||
|
|
||||||
TempoMap& tempo_map() const;
|
TempoMap& tempo_map() const;
|
||||||
nframes_t frame_rate() const;
|
nframes_t frame_rate() const;
|
||||||
nframes_t audible_frame () const;
|
nframes64_t audible_frame () const;
|
||||||
nframes_t transport_frame () const;
|
nframes64_t transport_frame () const;
|
||||||
nframes_t frames_since_cycle_start () const;
|
nframes_t frames_since_cycle_start () const;
|
||||||
nframes_t frame_time () const;
|
nframes64_t frame_time () const;
|
||||||
|
|
||||||
void request_locate (nframes_t frame, bool with_roll = false);
|
void request_locate (nframes64_t frame, bool with_roll = false);
|
||||||
void request_transport_speed (double speed);
|
void request_transport_speed (double speed);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -211,7 +211,7 @@ class MTC_Slave : public Slave, public sigc::trackable {
|
||||||
~MTC_Slave ();
|
~MTC_Slave ();
|
||||||
|
|
||||||
void rebind (MIDI::Port&);
|
void rebind (MIDI::Port&);
|
||||||
bool speed_and_position (double&, nframes_t&);
|
bool speed_and_position (double&, nframes64_t&);
|
||||||
|
|
||||||
bool locked() const;
|
bool locked() const;
|
||||||
bool ok() const;
|
bool ok() const;
|
||||||
|
|
@ -256,7 +256,7 @@ class MIDIClock_Slave : public Slave, public sigc::trackable {
|
||||||
~MIDIClock_Slave ();
|
~MIDIClock_Slave ();
|
||||||
|
|
||||||
void rebind (MIDI::Port&);
|
void rebind (MIDI::Port&);
|
||||||
bool speed_and_position (double&, nframes_t&);
|
bool speed_and_position (double&, nframes64_t&);
|
||||||
|
|
||||||
bool locked() const;
|
bool locked() const;
|
||||||
bool ok() const;
|
bool ok() const;
|
||||||
|
|
@ -311,17 +311,17 @@ class MIDIClock_Slave : public Slave, public sigc::trackable {
|
||||||
double b, c, omega;
|
double b, c, omega;
|
||||||
|
|
||||||
void reset ();
|
void reset ();
|
||||||
void start (MIDI::Parser& parser, nframes_t timestamp);
|
void start (MIDI::Parser& parser, nframes64_t timestamp);
|
||||||
void contineu (MIDI::Parser& parser, nframes_t timestamp);
|
void contineu (MIDI::Parser& parser, nframes64_t timestamp);
|
||||||
void stop (MIDI::Parser& parser, nframes_t timestamp);
|
void stop (MIDI::Parser& parser, nframes64_t timestamp);
|
||||||
void position (MIDI::Parser& parser, MIDI::byte* message, size_t size);
|
void position (MIDI::Parser& parser, MIDI::byte* message, size_t size);
|
||||||
// we can't use continue because it is a C++ keyword
|
// we can't use continue because it is a C++ keyword
|
||||||
void calculate_one_ppqn_in_frames_at(nframes_t time);
|
void calculate_one_ppqn_in_frames_at(nframes64_t time);
|
||||||
nframes_t calculate_song_position(uint16_t song_position_in_sixteenth_notes);
|
nframes64_t calculate_song_position(uint16_t song_position_in_sixteenth_notes);
|
||||||
void calculate_filter_coefficients();
|
void calculate_filter_coefficients();
|
||||||
void update_midi_clock (MIDI::Parser& parser, nframes_t timestamp);
|
void update_midi_clock (MIDI::Parser& parser, nframes64_t timestamp);
|
||||||
void read_current (SafeTime *) const;
|
void read_current (SafeTime *) const;
|
||||||
bool stop_if_no_more_clock_events(nframes_t& pos, nframes_t now);
|
bool stop_if_no_more_clock_events(nframes64_t& pos, nframes64_t now);
|
||||||
|
|
||||||
/// whether transport should be rolling
|
/// whether transport should be rolling
|
||||||
bool _started;
|
bool _started;
|
||||||
|
|
@ -337,7 +337,7 @@ class ADAT_Slave : public Slave
|
||||||
ADAT_Slave () {}
|
ADAT_Slave () {}
|
||||||
~ADAT_Slave () {}
|
~ADAT_Slave () {}
|
||||||
|
|
||||||
bool speed_and_position (double& speed, nframes_t& pos) {
|
bool speed_and_position (double& speed, nframes64_t& pos) {
|
||||||
speed = 0;
|
speed = 0;
|
||||||
pos = 0;
|
pos = 0;
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -355,7 +355,7 @@ class JACK_Slave : public Slave
|
||||||
JACK_Slave (jack_client_t*);
|
JACK_Slave (jack_client_t*);
|
||||||
~JACK_Slave ();
|
~JACK_Slave ();
|
||||||
|
|
||||||
bool speed_and_position (double& speed, nframes_t& pos);
|
bool speed_and_position (double& speed, nframes64_t& pos);
|
||||||
|
|
||||||
bool starting() const { return _starting; }
|
bool starting() const { return _starting; }
|
||||||
bool locked() const;
|
bool locked() const;
|
||||||
|
|
|
||||||
|
|
@ -626,6 +626,7 @@ AudioDiskstream::process (nframes_t transport_frame, nframes_t nframes, bool can
|
||||||
nframes_t total = chaninfo->playback_vector.len[0] + chaninfo->playback_vector.len[1];
|
nframes_t total = chaninfo->playback_vector.len[0] + chaninfo->playback_vector.len[1];
|
||||||
|
|
||||||
if (necessary_samples > total) {
|
if (necessary_samples > total) {
|
||||||
|
cerr << _name << " Need " << necessary_samples << " total = " << total << endl;
|
||||||
cerr << "underrun for " << _name << endl;
|
cerr << "underrun for " << _name << endl;
|
||||||
DiskUnderrun ();
|
DiskUnderrun ();
|
||||||
goto out;
|
goto out;
|
||||||
|
|
@ -1754,7 +1755,7 @@ AudioDiskstream::get_state ()
|
||||||
if (_session.config.get_punch_in() && ((pi = _session.locations()->auto_punch_location()) != 0)) {
|
if (_session.config.get_punch_in() && ((pi = _session.locations()->auto_punch_location()) != 0)) {
|
||||||
snprintf (buf, sizeof (buf), "%" PRId64, pi->start());
|
snprintf (buf, sizeof (buf), "%" PRId64, pi->start());
|
||||||
} else {
|
} else {
|
||||||
snprintf (buf, sizeof (buf), "%" PRIu32, _session.transport_frame());
|
snprintf (buf, sizeof (buf), "%" PRId64, _session.transport_frame());
|
||||||
}
|
}
|
||||||
|
|
||||||
cs_child->add_property (X_("at"), buf);
|
cs_child->add_property (X_("at"), buf);
|
||||||
|
|
|
||||||
|
|
@ -299,8 +299,7 @@ setup_enum_writer ()
|
||||||
REGISTER_CLASS_ENUM (Session::Event, SetSlaveSource);
|
REGISTER_CLASS_ENUM (Session::Event, SetSlaveSource);
|
||||||
REGISTER_CLASS_ENUM (Session::Event, Audition);
|
REGISTER_CLASS_ENUM (Session::Event, Audition);
|
||||||
REGISTER_CLASS_ENUM (Session::Event, InputConfigurationChange);
|
REGISTER_CLASS_ENUM (Session::Event, InputConfigurationChange);
|
||||||
REGISTER_CLASS_ENUM (Session::Event, SetAudioRange);
|
REGISTER_CLASS_ENUM (Session::Event, SetPlayAudioRange);
|
||||||
REGISTER_CLASS_ENUM (Session::Event, SetPlayRange);
|
|
||||||
REGISTER_CLASS_ENUM (Session::Event, StopOnce);
|
REGISTER_CLASS_ENUM (Session::Event, StopOnce);
|
||||||
REGISTER_CLASS_ENUM (Session::Event, AutoLoop);
|
REGISTER_CLASS_ENUM (Session::Event, AutoLoop);
|
||||||
REGISTER (_Session_Event_Type);
|
REGISTER (_Session_Event_Type);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include "pbd/error.h"
|
#include "pbd/error.h"
|
||||||
#include "pbd/filesystem_paths.h"
|
#include "pbd/filesystem_paths.h"
|
||||||
|
|
@ -36,18 +37,30 @@ using std::string;
|
||||||
sys::path
|
sys::path
|
||||||
user_config_directory ()
|
user_config_directory ()
|
||||||
{
|
{
|
||||||
const string home_dir = Glib::get_home_dir ();
|
const char* c = 0;
|
||||||
|
sys::path p;
|
||||||
|
|
||||||
if (home_dir.empty ()) {
|
/* adopt freedesktop standards, and put .ardour3 into $XDG_CONFIG_HOME or ~/.config
|
||||||
const string error_msg = "Unable to determine home directory";
|
*/
|
||||||
|
|
||||||
// log the error
|
if ((c = getenv ("XDG_CONFIG_HOME")) != 0) {
|
||||||
error << error_msg << endmsg;
|
p = c;
|
||||||
|
} else {
|
||||||
|
const string home_dir = Glib::get_home_dir();
|
||||||
|
|
||||||
|
if (home_dir.empty ()) {
|
||||||
|
const string error_msg = "Unable to determine home directory";
|
||||||
|
|
||||||
|
// log the error
|
||||||
|
error << error_msg << endmsg;
|
||||||
|
|
||||||
|
throw sys::filesystem_error(error_msg);
|
||||||
|
}
|
||||||
|
|
||||||
throw sys::filesystem_error(error_msg);
|
p = home_dir;
|
||||||
|
p /= ".config";
|
||||||
}
|
}
|
||||||
|
|
||||||
sys::path p(home_dir);
|
|
||||||
p /= user_config_dir_name;
|
p /= user_config_dir_name;
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ JACK_Slave::JACK_Slave (jack_client_t* j)
|
||||||
: jack (j)
|
: jack (j)
|
||||||
{
|
{
|
||||||
double x;
|
double x;
|
||||||
nframes_t p;
|
nframes64_t p;
|
||||||
/* call this to initialize things */
|
/* call this to initialize things */
|
||||||
speed_and_position (x, p);
|
speed_and_position (x, p);
|
||||||
}
|
}
|
||||||
|
|
@ -64,10 +64,11 @@ JACK_Slave::ok() const
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
JACK_Slave::speed_and_position (double& sp, nframes_t& position)
|
JACK_Slave::speed_and_position (double& sp, nframes64_t& position)
|
||||||
{
|
{
|
||||||
jack_position_t pos;
|
jack_position_t pos;
|
||||||
jack_transport_state_t state;
|
jack_transport_state_t state;
|
||||||
|
|
||||||
state = jack_transport_query (jack, &pos);
|
state = jack_transport_query (jack, &pos);
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ MIDIClock_Slave::rebind (MIDI::Port& p)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MIDIClock_Slave::calculate_one_ppqn_in_frames_at(nframes_t time)
|
MIDIClock_Slave::calculate_one_ppqn_in_frames_at(nframes64_t time)
|
||||||
{
|
{
|
||||||
const Tempo& current_tempo = session->tempo_map().tempo_at(time);
|
const Tempo& current_tempo = session->tempo_map().tempo_at(time);
|
||||||
const Meter& current_meter = session->tempo_map().meter_at(time);
|
const Meter& current_meter = session->tempo_map().meter_at(time);
|
||||||
|
|
@ -101,14 +101,14 @@ MIDIClock_Slave::calculate_one_ppqn_in_frames_at(nframes_t time)
|
||||||
one_ppqn_in_frames = frames_per_quarter_note / double (ppqn);
|
one_ppqn_in_frames = frames_per_quarter_note / double (ppqn);
|
||||||
}
|
}
|
||||||
|
|
||||||
ARDOUR::nframes_t
|
ARDOUR::nframes64_t
|
||||||
MIDIClock_Slave::calculate_song_position(uint16_t song_position_in_sixteenth_notes)
|
MIDIClock_Slave::calculate_song_position(uint16_t song_position_in_sixteenth_notes)
|
||||||
{
|
{
|
||||||
nframes_t song_position_frames = 0;
|
nframes64_t song_position_frames = 0;
|
||||||
for (uint16_t i = 1; i <= song_position_in_sixteenth_notes; ++i) {
|
for (uint16_t i = 1; i <= song_position_in_sixteenth_notes; ++i) {
|
||||||
// one quarter note contains ppqn pulses, so a sixteenth note is ppqn / 4 pulses
|
// one quarter note contains ppqn pulses, so a sixteenth note is ppqn / 4 pulses
|
||||||
calculate_one_ppqn_in_frames_at(song_position_frames);
|
calculate_one_ppqn_in_frames_at(song_position_frames);
|
||||||
song_position_frames += one_ppqn_in_frames * nframes_t(ppqn / 4);
|
song_position_frames += one_ppqn_in_frames * (nframes64_t)(ppqn / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
return song_position_frames;
|
return song_position_frames;
|
||||||
|
|
@ -124,7 +124,7 @@ MIDIClock_Slave::calculate_filter_coefficients()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, nframes_t timestamp)
|
MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, nframes64_t timestamp)
|
||||||
{
|
{
|
||||||
// some pieces of hardware send MIDI Clock all the time
|
// some pieces of hardware send MIDI Clock all the time
|
||||||
if ( (!_starting) && (!_started) ) {
|
if ( (!_starting) && (!_started) ) {
|
||||||
|
|
@ -133,7 +133,7 @@ MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, nframes_t timestamp)
|
||||||
|
|
||||||
calculate_one_ppqn_in_frames_at(should_be_position);
|
calculate_one_ppqn_in_frames_at(should_be_position);
|
||||||
|
|
||||||
nframes_t elapsed_since_start = timestamp - first_timestamp;
|
nframes64_t elapsed_since_start = timestamp - first_timestamp;
|
||||||
double error = 0;
|
double error = 0;
|
||||||
|
|
||||||
if (_starting || last_timestamp == 0) {
|
if (_starting || last_timestamp == 0) {
|
||||||
|
|
@ -195,7 +195,7 @@ MIDIClock_Slave::update_midi_clock (Parser& /*parser*/, nframes_t timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MIDIClock_Slave::start (Parser& /*parser*/, nframes_t /*timestamp*/)
|
MIDIClock_Slave::start (Parser& /*parser*/, nframes64_t /*timestamp*/)
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_MIDI_CLOCK
|
#ifdef DEBUG_MIDI_CLOCK
|
||||||
cerr << "MIDIClock_Slave got start message at time " << timestamp << " engine time: " << session->frame_time() << endl;
|
cerr << "MIDIClock_Slave got start message at time " << timestamp << " engine time: " << session->frame_time() << endl;
|
||||||
|
|
@ -223,7 +223,7 @@ MIDIClock_Slave::reset ()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MIDIClock_Slave::contineu (Parser& /*parser*/, nframes_t /*timestamp*/)
|
MIDIClock_Slave::contineu (Parser& /*parser*/, nframes64_t /*timestamp*/)
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_MIDI_CLOCK
|
#ifdef DEBUG_MIDI_CLOCK
|
||||||
std::cerr << "MIDIClock_Slave got continue message" << endl;
|
std::cerr << "MIDIClock_Slave got continue message" << endl;
|
||||||
|
|
@ -236,7 +236,7 @@ MIDIClock_Slave::contineu (Parser& /*parser*/, nframes_t /*timestamp*/)
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
MIDIClock_Slave::stop (Parser& /*parser*/, nframes_t /*timestamp*/)
|
MIDIClock_Slave::stop (Parser& /*parser*/, nframes64_t /*timestamp*/)
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_MIDI_CLOCK
|
#ifdef DEBUG_MIDI_CLOCK
|
||||||
std::cerr << "MIDIClock_Slave got stop message" << endl;
|
std::cerr << "MIDIClock_Slave got stop message" << endl;
|
||||||
|
|
@ -255,7 +255,7 @@ MIDIClock_Slave::stop (Parser& /*parser*/, nframes_t /*timestamp*/)
|
||||||
// that is the position of the last MIDI Clock
|
// that is the position of the last MIDI Clock
|
||||||
// message and that is probably what the master
|
// message and that is probably what the master
|
||||||
// expects where we are right now
|
// expects where we are right now
|
||||||
nframes_t stop_position = should_be_position;
|
nframes64_t stop_position = should_be_position;
|
||||||
|
|
||||||
// find out the last MIDI beat: go back #midi_clocks mod 6
|
// find out the last MIDI beat: go back #midi_clocks mod 6
|
||||||
// and lets hope the tempo didnt change in those last 6 beats :)
|
// and lets hope the tempo didnt change in those last 6 beats :)
|
||||||
|
|
@ -282,7 +282,7 @@ MIDIClock_Slave::position (Parser& /*parser*/, byte* message, size_t size)
|
||||||
assert((lsb <= 0x7f) && (msb <= 0x7f));
|
assert((lsb <= 0x7f) && (msb <= 0x7f));
|
||||||
|
|
||||||
uint16_t position_in_sixteenth_notes = (uint16_t(msb) << 7) | uint16_t(lsb);
|
uint16_t position_in_sixteenth_notes = (uint16_t(msb) << 7) | uint16_t(lsb);
|
||||||
nframes_t position_in_frames = calculate_song_position(position_in_sixteenth_notes);
|
nframes64_t position_in_frames = calculate_song_position(position_in_sixteenth_notes);
|
||||||
|
|
||||||
#ifdef DEBUG_MIDI_CLOCK
|
#ifdef DEBUG_MIDI_CLOCK
|
||||||
cerr << "Song Position: " << position_in_sixteenth_notes << " frames: " << position_in_frames << endl;
|
cerr << "Song Position: " << position_in_sixteenth_notes << " frames: " << position_in_frames << endl;
|
||||||
|
|
@ -313,7 +313,7 @@ MIDIClock_Slave::starting() const
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MIDIClock_Slave::stop_if_no_more_clock_events(nframes_t& pos, nframes_t now)
|
MIDIClock_Slave::stop_if_no_more_clock_events(nframes64_t& pos, nframes64_t now)
|
||||||
{
|
{
|
||||||
/* no timecode for 1/4 second ? conclude that its stopped */
|
/* no timecode for 1/4 second ? conclude that its stopped */
|
||||||
if (last_timestamp &&
|
if (last_timestamp &&
|
||||||
|
|
@ -332,7 +332,7 @@ MIDIClock_Slave::stop_if_no_more_clock_events(nframes_t& pos, nframes_t now)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MIDIClock_Slave::speed_and_position (double& speed, nframes_t& pos)
|
MIDIClock_Slave::speed_and_position (double& speed, nframes64_t& pos)
|
||||||
{
|
{
|
||||||
if (!_started || _starting) {
|
if (!_started || _starting) {
|
||||||
speed = 0.0;
|
speed = 0.0;
|
||||||
|
|
@ -340,7 +340,7 @@ MIDIClock_Slave::speed_and_position (double& speed, nframes_t& pos)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
nframes_t engine_now = session->frame_time();
|
nframes64_t engine_now = session->frame_time();
|
||||||
|
|
||||||
if (stop_if_no_more_clock_events(pos, engine_now)) {
|
if (stop_if_no_more_clock_events(pos, engine_now)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -353,8 +353,8 @@ MIDIClock_Slave::speed_and_position (double& speed, nframes_t& pos)
|
||||||
if (engine_now > last_timestamp) {
|
if (engine_now > last_timestamp) {
|
||||||
// we are in between MIDI clock messages
|
// we are in between MIDI clock messages
|
||||||
// so we interpolate position according to speed
|
// so we interpolate position according to speed
|
||||||
nframes_t elapsed = engine_now - last_timestamp;
|
nframes64_t elapsed = engine_now - last_timestamp;
|
||||||
pos = nframes_t (should_be_position + double(elapsed) * speed);
|
pos = (nframes64_t) (should_be_position + double(elapsed) * speed);
|
||||||
} else {
|
} else {
|
||||||
// A new MIDI clock message has arrived this cycle
|
// A new MIDI clock message has arrived this cycle
|
||||||
pos = should_be_position;
|
pos = should_be_position;
|
||||||
|
|
|
||||||
|
|
@ -1223,7 +1223,7 @@ MidiDiskstream::get_state ()
|
||||||
if (_session.config.get_punch_in() && ((pi = _session.locations()->auto_punch_location()) != 0)) {
|
if (_session.config.get_punch_in() && ((pi = _session.locations()->auto_punch_location()) != 0)) {
|
||||||
snprintf (buf, sizeof (buf), "%" PRId64, pi->start());
|
snprintf (buf, sizeof (buf), "%" PRId64, pi->start());
|
||||||
} else {
|
} else {
|
||||||
snprintf (buf, sizeof (buf), "%" PRIu32, _session.transport_frame());
|
snprintf (buf, sizeof (buf), "%" PRId64, _session.transport_frame());
|
||||||
}
|
}
|
||||||
|
|
||||||
cs_child->add_property (X_("at"), buf);
|
cs_child->add_property (X_("at"), buf);
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ void
|
||||||
MTC_Slave::update_mtc_qtr (Parser& /*p*/)
|
MTC_Slave::update_mtc_qtr (Parser& /*p*/)
|
||||||
{
|
{
|
||||||
cycles_t cnow = get_cycles ();
|
cycles_t cnow = get_cycles ();
|
||||||
nframes_t now = session.engine().frame_time();
|
nframes64_t now = session.engine().frame_time();
|
||||||
nframes_t qtr;
|
nframes_t qtr;
|
||||||
static cycles_t last_qtr = 0;
|
static cycles_t last_qtr = 0;
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ MTC_Slave::update_mtc_qtr (Parser& /*p*/)
|
||||||
void
|
void
|
||||||
MTC_Slave::update_mtc_time (const byte *msg, bool was_full)
|
MTC_Slave::update_mtc_time (const byte *msg, bool was_full)
|
||||||
{
|
{
|
||||||
nframes_t now = session.engine().frame_time();
|
nframes64_t now = session.engine().frame_time();
|
||||||
Timecode::Time timecode;
|
Timecode::Time timecode;
|
||||||
|
|
||||||
timecode.hours = msg[3];
|
timecode.hours = msg[3];
|
||||||
|
|
@ -258,9 +258,9 @@ MTC_Slave::ok() const
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MTC_Slave::speed_and_position (double& speed, nframes_t& pos)
|
MTC_Slave::speed_and_position (double& speed, nframes64_t& pos)
|
||||||
{
|
{
|
||||||
nframes_t now = session.engine().frame_time();
|
nframes64_t now = session.engine().frame_time();
|
||||||
SafeTime last;
|
SafeTime last;
|
||||||
nframes_t frame_rate;
|
nframes_t frame_rate;
|
||||||
nframes_t elapsed;
|
nframes_t elapsed;
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,6 @@ Session::Session (AudioEngine &eng,
|
||||||
pending_events (2048),
|
pending_events (2048),
|
||||||
state_tree (0),
|
state_tree (0),
|
||||||
_butler (new Butler (this)),
|
_butler (new Butler (this)),
|
||||||
post_transport_work((PostTransportWork)0),
|
|
||||||
_send_timecode_update (false),
|
_send_timecode_update (false),
|
||||||
midi_thread (pthread_t (0)),
|
midi_thread (pthread_t (0)),
|
||||||
midi_requests (128), // the size of this should match the midi request pool size
|
midi_requests (128), // the size of this should match the midi request pool size
|
||||||
|
|
@ -218,7 +217,6 @@ Session::Session (AudioEngine &eng,
|
||||||
pending_events (2048),
|
pending_events (2048),
|
||||||
state_tree (0),
|
state_tree (0),
|
||||||
_butler (new Butler (this)),
|
_butler (new Butler (this)),
|
||||||
post_transport_work((PostTransportWork)0),
|
|
||||||
_send_timecode_update (false),
|
_send_timecode_update (false),
|
||||||
midi_thread (pthread_t (0)),
|
midi_thread (pthread_t (0)),
|
||||||
midi_requests (16),
|
midi_requests (16),
|
||||||
|
|
@ -1229,12 +1227,12 @@ Session::maybe_enable_record ()
|
||||||
set_dirty();
|
set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
nframes_t
|
nframes64_t
|
||||||
Session::audible_frame () const
|
Session::audible_frame () const
|
||||||
{
|
{
|
||||||
nframes_t ret;
|
nframes64_t ret;
|
||||||
|
nframes64_t tf;
|
||||||
nframes_t offset;
|
nframes_t offset;
|
||||||
nframes_t tf;
|
|
||||||
|
|
||||||
/* the first of these two possible settings for "offset"
|
/* the first of these two possible settings for "offset"
|
||||||
mean that the audible frame is stationary until
|
mean that the audible frame is stationary until
|
||||||
|
|
@ -3517,7 +3515,7 @@ void
|
||||||
Session::set_audition (boost::shared_ptr<Region> r)
|
Session::set_audition (boost::shared_ptr<Region> r)
|
||||||
{
|
{
|
||||||
pending_audition_region = r;
|
pending_audition_region = r;
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportAudition);
|
add_post_transport_work (PostTransportAudition);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ static inline uint32_t next_power_of_two (uint32_t n)
|
||||||
void
|
void
|
||||||
Session::schedule_curve_reallocation ()
|
Session::schedule_curve_reallocation ()
|
||||||
{
|
{
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportCurveRealloc);
|
add_post_transport_work (PostTransportCurveRealloc);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ Session::overwrite_some_buffers (Diskstream* ds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportOverWrite);
|
add_post_transport_work (PostTransportOverWrite);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
#include "ardour/timestamps.h"
|
#include "ardour/timestamps.h"
|
||||||
|
|
||||||
#include "pbd/error.h"
|
#include "pbd/error.h"
|
||||||
|
#include "pbd/enumwriter.h"
|
||||||
#include <glibmm/thread.h>
|
#include <glibmm/thread.h>
|
||||||
|
|
||||||
#include "ardour/ardour.h"
|
#include "ardour/ardour.h"
|
||||||
|
|
@ -38,28 +39,6 @@ using namespace PBD;
|
||||||
|
|
||||||
MultiAllocSingleReleasePool Session::Event::pool ("event", sizeof (Session::Event), 512);
|
MultiAllocSingleReleasePool Session::Event::pool ("event", sizeof (Session::Event), 512);
|
||||||
|
|
||||||
static const char* event_names[] = {
|
|
||||||
"SetTransportSpeed",
|
|
||||||
"SetDiskstreamSpeed",
|
|
||||||
"Locate",
|
|
||||||
"LocateRoll",
|
|
||||||
"LocateRollLocate",
|
|
||||||
"SetLoop",
|
|
||||||
"PunchIn",
|
|
||||||
"PunchOut",
|
|
||||||
"RangeStop",
|
|
||||||
"RangeLocate",
|
|
||||||
"Overwrite",
|
|
||||||
"SetSlaveSource",
|
|
||||||
"Audition",
|
|
||||||
"InputConfigurationChange",
|
|
||||||
"SetAudioRange",
|
|
||||||
"SetMusicRange",
|
|
||||||
"SetPlayRange",
|
|
||||||
"StopOnce",
|
|
||||||
"AutoLoop"
|
|
||||||
};
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::add_event (nframes_t frame, Event::Type type, nframes_t target_frame)
|
Session::add_event (nframes_t frame, Event::Type type, nframes_t target_frame)
|
||||||
{
|
{
|
||||||
|
|
@ -161,7 +140,7 @@ Session::merge_event (Event* ev)
|
||||||
for (Events::iterator i = events.begin(); i != events.end(); ++i) {
|
for (Events::iterator i = events.begin(); i != events.end(); ++i) {
|
||||||
if ((*i)->type == ev->type && (*i)->action_frame == ev->action_frame) {
|
if ((*i)->type == ev->type && (*i)->action_frame == ev->action_frame) {
|
||||||
error << string_compose(_("Session: cannot have two events of type %1 at the same frame (%2)."),
|
error << string_compose(_("Session: cannot have two events of type %1 at the same frame (%2)."),
|
||||||
event_names[ev->type], ev->action_frame) << endmsg;
|
enum_2_string (ev->type), ev->action_frame) << endmsg;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -321,7 +300,7 @@ Session::process_event (Event* ev)
|
||||||
|
|
||||||
switch (ev->type) {
|
switch (ev->type) {
|
||||||
case Event::SetLoop:
|
case Event::SetLoop:
|
||||||
set_play_loop (ev->yes_or_no, (ev->speed == 1.0f));
|
set_play_loop (ev->yes_or_no);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Event::AutoLoop:
|
case Event::AutoLoop:
|
||||||
|
|
@ -362,7 +341,7 @@ Session::process_event (Event* ev)
|
||||||
|
|
||||||
|
|
||||||
case Event::SetTransportSpeed:
|
case Event::SetTransportSpeed:
|
||||||
set_transport_speed (ev->speed, ev->yes_or_no);
|
set_transport_speed (ev->speed, ev->yes_or_no, ev->second_yes_or_no);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Event::PunchIn:
|
case Event::PunchIn:
|
||||||
|
|
@ -425,17 +404,12 @@ Session::process_event (Event* ev)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Event::InputConfigurationChange:
|
case Event::InputConfigurationChange:
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportInputChange);
|
add_post_transport_work (PostTransportInputChange);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Event::SetAudioRange:
|
case Event::SetPlayAudioRange:
|
||||||
current_audio_range = ev->audio_range;
|
set_play_range (ev->audio_range, (ev->speed == 1.0f));
|
||||||
setup_auto_play ();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Event::SetPlayRange:
|
|
||||||
set_play_range (ev->yes_or_no, (ev->speed == 1.0f));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ Session::stop_audio_export ()
|
||||||
stuff that stop_transport() implements.
|
stuff that stop_transport() implements.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
realtime_stop (true);
|
realtime_stop (true, true);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
|
|
||||||
if (!export_status->aborted()) {
|
if (!export_status->aborted()) {
|
||||||
|
|
|
||||||
|
|
@ -480,7 +480,7 @@ bool
|
||||||
Session::follow_slave (nframes_t nframes)
|
Session::follow_slave (nframes_t nframes)
|
||||||
{
|
{
|
||||||
double slave_speed;
|
double slave_speed;
|
||||||
nframes_t slave_transport_frame;
|
nframes64_t slave_transport_frame;
|
||||||
nframes_t this_delta;
|
nframes_t this_delta;
|
||||||
int dir;
|
int dir;
|
||||||
bool starting;
|
bool starting;
|
||||||
|
|
|
||||||
|
|
@ -197,11 +197,10 @@ Session::first_stage_init (string fullpath, string snapshot_name)
|
||||||
_worst_input_latency = 0;
|
_worst_input_latency = 0;
|
||||||
_worst_track_latency = 0;
|
_worst_track_latency = 0;
|
||||||
_state_of_the_state = StateOfTheState(CannotSave|InitialConnecting|Loading);
|
_state_of_the_state = StateOfTheState(CannotSave|InitialConnecting|Loading);
|
||||||
|
_was_seamless = Config->get_seamless_loop ();
|
||||||
_slave = 0;
|
_slave = 0;
|
||||||
session_send_mmc = false;
|
session_send_mmc = false;
|
||||||
session_send_mtc = false;
|
session_send_mtc = false;
|
||||||
post_transport_work = PostTransportWork (0);
|
|
||||||
g_atomic_int_set (&_playback_load, 100);
|
g_atomic_int_set (&_playback_load, 100);
|
||||||
g_atomic_int_set (&_capture_load, 100);
|
g_atomic_int_set (&_capture_load, 100);
|
||||||
g_atomic_int_set (&_playback_load_min, 100);
|
g_atomic_int_set (&_playback_load_min, 100);
|
||||||
|
|
|
||||||
|
|
@ -452,7 +452,7 @@ Session::jack_sync_callback (jack_transport_state_t state,
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case JackTransportStopped:
|
case JackTransportStopped:
|
||||||
if (slave && _transport_frame != pos->frame && post_transport_work == 0) {
|
if (slave && _transport_frame != pos->frame && post_transport_work() == 0) {
|
||||||
request_locate (pos->frame, false);
|
request_locate (pos->frame, false);
|
||||||
// cerr << "SYNC: stopped, locate to " << pos->frame << " from " << _transport_frame << endl;
|
// cerr << "SYNC: stopped, locate to " << pos->frame << " from " << _transport_frame << endl;
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -461,9 +461,9 @@ Session::jack_sync_callback (jack_transport_state_t state,
|
||||||
}
|
}
|
||||||
|
|
||||||
case JackTransportStarting:
|
case JackTransportStarting:
|
||||||
// cerr << "SYNC: starting @ " << pos->frame << " a@ " << _transport_frame << " our work = " << post_transport_work << " pos matches ? " << (_transport_frame == pos->frame) << endl;
|
// cerr << "SYNC: starting @ " << pos->frame << " a@ " << _transport_frame << " our work = " << post_transport_work() << " pos matches ? " << (_transport_frame == pos->frame) << endl;
|
||||||
if (slave) {
|
if (slave) {
|
||||||
return _transport_frame == pos->frame && post_transport_work == 0;
|
return _transport_frame == pos->frame && post_transport_work() == 0;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,25 @@ using namespace ARDOUR;
|
||||||
using namespace sigc;
|
using namespace sigc;
|
||||||
using namespace PBD;
|
using namespace PBD;
|
||||||
|
|
||||||
|
void
|
||||||
|
Session::add_post_transport_work (PostTransportWork ptw)
|
||||||
|
{
|
||||||
|
PostTransportWork oldval;
|
||||||
|
PostTransportWork newval;
|
||||||
|
int tries = 0;
|
||||||
|
|
||||||
|
while (tries < 8) {
|
||||||
|
oldval = (PostTransportWork) g_atomic_int_get (&_post_transport_work);
|
||||||
|
newval = PostTransportWork (oldval | ptw);
|
||||||
|
if (g_atomic_int_compare_and_exchange (&_post_transport_work, oldval, newval)) {
|
||||||
|
/* success */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error << "Could not set post transport work! Crazy thread madness, call the programmers" << endmsg;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::request_input_change_handling ()
|
Session::request_input_change_handling ()
|
||||||
{
|
{
|
||||||
|
|
@ -63,12 +82,21 @@ void
|
||||||
Session::request_slave_source (SlaveSource src)
|
Session::request_slave_source (SlaveSource src)
|
||||||
{
|
{
|
||||||
Event* ev = new Event (Event::SetSlaveSource, Event::Add, Event::Immediate, 0, 0.0);
|
Event* ev = new Event (Event::SetSlaveSource, Event::Add, Event::Immediate, 0, 0.0);
|
||||||
|
bool seamless;
|
||||||
|
|
||||||
|
seamless = Config->get_seamless_loop ();
|
||||||
|
|
||||||
if (src == JACK) {
|
if (src == JACK) {
|
||||||
/* could set_seamless_loop() be disposed of entirely?*/
|
/* JACK cannot support seamless looping at present */
|
||||||
Config->set_seamless_loop (false);
|
Config->set_seamless_loop (false);
|
||||||
|
} else {
|
||||||
|
/* reset to whatever the value was before we last switched slaves */
|
||||||
|
Config->set_seamless_loop (_was_seamless);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* save value of seamless from before the switch */
|
||||||
|
_was_seamless = seamless;
|
||||||
|
|
||||||
ev->slave = src;
|
ev->slave = src;
|
||||||
queue_event (ev);
|
queue_event (ev);
|
||||||
}
|
}
|
||||||
|
|
@ -89,9 +117,9 @@ Session::request_diskstream_speed (Diskstream& ds, double speed)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::request_stop (bool abort)
|
Session::request_stop (bool abort, bool clear_state)
|
||||||
{
|
{
|
||||||
Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0, abort);
|
Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0, abort, clear_state);
|
||||||
queue_event (ev);
|
queue_event (ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +131,7 @@ Session::request_locate (nframes_t target_frame, bool with_roll)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::force_locate (nframes_t target_frame, bool with_roll)
|
Session::force_locate (nframes64_t target_frame, bool with_roll)
|
||||||
{
|
{
|
||||||
Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, true);
|
Event *ev = new Event (with_roll ? Event::LocateRoll : Event::Locate, Event::Add, Event::Immediate, target_frame, 0, true);
|
||||||
queue_event (ev);
|
queue_event (ev);
|
||||||
|
|
@ -132,8 +160,22 @@ Session::request_play_loop (bool yn, bool leave_rolling)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::realtime_stop (bool abort)
|
Session::request_play_range (list<AudioRange>* range, bool leave_rolling)
|
||||||
{
|
{
|
||||||
|
Event* ev = new Event (Event::SetPlayAudioRange, Event::Add, Event::Immediate, 0, (leave_rolling ? 1.0 : 0.0));
|
||||||
|
if (range) {
|
||||||
|
ev->audio_range = *range;
|
||||||
|
} else {
|
||||||
|
ev->audio_range.clear ();
|
||||||
|
}
|
||||||
|
queue_event (ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Session::realtime_stop (bool abort, bool clear_state)
|
||||||
|
{
|
||||||
|
PostTransportWork todo = PostTransportWork (0);
|
||||||
|
|
||||||
/* assume that when we start, we'll be moving forwards */
|
/* assume that when we start, we'll be moving forwards */
|
||||||
|
|
||||||
// FIXME: where should this really be? [DR]
|
// FIXME: where should this really be? [DR]
|
||||||
|
|
@ -142,9 +184,9 @@ Session::realtime_stop (bool abort)
|
||||||
deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
|
deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
|
||||||
|
|
||||||
if (_transport_speed < 0.0f) {
|
if (_transport_speed < 0.0f) {
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportStop | PostTransportReverse);
|
todo = (PostTransportWork (todo | PostTransportStop | PostTransportReverse));
|
||||||
} else {
|
} else {
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportStop);
|
todo = PostTransportWork (todo | PostTransportStop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actively_recording()) {
|
if (actively_recording()) {
|
||||||
|
|
@ -158,11 +200,19 @@ Session::realtime_stop (bool abort)
|
||||||
|
|
||||||
/* the duration change is not guaranteed to have happened, but is likely */
|
/* the duration change is not guaranteed to have happened, but is likely */
|
||||||
|
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportDuration);
|
todo = PostTransportWork (todo | PostTransportDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abort) {
|
if (abort) {
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportAbort);
|
todo = PostTransportWork (todo | PostTransportAbort);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clear_state) {
|
||||||
|
todo = PostTransportWork (todo | PostTransportClearSubstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (todo) {
|
||||||
|
add_post_transport_work (todo);
|
||||||
}
|
}
|
||||||
|
|
||||||
_clear_event_type (Event::StopOnce);
|
_clear_event_type (Event::StopOnce);
|
||||||
|
|
@ -188,29 +238,30 @@ Session::butler_transport_work ()
|
||||||
{
|
{
|
||||||
restart:
|
restart:
|
||||||
bool finished;
|
bool finished;
|
||||||
|
PostTransportWork ptw;
|
||||||
boost::shared_ptr<RouteList> r = routes.reader ();
|
boost::shared_ptr<RouteList> r = routes.reader ();
|
||||||
boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
|
boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
|
||||||
|
|
||||||
int on_entry = g_atomic_int_get (&_butler->should_do_transport_work);
|
int on_entry = g_atomic_int_get (&_butler->should_do_transport_work);
|
||||||
finished = true;
|
finished = true;
|
||||||
|
ptw = post_transport_work();
|
||||||
if (post_transport_work & PostTransportCurveRealloc) {
|
if (ptw & PostTransportCurveRealloc) {
|
||||||
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
|
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
|
||||||
(*i)->curve_reallocate();
|
(*i)->curve_reallocate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportInputChange) {
|
if (ptw & PostTransportInputChange) {
|
||||||
for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
|
for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
|
||||||
(*i)->non_realtime_input_change ();
|
(*i)->non_realtime_input_change ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportSpeed) {
|
if (ptw & PostTransportSpeed) {
|
||||||
non_realtime_set_speed ();
|
non_realtime_set_speed ();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportReverse) {
|
if (ptw & PostTransportReverse) {
|
||||||
|
|
||||||
clear_clicks();
|
clear_clicks();
|
||||||
cumulative_rf_motion = 0;
|
cumulative_rf_motion = 0;
|
||||||
|
|
@ -218,7 +269,7 @@ Session::butler_transport_work ()
|
||||||
|
|
||||||
/* don't seek if locate will take care of that in non_realtime_stop() */
|
/* don't seek if locate will take care of that in non_realtime_stop() */
|
||||||
|
|
||||||
if (!(post_transport_work & PostTransportLocate)) {
|
if (!(ptw & PostTransportLocate)) {
|
||||||
|
|
||||||
for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
|
for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
|
||||||
if (!(*i)->hidden()) {
|
if (!(*i)->hidden()) {
|
||||||
|
|
@ -233,19 +284,19 @@ Session::butler_transport_work ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportLocate) {
|
if (ptw & PostTransportLocate) {
|
||||||
non_realtime_locate ();
|
non_realtime_locate ();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportStop) {
|
if (ptw & PostTransportStop) {
|
||||||
non_realtime_stop (post_transport_work & PostTransportAbort, on_entry, finished);
|
non_realtime_stop (ptw & PostTransportAbort, on_entry, finished);
|
||||||
if (!finished) {
|
if (!finished) {
|
||||||
g_atomic_int_dec_and_test (&_butler->should_do_transport_work);
|
g_atomic_int_dec_and_test (&_butler->should_do_transport_work);
|
||||||
goto restart;
|
goto restart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportOverWrite) {
|
if (ptw & PostTransportOverWrite) {
|
||||||
non_realtime_overwrite (on_entry, finished);
|
non_realtime_overwrite (on_entry, finished);
|
||||||
if (!finished) {
|
if (!finished) {
|
||||||
g_atomic_int_dec_and_test (&_butler->should_do_transport_work);
|
g_atomic_int_dec_and_test (&_butler->should_do_transport_work);
|
||||||
|
|
@ -253,7 +304,7 @@ Session::butler_transport_work ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportAudition) {
|
if (ptw & PostTransportAudition) {
|
||||||
non_realtime_set_audition ();
|
non_realtime_set_audition ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -305,6 +356,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
|
||||||
time_t xnow;
|
time_t xnow;
|
||||||
bool did_record;
|
bool did_record;
|
||||||
bool saved;
|
bool saved;
|
||||||
|
PostTransportWork ptw = post_transport_work();
|
||||||
|
|
||||||
did_record = false;
|
did_record = false;
|
||||||
saved = false;
|
saved = false;
|
||||||
|
|
@ -390,7 +442,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
|
||||||
(Config->get_slave_source() == None && config.get_auto_return());
|
(Config->get_slave_source() == None && config.get_auto_return());
|
||||||
|
|
||||||
if (auto_return_enabled ||
|
if (auto_return_enabled ||
|
||||||
(post_transport_work & PostTransportLocate) ||
|
(ptw & PostTransportLocate) ||
|
||||||
(_requested_return_frame >= 0) ||
|
(_requested_return_frame >= 0) ||
|
||||||
synced_to_jack()) {
|
synced_to_jack()) {
|
||||||
|
|
||||||
|
|
@ -399,7 +451,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((auto_return_enabled || synced_to_jack() || _requested_return_frame >= 0) &&
|
if ((auto_return_enabled || synced_to_jack() || _requested_return_frame >= 0) &&
|
||||||
!(post_transport_work & PostTransportLocate)) {
|
!(ptw & PostTransportLocate)) {
|
||||||
|
|
||||||
/* no explicit locate queued */
|
/* no explicit locate queued */
|
||||||
|
|
||||||
|
|
@ -461,6 +513,14 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* do this before seeking, because otherwise the Diskstreams will do the wrong thing in seamless loop mode.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (ptw & PostTransportClearSubstate) {
|
||||||
|
_play_range = false;
|
||||||
|
unset_play_loop ();
|
||||||
|
}
|
||||||
|
|
||||||
/* this for() block can be put inside the previous if() and has the effect of ... ??? what */
|
/* this for() block can be put inside the previous if() and has the effect of ... ??? what */
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -481,24 +541,7 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
|
||||||
deliver_mmc (MIDI::MachineControl::cmdStop, 0);
|
deliver_mmc (MIDI::MachineControl::cmdStop, 0);
|
||||||
deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
|
deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
|
||||||
|
|
||||||
if (did_record) {
|
if ((ptw & PostTransportLocate) && get_record_enabled()) {
|
||||||
|
|
||||||
/* XXX its a little odd that we're doing this here
|
|
||||||
when realtime_stop(), which has already executed,
|
|
||||||
will have done this.
|
|
||||||
JLC - so let's not because it seems unnecessary and breaks loop record
|
|
||||||
*/
|
|
||||||
#if 0
|
|
||||||
if (!Config->get_latched_record_enable()) {
|
|
||||||
g_atomic_int_set (&_record_status, Disabled);
|
|
||||||
} else {
|
|
||||||
g_atomic_int_set (&_record_status, Enabled);
|
|
||||||
}
|
|
||||||
RecordStateChanged (); /* emit signal */
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((post_transport_work & PostTransportLocate) && get_record_enabled()) {
|
|
||||||
/* capture start has been changed, so save pending state */
|
/* capture start has been changed, so save pending state */
|
||||||
save_state ("", true);
|
save_state ("", true);
|
||||||
saved = true;
|
saved = true;
|
||||||
|
|
@ -514,23 +557,21 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished)
|
||||||
save_state (_current_snapshot_name);
|
save_state (_current_snapshot_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportDuration) {
|
if (ptw & PostTransportDuration) {
|
||||||
DurationChanged (); /* EMIT SIGNAL */
|
DurationChanged (); /* EMIT SIGNAL */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportStop) {
|
if (ptw & PostTransportStop) {
|
||||||
_play_range = false;
|
_play_range = false;
|
||||||
play_loop = false;
|
play_loop = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
nframes_t tf = _transport_frame;
|
PositionChanged ((nframes64_t) _transport_frame); /* EMIT SIGNAL */
|
||||||
|
|
||||||
PositionChanged (tf); /* EMIT SIGNAL */
|
|
||||||
TransportStateChange (); /* EMIT SIGNAL */
|
TransportStateChange (); /* EMIT SIGNAL */
|
||||||
|
|
||||||
/* and start it up again if relevant */
|
/* and start it up again if relevant */
|
||||||
|
|
||||||
if ((post_transport_work & PostTransportLocate) && Config->get_slave_source() == None && pending_locate_roll) {
|
if ((ptw & PostTransportLocate) && Config->get_slave_source() == None && pending_locate_roll) {
|
||||||
request_transport_speed (1.0);
|
request_transport_speed (1.0);
|
||||||
pending_locate_roll = false;
|
pending_locate_roll = false;
|
||||||
}
|
}
|
||||||
|
|
@ -561,35 +602,48 @@ Session::check_declick_out ()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::set_play_loop (bool yn, bool leave_rolling)
|
Session::unset_play_loop ()
|
||||||
|
{
|
||||||
|
play_loop = false;
|
||||||
|
clear_events (Event::AutoLoop);
|
||||||
|
|
||||||
|
// set all diskstreams to NOT use internal looping
|
||||||
|
boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
|
||||||
|
for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
|
||||||
|
if (!(*i)->hidden()) {
|
||||||
|
(*i)->set_loop (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Session::set_play_loop (bool yn)
|
||||||
{
|
{
|
||||||
/* Called from event-handling context */
|
/* Called from event-handling context */
|
||||||
|
|
||||||
Location *loc;
|
Location *loc;
|
||||||
|
|
||||||
if (yn == play_loop) {
|
if (yn == play_loop || (actively_recording() && yn) || (loc = _locations.auto_loop_location()) == 0) {
|
||||||
|
/* nothing to do, or can't change loop status while recording */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((actively_recording() && yn) || (loc = _locations.auto_loop_location()) == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_dirty();
|
set_dirty();
|
||||||
|
|
||||||
if (yn && Config->get_seamless_loop() && synced_to_jack()) {
|
if (yn && Config->get_seamless_loop() && synced_to_jack()) {
|
||||||
warning << _("Seamless looping cannot be supported while Ardour is using JACK transport.\n"
|
warning << _("Seamless looping cannot be supported while Ardour is using JACK transport.\n"
|
||||||
"Recommend changing the configured options")
|
"Recommend changing the configured options")
|
||||||
<< endmsg;
|
<< endmsg;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (yn) {
|
||||||
|
|
||||||
|
play_loop = true;
|
||||||
if ((play_loop = yn)) {
|
|
||||||
|
|
||||||
if (loc) {
|
if (loc) {
|
||||||
|
|
||||||
set_play_range (false, true);
|
unset_play_range ();
|
||||||
|
|
||||||
if (Config->get_seamless_loop()) {
|
if (Config->get_seamless_loop()) {
|
||||||
// set all diskstreams to use internal looping
|
// set all diskstreams to use internal looping
|
||||||
|
|
@ -609,34 +663,26 @@ Session::set_play_loop (bool yn, bool leave_rolling)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* stick in the loop event */
|
/* put the loop event into the event list */
|
||||||
|
|
||||||
Event* event = new Event (Event::AutoLoop, Event::Replace, loc->end(), loc->start(), 0.0f);
|
Event* event = new Event (Event::AutoLoop, Event::Replace, loc->end(), loc->start(), 0.0f);
|
||||||
merge_event (event);
|
merge_event (event);
|
||||||
|
|
||||||
|
/* locate to start of loop and roll. If doing seamless loop, force a
|
||||||
// locate to start of loop and roll
|
locate+buffer refill even if we are positioned there already.
|
||||||
event = new Event (Event::LocateRoll, Event::Add, Event::Immediate, loc->start(), 0, !synced_to_jack());
|
*/
|
||||||
merge_event (event);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
start_locate (loc->start(), true, true, false, Config->get_seamless_loop());
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
clear_events (Event::AutoLoop);
|
|
||||||
|
|
||||||
// set all diskstreams to NOT use internal looping
|
|
||||||
boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
|
|
||||||
for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
|
|
||||||
if (!(*i)->hidden()) {
|
|
||||||
(*i)->set_loop (0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
unset_play_loop ();
|
||||||
}
|
}
|
||||||
TransportStateChange (); /* EMIT SIGNAL */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
TransportStateChange ();
|
||||||
|
}
|
||||||
void
|
void
|
||||||
Session::flush_all_inserts ()
|
Session::flush_all_inserts ()
|
||||||
{
|
{
|
||||||
|
|
@ -648,12 +694,12 @@ Session::flush_all_inserts ()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::start_locate (nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop)
|
Session::start_locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool with_loop, bool force)
|
||||||
{
|
{
|
||||||
if (synced_to_jack()) {
|
if (synced_to_jack()) {
|
||||||
|
|
||||||
double sp;
|
double sp;
|
||||||
nframes_t pos;
|
nframes64_t pos;
|
||||||
|
|
||||||
_slave->speed_and_position (sp, pos);
|
_slave->speed_and_position (sp, pos);
|
||||||
|
|
||||||
|
|
@ -672,7 +718,7 @@ Session::start_locate (nframes_t target_frame, bool with_roll, bool with_flush,
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
locate (target_frame, with_roll, with_flush, with_loop);
|
locate (target_frame, with_roll, with_flush, with_loop, force);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -696,13 +742,13 @@ Session::micro_locate (nframes_t distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool with_loop)
|
Session::locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool with_loop, bool force)
|
||||||
{
|
{
|
||||||
if (actively_recording() && !with_loop) {
|
if (actively_recording() && !with_loop) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_transport_frame == target_frame && !loop_changing && !with_loop) {
|
if (!force && _transport_frame == target_frame && !loop_changing && !with_loop) {
|
||||||
if (with_roll) {
|
if (with_roll) {
|
||||||
set_transport_speed (1.0, false);
|
set_transport_speed (1.0, false);
|
||||||
}
|
}
|
||||||
|
|
@ -730,17 +776,18 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transport_rolling() && (!auto_play_legal || !config.get_auto_play()) && !with_roll && !(synced_to_jack() && play_loop)) {
|
if (transport_rolling() && (!auto_play_legal || !config.get_auto_play()) && !with_roll && !(synced_to_jack() && play_loop)) {
|
||||||
realtime_stop (false);
|
realtime_stop (false, true); // XXX paul - check if the 2nd arg is really correct
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !with_loop || loop_changing) {
|
if (force || !with_loop || loop_changing) {
|
||||||
|
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportLocate);
|
PostTransportWork todo = PostTransportLocate;
|
||||||
|
|
||||||
if (with_roll) {
|
if (with_roll) {
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportRoll);
|
todo = PostTransportWork (todo | PostTransportRoll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_post_transport_work (todo);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -792,7 +839,7 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
|
||||||
|
|
||||||
if (al && (_transport_frame < al->start() || _transport_frame > al->end())) {
|
if (al && (_transport_frame < al->start() || _transport_frame > al->end())) {
|
||||||
// cancel looping directly, this is called from event handling context
|
// cancel looping directly, this is called from event handling context
|
||||||
set_play_loop (false, false);
|
set_play_loop (false);
|
||||||
}
|
}
|
||||||
else if (al && _transport_frame == al->start()) {
|
else if (al && _transport_frame == al->start()) {
|
||||||
if (with_loop) {
|
if (with_loop) {
|
||||||
|
|
@ -824,7 +871,7 @@ Session::locate (nframes_t target_frame, bool with_roll, bool with_flush, bool w
|
||||||
* @param abort
|
* @param abort
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
Session::set_transport_speed (double speed, bool abort)
|
Session::set_transport_speed (double speed, bool abort, bool clear_state)
|
||||||
{
|
{
|
||||||
if (_transport_speed == speed) {
|
if (_transport_speed == speed) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -859,6 +906,13 @@ Session::set_transport_speed (double speed, bool abort)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (synced_to_jack ()) {
|
if (synced_to_jack ()) {
|
||||||
|
if (clear_state) {
|
||||||
|
/* do this here because our response to the slave won't
|
||||||
|
take care of it.
|
||||||
|
*/
|
||||||
|
_play_range = false;
|
||||||
|
unset_play_loop ();
|
||||||
|
}
|
||||||
_engine.transport_stop ();
|
_engine.transport_stop ();
|
||||||
} else {
|
} else {
|
||||||
stop_transport (abort);
|
stop_transport (abort);
|
||||||
|
|
@ -919,9 +973,11 @@ Session::set_transport_speed (double speed, bool abort)
|
||||||
/* if we are reversing relative to the current speed, or relative to the speed
|
/* if we are reversing relative to the current speed, or relative to the speed
|
||||||
before the last stop, then we have to do extra work.
|
before the last stop, then we have to do extra work.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
PostTransportWork todo = PostTransportWork (0);
|
||||||
|
|
||||||
if ((_transport_speed && speed * _transport_speed < 0.0) || (_last_transport_speed * speed < 0.0) || (_last_transport_speed == 0.0f && speed < 0.0f)) {
|
if ((_transport_speed && speed * _transport_speed < 0.0) || (_last_transport_speed * speed < 0.0) || (_last_transport_speed == 0.0f && speed < 0.0f)) {
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse);
|
todo = PostTransportWork (todo | PostTransportReverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
_last_transport_speed = _transport_speed;
|
_last_transport_speed = _transport_speed;
|
||||||
|
|
@ -930,11 +986,13 @@ Session::set_transport_speed (double speed, bool abort)
|
||||||
boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
|
boost::shared_ptr<DiskstreamList> dsl = diskstreams.reader();
|
||||||
for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
|
for (DiskstreamList::iterator i = dsl->begin(); i != dsl->end(); ++i) {
|
||||||
if ((*i)->realtime_set_speed ((*i)->speed(), true)) {
|
if ((*i)->realtime_set_speed ((*i)->speed(), true)) {
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed);
|
todo = PostTransportWork (todo | PostTransportSpeed);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & (PostTransportSpeed|PostTransportReverse)) {
|
if (todo) {
|
||||||
|
add_post_transport_work (todo);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -943,7 +1001,7 @@ Session::set_transport_speed (double speed, bool abort)
|
||||||
|
|
||||||
/** Stop the transport. */
|
/** Stop the transport. */
|
||||||
void
|
void
|
||||||
Session::stop_transport (bool abort)
|
Session::stop_transport (bool abort, bool clear_state)
|
||||||
{
|
{
|
||||||
if (_transport_speed == 0.0f) {
|
if (_transport_speed == 0.0f) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -979,7 +1037,7 @@ Session::stop_transport (bool abort)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
realtime_stop (abort);
|
realtime_stop (abort, clear_state);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1031,7 +1089,9 @@ Session::start_transport ()
|
||||||
void
|
void
|
||||||
Session::post_transport ()
|
Session::post_transport ()
|
||||||
{
|
{
|
||||||
if (post_transport_work & PostTransportAudition) {
|
PostTransportWork ptw = post_transport_work ();
|
||||||
|
|
||||||
|
if (ptw & PostTransportAudition) {
|
||||||
if (auditioner && auditioner->active()) {
|
if (auditioner && auditioner->active()) {
|
||||||
process_function = &Session::process_audition;
|
process_function = &Session::process_audition;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1039,14 +1099,14 @@ Session::post_transport ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportStop) {
|
if (ptw & PostTransportStop) {
|
||||||
|
|
||||||
transport_sub_state = 0;
|
transport_sub_state = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post_transport_work & PostTransportLocate) {
|
if (ptw & PostTransportLocate) {
|
||||||
|
|
||||||
if (((Config->get_slave_source() == None && (auto_play_legal && config.get_auto_play())) && !_exporting) || (post_transport_work & PostTransportRoll)) {
|
if (((Config->get_slave_source() == None && (auto_play_legal && config.get_auto_play())) && !_exporting) || (ptw & PostTransportRoll)) {
|
||||||
start_transport ();
|
start_transport ();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1055,8 +1115,10 @@ Session::post_transport ()
|
||||||
}
|
}
|
||||||
|
|
||||||
set_next_event ();
|
set_next_event ();
|
||||||
|
/* XXX is this really safe? shouldn't we just be unsetting the bits that we actually
|
||||||
post_transport_work = PostTransportWork (0);
|
know were handled ?
|
||||||
|
*/
|
||||||
|
set_post_transport_work (PostTransportWork (0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -1161,7 +1223,7 @@ Session::set_slave_source (SlaveSource src)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (non_rt_required) {
|
if (non_rt_required) {
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed);
|
add_post_transport_work (PostTransportSpeed);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1171,7 +1233,7 @@ Session::set_slave_source (SlaveSource src)
|
||||||
void
|
void
|
||||||
Session::reverse_diskstream_buffers ()
|
Session::reverse_diskstream_buffers ()
|
||||||
{
|
{
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportReverse);
|
add_post_transport_work (PostTransportReverse);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1179,110 +1241,107 @@ void
|
||||||
Session::set_diskstream_speed (Diskstream* stream, double speed)
|
Session::set_diskstream_speed (Diskstream* stream, double speed)
|
||||||
{
|
{
|
||||||
if (stream->realtime_set_speed (speed, false)) {
|
if (stream->realtime_set_speed (speed, false)) {
|
||||||
post_transport_work = PostTransportWork (post_transport_work | PostTransportSpeed);
|
add_post_transport_work (PostTransportSpeed);
|
||||||
_butler->schedule_transport_work ();
|
_butler->schedule_transport_work ();
|
||||||
set_dirty ();
|
set_dirty ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::set_audio_range (list<AudioRange>& range)
|
Session::unset_play_range ()
|
||||||
{
|
{
|
||||||
Event *ev = new Event (Event::SetAudioRange, Event::Add, Event::Immediate, 0, 0.0f);
|
_play_range = false;
|
||||||
ev->audio_range = range;
|
|
||||||
queue_event (ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Session::request_play_range (bool yn, bool leave_rolling)
|
|
||||||
{
|
|
||||||
Event* ev = new Event (Event::SetPlayRange, Event::Add, Event::Immediate, 0, (leave_rolling ? 1.0f : 0.0f), yn);
|
|
||||||
queue_event (ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Session::set_play_range (bool yn, bool leave_rolling)
|
|
||||||
{
|
|
||||||
/* Called from event-processing context */
|
|
||||||
|
|
||||||
if (yn) {
|
|
||||||
/* cancel loop play */
|
|
||||||
set_play_range (false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
_play_range = yn;
|
|
||||||
setup_auto_play ();
|
|
||||||
|
|
||||||
|
|
||||||
if (!_play_range && !leave_rolling) {
|
|
||||||
/* stop transport */
|
|
||||||
Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0f, false);
|
|
||||||
merge_event (ev);
|
|
||||||
}
|
|
||||||
TransportStateChange (); /* EMIT SIGNAL */
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Session::setup_auto_play ()
|
|
||||||
{
|
|
||||||
/* Called from event-processing context */
|
|
||||||
|
|
||||||
Event* ev;
|
|
||||||
|
|
||||||
_clear_event_type (Event::RangeStop);
|
_clear_event_type (Event::RangeStop);
|
||||||
_clear_event_type (Event::RangeLocate);
|
_clear_event_type (Event::RangeLocate);
|
||||||
|
}
|
||||||
|
|
||||||
if (!_play_range) {
|
void
|
||||||
|
Session::set_play_range (list<AudioRange>& range, bool leave_rolling)
|
||||||
|
{
|
||||||
|
Event* ev;
|
||||||
|
|
||||||
|
/* Called from event-processing context */
|
||||||
|
|
||||||
|
unset_play_range ();
|
||||||
|
|
||||||
|
if (range.empty()) {
|
||||||
|
/* _play_range set to false in unset_play_range()
|
||||||
|
*/
|
||||||
|
if (!leave_rolling) {
|
||||||
|
/* stop transport */
|
||||||
|
Event* ev = new Event (Event::SetTransportSpeed, Event::Add, Event::Immediate, 0, 0.0f, false);
|
||||||
|
merge_event (ev);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
list<AudioRange>::size_type sz = current_audio_range.size();
|
_play_range = true;
|
||||||
|
|
||||||
|
/* cancel loop play */
|
||||||
|
unset_play_loop ();
|
||||||
|
|
||||||
|
list<AudioRange>::size_type sz = range.size();
|
||||||
|
|
||||||
if (sz > 1) {
|
if (sz > 1) {
|
||||||
|
|
||||||
list<AudioRange>::iterator i = current_audio_range.begin();
|
list<AudioRange>::iterator i = range.begin();
|
||||||
list<AudioRange>::iterator next;
|
list<AudioRange>::iterator next;
|
||||||
|
|
||||||
while (i != current_audio_range.end()) {
|
while (i != range.end()) {
|
||||||
|
|
||||||
next = i;
|
next = i;
|
||||||
++next;
|
++next;
|
||||||
|
|
||||||
/* locating/stopping is subject to delays for declicking.
|
/* locating/stopping is subject to delays for declicking.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
nframes_t requested_frame = (*i).end;
|
nframes_t requested_frame = (*i).end;
|
||||||
|
|
||||||
if (requested_frame > current_block_size) {
|
if (requested_frame > current_block_size) {
|
||||||
requested_frame -= current_block_size;
|
requested_frame -= current_block_size;
|
||||||
} else {
|
} else {
|
||||||
requested_frame = 0;
|
requested_frame = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next == current_audio_range.end()) {
|
if (next == range.end()) {
|
||||||
ev = new Event (Event::RangeStop, Event::Add, requested_frame, 0, 0.0f);
|
ev = new Event (Event::RangeStop, Event::Add, requested_frame, 0, 0.0f);
|
||||||
} else {
|
} else {
|
||||||
ev = new Event (Event::RangeLocate, Event::Add, requested_frame, (*next).start, 0.0f);
|
ev = new Event (Event::RangeLocate, Event::Add, requested_frame, (*next).start, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
merge_event (ev);
|
merge_event (ev);
|
||||||
|
|
||||||
i = next;
|
i = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (sz == 1) {
|
} else if (sz == 1) {
|
||||||
|
|
||||||
ev = new Event (Event::RangeStop, Event::Add, current_audio_range.front().end, 0, 0.0f);
|
ev = new Event (Event::RangeStop, Event::Add, range.front().end, 0, 0.0f);
|
||||||
merge_event (ev);
|
merge_event (ev);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
/* save range so we can do auto-return etc. */
|
||||||
|
|
||||||
|
current_audio_range = range;
|
||||||
|
|
||||||
/* now start rolling at the right place */
|
/* now start rolling at the right place */
|
||||||
|
|
||||||
ev = new Event (Event::LocateRoll, Event::Add, Event::Immediate, current_audio_range.front().start, 0.0f, false);
|
ev = new Event (Event::LocateRoll, Event::Add, Event::Immediate, range.front().start, 0.0f, false);
|
||||||
merge_event (ev);
|
merge_event (ev);
|
||||||
|
|
||||||
|
TransportStateChange ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Session::request_bounded_roll (nframes_t start, nframes_t end)
|
||||||
|
{
|
||||||
|
AudioRange ar (start, end, 0);
|
||||||
|
list<AudioRange> lar;
|
||||||
|
|
||||||
|
lar.push_back (ar);
|
||||||
|
request_play_range (&lar, true);
|
||||||
|
}
|
||||||
void
|
void
|
||||||
Session::request_roll_at_and_return (nframes_t start, nframes_t return_to)
|
Session::request_roll_at_and_return (nframes_t start, nframes_t return_to)
|
||||||
{
|
{
|
||||||
|
|
@ -1291,14 +1350,6 @@ Session::request_roll_at_and_return (nframes_t start, nframes_t return_to)
|
||||||
queue_event (ev);
|
queue_event (ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
Session::request_bounded_roll (nframes_t start, nframes_t end)
|
|
||||||
{
|
|
||||||
Event *ev = new Event (Event::StopOnce, Event::Replace, end, Event::Immediate, 0.0);
|
|
||||||
queue_event (ev);
|
|
||||||
request_locate (start, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Session::engine_halted ()
|
Session::engine_halted ()
|
||||||
{
|
{
|
||||||
|
|
@ -1312,10 +1363,10 @@ Session::engine_halted ()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
g_atomic_int_set (&_butler->should_do_transport_work, 0);
|
g_atomic_int_set (&_butler->should_do_transport_work, 0);
|
||||||
post_transport_work = PostTransportWork (0);
|
set_post_transport_work (PostTransportWork (0));
|
||||||
_butler->stop ();
|
_butler->stop ();
|
||||||
|
|
||||||
realtime_stop (false);
|
realtime_stop (false, true);
|
||||||
non_realtime_stop (false, 0, ignored);
|
non_realtime_stop (false, 0, ignored);
|
||||||
transport_sub_state = 0;
|
transport_sub_state = 0;
|
||||||
|
|
||||||
|
|
@ -1326,7 +1377,7 @@ Session::engine_halted ()
|
||||||
void
|
void
|
||||||
Session::xrun_recovery ()
|
Session::xrun_recovery ()
|
||||||
{
|
{
|
||||||
Xrun (transport_frame()); //EMIT SIGNAL
|
Xrun ((nframes64_t)_transport_frame); //EMIT SIGNAL
|
||||||
|
|
||||||
if (Config->get_stop_recording_on_xrun() && actively_recording()) {
|
if (Config->get_stop_recording_on_xrun() && actively_recording()) {
|
||||||
|
|
||||||
|
|
@ -1342,12 +1393,14 @@ void
|
||||||
Session::update_latency_compensation (bool with_stop, bool abort)
|
Session::update_latency_compensation (bool with_stop, bool abort)
|
||||||
{
|
{
|
||||||
bool update_jack = false;
|
bool update_jack = false;
|
||||||
|
PostTransportWork ptw;
|
||||||
|
|
||||||
if (_state_of_the_state & Deletion) {
|
if (_state_of_the_state & Deletion) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_worst_track_latency = 0;
|
_worst_track_latency = 0;
|
||||||
|
ptw = post_transport_work();
|
||||||
|
|
||||||
#undef DEBUG_LATENCY
|
#undef DEBUG_LATENCY
|
||||||
#ifdef DEBUG_LATENCY
|
#ifdef DEBUG_LATENCY
|
||||||
|
|
@ -1359,8 +1412,7 @@ Session::update_latency_compensation (bool with_stop, bool abort)
|
||||||
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
|
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
|
||||||
|
|
||||||
if (with_stop) {
|
if (with_stop) {
|
||||||
(*i)->handle_transport_stopped (abort, (post_transport_work & PostTransportLocate),
|
(*i)->handle_transport_stopped (abort, (ptw & PostTransportLocate), (!(ptw & PostTransportLocate) || pending_locate_flush));
|
||||||
(!(post_transport_work & PostTransportLocate) || pending_locate_flush));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nframes_t old_latency = (*i)->output()->signal_latency ();
|
nframes_t old_latency = (*i)->output()->signal_latency ();
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,13 @@ SlaveSessionProxy::frame_rate() const
|
||||||
return session.frame_rate();
|
return session.frame_rate();
|
||||||
}
|
}
|
||||||
|
|
||||||
nframes_t
|
nframes64_t
|
||||||
SlaveSessionProxy::audible_frame() const
|
SlaveSessionProxy::audible_frame() const
|
||||||
{
|
{
|
||||||
return session.audible_frame();
|
return session.audible_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
nframes_t
|
nframes64_t
|
||||||
SlaveSessionProxy::transport_frame() const
|
SlaveSessionProxy::transport_frame() const
|
||||||
{
|
{
|
||||||
return session.transport_frame();
|
return session.transport_frame();
|
||||||
|
|
@ -53,14 +53,14 @@ SlaveSessionProxy::frames_since_cycle_start() const
|
||||||
return session.engine().frames_since_cycle_start();
|
return session.engine().frames_since_cycle_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
nframes_t
|
nframes64_t
|
||||||
SlaveSessionProxy::frame_time() const
|
SlaveSessionProxy::frame_time() const
|
||||||
{
|
{
|
||||||
return session.engine().frame_time();
|
return session.engine().frame_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
SlaveSessionProxy::request_locate(nframes_t frame, bool with_roll)
|
SlaveSessionProxy::request_locate(nframes64_t frame, bool with_roll)
|
||||||
{
|
{
|
||||||
session.request_locate(frame, with_roll);
|
session.request_locate(frame, with_roll);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue