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

mcallegari / qlcplus / 22149820832

18 Feb 2026 05:11PM UTC coverage: 34.067% (+0.08%) from 33.99%
22149820832

push

github

mcallegari
engine: improve universe composition efficiency

- Universe::tick() now releases the semaphore only if no pending token exists.
- Elapsed-time aware fading: improves fade timing accuracy under variable CPU load and scheduler jitter; fades progress according to real time
instead of assuming perfect 20 ms cadence
- Reduced lock hold in Universe fader processing to avoid holding the universe fader list lock during potentially expensive per-fader channel writes, reducing contention
and improving scalability with many faders/universes
- Lower allocation/copy pressure in output emission: avoids per-tick temporary copy for plugin output write path while preserving safe ownership semantics for queued
signal consumers
- GenericFader channel lookup/update restructuring: reduces lock churn and repeated channel metadata setup overhead in high-channel scenes
- Locking correctness improvements in GenericFader: fixes data-race risk and ensures thread-safe mutation behavior
- Additional performance validation is done through engine/test/universeperf

122 of 202 new or added lines in 9 files covered. (60.4%)

2 existing lines in 2 files now uncovered.

17708 of 51980 relevant lines covered (34.07%)

41221.91 hits per line

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

76.63
/engine/src/scene.cpp
1
/*
2
  Q Light Controller Plus
3
  scene.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 <cmath>
25
#include <QList>
26
#include <QFile>
27

28
#include "qlcfixturedef.h"
29
#include "qlccapability.h"
30

31
#include "genericfader.h"
32
#include "mastertimer.h"
33
#include "universe.h"
34
#include "scene.h"
35
#include "doc.h"
36
#include "bus.h"
37

38
/*****************************************************************************
39
 * Initialization
40
 *****************************************************************************/
41

42
Scene::Scene(Doc* doc)
214✔
43
    : Function(doc, Function::SceneType)
44
    , m_legacyFadeBus(Bus::invalid())
428✔
45
    , m_flashOverrides(false)
214✔
46
    , m_flashForceLTP(false)
214✔
47
    , m_blendFunctionID(Function::invalidId())
214✔
48
{
49
    setName(tr("New Scene"));
214✔
50
    registerAttribute(tr("ParentIntensity"), Multiply | Single);
214✔
51
}
214✔
52

53
Scene::~Scene()
414✔
54
{
55
}
414✔
56

57
QIcon Scene::getIcon() const
×
58
{
59
    return QIcon(":/scene.png");
×
60
}
61

62
quint32 Scene::totalDuration()
2✔
63
{
64
    return (quint32)duration();
2✔
65
}
66

67
/*****************************************************************************
68
 * Copying
69
 *****************************************************************************/
70

71
Function* Scene::createCopy(Doc* doc, bool addToDoc)
3✔
72
{
73
    Q_ASSERT(doc != NULL);
3✔
74

75
    Function* copy = new Scene(doc);
3✔
76
    if (copy->copyFrom(this) == false)
3✔
77
    {
78
        delete copy;
×
79
        copy = NULL;
×
80
    }
81
    if (addToDoc == true && doc->addFunction(copy) == false)
3✔
82
    {
83
        delete copy;
×
84
        copy = NULL;
×
85
    }
86

87
    return copy;
3✔
88
}
89

90
bool Scene::copyFrom(const Function* function)
6✔
91
{
92
    const Scene* scene = qobject_cast<const Scene*> (function);
6✔
93
    if (scene == NULL)
6✔
94
        return false;
1✔
95

96
    m_values.clear();
5✔
97
    m_values = scene->m_values;
5✔
98
    m_fixtures.clear();
5✔
99
    m_fixtures = scene->m_fixtures;
5✔
100
    m_channelGroups.clear();
5✔
101
    m_channelGroups = scene->m_channelGroups;
5✔
102
    m_channelGroupsLevels.clear();
5✔
103
    m_channelGroupsLevels = scene->m_channelGroupsLevels;
5✔
104
    m_fixtureGroups.clear();
5✔
105
    m_fixtureGroups = scene->m_fixtureGroups;
5✔
106
    m_palettes.clear();
5✔
107
    m_palettes = scene->m_palettes;
5✔
108

109
    return Function::copyFrom(function);
5✔
110
}
111

112
/*****************************************************************************
113
 * Values
114
 *****************************************************************************/
115

