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

mcallegari / qlcplus / 19144422256

06 Nov 2025 05:33PM UTC coverage: 34.256% (-0.1%) from 34.358%
19144422256

push

github

mcallegari
Back to 5.1.0 debug

17718 of 51723 relevant lines covered (34.26%)

19528.23 hits per line

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

72.53
/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 <QMutexLocker>
24

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

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

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

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

47
//#define DEBUG_MASTERTIMER
48

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

53
/*****************************************************************************
54
 * Initialization
55
 *****************************************************************************/
56

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

73
    QSettings settings;
229✔
74
    QVariant var = settings.value(MASTERTIMER_FREQUENCY);
229✔
75
    if (var.isValid() == true)
229✔
76
        s_frequency = var.toUInt();
×
77

78
    s_tick = uint(double(1000) / double(s_frequency));
229✔
79
}
229✔
80

81
MasterTimer::~MasterTimer()
425✔
82
{
83
    if (d_ptr->isRunning() == true)
229✔
84
        stop();
1✔
85

86
    delete d_ptr;
229✔
87
    d_ptr = NULL;
229✔
88
}
425✔
89

90
void MasterTimer::start()
10✔
91
{
92
    Q_ASSERT(d_ptr != NULL);
10✔
93
    d_ptr->start();
10✔
94
}
10✔
95

96
void MasterTimer::stop()
5✔
97
{
98
    Q_ASSERT(d_ptr != NULL);
5✔
99
    stopAllFunctions();
5✔
100
    d_ptr->stop();
5✔
101
}
5✔
102

103
void MasterTimer::timerTick()
588✔
104
{
105
    Doc *doc = qobject_cast<Doc*> (parent());
588✔
106
    Q_ASSERT(doc != NULL);
588✔
107

108
#ifdef DEBUG_MASTERTIMER
109
    qDebug() << "[MasterTimer] *********** tick:" << ticksCount++ << "**********";
110
#endif
111

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

123
                // restart the time for the next beat, starting at a delta
124
                // milliseconds, otherwise it will generate an unpleasant drift
125
                //qDebug() << "Elapsed:" << elapsedTime << ", delta:" << elapsedTime - m_beatTimeDuration;
126
                m_lastBeatOffset = elapsedTime - m_beatTimeDuration;
×
127
                m_beatTimer.restart();
×
128

129
                // inform the listening classes that a beat is happening
130
                emit beat();
×
131
            }
132
        }
133
        break;
×
134
        case External:
×
135
        break;
×
136

137
        case None:
588✔
138
        default:
139
            m_beatRequested = false;
588✔
140
        break;
588✔
141
    }
142

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

145
    timerTickFunctions(universes);
588✔
146
    timerTickDMXSources(universes);
588✔
147

148
    doc->inputOutputMap()->releaseUniverses();
588✔
149

150
    m_beatRequested = false;
588✔
151

152
    //qDebug() << ">>>>>>>> MASTERTIMER TICK";
153
    emit tickReady();
588✔
154
}
588✔
155

156
uint MasterTimer::frequency()
15✔
157
{
158
    return s_frequency;
15✔
159
}
160

161
uint MasterTimer::tick()
1,724,847✔
162
{
163
    return s_tick;
1,724,847✔
164
}
165

166
/*****************************************************************************
167
 * Functions
168
 *****************************************************************************/
169

170
void MasterTimer::startFunction(Function* function)
151✔
171
{
172
    if (function == NULL)
151✔
173
        return;
1✔
174

175
    QMutexLocker locker(&m_functionListMutex);
150✔
176
    if (m_startQueue.contains(function) == false)
150✔
177
        m_startQueue.append(function);
150✔
178
}
150✔
179

180
void MasterTimer::stopAllFunctions()
8✔
181
{
182
    m_stopAllFunctions = true;
8✔
183

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

194
    m_stopAllFunctions = false;
8✔
195
}
8✔
196

197
void MasterTimer::fadeAndStopAll(int timeout)
×
198
{
199
    if (timeout)
×
200
    {
201
        Doc *doc = qobject_cast<Doc*> (parent());
×
202
        Q_ASSERT(doc != NULL);
×
203

204
        QList<Universe *> universes = doc->inputOutputMap()->claimUniverses();
×
205
        foreach (Universe *universe, universes)
×
206
            universe->setFaderFadeOut(timeout);
×
207

208
        doc->inputOutputMap()->releaseUniverses();
×
209
    }
×
210

211
    // At last, stop all functions
212
    stopAllFunctions();
×
213
}
×
214

215
int MasterTimer::runningFunctions() const
79✔
216
{
217
    return m_functionList.size();
79✔
218
}
219

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

226
    bool functionListHasChanged = false;
588✔
227
    bool stoppedAFunction = true;
588✔
228
    bool firstIteration = true;
