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

mcallegari / qlcplus / 7252848206

18 Dec 2023 07:26PM UTC coverage: 32.067% (+0.001%) from 32.066%
7252848206

push

github

mcallegari
Code style review #1427

199 of 628 new or added lines in 101 files covered. (31.69%)

8 existing lines in 2 files now uncovered.

15169 of 47304 relevant lines covered (32.07%)

23733.74 hits per line

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

68.01
/engine/src/efxfixture.cpp
1
/*
2
  Q Light Controller Plus
3
  efxfixture.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 <QDebug>
24
#include <math.h>
25

26
#include "genericfader.h"
27
#include "fadechannel.h"
28
#include "mastertimer.h"
29
#include "efxfixture.h"
30
#include "qlcmacros.h"
31
#include "function.h"
32
#include "universe.h"
33
#include "gradient.h"
34
#include "efx.h"
35
#include "doc.h"
36

37
/*****************************************************************************
38
 * Initialization
39
 *****************************************************************************/
40
QImage EFXFixture::m_rgbGradient = QImage();
41

42
EFXFixture::EFXFixture(const EFX* parent)
54✔
43
    : m_parent(parent)
44
    , m_head()
45
    , m_universe(Universe::invalid())
54✔
46
    , m_direction(Function::Forward)
47
    , m_startOffset(0)
48
    , m_mode(EFXFixture::PanTilt)
49

50
    , m_serialNumber(0)
51
    , m_runTimeDirection(Function::Forward)
52
    , m_done(false)
53
    , m_started(false)
54
    , m_elapsed(0)
55
    , m_currentAngle(0)
54✔
56
{
57
    Q_ASSERT(parent != NULL);
54✔
58

59
    if (m_rgbGradient.isNull ())
54✔
60
        m_rgbGradient = Gradient::getRGBGradient (256, 256);
3✔
61
}
54✔
62

63
void EFXFixture::copyFrom(const EFXFixture* ef)
7✔
64
{
65
    // Don't copy m_parent because it is already assigned in constructor and might
66
    // be different than $ef's
67
    m_head = ef->m_head;
7✔
68
    m_universe = ef->m_universe;
7✔
69
    m_direction = ef->m_direction;
7✔
70
    m_startOffset = ef->m_startOffset;
7✔
71
    m_mode = ef->m_mode;
7✔
72

73
    m_serialNumber = ef->m_serialNumber;
7✔
74
    m_runTimeDirection = ef->m_runTimeDirection;
7✔
75
    m_done = ef->m_done;
7✔
76
    m_started = ef->m_started;
7✔
77
    m_elapsed = ef->m_elapsed;
7✔
78
    m_currentAngle = ef->m_currentAngle;
7✔
79
}
7✔
80

81
EFXFixture::~EFXFixture()
53✔
82
{
83
}
53✔
84

85
/****************************************************************************
86
 * Public properties
87
 ****************************************************************************/
88

89
void EFXFixture::setHead(GroupHead const & head)
44✔
90
{
91
    m_head = head;
44✔
92

93
    Fixture *fxi = doc()->fixture(head.fxi);
44✔
94
    if (fxi == NULL)
44✔
95
        return;
30✔
96

97
    m_universe = fxi->universe();
14✔
98

99
    QList<Mode> modes;
28✔
100

101
    if (fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, head.head) != QLCChannel::invalid() ||
16✔
102
        fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, head.head) != QLCChannel::invalid())
2✔
103
        modes << PanTilt;
14✔
104

105
    if (fxi->masterIntensityChannel() != QLCChannel::invalid() ||
16✔
106
        fxi->channelNumber(QLCChannel::Intensity, QLCChannel::MSB, head.head) != QLCChannel::invalid())
2✔
107
        modes << Dimmer;
12✔
108

109
    if (fxi->rgbChannels(head.head).size() >= 3)
14✔
110
        modes << RGB;
2✔
111

112
    if (!modes.contains(m_mode))
14✔
113
    {
114
        if (modes.size() > 0)
×
115
            m_mode = modes[0];
×
116
    }
117
}
118

119
GroupHead const & EFXFixture::head() const
1,374✔
120
{
121
    return m_head;
1,374✔
122
}
123

124
void EFXFixture::setDirection(Function::Direction dir)
18✔
125
{
126
    m_direction = dir;
18✔
127
    m_runTimeDirection = dir;
18✔
128
}
18✔
129