116
void Scene::setValue(const SceneValue& scv, bool blind, bool checkHTP)
556✔
117
{
118
    bool valChanged = false;
556✔
119

120
    if (!m_fixtures.contains(scv.fxi))
556✔
121
    {
122
        qWarning() << Q_FUNC_INFO << "Setting value for unknown fixture" << scv.fxi << ". Adding it.";
202✔
123
        m_fixtures.append(scv.fxi);
202✔
124
    }
125

126
    {
127
        QMutexLocker locker(&m_valueListMutex);
556✔
128

129
        QMap<SceneValue, uchar>::iterator it = m_values.find(scv);
556✔
130
        if (it == m_values.end())
556✔
131
        {
132
            m_values.insert(scv, scv.value);
553✔
133
            valChanged = true;
553✔
134
        }
135
        else if (it.value() != scv.value)
3✔
136
        {
137
            const_cast<uchar&>(it.key().value) = scv.value;
3✔
138
            it.value() = scv.value;
3✔
139
            valChanged = true;
3✔
140
        }
141

142
        // if the scene is running, we must
143
        // update/add the changed channel
144
        if (blind == false && m_fadersMap.isEmpty() == false)
556✔
145
        {
146
            Fixture *fixture = doc()->fixture(scv.fxi);
2✔
147
            if (fixture != NULL)
2✔
148
            {
149
                quint32 universe = fixture->universe();
2✔
150

151
                FadeChannel fc(doc(), scv.fxi, scv.channel);
2✔
152
                fc.setStart(scv.value);
2✔
153
                fc.setTarget(scv.value);
2✔
154
                fc.setCurrent(scv.value);
2✔
155
                fc.setFadeTime(0);
2✔
156

157
                if (m_fadersMap.contains(universe))
2✔
158
                {
159
                    if (checkHTP == false)
2✔
160
                        m_fadersMap[universe]->replace(fc);
1✔
161
                    else
162
                        m_fadersMap[universe]->add(fc);
1✔
163
                }
164
            }
2✔
165
        }
166
    }
556✔
167

168
    emit changed(this->id());
556✔
169
    if (valChanged)
556✔
170
        emit valueChanged(scv);
556✔
171
}
556✔
172

173
void Scene::setValue(quint32 fxi, quint32 ch, uchar value)
547✔
174
{
175
    setValue(SceneValue(fxi, ch, value));
547✔
176
}
547✔
177

178
void Scene::unsetValue(quint32 fxi, quint32 ch)
5✔
179
{
180
    if (!m_fixtures.contains(fxi))
5✔
181
        qWarning() << Q_FUNC_INFO << "Unsetting value for unknown fixture" << fxi;
1✔
182

183
    {
184
        QMutexLocker locker(&m_valueListMutex);
5✔
185
        m_values.remove(SceneValue(fxi, ch, 0));
5✔
186
    }
5✔
187

188
    emit changed(this->id());
5✔
189
}
5✔
190

191
uchar Scene::value(quint32 fxi, quint32 ch)
421✔
192
{
193
    return m_values.value(SceneValue(fxi, ch, 0), 0);
421✔
194
}
195

196
bool Scene::checkValue(SceneValue val)
409✔
197
{
198
    return m_values.contains(val);
409✔
199
}
200

201
QList <SceneValue> Scene::values() const
222✔
202
{
203
    return m_values.keys();
222✔
204
}
205

206
QList<quint32> Scene::components() const
1✔
207
{
208
    QList<quint32> ids;
1✔
209

210
    QMap <SceneValue, uchar>::const_iterator it = m_values.begin();
1✔
211
    for (; it != m_values.end(); it++)
5✔
212
    {
213
        const SceneValue& scv = it.key();
4✔
214
        if (ids.contains(scv.fxi) == false)
4✔
215
            ids.append(scv.fxi);
3✔
216
    }
217

218
    return ids;
2✔
219
}
×
220

