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

mcallegari / qlcplus / 27918870711

21 Jun 2026 09:28PM UTC coverage: 35.287% (-0.009%) from 35.296%
27918870711

push

github

mcallegari
engine: fix RGBMatrix dimmer mode not applied correctly to generic dimmers

0 of 3 new or added lines in 1 file covered. (0.0%)

422 existing lines in 3 files now uncovered.

18536 of 52529 relevant lines covered (35.29%)

41043.21 hits per line

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

72.97
/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)
244✔
58
    : QObject(doc)
59
    , d_ptr(new MasterTimerPrivate(this))
244✔
60
    , m_stopAllFunctions(false)
244✔
61
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
62
    , m_dmxSourceListMutex(QMutex::Recursive)
63
#endif
64
    , m_beatSourceType(None)
244✔
65
    , m_currentBPM(120)
244✔
66
    , m_beatTimeDuration(500)
244✔
67
    , m_beatRequested(false)
244✔
68
    , m_lastBeatOffset(0)
732✔
69
{
70
    Q_ASSERT(doc != NULL);
244✔
71
    Q_ASSERT(d_ptr != NULL);
244✔
72

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

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

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

86
    delete d_ptr;
244✔
87
    d_ptr = NULL;
244✔
88
}
455✔
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

102
    /* After the timer thread has fully stopped, clear any remaining function
103
     * pointers to prevent dangling references if a function slipped through
104
     * the stopAllFunctions/startQueue race window. */
105
    QMutexLocker locker(&m_functionListMutex);
5✔
106
    m_functionList.clear();
5✔
107
    m_startQueue.clear();
5✔
108
}
5✔
109

110
void MasterTimer::timerTick()
594✔
111
{
112
    Doc *doc = qobject_cast<Doc*> (parent());
594✔
113
    Q_ASSERT(doc != NULL);
594✔
114

115
#ifdef DEBUG_MASTERTIMER
116
    qDebug() << "[MasterTimer] *********** tick:" << ticksCount++ << "**********";
117
#endif
118

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

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

136
                // inform the listening classes that a beat is happening
UNCOV
137
                emit beat();
×
138
            }
139
        }
UNCOV
140
        break;
×
UNCOV
141
        case External:
×
UNCOV
142
        break;
×
143

144
        case None:
594✔
145
        default:
146
            m_beatRequested = false;
594✔
147
        break;
594✔
148
    }
149

150
    QList<Universe *> universes = doc->inputOutputMap()->claimUniverses();
1,188✔
151

152
    timerTickFunctions(universes);
594✔
153
    timerTickDMXSources(universes);
594✔
154

155
    doc->inputOutputMap()->releaseUniverses();
594✔
156

157
    m_beatRequested = false;
594✔
158

159
    //qDebug() << ">>>>>>>> MASTERTIMER TICK";
160
    emit tickReady();
594✔
161
}
594✔
162

163
uint MasterTimer::frequency()
15✔
164
{
165
    return s_frequency;
15✔
166
}
167

168
uint MasterTimer::tick()
352,179✔
169
{
170
    return s_tick;
352,179✔
171
}
172

173
/*****************************************************************************
174
 * Functions
175
 *****************************************************************************/
176

177
void MasterTimer::startFunction(Function* function)
157✔
178
{
179
    if (function == NULL)
157✔
180
        return;
1✔
181

182
    QMutexLocker locker(&m_functionListMutex);
156✔
183
    if (m_startQueue.contains(function) == false)
156✔
184
        m_startQueue.append(function);
156✔
185
}
156✔
186

187
void MasterTimer::stopAllFunctions()
8✔
188
{
189
    m_stopAllFunctions = true;
8✔
190

191
    /* Wait until all functions have been stopped */
192
    while (runningFunctions() > 0)
21✔
193
    {
194
#if defined(WIN32) || defined(Q_OS_WIN)
195
        Sleep(10);
196
#else
197
        usleep(10000);
13✔
198
#endif
199
    }
200

201
    m_stopAllFunctions = false;
8✔
202
}
8✔
203

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

UNCOV
211
        QList<Universe *> universes = doc->inputOutputMap()->claimUniverses();
×
212
        foreach (Universe *universe, universes)
×
213
            universe->setFaderFadeOut(timeout);
×
214

UNCOV
215
        doc->inputOutputMap()->releaseUniverses();
×
UNCOV
216
    }
×
217

218
    // At last, stop all functions
UNCOV
219
    stopAllFunctions();
×
UNCOV
220
}
×
221

222
int MasterTimer::runningFunctions() const
80✔
223
{
224
    return m_functionList.size();
80✔
225
}
226

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

233
    bool functionListHasChanged = false;
594✔
234
    bool stoppedAFunction = true;
594✔
235
    bool firstIteration = true;
594✔
236

237
    while (stoppedAFunction)
