• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mcallegari / qlcplus / 13633248611

03 Mar 2025 02:31PM UTC coverage: 31.871% (+0.4%) from 31.5%
13633248611

push

github

web-flow
actions: add chrpath to profile

14689 of 46089 relevant lines covered (31.87%)

26426.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.19
/engine/src/mastertimer.cpp
1
/*
2
  Q Light Controller Plus
3
  mastertimer.cpp
4

5
  Copyright (C) Heikki Junnila
6
                Massimo Callegari
7

8
  Licensed under the Apache License, Version 2.0 (the "License");
9
  you may not use this file except in compliance with the License.
10
  You may obtain a copy of the License at
11

12
      http://www.apache.org/licenses/LICENSE-2.0.txt
13

14
  Unless required by applicable law or agreed to in writing, software
15
  distributed under the License is distributed on an "AS IS" BASIS,
16
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
  See the License for the specific language governing permissions and
18
  limitations under the License.
19
*/
20

21
#include <QDebug>
22
#include <QSettings>
23
#include <QElapsedTimer>
24
#include <QMutexLocker>
25

26
#if defined(WIN32) || defined(Q_OS_WIN)
27
#   include "mastertimer-win32.h"
28
#else
29
#   include <unistd.h>
30
#   include "mastertimer-unix.h"
31
#endif
32

33
#include "inputoutputmap.h"
34
#include "genericfader.h"
35
#include "mastertimer.h"
36
#include "dmxsource.h"
37
#include "function.h"
38
#include "universe.h"
39
#include "doc.h"
40

41
#define MASTERTIMER_FREQUENCY "mastertimer/frequency"
42
#define LATE_TO_BEAT_THRESHOLD 25
43

44
/** The timer tick frequency in Hertz */
45
uint MasterTimer::s_frequency = 50;
46
uint MasterTimer::s_tick = 20;
47

48
//#define DEBUG_MASTERTIMER
49

50
#ifdef DEBUG_MASTERTIMER
51
quint64 ticksCount = 0;
52
#endif
53

54
/*****************************************************************************
55
 * Initialization
56
 *****************************************************************************/
57

58
MasterTimer::MasterTimer(Doc* doc)
210✔
59
    : QObject(doc)
60
    , d_ptr(new MasterTimerPrivate(this))
210✔
61
    , m_stopAllFunctions(false)
210✔
62
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
63
    , m_dmxSourceListMutex(QMutex::Recursive)
64
#endif
65
    , m_beatSourceType(None)
210✔
66
    , m_currentBPM(120)
210✔
67
    , m_beatTimeDuration(500)
210✔
68
    , m_beatRequested(false)
210✔
69
    , m_beatTimer(new QElapsedTimer())
210✔
70
    , m_lastBeatOffset(0)
420✔
71
{
72
    Q_ASSERT(doc != NULL);
73
    Q_ASSERT(d_ptr != NULL);
74

75
    QSettings settings;
210✔
76
    QVariant var = settings.value(MASTERTIMER_FREQUENCY);
420✔
77
    if (var.isValid() == true)
210✔
78
        s_frequency = var.toUInt();
×
79

80
    s_tick = uint(double(1000) / double(s_frequency));
210✔
81
}
210✔
82

83
MasterTimer::~MasterTimer()
387✔
84
{
85
    if (d_ptr->isRunning() == true)
210✔
86
        stop();
1✔
87

88
    delete d_ptr;
210✔
89
    d_ptr = NULL;
210✔
90

91
    delete m_beatTimer;
210✔
92
}
387✔
93

94
void MasterTimer::start()
10✔
95
{
96
    Q_ASSERT(d_ptr != NULL);
97
    d_ptr->start();
10✔
98
}
10✔
99

100
void MasterTimer::stop()
5✔
101
{
102
    Q_ASSERT(d_ptr != NULL);
103
    stopAllFunctions();
5✔
104
    d_ptr->stop();
5✔
105
}
5✔
106

107
void MasterTimer::timerTick()
588✔
108
{
109
    Doc *doc = qobject_cast<Doc*> (parent());
110
    Q_ASSERT(doc != NULL);
111

112
#ifdef DEBUG_MASTERTIMER
113
    qDebug() << "[MasterTimer] *********** tick:" << ticksCount++ << "**********";
114
#endif
115

116
    switch (m_beatSourceType)
588✔
117
    {
118
        case Internal:
×
119
        {
120
            int elapsedTime = qRound((double)m_beatTimer->nsecsElapsed() / 1000000) + m_lastBeatOffset;
×
121
            //qDebug() << "Elapsed beat:" << elapsedTime;
122
            if (elapsedTime >= m_beatTimeDuration)
×
123
            {
124
                // it's time to fire a beat
125
                m_beatRequested = true;
×
126

127
                // restart the time for the next beat, starting at a delta
128
                // milliseconds, otherwise it will generate an unpleasant drift
129
                //qDebug() << "Elapsed:" << elapsedTime << ", delta:" << elapsedTime - m_beatTimeDuration;
130
                m_lastBeatOffset = elapsedTime - m_beatTimeDuration;
×
131
                m_beatTimer->restart();
×
132

133
                // inform the listening classes that a beat is happening
134
                emit beat();
×
135
            }
136
        }
137
        break;
138
        case External:
139
        break;
140

141
        case None:
588✔
142
        default:
143
            m_beatRequested = false;
588✔
144
        break;
588✔
145
    }
146

147
    QList<Universe *> universes = doc->inputOutputMap()->claimUniverses();
1,176✔
148

149
    timerTickFunctions(universes);
588✔
150
    timerTickDMXSources(universes);
588✔
151

152
    doc->inputOutputMap()->releaseUniverses();
588✔
153

154
    m_beatRequested = false;
588✔
155

156
    //qDebug() << ">>>>>>>> MASTERTIMER TICK";
157
    emit tickReady();
588✔
158
}
588✔
159