221
QColor Scene::colorValue(quint32 fxi)
3✔
222
{
223
    int rVal = 0, gVal = 0, bVal = 0;
3✔
224
    int cVal = -1, mVal = -1, yVal = -1;
3✔
225
    bool found = false;
3✔
226
    QColor CMYcol;
3✔
227

228
    QMap <SceneValue, uchar>::iterator it = m_values.begin();
3✔
229
    for (; it != m_values.end(); it++)
27✔
230
    {
231
        const SceneValue& scv = it.key();
24✔
232

233
        if (fxi != Fixture::invalidId() && fxi != scv.fxi)
24✔
234
            continue;
16✔
235

236
        Fixture *fixture = doc()->fixture(scv.fxi);
8✔
237
        if (fixture == NULL)
8✔
238
            continue;
×
239

240
        const QLCChannel* channel = fixture->channel(scv.channel);
8✔
241
        if (channel == NULL)
8✔
242
            continue;
×
243

244
        if (channel->group() == QLCChannel::Intensity)
8✔
245
        {
246
            QLCChannel::PrimaryColour col = channel->colour();
7✔
247
            switch (col)
7✔
248
            {
249
                case QLCChannel::Red: rVal = scv.value; found = true; break;
1✔
250
                case QLCChannel::Green: gVal = scv.value; found = true; break;
1✔
251
                case QLCChannel::Blue: bVal = scv.value; found = true; break;
1✔
252
                case QLCChannel::Cyan: cVal = scv.value; break;
1✔
253
                case QLCChannel::Magenta: mVal = scv.value; break;
1✔
254
                case QLCChannel::Yellow: yVal = scv.value; break;
1✔
255
                case QLCChannel::White: rVal = gVal = bVal = scv.value; found = true; break;
×
256
                default: break;
1✔
257
            }
258
        }
259
        else if (channel->group() == QLCChannel::Colour)
1✔
260
        {
261
            QLCCapability *cap = channel->searchCapability(scv.value);
1✔
262
            if (cap &&
2✔
263
                (cap->presetType() == QLCCapability::SingleColor ||
1✔
264
                 cap->presetType() == QLCCapability::DoubleColor))
×
265
            {
266
                QColor col = cap->resource(0).value<QColor>();
1✔
267
                rVal = col.red();
1✔
268
                gVal = col.green();
1✔
269
                bVal = col.blue();
1✔
270
                found = true;
1✔
271
            }
272
        }
273

274
        if (cVal >= 0 && mVal >= 0 && yVal >= 0)
8✔
275
        {
276
            CMYcol.setCmyk(cVal, mVal, yVal, 0);
1✔
277
            rVal = CMYcol.red();
1✔
278
            gVal = CMYcol.green();
1✔
279
            bVal = CMYcol.blue();
1✔
280
            found = true;
1✔
281
        }
282
    }
283

284
    if (found)
3✔
285
        return QColor(rVal, gVal, bVal);
3✔
286

287
    return QColor();
×
288
}
289

290
void Scene::clear()
1✔
291
{
292
    m_values.clear();
1✔
293
    m_fixtures.clear();
1✔
294
    m_fixtureGroups.clear();
1✔
295
    m_palettes.clear();
1✔
296
}
1✔
297

298
/*********************************************************************
299
 * Channel Groups
300
 *********************************************************************/
301

302
void Scene::addChannelGroup(quint32 id)
2✔
303
{
304
    if (m_channelGroups.contains(id) == false)
2✔
305
    {
306
        m_channelGroups.append(id);
1✔
307
        m_channelGroupsLevels.append(0);
1✔
308
    }
309
}
2✔
310

311
void Scene::removeChannelGroup(quint32 id)
2✔
312
{
313
    int idx = m_channelGroups.indexOf(id);
2✔
314
    if (idx != -1)
2✔
315
    {
316
        m_channelGroups.removeAt(idx);
1✔
317
        m_channelGroupsLevels.removeAt(idx);
1✔
318
    }
319
}
2✔
320

321
void Scene::setChannelGroupLevel(quint32 id, uchar level)
1✔
322
{
323
    int idx = m_channelGroups.indexOf(id);
1✔
324
    if (idx >= 0 && idx < m_channelGroupsLevels.count())
1✔
325
        m_channelGroupsLevels[idx] = level;
1✔
326
}
1✔
327

328
QList<uchar> Scene::channelGroupsLevels()
6✔
329
{
330
    return m_channelGroupsLevels;
6✔
331
}
332

333
QList<quint32> Scene::channelGroups()
6✔
334
{
335
    return m_channelGroups;
6✔
336
}
337

338
/*****************************************************************************
339
 * Fixtures
340
 *****************************************************************************/
341

342
void Scene::slotFixtureRemoved(quint32 fxi_id)
4✔
343
{
344
    bool hasChanged = false;
4✔
345

346
    QMutableMapIterator <SceneValue, uchar> it(m_values);
4✔
347
    while (it.hasNext() == true)
10✔
348
    {
349
        SceneValue value(it.next().key());
6✔
350
        if (value.fxi == fxi_id)
6✔
351
        {
352
            it.remove();
2✔
353
            hasChanged = true;
2✔
354
        }
355
    }
6✔
356

357
    if (removeFixture(fxi_id))
4✔
358
        hasChanged = true;
2✔
359

360
    if (hasChanged)
4✔
361
        emit changed(this->id());
2✔
362
}
4✔
363

