/* Copyright (C) 2001 Paul Davis Copyright (C) 2004-2006 Grame This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "JackTransportEngine.h" #include "JackClientControl.h" #include "JackError.h" #include "JackTime.h" #include #include using namespace std; namespace Jack { JackTransportEngine::JackTransportEngine(): JackAtomicArrayState() { fTransportState = JackTransportStopped; fTransportCmd = fPreviousCmd = TransportCommandStop; fSyncTimeout = 2000000; /* 2 second default */ fSyncTimeLeft = 0; fTimeBaseMaster = -1; fWriteCounter = 0; fPendingPos = 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; JackLog("SyncTimeout fSyncTimeout = %ld fSyncTimeLeft = %ld\n", (long)fSyncTimeout, (long)fSyncTimeLeft); } 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; } } int JackTransportEngine::SetTimebase(int refnum, bool conditionnal) { if (conditionnal && fTimeBaseMaster > 0) { if (refnum != fTimeBaseMaster) { JackLog("conditional timebase for ref = %ld failed: %ld is already the master\n", refnum, fTimeBaseMaster); return EBUSY; } else { JackLog("ref = %ld was already timebase master\n", refnum); return 0; } } else { fTimeBaseMaster = refnum; JackLog("new timebase master: ref = %ld\n", refnum); return 0; } } bool JackTransportEngine::CheckOneSynching(JackClientInterface** table) { for (int i = REAL_REFNUM; i < CLIENT_NUM; i++) { JackClientInterface* client = table[i]; if (client && client->GetClientControl()->fTransportState == JackTransportSynching) { JackLog("CheckOneSynching\n"); return true; } } return false; } bool JackTransportEngine::CheckAllRolling(JackClientInterface** table) { for (int i = REAL_REFNUM; i < CLIENT_NUM; i++) { JackClientInterface* client = table[i]; if (client && client->GetClientControl()->fTransportState != JackTransportRolling) { JackLog("CheckAllRolling refnum = %ld is not rolling\n", i); return false; } } JackLog("CheckAllRolling\n"); return true; } void JackTransportEngine::MakeAllStarting(JackClientInterface** table) { for (int i = REAL_REFNUM; i < CLIENT_NUM; i++) { JackClientInterface* client = table[i]; if (client) { // Unactive clients don't have their process function called at all, they appear as already "rolling" for the transport.... client->GetClientControl()->fTransportState = (client->GetClientControl()->fActive) ? JackTransportStarting : JackTransportRolling; JackLog("MakeAllStarting refnum = %ld \n", i); } } JackLog("MakeAllStarting\n"); } void JackTransportEngine::CycleBegin(jack_nframes_t frame_rate, jack_time_t time) // really needed?? (woule be done in CycleEnd...) { jack_position_t* pending = WriteNextStateStart(1); // Update "pending" state pending->usecs = time; pending->frame_rate = frame_rate; WriteNextStateStop(1); } 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; JackLog("transport command: %s\n", (cmd == TransportCommandStart ? "START" : "STOP")); } else { cmd = TransportCommandNone; } /* state transition switch */ switch (fTransportState) { case JackTransportSynching: if (cmd == TransportCommandStart) { fTransportState = JackTransportStarting; MakeAllStarting(table); SyncTimeout(frame_rate, buffer_size); JackLog("transport locate ==> starting....\n"); } else if (fPendingPos) { fTransportState = JackTransportSynching; JackLog("transport locate ==> locate....\n"); } else { fTransportState = JackTransportStopped; JackLog("transport locate ==> stopped....\n"); } break; case JackTransportStopped: // Set a JackTransportStarting for the current cycle, if all clients are ready (now slow_sync) ==> JackTransportRolling next state if (cmd == TransportCommandStart) { fTransportState = JackTransportStarting; MakeAllStarting(table); SyncTimeout(frame_rate, buffer_size); JackLog("transport stopped ==> starting....\n"); } else if (fPendingPos || CheckOneSynching(table)) { fTransportState = JackTransportSynching; JackLog("transport stopped ==> locate....\n"); } break; case JackTransportStarting: JackLog("transport starting fSyncTimeLeft %ld\n", fSyncTimeLeft); if (cmd == TransportCommandStop) { fTransportState = JackTransportStopped; JackLog("transport starting ==> stopped\n"); } else if (fPendingPos) { fTransportState = JackTransportStarting; MakeAllStarting(table); SyncTimeout(frame_rate, buffer_size); } else if (--fSyncTimeLeft == 0 || CheckAllRolling(table)) { fTransportState = JackTransportRolling; JackLog("transport starting ==> rolling.... fSyncTimeLeft %ld\n", fSyncTimeLeft); } break; case JackTransportRolling: if (cmd == TransportCommandStop) { fTransportState = JackTransportStopped; JackLog("transport rolling ==> stopped\n"); } else if (fPendingPos || CheckOneSynching(table)) { fTransportState = JackTransportStarting; MakeAllStarting(table); SyncTimeout(frame_rate, buffer_size); JackLog("transport rolling ==> starting....\n"); } 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) { JackLog("New pos = %ld\n", request->frame); jack_position_t* pending = WriteNextStateStart(1); TransportCopyPosition(request, pending); WriteNextStateStop(1); } } 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::TransportCopyPosition(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