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

mcallegari / qlcplus / 13619171849

02 Mar 2025 08:47PM UTC coverage: 31.5% (-0.003%) from 31.503%
13619171849

push

github

mcallegari
engine: fix RGB Matrix algorithm change while running on Qt5

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

1 existing line in 1 file now uncovered.

14155 of 44937 relevant lines covered (31.5%)

27008.17 hits per line

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

41.22
/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      QString("MonoColor")
39
#define KXMLQLCRGBMatrixEndColor        QString("EndColor")
40
#define KXMLQLCRGBMatrixColor           QString("Color")
41
#define KXMLQLCRGBMatrixColorIndex      QString("Index")
42

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

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

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

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

62
RGBMatrix::RGBMatrix(Doc *doc)
9✔
63
    : Function(doc, Function::RGBMatrixType)
64
    , m_dimmerControl(false)
65
    , m_fixtureGroupID(FixtureGroup::invalidId())
9✔
66
    , m_group(NULL)
67
    , m_requestEngineCreation(true)
68
    , m_runAlgorithm(NULL)
69
    , m_algorithm(NULL)
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_roundTime(new QElapsedTimer())
9✔
75
    , m_stepsCount(0)
76
    , m_stepBeatDuration(0)
77
    , m_controlMode(RGBMatrix::ControlModeRgb)
18✔
78
{
79
    setName(tr("New RGB Matrix"));
9✔
80
    setDuration(500);
9✔
81

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

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

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

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

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

106
    if (m_algorithm == NULL)
1✔
107
        return;
108

109
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
1✔
110
    if (grp == NULL)
1✔
111
        return;
112

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

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

121
    if (m_algorithm == NULL)
3✔
122
        return 0;
123

124
    FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
3✔
125
    if (grp == NULL)
3✔
126
        return 0;
127

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

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

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

142
/****************************************************************************
143
 * Copying
144
 ****************************************************************************/
145

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

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

162
    return copy;
1✔
163
}
164

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

171
    setDimmerControl(mtx->dimmerControl());
1✔
172
    setFixtureGroup(mtx->fixtureGroup());
1✔
173

174
    m_rgbColors.clear();
1✔
175
    foreach (QColor col, mtx->getColors())
12✔
176
        m_rgbColors.append(col);
5✔
177

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

183
    setControlMode(mtx->controlMode());
1✔
184

185
    return Function::copyFrom(function);
1✔
186
}
187

188
/****************************************************************************
189
 * Fixtures
190
 ****************************************************************************/
191

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

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

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

212
    return QList<quint32>();
213
}
214

215
/****************************************************************************
216
 * Algorithm
217
 ****************************************************************************/
218

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

226
        m_requestEngineCreation = true;
13✔
227

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

249
    emit changed(id());
13✔
250
}
13✔
251

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

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

269

270
int RGBMatrix::stepsCount()
2✔
271
{
272
    return m_stepsCount;
2✔
273
}
274

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

279
    if (m_algorithm == NULL)
22✔
280
        return 0;
281

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

286
    return 0;
287
}
288

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

295
    if (m_group == NULL)
7✔
296
        m_group = doc()->fixtureGroup(fixtureGroup());
1✔
297

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

305
/****************************************************************************
306
 * Color
307
 ****************************************************************************/
308

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

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

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

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

335
    return m_rgbColors.at(i);
336
}
337

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

343
void RGBMatrix::updateColorDelta()
16✔
344
{
345
    m_stepHandler->calculateColorDelta(m_rgbColors[0], m_rgbColors[1], m_algorithm);
32✔
346
}
16✔
347

348
void RGBMatrix::setMapColors(RGBAlgorithm *algorithm)
31✔
349
{
350
    QMutexLocker algorithmLocker(&m_algorithmMutex);
31✔
351
    if (algorithm == NULL)
31✔
352
        return;
353

354
    if (algorithm->apiVersion() < 3)
22✔
355
        return;
356

357
    if (m_group == NULL)
×
358
        m_group = doc()->fixtureGroup(fixtureGroup());
×
359

360
    if (m_group != NULL)
×
361
    {
362
        QVector<unsigned int> rawColors;
×
363
        for (int i = 0; i < algorithm->acceptColors(); i++)
×
364
        {
365
            QColor col = m_rgbColors.at(i);
×
366
            rawColors.append(col.isValid() ? col.rgb() : 0);
×
367
        }
368

369
        algorithm->rgbMapSetColors(rawColors);
×
370
    }
371
}
372

373
/************************************************************************
374
 * Properties
375
 ************************************************************************/
376

377
void RGBMatrix::setProperty(QString propName, QString value)
1✔
378
{
379
    QMutexLocker algoLocker(&m_algorithmMutex);
1✔
380
    m_properties[propName] = value;
1✔
381
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
1✔
382
    {
383
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
1✔
384
        script->setProperty(propName, value);
1✔
385

386
        QVector<uint> colors = script->rgbMapGetColors();
2✔
387
        for (int i = 0; i < colors.count(); i++)
1✔
388
            setColor(i, QColor::fromRgb(colors.at(i)));
×
389
    }
390
    m_stepsCount = algorithmStepsCount();
1✔
391
}
1✔
392