364
void Scene::addFixture(quint32 fixtureId)
7✔
365
{
366
    if (m_fixtures.contains(fixtureId) == false)
7✔
367
        m_fixtures.append(fixtureId);
7✔
368
}
7✔
369

370
bool Scene::removeFixture(quint32 fixtureId)
4✔
371
{
372
    return m_fixtures.removeOne(fixtureId);
4✔
373
}
374

375
QList<quint32> Scene::fixtures() const
2✔
376
{
377
    return m_fixtures;
2✔
378
}
379

380
/*********************************************************************
381
 * Fixture Groups
382
 *********************************************************************/
383

384
void Scene::addFixtureGroup(quint32 id)
×
385
{
386
    if (m_fixtureGroups.contains(id) == false)
×
387
        m_fixtureGroups.append(id);
×
388
}
×
389

390
bool Scene::removeFixtureGroup(quint32 id)
×
391
{
392
    return m_fixtureGroups.removeOne(id);
×
393
}
394

395
QList<quint32> Scene::fixtureGroups() const
×
396
{
397
    return m_fixtureGroups;
×
398
}
399

400
/*********************************************************************
401
 * Palettes
402
 *********************************************************************/
403

404
void Scene::addPalette(quint32 id)
×
405
{
406
    if (m_palettes.contains(id) == false)
×
407
        m_palettes.append(id);
×
408
}
×
409

410
bool Scene::removePalette(quint32 id)
×
411
{
412
    return m_palettes.removeOne(id);
×
413
}
414

415
QList<quint32> Scene::palettes() const
122✔
416
{
417
    return m_palettes;
122✔
418
}
419

420
/*****************************************************************************
421
 * Load & Save
422
 *****************************************************************************/
423

424
bool Scene::saveXML(QXmlStreamWriter *doc) const
2✔
425
{
426
    Q_ASSERT(doc != NULL);
2✔
427

428
    /* Function tag */
429
    doc->writeStartElement(KXMLQLCFunction);
4✔
430

431
    /* Common attributes */
432
    saveXMLCommon(doc);
2✔
433

434
    /* Tempo type */
435
    saveXMLTempoType(doc);
2✔
436

437
    /* Speed */
438
    saveXMLSpeed(doc);
2✔
439

440
    /* Channel groups */
441
    if (m_channelGroups.count() > 0)
2✔
442
    {
443
        QString chanGroupsIDs;
×
444
        for (int i = 0; i < m_channelGroups.size(); ++i)
×
445
        {
446
            if (chanGroupsIDs.isEmpty() == false)
×
447
                chanGroupsIDs.append(QString(","));
×
448
            int id = m_channelGroups.at(i);
×
449
            int val = m_channelGroupsLevels.at(i);
×
450
            chanGroupsIDs.append(QString("%1,%2").arg(id).arg(val));
×
451
        }
452
        doc->writeTextElement(KXMLQLCSceneChannelGroupsValues, chanGroupsIDs);
×
453
    }
×
454

455
    /* Scene contents */
456
    // make a copy of the Scene values cause we need to empty it in the process
457
    QList<SceneValue> values = m_values.keys();
2✔
458

459
    // loop through the Scene Fixtures in the order they've been added
460
    foreach (quint32 fxId, m_fixtures)
4✔
461
    {
462
        QStringList currFixValues;
2✔
463
        bool found = false;
2✔
464

465
        // look for the values that match the current Fixture ID
466
        for (int j = 0; j < values.count(); j++)
6✔
467
        {
468
            SceneValue scv = values.at(j);
5✔
469
            if (scv.fxi != fxId)
5✔
470
            {
471
                if (found == true)
1✔
472
                    break;
1✔
473
                else
474
                    continue;
×
475
            }
476

477
            found = true;
4✔
478
            currFixValues.append(QString::number(scv.channel));
4✔
479
            // IMPORTANT: if a Scene is hidden, so used as a container by some Sequences,
480
            // it must be saved with values set to zero
481
            currFixValues.append(QString::number(isVisible() ? scv.value : 0));
4✔
482
            values.removeAt(j);
4✔
483
            j--;
4✔
484
        }
5✔
485

486
        saveXMLFixtureValues(doc, fxId, currFixValues);
2✔
487
    }
4✔
488

489
    /* Save referenced Fixture Groups */
490
    foreach (quint32 groupId, m_fixtureGroups)
2✔
491
    {
492
        doc->writeStartElement(KXMLQLCFixtureGroup);
×
493
        doc->writeAttribute(KXMLQLCFixtureGroupID, QString::number(groupId));
×
494
        doc->writeEndElement();
×
495
    }
2✔
496

497
    /* Save referenced Palettes */
498
    foreach (quint32 pId, m_palettes)
2✔
499
    {
500
        doc->writeStartElement(KXMLQLCPalette);
×
501
        doc->writeAttribute(KXMLQLCPaletteID, QString::number(pId));
×
502
        doc->writeEndElement();
×
503
    }
2✔
504

505
    /* End the <Function> tag */
506
    doc->writeEndElement();
2✔
507

508
    return true;
2✔
509
}
2✔
510

