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

mcallegari / qlcplus / 19144422256

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

push

github

mcallegari
Back to 5.1.0 debug

17718 of 51723 relevant lines covered (34.26%)

19528.23 hits per line

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

85.46
/engine/src/efx.cpp
1
/*
2
  Q Light Controller Plus
3
  efx.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 <QVector>
22
#include <QDebug>
23
#include <QList>
24
#include <QXmlStreamReader>
25
#include <QXmlStreamWriter>
26

27
#include <math.h>
28

29
#include "genericfader.h"
30
#include "mastertimer.h"
31
#include "qlcmacros.h"
32
#include "doc.h"
33
#include "efx.h"
34
#include "bus.h"
35

36
/*****************************************************************************
37
 * Initialization
38
 *****************************************************************************/
39

40
EFX::EFX(Doc* doc)
74✔
41
    : Function(doc, Function::EFXType)
42
    , m_algorithm(EFX::Circle)
74✔
43
    , m_isRelative(false)
74✔
44
    , m_xFrequency(2)
74✔
45
    , m_yFrequency(3)
74✔
46
    , m_xPhase(M_PI / 2.0)
74✔
47
    , m_yPhase(0)
74✔
48
    , m_propagationMode(Parallel)
74✔
49
    , m_legacyFadeBus(Bus::invalid())
74✔
50
    , m_legacyHoldBus(Bus::invalid())
148✔
51
{
52
    updateRotationCache();
74✔
53
    setName(tr("New EFX"));
74✔
54
    setDuration(20000); // 20s
74✔
55

56
    registerAttribute(tr("Width"), Function::LastWins, 0.0, 127.0, 127.0);
74✔
57
    registerAttribute(tr("Height"), Function::LastWins, 0.0, 127.0, 127.0);
74✔
58
    registerAttribute(tr("Rotation"), Function::LastWins, 0.0, 359.0, 0.0);
74✔
59
    registerAttribute(tr("X Offset"), Function::LastWins, 0.0, 255.0, 127.0);
74✔
60
    registerAttribute(tr("Y Offset"), Function::LastWins, 0.0, 255.0, 127.0);
74✔
61
    registerAttribute(tr("Start Offset"), Function::LastWins, 0.0, 359.0, 0.0);
74✔
62
}
74✔
63

64
EFX::~EFX()
84✔
65
{
66
    while (m_fixtures.isEmpty() == false)
105✔
67
        delete m_fixtures.takeFirst();
31✔
68
}
84✔
69

70
QIcon EFX::getIcon() const
×
71
{
72
    return QIcon(":/efx.png");
×
73
}
74

75
/*****************************************************************************
76
 * Copying
77
 *****************************************************************************/
78

79
Function* EFX::createCopy(Doc* doc, bool addToDoc)
1✔
80
{
81
    Q_ASSERT(doc != NULL);
1✔
82

83
    Function* copy = new EFX(doc);
1✔
84
    if (copy->copyFrom(this) == false)
1✔
85
    {
86
        delete copy;
×
87
        copy = NULL;
×
88
    }
89
    if (addToDoc == true && doc->addFunction(copy) == false)
1✔
90
    {
91
        delete copy;
×
92
        copy = NULL;
×
93
    }
94

95
    return copy;
1✔
96
}
97

98
bool EFX::copyFrom(const Function* function)
4✔
99
{
100
    const EFX* efx = qobject_cast<const EFX*> (function);
4✔
101
    if (efx == NULL)
4✔
102
        return false;
1✔
103

104
    while (m_fixtures.isEmpty() == false)
5✔
105
        delete m_fixtures.takeFirst();
2✔
106

107
    QListIterator <EFXFixture*> it(efx->m_fixtures);
3✔
108
    while (it.hasNext() == true)
9✔
109
    {
110
        EFXFixture* ef = new EFXFixture(this);
6✔
111
        ef->copyFrom(it.next());
6✔
112
        m_fixtures.append(ef);
6✔
113
    }
114

115
    m_propagationMode = efx->m_propagationMode;
3✔
116

117
    for (int i = 0; i < efx->attributes().count(); i++)
24✔
118
        adjustAttribute(efx->attributes().at(i).m_value, i);
21✔
119

120
    m_isRelative = efx->m_isRelative;
3✔
121

122
    updateRotationCache();
3✔
123

124
    m_xFrequency = efx->m_xFrequency;
3✔
125
    m_yFrequency = efx->m_yFrequency;
3✔
126
    m_xPhase = efx->m_xPhase;
3✔
127
    m_yPhase = efx->m_yPhase;
3✔
128

129
    m_algorithm = efx->m_algorithm;
3✔
130

131
    return Function::copyFrom(function);
3✔
132
}
3✔
133

134
void EFX::setDuration(uint ms)
82✔
135
{
136
    Function::setDuration(ms);
82✔
137

138
    for (int i = 0; i < m_fixtures.size(); ++i)
85✔
139
        m_fixtures[i]->durationChanged();
3✔
140

141
    emit durationChanged(ms);
82✔
142
}
82✔
143

144
uint EFX::loopDuration() const
658✔
145
{
146
    uint fadeIn = overrideFadeInSpeed() == defaultSpeed() ? fadeInSpeed() : overrideFadeInSpeed();
658✔
147

148
    return duration() - fadeIn;
658✔
149
}
150

151
/*****************************************************************************
152
 * Algorithm
153
 *****************************************************************************/
154

155
EFX::Algorithm EFX::algorithm() const
22,698✔
156
{
157
    return m_algorithm;
22,698✔
158
}
159

