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

mcallegari / qlcplus / 15805077468

22 Jun 2025 08:36AM UTC coverage: 31.876% (-0.01%) from 31.89%
15805077468

push

github

mcallegari
plugins/dmxusb: fix RDM discovery and commands while DMX is running

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

3722 existing lines in 175 files now uncovered.

16438 of 51569 relevant lines covered (31.88%)

19266.08 hits per line

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

41.64
/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✔
UNCOV
105
        return;
×
106

107
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
1✔
108
    if (grp == NULL)
1✔
UNCOV
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✔
UNCOV
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;
×
UNCOV
152
        copy = NULL;
×
153
    }
154
    if (addToDoc == true && doc->addFunction(copy) == false)
1✔
155
    {
156
        delete copy;
×
UNCOV
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✔
UNCOV
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()
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
            {
UNCOV
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()
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✔
UNCOV
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✔
UNCOV
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✔
UNCOV
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

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

498
    return true;
1✔
499
}
500

501
bool RGBMatrix::saveXML(QXmlStreamWriter *doc)
1✔
502
{
503
    Q_ASSERT(doc != NULL);
1✔
504

505
    /* Function tag */
506
    doc->writeStartElement(KXMLQLCFunction);
2✔
507

508
    /* Common attributes */
509
    saveXMLCommon(doc);
1✔
510

511
    /* Speeds */
512
    saveXMLSpeed(doc);
1✔
513

514
    /* Direction */
515
    saveXMLDirection(doc);
1✔
516

517
    /* Run order */
518
    saveXMLRunOrder(doc);
1✔
519

520
    /* Algorithm */
521
    if (m_algorithm != NULL)
1✔
522
        m_algorithm->saveXML(doc);
1✔
523

524
    /* LEGACY - Dimmer Control */
525
    if (dimmerControl())
1✔
526
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
527

528
    /* Colors */
529
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
530
    {
531
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
10✔
532
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
10✔
533
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
534
        doc->writeEndElement();
5✔
535
    }
536

537
    /* Control Mode */
538
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
2✔
539

540
    /* Fixture Group */
541
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
2✔
542

543
    /* Properties */
544
    QMapIterator<QString, QString> it(m_properties);
1✔
545
    while (it.hasNext())
1✔
546
    {
UNCOV
547
        it.next();
×
548
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
549
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
550
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
551
        doc->writeEndElement();
×
552
    }
553

554
    /* End the <Function> tag */
555
    doc->writeEndElement();
1✔
556

557
    return true;
1✔
558
}
1✔
559

560
/****************************************************************************
561
 * Running
562
 ****************************************************************************/
563

564
void RGBMatrix::tap()
×
565
{
566
    if (stopped() == false)
×
567
    {
568
        FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
×
569
        // Filter out taps that are too close to each other
570
        if (grp != NULL && uint(m_roundTime.elapsed()) >= (duration() / 4))
×
571
        {
572
            roundCheck();
×
573
            resetElapsed();
×
574
        }
575
    }
576
}
×
577

578
void RGBMatrix::checkEngineCreation()
×
579
{
580
    m_runAlgorithm = m_algorithm;
×
581
    m_requestEngineCreation = false;
×
582
}
×
583

584
void RGBMatrix::preRun(MasterTimer *timer)
×
585
{
586
    {
587
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
588

589
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
590
        if (m_group == NULL)
×
591
        {
592
            // No fixture group to control
593
            stop(FunctionParent::master());
×
UNCOV
594
            return;
×
595
        }
596

597
        if (m_algorithm != NULL)
×
598
        {
599
            checkEngineCreation();
×
600

601
            // Copy direction from parent class direction
602
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_runAlgorithm);
×
603

604
            if (m_runAlgorithm->type() == RGBAlgorithm::Script)
×
605
            {
606
                RGBScript *script = static_cast<RGBScript*> (m_runAlgorithm);
×
607
                QMapIterator<QString, QString> it(m_properties);
×
608
                while (it.hasNext())
×
609
                {
UNCOV
610
                    it.next();
×
611
                    script->setProperty(it.key(), it.value());
×
612
                }
UNCOV
613
            }
×
614
            else if (m_runAlgorithm->type() == RGBAlgorithm::Image)
×
615
            {
616
                RGBImage *image = static_cast<RGBImage*> (m_runAlgorithm);
×
617
                if (image->animatedSource())
×
618
                    image->rewindAnimation();
×
619
            }
620
        }
UNCOV
621
    }
×
622

623
    m_roundTime.restart();
×
624

625
    Function::preRun(timer);
×
626
}
627

