/* 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 #include #include using namespace std; namespace Jack { JackTransportEngine::JackTransportEngine(): JackAtomicArrayState() { 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