393
QString RGBMatrix::property(QString propName)
3✔
394
{
395
    QMutexLocker algoLocker(&m_algorithmMutex);
3✔
396

397
    /** If the property is cached, then return it right away */
398
    if (m_properties.contains(propName))
3✔
399
        return m_properties[propName];
1✔
400

401
    /** Otherwise, let's retrieve it from the Script */
402
    if (m_algorithm != NULL && m_algorithm->type() == RGBAlgorithm::Script)
2✔
403
    {
404
        RGBScript *script = static_cast<RGBScript*> (m_algorithm);
2✔
405
        return script->property(propName);
2✔
406
    }
407

408
    return QString();
409
}
410

411
/****************************************************************************
412
 * Load & Save
413
 ****************************************************************************/
414

415
bool RGBMatrix::loadXML(QXmlStreamReader &root)
3✔
416
{
417
    if (root.name() != KXMLQLCFunction)
6✔
418
    {
419
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
420
        return false;
1✔
421
    }
422

423
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::RGBMatrixType))
4✔
424
    {
425
        qWarning() << Q_FUNC_INFO << "Function is not an RGB matrix";
1✔
426
        return false;
1✔
427
    }
428

429
    /* Load matrix contents */
430
    while (root.readNextStartElement())
12✔
431
    {
432
        if (root.name() == KXMLQLCFunctionSpeed)
22✔
433
        {
434
            loadXMLSpeed(root);
1✔
435
        }
436
        else if (root.name() == KXMLQLCRGBAlgorithm)
20✔
437
        {
438
            setAlgorithm(RGBAlgorithm::loader(doc(), root));
1✔
439
        }
440
        else if (root.name() == KXMLQLCRGBMatrixFixtureGroup)
18✔
441
        {
442
            setFixtureGroup(root.readElementText().toUInt());
1✔
443
        }
444
        else if (root.name() == KXMLQLCFunctionDirection)
16✔
445
        {
446
            loadXMLDirection(root);
1✔
447
        }
448
        else if (root.name() == KXMLQLCFunctionRunOrder)
14✔
449
        {
450
            loadXMLRunOrder(root);
1✔
451
        }
452
        // Legacy support
453
        else if (root.name() == KXMLQLCRGBMatrixStartColor)
12✔
454
        {
455
            setColor(0, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
456
        }
457
        else if (root.name() == KXMLQLCRGBMatrixEndColor)
12✔
458
        {
459
            setColor(1, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
×
460
        }
461
        else if (root.name() == KXMLQLCRGBMatrixColor)
12✔
462
        {
463
            int colorIdx = root.attributes().value(KXMLQLCRGBMatrixColorIndex).toInt();
5✔
464
            setColor(colorIdx, QColor::fromRgb(QRgb(root.readElementText().toUInt())));
5✔
465
        }
466
        else if (root.name() == KXMLQLCRGBMatrixControlMode)
2✔
467
        {
468
            setControlMode(stringToControlMode(root.readElementText()));
1✔
469
        }
470
        else if (root.name() == KXMLQLCRGBMatrixProperty)
×
471
        {
472
            QString name = root.attributes().value(KXMLQLCRGBMatrixPropertyName).toString();
×
473
            QString value = root.attributes().value(KXMLQLCRGBMatrixPropertyValue).toString();
×
474
            setProperty(name, value);
×
475
            root.skipCurrentElement();
×
476
        }
477
        else if (root.name() == KXMLQLCRGBMatrixDimmerControl)
×
478
        {
479
            setDimmerControl(root.readElementText().toInt());
×
480
        }
481
        else
482
        {
483
            qWarning() << Q_FUNC_INFO << "Unknown RGB matrix tag:" << root.name();
×
484
            root.skipCurrentElement();
×
485
        }
486
    }
487

488
    return true;
489
}
490

491
bool RGBMatrix::saveXML(QXmlStreamWriter *doc)
1✔
492
{
493
    Q_ASSERT(doc != NULL);
494

495
    /* Function tag */
496
    doc->writeStartElement(KXMLQLCFunction);
1✔
497

498
    /* Common attributes */
499
    saveXMLCommon(doc);
1✔
500

501
    /* Speeds */
502
    saveXMLSpeed(doc);
1✔
503

504
    /* Direction */
505
    saveXMLDirection(doc);
1✔
506

507
    /* Run order */
508
    saveXMLRunOrder(doc);
1✔
509

510
    /* Algorithm */
511
    if (m_algorithm != NULL)
1✔
512
        m_algorithm->saveXML(doc);
1✔
513

514
    /* LEGACY - Dimmer Control */
515
    if (dimmerControl())
1✔
516
        doc->writeTextElement(KXMLQLCRGBMatrixDimmerControl, QString::number(dimmerControl()));
×
517

518
    /* Colors */
519
    for (int i = 0; i < m_rgbColors.count(); i++)
6✔
520
    {
521
        doc->writeStartElement(KXMLQLCRGBMatrixColor);
5✔
522
        doc->writeAttribute(KXMLQLCRGBMatrixColorIndex, QString::number(i));
5✔
523
        doc->writeCharacters(QString::number(m_rgbColors.at(i).rgb()));
5✔
524
        doc->writeEndElement();
5✔
525
    }
526

527
    /* Control Mode */
528
    doc->writeTextElement(KXMLQLCRGBMatrixControlMode, RGBMatrix::controlModeToString(m_controlMode));
1✔
529

530
    /* Fixture Group */
531
    doc->writeTextElement(KXMLQLCRGBMatrixFixtureGroup, QString::number(fixtureGroup()));
1✔
532

533
    /* Properties */
534
    QHashIterator<QString, QString> it(m_properties);
1✔
535
    while (it.hasNext())
1✔
536
    {
537
        it.next();
538
        doc->writeStartElement(KXMLQLCRGBMatrixProperty);
×
539
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyName, it.key());
×
540
        doc->writeAttribute(KXMLQLCRGBMatrixPropertyValue, it.value());
×
541
        doc->writeEndElement();
×
542
    }
543

544
    /* End the <Function> tag */
545
    doc->writeEndElement();
1✔
546

547
    return true;
1✔
548
}
549

