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

mcallegari / qlcplus / 22149820832

18 Feb 2026 05:11PM UTC coverage: 34.067% (+0.08%) from 33.99%
22149820832

push

github

mcallegari
engine: improve universe composition efficiency

- Universe::tick() now releases the semaphore only if no pending token exists.
- Elapsed-time aware fading: improves fade timing accuracy under variable CPU load and scheduler jitter; fades progress according to real time
instead of assuming perfect 20 ms cadence
- Reduced lock hold in Universe fader processing to avoid holding the universe fader list lock during potentially expensive per-fader channel writes, reducing contention
and improving scalability with many faders/universes
- Lower allocation/copy pressure in output emission: avoids per-tick temporary copy for plugin output write path while preserving safe ownership semantics for queued
signal consumers
- GenericFader channel lookup/update restructuring: reduces lock churn and repeated channel metadata setup overhead in high-channel scenes
- Locking correctness improvements in GenericFader: fixes data-race risk and ensures thread-safe mutation behavior
- Additional performance validation is done through engine/test/universeperf

122 of 202 new or added lines in 9 files covered. (60.4%)

2 existing lines in 2 files now uncovered.

17708 of 51980 relevant lines covered (34.07%)

41221.91 hits per line

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

41.16
/engine/src/rgbmatrix.cpp
1
/*
2
  Q Light Controller Plus
3
  rgbmatrix.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 <QXmlStreamReader>
22
#include <QXmlStreamWriter>
23
#include <QCoreApplication>
24
#include <QElapsedTimer>
25
#include <QDebug>
26
#include <cmath>
27
#include <QDir>
28

29
#include "rgbscriptscache.h"
30
#include "qlcfixturehead.h"
31
#include "fixturegroup.h"
32
#include "genericfader.h"
33
#include "fadechannel.h"
34
#include "rgbmatrix.h"
35
#include "rgbimage.h"
36
#include "doc.h"
37

38
#define KXMLQLCRGBMatrixStartColor      QStringLiteral("MonoColor")
39
#define KXMLQLCRGBMatrixEndColor        QStringLiteral("EndColor")
40
#define KXMLQLCRGBMatrixColor           QStringLiteral("Color")
41
#define KXMLQLCRGBMatrixColorIndex      QStringLiteral("Index")
42

43
#define KXMLQLCRGBMatrixFixtureGroup    QStringLiteral("FixtureGroup")
44
#define KXMLQLCRGBMatrixDimmerControl   QStringLiteral("DimmerControl")
45

46
#define KXMLQLCRGBMatrixProperty        QStringLiteral("Property")
47
#define KXMLQLCRGBMatrixPropertyName    QStringLiteral("Name")
48
#define KXMLQLCRGBMatrixPropertyValue   QStringLiteral("Value")
49

50
#define KXMLQLCRGBMatrixControlMode         QStringLiteral("ControlMode")
51
#define KXMLQLCRGBMatrixControlModeRgb      QStringLiteral("RGB")
52
#define KXMLQLCRGBMatrixControlModeAmber    QStringLiteral("Amber")
53
#define KXMLQLCRGBMatrixControlModeWhite    QStringLiteral("White")
54
#define KXMLQLCRGBMatrixControlModeUV       QStringLiteral("UV")
55
#define KXMLQLCRGBMatrixControlModeDimmer   QStringLiteral("Dimmer")
56
#define KXMLQLCRGBMatrixControlModeShutter  QStringLiteral("Shutter")
57

58
/****************************************************************************
59
 * Initialization
60
 ****************************************************************************/
61

62
RGBMatrix::RGBMatrix(Doc *doc)
9✔
63
    : Function(doc, Function::RGBMatrixType)
64
    , m_dimmerControl(false)
9✔
65
    , m_fixtureGroupID(FixtureGroup::invalidId())
18✔
66
    , m_group(NULL)
9✔
67
    , m_requestEngineCreation(true)
9✔
68
    , m_runAlgorithm(NULL)
9✔
69
    , m_algorithm(NULL)
9✔
70
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
71
    , m_algorithmMutex(QMutex::Recursive)
72
#endif
73
    , m_stepHandler(new RGBMatrixStep())
9✔
74
    , m_stepsCount(0)
9✔
75
    , m_stepBeatDuration(0)
9✔
76
    , m_controlMode(RGBMatrix::ControlModeRgb)
18✔
77
{
78
    setName(tr("New RGB Matrix"));
9✔
79
    setDuration(500);
9✔
80

81
    m_rgbColors.fill(QColor(), RGBAlgorithmColorDisplayCount);
9✔
82
    setColor(0, Qt::red);
9✔
83

84
    setAlgorithm(RGBAlgorithm::algorithm(doc, "Stripes"));
9✔
85
}
9✔
86

87
RGBMatrix::~RGBMatrix()
11✔
88
{
89
    //if (m_runAlgorithm != NULL)
90
    //    delete m_runAlgorithm;
91
    delete m_algorithm;
9✔
92
    delete m_stepHandler;
9✔
93
}
11✔
94

95
QIcon RGBMatrix::getIcon() const
×
96
{
97
    return QIcon(":/rgbmatrix.png");
×
98
}
99