511
bool Scene::saveXMLFixtureValues(QXmlStreamWriter* doc, quint32 fixtureID, QStringList const& values)
2✔
512
{
513
    doc->writeStartElement(KXMLQLCFixtureValues);
4✔
514
    doc->writeAttribute(KXMLQLCFixtureID, QString::number(fixtureID));
4✔
515
    if (values.size() > 0)
2✔
516
        doc->writeCharacters(values.join(","));
2✔
517
    doc->writeEndElement();
2✔
518
    return true;
2✔
519
}
520

521
bool Scene::loadXML(QXmlStreamReader &root)
4✔
522
{
523
    if (root.name() != KXMLQLCFunction)
4✔
524
    {
525
        qWarning() << Q_FUNC_INFO << "Function node not found";
1✔
526
        return false;
1✔
527
    }
528

529
    if (root.attributes().value(KXMLQLCFunctionType).toString() != typeToString(Function::SceneType))
6✔
530
    {
531
        qWarning() << Q_FUNC_INFO << "Function is not a scene";
1✔
532
        return false;
1✔
533
    }
534

535
    /* Load scene contents */
536
    while (root.readNextStartElement())
9✔
537
    {
538
        if (root.name() == KXMLQLCBus)
7✔
539
        {
540
            m_legacyFadeBus = root.readElementText().toUInt();
2✔
541
        }
542
        else if (root.name() == KXMLQLCFunctionSpeed)
5✔
543
        {
544
            loadXMLSpeed(root);
1✔
545
        }
546
        else if (root.name() == KXMLQLCFunctionTempoType)
4✔
547
        {
548
            loadXMLTempoType(root);
×
549
        }
550
        else if (root.name() == KXMLQLCSceneChannelGroups)
4✔
551
        {
552
            QString chGrpIDs = root.readElementText();
×
553
            if (chGrpIDs.isEmpty() == false)
×
554
            {
555
                QStringList grpArray = chGrpIDs.split(",");
×
556
                foreach (QString grp, grpArray)
×
557
                {
558
                    m_channelGroups.append(grp.toUInt());
×
559
                    m_channelGroupsLevels.append(0);
×
560
                }
×
561
            }
×
562
        }
×
563
        else if (root.name() == KXMLQLCSceneChannelGroupsValues)
4✔
564
        {
565
            QString chGrpIDs = root.readElementText();
×
566
            if (chGrpIDs.isEmpty() == false)
×
567
            {
568
                QStringList grpArray = chGrpIDs.split(",");
×
569
                for (int i = 0; i + 1 < grpArray.count(); i+=2)
×
570
                {
571
                    m_channelGroups.append(grpArray.at(i).toUInt());
×
572
                    m_channelGroupsLevels.append(grpArray.at(i + 1).toUInt());
×
573
                }
574
            }
×
575
        }
×
576
        /* "old" style XML */
577
        else if (root.name() == KXMLQLCFunctionValue)
4✔
578
        {
579
            /* Channel value */
580
            SceneValue scv;
3✔
581
            if (scv.loadXML(root) == true)
3✔
582
                setValue(scv);
3✔
583
        }
3✔
584
        /* "new" style XML */
585
        else if (root.name() == KXMLQLCFixtureValues)
1✔
586
        {
587
            quint32 fxi = root.attributes().value(KXMLQLCFixtureID).toString().toUInt();
×
588
            addFixture(fxi);
×
589
            QString strvals = root.readElementText();
×
590
            if (strvals.isEmpty() == false)
×
591
            {
592
                QStringList varray = strvals.split(",");
×
593
                for (int i = 0; i + 1 < varray.count(); i+=2)
×
594
                {
595
                    SceneValue scv;
×
596
                    scv.fxi = fxi;
×
597
                    scv.channel = QString(varray.at(i)).toUInt();
×
598
                    scv.value = uchar(QString(varray.at(i + 1)).toInt());
×
599
                    setValue(scv);
×
600
                }
×
601
            }
×
602
        }
×
603
        else if (root.name() == KXMLQLCFixtureGroup)
1✔
604
        {
605
            quint32 id = root.attributes().value(KXMLQLCFixtureGroupID).toString().toUInt();
×
606
            addFixtureGroup(id);
×
607
            root.skipCurrentElement();
×
608
        }
609
        else if (root.name() == KXMLQLCPalette)
1✔
610
        {
611
            quint32 id = root.attributes().value(KXMLQLCPaletteID).toString().toUInt();
×
612
            addPalette(id);
×
613
            root.skipCurrentElement();
×
614
        }
615
        else
616
        {
617
            qWarning() << Q_FUNC_INFO << "Unknown scene tag:" << root.name();
1✔
618
            root.skipCurrentElement();
1✔
619
        }
620
    }
621

622
    return true;
2✔
623
}
624

