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

mcallegari / qlcplus / 19144422256

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

push

github

mcallegari
Back to 5.1.0 debug

17718 of 51723 relevant lines covered (34.26%)

19528.23 hits per line

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

41.8
/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()
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()
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)
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

771
FadeChannel *RGBMatrix::getFader(Universe *universe, quint32 fixtureID, quint32 channel)
×
772
{
773
    // get the universe Fader first. If doesn't exist, create it
774
    if (universe == NULL)
×
775
        return NULL;
×
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

788
    return fader->getChannelFader(doc(), universe, fixtureID, channel);
×
789
}
×
790

791
void RGBMatrix::updateFaderValues(FadeChannel *fc, uchar value, uint fadeTime)
×
792
{
793
    fc->setStart(fc->current());
×
794
    fc->setTarget(value);
×
795
    fc->setElapsed(0);
×
796
    fc->setReady(false);
×
797
    // fade in/out depends on target value
798
    if (value == 0)
×
799
        fc->setFadeTime(fadeOutSpeed());
×
800
    else
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
            // http://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);
×
917

918
            FadeChannel *fc = getFader(universes.at(universeIndex), grpHead.fxi, channelList.at(i));
×
919
            updateFaderValues(fc, valueList.at(i), fadeTime);
×
920
        }
921
    }
×
922
}
×
923

924
uchar RGBMatrix::rgbToGrey(uint col)
×
925
{
926
    // the weights are taken from
927
    // https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
928
    return (0.299 * qRed(col) + 0.587 * qGreen(col) + 0.114 * qBlue(col));
×
929
}
930

931
/*********************************************************************
932
 * Attributes
933
 *********************************************************************/
934

935
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
936
{
937
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
938

939
    if (attrIndex == Intensity)
×
940
    {
941
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
942
        {
943
            if (!fader.isNull())
×
944
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
945
        }
×
946
    }
947

948
    return attrIndex;
×
949
}
950

951
/*************************************************************************
952
 * Blend mode
953
 *************************************************************************/
954

955
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
956
{
957
    if (mode == blendMode())
×
958
        return;
×
959

960
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
961
    {
962
        if (!fader.isNull())
×
963
            fader->setBlendMode(mode);
×
964
    }
×
965

966
    Function::setBlendMode(mode);
×
967
    emit changed(id());
×
968
}
969

970
/*************************************************************************
971
 * Control Mode
972
 *************************************************************************/
973

974
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
975
{
976
    return m_controlMode;
2✔
977
}
978

979
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
980
{
981
    m_controlMode = mode;
3✔
982
    emit changed(id());
3✔
983
}
3✔
984

985
RGBMatrix::ControlMode RGBMatrix::stringToControlMode(QString mode)
1✔
986
{
987
    if (mode == KXMLQLCRGBMatrixControlModeRgb)
1✔
988
        return ControlModeRgb;
1✔
989
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
990
        return ControlModeAmber;
×
991
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
992
        return ControlModeWhite;
×
993
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
994
        return ControlModeUV;
×
995
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
996
        return ControlModeDimmer;
×
997
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
998
        return ControlModeShutter;
×
999

1000
    return ControlModeRgb;
×
1001
}
1002

1003
QString RGBMatrix::controlModeToString(RGBMatrix::ControlMode mode)
1✔
1004
{
1005
    switch(mode)
1✔
1006
    {
1007
        default:
1✔
1008
        case ControlModeRgb:
1009
            return QString(KXMLQLCRGBMatrixControlModeRgb);
1✔
1010
        break;
1011
        case ControlModeAmber:
×
1012
            return QString(KXMLQLCRGBMatrixControlModeAmber);
×
1013
        break;
1014
        case ControlModeWhite:
×
1015
            return QString(KXMLQLCRGBMatrixControlModeWhite);
×
1016
        break;
1017
        case ControlModeUV:
×
1018
            return QString(KXMLQLCRGBMatrixControlModeUV);
×
1019
        break;
1020
        case ControlModeDimmer:
×
1021
            return QString(KXMLQLCRGBMatrixControlModeDimmer);
×
1022
        break;
1023
        case ControlModeShutter:
×
1024
            return QString(KXMLQLCRGBMatrixControlModeShutter);
×
1025
        break;
1026
    }
1027
}
1028