588✔
229

230
    while (stoppedAFunction)
1,288✔
231
    {
232
        stoppedAFunction = false;
700✔
233
        removeList.clear();
700✔
234

235
        for (int i = 0; i < m_functionList.size(); i++)
1,334✔
236
        {
237
            Function* function = m_functionList.at(i);
634✔
238

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

259
                    emit functionStopped(function->id());
123✔
260
                }
261
            }
262
        }
263

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

276
        firstIteration = false;
700✔
277
    }
700✔
278

279
    {
280
        QMutexLocker locker(&m_functionListMutex);
588✔
281
        while (m_startQueue.size() > 0)
728✔
282
        {
283
            QList<Function*> startQueue(m_startQueue);
140✔
284
            m_startQueue.clear();
140✔
285
            locker.unlock();
140✔
286

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

303
            locker.relock();
140✔
304
        }
140✔
305
    }
588✔
306

307
    if (functionListHasChanged)
588✔
308
        emit functionListChanged();
150✔
309
}
588✔
310

311
/****************************************************************************
312
 * DMX Sources
313
 ****************************************************************************/
314

315
void MasterTimer::registerDMXSource(DMXSource *source)
30✔
316
{
317
    Q_ASSERT(source != NULL);
30✔
318

319
    QMutexLocker lock(&m_dmxSourceListMutex);
30✔
320
    if (m_dmxSourceList.contains(source) == false)
30✔
321
        m_dmxSourceList.append(source);
26✔
322
}
30✔
323

324
void MasterTimer::unregisterDMXSource(DMXSource *source)
28✔
325
{
326
    Q_ASSERT(source != NULL);
28✔
327

328
    QMutexLocker lock(&m_dmxSourceListMutex);
28✔
329
    m_dmxSourceList.removeAll(source);
28✔
330
}
28✔
331

332
void MasterTimer::timerTickDMXSources(QList<Universe *> universes)
588✔
333
{
334
    /* Lock before accessing the DMX sources list. */
335
    QMutexLocker lock(&m_dmxSourceListMutex);
588✔
336

337
    foreach (DMXSource *source, m_dmxSourceList)
698✔
338
    {
339
        Q_ASSERT(source != NULL);
110✔
340

341
#ifdef DEBUG_MASTERTIMER
342
        qDebug() << "[MasterTimer] ticking DMX source" << i;
343
#endif
344

345
        /* Get DMX data from the source */
346
        source->writeDMX(this, universes);
110✔
347
    }
588✔
348
}
588✔
349

350
/*************************************************************************
351
 * Beats generation
352
 *************************************************************************/
353

354
void MasterTimer::setBeatSourceType(MasterTimer::BeatsSourceType type)
×
355
{
356
    if (type == m_beatSourceType)
×
357
        return;
×
358

359
    // alright, this causes a time drift of maximum 1ms per beat
360
    // but at the moment I am not looking for a better solution
361
    m_beatTimeDuration = 60000 / m_currentBPM;
×
362
    m_beatTimer.restart();
×
363

364
    m_beatSourceType = type;
×
365
}
366

367
MasterTimer::BeatsSourceType MasterTimer::beatSourceType() const
×
368
{
369
    return m_beatSourceType;
×
370
}
371

372
void MasterTimer::requestBpmNumber(int bpm)
×
373
{
374
    if (bpm == m_currentBPM)
×
375
        return;
×
376

377
    m_currentBPM = bpm;
×
378
    m_beatTimeDuration = 60000 / m_currentBPM;
×
379
    m_beatTimer.restart();
×
380

381
    emit bpmNumberChanged(bpm);
×
382
}
383

384
int MasterTimer::bpmNumber() const
2✔
385
{
386
    return m_currentBPM;
2✔
387
}
388

389
int MasterTimer::beatTimeDuration() const
×
390
{
391
    return m_beatTimeDuration;
×
392
}
393

394
int MasterTimer::timeToNextBeat() const
×
395
{
396
    return m_beatTimeDuration - m_beatTimer.elapsed();
×
397
}
398

399
int MasterTimer::nextBeatTimeOffset() const
×
400
{
401
    // get the time offset to the next beat
402
    int toNext = timeToNextBeat();
×
403
    // get the percentage of beat time passed
404
    int beatPercentage = (100 * toNext) / m_beatTimeDuration;
×
405

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

412
    // otherwise we're running early, so we should wait the
413
    // whole remaining time
414
    return -toNext;
×
415
}
416

417
bool MasterTimer::isBeat() const
398✔
418
{
419
    return m_beatRequested;
398✔
420
}
421

422
void MasterTimer::requestBeat()
×
423
{
424
    // forceful request of a beat, processed at
425
    // the next timerTick call
426
    m_beatRequested = true;
×
427
}
×
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

© 2026 Coveralls, Inc