1,301✔
238
    {
239
        stoppedAFunction = false;
707✔
240
        removeList.clear();
707✔
241

242
        for (int i = 0; i < m_functionList.size(); i++)
1,346✔
243
        {
244
            Function* function = m_functionList.at(i);
639✔
245

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

266
                    emit functionStopped(function->id());
125✔
267
                }
268
            }
269
        }
270

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

283
        firstIteration = false;
707✔
284
    }
707✔
285

286
    {
287
        QMutexLocker locker(&m_functionListMutex);
594✔
288
        while (m_startQueue.size() > 0)
740✔
289
        {
290
            QList<Function*> startQueue(m_startQueue);
146✔
291
            m_startQueue.clear();
146✔
292
            locker.unlock();
146✔
293

294
            foreach (Function* f, startQueue)
300✔
295
            {
296
                if (m_functionList.contains(f))
154✔
297
                {
298
                    f->postRun(this, universes);
1✔
299
                }
300
                else
301
                {
302
                    m_functionList.append(f);
153✔
303
                    functionListHasChanged = true;
153✔
304
                }
305
                f->preRun(this);
154✔
306
                f->write(this, universes);
154✔
307
                emit functionStarted(f->id());
154✔
308
            }
146✔
309

310
            locker.relock();
146✔
311
        }
146✔
312
    }
594✔
313

314
    if (functionListHasChanged)
594✔
315
        emit functionListChanged();
155✔
316
}
594✔
317

318
/****************************************************************************
319
 * DMX Sources
320
 ****************************************************************************/
321

322
void MasterTimer::registerDMXSource(DMXSource *source)
30✔
323
{
324
    Q_ASSERT(source != NULL);
30✔
325

326
    QMutexLocker lock(&m_dmxSourceListMutex);
30✔
327
    if (m_dmxSourceList.contains(source) == false)
30✔
328
        m_dmxSourceList.append(source);
26✔
329
}
30✔
330

331
void MasterTimer::unregisterDMXSource(DMXSource *source)
30✔
332
{
333
    Q_ASSERT(source != NULL);
30✔
334

335
    QMutexLocker lock(&m_dmxSourceListMutex);
30✔
336
    m_dmxSourceList.removeAll(source);
30✔
337
}
30✔
338

339
void MasterTimer::timerTickDMXSources(QList<Universe *> universes)
594✔
340
{
341
    /* Lock before accessing the DMX sources list. */
342
    QMutexLocker lock(&m_dmxSourceListMutex);
594✔
343

344
    foreach (DMXSource *source, m_dmxSourceList)
704✔
345
    {
346
        Q_ASSERT(source != NULL);
110✔
347

348
#ifdef DEBUG_MASTERTIMER
349
        qDebug() << "[MasterTimer] ticking DMX source" << i;
350
#endif
351

352
        /* Get DMX data from the source */
353
        source->writeDMX(this, universes);
110✔
354
    }
594✔
355
}
594✔
356

357
/*************************************************************************
358
 * Beats generation
359
 *************************************************************************/
360

361
void MasterTimer::setBeatSourceType(MasterTimer::BeatsSourceType type)
×
362
{
UNCOV
363
    if (type == m_beatSourceType)
×
364
        return;
×
365

366
    // alright, this causes a time drift of maximum 1ms per beat
367
    // but at the moment I am not looking for a better solution
UNCOV
368
    m_beatTimeDuration = 60000 / m_currentBPM;
×
369
    m_beatTimer.restart();
×
370

UNCOV
371
    m_beatSourceType = type;
×
372
}
373

374
MasterTimer::BeatsSourceType MasterTimer::beatSourceType() const
×
375
{
UNCOV
376
    return m_beatSourceType;
×
377
}
378

379
void MasterTimer::requestBpmNumber(int bpm)
×
380
{
381
    if (bpm == m_currentBPM)
×
UNCOV
382
        return;
×
383

UNCOV
384
    m_currentBPM = bpm;
×
UNCOV
385
    m_beatTimeDuration = 60000 / m_currentBPM;
×
UNCOV
386
    m_beatTimer.restart();
×
387

UNCOV
388
    emit bpmNumberChanged(bpm);
×
389
}
390

391
int MasterTimer::bpmNumber() const
2✔
392
{
393
    return m_currentBPM;
2✔
394
}
395

396
int MasterTimer::beatTimeDuration() const
×
397
{
UNCOV
398
    return m_beatTimeDuration;
×
399
}
400

UNCOV
401
int MasterTimer::timeToNextBeat() const
×
402
{
UNCOV
403
    return m_beatTimeDuration - m_beatTimer.elapsed();
×
404
}
405

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

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

419
    // otherwise we're running early, so we should wait the
420
    // whole remaining time
UNCOV
421
    return -toNext;
×
422
}
423

424
bool MasterTimer::isBeat() const
405✔
425
{
426
    return m_beatRequested;
405✔
427
}
428

UNCOV
429
void MasterTimer::requestBeat()
×
430
{
431
    // forceful request of a beat, processed at
432
    // the next timerTick call
UNCOV
433
    m_beatRequested = true;
×
UNCOV
434
}
×
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