1029

1030
/*************************************************************************
1031
 *************************************************************************
1032
 *                          RGBMatrixStep class
1033
 *************************************************************************
1034
 *************************************************************************/
1035

1036
RGBMatrixStep::RGBMatrixStep()
10✔
1037
    : m_direction(Function::Forward)
10✔
1038
    , m_currentStepIndex(0)
10✔
1039
    , m_stepColor(QColor())
10✔
1040
    , m_crDelta(0)
10✔
1041
    , m_cgDelta(0)
10✔
1042
    , m_cbDelta(0)
10✔
1043
{
1044

1045
}
10✔
1046

1047
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1048
{
1049
    m_currentStepIndex = index;
×
1050
}
×
1051

1052
int RGBMatrixStep::currentStepIndex() const
1✔
1053
{
1054
    return m_currentStepIndex;
1✔
1055
}
1056

1057
void RGBMatrixStep::calculateColorDelta(QColor startColor, QColor endColor, RGBAlgorithm *algorithm)
16✔
1058
{
1059
    m_crDelta = 0;
16✔
1060
    m_cgDelta = 0;
16✔
1061
    m_cbDelta = 0;
16✔
1062

1063
    if (endColor.isValid() && algorithm != NULL && algorithm->acceptColors() > 1)
16✔
1064
    {
1065
        m_crDelta = endColor.red() - startColor.red();
10✔
1066
        m_cgDelta = endColor.green() - startColor.green();
10✔
1067
        m_cbDelta = endColor.blue() - startColor.blue();
10✔
1068

1069
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1070
    }
1071
}
16✔
1072

1073
void RGBMatrixStep::setStepColor(QColor color)
×
1074
{
1075
    m_stepColor = color;
×
1076
}
×
1077

1078
QColor RGBMatrixStep::stepColor()
6✔
1079
{
1080
    return m_stepColor;
6✔
1081
}
1082

1083
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1084
{
1085
    if (stepsCount <= 0)
×
1086
        return;
×
1087

1088
    if (stepsCount == 1)
×
1089
    {
1090
        m_stepColor = startColor;
×
1091
    }
1092
    else
1093
    {
1094
        m_stepColor.setRed(startColor.red() + (m_crDelta * stepIndex / (stepsCount - 1)));
×
1095
        m_stepColor.setGreen(startColor.green() + (m_cgDelta * stepIndex / (stepsCount - 1)));
×
1096
        m_stepColor.setBlue(startColor.blue() + (m_cbDelta * stepIndex / (stepsCount - 1)));
×
1097
    }
1098

1099
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1100
}
1101

1102
void RGBMatrixStep::initializeDirection(Function::Direction direction, QColor startColor, QColor endColor, int stepsCount, RGBAlgorithm *algorithm)
×
1103
{
1104
    m_direction = direction;
×
1105

1106
    if (m_direction == Function::Forward)
×
1107
    {
1108
        setCurrentStepIndex(0);
×
1109
        setStepColor(startColor);
×
1110
    }
1111
    else
1112
    {
1113
        setCurrentStepIndex(stepsCount - 1);
×
1114

1115
        if (endColor.isValid())
×
1116
            setStepColor(endColor);
×
1117
        else
1118
            setStepColor(startColor);
×
1119
    }
1120

1121
    calculateColorDelta(startColor, endColor, algorithm);
×
1122
}
×
1123

1124
bool RGBMatrixStep::checkNextStep(Function::RunOrder order,
×
1125
                                  QColor startColor, QColor endColor, int stepsNumber)
1126
{
1127
    if (order == Function::PingPong)
×
1128
    {
1129
        if (m_direction == Function::Forward && (m_currentStepIndex + 1) == stepsNumber)
×
1130
        {
1131
            m_direction = Function::Backward;
×
1132
            m_currentStepIndex = stepsNumber - 2;
×
1133
            if (endColor.isValid())
×
1134
                m_stepColor = endColor;
×
1135

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

1208
    return true;
×
1209
}
1210

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