628
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
629
{
630
    Q_UNUSED(timer);
631

632
    {
633
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
634
        if (m_group == NULL)
×
635
        {
636
            // No fixture group to control
637
            stop(FunctionParent::master());
×
UNCOV
638
            return;
×
639
        }
640

641
        // No time to do anything.
642
        if (duration() == 0)
×
UNCOV
643
            return;
×
644

645
        if (m_algorithm != NULL && m_requestEngineCreation)
×
646
            checkEngineCreation();
×
647

648
        // Invalid/nonexistent script
649
        if (m_runAlgorithm == NULL || m_runAlgorithm->apiVersion() == 0)
×
650
            return;
×
651

652
        if (isPaused() == false)
×
653
        {
654
            // Get a new map every time elapsed is reset to zero
655
            if (elapsed() < MasterTimer::tick())
×
656
            {
657
                if (tempoType() == Beats)
×
658
                    m_stepBeatDuration = beatsToTime(duration(), timer->beatTimeDuration());
×
659

660
                //qDebug() << "RGBMatrix step" << m_stepHandler->currentStepIndex() << ", color:" << QString::number(m_stepHandler->stepColor().rgb(), 16);
661
                m_runAlgorithm->rgbMap(m_group->size(), m_stepHandler->stepColor().rgb(),
×
662
                                       m_stepHandler->currentStepIndex(), m_stepHandler->m_map);
×
663
                updateMapChannels(m_stepHandler->m_map, m_group, universes);
×
664
            }
665
        }
UNCOV
666
    }
×
667

668
    if (isPaused() == false)
×
669
    {
670
        // Increment the ms elapsed time
671
        incrementElapsed();
×
672

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

709
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
710
{
711
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
712

713
    /* If no fade out is needed, dismiss all the requested faders.
714
     * Otherwise, set all the faders to fade out and let Universe dismiss them
715
     * when done */
716
    if (fadeout == 0)
×
717
    {
718
        dismissAllFaders();
×
719
    }
720
    else
721
    {
722
        if (tempoType() == Beats)
×
723
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
724

725
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
726
        {
727
            if (!fader.isNull())
×
728
                fader->setFadeOut(true, fadeout);
×
UNCOV
729
        }
×
730
    }
731

732
    m_fadersMap.clear();
×
733

734
    {
735
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
736
        checkEngineCreation();
×
737
        if (m_runAlgorithm != NULL)
×
738
            m_runAlgorithm->postRun();
×
UNCOV
739
    }
×
740

741
    Function::postRun(timer, universes);
×
742
}
×
743

744
void RGBMatrix::roundCheck()
×
745
{
746
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
747
    if (m_algorithm == NULL)
×
UNCOV
748
        return;
×
749

750
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
751
        stop(FunctionParent::master());
×
752

753
    m_roundTime.restart();
×
754

755
    if (tempoType() == Beats)
×
756
        roundElapsed(m_stepBeatDuration);
×
757
    else
758
        roundElapsed(duration());
×
UNCOV
759
}
×
760

761
FadeChannel *RGBMatrix::getFader(Universe *universe, quint32 fixtureID, quint32 channel)
×
762
{
763
    // get the universe Fader first. If doesn't exist, create it
764
    if (universe == NULL)
×
UNCOV
765
        return NULL;
×
766

767
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
×
768
    if (fader.isNull())
×
769
    {
770
        fader = universe->requestFader();
×
771
        fader->adjustIntensity(getAttributeValue(Intensity));
×
772
        fader->setBlendMode(blendMode());
×
773
        fader->setName(name());
×
774
        fader->setParentFunctionID(id());
×
775
        m_fadersMap[universe->id()] = fader;
×
776
    }
777

778
    return fader->getChannelFader(doc(), universe, fixtureID, channel);
×
UNCOV
779
}
×
780

781
void RGBMatrix::updateFaderValues(FadeChannel *fc, uchar value, uint fadeTime)
×
782
{
783
    fc->setStart(fc->current());
×
784
    fc->setTarget(value);
×
785
    fc->setElapsed(0);
×
786
    fc->setReady(false);
×
787
    // fade in/out depends on target value
788
    if (value == 0)
×
789
        fc->setFadeTime(fadeOutSpeed());
×
790
    else
791
        fc->setFadeTime(fadeTime);
×
792
}
×
793

794
void RGBMatrix::updateMapChannels(const RGBMap& map, const FixtureGroup *grp, QList<Universe *> universes)
×
795
{
796
    uint fadeTime = (overrideFadeInSpeed() == defaultSpeed()) ? fadeInSpeed() : overrideFadeInSpeed();
×
797

798
    // Create/modify fade channels for ALL heads in the group
799
    QMapIterator<QLCPoint, GroupHead> it(grp->headsMap());
×
800
    while (it.hasNext())
×
801
    {
UNCOV
802
        it.next();
×
803
        QLCPoint pt = it.key();
×
804
        GroupHead grpHead = it.value();
×
805
        Fixture *fxi = doc()->fixture(grpHead.fxi);
×
806
        if (fxi == NULL)
×
807
            continue;
×
808

809
        QLCFixtureHead head = fxi->head(grpHead.head);
×
810

811
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
UNCOV
812
            continue;
×
813

814
        uint col = map[pt.y()][pt.x()];
×
815
        QVector<quint32> channelList;
×
816
        QVector<uchar> valueList;
×
817

818
        if (m_controlMode == ControlModeRgb)
×
819
        {
820
            channelList = head.rgbChannels();
×
821

822
            if (channelList.size() == 3)
×
823
            {
824
                valueList.append(qRed(col));
×
825
                valueList.append(qGreen(col));
×
826
                valueList.append(qBlue(col));
×
827
            }
828
            else
829
            {
830
                channelList = head.cmyChannels();
×
831

832
                if (channelList.size() == 3)
×
833
                {
834
                    // CMY color mixing
835
                    QColor cmyCol(col);
×
836
                    valueList.append(cmyCol.cyan());
×
837
                    valueList.append(cmyCol.magenta());
×
838
                    valueList.append(cmyCol.yellow());
×
839
                }
840
            }
841
        }
842
        else if (m_controlMode == ControlModeShutter)
×
843
        {
844
            channelList = head.shutterChannels();
×
845

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

872
            quint32 masterDim = fxi->masterIntensityChannel();
×
873
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
874

875
            if (masterDim != QLCChannel::invalid())
×
876
            {
877
                channelList.append(masterDim);
×
878
                valueList.append(rgbToGrey(col));
×
879
            }
880

881
            if (headDim != QLCChannel::invalid() && headDim != masterDim)
×
882
            {
883
                channelList.append(headDim);
×
884
                valueList.append(rgbToGrey(col) == 0 ? 0 : 255);
×
885
            }
UNCOV
886
        }
×
887
        else
888
        {
889
            if (m_controlMode == ControlModeWhite)
×
890
                channelList.append(head.channelNumber(QLCChannel::White, QLCChannel::MSB));
×
891
            else if (m_controlMode == ControlModeAmber)
×
892
                channelList.append(head.channelNumber(QLCChannel::Amber, QLCChannel::MSB));
×
893
            else if (m_controlMode == ControlModeUV)
×
894
                channelList.append(head.channelNumber(QLCChannel::UV, QLCChannel::MSB));
×
895

896
            valueList.append(rgbToGrey(col));
×
897
        }
898

899
        quint32 absAddress = fxi->universeAddress();
×
900

901
        for (int i = 0; i < channelList.count(); i++)
×
902
        {
903
            if (channelList.at(i) == QLCChannel::invalid())
×
904
                continue;
×
905

906
            quint32 universeIndex = floor((absAddress + channelList.at(i)) / 512);
×
907

908
            FadeChannel *fc = getFader(universes.at(universeIndex), grpHead.fxi, channelList.at(i));
×
909
            updateFaderValues(fc, valueList.at(i), fadeTime);
×
910
        }
911
    }
×
912
}
×
913

914
uchar RGBMatrix::rgbToGrey(uint col)
×
915
{
916
    // the weights are taken from
917
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
918
    return (0.299 * qRed(col) + 0.587 * qGreen(col) + 0.114 * qBlue(col));
×
919
}
920

921
/*********************************************************************
922
 * Attributes
923
 *********************************************************************/
924

925
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
926
{
927
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
928

929
    if (attrIndex == Intensity)
×
930
    {
931
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
932
        {
933
            if (!fader.isNull())
×
934
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
UNCOV
935
        }
×
936
    }
937

938
    return attrIndex;
×
939
}
940

941
/*************************************************************************
942
 * Blend mode
943
 *************************************************************************/
944

945
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
946
{
947
    if (mode == blendMode())
×
UNCOV
948
        return;
×
949

950
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
951
    {
952
        if (!fader.isNull())
×
953
            fader->setBlendMode(mode);
×
UNCOV
954
    }
×
955

956
    Function::setBlendMode(mode);
×
957
    emit changed(id());
×
958
}
959

960
/*************************************************************************
961
 * Control Mode
962
 *************************************************************************/
963

964
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
965
{
966
    return m_controlMode;
2✔
967
}
968

969
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
970
{
971
    m_controlMode = mode;
3✔
972
    emit changed(id());
3✔
973
}
3✔
974

975
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
976
{
977
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
978
        return ControlModeRgb;
1✔
979
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
UNCOV
980
        return ControlModeAmber;
×
981
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
UNCOV
982
        return ControlModeWhite;
×
983
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
UNCOV
984
        return ControlModeUV;
×
985
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
UNCOV
986
        return ControlModeDimmer;
×
987
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
988
        return ControlModeShutter;
×
989

UNCOV
990
    return ControlModeRgb;
×
991
}
992

993
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
994
{
995
    switch(mode)
1✔
996
    {
997
        default:
1✔
998
        case ControlModeRgb:
999
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
1000
        break;
1001
        case ControlModeAmber:
×
1002
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
1003
        break;
1004
        case ControlModeWhite:
×
1005
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
1006
        break;
1007
        case ControlModeUV:
×
1008
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1009
        break;
1010
        case ControlModeDimmer:
×
1011
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1012
        break;
1013
        case ControlModeShutter:
×
1014
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1015
        break;
1016
    }
1017
}
1018

1019

1020
/*************************************************************************
1021
 *************************************************************************
1022
 *                          RGBMatrixStep class
1023
 *************************************************************************
1024
 *************************************************************************/
1025

1026
RGBMatrixStep::RGBMatrixStep()
10✔
1027
    : m_direction(Function::Forward)
10✔
1028
    , m_currentStepIndex(0)
10✔
1029
    , m_stepColor(QColor())
10✔
1030
    , m_crDelta(0)
10✔
1031
    , m_cgDelta(0)
10✔
1032
    , m_cbDelta(0)
10✔
1033
{
1034

1035
}
10✔
1036

1037
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1038
{
1039
    m_currentStepIndex = index;
×
1040
}
×
1041

1042
int RGBMatrixStep::currentStepIndex() const
1✔
1043
{
1044
    return m_currentStepIndex;
1✔
1045
}
1046

1047
void RGBMatrixStep::calculateColorDelta(QColor startColor, QColor endColor, RGBAlgorithm *algorithm)
16✔
1048
{
1049
    m_crDelta = 0;
16✔
1050
    m_cgDelta = 0;
16✔
1051
    m_cbDelta = 0;
16✔
1052

1053
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1054
    {
1055
        m_crDelta = endColor.red() - startColor.red();
10✔
1056
        m_cgDelta = endColor.green() - startColor.green();
10✔
1057
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1058

1059
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1060
    }
1061
}
16✔
1062

1063
void RGBMatrixStep::setStepColor(QColor color)
×
1064
{
1065
    m_stepColor = color;
×
1066
}
×
1067

1068
QColor RGBMatrixStep::stepColor()
6✔
1069
{
1070
    return m_stepColor;
6✔
1071
}
1072

1073
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1074
{
1075
    if (stepsCount <= 0)
×
UNCOV
1076
        return;
×
1077

1078
    if (stepsCount == 1)
×
1079
    {
1080
        m_stepColor = startColor;
×
1081
    }
1082
    else
1083
    {
1084
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
1085
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
1086
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1087
    }
1088

1089
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1090
}
1091

1092
void RGBMatrixStep::initializeDirection(Function::Direction direction, QColor startColor, QColor endColor, int stepsCount, RGBAlgorithm *algorithm)
×
1093
{
1094
    m_direction = direction;
×
1095

1096
    if (m_direction == Function::Forward)
×
1097
    {
1098
        setCurrentStepIndex(0);
×
1099
        setStepColor(startColor);
×
1100
    }
1101
    else
1102
    {
1103
        setCurrentStepIndex(stepsCount - 1);
×
1104

1105
        if (endColor.isValid())
×
1106
            setStepColor(endColor);
×
1107
        else
1108
            setStepColor(startColor);
×
1109
    }
1110

1111
    calculateColorDelta(startColor, endColor, algorithm);
×
1112
}
×
1113

1114
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1115
                                  QColor startColor, QColor endColor, int stepsNumber)
1116
{
1117
    if (order == Function::PingPong)
×
1118
    {
1119
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1120
        {
1121
            m_direction = Function::Backward;
×
1122
            m_currentStepIndex = stepsNumber - 2;
×
1123
            if (endColor.isValid())
×
1124
                m_stepColor = endColor;
×
1125

1126
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1127
        }
1128
        else if (m_direction == Function::Backward && (m_currentStepIndex - 1) < 0)
×
1129
        {
1130
            m_direction = Function::Forward;
×
1131
            m_currentStepIndex = 1;
×
1132
            m_stepColor = startColor;
×
1133
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1134
        }
1135
        else
1136
        {
1137
            if (m_direction == Function::Forward)
×
1138
                m_currentStepIndex++;
×
1139
            else
1140
                m_currentStepIndex--;
×
1141
            updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1142
        }
1143
    }
1144
    else if (order == Function::SingleShot)
×
1145
    {
1146
        if (m_direction == Function::Forward)
×
1147
        {
1148
            if (m_currentStepIndex >= stepsNumber - 1)
×
UNCOV
1149
                return false;
×
1150
            else
1151
            {
1152
                m_currentStepIndex++;
×
1153
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1154
            }
1155
        }
1156
        else
1157
        {
1158
            if (m_currentStepIndex <= 0)
×
UNCOV
1159
                return false;
×
1160
            else
1161
            {
1162
                m_currentStepIndex--;
×
1163
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1164
            }
1165
        }
1166
    }
1167
    else
1168
    {
1169
        if (m_direction == Function::Forward)
×
1170
        {
1171
            if (m_currentStepIndex >= stepsNumber - 1)
×
1172
            {
1173
                m_currentStepIndex = 0;
×
1174
                m_stepColor = startColor;
×
1175
            }
1176
            else
1177
            {
1178
                m_currentStepIndex++;
×
1179
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1180
            }
1181
        }
1182
        else
1183
        {
1184
            if (m_currentStepIndex <= 0)
×
1185
            {
1186
                m_currentStepIndex = stepsNumber - 1;
×
1187
                if (endColor.isValid())
×
1188
                    m_stepColor = endColor;
×
1189
            }
1190
            else
1191
            {
1192
                m_currentStepIndex--;
×
1193
                updateStepColor(m_currentStepIndex, startColor, stepsNumber);
×
1194
            }
1195
        }
1196
    }
1197

UNCOV
1198
    return true;
×
1199
}
1200

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