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

mcallegari / qlcplus / 13462865500

21 Feb 2025 06:23PM UTC coverage: 31.501% (-0.002%) from 31.503%
13462865500

push

github

mcallegari
engine: fix RGBMatrix concurrency on Qt5

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

1 existing line in 1 file now uncovered.

14154 of 44932 relevant lines covered (31.5%)

26902.48 hits per line

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

41.65
/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

568
void RGBMatrix::preRun(MasterTimer *timer)
×
569
{
570
    {
571
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
572

573
        m_group = doc()->fixtureGroup(m_fixtureGroupID);
×
574
        if (m_group == NULL)
×
575
        {
576
            // No fixture group to control
577
            stop(FunctionParent::master());
×
578
            return;
579
        }
580

581
        if (m_algorithm != NULL)
×
582
        {
583
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
584
            if (m_requestEngineCreation)
585
            {
586
                // And here's the hack: Qt6 JS engine is not thread safe. Nice job!
587
                // It's not possible to use the instance created in the main thread
588
                // so we need to create a clone on the fly from the MasterTimer thread :vomiting_face:
589
                m_runAlgorithm = m_algorithm->clone();
590
                m_requestEngineCreation = false;
591
            }
592
#else
NEW
593
            m_runAlgorithm = m_algorithm;
×
594
#endif
595

596
            // Copy direction from parent class direction
597
            m_stepHandler->initializeDirection(direction(), m_rgbColors[0], m_rgbColors[1], m_stepsCount, m_runAlgorithm);
×
598

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

618
    m_roundTime->restart();
×
619

620
    Function::preRun(timer);
×
621
}
622

623
void RGBMatrix::write(MasterTimer *timer, QList<Universe *> universes)
×
624
{
625
    Q_UNUSED(timer);
626

627
    {
628
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
629
        if (m_group == NULL)
×
630
        {
631
            // No fixture group to control
632
            stop(FunctionParent::master());
×
633
            return;
634
        }
635

636
        // No time to do anything.
637
        if (duration() == 0)
×
638
            return;
639

UNCOV
640
        if (m_algorithm != NULL && m_requestEngineCreation)
×
641
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)        
642
        {
643
            // And here's the hack: Qt6 JS engine is not thread safe. Nice job!
644
            // It's not possible to use the instance created in the main thread
645
            // so we need to create a clone on the fly from the MasterTimer thread :vomiting_face:
646
            m_runAlgorithm = m_algorithm->clone();
647
            m_requestEngineCreation = false;
648
        }
649
#else
NEW
650
            m_runAlgorithm = m_algorithm;
×
651
#endif
652

653
        // Invalid/nonexistent script
654
        if (m_runAlgorithm == NULL || m_runAlgorithm->apiVersion() == 0)
×
655
            return;
×
656

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

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

673
    if (isPaused() == false)
×
674
    {
675
        // Increment the ms elapsed time
676
        incrementElapsed();
×
677

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

714
void RGBMatrix::postRun(MasterTimer *timer, QList<Universe *> universes)
×
715
{
716
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
×
717

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

730
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap.values())
×
731
        {
732
            if (!fader.isNull())
×
733
                fader->setFadeOut(true, fadeout);
×
734
        }
735
    }
736

737
    m_fadersMap.clear();
×
738

739
    {
740
        QMutexLocker algorithmLocker(&m_algorithmMutex);
×
741
        if (m_runAlgorithm != NULL)
×
742
            m_runAlgorithm->postRun();
×
743
    }
744

745
    Function::postRun(timer, universes);
×
746
}
×
747

748
void RGBMatrix::roundCheck()
×
749
{
750
    QMutexLocker algorithmLocker(&m_algorithmMutex);
×
751
    if (m_algorithm == NULL)
×
752
        return;
753

754
    if (m_stepHandler->checkNextStep(runOrder(), m_rgbColors[0], m_rgbColors[1], m_stepsCount) == false)
×
755
        stop(FunctionParent::master());
×
756

757
    m_roundTime->restart();
×
758

759
    if (tempoType() == Beats)
×
760
        roundElapsed(m_stepBeatDuration);
×
761
    else
762
        roundElapsed(duration());
×
763
}
764

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

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

782
    return fader->getChannelFader(doc(), universe, fixtureID, channel);
×
783
}
784

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

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

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

813
        QLCFixtureHead head = fxi->head(grpHead.head);
×
814

815
        if (pt.y() >= map.count() || pt.x() >= map[pt.y()].count())
×
816
            continue;
×
817

818
        uint col = map[pt.y()][pt.x()];
×
819
        QVector<quint32> channelList;
×
820
        QVector<uchar> valueList;
×
821

822
        if (m_controlMode == ControlModeRgb)
×
823
        {
824
            channelList = head.rgbChannels();
×
825

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

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

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

876
            quint32 masterDim = fxi->masterIntensityChannel();
×
877
            quint32 headDim = head.channelNumber(QLCChannel::Intensity, QLCChannel::MSB);
×
878

879
            if (masterDim != QLCChannel::invalid())
×
880
            {
881
                channelList.append(masterDim);
×
882
                valueList.append(rgbToGrey(col));
×
883
            }
884

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

900
            valueList.append(rgbToGrey(col));
×
901
        }
902

903
        quint32 absAddress = fxi->universeAddress();
×
904

905
        for (int i = 0; i < channelList.count(); i++)
×
906
        {
907
            if (channelList.at(i) == QLCChannel::invalid())
×
908
                continue;
×
909

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

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

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

925
/*********************************************************************
926
 * Attributes
927
 *********************************************************************/
928

929
int RGBMatrix::adjustAttribute(qreal fraction, int attributeId)
×
930
{
931
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
×
932

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

942
    return attrIndex;
×
943
}
944

945
/*************************************************************************
946
 * Blend mode
947
 *************************************************************************/
948

949
void RGBMatrix::setBlendMode(Universe::BlendMode mode)
×
950
{
951
    if (mode == blendMode())
×
952
        return;
953

954
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap.values())
×
955
    {
956
        if (!fader.isNull())
×
957
            fader->setBlendMode(mode);
×
958
    }
959

960
    Function::setBlendMode(mode);
×
961
    emit changed(id());
×
962
}
963

964
/*************************************************************************
965
 * Control Mode
966
 *************************************************************************/
967

968
RGBMatrix::ControlMode RGBMatrix::controlMode() const
2✔
969
{
970
    return m_controlMode;
2✔
971
}
972

973
void RGBMatrix::setControlMode(RGBMatrix::ControlMode mode)
3✔
974
{
975
    m_controlMode = mode;
3✔
976
    emit changed(id());
3✔
977
}
3✔
978

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

994
    return ControlModeRgb;
995
}
996

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