160
void EFX::setAlgorithm(EFX::Algorithm algo)
37✔
161
{
162
    if (algo == m_algorithm)
37✔
163
        return;
×
164

165
    if (algo >= EFX::Circle && algo <= EFX::Lissajous)
37✔
166
        m_algorithm = algo;
36✔
167
    else
168
        m_algorithm = EFX::Circle;
1✔
169

170
    emit changed(this->id());
37✔
171
}
172

173
QStringList EFX::algorithmList()
1✔
174
{
175
    QStringList list;
1✔
176
    list << algorithmToString(EFX::Circle);
1✔
177
    list << algorithmToString(EFX::Eight);
1✔
178
    list << algorithmToString(EFX::Line);
1✔
179
    list << algorithmToString(EFX::Line2);
1✔
180
    list << algorithmToString(EFX::Diamond);
1✔
181
    list << algorithmToString(EFX::Square);
1✔
182
    list << algorithmToString(EFX::SquareChoppy);
1✔
183
        list << algorithmToString(EFX::SquareTrue);
1✔
184
    list << algorithmToString(EFX::Leaf);
1✔
185
    list << algorithmToString(EFX::Lissajous);
1✔
186
    return list;
1✔
187
}
×
188

189
QString EFX::algorithmToString(EFX::Algorithm algo)
12✔
190
{
191
    switch (algo)
12✔
192
    {
193
        default:
2✔
194
        case EFX::Circle:
195
            return QString(KXMLQLCEFXCircleAlgorithmName);
2✔
196
        case EFX::Eight:
1✔
197
            return QString(KXMLQLCEFXEightAlgorithmName);
1✔
198
        case EFX::Line:
1✔
199
            return QString(KXMLQLCEFXLineAlgorithmName);
1✔
200
        case EFX::Line2:
1✔
201
            return QString(KXMLQLCEFXLine2AlgorithmName);
1✔
202
        case EFX::Diamond:
1✔
203
            return QString(KXMLQLCEFXDiamondAlgorithmName);
1✔
204
        case EFX::Square:
1✔
205
            return QString(KXMLQLCEFXSquareAlgorithmName);
1✔
206
        case EFX::SquareChoppy:
1✔
207
            return QString(KXMLQLCEFXSquareChoppyAlgorithmName);
1✔
208
                 case EFX::SquareTrue:
1✔
209
            return QString(KXMLQLCEFXSquareTrueAlgorithmName);
1✔
210
        case EFX::Leaf:
1✔
211
            return QString(KXMLQLCEFXLeafAlgorithmName);
1✔
212
        case EFX::Lissajous:
2✔
213
            return QString(KXMLQLCEFXLissajousAlgorithmName);
2✔
214
    }
215
}
216

217
EFX::Algorithm EFX::stringToAlgorithm(const QString& str)
14✔
218
{
219
    if (str == QString(KXMLQLCEFXEightAlgorithmName))
14✔
220
        return EFX::Eight;
1✔
221
    else if (str == QString(KXMLQLCEFXLineAlgorithmName))
13✔
222
        return EFX::Line;
1✔
223
    else if (str == QString(KXMLQLCEFXLine2AlgorithmName))
12✔
224
        return EFX::Line2;
1✔
225
    else if (str == QString(KXMLQLCEFXDiamondAlgorithmName))
11✔
226
        return EFX::Diamond;
4✔
227
    else if (str == QString(KXMLQLCEFXSquareAlgorithmName))
7✔
228
        return EFX::Square;
1✔
229
    else if (str == QString(KXMLQLCEFXSquareChoppyAlgorithmName))
6✔
230
        return EFX::SquareChoppy;
1✔
231
         else if (str == QString(KXMLQLCEFXSquareTrueAlgorithmName))
5✔
232
        return EFX::SquareTrue;
1✔
233
    else if (str == QString(KXMLQLCEFXLeafAlgorithmName))
4✔
234
        return EFX::Leaf;
1✔
235
    else if (str == QString(KXMLQLCEFXLissajousAlgorithmName))
3✔
236
        return EFX::Lissajous;
1✔
237
    else
238
        return EFX::Circle;
2✔
239
}
240

241
void EFX::preview(QPolygonF &polygon) const
35✔
242
{
243
    preview(polygon, Function::Forward, 0);
35✔
244
}
35✔
245

246
void EFX::previewFixtures(QVector <QPolygonF>& polygons) const
×
247
{
248
    polygons.resize(m_fixtures.size());
×
249
    for (int i = 0; i < m_fixtures.size(); ++i)
×
250
    {
251
        int propagationOffset =
252
            (m_propagationMode == EFX::Asymmetric || m_propagationMode == EFX::Serial)
×
253
                ? 360 / m_fixtures.size() * i
×
254
                : 0;
×
255

256
        preview(polygons[i], m_fixtures[i]->m_direction,
×
257
                m_fixtures[i]->m_startOffset + propagationOffset);
×
258
    }
259
}
×
260

261
void EFX::preview(QPolygonF &polygon, Function::Direction direction, int startOffset) const
35✔
262
{
263
    float stepCount = 512.0;
35✔
264
    int step = 0;
35✔
265
    float stepSize = 1.0 / (stepCount / (M_PI * 2.0));
35✔
266

267
    float i = 0;
35✔
268
    float x = 0;
35✔
269
    float y = 0;
35✔
270

271
    /* Reset the polygon to fill it with new values */
272
    polygon.clear();
35✔
273

274
    /* Draw a preview of the effect */
275
    for (step = 0; step < stepCount; step++)
17,955✔
276
    {
277
        calculatePoint(direction, startOffset, i, &x, &y);
17,920✔
278
        polygon << QPointF(x, y);
17,920✔
279
        i += stepSize;
17,920✔
280
    }
281
}
35✔
282

283
void EFX::calculatePoint(Function::Direction direction, int startOffset, float iterator, float *x, float *y) const
18,072✔
284
{
285
    iterator = calculateDirection(direction, iterator);
18,072✔
286
    iterator += convertOffset(startOffset + getAttributeValue(StartOffset));
18,072✔
287

288
    if (iterator >= M_PI * 2.0)
18,072✔
289
        iterator -= M_PI * 2.0;
8✔
290

291
    calculatePoint(iterator, x, y);
18,072✔
292
}
18,072✔
293

294
void EFX::rotateAndScale(float *x, float *y) const
18,087✔
295
{
296
    float xx = *x;
18,087✔
297
    float yy = *y;
18,087✔
298
    float w = getAttributeValue(Width);
18,087✔
299
    float h = getAttributeValue(Height);
18,087✔
300
    float fadeScale = 1.0;
18,087✔
301

302
    if (isRunning())
18,087✔
303
    {
304
        uint fadeIn = overrideFadeInSpeed() == defaultSpeed() ? fadeInSpeed() : overrideFadeInSpeed();
152✔
305
        if (fadeIn > 0 && elapsed() <= fadeIn)
152✔
306
        {
307
            fadeScale = SCALE(float(elapsed()),
×
308
                              float(0), float(fadeIn),
309
                              float(0), float(1.0));
310
        }
311
    }
312

313
    *x = (getAttributeValue(XOffset)) + xx * m_cosR * (w * fadeScale) + yy * m_sinR * (h * fadeScale);
18,087✔
314
    *y = (getAttributeValue(YOffset)) + -xx * m_sinR * (w * fadeScale) + yy * m_cosR * (h * fadeScale);
18,087✔
315
}
18,087✔
316

317
float EFX::calculateDirection(Function::Direction direction, float iterator) const
18,072✔
318
{
319
    if (direction == this->direction())
18,072✔
320
        return iterator;
13,464✔
321

322
    switch (algorithm())
4,608✔
323
    {
324
    default:
4,096✔
325
    case Circle:
326
    case Eight:
327
    case Line2:
328
    case Diamond:
329
    case Square:
330
    case SquareChoppy:
331
        case SquareTrue:
332
    case Leaf:
333
    case Lissajous:
334
        return (M_PI * 2.0) - iterator;
4,096✔
335
    case Line:
512✔
336
        return (iterator > M_PI) ? (iterator - M_PI) : (iterator + M_PI);
512✔
337
    }
338
}
339

340
// this function should map from 0..M_PI * 2 -> -1..1
341
void EFX::calculatePoint(float iterator, float *x, float *y) const
18,072✔
342
{
343
    switch (algorithm())
18,072✔
344
    {
345
    default:
5,272✔
346
    case Circle:
347
        *x = cos(iterator + M_PI_2);
5,272✔
348
        *y = cos(iterator);
5,272✔
349
        break;
5,272✔
350

351
    case Eight:
1,536✔
352
        *x = cos((iterator * 2) + M_PI_2);
1,536✔
353
        *y = cos(iterator);
1,536✔
354
        break;
1,536✔
355

356
    case Line:
1,536✔
357
        *x = cos(iterator);
1,536✔
358
        *y = cos(iterator);
1,536✔
359
        break;
1,536✔
360

361
    case Line2:
1,536✔
362
        *x = iterator / M_PI - 1;
1,536✔
363
        *y = iterator / M_PI - 1;
1,536✔
364
        break;
1,536✔
365

366
    case Diamond:
1,536✔
367
        *x = pow(cos(iterator - M_PI_2), 3);
1,536✔
368
        *y = pow(cos(iterator), 3);
1,536✔
369
        break;
1,536✔
370

371
    case Square:
1,536✔
372
        if (iterator < M_PI / 2)
1,536✔
373
        {
374
            *x = (iterator * 2 / M_PI) * 2 - 1;
385✔
375
            *y = 1;
385✔
376
        }
377
        else if (M_PI / 2 <= iterator && iterator < M_PI)
1,151✔
378
        {
379
            *x = 1;
384✔
380
            *y = (1 - (iterator - M_PI / 2) * 2 / M_PI) * 2 - 1;
384✔
381
        }
382
        else if (M_PI <= iterator && iterator < M_PI * 3 / 2)
767✔
383
        {
384
            *x = (1 - (iterator - M_PI) * 2 / M_PI) * 2 - 1;
384✔
385
            *y = -1;
384✔
386
        }
387
        else // M_PI * 3 / 2 <= iterator
388
        {
389
            *x = -1;
383✔
390
            *y = ((iterator - M_PI * 3 / 2) * 2 / M_PI) * 2 - 1;
383✔
391
        }
392
        break;
1,536✔
393

394
    case SquareChoppy:
1,536✔
395
        *x = round(cos(iterator));
1,536✔
396
        *y = round(sin(iterator));
1,536✔
397
        break;
1,536✔
398
                
399
        case SquareTrue:
512✔
400
        if (iterator < M_PI / 2)
512✔
401
        {
402
            *x = 1;
128✔
403
            *y = 1;
128✔
404
        }
405
        else if (M_PI / 2 <= iterator && iterator < M_PI)
384✔
406
        {
407
            *x = 1;
128✔
408
            *y = -1;
128✔
409
        }
410
        else if (M_PI <= iterator && iterator < M_PI * 3 / 2)
256✔
411
        {
412
            *x = -1;
128✔
413
            *y = -1;
128✔
414
        }
415
        else // M_PI * 3 / 2 <= iterator
416
        {
417
            *x = -1;
128✔
418
            *y = 1;
128✔
419
        }
420
                break;
512✔
421

422
    case Leaf:
1,536✔
423
        *x = pow(cos(iterator + M_PI_2), 5);
1,536✔
424
        *y = cos(iterator);
1,536✔
425
        break;
1,536✔
426

427
    case Lissajous:
1,536✔
428
        {
429
            if (m_xFrequency > 0)
1,536✔
430
                *x = cos((m_xFrequency * iterator) - m_xPhase);
1,536✔
431
            else
432
            {
433
                float iterator0 = ((iterator + m_xPhase) / M_PI);
×
434
                int fff = iterator0;
×
435
                iterator0 -= (fff - fff % 2);
×
436
                float forward = 1 - floor(iterator0); // 1 when forward
×
437
                float backward = 1 - forward; // 1 when backward
×
438
                iterator0 = iterator0 - floor(iterator0);
×
439
                *x = (forward * iterator0 + backward * (1 - iterator0)) * 2 - 1;
×
440
            }
441
            if (m_yFrequency > 0)
1,536✔
442
                *y = cos((m_yFrequency * iterator) - m_yPhase);
1,536✔
443
            else
444
            {
445
                float iterator0 = ((iterator + m_yPhase) / M_PI);
×
446
                int fff = iterator0;
×
447
                iterator0 -= (fff - fff % 2);
×
448
                float forward = 1 - floor(iterator0); // 1 when forward
×
449
                float backward = 1 - forward; // 1 when backward
×
450
                iterator0 = iterator0 - floor(iterator0);
×
451
                *y = (forward * iterator0 + backward * (1 - iterator0)) * 2 - 1;
×
452
            }
453
        }
454
        break;
1,536✔
455
    }
456

457
    rotateAndScale(x, y);
18,072✔
458
}
18,072✔
459

460
/*****************************************************************************
461
 * Width
462
 *****************************************************************************/
463

464
void EFX::setWidth(int width)
28✔
465
{
466
    adjustAttribute(static_cast<double> (CLAMP(width, 0, 127)), Width);
28✔
467
    emit changed(this->id());
28✔
468
}
28✔
469

470
int EFX::width() const
13✔
471
{
472
    return static_cast<int> (attributes().at(Width).m_value);
13✔
473
}
474

475
/*****************************************************************************
476
 * Height
477
 *****************************************************************************/
478

479
void EFX::setHeight(int height)
28✔
480
{
481
    adjustAttribute(static_cast<double> (CLAMP(height, 0, 127)), Height);
28✔
482
    emit changed(this->id());
28✔
483
}
28✔
484

485
int EFX::height() const
13✔
486
{
487
    return static_cast<int> (attributes().at(Height).m_value);
13✔
488
}
489

490
/*****************************************************************************
491
 * Rotation
492
 *****************************************************************************/
493

494
void EFX::setRotation(int rot)
27✔
495
{
496
    adjustAttribute(CLAMP(rot, 0, 359), Rotation);
27✔
497
    updateRotationCache();
27✔
498
    emit changed(this->id());
27✔
499
}
27✔
500

501
int EFX::rotation() const
13✔
502
{
503
    return static_cast<int> (attributes().at(Rotation).m_value);
13✔
504
}
505

506
void EFX::updateRotationCache()
206✔
507
{
508
    double r = M_PI/180 * getAttributeValue(Rotation);
206✔
509
    m_cosR = cos(r);
206✔
510
    m_sinR = sin(r);
206✔
511
}
206✔
512

513
/*****************************************************************************
514
 * Start Offset
515
 *****************************************************************************/
516

517
void EFX::setStartOffset(int startOffset)
1✔
518
{
519
    adjustAttribute(CLAMP(startOffset, 0, 359), StartOffset);
1✔
520
    emit changed(this->id());
1✔
521
}
1✔
522

523
int EFX::startOffset() const
2✔
524
{
525
    return static_cast<int> (attributes().at(StartOffset).m_value);
2✔
526
}
527

528
float EFX::convertOffset(int offset) const
18,072✔
529
{
530
    return M_PI/180 * (offset % 360);
18,072✔
531
}
532

533
/*****************************************************************************
534
 * Is Relative
535
 *****************************************************************************/
536

537
void EFX::setIsRelative(bool isRelative)
1✔
538
{
539
    m_isRelative = isRelative;
1✔
540
    emit changed(this->id());
1✔
541
}
1✔
542

543
bool EFX::isRelative() const
312✔
544
{
545
    return m_isRelative;
312✔
546
}
547

548
/*****************************************************************************
549
 * Offset
550
 *****************************************************************************/
551

552
void EFX::setXOffset(int offset)
29✔
553
{
554
    adjustAttribute(static_cast<double> (CLAMP(offset, 0, (int)UCHAR_MAX)), XOffset);
29✔
555
    emit changed(this->id());
29✔
556
}
29✔
557

558
int EFX::xOffset() const
14✔
559
{
560
    return static_cast<int> (attributes().at(XOffset).m_value);
14✔
561
}
562

563
void EFX::setYOffset(int offset)
29✔
564
{
565
    adjustAttribute(static_cast<double> (CLAMP(offset, 0, (int)UCHAR_MAX)), YOffset);
29✔
566
    emit changed(this->id());
29✔
567
}
29✔
568

569
int EFX::yOffset() const
13✔
570
{
571
    return static_cast<int> (attributes().at(YOffset).m_value);
13✔
572
}
573

574
/*****************************************************************************
575
 * Frequency
576
 *****************************************************************************/
577

578
void EFX::setXFrequency(int freq)
18✔
579
{
580
    m_xFrequency = static_cast<float> (CLAMP(freq, 0, 32));
18✔
581
    emit changed(this->id());
18✔
582
}
18✔
583

584
int EFX::xFrequency() const
19✔
585
{
586
    return static_cast<int> (m_xFrequency);
19✔
587
}
588

589
void EFX::setYFrequency(int freq)
18✔
590
{
591
    m_yFrequency = static_cast<float> (CLAMP(freq, 0, 32));
18✔
592
    emit changed(this->id());
18✔
593
}
18✔
594

595
int EFX::yFrequency() const
18✔
596
{
597
    return static_cast<int> (m_yFrequency);
18✔
598
}
599

600
bool EFX::isFrequencyEnabled()
5✔
601
{
602
    if (m_algorithm == EFX::Lissajous)
5✔
603
        return true;
2✔
604
    else
605
        return false;
3✔
606
}
607

608
/*****************************************************************************
609
 * Phase
610
 *****************************************************************************/
611

612
void EFX::setXPhase(int phase)
20✔
613
{
614
    m_xPhase = static_cast<float> (CLAMP(phase, 0, 359)) * M_PI / 180.0;
20✔
615
    emit changed(this->id());
20✔
616
}
20✔
617

618
int EFX::xPhase() const
21✔
619
{
620
    return static_cast<int> (floor((m_xPhase * 180.0 / M_PI) + 0.5));
21✔
621
}
622

623
void EFX::setYPhase(int phase)
20✔
624
{
625
    m_yPhase = static_cast<float> (CLAMP(phase, 0, 359)) * M_PI / 180.0;
20✔
626
    emit changed(this->id());
20✔
627
}
20✔
628

629
int EFX::yPhase() const
20✔
630
{
631
    return static_cast<int> (floor((m_yPhase * 180.0 / M_PI) + 0.5));
20✔
632
}
633

634
bool EFX::isPhaseEnabled() const
5✔
635
{
636
    if (m_algorithm == EFX::Lissajous)
5✔
637
        return true;
2✔
638
    else
639
        return false;
3✔
640
}
641

642
/*****************************************************************************
643
 * Fixtures
644
 *****************************************************************************/
645

646
bool EFX::addFixture(EFXFixture* ef)
32✔
647
{
648
    Q_ASSERT(ef != NULL);
32✔
649

650
    /* Search for an existing fixture with the same ID and append at last but do
651
     * not prevent multiple entries because a fixture can have multiple efx. */
652
    //! @todo Prevent multiple entries using head & mode
653
    int i;
654
    for (i = 0; i < m_fixtures.size (); i++)
60✔
655
    {
656
        if (m_fixtures[i]->head() == ef->head())
28✔
657
        {
658
            m_fixtures.insert(i, ef);
×
659
            break;
×
660
        }
661
    }
662

663
    /* If not inserted, put the EFXFixture object into our list */
664
    if (i >= m_fixtures.size())
32✔
665
        m_fixtures.append(ef);
32✔
666

667
    emit changed(this->id());
32✔
668

669
    return true;
32✔
670
}
671

672
bool EFX::addFixture(quint32 fxi, int head)
×
673
{
674
    EFXFixture *ef = new EFXFixture(this);
×
675
    GroupHead gHead(fxi, head);
×
676
    ef->setHead(gHead);
×
677

678
    return addFixture(ef);
×
679
}
×
680

681
bool EFX::removeFixture(EFXFixture* ef)
2✔
682
{
683
    Q_ASSERT(ef != NULL);
2✔
684

685
    if (m_fixtures.removeAll(ef) > 0)
2✔
686
    {
687
        emit changed(this->id());
1✔
688
        return true;
1✔
689
    }
690
    else
691
    {
692
        return false;
1✔
693
    }
694
}
695

696
bool EFX::removeFixture(quint32 fxi, int head)
2✔
697
{
698
    for (int i = 0; i < m_fixtures.count(); i++)
2✔
699
    {
700
        EFXFixture *ef = m_fixtures.at(i);
1✔
701
        if (ef->head().fxi == fxi && ef->head().head == head)
1✔
702
        {
703
            m_fixtures.removeAt(i);
1✔
704
            return true;
1✔
705
        }
706
    }
707

708
    return false;
1✔
709
}
710

711
void EFX::removeAllFixtures()
×
712
{
713
    m_fixtures.clear();
×
714
    emit changed(this->id());
×
715
}
×
716

717
bool EFX::raiseFixture(EFXFixture* ef)
3✔
718
{
719
    Q_ASSERT(ef != NULL);
3✔
720

721
    int index = m_fixtures.indexOf(ef);
3✔
722
    if (index > 0)
3✔
723
    {
724
        m_fixtures.move(index, index - 1);
1✔
725
        emit changed(this->id());
1✔
726
        return true;
1✔
727
    }
728
    else
729
    {
730
        return false;
2✔
731
    }
732
}
733

734
bool EFX::lowerFixture(EFXFixture* ef)
3✔
735
{
736
    int index = m_fixtures.indexOf(ef);
3✔
737
    if (index < (m_fixtures.count() - 1))
3✔
738
    {
739
        m_fixtures.move(index, index + 1);
1✔
740
        emit changed(this->id());
1✔
741
        return true;
1✔
742
    }
743
    else
744
    {
745
        return false;
2✔
746
    }
747
}
748

749
const QList <EFXFixture*> EFX::fixtures() const
77✔
750
{
751
    return m_fixtures;
77✔
752
}
753

754
EFXFixture *EFX::fixture(quint32 id, int headIndex)
4✔
755
{
756
    foreach (EFXFixture *ef, m_fixtures)
10✔
757
    {
758
        if (ef->head().fxi == id && ef->head().head == headIndex)
9✔
759
            return ef;
3✔
760
    }
4✔
761

762
    return NULL;
1✔
763
}
764

765
QList<quint32> EFX::components()
×
766
{
767
    QList<quint32> ids;
×
768

769
    foreach (EFXFixture *ef, m_fixtures)
×
770
    {
771
        if (ids.contains(ef->head().fxi) == false)
×
772
            ids.append(ef->head().fxi);
×
773
    }
×
774

775
    return ids;
×
776
}
×
777

778
void EFX::slotFixtureRemoved(quint32 fxi_id)
5✔
779
{
780
    /* Remove the destroyed fixture from our list */
781
    QMutableListIterator <EFXFixture*> it(m_fixtures);
5✔
782
    while (it.hasNext() == true)
11✔
783
    {
784
        it.next();
9✔
785

786
        if (it.value()->head().fxi == fxi_id)
9✔
787
        {
788
            delete it.value();
3✔
789
            it.remove();
3✔
790
            break;
3✔
791
        }
792
    }
793
}
5✔
794

795
/*****************************************************************************
796
 * Fixture propagation mode
797
 *****************************************************************************/
798

799
void EFX::setPropagationMode(PropagationMode mode)
9✔
800
{
801
    m_propagationMode = mode;
9✔
802
    emit changed(this->id());
9✔
803
}
9✔
804

805
EFX::PropagationMode EFX::propagationMode() const
465✔
806
{
807
    return m_propagationMode;
465✔
808
}
809

810
QString EFX::propagationModeToString(PropagationMode mode)
6✔
811
{
812
    if (mode == Serial)
6✔
813
        return QString(KXMLQLCEFXPropagationModeSerial);
2✔
814
    else if (mode == Asymmetric)
4✔
815
        return QString(KXMLQLCEFXPropagationModeAsymmetric);
1✔
816
    else
817
        return QString(KXMLQLCEFXPropagationModeParallel);
3✔
818
}
819

820
EFX::PropagationMode EFX::stringToPropagationMode(QString str)
7✔
821
{
822
    if (str == QString(KXMLQLCEFXPropagationModeSerial))
7✔
823
        return Serial;
4✔
824
    else if (str == QString(KXMLQLCEFXPropagationModeAsymmetric))
3✔
825
        return Asymmetric;
1✔
826
    else
827
        return Parallel;
2✔
828
}
829

830
/*****************************************************************************
831
 * Load & Save
832
 *****************************************************************************/
833

834
bool EFX::saveXML(QXmlStreamWriter *doc)
2✔
835
{
836
    Q_ASSERT(doc != NULL);
2✔
837

838
    /* Function tag */
839
    doc->writeStartElement(KXMLQLCFunction);
4✔
840

841
    /* Common attributes */
842
    saveXMLCommon(doc);
2✔
843

844
    /* Fixtures */
845
    QListIterator <EFXFixture*> it(m_fixtures);
2✔
846
    while (it.hasNext() == true)
5✔
847
        it.next()->saveXML(doc);
3✔
848

849
    /* Propagation mode */
850
    doc->writeTextElement(KXMLQLCEFXPropagationMode, propagationModeToString(m_propagationMode));
4✔
851

852
    /* Tempo type */
853
    saveXMLTempoType(doc);
2✔
854
    /* Speeds */
855
    saveXMLSpeed(doc);
2✔
856
    /* Direction */
857
    saveXMLDirection(doc);
2✔
858
    /* Run order */
859
    saveXMLRunOrder(doc);
2✔
860

861
    /* Algorithm */
862
    doc->writeTextElement(KXMLQLCEFXAlgorithm, algorithmToString(algorithm()));
4✔
863
    /* Width */
864
    doc->writeTextElement(KXMLQLCEFXWidth, QString::number(width()));
4✔
865
    /* Height */
866
    doc->writeTextElement(KXMLQLCEFXHeight, QString::number(height()));
4✔
867
    /* Rotation */
868
    doc->writeTextElement(KXMLQLCEFXRotation, QString::number(rotation()));
4✔
869
    /* StartOffset */
870
    doc->writeTextElement(KXMLQLCEFXStartOffset, QString::number(startOffset()));
4✔
871
    /* IsRelative */
872
    doc->writeTextElement(KXMLQLCEFXIsRelative, QString::number(isRelative() ? 1 : 0));
4✔
873

874
    /********************************************
875
     * X-Axis
876
     ********************************************/
877
    doc->writeStartElement(KXMLQLCEFXAxis);
4✔
878
    doc->writeAttribute(KXMLQLCFunctionName, KXMLQLCEFXX);
6✔
879

880
    /* Offset */
881
    doc->writeTextElement(KXMLQLCEFXOffset, QString::number(xOffset()));
4✔
882
    /* Frequency */
883
    doc->writeTextElement(KXMLQLCEFXFrequency, QString::number(xFrequency()));
4✔
884
    /* Phase */
885
    doc->writeTextElement(KXMLQLCEFXPhase, QString::number(xPhase()));
4✔
886

887
    /* End the (X) <Axis> tag */
888
    doc->writeEndElement();
2✔
889

890
    /********************************************
891
     * Y-Axis
892
     ********************************************/
893
    doc->writeStartElement(KXMLQLCEFXAxis);
4✔
894
    doc->writeAttribute(KXMLQLCFunctionName, KXMLQLCEFXY);
6✔
895

896
    /* Offset */
897
    doc->writeTextElement(KXMLQLCEFXOffset, QString::number(yOffset()));
4✔
898
    /* Frequency */
899
    doc->writeTextElement(KXMLQLCEFXFrequency, QString::number(yFrequency()));
4✔
900
    /* Phase */
901
    doc->writeTextElement(KXMLQLCEFXPhase, QString::number(yPhase()));
4✔
902

903
    /* End the (Y) <Axis> tag */
904
    doc->writeEndElement();
2✔
905

906
    /* End the <Function> tag */
907
    doc->writeEndElement();
2✔
908

909
    return true;
2✔
910
}
2✔
911

912
bool EFX::loadXML(QXmlStreamReader &root)
5✔
913
{
914
    if (root.name() != KXMLQLCFunction)
5✔
915
    {
916
        qWarning() << "Function node not found!";
1✔
917
        return false;
1✔
918
    }
919

920
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::EFXType))
8✔
921
    {
922
        qWarning("Function is not an EFX!");
1✔
923
        return false;
1✔
924
    }
925

926
    /* Load EFX contents */
927
    while (root.readNextStartElement())
45✔
928
    {
929
        if (root.name() == KXMLQLCBus)
42✔
930
        {
931
            /* Bus */
932
            QString str = root.attributes().value(KXMLQLCBusRole).toString();
6✔
933
            if (str == KXMLQLCBusFade)
3✔
934
                m_legacyFadeBus = root.readElementText().toUInt();
2✔
935
            else if (str == KXMLQLCBusHold)
1✔
936
                m_legacyHoldBus = root.readElementText().toUInt();
1✔
937
        }
3✔
938
        else if (root.name() == KXMLQLCFunctionSpeed)
39✔
939
        {
940
            loadXMLSpeed(root);
1✔
941
        }
942
        else if (root.name() == KXMLQLCFunctionTempoType)
38✔
943
        {
944
            loadXMLTempoType(root);
×
945
        }
946
        else if (root.name() == KXMLQLCEFXFixture)
38✔
947
        {
948
            EFXFixture* ef = new EFXFixture(this);
9✔
949
            ef->loadXML(root);
9✔
950
            if (ef->head().isValid())
9✔
951
            {
952
                if (addFixture(ef) == false)
9✔
953
                    delete ef;
×
954
            }
955
        }
956
        else if (root.name() == KXMLQLCEFXPropagationMode)
29✔
957
        {
958
            /* Propagation mode */
959
            setPropagationMode(stringToPropagationMode(root.readElementText()));
3✔
960
        }
961
        else if (root.name() == KXMLQLCEFXAlgorithm)
26✔
962
        {
963
            /* Algorithm */
964
            setAlgorithm(stringToAlgorithm(root.readElementText()));
3✔
965
        }
966
        else if (root.name() == KXMLQLCFunctionDirection)
23✔
967
        {
968
            loadXMLDirection(root);
3✔
969
        }
970
        else if (root.name() == KXMLQLCFunctionRunOrder)
20✔
971
        {
972
            loadXMLRunOrder(root);
3✔
973
        }
974
        else if (root.name() == KXMLQLCEFXWidth)
17✔
975
        {
976
            /* Width */
977
            setWidth(root.readElementText().toInt());
3✔
978
        }
979
        else if (root.name() == KXMLQLCEFXHeight)
14✔
980
        {
981
            /* Height */
982
            setHeight(root.readElementText().toInt());
3✔
983
        }
984
        else if (root.name() == KXMLQLCEFXRotation)
11✔
985
        {
986
            /* Rotation */
987
            setRotation(root.readElementText().toInt());
3✔
988
        }
989
        else if (root.name() == KXMLQLCEFXStartOffset)
8✔
990
        {
991
            /* StartOffset */
992
            setStartOffset(root.readElementText().toInt());
×
993
        }
994
        else if (root.name() == KXMLQLCEFXIsRelative)
8✔
995
        {
996
            /* IsRelative */
997
            setIsRelative(root.readElementText().toInt() != 0);
×
998
        }
999
        else if (root.name() == KXMLQLCEFXAxis)
8✔
1000
        {
1001
            /* Axes */
1002
            loadXMLAxis(root);
6✔
1003
        }
1004
        else
1005
        {
1006
            qWarning() << "Unknown EFX tag:" << root.name();
2✔
1007
            root.skipCurrentElement();
2✔
1008
        }
1009
    }
1010

1011
    return true;
3✔
1012
}
1013

1014
bool EFX::loadXMLAxis(QXmlStreamReader &root)
10✔
1015
{
1016
    int frequency = 0;
10✔
1017
    int offset = 0;
10✔
1018
    int phase = 0;
10✔
1019
    QString axis;
10✔
1020

1021
    if (root.name() != KXMLQLCEFXAxis)
10✔
1022
    {
1023
        qWarning() << "EFX axis node not found!";
1✔
1024
        return false;
1✔
1025
    }
1026

1027
    /* Get the axis name */
1028
    axis = root.attributes().value(KXMLQLCFunctionName).toString();
18✔
1029

1030
    /* Load axis contents */
1031
    while (root.readNextStartElement())
37✔
1032
    {
1033
        if (root.name() == KXMLQLCEFXOffset)
28✔
1034
            offset = root.readElementText().toInt();
9✔
1035
        else if (root.name() == KXMLQLCEFXFrequency)
19✔
1036
            frequency = root.readElementText().toInt();
9✔
1037
        else if (root.name() == KXMLQLCEFXPhase)
10✔
1038
            phase = root.readElementText().toInt();
9✔
1039
        else
1040
        {
1041
            qWarning() << "Unknown EFX axis tag:" << root.name();
1✔
1042
            root.skipCurrentElement();
1✔
1043
        }
1044
    }
1045

1046
    if (axis == KXMLQLCEFXY)
9✔
1047
    {
1048
        setYOffset(offset);
4✔
1049
        setYFrequency(frequency);
4✔
1050
        setYPhase(phase);
4✔
1051
        return true;
4✔
1052
    }
1053
    else if (axis == KXMLQLCEFXX)
5✔
1054
    {
1055
        setXOffset(offset);
4✔
1056
        setXFrequency(frequency);
4✔
1057
        setXPhase(phase);
4✔
1058
        return true;
4✔
1059
    }
1060
    else
1061
    {
1062
        qWarning() << "Unknown EFX axis:" << axis;
1✔
1063
        return false;
1✔
1064
    }
1065
}
10✔
1066

1067
void EFX::postLoad()
1✔
1068
{
1069
    // Map legacy bus speeds to fixed speed values
1070
    if (m_legacyFadeBus != Bus::invalid())
1✔
1071
    {
1072
        quint32 value = Bus::instance()->value(m_legacyFadeBus);
1✔
1073
        setFadeInSpeed((value / MasterTimer::frequency()) * 1000);
1✔
1074
        setFadeOutSpeed((value / MasterTimer::frequency()) * 1000);
1✔
1075
    }
1076

1077
    if (m_legacyHoldBus != Bus::invalid())
1✔
1078
    {
1079
        quint32 value = Bus::instance()->value(m_legacyHoldBus);
1✔
1080
        setDuration((value / MasterTimer::frequency()) * 1000);
1✔
1081
    }
1082
}
1✔
1083

1084
/*****************************************************************************
1085
 * Running
1086
 *****************************************************************************/
1087
QSharedPointer<GenericFader> EFX::getFader(QList<Universe *> universes, quint32 universeID)
×
1088
{
1089
    // get the universe Fader first. If doesn't exist, create it
1090
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universeID, QSharedPointer<GenericFader>());
×
1091
    if (fader.isNull())
×
1092
    {
1093
        fader = universes[universeID]->requestFader(isRelative() ? Universe::Override : Universe::Auto);
×
1094
        fader->adjustIntensity(getAttributeValue(Intensity));
×
1095
        fader->setBlendMode(blendMode());
×
1096
        fader->setName(name());
×
1097
        fader->setParentFunctionID(id());
×
1098
        fader->setHandleSecondary(true);
×
1099
        m_fadersMap[universeID] = fader;
×
1100
    }
1101

1102
    return fader;
×
1103
}
×
1104

1105
void EFX::preRun(MasterTimer* timer)
5✔
1106
{
1107
    int serialNumber = 0;
5✔
1108

1109
    QListIterator <EFXFixture*> it(m_fixtures);
5✔
1110
    while (it.hasNext() == true)
10✔
1111
    {
1112
        EFXFixture *ef = it.next();
5✔
1113
        Q_ASSERT(ef != NULL);
5✔
1114
        ef->setSerialNumber(serialNumber++);
5✔
1115
    }
1116

1117
    Function::preRun(timer);
5✔
1118
}
5✔
1119

1120
void EFX::write(MasterTimer *timer, QList<Universe*> universes)
×
1121
{
1122
    Q_UNUSED(timer);
1123

1124
    if (isPaused())
×
1125
        return;
×
1126

1127
    int done = 0;
×
1128

1129
    QListIterator <EFXFixture*> it(m_fixtures);
×
1130
    while (it.hasNext() == true)
×
1131
    {
1132
        EFXFixture *ef = it.next();
×
1133
        if (ef->isDone() == false)
×
1134
        {
1135
            QSharedPointer<GenericFader> fader = getFader(universes, ef->universe());
×
1136
            ef->nextStep(universes, fader);
×
1137
        }
×
1138
        else
1139
        {
1140
            done++;
×
1141
        }
1142
    }
1143

1144
    incrementElapsed();
×
1145

1146
    /* Check for stop condition */
1147
    if (done == m_fixtures.count())
×
1148
        stop(FunctionParent::master());
×
1149
}
×
1150

1151
void EFX::postRun(MasterTimer *timer, QList<Universe *> universes)
5✔
1152
{
1153
    /* Reset all fixtures */
1154
    QListIterator <EFXFixture*> it(m_fixtures);
5✔
1155
    while (it.hasNext() == true)
10✔
1156
    {
1157
        EFXFixture* ef(it.next());
5✔
1158

1159
        /* Run the EFX's stop scene for Loop & PingPong modes */
1160
        if (runOrder() != SingleShot)
5✔
1161
            ef->stop();
4✔
1162
        ef->reset();
5✔
1163
    }
1164

1165
    dismissAllFaders();
5✔
1166

1167
    Function::postRun(timer, universes);
5✔
1168
}
5✔
1169

1170
/*****************************************************************************
1171
 * Intensity
1172
 *****************************************************************************/
1173

1174
int EFX::adjustAttribute(qreal fraction, int attributeId)
165✔
1175
{
1176
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
165✔
1177

1178
    switch (attrIndex)
165✔
1179
    {
1180
        case Intensity:
2✔
1181
        {
1182
            foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
2✔
1183
            {
1184
                if (!fader.isNull())
×
1185
                    fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
1186
            }
2✔
1187
        }
1188
        break;
2✔
1189

1190
        case Height:
102✔
1191
        case Width:
1192
        case XOffset:
1193
        case YOffset:
1194
        case Rotation:
1195
            updateRotationCache();
102✔
1196
        break;
102✔
1197
    }
1198

1199
    return attrIndex;
165✔
1200
}
1201

1202
/*************************************************************************
1203
 * Blend mode
1204
 *************************************************************************/
1205

1206
void EFX::setBlendMode(Universe::BlendMode mode)
1✔
1207
{
1208
    if (mode == blendMode())
1✔
1209
        return;
1✔
1210

1211
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
1212
    {
1213
        if (!fader.isNull())
×
1214
            fader->setBlendMode(mode);
×
1215
    }
×
1216

1217
    Function::setBlendMode(mode);
×
1218
}
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