322 lines
11 KiB
C++
322 lines
11 KiB
C++
/*
|
|
Copyright (C) 2001 Paul Davis
|
|
Copyright (C) 2004-2008 Grame
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
#include "JackTransportEngine.h"
|
|
#include "JackClientInterface.h"
|
|
#include "JackClientControl.h"
|
|
#include "JackEngineControl.h"
|
|
#include "JackGlobals.h"
|
|
#include "JackError.h"
|
|
#include "JackTime.h"
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
|
|
using namespace std;
|
|
|
|
namespace Jack
|
|
{
|
|
|
|
JackTransportEngine::JackTransportEngine(): JackAtomicArrayState<jack_position_t>()
|
|
{
|
|
static_assert(offsetof(JackTransportEngine, fWriteCounter) % sizeof(fWriteCounter) == 0,
|
|
"fWriteCounter must be first member of JackTransportEngine to ensure its alignment");
|
|
fTransportState = JackTransportStopped;
|
|
fTransportCmd = fPreviousCmd = TransportCommandStop;
|
|
fSyncTimeout = 10000000; /* 10 seconds default...
|
|
in case of big netjack1 roundtrip */
|
|
fSyncTimeLeft = 0;
|
|
fTimeBaseMaster = -1;
|
|
fWriteCounter = 0;
|
|
fConditionnal = false;
|
|
fPendingPos = false;
|
|
fNetworkSync = false;
|
|
}
|
|
|
|
// compute the number of cycle for timeout
|
|
void JackTransportEngine::SyncTimeout(jack_nframes_t frame_rate, jack_nframes_t buffer_size)
|
|
{
|
|
long buf_usecs = (long)((buffer_size * (jack_time_t)1000000) / frame_rate);
|
|
fSyncTimeLeft = fSyncTimeout / buf_usecs;
|
|
jack_log("SyncTimeout fSyncTimeout = %ld fSyncTimeLeft = %ld", (long)fSyncTimeout, (long)fSyncTimeLeft);
|
|
}
|
|
|
|
// Server
|
|
int JackTransportEngine::ResetTimebase(int refnum)
|
|
{
|
|
if (fTimeBaseMaster == refnum) {
|
|
jack_position_t* request = WriteNextStateStart(2); // To check
|
|
request->valid = (jack_position_bits_t)0;
|
|
WriteNextStateStop(2);
|
|
fTimeBaseMaster = -1;
|
|
return 0;
|
|
} else {
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
// Server
|
|
int JackTransportEngine::SetTimebaseMaster(int refnum, bool conditionnal)
|
|
{
|
|
if (conditionnal && fTimeBaseMaster > 0) {
|
|
if (refnum != fTimeBaseMaster) {
|
|
jack_log("conditional timebase for ref = %ld failed: %ld is already the master", refnum, fTimeBaseMaster);
|
|
return EBUSY;
|
|
} else {
|
|
jack_log("ref = %ld was already timebase master", refnum);
|
|
return 0;
|
|
}
|
|
} else {
|
|
fTimeBaseMaster = refnum;
|
|
fConditionnal = conditionnal;
|
|
jack_log("new timebase master: ref = %ld", refnum);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// RT
|
|
bool JackTransportEngine::CheckAllRolling(JackClientInterface** table)
|
|
{
|
|
for (int i = GetEngineControl()->fDriverNum; i < CLIENT_NUM; i++) {
|
|
JackClientInterface* client = table[i];
|
|
if (client && client->GetClientControl()->fTransportState != JackTransportRolling) {
|
|
jack_log("CheckAllRolling ref = %ld is not rolling", i);
|
|
return false;
|
|
}
|
|
}
|
|
jack_log("CheckAllRolling");
|
|
return true;
|
|
}
|
|
|
|
// RT
|
|
void JackTransportEngine::MakeAllStartingLocating(JackClientInterface** table)
|
|
{
|
|
for (int i = GetEngineControl()->fDriverNum; i < CLIENT_NUM; i++) {
|
|
JackClientInterface* client = table[i];
|
|
if (client) {
|
|
JackClientControl* control = client->GetClientControl();
|
|
// Inactive clients don't have their process function called at all, so they must appear as already "rolling" for the transport....
|
|
control->fTransportState = (control->fActive && control->fCallback[kRealTimeCallback]) ? JackTransportStarting : JackTransportRolling;
|
|
control->fTransportSync = true;
|
|
control->fTransportTimebase = true;
|
|
jack_log("MakeAllStartingLocating ref = %ld", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// RT
|
|
void JackTransportEngine::MakeAllStopping(JackClientInterface** table)
|
|
{
|
|
for (int i = GetEngineControl()->fDriverNum; i < CLIENT_NUM; i++) {
|
|
JackClientInterface* client = table[i];
|
|
if (client) {
|
|
JackClientControl* control = client->GetClientControl();
|
|
control->fTransportState = JackTransportStopped;
|
|
control->fTransportSync = false;
|
|
control->fTransportTimebase = false;
|
|
jack_log("MakeAllStopping ref = %ld", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// RT
|
|
void JackTransportEngine::MakeAllLocating(JackClientInterface** table)
|
|
{
|
|
for (int i = GetEngineControl()->fDriverNum; i < CLIENT_NUM; i++) {
|
|
JackClientInterface* client = table[i];
|
|
if (client) {
|
|
JackClientControl* control = client->GetClientControl();
|
|
control->fTransportState = JackTransportStopped;
|
|
control->fTransportSync = true;
|
|
control->fTransportTimebase = true;
|
|
jack_log("MakeAllLocating ref = %ld", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// RT
|
|
void JackTransportEngine::CycleBegin(jack_nframes_t frame_rate, jack_time_t time)
|
|
{
|
|
jack_position_t* pending = WriteNextStateStart(1); // Update "pending" state
|
|
pending->usecs = time;
|
|
pending->frame_rate = frame_rate;
|
|
WriteNextStateStop(1);
|
|
}
|
|
|
|
// RT
|
|
void JackTransportEngine::CycleEnd(JackClientInterface** table, jack_nframes_t frame_rate, jack_nframes_t buffer_size)
|
|
{
|
|
TrySwitchState(1); // Switch from "pending" to "current", it always works since there is always a pending state
|
|
|
|
/* Handle any new transport command from the last cycle. */
|
|
transport_command_t cmd = fTransportCmd;
|
|
if (cmd != fPreviousCmd) {
|
|
fPreviousCmd = cmd;
|
|
jack_log("transport command: %s", (cmd == TransportCommandStart ? "Transport start" : "Transport stop"));
|
|
} else {
|
|
cmd = TransportCommandNone;
|
|
}
|
|
|
|
/* state transition switch */
|
|
switch (fTransportState) {
|
|
|
|
case JackTransportStopped:
|
|
// Set a JackTransportStarting for the current cycle, if all clients are ready (no slow_sync) ==> JackTransportRolling next state
|
|
if (cmd == TransportCommandStart) {
|
|
jack_log("transport stopped ==> starting frame = %d", ReadCurrentState()->frame);
|
|
fTransportState = JackTransportStarting;
|
|
MakeAllStartingLocating(table);
|
|
SyncTimeout(frame_rate, buffer_size);
|
|
} else if (fPendingPos) {
|
|
jack_log("transport stopped ==> stopped (locating) frame = %d", ReadCurrentState()->frame);
|
|
MakeAllLocating(table);
|
|
}
|
|
break;
|
|
|
|
case JackTransportStarting:
|
|
if (cmd == TransportCommandStop) {
|
|
jack_log("transport starting ==> stopped frame = %d", ReadCurrentState()->frame);
|
|
fTransportState = JackTransportStopped;
|
|
MakeAllStopping(table);
|
|
} else if (fPendingPos) {
|
|
jack_log("transport starting ==> starting frame = %d", ReadCurrentState()->frame);
|
|
fTransportState = JackTransportStarting;
|
|
MakeAllStartingLocating(table);
|
|
SyncTimeout(frame_rate, buffer_size);
|
|
} else if (--fSyncTimeLeft == 0 || CheckAllRolling(table)) { // Slow clients may still catch up
|
|
if (fNetworkSync) {
|
|
jack_log("transport starting ==> netstarting frame = %d");
|
|
fTransportState = JackTransportNetStarting;
|
|
} else {
|
|
jack_log("transport starting ==> rolling fSyncTimeLeft = %ld", fSyncTimeLeft);
|
|
fTransportState = JackTransportRolling;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JackTransportRolling:
|
|
if (cmd == TransportCommandStop) {
|
|
jack_log("transport rolling ==> stopped");
|
|
fTransportState = JackTransportStopped;
|
|
MakeAllStopping(table);
|
|
} else if (fPendingPos) {
|
|
jack_log("transport rolling ==> starting");
|
|
fTransportState = JackTransportStarting;
|
|
MakeAllStartingLocating(table);
|
|
SyncTimeout(frame_rate, buffer_size);
|
|
}
|
|
break;
|
|
|
|
case JackTransportNetStarting:
|
|
break;
|
|
|
|
default:
|
|
jack_error("Invalid JACK transport state: %d", fTransportState);
|
|
}
|
|
|
|
/* Update timebase, if needed. */
|
|
if (fTransportState == JackTransportRolling) {
|
|
jack_position_t* pending = WriteNextStateStart(1); // Update "pending" state
|
|
pending->frame += buffer_size;
|
|
WriteNextStateStop(1);
|
|
}
|
|
|
|
/* See if an asynchronous position request arrived during the last cycle. */
|
|
jack_position_t* request = WriteNextStateStart(2, &fPendingPos);
|
|
if (fPendingPos) {
|
|
jack_log("New pos = %ld", request->frame);
|
|
jack_position_t* pending = WriteNextStateStart(1);
|
|
CopyPosition(request, pending);
|
|
WriteNextStateStop(1);
|
|
}
|
|
}
|
|
|
|
// Client
|
|
void JackTransportEngine::ReadCurrentPos(jack_position_t* pos)
|
|
{
|
|
UInt16 next_index = GetCurrentIndex();
|
|
UInt16 cur_index;
|
|
do {
|
|
cur_index = next_index;
|
|
memcpy(pos, ReadCurrentState(), sizeof(jack_position_t));
|
|
next_index = GetCurrentIndex();
|
|
} while (cur_index != next_index); // Until a coherent state has been read
|
|
}
|
|
|
|
void JackTransportEngine::RequestNewPos(jack_position_t* pos)
|
|
{
|
|
jack_position_t* request = WriteNextStateStart(2);
|
|
pos->unique_1 = pos->unique_2 = GenerateUniqueID();
|
|
CopyPosition(pos, request);
|
|
jack_log("RequestNewPos pos = %ld", pos->frame);
|
|
WriteNextStateStop(2);
|
|
}
|
|
|
|
jack_transport_state_t JackTransportEngine::Query(jack_position_t* pos)
|
|
{
|
|
if (pos)
|
|
ReadCurrentPos(pos);
|
|
return GetState();
|
|
}
|
|
|
|
jack_nframes_t JackTransportEngine::GetCurrentFrame()
|
|
{
|
|
jack_position_t pos;
|
|
ReadCurrentPos(&pos);
|
|
|
|
if (fTransportState == JackTransportRolling) {
|
|
float usecs = GetMicroSeconds() - pos.usecs;
|
|
jack_nframes_t elapsed = (jack_nframes_t)floor((((float) pos.frame_rate) / 1000000.0f) * usecs);
|
|
return pos.frame + elapsed;
|
|
} else {
|
|
return pos.frame;
|
|
}
|
|
}
|
|
|
|
// RT, client
|
|
void JackTransportEngine::CopyPosition(jack_position_t* from, jack_position_t* to)
|
|
{
|
|
int tries = 0;
|
|
long timeout = 1000;
|
|
|
|
do {
|
|
/* throttle the busy wait if we don't get the answer
|
|
* very quickly. See comment above about this
|
|
* design.
|
|
*/
|
|
if (tries > 10) {
|
|
JackSleep(20);
|
|
tries = 0;
|
|
|
|
/* debug code to avoid system hangs... */
|
|
if (--timeout == 0) {
|
|
jack_error("hung in loop copying position B");
|
|
abort();
|
|
}
|
|
}
|
|
*to = *from;
|
|
tries++;
|
|
|
|
} while (to->unique_1 != to->unique_2);
|
|
}
|
|
|
|
|
|
} // end of namespace
|