625
void Scene::postLoad()
×
626
{
627
    // Map legacy bus speed to fixed speed values
628
    if (m_legacyFadeBus != Bus::invalid())
×
629
    {
630
        quint32 value = Bus::instance()->value(m_legacyFadeBus);
×
631
        setFadeInSpeed((value / MasterTimer::frequency()) * 1000);
×
632
        setFadeOutSpeed((value / MasterTimer::frequency()) * 1000);
×
633
    }
634

635
    // Remove such fixtures and channels that don't exist
636
    QMutableMapIterator <SceneValue, uchar> it(m_values);
×
637
    while (it.hasNext() == true)
×
638
    {
639
        SceneValue value(it.next().key());
×
640
        Fixture* fxi = doc()->fixture(value.fxi);
×
641
        if (fxi == NULL || fxi->channel(value.channel) == NULL)
×
642
            it.remove();
×
643
    }
×
644
}
×
645

646
/****************************************************************************
647
 * Flashing
648
 ****************************************************************************/
649

650
void Scene::flash(MasterTimer *timer, bool shouldOverride, bool forceLTP)
6✔
651
{
652
    if (flashing() == true)
6✔
653
        return;
1✔
654

655
    m_flashOverrides = shouldOverride;
5✔
656
    m_flashForceLTP = forceLTP;
5✔
657

658
    Q_ASSERT(timer != NULL);
5✔
659
    Function::flash(timer, shouldOverride, forceLTP);
5✔
660
    timer->registerDMXSource(this);
5✔
661
}
662

663
void Scene::unFlash(MasterTimer *timer)
5✔
664
{
665
    if (flashing() == false)
5✔
666
        return;
×
667

668
    Q_ASSERT(timer != NULL);
5✔
669
    Function::unFlash(timer);
5✔
670
}
671

672
void Scene::writeDMX(MasterTimer *timer, QList<Universe *> ua)
5✔
673
{
674
    Q_ASSERT(timer != NULL);
5✔
675

676
    if (flashing() == true)
5✔
677
    {
678
        if (m_fadersMap.isEmpty())
2✔
679
        {
680
            // Keep HTP and LTP channels up. Flash is more or less a forceful intervention
681
            // so enforce all values that the user has chosen to flash.
682
            QMap <SceneValue, uchar>::iterator it = m_values.begin();
1✔
683
            for (; it != m_values.end(); it++)
4✔
684
            {
685
                const SceneValue& sv = it.key();
3✔
686

687
                FadeChannel fc(doc(), sv.fxi, sv.channel);
3✔
688
                quint32 universe = fc.universe();
3✔
689
                if (universe == Universe::invalid())
3✔
690
                    continue;
×
691

692
                QSharedPointer<GenericFader> fader = m_fadersMap.value(universe, QSharedPointer<GenericFader>());
3✔
693
                if (fader.isNull())
3✔
694
                {
695
                    fader = ua[universe]->requestFader(m_flashOverrides ? Universe::Flashing : Universe::Auto);
1✔
696

697
                    fader->adjustIntensity(getAttributeValue(Intensity));
1✔
698
                    fader->setBlendMode(blendMode());
1✔
699
                    fader->setName(name());
1✔
700
                    fader->setParentFunctionID(id());
1✔
701
                    m_fadersMap[universe] = fader;
1✔
702
                }
703

704
                if (m_flashForceLTP)
3✔
705
                    fc.addFlag(FadeChannel::ForceLTP);
×
706
                fc.setTarget(sv.value);
3✔
707
                fc.addFlag(FadeChannel::Flashing);
3✔
708
                fader->add(fc);
3✔
709
            }
3✔
710
        }
711
    }
712
    else
713
    {
714
        handleFadersEnd(timer);
3✔
715
        timer->unregisterDMXSource(this);
3✔
716
    }
717
}
5✔
718