1023

1024
/*************************************************************************
1025
 *************************************************************************
1026
 *                          RGBMatrixStep class
1027
 *************************************************************************
1028
 *************************************************************************/
1029

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

1039
}
10✔
1040

1041
void RGBMatrixStep::setCurrentStepIndex(int index)
×
1042
{
1043
    m_currentStepIndex = index;
×
1044
}
×
1045

1046
int RGBMatrixStep::currentStepIndex() const
1✔
1047
{
1048
    return m_currentStepIndex;
1✔
1049
}
1050

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

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

1063
        //qDebug() << "Color deltas:" << m_crDelta << m_cgDelta << m_cbDelta;
1064
    }
1065
}
16✔
1066

1067
void RGBMatrixStep::setStepColor(QColor color)
×
1068
{
1069
    m_stepColor = color;
×
1070
}
×
1071

1072
QColor RGBMatrixStep::stepColor()
6✔
1073
{
1074
    return m_stepColor;
6✔
1075
}
1076

1077
void RGBMatrixStep::updateStepColor(int stepIndex, QColor startColor, int stepsCount)
×
1078
{
1079
    if (stepsCount <= 0)
×
1080
        return;
1081

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

1093
    //qDebug() << "RGBMatrix step" << stepIndex << ", color:" << QString::number(m_stepColor.rgb(), 16);
1094
}
1095

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

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

1109
        if (endColor.isValid())
×
1110
            setStepColor(endColor);
×
1111
        else
1112
            setStepColor(startColor);
×
1113
    }
1114

1115
    calculateColorDelta(startColor, endColor, algorithm);
×
1116
}
×
1117

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

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

1202
    return true;
1203
}
1204

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