100
void RGBMatrix::setTotalDuration(quint32 msec)
1✔
101
{
102
    QMutexLocker algorithmLocker(&m_algorithmMutex);
1✔
103

104
    if (m_algorithm == NULL)
1✔
105
        return;
×
106

107
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
1✔
108
    if (grp == NULL)
1✔
109
        return;
×
110

111
    int steps = m_algorithm->rgbMapStepCount(grp->size());
1✔
112
    setDuration(msec / steps);
1✔
113
}
1✔
114

115
quint32 RGBMatrix::totalDuration()
3✔
116
{
117
    QMutexLocker algorithmLocker(&m_algorithmMutex);
3✔
118

119
    if (m_algorithm == NULL)
3✔
120
        return 0;
×
121

122
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
3✔
123
    if (grp == NULL)
3✔
124
        return 0;
1✔
125

126
    //qDebug () << "Algorithm steps:" << m_algorithm->rgbMapStepCount(grp->size());
127
    return m_algorithm->rgbMapStepCount(grp->size()) * duration();
2✔
128
}
3✔
129

130
void RGBMatrix::setDimmerControl(bool dimmerControl)
1✔
131
{
132
    m_dimmerControl = dimmerControl;
1✔
133
}
1✔
134

135
bool RGBMatrix::dimmerControl() const
2✔
136
{
137
    return m_dimmerControl;
2✔
138
}
139

140
/****************************************************************************
141
 * Copying
142
 ****************************************************************************/
143

144
Function* RGBMatrix::createCopy(Doc* doc, bool addToDoc)
1✔
145
{
146
    Q_ASSERT(doc != NULL);
1✔
147

148
    Function *copy = new RGBMatrix(doc);
1✔
149
    if (copy->copyFrom(this) == false)
1✔
150
    {
151
        delete copy;
×
152
        copy = NULL;
×
153
    }
154
    if (addToDoc == true && doc->addFunction(copy) == false)
1✔
155
    {
156
        delete copy;
×
157
        copy = NULL;
×
158
    }
159

160
    return copy;
1✔
161
}
162

163
bool RGBMatrix::copyFrom(const Function* function)
1✔
164
{
165
    const RGBMatrix *mtx = qobject_cast<const RGBMatrix*> (function);
1✔
166
    if (mtx == NULL)
1✔
167
        return false;
×
168

169
    setDimmerControl(mtx->dimmerControl());
1✔
170
    setFixtureGroup(mtx->fixtureGroup());
1✔
171

172
    m_rgbColors.clear();
1✔
173
    foreach (QColor col, mtx->getColors())
7✔
174
        m_rgbColors.append(col);
6✔
175

176
    if (mtx->algorithm() != NULL)
1✔
177
        setAlgorithm(mtx->algorithm()->clone());
1✔
178
    else
179
        setAlgorithm(NULL);
×
180

181
    setControlMode(mtx->controlMode());
1✔
182

183
    return Function::copyFrom(function);
1✔
184
}
185

186
/****************************************************************************
187
 * Fixtures
188
 ****************************************************************************/
189

190
quint32 RGBMatrix::fixtureGroup() const
35✔
191
{
192
    return m_fixtureGroupID;
35✔
193
}
194

195
void RGBMatrix::setFixtureGroup(quint32 id)
8✔
196
{
197
    m_fixtureGroupID = id;
8✔
198
    {
199
        QMutexLocker algoLocker(&m_algorithmMutex);
8✔
200
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
8✔
201
    }
8✔
202
    m_stepsCount = algorithmStepsCount();
8✔
203
}
8✔
204

205
QList<quint32> RGBMatrix::components() const
2✔
206
{
207
    if (m_group != NULL)
2✔
208
        return m_group->fixtureList();
1✔
209

210
    return QList<quint32>();
1✔
211
}
212

213
/****************************************************************************
214
 * Algorithm
215
 ****************************************************************************/
216

217
void RGBMatrix::setAlgorithm(RGBAlgorithm *algo)
13✔
218
{
219
    {
220
        QMutexLocker algorithmLocker(&m_algorithmMutex);
13✔
221
        delete m_algorithm;
13✔
222
        m_algorithm = algo;
13✔
223

224
        m_requestEngineCreation = true;
13✔
225

226
        /** If there's been a change of Script algorithm "on the fly",
227
         *  then re-apply the properties currently set in this RGBMatrix */
228
        if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
13✔
229
        {
230
            RGBScript *script = static_cast<RGBScript*> (m_algorithm);
13✔
231
            QMapIterator<QString, QString> it(m_properties);
13✔
232
            while (it.hasNext())
13✔
233
            {
234
                it.next();
×
235
                if (script->setProperty(it.key(), it.value()) == false)
×
236
                {
237
                    /** If the new algorithm doesn't expose a property,
238
                     *  then remove it from the cached list, otherwise
239
                     *  it would be carried around forever (and saved on XML) */
240
                    m_properties.take(it.key());
×
241
                }
242
            }
243

244
            QVector<uint> colors = script->rgbMapGetColors();
13✔
245
            for (int i = 0; i < colors.count(); i++)
13✔
246
                m_rgbColors.replace(i, QColor::fromRgb(colors.at(i)));
×
247
        }
13✔
248
    }
13✔
249
    m_stepsCount = algorithmStepsCount();
13✔
250

251
    emit changed(id());
13✔
252
}
13✔
253