719
/****************************************************************************
720
 * Running
721
 ****************************************************************************/
722

723
void Scene::processValue(MasterTimer *timer, QList<Universe*> ua, uint fadeIn, SceneValue &scv)
515✔
724
{
725
    Fixture *fixture = doc()->fixture(scv.fxi);
515✔
726

727
    if (fixture == NULL)
515✔
728
        return;
×
729

730
    int universeIndex = floor((fixture->universeAddress() + scv.channel) / 512);
515✔
731
    if (universeIndex >= ua.count())
515✔
732
        return;
×
733

734
    Universe *universe = ua.at(universeIndex);
515✔
735

736
    QSharedPointer<GenericFader> fader = m_fadersMap.value(universe->id(), QSharedPointer<GenericFader>());
515✔
737
    if (fader.isNull())
515✔
738
    {
739
        fader = universe->requestFader();
122✔
740
        fader->adjustIntensity(getAttributeValue(Intensity));
122✔
741
        fader->setBlendMode(blendMode());
122✔
742
        fader->setName(name());
122✔
743
        fader->setParentFunctionID(id());
122✔
744
        fader->setParentIntensity(getAttributeValue(ParentIntensity));
122✔
745
        fader->setHandleSecondary(true);
122✔
746
        m_fadersMap[universe->id()] = fader;
122✔
747
    }
748

749
    const bool doBlend = blendFunctionID() != Function::invalidId();
515✔
750
    Scene *blendScene = doBlend ? qobject_cast<Scene *>(doc()->function(blendFunctionID())) : NULL;
515✔
751

752
    fader->updateChannel(doc(), universe, scv.fxi, scv.channel, [&](FadeChannel &fc)
515✔
753
    {
754
        int chIndex = fc.channelIndex(scv.channel);
515✔
755

756
        /** If a blend Function has been set, check if this channel needs to
757
         *  be blended from a previous value. If so, mark it for crossfade
758
         *  and set its current value */
759
        if (blendScene != NULL && blendScene->checkValue(scv))
515✔
760
        {
761
            fc.addFlag(FadeChannel::CrossFade);
407✔
762
            fc.setCurrent(blendScene->value(scv.fxi, scv.channel), chIndex);
407✔
763
            qDebug() << "----- BLEND from Scene" << blendScene->name()
814✔
764
                     << ", fixture:" << scv.fxi << ", channel:" << scv.channel << ", value:" << fc.current();
407✔
765
        }
766

767
        fc.setStart(fc.current(chIndex), chIndex);
515✔
768
        fc.setTarget(scv.value, chIndex);
515✔
769

770
        if (fc.canFade() == false)
515✔
771
        {
NEW
772
            fc.setFadeTime(0);
×
773
        }
774
        else
775
        {
776
            if (tempoType() == Beats)
515✔
777
            {
NEW
778
                int fadeInTime = beatsToTime(fadeIn, timer->beatTimeDuration());
×
NEW
779
                int beatOffset = timer->nextBeatTimeOffset();
×
780

NEW
781
                if (fadeInTime - beatOffset > 0)
×
NEW
782
                    fc.setFadeTime(fadeInTime - beatOffset);
×
783
                else
NEW
784
                    fc.setFadeTime(fadeInTime);
×
785
            }
786
            else
787
            {
788
                fc.setFadeTime(fadeIn);
515✔
789
            }
790
        }
791
    });
515✔
792
}
515✔
793

794
void Scene::handleFadersEnd(MasterTimer *timer)
111✔
795
{
796
    uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
111✔
797

798
    /* If no fade out is needed, dismiss all the requested faders.
799
     * Otherwise, set all the faders to fade out and let Universe dismiss them
800
     * when done */
801
    if (fadeout == 0)
111✔
802
    {
803
        dismissAllFaders();
106✔
804
    }
805
    else
806
    {
807
        if (tempoType() == Beats)
5✔
808
            fadeout = beatsToTime(fadeout, timer->beatTimeDuration());
×
809

810
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
9✔
811
        {
812
            if (!fader.isNull())
4✔
813
                fader->setFadeOut(true, fadeout);
4✔
814
        }
9✔
815
    }
816

817
    m_fadersMap.clear();
111✔
818

819
    // autonomously reset a blend function if set
820
    setBlendFunctionID(Function::invalidId());
111✔
821
}
111✔
822

