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

mcallegari / qlcplus / 6683238402

29 Oct 2023 12:10PM UTC coverage: 28.07%. Remained the same
6683238402

push

github

mcallegari
engine: fix build

15385 of 54809 relevant lines covered (28.07%)

20267.63 hits per line

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

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

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

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

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

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

91
    delete m_beatTimer;
206✔
92
}
380✔
93

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

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

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

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

116
    switch (m_beatSourceType)
558✔
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:
558✔
142
        default:
143
            m_beatRequested = false;
558✔
144
        break;
558✔
145
    }
146

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

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

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

154
    m_beatRequested = false;
558✔
155

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

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

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

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

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

179
    QMutexLocker locker(&m_functionListMutex);
292✔
180
    if (m_startQueue.contains(function) == false)
146✔
181
        m_startQueue.append(function);
146✔
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
        {
211
            foreach (QSharedPointer<GenericFader> fader, universe->faders())
×
212
            {
213
                if (!fader.isNull() && fader->parentFunctionID() != Function::invalidId())
×
214
                    fader->setFadeOut(true, uint(timeout));
×
215
            }
216
        }
217
        doc->inputOutputMap()->releaseUniverses();
×
218
    }
219

220
    // At last, stop all functions
221
    stopAllFunctions();
×
222
}
×
223

224
int MasterTimer::runningFunctions() const
79✔
225
{
226
    return m_functionList.size();
79✔
227
}
228

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

235
    bool functionListHasChanged = false;
558✔
236
    bool stoppedAFunction = true;
558✔
237
    bool firstIteration = true;
558✔
238

239
    while (stoppedAFunction)
1,226✔
240
    {
241
        stoppedAFunction = false;
668✔
242
        removeList.clear();
668✔
243

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

248
            if (function != NULL)
574✔
249
            {
250
                /* Run the function unless it's supposed to be stopped */
251
                if (function->stopped() == false && m_stopAllFunctions == false)
574✔
252
                {
253
                    if (firstIteration)
453✔
254
                        function->write(this, universes);
426✔
255
                }
256
                else
257
                {
258
                    // Clear function's parentList
259
                    if (m_stopAllFunctions)
121✔
260
                        function->stop(FunctionParent::master());
14✔
261
                    /* Function should be stopped instead */
262
                    function->postRun(this, universes);
121✔
263
                    //qDebug() << "[MasterTimer] Add function (ID: " << function->id() << ") to remove list ";
264
                    removeList << i; // Don't remove the item from the list just yet.
121✔
265
                    functionListHasChanged = true;
121✔
266
                    stoppedAFunction = true;
121✔
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);
668✔
279
        it.toBack();
668✔
280
        while (it.hasPrevious() == true)
789✔
281
            m_functionList.removeAt(it.previous());
121✔
282

283
        firstIteration = false;
668✔
284
    }
285

286
    {
287
        QMutexLocker locker(&m_functionListMutex);
1,116✔
288
        while (m_startQueue.size() > 0)
694✔
289
        {
290
            QList<Function*> startQueue(m_startQueue);
272✔
291
            m_startQueue.clear();
136✔
292
            locker.unlock();
136✔
293

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

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

314
    if (functionListHasChanged)
558✔
315
        emit functionListChanged();
147✔
316
}
558✔
317

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

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

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

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

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

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

344
    foreach (DMXSource *source, m_dmxSourceList)
778✔
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
    }
355
}
558✔
356

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

361
void MasterTimer::setBeatSourceType(MasterTimer::BeatsSourceType type)
×
362
{
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
368
    m_beatTimeDuration = 60000 / m_currentBPM;
×
369
    m_beatTimer->restart();
×
370

371
    m_beatSourceType = type;
×
372
}
373

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

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

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

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
{
398
    return m_beatTimeDuration;
×
399
}
400

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

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
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
416
    if (beatPercentage <= LATE_TO_BEAT_THRESHOLD)
×
417
        return toNext;
×
418

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

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

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