254
RGBAlgorithm *RGBMatrix::algorithm() const
18✔
255
{
256
    return m_algorithm;
18✔
257
}
258

259
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
260
QMutex& RGBMatrix::algorithmMutex()
261
{
262
    return m_algorithmMutex;
263
}
264
#else
265
QRecursiveMutex& RGBMatrix::algorithmMutex()
×
266
{
267
    return m_algorithmMutex;
×
268
}
269
#endif
270

271

272
int RGBMatrix::stepsCount() const
2✔
273
{
274
    return m_stepsCount;
2✔
275
}
276

277
int RGBMatrix::algorithmStepsCount()
22✔
278
{
279
    QMutexLocker algorithmLocker(&m_algorithmMutex);
22✔
280

281
    if (m_algorithm == NULL)
22✔
282
        return 0;
×
283

284
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
22✔
285
    if (grp != NULL)
22✔
286
        return m_algorithm->rgbMapStepCount(grp->size());
6✔
287

288
    return 0;
16✔
289
}
22✔
290

291
void RGBMatrix::previewMap(int step, RGBMatrixStep *handler)
7✔
292
{
293
    QMutexLocker algorithmLocker(&m_algorithmMutex);
7✔
294
    if (m_algorithm == NULL || handler == NULL)
7✔
295
        return;
×
296

297
    if (m_group == NULL)
7✔
298
        m_group = doc()->fixtureGroup(fixtureGroup());
1✔
299

300
    if (m_group != NULL)
7✔
301
    {
302
        setMapColors(m_algorithm);
6✔
303
        m_algorithm->rgbMap(m_group->size(), handler->stepColor().rgb(), step, handler->m_map);
6✔
304
    }
305
}
7✔
306

307
/****************************************************************************
308
 * Color
309
 ****************************************************************************/
310

311
void RGBMatrix::setColor(int i, QColor c)
25✔
312
{
313
    if (i < 0)
25✔
314
        return;
×
315

316
    if (i >= m_rgbColors.count())
25✔
317
        m_rgbColors.resize(i + 1);
×
318

319
    m_rgbColors.replace(i, c);
25✔
320
    {
321
        QMutexLocker algorithmLocker(&m_algorithmMutex);
25✔
322
        if (m_algorithm != NULL)
25✔
323
        {
324
            m_algorithm->setColors(m_rgbColors);
16✔
325
            updateColorDelta();
16✔
326
        }
327
    }
25✔
328
    setMapColors(m_algorithm);
25✔
329
    emit changed(id());
25✔
330
}
331

332
QColor RGBMatrix::getColor(int i) const
10✔
333
{
334
    if (i < 0 || i >= m_rgbColors.count())
10✔
335
        return QColor();
×
336

337
    return m_rgbColors.at(i);
10✔
338
}
339

340
QVector<QColor> RGBMatrix::getColors() const
1✔
341
{
342
    return m_rgbColors;
1✔
343
}
344

345
void RGBMatrix::updateColorDelta()
16✔
346
{
347
    if (m_rgbColors.count() > 1)
16✔
348
        m_stepHandler->calculateColorDelta(m_rgbColors[0], m_rgbColors[1], m_algorithm);
16✔
349
}
16✔
350

351
void RGBMatrix::setMapColors(RGBAlgorithm *algorithm)
31✔
352
{
353
    QMutexLocker algorithmLocker(&m_algorithmMutex);
31✔
354
    if (algorithm == NULL)
31✔
355
        return;
9✔
356

357
    if (algorithm->apiVersion() < 3)
22✔
358
        return;
22✔
359

360
    if (m_group == NULL)
×
361
        m_group = doc()->fixtureGroup(fixtureGroup());
×
362

363
    QVector<unsigned int> rawColors;
×
364
    const int acceptColors = algorithm->acceptColors();
×
365
    rawColors.reserve(acceptColors);
×
366
    for (int i = 0; i < acceptColors; i++)
×
367
    {
368
        if (m_rgbColors.count() > i)
×
369
        {
370
            QColor col = m_rgbColors.at(i);
×
371
            rawColors.append(col.isValid() ? col.rgb() : 0);
×
372
        }
373
        else
374
        {
375
            rawColors.append(0);
×
376
        }
377
    }
378

379
    algorithm->rgbMapSetColors(rawColors);
×
380
}
31✔
381

382
/************************************************************************
383
 * Properties
384
 ************************************************************************/
385

386
void RGBMatrix::setProperty(QString propName, QString value)
1✔
387
{
388
    QMutexLocker algoLocker(&m_algorithmMutex);
1✔
389
    m_properties[propName] = value;
1✔
390
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
1✔
391
    {
392
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
1✔
393
        script->setProperty(propName, value);
1✔
394

395
        QVector<uint> colors = script->rgbMapGetColors();
1✔
396
        for (int i = 0; i < colors.count(); i++)
1✔
397
            setColor(i, QColor::fromRgb(colors.at(i)));
×
398
    }
1✔
399
    m_stepsCount = algorithmStepsCount();
1✔
400
}
1✔
401