130
Function::Direction EFXFixture::direction() const
13✔
131
{
132
    return m_direction;
13✔
133
}
134

135
void EFXFixture::setStartOffset(int startOffset)
5✔
136
{
137
    m_startOffset = CLAMP(startOffset, 0, 359);
5✔
138
}
5✔
139

140
int EFXFixture::startOffset() const
9✔
141
{
142
    return m_startOffset;
9✔
143
}
144

145
void EFXFixture::setMode(Mode mode)
×
146
{
147
    m_mode = mode;
×
148
}
×
149

150
EFXFixture::Mode EFXFixture::mode() const
4✔
151
{
152
    return m_mode;
4✔
153
}
154

155
quint32 EFXFixture::universe()
156✔
156
{
157
    return m_universe;
156✔
158
}
159

160
bool EFXFixture::isValid() const
158✔
161
{
162
    Fixture *fxi = doc()->fixture(head().fxi);
158✔
163

164
    if (fxi == NULL)
158✔
165
        return false;
2✔
166
    else if (head().head >= fxi->heads())
156✔
167
        return false;
×
168
    else if (m_mode == PanTilt &&
468✔
169
             fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, head().head) == QLCChannel::invalid() && // Maybe a device can pan OR tilt
156✔
170
             fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, head().head) == QLCChannel::invalid())   // but not both
×
171
        return false;
×
172
    else if (m_mode == Dimmer &&
312✔
173
             fxi->masterIntensityChannel() == QLCChannel::invalid() &&
156✔
174
             fxi->channelNumber(QLCChannel::Intensity, QLCChannel::MSB, head().head) == QLCChannel::invalid())
×
175
        return false;
×
176
    else if (m_mode == RGB && fxi->rgbChannels(head().head).size () == 0)
156✔
177
        return false;
×
178
    else
179
        return true;
156✔
180
}
181

182
void EFXFixture::durationChanged()
3✔
183
{
184
    // To avoid jumps when changing duration,
185
    // the elapsed time is rescaled to the
186
    // new duration.
187
    m_elapsed = SCALE(float(m_currentAngle),
3✔
188
            float(0), float(M_PI * 2),
189
            float(0), float(m_parent->loopDuration()));
190

191
    // Serial or Asymmetric propagation mode:
192
    // we must substract the offset from the current position
193
    if (timeOffset())
3✔
194
    {
195
        if (m_elapsed < timeOffset())
×
196
            m_elapsed += m_parent->loopDuration();
×
197
        m_elapsed -= timeOffset();
×
198
    }
199
}
3✔
200

201
QStringList EFXFixture::modeList()
×
202
{
203
    Fixture* fxi = doc()->fixture(head().fxi);
×
204
    Q_ASSERT(fxi != NULL);
×
205

206
    QStringList modes;
×
207

NEW
208
    if (fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, head().head) != QLCChannel::invalid() ||
×
209
       fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, head().head) != QLCChannel::invalid())
×
210
        modes << KXMLQLCEFXFixtureModePanTilt;
×
211

NEW
212
    if (fxi->masterIntensityChannel() != QLCChannel::invalid() ||
×
213
       fxi->channelNumber(QLCChannel::Intensity, QLCChannel::MSB, head().head) != QLCChannel::invalid())
×
214
        modes << KXMLQLCEFXFixtureModeDimmer;
×
215

NEW
216
    if (fxi->rgbChannels(head().head).size() >= 3)
×
217
        modes << KXMLQLCEFXFixtureModeRGB;
×
218

219
    return modes;
×
220
}
221

222
QString EFXFixture::modeToString(Mode mode)
×
223
{
224
    switch (mode)
×
225
    {
226
        default:
×
227
        case PanTilt:
228
            return QString(KXMLQLCEFXFixtureModePanTilt);
×
229
        case Dimmer:
×
230
            return QString(KXMLQLCEFXFixtureModeDimmer);
×
231
        case RGB:
×
232
            return QString(KXMLQLCEFXFixtureModeRGB);
×
233
    }
234
}
235

236
EFXFixture::Mode EFXFixture::stringToMode(const QString& str)
×
237
{
238
    if (str == QString(KXMLQLCEFXFixtureModePanTilt))
×
239
        return PanTilt;
×
240
    else if (str == QString(KXMLQLCEFXFixtureModeDimmer))
×
241
        return Dimmer;
×
242
    else if (str == QString(KXMLQLCEFXFixtureModeRGB))
×
243
        return RGB;
×
244
    else
245
        return PanTilt;
×
246
}
247

