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

mcallegari / qlcplus / 14452115210

14 Apr 2025 05:40PM UTC coverage: 31.855% (+0.001%) from 31.854%
14452115210

Pull #1708

github

web-flow
Merge 772292420 into 02d6f8913
Pull Request #1708: simplify work with containers

55 of 80 new or added lines in 19 files covered. (68.75%)

3 existing lines in 3 files now uncovered.

14671 of 46056 relevant lines covered (31.85%)

26461.66 hits per line

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

42.46
/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)
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_roundTime(new QElapsedTimer())
9✔
75
    , m_stepsCount(0)
9✔
76
    , m_stepBeatDuration(0)
9✔
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())
7✔
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();
1✔
387
        for (int i = 0; i < colors.count(); i++)
1✔
388
            setColor(i, QColor::fromRgb(colors.at(i)));
×
389
    }
1✔
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
    QHash<QString, QString>::iterator it = m_properties.find(propName);
3✔
399
    if (it != m_properties.end())
3✔
400
        return it.value();
401

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

409
    return QString();
410
}
411

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

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

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

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

489
    return true;
490
}
491

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

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

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

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

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

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

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

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

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

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

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

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

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

548
    return true;
1✔
549
}
550

551
/****************************************************************************
552
 * Running
553
 ****************************************************************************/
554

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

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

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

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

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

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

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

624
    m_roundTime->restart();
×
625

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

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

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

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

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

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

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

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

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

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

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

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

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

733
    m_fadersMap.clear();
×
734

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

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

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

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

754
    m_roundTime->restart();
×
755

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

922
/*********************************************************************
923
 * Attributes
924
 *********************************************************************/
925

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

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

939
    return attrIndex;
×
940
}
941

942
/*************************************************************************
943
 * Blend mode
944
 *************************************************************************/
945

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

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

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

961
/*************************************************************************
962
 * Control Mode
963
 *************************************************************************/
964

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

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

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

991
    return ControlModeRgb;
992
}
993

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

1020

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

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

1036
}
10✔
1037

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1199
    return true;
1200
}
1201

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