402
QString RGBMatrix::property(QString propName)
3✔
403
{
404
    QMutexLocker algoLocker(&m_algorithmMutex);
3✔
405

406
    /** If the property is cached, then return it right away */
407
    QMap<QString, QString>::iterator it = m_properties.find(propName);
3✔
408
    if (it != m_properties.end())
3✔
409
        return it.value();
1✔
410

411
    /** Otherwise, let's retrieve it from the Script */
412
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
2✔
413
    {
414
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
2✔
415
        return script->property(propName);
2✔
416
    }
417

418
    return QString();
×
419
}
3✔
420

421
/****************************************************************************
422
 * Load & Save
423
 ****************************************************************************/
424

425
bool RGBMatrix::loadXML(QXmlStreamReader &root)
3✔
426
{
427
    if (root.name() != KXMLQLCFunction)
3✔
428
    {
429
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
430
        return false;
1✔
431
    }
432

433
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::RGBMatrixType))
4✔
434
    {
435
        qWarning() << Q_FUNC_INFO << "Function is not an RGB matrix";
1✔
436
        return false;
1✔
437
    }
438

439
    /* Load matrix contents */
440
    while (root.readNextStartElement())
12✔
441
    {
442
        if (root.name() == KXMLQLCFunctionSpeed)
11✔
443
        {
444
            loadXMLSpeed(root);
1✔
445
        }
446
        else if (root.name() == KXMLQLCFunctionTempoType)
10✔
447
        {
448
            loadXMLTempoType(root);
×
449
        }
450
        else if (root.name() == KXMLQLCRGBAlgorithm)
10✔
451
        {
452
            setAlgorithm(RGBAlgorithm::loader(doc(), root));
1✔
453
        }
454
        else if (root.name() == KXMLQLCRGBMatrixFixtureGroup)
9✔
455
        {
456
            setFixtureGroup(root.readElementText().toUInt());
1✔
457
        }
458
        else if (root.name() == KXMLQLCFunctionDirection)
8✔
459
        {
460
            loadXMLDirection(root);
1✔
461
        }
462
        else if (root.name() == KXMLQLCFunctionRunOrder)
7✔
463
        {
464
            loadXMLRunOrder(root);
1✔
465
        }
466
        // Legacy support
467
        else if (root.name() == KXMLQLCRGBMatrixStartColor)
6✔
468
        {
469
            setColor(0, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
470
        }
471
        else if (root.name() == KXMLQLCRGBMatrixEndColor)
6✔
472
        {
473
            setColor(1, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
474
        }
475
        else if (root.name() == KXMLQLCRGBMatrixColor)
6✔
476
        {
477
            int colorIdx = root.attributes().value(KXMLQLCRGBMatrixColorIndex).toInt();
10✔
478
            setColor(colorIdx, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
5✔
479
        }
480
        else if (root.name() == KXMLQLCRGBMatrixControlMode)
1✔
481
        {
482
            setControlMode(stringToControlMode(root.readElementText()));
1✔
483
        }
484
        else if (root.name() == KXMLQLCRGBMatrixProperty)
×
485
        {
486
            QString name = root.attributes().value(KXMLQLCRGBMatrixPropertyName).toString();
×
487
            QString value = root.attributes().value(KXMLQLCRGBMatrixPropertyValue).toString();
×
488
            setProperty(name, value);
×
489
            root.skipCurrentElement();
×
490
        }
×
491
        else if (root.name() == KXMLQLCRGBMatrixDimmerControl)
×
492
        {
493
            setDimmerControl(root.readElementText().toInt());
×
494
        }
495
        else
496
        {
497
            qWarning() << Q_FUNC_INFO << "Unknown RGB matrix tag:" << root.name();
×
498
            root.skipCurrentElement();
×
499
        }
500
    }
501

502
    return true;
1✔
503
}
504

505
bool RGBMatrix::saveXML(QXmlStreamWriter *doc) const
1✔
506
{
507
    Q_ASSERT(doc != NULL);
1✔
508

509
    /* Function tag */
510
    doc->writeStartElement(KXMLQLCFunction);
2✔
511

512
    /* Common attributes */
513
    saveXMLCommon(doc);
1✔
514

515
    /* Tempo type */
516
    saveXMLTempoType(doc);
1✔
517

518
    /* Speeds */
519
    saveXMLSpeed(doc);
1✔
520

521
    /* Direction */
522
    saveXMLDirection(doc);
1✔
523

524
    /* Run order */
525
    saveXMLRunOrder(doc);
1✔
526

527
    /* Algorithm */
528
    if (m_algorithm != NULL)
1✔
529
        m_algorithm->saveXML(doc);
1✔
530

531
    /* LEGACY - Dimmer Control */
532
    if (dimmerControl())
1✔
533
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
534

535
    /* Colors */
536
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
537
    {
538
        if (m_rgbColors.at(i).isValid() == false)
5✔
539
            continue;
×
540

541
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
10✔
542
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
10✔
543
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
544
        doc->writeEndElement();
5✔
545
    }
546

547
    /* Control Mode */
548
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
2✔
549

550
    /* Fixture Group */
551
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
2✔
552

553
    /* Properties */
554
    QMapIterator<QString, QString> it(m_properties);
1✔
555
    while (it.hasNext())
1✔
556
    {
557
        it.next();
×
558
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
559
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
560
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
561
        doc->writeEndElement();
×
562
    }
563

564
    /* End the <Function> tag */
565
    doc->writeEndElement();
1✔
566

567
    return true;
1✔
568
}
1✔
569

570
/****************************************************************************
571
 * Running
572
 ****************************************************************************/
573

574
void RGBMatrix::tap()
×
575
{
576
    if (stopped() == false)
×
577
    {
578
        FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
×
579
        // Filter out taps that are too close to each other
580
        if (grp != NULL && uint(m_roundTime.elapsed()) >= (duration() / 4))
×
581
        {
582
            roundCheck();
×
583
            resetElapsed();
×
584
        }
585
    }
586
}
×
587

588
void RGBMatrix::checkEngineCreation()
×
589
{
590
    m_runAlgorithm = m_algorithm;
×
591
    m_requestEngineCreation = false;
×
592
}
×
593

594
void RGBMatrix::preRun(MasterTimer *timer)
×
595
{
596
    {
597
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
598

599
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
600
        if (m_group == NULL)
×
601
        {
602
            // No fixture group to control
603
            stop(FunctionParent::master());
×
604
            return;
×
605
        }
606

607
        if (m_algorithm != NULL)
×
608
        {
609
            checkEngineCreation();
×
610

611
            // Copy direction from parent class direction
612
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_runAlgorithm);
×
613

614
            if (m_runAlgorithm->type() == RGBAlgorithm::Script)
×
615
            {
616
                RGBScript *script = static_cast<RGBScript*> (m_runAlgorithm);
×
617
                QMapIterator<QString, QString> it(m_properties);
×
618
                while (it.hasNext())
×
619
                {
620
                    it.next();
×
621
                    script->setProperty(it.key(), it.value());
×
622
                }
623
            }
×
624
            else if (m_runAlgorithm->type() == RGBAlgorithm::Image)
×
625
            {
626
                RGBImage *image = static_cast<RGBImage*> (m_runAlgorithm);
×
627
                if (image->animatedSource())
×
628
                    image->rewindAnimation();
×
629
            }
630
        }
631
    }
×
632

633
    m_roundTime.restart();
×
634

635
    Function::preRun(timer);
×
636
}
637

638
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
639
{
640
    Q_UNUSED(timer);
641

642
    {
643
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
644
        if (m_group == NULL)
×
645
        {
646
            // No fixture group to control
647
            stop(FunctionParent::master());
×
648
            return;
×
649
        }
650

651
        // No time to do anything.
652
        if (duration() == 0)
×
653
            return;
×
654

655
        if (m_algorithm != NULL && m_requestEngineCreation)
×
656
            checkEngineCreation();
×
657

658
        // Invalid/nonexistent script
659
        if (m_runAlgorithm == NULL || m_runAlgorithm->apiVersion() == 0)
×
660
            return;
×
661

662
        if (isPaused() == false)
×
663
        {
664
            // Get a new map every time elapsed is reset to zero
665
            if (elapsed() < MasterTimer::tick())
×
666
            {
667
                if (tempoType() == Beats)
×
668
                    m_stepBeatDuration = beatsToTime(duration(), timer->beatTimeDuration());
×
669

670
                //qDebug() << "RGBMatrix step" << m_stepHandler->currentStepIndex() << ", color:" << QString::number(m_stepHandler->stepColor().rgb(), 16);
671
                m_runAlgorithm->rgbMap(m_group->size(), m_stepHandler->stepColor().rgb(),
×
672
                                       m_stepHandler->currentStepIndex(), m_stepHandler->m_map);
×
673
                updateMapChannels(m_stepHandler->m_map, m_group, universes);
×
674
            }
675
        }
676
    }
×
677

678
    if (isPaused() == false)
×
679
    {
680
        // Increment the ms elapsed time
681
        incrementElapsed();
×
682

683
        /* Check if we need to change direction, stop completely or go to next step
684
         * The cases are:
685
         * 1- time tempo type: act normally, on ms elapsed time
686
         * 2- beat tempo type, beat occurred: check if the elapsed beats is a multiple of
687
         *    the step beat duration. If so, proceed to the next step
688
         * 3- beat tempo type, not beat: if the ms elapsed time reached the step beat
689
         *    duration in ms, and the ms time to the next beat is not less than 1/16 of
690
         *    the step beat duration in ms, then proceed to the next step. If the ms time to the
691
         *    next beat is less than 1/16 of the step beat duration in ms, then defer the step
692
         *    change to case #2, to resync the matrix to the next beat
693
         */
694
        if (tempoType() == Time && elapsed() >= duration())
×
695
        {
696
            roundCheck();
×
697
        }
698
        else if (tempoType() == Beats)
×
699
        {
700
            if (timer->isBeat())
×
701
            {
702
                incrementElapsedBeats();
×
703
                qDebug() << "Elapsed beats:" << elapsedBeats() << ", time elapsed:" << elapsed() << ", step time:" << m_stepBeatDuration;
×
704
                if (elapsedBeats() % duration() == 0)
×
705
                {
706
                    roundCheck();
×
707
                    resetElapsed();
×
708
                }
709
            }
710
            else if (elapsed() >= m_stepBeatDuration && (uint)timer->timeToNextBeat() > m_stepBeatDuration / 16)
×
711
            {
712
                qDebug() << "Elapsed exceeded";
×
713
                roundCheck();
×
714
            }
715
        }
716
    }
717
}
718

719
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
720
{
721
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
722

723
    /* If no fade out is needed, dismiss all the requested faders.
724
     * Otherwise, set all the faders to fade out and let Universe dismiss them
725
     * when done */
726
    if (fadeout == 0)
×
727
    {
728
        dismissAllFaders();
×
729
    }
730
    else
731
    {
732
        if (tempoType() == Beats)
×
733
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
734

735
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
736
        {
737
            if (!fader.isNull())
×
738
                fader->setFadeOut(true, fadeout);
×
739
        }
×
740
    }
741

742
    m_fadersMap.clear();
×
743

744
    {
745
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
746
        checkEngineCreation();
×
747
        if (m_runAlgorithm != NULL)
×
748
            m_runAlgorithm->postRun();
×
749
    }
×
750

751
    Function::postRun(timer, universes);
×
752
}
×
753

754
void RGBMatrix::roundCheck()
×
755
{
756
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
757
    if (m_algorithm == NULL)
×
758
        return;
×
759

760
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
761
        stop(FunctionParent::master());
×
762

763
    m_roundTime.restart();
×
764

765
    if (tempoType() == Beats)
×
766
        roundElapsed(m_stepBeatDuration);
×
767
    else
768
        roundElapsed(duration());
×
769
}
×
770

NEW
771
QSharedPointer<GenericFader> RGBMatrix::getFader(Universe *universe)
×
772
{
773
    // get the universe Fader first. If doesn't exist, create it
774
    if (universe == NULL)
×
NEW
775
        return QSharedPointer<GenericFader>();
×
776

777
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
×
778
    if (fader.isNull())
×
779
    {
780
        fader = universe->requestFader();
×
781
        fader->adjustIntensity(getAttributeValue(Intensity));
×
782
        fader->setBlendMode(blendMode());
×
783
        fader->setName(name());
×
784
        fader->setParentFunctionID(id());
×
785
        m_fadersMap[universe->id()] = fader;
×
786
    }
787

NEW
788
    return fader;
×
789
}
×
790

NEW
791
void RGBMatrix::updateFaderValues(FadeChannel &fc, uchar value, uint fadeTime)
×
792
{
NEW
793
    fc.setStart(fc.current());
×
NEW
794
    fc.setTarget(value);
×
NEW
795
    fc.setElapsed(0);
×
NEW
796
    fc.setReady(false);
×
797
    // fade in/out depends on target value
798
    if (value == 0)
×
NEW
799
        fc.setFadeTime(fadeOutSpeed());
×
800
    else
NEW
801
        fc.setFadeTime(fadeTime);
×
802
}
×
803

804
void RGBMatrix::updateMapChannels(const RGBMap& map, const FixtureGroup *grp, QList<Universe *> universes)
×
805
{
806
    uint fadeTime = (overrideFadeInSpeed() == defaultSpeed()) ? fadeInSpeed() : overrideFadeInSpeed();
×
807

808
    // Create/modify fade channels for ALL heads in the group
809
    QMapIterator<QLCPoint, GroupHead> it(grp->headsMap());
×
810
    while (it.hasNext())
×
811
    {
812
        it.next();
×
813
        QLCPoint pt = it.key();
×
814
        GroupHead grpHead = it.value();
×
815
        Fixture *fxi = doc()->fixture(grpHead.fxi);
×
816
        if (fxi == NULL)
×
817
            continue;
×
818

819
        QLCFixtureHead head = fxi->head(grpHead.head);
×
820

821
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
822
            continue;
×
823

824
        uint col = map[pt.y()][pt.x()];
×
825
        QVector<quint32> channelList;
×
826
        QVector<uchar> valueList;
×
827

828
        if (m_controlMode == ControlModeRgb)
×
829
        {
830
            channelList = head.rgbChannels();
×
831

832
            if (channelList.size() == 3)
×
833
            {
834
                valueList.append(qRed(col));
×
835
                valueList.append(qGreen(col));
×
836
                valueList.append(qBlue(col));
×
837
            }
838
            else
839
            {
840
                channelList = head.cmyChannels();
×
841

842
                if (channelList.size() == 3)
×
843
                {
844
                    // CMY color mixing
845
                    QColor cmyCol(col);
×
846
                    valueList.append(cmyCol.cyan());
×
847
                    valueList.append(cmyCol.magenta());
×
848
                    valueList.append(cmyCol.yellow());
×
849
                }
850
            }
851
        }
852
        else if (m_controlMode == ControlModeShutter)
×
853
        {
854
            channelList = head.shutterChannels();
×
855

856
            if (channelList.size())
×
857
            {
858
                // make sure only one channel is in the list
859
                channelList.resize(1);
×
860
                valueList.append(rgbToGrey(col));
×
861
            }
862
        }
863
        else if (m_controlMode == ControlModeDimmer || m_dimmerControl)
×
864
        {
865
            // Collect all dimmers that affect current head:
866
            // They are the master dimmer (affects whole fixture)
867
            // and per-head dimmer.
868
            //
869
            // If there are no RGB or CMY channels, the least important* dimmer channel
870
            // is used to create grayscale image.
871
            //
872
            // The rest of the dimmer channels are set to full if dimmer control is
873
            // enabled and target color is > 0 (see
874
            // https://www.qlcplus.org/forum/viewtopic.php?f=29&t=11090)
875
            //
876
            // Note: If there is only one head, and only one dimmer channel,
877
            // make it a master dimmer in fixture definition.
878
            //
879
            // *least important - per head dimmer if present,
880
            // otherwise per fixture dimmer if present
881

882
            quint32 masterDim = fxi->masterIntensityChannel();
×
883
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
884

885
            if (masterDim != QLCChannel::invalid())
×
886
            {
887
                channelList.append(masterDim);
×
888
                valueList.append(rgbToGrey(col));
×
889
            }
890

891
            if (headDim != QLCChannel::invalid() && headDim != masterDim)
×
892
            {
893
                channelList.append(headDim);
×
894
                valueList.append(rgbToGrey(col) == 0 ? 0 : 255);
×
895
            }
896
        }
×
897
        else
898
        {
899
            if (m_controlMode == ControlModeWhite)
×
900
                channelList.append(head.channelNumber(QLCChannel::White, QLCChannel::MSB));
×
901
            else if (m_controlMode == ControlModeAmber)
×
902
                channelList.append(head.channelNumber(QLCChannel::Amber, QLCChannel::MSB));
×
903
            else if (m_controlMode == ControlModeUV)
×
904
                channelList.append(head.channelNumber(QLCChannel::UV, QLCChannel::MSB));
×
905

906
            valueList.append(rgbToGrey(col));
×
907
        }
908

909
        quint32 absAddress = fxi->universeAddress();
×
910

911
        for (int i = 0; i < channelList.count(); i++)
×
912
        {
913
            if (channelList.at(i) == QLCChannel::invalid())
×
914
                continue;
×
915

916
            quint32 universeIndex = floor((absAddress + channelList.at(i)) / 512);
×
NEW
917
            Universe *universe = universes.at(universeIndex);
×
NEW
918
            QSharedPointer<GenericFader> fader = getFader(universe);
×
NEW
919
            if (fader.isNull())
×
NEW
920
                continue;
×
921

NEW
922
            const quint32 fixtureID = grpHead.fxi;
×
NEW
923
            const quint32 channel = channelList.at(i);
×
NEW
924
            const uchar value = valueList.at(i);
×
NEW
925
            fader->updateChannel(doc(), universe, fixtureID, channel, [this, value, fadeTime](FadeChannel &fc)
×
926
            {
NEW
927
                updateFaderValues(fc, value, fadeTime);
×
NEW
928
            });
×
929
        }
×
930
    }
×
931
}
×
932

933
uchar RGBMatrix::rgbToGrey(uint col)
×
934
{
935
    // the weights are taken from
936
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
937
    return (0.299 * qRed(col) + 0.587 * qGreen(col) + 0.114 * qBlue(col));
×
938
}
939

940
/*********************************************************************
941
 * Attributes
942
 *********************************************************************/
943

944
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
945
{
946
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
947

948
    if (attrIndex == Intensity)
×
949
    {
950
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
951
        {
952
            if (!fader.isNull())
×
953
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
954
        }
×
955
    }
956

957
    return attrIndex;
×
958
}
959

960
/*************************************************************************
961
 * Blend mode
962
 *************************************************************************/
963

964
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
965
{
966
    if (mode == blendMode())
×
967
        return;
×
968

969
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
970
    {
971
        if (!fader.isNull())
×
972
            fader->setBlendMode(mode);
×
973
    }
×
974

975
    Function::setBlendMode(mode);
×
976
    emit changed(id());
×
977
}
978

979
/*************************************************************************
980
 * Control Mode
981
 *************************************************************************/
982

983
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
984
{
985
    return m_controlMode;
2✔
986
}
987

988
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
989
{
990
    m_controlMode = mode;
3✔
991
    emit changed(id());
3✔
992
}
3✔
993

994
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
995
{
996
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
997
        return ControlModeRgb;
1✔
998
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
999
        return ControlModeAmber;
×
1000
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
1001
        return ControlModeWhite;
×
1002
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
1003
        return ControlModeUV;
×
1004
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
1005
        return ControlModeDimmer;
×
1006
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
1007
        return ControlModeShutter;
×
1008

1009
    return ControlModeRgb;
×
1010
}
1011

1012
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
1013
{
1014
    switch(mode)
1✔
1015
    {
1016
        default:
1✔
1017
        case ControlModeRgb:
1018
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
1019
        break;
1020
        case ControlModeAmber:
×
1021
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
1022
        break;
1023
        case ControlModeWhite:
×
1024
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
1025
        break;
1026
        case ControlModeUV:
×
1027
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1028
        break;
1029
        case ControlModeDimmer:
×
1030
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1031
        break;
1032
        case ControlModeShutter:
×
1033
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1034
        break;
1035
    }
1036
}
1037

1038

1039
/*************************************************************************
1040
 *************************************************************************
1041
 *                          RGBMatrixStep class
1042
 *************************************************************************
1043
 *************************************************************************/
1044

1045
RGBMatrixStep::RGBMatrixStep()
10✔
1046
    : m_direction(Function::Forward)
10✔
1047
    , m_currentStepIndex(0)
10✔
1048
    , m_stepColor(QColor())
10✔
1049
    , m_crDelta(0)
10✔
1050
    , m_cgDelta(0)
10✔
1051
    , m_cbDelta(0)
10✔
1052
{
1053

1054
}
10✔
1055

1056
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1057
{
1058
    m_currentStepIndex = index;
×
1059
}
×
1060

1061
int RGBMatrixStep::currentStepIndex() const
1✔
1062
{
1063
    return m_currentStepIndex;
1✔
1064
}
1065

1066
void RGBMatrixStep::calculateColorDelta(QColor startColor, QColor endColor, RGBAlgorithm *algorithm)
16✔
1067
{
1068
    m_crDelta = 0;
16✔
1069
    m_cgDelta = 0;
16✔
1070
    m_cbDelta = 0;
16✔
1071

1072
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1073
    {
1074
        m_crDelta = endColor.red() - startColor.red();
10✔
1075
        m_cgDelta = endColor.green() - startColor.green();
10✔
1076
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1077

1078
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1079
    }
1080
}
16✔
1081

1082
void RGBMatrixStep::setStepColor(QColor color)
×
1083
{
1084
    m_stepColor = color;
×
1085
}
×
1086

1087
QColor RGBMatrixStep::stepColor() const
6✔
1088
{
1089
    return m_stepColor;
6✔
1090
}
1091

1092
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1093
{
1094
    if (stepsCount <= 0)
×
1095
        return;
×
1096

1097
    if (stepsCount == 1)
×
1098
    {
1099
        m_stepColor = startColor;
×
1100
    }
1101
    else
1102
    {
1103
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
1104
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
1105
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1106
    }
1107

1108
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1109
}
1110

1111
void RGBMatrixStep::initializeDirection(Function::Direction direction, QColor startColor, QColor endColor, int stepsCount, RGBAlgorithm *algorithm)
×
1112
{
1113
    m_direction = direction;
×
1114

1115
    if (m_direction == Function::Forward)
×
1116
    {
1117
        setCurrentStepIndex(0);
×
1118
        setStepColor(startColor);
×
1119
    }
1120
    else
1121
    {
1122
        setCurrentStepIndex(stepsCount - 1);
×
1123

1124
        if (endColor.isValid())
×
1125
            setStepColor(endColor);
×
1126
        else
1127
            setStepColor(startColor);
×
1128
    }
1129

1130
    calculateColorDelta(startColor, endColor, algorithm);
×
1131
}
×
1132

1133
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1134
                                  QColor startColor, QColor endColor, int stepsNumber)