248
/*****************************************************************************
249
 * Load & Save
250
 *****************************************************************************/
251

252
bool EFXFixture::loadXML(QXmlStreamReader &root)
13✔
253
{
254
    if (root.name() != KXMLQLCEFXFixture)
13✔
255
    {
256
        qWarning("EFX Fixture node not found!");
1✔
257
        return false;
1✔
258
    }
259

260
    GroupHead head;
12✔
261
    head.head = 0;
12✔
262

263
    /* New file format contains sub tags */
264
    while (root.readNextStartElement())
38✔
265
    {
266
        if (root.name() == KXMLQLCEFXFixtureID)
26✔
267
        {
268
            /* Fixture ID */
269
            head.fxi = root.readElementText().toInt();
12✔
270
        }
271
        else if (root.name() == KXMLQLCEFXFixtureHead)
14✔
272
        {
273
            /* Fixture Head */
274
            head.head = root.readElementText().toInt();
1✔
275
        }
276
        else if (root.name() == KXMLQLCEFXFixtureMode)
13✔
277
        {
278
            /* Fixture Mode */
279
            setMode ((Mode) root.readElementText().toInt());
×
280
        }
281
        else if (root.name() == KXMLQLCEFXFixtureDirection)
13✔
282
        {
283
            /* Direction */
284
            Function::Direction dir = Function::stringToDirection(root.readElementText());
12✔
285
            setDirection(dir);
12✔
286
        }
287
        else if (root.name() == KXMLQLCEFXFixtureStartOffset)
1✔
288
        {
289
            /* Start offset */
290
            setStartOffset(root.readElementText().toInt());
×
291
        }
292
        else if (root.name() == KXMLQLCEFXFixtureIntensity)
1✔
293
        {
294
            /* Intensity - LEGACY */
295
            root.skipCurrentElement();
×
296
        }
297
        else
298
        {
299
            qWarning() << "Unknown EFX Fixture tag:" << root.name();
1✔
300
            root.skipCurrentElement();
1✔
301
        }
302
    }
303

304
    if (head.fxi != Fixture::invalidId())
12✔
305
       setHead(head);
12✔
306

307
    return true;
12✔
308
}
309

310
bool EFXFixture::saveXML(QXmlStreamWriter *doc) const
4✔
311
{
312
    Q_ASSERT(doc != NULL);
4✔
313

314
    /* EFXFixture */
315
    doc->writeStartElement(KXMLQLCEFXFixture);
4✔
316

317
    /* Fixture ID */
318
    doc->writeTextElement(KXMLQLCEFXFixtureID, QString("%1").arg(head().fxi));
4✔
319
    /* Fixture Head */
320
    doc->writeTextElement(KXMLQLCEFXFixtureHead, QString("%1").arg(head().head));
4✔
321
    /* Mode */
322
    doc->writeTextElement(KXMLQLCEFXFixtureMode, QString::number(mode()));
4✔
323
    /* Direction */
324
    doc->writeTextElement(KXMLQLCEFXFixtureDirection, Function::directionToString(m_direction));
4✔
325
    /* Start offset */
326
    doc->writeTextElement(KXMLQLCEFXFixtureStartOffset, QString::number(startOffset()));
4✔
327

328
    /* End the <Fixture> tag */
329
    doc->writeEndElement();
4✔
330

331
    return true;
4✔
332
}
333

334
/****************************************************************************
335
 * Run-time properties
336
 ****************************************************************************/
337

338
const Doc* EFXFixture::doc() const
670✔
339
{
340
    Q_ASSERT(m_parent != NULL);
670✔
341
    return m_parent->doc();
670✔
342
}
343

344
void EFXFixture::setSerialNumber(int number)
13✔
345
{
346
    m_serialNumber = number;
13✔
347
}
13✔
348

349
int EFXFixture::serialNumber() const
5✔
350
{
351
    return m_serialNumber;
5✔
352
}
353

354
void EFXFixture::reset()
10✔
355
{
356
    m_done = false;
10✔
357
    m_runTimeDirection = m_direction;
10✔
358
    m_started = false;
10✔
359
    m_elapsed = 0;
10✔
360
    m_currentAngle = 0;
10✔
361
}
10✔
362