160
uint MasterTimer::frequency()
15✔
161
{
162
    return s_frequency;
15✔
163
}
164

165
uint MasterTimer::tick()
1,724,847✔
166
{
167
    return s_tick;
1,724,847✔
168
}
169

170
/*****************************************************************************
171
 * Functions
172
 *****************************************************************************/
173

174
void MasterTimer::startFunction(Function* function)
151✔
175
{
176
    if (function == NULL)
151✔
177
        return;
1✔
178

179
    QMutexLocker locker(&m_functionListMutex);
150✔
180
    if (m_startQueue.contains(function) == false)
150✔
181
        m_startQueue.append(function);
150✔
182
}
183

184
void MasterTimer::stopAllFunctions()
8✔
185
{
186
    m_stopAllFunctions = true;
8✔
187

188
    /* Wait until all functions have been stopped */
189
    while (runningFunctions() > 0)
20✔
190
    {
191
#if defined(WIN32) || defined(Q_OS_WIN)
192
        Sleep(10);
193
#else
194
        usleep(10000);
12✔
195
#endif
196
    }
197

198
    m_stopAllFunctions = false;
8✔
199
}
8✔
200

201
void MasterTimer::fadeAndStopAll(int timeout)
×
202
{
203
    if (timeout)
×
204
    {
205
        Doc *doc = qobject_cast<Doc*> (parent());
206
        Q_ASSERT(doc != NULL);
207

208
        QList<Universe *> universes = doc->inputOutputMap()->claimUniverses();
×
209
        foreach (Universe *universe, universes)
×
210
            universe->setFaderFadeOut(timeout);
×
211

212
        doc->inputOutputMap()->releaseUniverses();
×
213
    }
×
214

215
    // At last, stop all functions
216
    stopAllFunctions();
×
217
}
×
218

219
int MasterTimer::runningFunctions() const
79✔
220
{
221
    return m_functionList.size();
79✔
222
}
223

224
void MasterTimer::timerTickFunctions(QList<Universe *> universes)
588✔
225
{
226
    // List of m_functionList indices that should be removed at the end of this
227
    // function. The functions at the indices have been stopped.
228
    QList<int> removeList;
229

230
    bool functionListHasChanged = false;
231
    bool stoppedAFunction = true;
232
    bool firstIteration = true;
233

234
    while (stoppedAFunction)
1,288✔
235
    {
236
        stoppedAFunction = false;
237
        removeList.clear();
700✔
238

239
        for (int i = 0; i < m_functionList.size(); i++)
1,334✔
240
        {
241
            Function* function = m_functionList.at(i);
634✔
242

243
            if (function != NULL)
634✔
244
            {
245
                /* Run the function unless it's supposed to be stopped */
246
                if (function->stopped() == false && m_stopAllFunctions == false)
634✔
247
                {
248
                    if (firstIteration)
511✔
249
                        function->write(this, universes);
482✔
250
                }
251
                else
252
                {
253
                    // Clear function's parentList
254
                    if (m_stopAllFunctions)
123✔
255
                        function->stop(FunctionParent::master());
14✔
256
                    /* Function should be stopped instead */
257
                    function->postRun(this, universes);
123✔
258
                    //qDebug() << "[MasterTimer] Add function (ID: " << function->id() << ") to remove list ";
259
                    removeList << i; // Don't remove the item from the list just yet.
260
                    functionListHasChanged = true;
261
                    stoppedAFunction = true;
262

263
                    emit functionStopped(function->id());
123✔
264
                }
265
            }
266
        }
267

268
        // Remove functions that need to be removed AFTER all functions have been run
269
        // for this round. This is done separately to prevent a case when a function
270
        // is first removed and then another is added (chaser, for example), keeping the
271
        // list's size the same, thus preventing the last added function from being run
272
        // on this round. The indices in removeList are automatically sorted because the
273
        // list is iterated with an int above from 0 to size, so iterating the removeList
274
        // backwards here will always remove the correct indices.
275
        QListIterator <int> it(removeList);
700✔
276
        it.toBack();
277
        while (it.hasPrevious() == true)
823✔
278
            m_functionList.removeAt(it.previous());
123✔
279

280
        firstIteration = false;
281
    }
282

283
    {
284
        QMutexLocker locker(&m_functionListMutex);
588✔
285
        while (m_startQueue.size() > 0)
728✔
286
        {
287
            QList<Function*> startQueue(m_startQueue);
140✔
288
            m_startQueue.clear();
140✔
289
            locker.unlock();
140✔
290

291
            foreach (Function* f, startQueue)
288✔
292
            {
293
                if (m_functionList.contains(f))
148✔
294
                {
295
                    f->postRun(this, universes);
1✔
296
                }
297
                else
298
                {
299
                    m_functionList.append(f);
147✔
300
                    functionListHasChanged = true;
301
                }
302
                f->preRun(this);
148✔
303
                f->write(this, universes);
148✔
304
                emit functionStarted(f->id());
148✔
305
            }
306

307
            locker.relock();
140✔
308
        }
140✔
309
    }
310

311
    if (functionListHasChanged)
588✔
312
        emit functionListChanged();
150✔
313
}
588✔
314