1135
{
1136
    if (order == Function::PingPong)
×
1137
    {
1138
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1139
        {
1140
            m_direction = Function::Backward;
×
1141
            m_currentStepIndex = stepsNumber - 2;
×
1142
            if (endColor.isValid())
×
1143
                m_stepColor = endColor;
×
1144

1145
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1146
        }
1147
        else if (m_direction == Function::Backward && (m_currentStepIndex - 1) < 0)
×
1148
        {
1149
            m_direction = Function::Forward;
×
1150
            m_currentStepIndex = 1;
×
1151
            m_stepColor = startColor;
×
1152
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1153
        }
1154
        else
1155
        {
1156
            if (m_direction == Function::Forward)
×
1157
                m_currentStepIndex++;
×
1158
            else
1159
                m_currentStepIndex--;
×
1160
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1161
        }
1162
    }
1163
    else if (order == Function::SingleShot)
×
1164
    {
1165
        if (m_direction == Function::Forward)
×
1166
        {
1167
            if (m_currentStepIndex >= stepsNumber - 1)
×
1168
                return false;
×
1169
            else
1170
            {
1171
                m_currentStepIndex++;
×
1172
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1173
            }
1174
        }
1175
        else
1176
        {
1177
            if (m_currentStepIndex <= 0)
×
1178
                return false;
×
1179
            else
1180
            {
1181
                m_currentStepIndex--;
×
1182
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1183
            }
1184
        }
1185
    }
1186
    else
1187
    {
1188
        if (m_direction == Function::Forward)
×
1189
        {
1190
            if (m_currentStepIndex >= stepsNumber - 1)
×
1191
            {
1192
                m_currentStepIndex = 0;
×
1193
                m_stepColor = startColor;
×
1194
            }
1195
            else
1196
            {
1197
                m_currentStepIndex++;
×
1198
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1199
            }
1200
        }
1201
        else
1202
        {
1203
            if (m_currentStepIndex <= 0)
×
1204
            {
1205
                m_currentStepIndex = stepsNumber - 1;
×
1206
                if (endColor.isValid())
×
1207
                    m_stepColor = endColor;
×
1208
            }
1209
            else
1210
            {
1211
                m_currentStepIndex--;
×
1212
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1213
            }
1214
        }
1215
    }
1216

1217
    return true;
×
1218
}
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