363
bool EFXFixture::isDone() const
206✔
364
{
365
    return m_done;
206✔
366
}
367

368
uint EFXFixture::timeOffset() const
155✔
369
{
370
    if (m_parent->propagationMode() == EFX::Asymmetric ||
310✔
371
        m_parent->propagationMode() == EFX::Serial)
155✔
372
    {
373
        return m_parent->loopDuration() / (m_parent->fixtures().size() + 1) * serialNumber();
3✔
374
    }
375
    else
376
    {
377
        return 0;
152✔
378
    }
379
}
380

381
/*****************************************************************************
382
 * Running
383
 *****************************************************************************/
384

385
void EFXFixture::start()
3✔
386
{
387
    m_started = true;
3✔
388
}
3✔
389

390
void EFXFixture::stop()
5✔
391
{
392
    m_started = false;
5✔
393
}
5✔
394

395
void EFXFixture::nextStep(QList<Universe *> universes, QSharedPointer<GenericFader> fader)
202✔
396
{
397
    // Nothing to do
398
    if (m_parent->loopDuration() == 0)
202✔
399
        return;
50✔
400

401
    // Bail out without doing anything if this fixture is ready (after single-shot)
402
    // or it has no pan&tilt channels (not valid).
403
    if (m_done == true || isValid() == false)
152✔
404
        return;
×
405

406
    m_elapsed += MasterTimer::tick();
152✔
407

408
    // Check time wrapping
409
    if (m_elapsed > m_parent->loopDuration())
152✔
410
    {
411
        if (m_parent->runOrder() == Function::PingPong)
2✔
412
        {
413
            /* Reverse direction for ping-pong EFX. */
414
            if (m_runTimeDirection == Function::Forward)
×
415
                m_runTimeDirection = Function::Backward;
×
416
            else
417
                m_runTimeDirection = Function::Forward;
×
418
        }
419
        else if (m_parent->runOrder() == Function::SingleShot)
2✔
420
        {
421
            /* De-initialize the fixture and mark as ready. */
422
            m_done = true;
1✔
423
            stop();
1✔
424
        }
425

426
        m_elapsed = 0;
2✔
427
    }
428

429
    // Bail out without doing anything if this fixture is waiting for its turn.
430
    if (m_parent->propagationMode() == EFX::Serial && m_elapsed < timeOffset() && !m_started)
152✔
431
        return;
×
432

433
    // Fade in
434
    if (m_started == false)
152✔
435
        start();
3✔
436

437
    // Scale from elapsed time in relation to overall duration to a point in a circle
438
    uint pos = (m_elapsed + timeOffset()) % m_parent->loopDuration();
152✔
439
    m_currentAngle = SCALE(float(pos),
152✔
440
                           float(0), float(m_parent->loopDuration()),
441
                           float(0), float(M_PI * 2));
442

443
    float valX = 0;
152✔
444
    float valY = 0;
152✔
445

446
    m_parent->calculatePoint(m_runTimeDirection, m_startOffset, m_currentAngle, &valX, &valY);
152✔
447

448
    /* Set target values on faders/universes */
449
    switch (m_mode)
152✔
450
    {
451
        case PanTilt:
152✔
452
            setPointPanTilt(universes, fader, valX, valY);
152✔
453
        break;
152✔
454

455
        case RGB:
×
456
            setPointRGB(universes, fader, valX, valY);
×
457
        break;
×
458

459
        case Dimmer:
×
460
            //Use Y for coherence with RGB gradient.
461
            setPointDimmer(universes, fader, valY);
×
462
        break;
×
463
    }
464
}
465

466
void EFXFixture::updateFaderValues(FadeChannel *fc, uchar value)
312✔
467
{
468
    fc->setStart(fc->current());
312✔
469
    fc->setTarget(value);
312✔
470
    fc->setElapsed(0);
312✔
471
    fc->setReady(false);
312✔
472
    fc->setFadeTime(0);
312✔
473
}
312✔
474

475
void EFXFixture::setPointPanTilt(QList<Universe *> universes, QSharedPointer<GenericFader> fader,
156✔
476
                                 float pan, float tilt)