550
/****************************************************************************
551
 * Running
552
 ****************************************************************************/
553

554
void RGBMatrix::tap()
×
555
{
556
    if (stopped() == false)
×
557
    {
558
        FixtureGroup *grp = doc()->fixtureGroup(fixtureGroup());
×
559
        // Filter out taps that are too close to each other
560
        if (grp != NULL && uint(m_roundTime->elapsed()) >= (duration() / 4))
×
561
        {
562
            roundCheck();
×
563
            resetElapsed();
×
564
        }
565
    }
566
}
×
567

NEW
568
void RGBMatrix::checkEngineCreation()
×
569
{
570
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
571
    if (m_requestEngineCreation)
572
    {
573
        // And here's the hack: Qt6 JS engine is not thread safe. Nice job!
574
        // It's not possible to use the instance created in the main thread
575
        // so we need to create a clone on the fly from the MasterTimer thread :vomiting_face:
576
        m_runAlgorithm = m_algorithm->clone();
577
    }
578
#else
NEW
579
    m_runAlgorithm = m_algorithm;
×
580
#endif
NEW
581
    m_requestEngineCreation = false;
×
NEW
582
}
×
583

UNCOV
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());
×
594
            return;
595
        }
596

597
        if (m_algorithm != NULL)
×
598
        {
NEW
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
                QHashIterator<QString, QString> it(m_properties);
×
608
                while (it.hasNext())
×
609
                {
610
                    it.next();
611
                    script->setProperty(it.key(), it.value());
×
612
                }
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
        }
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());
×
638
            return;
639
        }
640

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

645
        if (m_algorithm != NULL && m_requestEngineCreation)
×
NEW
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
        }
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.values())
×
726
        {
727
            if (!fader.isNull())
×
728
                fader->setFadeOut(true, fadeout);
×
729
        }
730
    }
731

732
    m_fadersMap.clear();
×
733

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

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

744
void RGBMatrix::roundCheck()
×
745
{
746
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
747
    if (m_algorithm == NULL)
×
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());
×
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)
×
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);
×
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
    {
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())
×
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(col == 0 ? 0 : 255);
×
885
            }
×
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.values())
×
932
        {
933
            if (!fader.isNull())
×
934
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
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())
×
948
        return;
949

950
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap.values())
×
951
    {
952
        if (!fader.isNull())
×
953
            fader->setBlendMode(mode);
×
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;
979
    else if (mode == KXMLQLCRGBMatrixControlModeAmber)
×
980
        return ControlModeAmber;
981
    else if (mode == KXMLQLCRGBMatrixControlModeWhite)
×
982
        return ControlModeWhite;
983
    else if (mode == KXMLQLCRGBMatrixControlModeUV)
×
984
        return ControlModeUV;
985
    else if (mode == KXMLQLCRGBMatrixControlModeDimmer)
×
986
        return ControlModeDimmer;
987
    else if (mode == KXMLQLCRGBMatrixControlModeShutter)
×
988
        return ControlModeShutter;
×
989

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)
1028
    , m_currentStepIndex(0)
1029
    , m_stepColor(QColor())
1030
    , m_crDelta(0)
1031
    , m_cgDelta(0)
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)
×
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)
×
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)
×
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

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

© 2025 Coveralls, Inc