823
void Scene::write(MasterTimer *timer, QList<Universe*> ua)
402✔
824
{
825
    //qDebug() << Q_FUNC_INFO << elapsed();
826

827
    if (m_values.count() == 0 && m_palettes.count() == 0)
402✔
828
    {
829
        stop(FunctionParent::master());
1✔
830
        return;
1✔
831
    }
832

833
    if (m_fadersMap.isEmpty())
401✔
834
    {
835
        uint fadeIn = overrideFadeInSpeed() == defaultSpeed() ? fadeInSpeed() : overrideFadeInSpeed();
122✔
836

837
        foreach (quint32 paletteID, palettes())
244✔
838
        {
839
            QLCPalette *palette = doc()->palette(paletteID);
×
840
            if (palette == NULL)
×
841
                continue;
×
842

843
            foreach (SceneValue scv, palette->valuesFromFixtureGroups(doc(), fixtureGroups()))
×
844
                processValue(timer, ua, fadeIn, scv);
×
845

846
            foreach (SceneValue scv, palette->valuesFromFixtures(doc(), fixtures()))
×
847
                processValue(timer, ua, fadeIn, scv);
×
848
        }
122✔
849

850
        QMutexLocker locker(&m_valueListMutex);
122✔
851
        QMapIterator <SceneValue, uchar> it(m_values);
122✔
852
        while (it.hasNext() == true)
637✔
853
        {
854
            SceneValue scv(it.next().key());
515✔
855
            processValue(timer, ua, fadeIn, scv);
515✔
856
        }
515✔
857
    }
122✔
858

859
    if (isPaused() == false)
401✔
860
    {
861
        incrementElapsed();
398✔
862
        if (timer->isBeat() && tempoType() == Beats)
398✔
863
            incrementElapsedBeats();
×
864
    }
865
}
866

867
void Scene::postRun(MasterTimer* timer, QList<Universe *> ua)
108✔
868
{
869
    handleFadersEnd(timer);
108✔
870

871
    Function::postRun(timer, ua);
108✔
872
}
108✔
873

874
void Scene::setPause(bool enable)
6✔
875
{
876
    if (!isRunning())
6✔
877
        return;
×
878

879
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
8✔
880
    {
881
        if (!fader.isNull())
2✔
882
            fader->setPaused(enable);
2✔
883
    }
8✔
884
    Function::setPause(enable);
6✔
885
}
886

887
/****************************************************************************
888
 * Intensity
889
 ****************************************************************************/
890

891
int Scene::adjustAttribute(qreal fraction, int attributeId)
248✔
892
{
893
    int attrIndex = Function::adjustAttribute(fraction, attributeId);
248✔
894

895
    if (attrIndex == Intensity)
248✔
896
    {
897
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
134✔
898
        {
899
            if (!fader.isNull())
3✔
900
                fader->adjustIntensity(getAttributeValue(Function::Intensity));
3✔
901
        }
134✔
902
    }
903
    else if (attrIndex == ParentIntensity)
117✔
904
    {
905
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
120✔
906
        {
907
            if (!fader.isNull())
3✔
908
                fader->setParentIntensity(getAttributeValue(ParentIntensity));
3✔
909
        }
120✔
910
    }
911

912
    return attrIndex;
248✔
913
}
914

915
/*************************************************************************
916
 * Blend mode
917
 *************************************************************************/
918

919
void Scene::setBlendMode(Universe::BlendMode mode)
3✔
920
{
921
    if (mode == blendMode())
3✔
922
        return;
1✔
923

924
    qDebug() << "Scene" << name() << "blend mode set to" << Universe::blendModeToString(mode);
2✔
925

926
    foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
2✔
927
    {
928
        if (!fader.isNull())
×
929
            fader->setBlendMode(mode);
×
930
    }
2✔
931

932
    Function::setBlendMode(mode);
2✔
933
}
934

935
quint32 Scene::blendFunctionID() const
922✔
936
{
937
    return m_blendFunctionID;
922✔
938
}
939

940
void Scene::setBlendFunctionID(quint32 fid)
204✔
941
{
942
    m_blendFunctionID = fid;
204✔
943
    if (isRunning() && fid == Function::invalidId())
204✔
944
    {
945
        foreach (QSharedPointer<GenericFader> fader, m_fadersMap)
109✔
946
        {
947
            if (!fader.isNull())
×
948
                fader->resetCrossfade();
×
949
        }
109✔
950
    }
951
}
204✔
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