315
/****************************************************************************
316
 * DMX Sources
317
 ****************************************************************************/
318

319
void MasterTimer::registerDMXSource(DMXSource *source)
23✔
320
{
321
    Q_ASSERT(source != NULL);
322

323
    QMutexLocker lock(&m_dmxSourceListMutex);
23✔
324
    if (m_dmxSourceList.contains(source) == false)
23✔
325
        m_dmxSourceList.append(source);
19✔
326
}
23✔
327

328
void MasterTimer::unregisterDMXSource(DMXSource *source)
21✔
329
{
330
    Q_ASSERT(source != NULL);
331

332
    QMutexLocker lock(&m_dmxSourceListMutex);
21✔
333
    m_dmxSourceList.removeAll(source);
21✔
334
}
21✔
335

336
void MasterTimer::timerTickDMXSources(QList<Universe *> universes)
588✔
337
{
338
    /* Lock before accessing the DMX sources list. */
339
    QMutexLocker lock(&m_dmxSourceListMutex);
588✔
340

341
    foreach (DMXSource *source, m_dmxSourceList)
1,286✔
342
    {
343
        Q_ASSERT(source != NULL);
344

345
#ifdef DEBUG_MASTERTIMER
346
        qDebug() << "[MasterTimer] ticking DMX source" << i;
347
#endif
348

349
        /* Get DMX data from the source */
350
        source->writeDMX(this, universes);
110✔
351
    }
352
}
588✔
353

354
/*************************************************************************
355
 * Beats generation
356
 *************************************************************************/
357

358
void MasterTimer::setBeatSourceType(MasterTimer::BeatsSourceType type)
×
359
{
360
    if (type == m_beatSourceType)
×
361
        return;
362

363
    // alright, this causes a time drift of maximum 1ms per beat
364
    // but at the moment I am not looking for a better solution
365
    m_beatTimeDuration = 60000 / m_currentBPM;
×
366
    m_beatTimer->restart();
×
367

368
    m_beatSourceType = type;
×
369
}
370

371
MasterTimer::BeatsSourceType MasterTimer::beatSourceType() const
×
372
{
373
    return m_beatSourceType;
×
374
}
375

376
void MasterTimer::requestBpmNumber(int bpm)
×
377
{
378
    if (bpm == m_currentBPM)
×
379
        return;
380

381
    m_currentBPM = bpm;
×
382
    m_beatTimeDuration = 60000 / m_currentBPM;
×
383
    m_beatTimer->restart();
×
384

385
    emit bpmNumberChanged(bpm);
×
386
}
387

388
int MasterTimer::bpmNumber() const
2✔
389
{
390
    return m_currentBPM;
2✔
391
}
392

393
int MasterTimer::beatTimeDuration() const
×
394
{
395
    return m_beatTimeDuration;
×
396
}
397

398
int MasterTimer::timeToNextBeat() const
×
399
{
400
    return m_beatTimeDuration - m_beatTimer->elapsed();
×
401
}
402

403
int MasterTimer::nextBeatTimeOffset() const
×
404
{
405
    // get the time offset to the next beat
406
    int toNext = timeToNextBeat();
×
407
    // get the percentage of beat time passed
408
    int beatPercentage = (100 * toNext) / m_beatTimeDuration;
×
409

410
    // if a Function has been started within the first LATE_TO_BEAT_THRESHOLD %
411
    // of a beat, then it means it is "late" but there's
412
    // no need to wait a whole beat
413
    if (beatPercentage <= LATE_TO_BEAT_THRESHOLD)
×
414
        return toNext;
415

416
    // otherwise we're running early, so we should wait the
417
    // whole remaining time
418
    return -toNext;
×
419
}
420

421
bool MasterTimer::isBeat() const
398✔
422
{
423
    return m_beatRequested;
398✔
424
}
425

426
void MasterTimer::requestBeat()
×
427
{
428
    // forceful request of a beat, processed at
429
    // the next timerTick call
430
    m_beatRequested = true;
×
431
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc