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

mcallegari / qlcplus / 28530951808

01 Jul 2026 04:03PM UTC coverage: 35.287% (+0.01%) from 35.277%
28530951808

push

github

mcallegari
engine: fix EFX test unit

18560 of 52598 relevant lines covered (35.29%)

40989.4 hits per line

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

85.58
/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)
75✔
41
    : Function(doc, Function::EFXType)
42
    , m_algorithm(EFX::Circle)
75✔
43
    , m_dimmerControl(false)
75✔
44
    , m_isRelative(false)
75✔
45
    , m_xFrequency(2)
75✔
46
    , m_yFrequency(3)
75✔
47
    , m_xPhase(M_PI / 2.0)
75✔
48
    , m_yPhase(0)
75✔
49
    , m_propagationMode(Parallel)
75✔
50
    , m_legacyFadeBus(Bus::invalid())
75✔
51
    , m_legacyHoldBus(Bus::invalid())
150✔
52
{
53
    updateRotationCache();
75✔
54
    setName(tr("New EFX"));
75✔
55
    setDuration(20000); // 20s
75✔
56

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

65
EFX::~EFX()
85✔
66
{
67
    while (m_fixtures.isEmpty() == false)
106✔
68
        delete m_fixtures.takeFirst();
31✔
69
}
85✔
70

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

76
/*****************************************************************************
77
 * Copying
78
 *****************************************************************************/
79

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

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

96
    return copy;
1✔
97
}
98

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

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

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

116
    m_propagationMode = efx->m_propagationMode;
3✔
117

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

121
    m_isRelative = efx->m_isRelative;
3✔
122

123
    updateRotationCache();
3✔
124

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

130
    m_algorithm = efx->m_algorithm;
3✔
131
    m_dimmerControl = efx->m_dimmerControl;
3✔
132

133
    return Function::copyFrom(function);
3✔
134
}
3✔
135

136
void EFX::setDuration(uint ms)
83✔
137
{
138
    Function::setDuration(ms);
83✔
139

140
    for (int i = 0; i < m_fixtures.size(); ++i)
86✔
141
        m_fixtures[i]->durationChanged();
3✔
142

143
    emit durationChanged(ms);
83✔
144
}
83✔
145

146
uint EFX::loopDuration() const
810✔
147
{
148
    uint fadeIn = overrideFadeInSpeed() == defaultSpeed() ? fadeInSpeed() : overrideFadeInSpeed();
810✔
149

150
    return duration() - fadeIn;
810✔
151
}
152

153
/*****************************************************************************
154
 * Algorithm
155
 *****************************************************************************/
156

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

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

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

172
    emit changed(this->id());
37✔
173
}
174

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

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

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

243
bool EFX::dimmerControlEnabled() const
163✔
244
{
245
    return m_dimmerControl;
163✔
246
}
247

248
void EFX::setDimmerControlEnabled(bool enable)
1✔
249
{
250
    m_dimmerControl = enable;
1✔
251
}
1✔
252

253
void EFX::preview(QPolygonF &polygon) const
35✔
254
{
255
    preview(polygon, Function::Forward, 0);
35✔
256
}
35✔
257

258
void EFX::previewFixtures(QVector <QPolygonF>& polygons) const
×
259
{
260
    polygons.resize(m_fixtures.size());
×
261
    for (int i = 0; i < m_fixtures.size(); ++i)
×
262
    {
263
        int propagationOffset =
264
            (m_propagationMode == EFX::Asymmetric || m_propagationMode == EFX::Serial)
×
265
                ? 360 / m_fixtures.size() * i
×
266
                : 0;
×
267

268
        preview(polygons[i], m_fixtures[i]->m_direction,
×
269
                m_fixtures[i]->m_startOffset + propagationOffset);
×
270
    }
271
}
×
272

273
void EFX::preview(QPolygonF &polygon, Function::Direction direction, int startOffset) const
35✔
274
{
275
    float stepCount = 512.0;
35✔
276
    int step = 0;
35✔
277
    float stepSize = 1.0 / (stepCount / (M_PI * 2.0));
35✔
278

279
    float i = 0;
35✔
280
    float x = 0;
35✔
281
    float y = 0;
35✔
282

283
    /* Reset the polygon to fill it with new values */
284
    polygon.clear();
35✔
285

286
    /* Draw a preview of the effect */
287
    for (step = 0; step < stepCount; step++)
17,955✔
288
    {
289
        calculatePoint(direction, startOffset, i, &x, &y);
17,920✔
290
        polygon << QPointF(x, y);
17,920✔
291
        i += stepSize;
17,920✔
292
    }
293
}
35✔
294

295
void EFX::calculatePoint(Function::Direction direction, int startOffset, float iterator, float *x, float *y) const
18,072✔
296
{
297
    iterator = calculateDirection(direction, iterator);
18,072✔
298
    iterator += convertOffset(startOffset + getAttributeValue(StartOffset));
18,072✔
299

300
    if (iterator >= M_PI * 2.0)
18,072✔
301
        iterator -= M_PI * 2.0;
8✔
302

303
    calculatePoint(iterator, x, y);
18,072✔
304
}
18,072✔
305

306
void EFX::rotateAndScale(float *x, float *y) const
18,087✔
307
{
308
    float xx = *x;
18,087✔
309
    float yy = *y;
18,087✔
310
    float w = getAttributeValue(Width);
18,087✔
311
    float h = getAttributeValue(Height);
18,087✔
312
    float fadeScale = 1.0;
18,087✔
313

314
    if (isRunning())
18,087✔
315
    {
316
        uint fadeIn = overrideFadeInSpeed() == defaultSpeed() ? fadeInSpeed() : overrideFadeInSpeed();
152✔
317
        if (fadeIn > 0 && elapsed() <= fadeIn)
152✔
318
        {
319
            fadeScale = SCALE(float(elapsed()),
×
320
                              float(0), float(fadeIn),
321
                              float(0), float(1.0));
322
        }
323
    }
324

325
    *x = (getAttributeValue(XOffset)) + xx * m_cosR * (w * fadeScale) + yy * m_sinR * (h * fadeScale);
18,087✔
326
    *y = (getAttributeValue(YOffset)) + -xx * m_sinR * (w * fadeScale) + yy * m_cosR * (h * fadeScale);
18,087✔
327
}
18,087✔
328

329
float EFX::calculateDirection(Function::Direction direction, float iterator) const
18,072✔
330
{
331
    if (direction == this->direction())
18,072✔
332
        return iterator;
13,464✔
333

334
    switch (algorithm())
4,608✔
335
    {
336
    default:
4,096✔
337
    case Circle:
338
    case Eight:
339
    case Line2:
340
    case Diamond:
341
    case Square:
342
    case SquareChoppy:
343
    case SquareTrue:
344
    case Leaf:
345
    case Lissajous:
346
        return (M_PI * 2.0) - iterator;
4,096✔
347
    case Line:
512✔
348
        return (iterator > M_PI) ? (iterator - M_PI) : (iterator + M_PI);
512✔
349
    }
350
}
351

352
float EFX::dimmerLevel(float startOffset, float iterator) const
8✔
353
{
354
    /* The dimmer follows the tilt itself, so it fades at the same RATE as the
355
       movement (not linearly), reaching full/0 exactly when the tilt reaches its
356
       extremes. The angle is phased per fixture by its StartOffset (both in
357
       radians). Over the first half [0, PI) the tilt goes from its +1 extreme to
358
       its -1 extreme; the second half [PI, 2PI) (the return stroke) stays black.
359
       The EFX Direction is handled by the movement itself, so it is not applied
360
       here. */
361
    const float PI = float(M_PI);
8✔
362
    const float TWO_PI = float(M_PI * 2.0);
8✔
363

364
    float angle = iterator + startOffset;
8✔
365
    angle = fmodf(angle, TWO_PI);
8✔
366
    if (angle < 0.0f)
8✔
367
        angle += TWO_PI;
×
368

369
    if (angle >= PI)
8✔
370
        return 0.0f; // return stroke: black
4✔
371

372
    /* cos(angle): +1 at angle 0 (tilt extreme) -> -1 at angle PI (other extreme).
373
       Map to 0..1 so brightness tracks the tilt curve: full at +1, dark at -1. */
374
    return (cosf(angle) + 1.0f) * 0.5f;
4✔
375
}
376

377
// this function should map from 0..M_PI * 2 -> -1..1
378
void EFX::calculatePoint(float iterator, float *x, float *y) const
18,072✔
379
{
380
    switch (algorithm())
18,072✔
381
    {
382
    default:
5,272✔
383
    case Circle:
384
        *x = cos(iterator + M_PI_2);
5,272✔
385
        *y = cos(iterator);
5,272✔
386
        break;
5,272✔
387

388
    case Eight:
1,536✔
389
        *x = cos((iterator * 2) + M_PI_2);
1,536✔
390
        *y = cos(iterator);
1,536✔
391
        break;
1,536✔
392

393
    case Line:
1,536✔
394
        *x = cos(iterator);
1,536✔
395
        *y = cos(iterator);
1,536✔
396
        break;
1,536✔
397

398
    case Line2:
1,536✔
399
        *x = iterator / M_PI - 1;
1,536✔
400
        *y = iterator / M_PI - 1;
1,536✔
401
        break;
1,536✔
402

403
    case Diamond:
1,536✔
404
        *x = pow(cos(iterator - M_PI_2), 3);
1,536✔
405
        *y = pow(cos(iterator), 3);
1,536✔
406
        break;
1,536✔
407

408
    case Square:
1,536✔
409
        if (iterator < M_PI / 2)
1,536✔
410
        {
411
            *x = (iterator * 2 / M_PI) * 2 - 1;
385✔
412
            *y = 1;
385✔
413
        }
414
        else if (M_PI / 2 <= iterator && iterator < M_PI)
1,151✔
415
        {
416
            *x = 1;
384✔
417
            *y = (1 - (iterator - M_PI / 2) * 2 / M_PI) * 2 - 1;
384✔
418
        }
419
        else if (M_PI <= iterator && iterator < M_PI * 3 / 2)
767✔
420
        {
421
            *x = (1 - (iterator - M_PI) * 2 / M_PI) * 2 - 1;
384✔
422
            *y = -1;
384✔
423
        }
424
        else // M_PI * 3 / 2 <= iterator
425
        {
426
            *x = -1;
383✔
427
            *y = ((iterator - M_PI * 3 / 2) * 2 / M_PI) * 2 - 1;
383✔
428
        }
429
        break;
1,536✔
430

431
    case SquareChoppy:
1,536✔
432
        *x = round(cos(iterator));
1,536✔
433
        *y = round(sin(iterator));
1,536✔
434
        break;
1,536✔
435

436
    case SquareTrue:
512✔
437
        if (iterator < M_PI / 2)
512✔
438
        {
439
            *x = 1;
128✔
440
            *y = 1;
128✔
441
        }
442
        else if (M_PI / 2 <= iterator && iterator < M_PI)
384✔
443
        {
444
            *x = 1;
128✔
445
            *y = -1;
128✔
446
        }
447
        else if (M_PI <= iterator && iterator < M_PI * 3 / 2)
256✔
448
        {
449
            *x = -1;
128✔
450
            *y = -1;
128✔
451
        }
452
        else // M_PI * 3 / 2 <= iterator
453
        {
454
            *x = -1;
128✔
455
            *y = 1;
128✔
456
        }
457
        break;
512✔
458

459
    case Leaf:
1,536✔
460
        *x = pow(cos(iterator + M_PI_2), 5);
1,536✔
461
        *y = cos(iterator);
1,536✔
462
        break;
1,536✔
463

464
    case Lissajous:
1,536✔
465
        {
466
            if (m_xFrequency > 0)
1,536✔
467
                *x = cos((m_xFrequency * iterator) - m_xPhase);
1,536✔
468
            else
469
            {
470
                float iterator0 = ((iterator + m_xPhase) / M_PI);
×
471
                int fff = iterator0;
×
472
                iterator0 -= (fff - fff % 2);
×
473
                float forward = 1 - floor(iterator0); // 1 when forward
×
474
                float backward = 1 - forward; // 1 when backward
×
475
                iterator0 = iterator0 - floor(iterator0);
×
476
                *x = (forward * iterator0 + backward * (1 - iterator0)) * 2 - 1;
×
477
            }
478
            if (m_yFrequency > 0)
1,536✔
479
                *y = cos((m_yFrequency * iterator) - m_yPhase);
1,536✔
480
            else
481
            {
482
                float iterator0 = ((iterator + m_yPhase) / M_PI);
×
483
                int fff = iterator0;
×
484
                iterator0 -= (fff - fff % 2);
×
485
                float forward = 1 - floor(iterator0); // 1 when forward
×
486
                float backward = 1 - forward; // 1 when backward
×
487
                iterator0 = iterator0 - floor(iterator0);
×
488
                *y = (forward * iterator0 + backward * (1 - iterator0)) * 2 - 1;
×
489
            }
490
        }
491
        break;
1,536✔
492
    }
493

494
    rotateAndScale(x, y);
18,072✔
495
}
18,072✔
496

497
/*****************************************************************************
498
 * Width
499
 *****************************************************************************/
500

501
void EFX::setWidth(int width)
28✔
502
{
503
    adjustAttribute(static_cast<double> (CLAMP(width, 0, 127)), Width);
28✔
504
    emit changed(this->id());
28✔
505
}
28✔
506

507
int EFX::width() const
13✔
508
{
509
    return static_cast<int> (attributes().at(Width).m_value);
13✔
510
}
511

512
/*****************************************************************************
513
 * Height
514
 *****************************************************************************/
515

516
void EFX::setHeight(int height)
28✔
517
{
518
    adjustAttribute(static_cast<double> (CLAMP(height, 0, 127)), Height);
28✔
519
    emit changed(this->id());
28✔
520
}
28✔
521

522
int EFX::height() const
13✔
523
{
524
    return static_cast<int> (attributes().at(Height).m_value);
13✔
525
}
526

527
/*****************************************************************************
528
 * Rotation
529
 *****************************************************************************/
530

531
void EFX::setRotation(int rot)
27✔
532
{
533
    adjustAttribute(CLAMP(rot, 0, 359), Rotation);
27✔
534
    updateRotationCache();
27✔
535
    emit changed(this->id());
27✔
536
}
27✔
537

538
int EFX::rotation() const
13✔
539
{
540
    return static_cast<int> (attributes().at(Rotation).m_value);
13✔
541
}
542

543
void EFX::updateRotationCache()
207✔
544
{
545
    double r = M_PI/180 * getAttributeValue(Rotation);
207✔
546
    m_cosR = cos(r);
207✔
547
    m_sinR = sin(r);
207✔
548
}
207✔
549

550
/*****************************************************************************
551
 * Start Offset
552
 *****************************************************************************/
553

554
void EFX::setStartOffset(int startOffset)
1✔
555
{
556
    adjustAttribute(CLAMP(startOffset, 0, 359), StartOffset);
1✔
557
    emit changed(this->id());
1✔
558
}
1✔
559

560
int EFX::startOffset() const
2✔
561
{
562
    return static_cast<int> (attributes().at(StartOffset).m_value);
2✔
563
}
564

565
float EFX::convertOffset(int offset) const
18,072✔
566
{
567
    return M_PI/180 * (offset % 360);
18,072✔
568
}
569

570
/*****************************************************************************
571
 * Is Relative
572
 *****************************************************************************/
573

574
void EFX::setIsRelative(bool isRelative)
1✔
575
{
576
    m_isRelative = isRelative;
1✔
577
    emit changed(this->id());
1✔
578
}
1✔
579

580
bool EFX::isRelative() const
312✔
581
{
582
    return m_isRelative;
312✔
583
}
584

585
/*****************************************************************************
586
 * Offset
587
 *****************************************************************************/
588

589
void EFX::setXOffset(int offset)
29✔
590
{
591
    adjustAttribute(static_cast<double> (CLAMP(offset, 0, (int)UCHAR_MAX)), XOffset);
29✔
592
    emit changed(this->id());
29✔
593
}
29✔
594

595
int EFX::xOffset() const
14✔
596
{
597
    return static_cast<int> (attributes().at(XOffset).m_value);
14✔
598
}
599

600
void EFX::setYOffset(int offset)
29✔
601
{
602
    adjustAttribute(static_cast<double> (CLAMP(offset, 0, (int)UCHAR_MAX)), YOffset);
29✔
603
    emit changed(this->id());
29✔
604
}
29✔
605

606
int EFX::yOffset() const
13✔
607
{
608
    return static_cast<int> (attributes().at(YOffset).m_value);
13✔
609
}
610

611
/*****************************************************************************
612
 * Frequency
613
 *****************************************************************************/
614

615
void EFX::setXFrequency(int freq)
18✔
616
{
617
    m_xFrequency = static_cast<float> (CLAMP(freq, 0, 32));
18✔
618
    emit changed(this->id());
18✔
619
}
18✔
620

621
int EFX::xFrequency() const
19✔
622
{
623
    return static_cast<int> (m_xFrequency);
19✔
624
}
625

626
void EFX::setYFrequency(int freq)
18✔
627
{
628
    m_yFrequency = static_cast<float> (CLAMP(freq, 0, 32));
18✔
629
    emit changed(this->id());
18✔
630
}
18✔
631

632
int EFX::yFrequency() const
18✔
633
{
634
    return static_cast<int> (m_yFrequency);
18✔
635
}
636

637
bool EFX::isFrequencyEnabled() const
5✔
638
{
639
    if (m_algorithm == EFX::Lissajous)
5✔
640
        return true;
2✔
641
    else
642
        return false;
3✔
643
}
644

645
/*****************************************************************************
646
 * Phase
647
 *****************************************************************************/
648

649
void EFX::setXPhase(int phase)
20✔
650
{
651
    m_xPhase = static_cast<float> (CLAMP(phase, 0, 359)) * M_PI / 180.0;
20✔
652
    emit changed(this->id());
20✔
653
}
20✔
654

655
int EFX::xPhase() const
21✔
656
{
657
    return static_cast<int> (floor((m_xPhase * 180.0 / M_PI) + 0.5));
21✔
658
}
659

660
void EFX::setYPhase(int phase)
20✔
661
{
662
    m_yPhase = static_cast<float> (CLAMP(phase, 0, 359)) * M_PI / 180.0;
20✔
663
    emit changed(this->id());
20✔
664
}
20✔
665

666
int EFX::yPhase() const
20✔
667
{
668
    return static_cast<int> (floor((m_yPhase * 180.0 / M_PI) + 0.5));
20✔
669
}
670

671
bool EFX::isPhaseEnabled() const
5✔
672
{
673
    if (m_algorithm == EFX::Lissajous)
5✔
674
        return true;
2✔
675
    else
676
        return false;
3✔
677
}
678

679
/*****************************************************************************
680
 * Fixtures
681
 *****************************************************************************/
682

683
bool EFX::addFixture(EFXFixture* ef)
32✔
684
{
685
    Q_ASSERT(ef != NULL);
32✔
686

687
    /* Search for an existing fixture with the same ID and append at last but do
688
     * not prevent multiple entries because a fixture can have multiple efx. */
689
    //! @todo Prevent multiple entries using head & mode
690
    int i;
691
    for (i = 0; i < m_fixtures.size(); i++)
60✔
692
    {
693
        if (m_fixtures[i]->head() == ef->head())
28✔
694
        {
695
            m_fixtures.insert(i, ef);
×
696
            break;
×
697
        }
698
    }
699

700
    /* If not inserted, put the EFXFixture object into our list */
701
    if (i >= m_fixtures.size())
32✔
702
        m_fixtures.append(ef);
32✔
703

704
    emit changed(this->id());
32✔
705

706
    return true;
32✔
707
}
708

709
bool EFX::addFixture(quint32 fxi, int head)
×
710
{
711
    EFXFixture *ef = new EFXFixture(this);
×
712
    GroupHead gHead(fxi, head);
×
713
    ef->setHead(gHead);
×
714

715
    return addFixture(ef);
×
716
}
×
717

718
bool EFX::removeFixture(EFXFixture* ef)
2✔
719
{
720
    Q_ASSERT(ef != NULL);
2✔
721

722
    if (m_fixtures.removeAll(ef) > 0)
2✔
723
    {
724
        emit changed(this->id());
1✔
725
        return true;
1✔
726
    }
727
    else
728
    {
729
        return false;
1✔
730
    }
731
}
732

733
bool EFX::removeFixture(quint32 fxi, int head)
2✔
734
{
735
    for (int i = 0; i < m_fixtures.count(); i++)
2✔
736
    {
737
        EFXFixture *ef = m_fixtures.at(i);
1✔
738
        if (ef->head().fxi == fxi && ef->head().head == head)
1✔
739
        {
740
            m_fixtures.removeAt(i);
1✔
741
            return true;
1✔
742
        }
743
    }
744

745
    return false;
1✔
746
}
747

748
void EFX::removeAllFixtures()
×
749
{
750
    m_fixtures.clear();
×
751
    emit changed(this->id());
×
752
}
×
753

754
bool EFX::raiseFixture(EFXFixture* ef)
3✔
755
{
756
    Q_ASSERT(ef != NULL);
3✔
757

758
    int index = m_fixtures.indexOf(ef);
3✔
759
    if (index > 0)
3✔
760
    {
761
        m_fixtures.move(index, index - 1);
1✔
762
        emit changed(this->id());
1✔
763
        return true;
1✔
764
    }
765
    else
766
    {
767
        return false;
2✔
768
    }
769
}
770

771
bool EFX::lowerFixture(EFXFixture* ef)
3✔
772
{
773
    int index = m_fixtures.indexOf(ef);
3✔
774
    if (index < (m_fixtures.count() - 1))
3✔
775
    {
776
        m_fixtures.move(index, index + 1);
1✔
777
        emit changed(this->id());
1✔
778
        return true;
1✔
779
    }
780
    else
781
    {
782
        return false;
2✔
783
    }
784
}
785

786
const QList <EFXFixture*> EFX::fixtures() const
77✔
787
{
788
    return m_fixtures;
77✔
789
}
790

791
EFXFixture *EFX::fixture(quint32 id, int headIndex)
4✔
792
{
793
    foreach (EFXFixture *ef, m_fixtures)
10✔
794
    {
795
        if (ef->head().fxi == id && ef->head().head == headIndex)
9✔
796
            return ef;
3✔
797
    }
4✔
798

799
    return NULL;
1✔
800
}
801

802
QList<quint32> EFX::components() const
×
803
{
804
    QList<quint32> ids;
×
805

806
    foreach (EFXFixture *ef, m_fixtures)
×
807
    {
808
        if (ids.contains(ef->head().fxi) == false)
×
809
            ids.append(ef->head().fxi);
×
810
    }
×
811

812
    return ids;
×
813
}
×
814

815
void EFX::slotFixtureRemoved(quint32 fxi_id)
5✔
816
{
817
    /* Remove the destroyed fixture from our list */
818
    QMutableListIterator <EFXFixture*> it(m_fixtures);
5✔
819
    while (it.hasNext() == true)
11✔
820
    {
821
        it.next();
9✔
822

823
        if (it.value()->head().fxi == fxi_id)
9✔
824
        {
825
            delete it.value();
3✔
826
            it.remove();
3✔
827
            break;
3✔
828
        }
829
    }
830
}
5✔
831

832
/*****************************************************************************
833
 * Fixture propagation mode
834
 *****************************************************************************/
835

836
void EFX::setPropagationMode(PropagationMode mode)
9✔
837
{
838
    m_propagationMode = mode;
9✔
839
    emit changed(this->id());
9✔
840
}
9✔
841

842
EFX::PropagationMode EFX::propagationMode() const
465✔
843
{
844
    return m_propagationMode;
465✔
845
}
846

847
QString EFX::propagationModeToString(PropagationMode mode)
6✔
848
{
849
    if (mode == Serial)
6✔
850
        return QString(KXMLQLCEFXPropagationModeSerial);
2✔
851
    else if (mode == Asymmetric)
4✔
852
        return QString(KXMLQLCEFXPropagationModeAsymmetric);
1✔
853
    else
854
        return QString(KXMLQLCEFXPropagationModeParallel);
3✔
855
}
856

857
EFX::PropagationMode EFX::stringToPropagationMode(const QString& str)
7✔
858
{
859
    if (str == QString(KXMLQLCEFXPropagationModeSerial))
7✔
860
        return Serial;
4✔
861
    else if (str == QString(KXMLQLCEFXPropagationModeAsymmetric))
3✔
862
        return Asymmetric;
1✔
863
    else
864
        return Parallel;
2✔
865
}
866

867
/*****************************************************************************
868
 * Load & Save
869
 *****************************************************************************/
870

871
bool EFX::saveXML(QXmlStreamWriter *doc) const
2✔
872
{
873
    Q_ASSERT(doc != NULL);
2✔
874

875
    /* Function tag */
876
    doc->writeStartElement(KXMLQLCFunction);
4✔
877

878
    /* Common attributes */
879
    saveXMLCommon(doc);
2✔
880

881
    /* Fixtures */
882
    QListIterator <EFXFixture*> it(m_fixtures);
2✔
883
    while (it.hasNext() == true)
5✔
884
        it.next()->saveXML(doc);
3✔
885

886
    /* Propagation mode */
887
    doc->writeTextElement(KXMLQLCEFXPropagationMode, propagationModeToString(m_propagationMode));
4✔
888

889
    /* Tempo type */
890
    saveXMLTempoType(doc);
2✔
891
    /* Speeds */
892
    saveXMLSpeed(doc);
2✔
893
    /* Direction */
894
    saveXMLDirection(doc);
2✔
895
    /* Run order */
896
    saveXMLRunOrder(doc);
2✔
897

898
    /* Algorithm */
899
    doc->writeTextElement(KXMLQLCEFXAlgorithm, algorithmToString(algorithm()));
4✔
900
    /* Width */
901
    doc->writeTextElement(KXMLQLCEFXWidth, QString::number(width()));
4✔
902
    /* Height */
903
    doc->writeTextElement(KXMLQLCEFXHeight, QString::number(height()));
4✔
904
    /* Rotation */
905
    doc->writeTextElement(KXMLQLCEFXRotation, QString::number(rotation()));
4✔
906
    /* StartOffset */
907
    doc->writeTextElement(KXMLQLCEFXStartOffset, QString::number(startOffset()));
4✔
908
    /* IsRelative */
909
    doc->writeTextElement(KXMLQLCEFXIsRelative, QString::number(isRelative() ? 1 : 0));
4✔
910
    /* DimmerControl */
911
    doc->writeTextElement(KXMLQLCEFXDimmerControl, QString::number(dimmerControlEnabled() ? 1 : 0));
4✔
912

913
    /********************************************
914
     * X-Axis
915
     ********************************************/
916
    doc->writeStartElement(KXMLQLCEFXAxis);
4✔
917
    doc->writeAttribute(KXMLQLCFunctionName, KXMLQLCEFXX);
6✔
918

919
    /* Offset */
920
    doc->writeTextElement(KXMLQLCEFXOffset, QString::number(xOffset()));
4✔
921
    /* Frequency */
922
    doc->writeTextElement(KXMLQLCEFXFrequency, QString::number(xFrequency()));
4✔
923
    /* Phase */
924
    doc->writeTextElement(KXMLQLCEFXPhase, QString::number(xPhase()));
4✔
925

926
    /* End the (X) <Axis> tag */
927
    doc->writeEndElement();
2✔
928

929
    /********************************************
930
     * Y-Axis
931
     ********************************************/
932
    doc->writeStartElement(KXMLQLCEFXAxis);
4✔
933
    doc->writeAttribute(KXMLQLCFunctionName, KXMLQLCEFXY);
6✔
934

935
    /* Offset */
936
    doc->writeTextElement(KXMLQLCEFXOffset, QString::number(yOffset()));
4✔
937
    /* Frequency */
938
    doc->writeTextElement(KXMLQLCEFXFrequency, QString::number(yFrequency()));
4✔
939
    /* Phase */
940
    doc->writeTextElement(KXMLQLCEFXPhase, QString::number(yPhase()));
4✔
941

942
    /* End the (Y) <Axis> tag */
943
    doc->writeEndElement();
2✔
944

945
    /* End the <Function> tag */
946
    doc->writeEndElement();
2✔
947

948
    return true;
2✔
949
}
2✔
950

951
bool EFX::loadXML(QXmlStreamReader &root)
5✔
952
{
953
    if (root.name() != KXMLQLCFunction)
5✔
954
    {
955
        qWarning() << "Function node not found!";
1✔
956
        return false;
1✔
957
    }
958

959
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::EFXType))
8✔
960
    {
961
        qWarning("Function is not an EFX!");
1✔
962
        return false;
1✔
963
    }
964

965
    /* Load EFX contents */
966
    while (root.readNextStartElement())
45✔
967
    {
968
        if (root.name() == KXMLQLCBus)
42✔
969
        {
970
            /* Bus */
971
            QString str = root.attributes().value(KXMLQLCBusRole).toString();
6✔
972
            if (str == KXMLQLCBusFade)
3✔
973
                m_legacyFadeBus = root.readElementText().toUInt();
2✔
974
            else if (str == KXMLQLCBusHold)
1✔
975
                m_legacyHoldBus = root.readElementText().toUInt();
1✔
976
        }
3✔
977
        else if (root.name() == KXMLQLCFunctionSpeed)
39✔
978
        {
979
            loadXMLSpeed(root);
1✔
980
        }
981
        else if (root.name() == KXMLQLCFunctionTempoType)
38✔
982
        {
983
            loadXMLTempoType(root);
×
984
        }
985
        else if (root.name() == KXMLQLCEFXFixture)
38✔
986
        {
987
            EFXFixture* ef = new EFXFixture(this);
9✔
988
            ef->loadXML(root);
9✔
989
            if (ef->head().isValid())
9✔
990
            {
991
                if (addFixture(ef) == false)
9✔
992
                    delete ef;
×
993
            }
994
        }
995
        else if (root.name() == KXMLQLCEFXPropagationMode)
29✔
996
        {
997
            /* Propagation mode */
998
            setPropagationMode(stringToPropagationMode(root.readElementText()));
3✔
999
        }
1000
        else if (root.name() == KXMLQLCEFXAlgorithm)
26✔
1001
        {
1002
            /* Algorithm */
1003
            setAlgorithm(stringToAlgorithm(root.readElementText()));
3✔
1004
        }
1005
        else if (root.name() == KXMLQLCFunctionDirection)
23✔
1006
        {
1007
            loadXMLDirection(root);
3✔
1008
        }
1009
        else if (root.name() == KXMLQLCFunctionRunOrder)
20✔
1010
        {
1011
            loadXMLRunOrder(root);
3✔
1012
        }
1013
        else if (root.name() == KXMLQLCEFXWidth)
17✔
1014
        {
1015
            /* Width */
1016
            setWidth(root.readElementText().toInt());
3✔
1017
        }
1018
        else if (root.name() == KXMLQLCEFXHeight)
14✔
1019
        {
1020
            /* Height */
1021
            setHeight(root.readElementText().toInt());
3✔
1022
        }
1023
        else if (root.name() == KXMLQLCEFXRotation)
11✔
1024
        {
1025
            /* Rotation */
1026
            setRotation(root.readElementText().toInt());
3✔
1027
        }
1028
        else if (root.name() == KXMLQLCEFXStartOffset)
8✔
1029
        {
1030
            /* StartOffset */
1031
            setStartOffset(root.readElementText().toInt());
×
1032
        }
1033
        else if (root.name() == KXMLQLCEFXIsRelative)
8✔
1034
        {
1035
            /* IsRelative */
1036
            setIsRelative(root.readElementText().toInt() != 0);
×
1037
        }
1038
        else if (root.name() == KXMLQLCEFXDimmerControl)
8✔
1039
        {
1040
            /* DimmerControl */
1041
            setDimmerControlEnabled(root.readElementText().toInt() != 0);
×
1042
        }
1043
        else if (root.name() == QStringLiteral("DimmerWidth") ||
24✔
1044
                 root.name() == QStringLiteral("DimmerAttack") ||
24✔
1045
                 root.name() == QStringLiteral("DimmerDecay") ||
24✔
1046
                 root.name() == QStringLiteral("DimmerShape") ||
40✔
1047
                 root.name() == QStringLiteral("DimmerOrder"))
16✔
1048
        {
1049
            /* LEGACY: dimmer sub-parameters that no longer exist. Skip silently. */
1050
            root.skipCurrentElement();
×
1051
        }
1052
        else if (root.name() == KXMLQLCEFXAxis)
8✔
1053
        {
1054
            /* Axes */
1055
            loadXMLAxis(root);
6✔
1056
        }
1057
        else
1058
        {
1059
            qWarning() << "Unknown EFX tag:" << root.name();
2✔
1060
            root.skipCurrentElement();
2✔
1061
        }
1062
    }
1063

1064
    return true;
3✔
1065
}
1066

1067
bool EFX::loadXMLAxis(QXmlStreamReader &root)
10✔
1068
{
1069
    int frequency = 0;
10✔
1070
    int offset = 0;
10✔
1071
    int phase = 0;
10✔
1072
    QString axis;
10✔
1073

1074
    if (root.name() != KXMLQLCEFXAxis)
10✔
1075
    {
1076
        qWarning() << "EFX axis node not found!";
1✔
1077
        return false;
1✔
1078
    }
1079

1080
    /* Get the axis name */
1081
    axis = root.attributes().value(KXMLQLCFunctionName).toString();
18✔
1082

1083
    /* Load axis contents */
1084
    while (root.readNextStartElement())
37✔
1085
    {
1086
        if (root.name() == KXMLQLCEFXOffset)
28✔
1087
            offset = root.readElementText().toInt();
9✔
1088
        else if (root.name() == KXMLQLCEFXFrequency)
19✔
1089
            frequency = root.readElementText().toInt();
9✔
1090
        else if (root.name() == KXMLQLCEFXPhase)
10✔
1091
            phase = root.readElementText().toInt();
9✔
1092
        else
1093
        {
1094
            qWarning() << "Unknown EFX axis tag:" << root.name();
1✔
1095
            root.skipCurrentElement();
1✔
1096
        }
1097
    }
1098

1099
    if (axis == KXMLQLCEFXY)
9✔
1100
    {
1101
        setYOffset(offset);
4✔
1102
        setYFrequency(frequency);
4✔
1103
        setYPhase(phase);
4✔
1104
        return true;
4✔
1105
    }
1106
    else if (axis == KXMLQLCEFXX)
5✔
1107
    {
1108
        setXOffset(offset);
4✔
1109
        setXFrequency(frequency);
4✔
1110
        setXPhase(phase);
4✔
1111
        return true;
4✔
1112
    }
1113
    else
1114
    {
1115
        qWarning() << "Unknown EFX axis:" << axis;
1✔
1116
        return false;
1✔
1117
    }
1118
}
10✔
1119

1120
void EFX::postLoad()
1✔
1121
{
1122
    // Map legacy bus speeds to fixed speed values
1123
    if (m_legacyFadeBus != Bus::invalid())
1✔
1124
    {
1125
        quint32 value = Bus::instance()->value(m_legacyFadeBus);
1✔
1126
        setFadeInSpeed((value / MasterTimer::frequency()) * 1000);
1✔
1127
        setFadeOutSpeed((value / MasterTimer::frequency()) * 1000);
1✔
1128
    }
1129

1130
    if (m_legacyHoldBus != Bus::invalid())
1✔
1131
    {
1132
        quint32 value = Bus::instance()->value(m_legacyHoldBus);
1✔
1133
        setDuration((value / MasterTimer::frequency()) * 1000);
1✔
1134
    }
1135
}
1✔
1136

1137
/*****************************************************************************
1138
 * Running
1139
 *****************************************************************************/
1140
QSharedPointer<GenericFader> EFX::getFader(QList<Universe *> universes, quint32 universeID)
×
1141
{
1142
    // get the universe Fader first. If doesn't exist, create it
1143
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universeID, QSharedPointer<GenericFader>());
×
1144
    if (fader.isNull())
×
1145
    {
1146
        fader = universes[universeID]->requestFader(isRelative() ? Universe::Override : Universe::Auto);
×
1147
        fader->adjustIntensity(getAttributeValue(Intensity));
×
1148
        fader->setBlendMode(blendMode());
×
1149
        fader->setName(name());
×
1150
        fader->setParentFunctionID(id());
×
1151
        fader->setHandleSecondary(true);
×
1152
        m_fadersMap[universeID] = fader;
×
1153
    }
1154

1155
    return fader;
×
1156
}
×
1157

1158
void EFX::preRun(MasterTimer* timer)
5✔
1159
{
1160
    int serialNumber = 0;
5✔
1161

1162
    QListIterator <EFXFixture*> it(m_fixtures);
5✔
1163
    while (it.hasNext() == true)
10✔
1164
    {
1165
        EFXFixture *ef = it.next();
5✔
1166
        Q_ASSERT(ef != NULL);
5✔
1167
        ef->setSerialNumber(serialNumber++);
5✔
1168
    }
1169

1170
    Function::preRun(timer);
5✔
1171
}
5✔
1172

1173
void EFX::write(MasterTimer *timer, QList<Universe*> universes)
×
1174
{
1175
    Q_UNUSED(timer);
1176

1177
    if (isPaused())
×
1178
        return;
×
1179

1180
    int done = 0;
×
1181

1182
    QListIterator <EFXFixture*> it(m_fixtures);
×
1183
    while (it.hasNext() == true)
×
1184
    {
1185
        EFXFixture *ef = it.next();
×
1186
        if (ef->isDone() == false)
×
1187
        {
1188
            QSharedPointer<GenericFader> fader = getFader(universes, ef->universe());
×
1189
            ef->nextStep(universes, fader);
×
1190
        }
×
1191
        else
1192
        {
1193
            done++;
×
1194
        }
1195
    }
1196

1197
    incrementElapsed();
×
1198

1199
    /* Check for stop condition */
1200
    if (done == m_fixtures.count())
×
1201
        stop(FunctionParent::master());
×
1202
}
×
1203

1204
void EFX::postRun(MasterTimer *timer, QList<Universe *> universes)
5✔
1205
{
1206
    /* Reset all fixtures */
1207
    QListIterator <EFXFixture*> it(m_fixtures);
5✔
1208
    while (it.hasNext() == true)
10✔
1209
    {
1210
        EFXFixture* ef(it.next());
5✔
1211

1212
        /* Run the EFX's stop scene for Loop & PingPong modes */
1213
        if (runOrder() != SingleShot)
5✔
1214
            ef->stop();
4✔
1215
        ef->reset();
5✔
1216
    }
1217

1218
    dismissAllFaders();
5✔
1219

1220
    Function::postRun(timer, universes);
5✔
1221
}
5✔
1222

1223
/*****************************************************************************
1224
 * Intensity
1225
 *****************************************************************************/
1226

1227
int EFX::adjustAttribute(qreal fraction, int attributeId)
165✔
1228
{
1229
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
165✔
1230

1231
    switch (attrIndex)
165✔
1232
    {
1233
        case Intensity:
2✔
1234
        {
1235
            foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
2✔
1236
            {
1237
                if (!fader.isNull())
×
1238
                    fader->adjustIntensity(getAttributeValue(Function::Intensity));
×
1239
            }
2✔
1240
        }
1241
        break;
2✔
1242

1243
        case Height:
102✔
1244
        case Width:
1245
        case XOffset:
1246
        case YOffset:
1247
        case Rotation:
1248
            updateRotationCache();
102✔
1249
        break;
102✔
1250
    }
1251

1252
    return attrIndex;
165✔
1253
}
1254

1255
/*************************************************************************
1256
 * Blend mode
1257
 *************************************************************************/
1258

1259
void EFX::setBlendMode(Universe::BlendMode mode)
1✔
1260
{
1261
    if (mode == blendMode())
1✔
1262
        return;
1✔
1263

1264
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
×
1265
    {
1266
        if (!fader.isNull())
×
1267
            fader->setBlendMode(mode);
×
1268
    }
×
1269

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