477
{
478
    Fixture* fxi = doc()->fixture(head().fxi);
156✔
479
    Q_ASSERT(fxi != NULL);
156✔
480
    Universe *uni = universes[universe()];
156✔
481

482
    //qDebug() << "Pan value: " << pan << ", tilt value:" << tilt;
483

484
    quint32 panMsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, head().head);
156✔
485
    quint32 panLsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::LSB, head().head);
156✔
486
    quint32 tiltMsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, head().head);
156✔
487
    quint32 tiltLsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::LSB, head().head);
156✔
488

489
    /* Write coarse point data to universes */
490
    if (panMsbChannel != QLCChannel::invalid() && !fader.isNull())
156✔
491
    {
492
        FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), panMsbChannel);
155✔
493
        if (m_parent->isRelative())
155✔
494
            fc->addFlag(FadeChannel::Relative);
×
495
        updateFaderValues(fc, static_cast<uchar>(pan));
155✔
496
    }
497
    if (tiltMsbChannel != QLCChannel::invalid() && !fader.isNull())
156✔
498
    {
499
        FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), tiltMsbChannel);
155✔
500
        if (m_parent->isRelative())
155✔
501
            fc->addFlag(FadeChannel::Relative);
×
502
        updateFaderValues(fc, static_cast<uchar>(tilt));
155✔
503
    }
504

505
    /* Write fine point data to universes if applicable */
506
    if (panLsbChannel != QLCChannel::invalid() && !fader.isNull())
156✔
507
    {
508
        /* Leave only the fraction */
509
        float value = ((pan - floor(pan)) * float(UCHAR_MAX));
1✔
510
        FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), panLsbChannel);
1✔
511
        updateFaderValues(fc, static_cast<uchar>(value));
1✔
512
    }
513

514
    if (tiltLsbChannel != QLCChannel::invalid() && !fader.isNull())
156✔
515
    {
516
        /* Leave only the fraction */
517
        float value = ((tilt - floor(tilt)) * float(UCHAR_MAX));
1✔
518
        FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), tiltLsbChannel);
1✔
519
        updateFaderValues(fc, static_cast<uchar>(value));
1✔
520
    }
521
}
156✔
522

523
void EFXFixture::setPointDimmer(QList<Universe *> universes, QSharedPointer<GenericFader> fader, float dimmer)
×
524
{
525
    Fixture *fxi = doc()->fixture(head().fxi);
×
526
    Q_ASSERT(fxi != NULL);
×
527
    Universe *uni = universes[universe()];
×
528

529
    quint32 intChannel = fxi->channelNumber(QLCChannel::Intensity, QLCChannel::MSB, head().head);
×
530

531
    /* Don't write dimmer data directly to universes but use FadeChannel to avoid steps at EFX loop restart */
532
    if (intChannel != QLCChannel::invalid())
×
533
    {
534
        if (!fader.isNull())
×
535
        {
536
            FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), intChannel);
×
537
            updateFaderValues(fc, dimmer);
×
538
        }
539
    }
540
    else if (fxi->masterIntensityChannel() != QLCChannel::invalid())
×
541
    {
542
        if (!fader.isNull())
×
543
        {
544
            FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), fxi->masterIntensityChannel());
×
545
            updateFaderValues(fc, dimmer);
×
546
        }
547
    }
548
}
×
549

550
void EFXFixture::setPointRGB(QList<Universe *> universes, QSharedPointer<GenericFader> fader, float x, float y)
×
551
{
552
    Fixture* fxi = doc()->fixture(head().fxi);
×
553
    Q_ASSERT(fxi != NULL);
×
554
    Universe *uni = universes[universe()];
×
555

556
    QVector<quint32> rgbChannels = fxi->rgbChannels(head().head);
×
557

558
    /* Don't write dimmer data directly to universes but use FadeChannel to avoid steps at EFX loop restart */
559
    if (rgbChannels.size() >= 3 && !fader.isNull())
×
560
    {
561
        QColor pixel = m_rgbGradient.pixel(x, y);
×
562

563
        FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), rgbChannels[0]);
×
564
        updateFaderValues(fc, pixel.red());
×
565
        fc = fader->getChannelFader(doc(), uni, fxi->id(), rgbChannels[1]);
×
566
        updateFaderValues(fc, pixel.green());
×
567
        fc = fader->getChannelFader(doc(), uni, fxi->id(), rgbChannels[2]);
×
568
        updateFaderValues(fc, pixel.blue());
×
569
    }
570
}
×
571

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