1 /*
2 Copyright (C) 2001 Paul Davis
3 Copyright (C) 2004-2008 Grame
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14 
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 
19 */
20 
21 #include "JackTransportEngine.h"
22 #include "JackClientInterface.h"
23 #include "JackClientControl.h"
24 #include "JackEngineControl.h"
25 #include "JackGlobals.h"
26 #include "JackError.h"
27 #include "JackTime.h"
28 #include <assert.h>
29 #include <math.h>
30 #include <stdlib.h>
31 
32 using namespace std;
33 
34 namespace Jack
35 {
36 
JackTransportEngine()37 JackTransportEngine::JackTransportEngine(): JackAtomicArrayState<jack_position_t>()
38 {
39     fTransportState = JackTransportStopped;
40     fTransportCmd = fPreviousCmd = TransportCommandStop;
41     fSyncTimeout = 10000000;	/* 10 seconds default...
42 				   in case of big netjack1 roundtrip */
43     fSyncTimeLeft = 0;
44     fTimeBaseMaster = -1;
45     fWriteCounter = 0;
46     fConditionnal = false;
47     fPendingPos = false;
48     fNetworkSync = false;
49 }
50 
51 // compute the number of cycle for timeout
SyncTimeout(jack_nframes_t frame_rate,jack_nframes_t buffer_size)52 void JackTransportEngine::SyncTimeout(jack_nframes_t frame_rate, jack_nframes_t buffer_size)
53 {
54     long buf_usecs = (long)((buffer_size * (jack_time_t)1000000) / frame_rate);
55     fSyncTimeLeft = fSyncTimeout / buf_usecs;
56     jack_log("SyncTimeout fSyncTimeout = %ld fSyncTimeLeft = %ld", (long)fSyncTimeout, (long)fSyncTimeLeft);
57 }
58 
59 // Server
ResetTimebase(int refnum)60 int JackTransportEngine::ResetTimebase(int refnum)
61 {
62     if (fTimeBaseMaster == refnum) {
63         jack_position_t* request = WriteNextStateStart(2); // To check
64         request->valid = (jack_position_bits_t)0;
65         WriteNextStateStop(2);
66         fTimeBaseMaster = -1;
67         return 0;
68     } else {
69         return EINVAL;
70     }
71 }
72 
73 // Server
SetTimebaseMaster(int refnum,bool conditionnal)74 int JackTransportEngine::SetTimebaseMaster(int refnum, bool conditionnal)
75 {
76     if (conditionnal && fTimeBaseMaster > 0) {
77         if (refnum != fTimeBaseMaster) {
78             jack_log("conditional timebase for ref = %ld failed: %ld is already the master", refnum, fTimeBaseMaster);
79             return EBUSY;
80         } else {
81             jack_log("ref = %ld was already timebase master", refnum);
82             return 0;
83         }
84     } else {
85         fTimeBaseMaster = refnum;
86         fConditionnal = conditionnal;
87         jack_log("new timebase master: ref = %ld", refnum);
88         return 0;
89     }
90 }
91 
92 // RT
CheckAllRolling(JackClientInterface ** table)93 bool JackTransportEngine::CheckAllRolling(JackClientInterface** table)
94 {
95     for (int i = GetEngineControl()->fDriverNum; i < CLIENT_NUM; i++) {
96         JackClientInterface* client = table[i];
97         if (client && client->GetClientControl()->fTransportState != JackTransportRolling) {
98             jack_log("CheckAllRolling ref = %ld is not rolling", i);
99             return false;
100         }
101     }
102     jack_log("CheckAllRolling");
103     return true;
104 }
105 
106 // RT
MakeAllStartingLocating(JackClientInterface ** table)107 void JackTransportEngine::MakeAllStartingLocating(JackClientInterface** table)
108 {
109     for (int i = GetEngineControl()->fDriverNum; i < CLIENT_NUM; i++) {
110         JackClientInterface* client = table[i];
111         if (client) {
112             JackClientControl* control = client->GetClientControl();
113             // Inactive clients don't have their process function called at all, so they must appear as already "rolling" for the transport....
114             control->fTransportState = (control->fActive && control->fCallback[kRealTimeCallback]) ? JackTransportStarting : JackTransportRolling;
115             control->fTransportSync = true;
116             control->fTransportTimebase = true;
117             jack_log("MakeAllStartingLocating ref = %ld", i);
118         }
119     }
120 }
121 
122 // RT
MakeAllStopping(JackClientInterface ** table)123 void JackTransportEngine::MakeAllStopping(JackClientInterface** table)
124 {
125     for (int i = GetEngineControl()->fDriverNum; i < CLIENT_NUM; i++) {
126         JackClientInterface* client = table[i];
127         if (client) {
128             JackClientControl* control = client->GetClientControl();
129             control->fTransportState = JackTransportStopped;
130             control->fTransportSync = false;
131             control->fTransportTimebase = false;
132             jack_log("MakeAllStopping ref = %ld", i);
133         }
134     }
135 }
136 
137 // RT
MakeAllLocating(JackClientInterface ** table)138 void JackTransportEngine::MakeAllLocating(JackClientInterface** table)
139 {
140     for (int i = GetEngineControl()->fDriverNum; i < CLIENT_NUM; i++) {
141         JackClientInterface* client = table[i];
142         if (client) {
143             JackClientControl* control = client->GetClientControl();
144             control->fTransportState = JackTransportStopped;
145             control->fTransportSync = true;
146             control->fTransportTimebase = true;
147             jack_log("MakeAllLocating ref = %ld", i);
148         }
149     }
150 }
151 
152 // RT
CycleBegin(jack_nframes_t frame_rate,jack_time_t time)153 void JackTransportEngine::CycleBegin(jack_nframes_t frame_rate, jack_time_t time)
154 {
155     jack_position_t* pending = WriteNextStateStart(1); // Update "pending" state
156     pending->usecs = time;
157     pending->frame_rate = frame_rate;
158     WriteNextStateStop(1);
159 }
160 
161 // RT
CycleEnd(JackClientInterface ** table,jack_nframes_t frame_rate,jack_nframes_t buffer_size)162 void JackTransportEngine::CycleEnd(JackClientInterface** table, jack_nframes_t frame_rate, jack_nframes_t buffer_size)
163 {
164     TrySwitchState(1);	// Switch from "pending" to "current", it always works since there is always a pending state
165 
166     /* Handle any new transport command from the last cycle. */
167     transport_command_t cmd = fTransportCmd;
168     if (cmd != fPreviousCmd) {
169         fPreviousCmd = cmd;
170         jack_log("transport command: %s", (cmd == TransportCommandStart ? "Transport start" : "Transport stop"));
171     } else {
172         cmd = TransportCommandNone;
173     }
174 
175     /* state transition switch */
176     switch (fTransportState) {
177 
178         case JackTransportStopped:
179             // Set a JackTransportStarting for the current cycle, if all clients are ready (no slow_sync) ==> JackTransportRolling next state
180             if (cmd == TransportCommandStart) {
181                 jack_log("transport stopped ==> starting frame = %d", ReadCurrentState()->frame);
182                 fTransportState = JackTransportStarting;
183                 MakeAllStartingLocating(table);
184                 SyncTimeout(frame_rate, buffer_size);
185             } else if (fPendingPos) {
186                 jack_log("transport stopped ==> stopped (locating) frame = %d", ReadCurrentState()->frame);
187                 MakeAllLocating(table);
188             }
189             break;
190 
191         case JackTransportStarting:
192             if (cmd == TransportCommandStop) {
193                 jack_log("transport starting ==> stopped frame = %d", ReadCurrentState()->frame);
194                 fTransportState = JackTransportStopped;
195                 MakeAllStopping(table);
196             } else if (fPendingPos) {
197                 jack_log("transport starting ==> starting frame = %d", ReadCurrentState()->frame);
198                 fTransportState = JackTransportStarting;
199                 MakeAllStartingLocating(table);
200                 SyncTimeout(frame_rate, buffer_size);
201             } else if (--fSyncTimeLeft == 0 || CheckAllRolling(table)) {  // Slow clients may still catch up
202                 if (fNetworkSync) {
203                     jack_log("transport starting ==> netstarting frame = %d");
204                     fTransportState = JackTransportNetStarting;
205                 } else {
206                     jack_log("transport starting ==> rolling fSyncTimeLeft = %ld", fSyncTimeLeft);
207                     fTransportState = JackTransportRolling;
208                 }
209             }
210             break;
211 
212         case JackTransportRolling:
213             if (cmd == TransportCommandStop) {
214                 jack_log("transport rolling ==> stopped");
215                 fTransportState = JackTransportStopped;
216                 MakeAllStopping(table);
217             } else if (fPendingPos) {
218                 jack_log("transport rolling ==> starting");
219                 fTransportState = JackTransportStarting;
220                 MakeAllStartingLocating(table);
221                 SyncTimeout(frame_rate, buffer_size);
222             }
223             break;
224 
225         case JackTransportNetStarting:
226             break;
227 
228         default:
229             jack_error("Invalid JACK transport state: %d", fTransportState);
230     }
231 
232     /* Update timebase, if needed. */
233     if (fTransportState == JackTransportRolling) {
234         jack_position_t* pending = WriteNextStateStart(1); // Update "pending" state
235         pending->frame += buffer_size;
236         WriteNextStateStop(1);
237     }
238 
239     /* See if an asynchronous position request arrived during the last cycle. */
240     jack_position_t* request = WriteNextStateStart(2, &fPendingPos);
241     if (fPendingPos) {
242         jack_log("New pos = %ld", request->frame);
243         jack_position_t* pending = WriteNextStateStart(1);
244         CopyPosition(request, pending);
245         WriteNextStateStop(1);
246     }
247 }
248 
249 // Client
ReadCurrentPos(jack_position_t * pos)250 void JackTransportEngine::ReadCurrentPos(jack_position_t* pos)
251 {
252     UInt16 next_index = GetCurrentIndex();
253     UInt16 cur_index;
254     do {
255         cur_index = next_index;
256         memcpy(pos, ReadCurrentState(), sizeof(jack_position_t));
257         next_index = GetCurrentIndex();
258     } while (cur_index != next_index); // Until a coherent state has been read
259 }
260 
RequestNewPos(jack_position_t * pos)261 void JackTransportEngine::RequestNewPos(jack_position_t* pos)
262 {
263     jack_position_t* request = WriteNextStateStart(2);
264     pos->unique_1 = pos->unique_2 = GenerateUniqueID();
265     CopyPosition(pos, request);
266     jack_log("RequestNewPos pos = %ld", pos->frame);
267     WriteNextStateStop(2);
268 }
269 
Query(jack_position_t * pos)270 jack_transport_state_t JackTransportEngine::Query(jack_position_t* pos)
271 {
272     if (pos)
273         ReadCurrentPos(pos);
274     return GetState();
275 }
276 
GetCurrentFrame()277 jack_nframes_t JackTransportEngine::GetCurrentFrame()
278 {
279     jack_position_t pos;
280     ReadCurrentPos(&pos);
281 
282     if (fTransportState == JackTransportRolling) {
283         float usecs = GetMicroSeconds() - pos.usecs;
284         jack_nframes_t elapsed = (jack_nframes_t)floor((((float) pos.frame_rate) / 1000000.0f) * usecs);
285         return pos.frame + elapsed;
286     } else {
287         return pos.frame;
288     }
289 }
290 
291 // RT, client
CopyPosition(jack_position_t * from,jack_position_t * to)292 void JackTransportEngine::CopyPosition(jack_position_t* from, jack_position_t* to)
293 {
294     int tries = 0;
295     long timeout = 1000;
296 
297     do {
298         /* throttle the busy wait if we don't get the answer
299          * very quickly. See comment above about this
300          * design.
301          */
302         if (tries > 10) {
303             JackSleep(20);
304             tries = 0;
305 
306             /* debug code to avoid system hangs... */
307             if (--timeout == 0) {
308                 jack_error("hung in loop copying position B");
309                 abort();
310             }
311         }
312         *to = *from;
313         tries++;
314 
315     } while (to->unique_1 != to->unique_2);
316 }
317 
318 
319 } // end of namespace
320