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

mcallegari / qlcplus / 25515859594

07 May 2026 06:55PM UTC coverage: 34.112% (+0.06%) from 34.048%
25515859594

Pull #2009

github

web-flow
Merge 1734a65c9 into 33d41a4e8
Pull Request #2009: chaserrunner: apply submaster updates to all crossfade steps

13 of 19 new or added lines in 1 file covered. (68.42%)

2 existing lines in 1 file now uncovered.

17797 of 52172 relevant lines covered (34.11%)

41154.19 hits per line

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

74.13
/engine/src/chaserrunner.cpp
1
/*
2
  Q Light Controller Plus
3
  chaserrunner.cpp
4

5
  Copyright (c) Heikki Junnila
6
                Massimo Callegari
7
                Jano Svitok
8

9
  Licensed under the Apache License, Version 2.0 (the "License");
10
  you may not use this file except in compliance with the License.
11
  You may obtain a copy of the License at
12

13
      http://www.apache.org/licenses/LICENSE-2.0.txt
14

15
  Unless required by applicable law or agreed to in writing, software
16
  distributed under the License is distributed on an "AS IS" BASIS,
17
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
  See the License for the specific language governing permissions and
19
  limitations under the License.
20
*/
21

22
#include <QElapsedTimer>
23
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
24
#include <QRandomGenerator>
25
#endif
26
#include <QDebug>
27

28
#include "chaserrunner.h"
29
#include "mastertimer.h"
30
#include "chaserstep.h"
31
#include "qlcmacros.h"
32
#include "chaser.h"
33
#include "scene.h"
34
#include "doc.h"
35

36
ChaserRunner::ChaserRunner(const Doc *doc, const Chaser *chaser, quint32 startTime)
32✔
37
    : QObject(NULL)
38
    , m_doc(doc)
32✔
39
    , m_chaser(chaser)
32✔
40
    , m_updateOverrideSpeeds(false)
32✔
41
    , m_startOffset(0)
32✔
42
    , m_lastRunStepIdx(-1)
32✔
43
    , m_lastFunctionID(Function::invalidId())
32✔
44
    , m_roundTime(new QElapsedTimer())
32✔
45
    , m_order()
64✔
46
{
47
    Q_ASSERT(chaser != NULL);
32✔
48

49
    m_pendingAction.m_action = ChaserNoAction;
32✔
50
    m_pendingAction.m_masterIntensity = 1.0;
32✔
51
    m_pendingAction.m_stepIntensity = 1.0;
32✔
52
    m_pendingAction.m_fadeMode = Chaser::FromFunction;
32✔
53
    m_pendingAction.m_stepIndex = -1;
32✔
54

55
    if (startTime > 0)
32✔
56
    {
57
        qDebug() << "[ChaserRunner] startTime:" << startTime;
×
58
        int idx = 0;
×
59
        quint32 stepsTime = 0;
×
60
        foreach (ChaserStep step, chaser->steps())
×
61
        {
62
            uint duration = m_chaser->durationMode() == Chaser::Common ? m_chaser->duration() : step.duration;
×
63

64
            if (startTime < stepsTime + duration)
×
65
            {
66
                m_pendingAction.m_action = ChaserSetStepIndex;
×
67
                m_pendingAction.m_stepIndex = idx;
×
68
                m_startOffset = startTime - stepsTime;
×
69
                qDebug() << "[ChaserRunner] Starting from step:" << idx;
×
70
                break;
×
71
            }
72
            idx++;
×
73
            stepsTime += duration;
×
74
        }
×
75
    }
76

77
    m_direction = m_chaser->direction();
32✔
78
    connect(chaser, SIGNAL(changed(quint32)), this, SLOT(slotChaserChanged()));
32✔
79
    m_roundTime->restart();
32✔
80

81
    fillOrder();
32✔
82
}
32✔
83

84
ChaserRunner::~ChaserRunner()
43✔
85
{
86
    clearRunningList();
32✔
87
    delete m_roundTime;
32✔
88
}
43✔
89

90
/****************************************************************************
91
 * Speed
92
 ****************************************************************************/
93

94
void ChaserRunner::slotChaserChanged()
38✔
95
{
96
    // Handle (possible) speed change on the next write() pass
97
    m_updateOverrideSpeeds = true;
38✔
98
    QList<ChaserRunnerStep*> delList;
38✔
99
    foreach (ChaserRunnerStep *step, m_runnerSteps)
46✔
100
    {
101
        if (!m_chaser->steps().contains(ChaserStep(step->m_function->id())))
8✔
102
        {
103
            // Disappearing function: remove step
104
            delList.append(step);
5✔
105
        }
106
        else
107
        {
108
            // Recalculate the speed of each running step
109
            step->m_fadeIn = stepFadeIn(step->m_index);
3✔
110
            step->m_fadeOut = stepFadeOut(step->m_index);
3✔
111
            step->m_duration = stepDuration(step->m_index);
3✔
112
        }
113
    }
38✔
114
    foreach (ChaserRunnerStep *step, delList)
43✔
115
    {
116
        step->m_function->stop(functionParent());
5✔
117
        m_runnerSteps.removeAll(step);
5✔
118
        delete step;
5✔
119
    }
38✔
120
}
38✔
121

122
uint ChaserRunner::stepFadeIn(int stepIdx) const
132✔
123
{
124
    uint speed = 0;
132✔
125
    if (m_chaser->overrideFadeInSpeed() != Function::defaultSpeed())
132✔
126
    {
127
        // Override speed is used when another function has started the chaser,
128
        // i.e. chaser inside a chaser that wants to impose its own fade in speed
129
        // to its members.
130
        speed = m_chaser->overrideFadeInSpeed();
10✔
131
    }
132
    else
133
    {
134
        switch (m_chaser->fadeInMode())
122✔
135
        {
136
            case Chaser::Common:
3✔
137
                // All steps' fade in speed is dictated by the chaser
138
                speed = m_chaser->fadeInSpeed();
3✔
139
            break;
3✔
140
            case Chaser::PerStep:
4✔
141
                // Each step specifies its own fade in speed
142
                if (stepIdx >= 0 && stepIdx < m_chaser->stepsCount())
4✔
143
                    speed = m_chaser->steps().at(stepIdx).fadeIn;
3✔
144
                else
145
                    speed = Function::defaultSpeed();
1✔
146
            break;
4✔
147
            default:
115✔
148
            case Chaser::Default:
149
                // Don't touch members' fade in speed at all
150
                speed = Function::defaultSpeed();
115✔
151
            break;
115✔
152
        }
153
    }
154

155
    return speed;
132✔
156
}
157

158
uint ChaserRunner::stepFadeOut(int stepIdx) const
168✔
159
{
160
    uint speed = 0;
168✔
161
    if (m_chaser->overrideFadeOutSpeed() != Function::defaultSpeed())
168✔
162
    {
163
        // Override speed is used when another function has started the chaser,
164
        // i.e. chaser inside a chaser that wants to impose its own fade out speed
165
        // to its members.
166
        speed = m_chaser->overrideFadeOutSpeed();
10✔
167
    }
168
    else
169
    {
170
        switch (m_chaser->fadeOutMode())
158✔
171
        {
172
            case Chaser::Common:
3✔
173
                // All steps' fade out speed is dictated by the chaser
174
                speed = m_chaser->fadeOutSpeed();
3✔
175
            break;
3✔
176
            case Chaser::PerStep:
4✔
177
                // Each step specifies its own fade out speed
178
                if (stepIdx >= 0 && stepIdx < m_chaser->stepsCount())
4✔
179
                    speed = m_chaser->steps().at(stepIdx).fadeOut;
3✔
180
                else
181
                    speed = Function::defaultSpeed();
1✔
182
            break;
4✔
183
            default:
151✔
184
            case Chaser::Default:
185
                // Don't touch members' fade out speed at all
186
                speed = Function::defaultSpeed();
151✔
187
            break;
151✔
188
        }
189
    }
190

191
    return speed;
168✔
192
}
193

194
uint ChaserRunner::stepDuration(int stepIdx) const
142✔
195
{
196
    uint speed = 0;
142✔
197
    if (m_chaser->overrideDuration() != Function::defaultSpeed())
142✔
198
    {
199
        // Override speed is used when another function has started the chaser,
200
        // i.e. chaser inside a chaser that wants to impose its own duration
201
        // to its members.
202
        speed = m_chaser->overrideDuration();
10✔
203
    }
204
    else
205
    {
206
        switch (m_chaser->durationMode())
132✔
207
        {
208
            default:
128✔
209
            case Chaser::Default:
210
            case Chaser::Common:
211
                // All steps' duration is dictated by the chaser
212
                speed = m_chaser->duration();
128✔
213
            break;
128✔
214
            case Chaser::PerStep:
4✔
215
                // Each step specifies its own duration
216
                if (stepIdx >= 0 && stepIdx < m_chaser->stepsCount())
4✔
217
                    speed = m_chaser->steps().at(stepIdx).duration;
3✔
218
                else
219
                    speed = m_chaser->duration();
1✔
220
            break;
4✔
221
        }
222
    }
223

224
    return speed;
142✔
225
}
226

227
/****************************************************************************
228
 * Step control
229
 ****************************************************************************/
230

231
void ChaserRunner::setAction(const ChaserAction &action)
39✔
232
{
233
    // apply the actions that can be applied immediately
234
    switch (action.m_action)
39✔
235
    {
236
        case ChaserNoAction:
7✔
237
            m_pendingAction.m_masterIntensity = action.m_masterIntensity;
7✔
238
            m_pendingAction.m_stepIntensity = action.m_stepIntensity;
7✔
239
        break;
7✔
240

241
        case ChaserStopStep:
×
242
        {
243
            bool stopped = false;
×
244

245
            foreach (ChaserRunnerStep *step, m_runnerSteps)
×
246
            {
247
                if (action.m_stepIndex == step->m_index)
×
248
                {
249
                    qDebug() << "[ChaserRunner] Stopping step idx:" << action.m_stepIndex << "(running:" << m_runnerSteps.count() << ")";
×
250
                    m_lastFunctionID = step->m_function->type() == Function::SceneType ? step->m_function->id() : Function::invalidId();
×
251
                    step->m_function->stop(functionParent());
×
252
                    m_runnerSteps.removeOne(step);
×
253
                    delete step;
×
254
                    stopped = true;
×
255
                }
256
            }
×
257

258
            if (stopped && m_runnerSteps.size() == 1)
×
259
            {
260
                ChaserRunnerStep *lastStep = m_runnerSteps.at(0);
×
261
                m_lastRunStepIdx = lastStep->m_index;
×
262
                emit currentStepChanged(m_lastRunStepIdx);
×
263
            }
264
        }
265
        break;
×
266

267
        // copy to pending action. Will be processed at the next write call
268
        default:
32✔
269
            m_pendingAction.m_stepIndex = action.m_stepIndex;
32✔
270
            m_pendingAction.m_masterIntensity = action.m_masterIntensity;
32✔
271
            m_pendingAction.m_stepIntensity = action.m_stepIntensity;
32✔
272
            m_pendingAction.m_fadeMode = action.m_fadeMode;
32✔
273
            m_pendingAction.m_action = action.m_action;
32✔
274
        break;
32✔
275
    }
276
}
39✔
277

278
void ChaserRunner::tap()
2✔
279
{
280
    if (uint(m_roundTime->elapsed()) >= (stepDuration(m_lastRunStepIdx) / 4))
2✔
281
        m_pendingAction.m_action = ChaserNextStep;
2✔
282
}
2✔
283

284
int ChaserRunner::currentStepIndex() const
65✔
285
{
286
    return m_lastRunStepIdx;
65✔
287
}
288

289
int ChaserRunner::runningStepsNumber() const
1✔
290
{
291
    return m_runnerSteps.count();
1✔
292
}
293

294
ChaserRunnerStep *ChaserRunner::currentRunningStep() const
×
295
{
296
    if (m_runnerSteps.count() > 0)
×
297
        return m_runnerSteps.at(0);
×
298
    return NULL;
×
299
}
300

301
int ChaserRunner::computeNextStep(int currentStep) const
19✔
302
{
303
    int nextStep = currentStep;
19✔
304

305
    if (m_chaser->runOrder() == Function::Random)
19✔
306
    {
307
        nextStep = m_order.indexOf(nextStep);
×
308
        if (nextStep == -1)
×
309
        {
310
            qDebug() << "[ChaserRunner] steps order not found";
×
311
            nextStep = currentStep;
×
312
        }
313
    }
314

315
    // Next step
316
    if (m_direction == Function::Forward)
19✔
317
    {
318
        nextStep++;
19✔
319
    }
320
    else
321
    {
322
        nextStep--;
×
323
    }
324

325
    if (nextStep < m_chaser->stepsCount() && nextStep >= 0)
19✔
326
    {
327
        if (m_chaser->runOrder() == Function::Random)
16✔
328
        {
329
            nextStep = randomStepIndex(nextStep);
×
330
        }
331
        return nextStep; // In the middle of steps. No need to go any further.
16✔
332
    }
333

334
    if (m_chaser->runOrder() == Function::SingleShot)
3✔
335
    {
336
        return -1; // Forward or Backward SingleShot has been completed.
×
337
    }
338
    else if (m_chaser->runOrder() == Function::Loop)
3✔
339
    {
340
        if (m_direction == Function::Forward)
3✔
341
        {
342
            if (nextStep >= m_chaser->stepsCount())
3✔
343
                nextStep = 0;
3✔
344
            else
345
                nextStep = m_chaser->stepsCount() - 1; // Used by CueList with manual prev
×
346
        }
347
        else // Backward
348
        {
349
            if (nextStep < 0)
×
350
                nextStep = m_chaser->stepsCount() - 1;
×
351
            else
352
                nextStep = 0;
×
353
        }
354
    }
355
    else if (m_chaser->runOrder() == Function::Random)
×
356
    {
357
        nextStep = randomStepIndex(nextStep);
×
358
    }
359
    else // Ping Pong
360
    {
361
        // Change direction, but don't run the first/last step twice.
362
        if (m_direction == Function::Forward)
×
363
        {
364
            nextStep = m_chaser->stepsCount() - 2;
×
365
        }
366
        else // Backwards
367
        {
368
            nextStep = 1;
×
369
        }
370

371
        // Make sure we don't go beyond limits.
372
        nextStep = CLAMP(nextStep, 0, m_chaser->stepsCount() - 1);
×
373
    }
374

375
    return nextStep;
3✔
376
}
377

378
void ChaserRunner::shuffle(QVector<int> & data)
32✔
379
{
380
   int n = data.size();
32✔
381
   for (int i = n - 1; i > 0; --i)
90✔
382
   {
383
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
384
      qSwap(data[i], data[qrand() % (i + 1)]);
385
#else
386
      qSwap(data[i], data[QRandomGenerator::global()->generate() % (i + 1)]);
58✔
387
#endif
388
   }
389
}
32✔
390

391
int ChaserRunner::randomStepIndex(int step) const
×
392
{
393
   if (m_chaser->runOrder() == Function::Random && step >= 0 && step < m_order.size())
×
394
       return m_order[step];
×
395

396
   return step;
×
397
}
398

399
void ChaserRunner::fillOrder()
32✔
400
{
401
    fillOrder(m_chaser->stepsCount());
32✔
402
}
32✔
403

404
void ChaserRunner::fillOrder(int size)
32✔
405
{
406
   m_order.resize(size);
32✔
407
   for (int i = 0; i < size; ++i)
118✔
408
       m_order[i] = i;
86✔
409

410
   shuffle(m_order);
32✔
411
}
32✔
412

413
/****************************************************************************
414
 * Intensity
415
 ****************************************************************************/
416

417
void ChaserRunner::adjustStepIntensity(qreal fraction, int requestedStepIndex, int fadeControl)
10✔
418
{
419
    fraction = CLAMP(fraction, qreal(0.0), qreal(1.0));
10✔
420

421
    //qDebug() << "Adjust intensity" << fraction << "step:" << requestedStepIndex << "fade:" << fadeControl;
422

423
    int stepIndex = requestedStepIndex;
10✔
424
    if (stepIndex == -1)
10✔
425
    {
426
        // store the intensity to be applied at the next step startup
427
        m_pendingAction.m_masterIntensity = fraction;
8✔
428

429
        foreach (ChaserRunnerStep *step, m_runnerSteps)
12✔
430
        {
431
            if (step == NULL || step->m_function == NULL)
4✔
NEW
432
                continue;
×
433

434
            step->m_masterIntensity = fraction;
4✔
435
            if (step->m_function->type() == Function::SceneType)
4✔
436
            {
437
                Scene *scene = qobject_cast<Scene *>(step->m_function);
4✔
438
                scene->adjustAttribute(fraction, step->m_pIntensityOverrideId);
4✔
439
            }
440
            else
441
            {
NEW
442
                step->m_function->adjustAttribute(fraction * step->m_stepIntensity, step->m_intensityOverrideId);
×
443
            }
444
        }
8✔
445

446
        return;
8✔
447
    }
448

449
    foreach (ChaserRunnerStep *step, m_runnerSteps)
3✔
450
    {
451
        if (stepIndex == step->m_index && step->m_function != NULL)
1✔
452
        {
NEW
453
            step->m_stepIntensity = fraction;
×
NEW
454
            if (step->m_function->type() == Function::SceneType)
×
NEW
455
                step->m_function->adjustAttribute(fraction, step->m_intensityOverrideId);
×
456
            else
NEW
457
                step->m_function->adjustAttribute(step->m_masterIntensity * fraction, step->m_intensityOverrideId);
×
UNCOV
458
            return;
×
459
        }
460
    }
2✔
461

462
    // No need to start a new step if it is not wanted
463
    if (requestedStepIndex == -1)
2✔
UNCOV
464
        return;
×
465

466
    // Don't start a step with an intensity of zero
467
    if (fraction == qreal(0.0))
2✔
468
        return;
×
469

470
    // not found ? It means we need to start a new step and crossfade kicks in !
471
    startNewStep(stepIndex, m_doc->masterTimer(), m_pendingAction.m_masterIntensity, fraction, fadeControl);
2✔
472
}
473

474
/****************************************************************************
475
 * Running
476
 ****************************************************************************/
477

478
void ChaserRunner::clearRunningList()
65✔
479
{
480
    // empty the running queue
481
    foreach (ChaserRunnerStep *step, m_runnerSteps)
101✔
482
    {
483
        if (step->m_function)
36✔
484
        {
485
            // restore the original Function fade out time
486
            step->m_function->setOverrideFadeOutSpeed(stepFadeOut(step->m_index));
36✔
487
            step->m_function->stop(functionParent(), m_chaser->type() == Function::SequenceType);
36✔
488
            m_lastFunctionID = step->m_function->type() == Function::SceneType ? step->m_function->id() : Function::invalidId();
36✔
489
        }
490
        delete step;
36✔
491
    }
65✔
492
    m_runnerSteps.clear();
65✔
493
}
65✔
494

495
void ChaserRunner::startNewStep(int index, MasterTimer *timer, qreal mIntensity, qreal sIntensity,
117✔
496
                                int fadeControl, quint32 elapsed)
497
{
498
    if (m_chaser == NULL || m_chaser->stepsCount() == 0)
117✔
499
        return;
×
500

501
    if (index < 0 || index >= m_chaser->stepsCount())
117✔
502
        index = 0; // fallback to the first step
×
503

504
    ChaserStep step(m_chaser->steps().at(index));
117✔
505
    Function *func = m_doc->function(step.fid);
117✔
506
    if (func == NULL)
117✔
507
        return;
×
508

509
    ChaserRunnerStep *newStep = new ChaserRunnerStep();
117✔
510
    newStep->m_index = index;
117✔
511
    newStep->m_function = func;
117✔
512
    newStep->m_masterIntensity = mIntensity;
117✔
513
    newStep->m_stepIntensity = sIntensity;
117✔
514
    newStep->m_intensityOverrideId = Function::invalidAttributeId();
117✔
515
    newStep->m_pIntensityOverrideId = Function::invalidAttributeId();
117✔
516

517
    // check if blending between Scenes is needed
518
    if (m_lastFunctionID != Function::invalidId() &&
210✔
519
        func->type() == Function::SceneType)
93✔
520
    {
521
        Scene *scene = qobject_cast<Scene *>(func);
93✔
522
        scene->setBlendFunctionID(m_lastFunctionID);
93✔
523
    }
524

525
    // this happens only during crossfades
526
    if (m_runnerSteps.count())
117✔
527
    {
528
        ChaserRunnerStep *lastStep = m_runnerSteps.last();
1✔
529
        if (lastStep->m_function &&
3✔
530
            lastStep->m_function->type() == Function::SceneType &&
2✔
531
            func->type() == Function::SceneType)
1✔
532
        {
533
            Scene *lastScene = qobject_cast<Scene *>(lastStep->m_function);
1✔
534
            lastScene->setBlendFunctionID(Function::invalidId());
1✔
535
            Scene *scene = qobject_cast<Scene *>(func);
1✔
536
            scene->setBlendFunctionID(lastStep->m_function->id());
1✔
537
        }
538
    }
539

540
    switch (fadeControl)
117✔
541
    {
542
        case Chaser::FromFunction:
109✔
543
            newStep->m_fadeIn = stepFadeIn(index);
109✔
544
            newStep->m_fadeOut = stepFadeOut(index);
109✔
545
        break;
109✔
546
        case Chaser::Blended:
×
547
            newStep->m_fadeIn = stepFadeIn(index);
×
548
            newStep->m_fadeOut = stepFadeOut(index);
×
549
        break;
×
550
        case Chaser::Crossfade:
×
551
            newStep->m_fadeIn = 0;
×
552
            newStep->m_fadeOut = 0;
×
553
        break;
×
554
        case Chaser::BlendedCrossfade:
2✔
555
            newStep->m_fadeIn = 0;
2✔
556
            newStep->m_fadeOut = 0;
2✔
557
        break;
2✔
558
    }
559

560
    newStep->m_duration = stepDuration(index);
117✔
561

562
    if (m_startOffset != 0)
117✔
563
        newStep->m_elapsed = m_startOffset + MasterTimer::tick();
×
564
    else
565
        newStep->m_elapsed = MasterTimer::tick() + elapsed;
117✔
566
    newStep->m_elapsedBeats = 0; //(newStep->m_elapsed / timer->beatTimeDuration()) * 1000;
117✔
567

568
    m_startOffset = 0;
117✔
569

570
    if (m_chaser->type() == Function::SequenceType)
117✔
571
    {
572
        Scene *s = qobject_cast<Scene*>(func);
×
573
        // blind == true is a workaround to reuse the same scene
574
        // without messing up the previous values
575
        for (int i = 0; i < step.values.count(); i++)
×
576
            s->setValue(step.values.at(i), true);
×
577
    }
578

579
    qDebug() << "[ChaserRunner] Starting step" << index << "fade in" << newStep->m_fadeIn
234✔
580
             << "fade out" << newStep->m_fadeOut << "intensity" << mIntensity
117✔
581
             << "fadeMode" << fadeControl;
117✔
582

583
    // Set intensity before starting the function. Otherwise the intensity
584
    // might momentarily jump too high.
585
    if (func->type() == Function::SceneType)
117✔
586
    {
587
        Scene *scene = qobject_cast<Scene *>(func);
117✔
588
        newStep->m_intensityOverrideId = func->requestAttributeOverride(Function::Intensity, sIntensity);
117✔
589
        newStep->m_pIntensityOverrideId = scene->requestAttributeOverride(Scene::ParentIntensity, mIntensity);
117✔
590
        qDebug() << "[ChaserRunner] Set step intensity:" << sIntensity << ", master:" << mIntensity;
117✔
591
    }
592
    else
593
    {
594
        newStep->m_intensityOverrideId = func->requestAttributeOverride(Function::Intensity, mIntensity * sIntensity);
×
595
    }
596

597
    // Start the fire up!
598
    func->start(timer, functionParent(), 0, newStep->m_fadeIn, newStep->m_fadeOut,
117✔
599
                func->defaultSpeed(), m_chaser->tempoType());
117✔
600
    m_runnerSteps.append(newStep);
117✔
601
    m_roundTime->restart();
117✔
602
}
117✔
603

604
int ChaserRunner::getNextStepIndex()
113✔
605
{
606
    int currentStepIndex = m_lastRunStepIdx;
113✔
607

608
    if (m_chaser->runOrder() == Function::Random)
113✔
609
    {
610
        currentStepIndex = m_order.indexOf(currentStepIndex);
×
611
        if (currentStepIndex == -1)
×
612
        {
613
            qDebug() << "[ChaserRunner] steps order not found";
×
614
            currentStepIndex = m_lastRunStepIdx;
×
615
        }
616
    }
617

618
    if (currentStepIndex == -1 &&
131✔
619
        m_chaser->direction() == Function::Backward)
18✔
620
            currentStepIndex = m_chaser->stepsCount();
6✔
621

622
    // Handle reverse Ping Pong at boundaries
623
    if (m_chaser->runOrder() == Function::PingPong &&
141✔
624
        m_pendingAction.m_action == ChaserPreviousStep)
28✔
625
    {
626
        if (currentStepIndex == 0)
×
627
            m_direction = Function::Backward;
×
628
        else if (currentStepIndex == m_chaser->stepsCount() - 1)
×
629
            m_direction = Function::Forward;
×
630
    }
631

632
    // Next step
633
    if (m_direction == Function::Forward)
113✔
634
    {
635
        // "Previous" for a forward chaser is -1
636
        if (m_pendingAction.m_action == ChaserPreviousStep)
79✔
637
            currentStepIndex--;
10✔
638
        else
639
            currentStepIndex++;
69✔
640
    }
641
    else
642
    {
643
        // "Previous" for a backward scene is +1
644
        if (m_pendingAction.m_action == ChaserPreviousStep)
34✔
645
            currentStepIndex++;
×
646
        else
647
            currentStepIndex--;
34✔
648
    }
649

650
    if (currentStepIndex < m_chaser->stepsCount() && currentStepIndex >= 0)
113✔
651
    {
652
        if (m_chaser->runOrder() == Function::Random)
82✔
653
        {
654
            currentStepIndex = randomStepIndex(currentStepIndex);
×
655
        }
656
        return currentStepIndex; // In the middle of steps. No need to go any further.
82✔
657
    }
658

659
    if (m_chaser->runOrder() == Function::SingleShot)
31✔
660
    {
661
        return -1; // Forward or Backward SingleShot has been completed.
4✔
662
    }
663
    else if (m_chaser->runOrder() == Function::Loop)
27✔
664
    {
665
        if (m_direction == Function::Forward)
19✔
666
        {
667
            if (currentStepIndex >= m_chaser->stepsCount())
17✔
668
                currentStepIndex = 0;
13✔
669
            else
670
                currentStepIndex = m_chaser->stepsCount() - 1; // Used by CueList with manual prev
4✔
671
        }
672
        else // Backward
673
        {
674
            if (currentStepIndex < 0)
2✔
675
                currentStepIndex = m_chaser->stepsCount() - 1;
2✔
676
            else
677
                currentStepIndex = 0;
×
678
        }
679
    }
680
    else if (m_chaser->runOrder() == Function::Random)
8✔
681
    {
682
        fillOrder();
×
683
        if (m_direction == Function::Forward)
×
684
        {
685
            if (currentStepIndex >= m_chaser->stepsCount())
×
686
                currentStepIndex = 0;
×
687
            else
688
                currentStepIndex = m_chaser->stepsCount() - 1; // Used by CueList with manual prev
×
689
        }
690
        else // Backward
691
        {
692
            if (currentStepIndex < 0)
×
693
                currentStepIndex = m_chaser->stepsCount() - 1;
×
694
            else
695
                currentStepIndex = 0;
×
696
        }
697
        // Don't run the same function 2 times in a row
698
        while (currentStepIndex < m_chaser->stepsCount()
×
699
                && randomStepIndex(currentStepIndex) == m_lastRunStepIdx)
×
700
            ++currentStepIndex;
×
701
        currentStepIndex = randomStepIndex(currentStepIndex);
×
702
    }
703
    else // Ping Pong
704
    {
705
        // Change direction, but don't run the first/last step twice.
706
        if (m_direction == Function::Forward)
8✔
707
        {
708
            currentStepIndex = m_chaser->stepsCount() - 2;
4✔
709
            m_direction = Function::Backward;
4✔
710
        }
711
        else // Backwards
712
        {
713
            currentStepIndex = 1;
4✔
714
            m_direction = Function::Forward;
4✔
715
        }
716

717
        // Make sure we don't go beyond limits.
718
        currentStepIndex = CLAMP(currentStepIndex, 0, m_chaser->stepsCount() - 1);
8✔
719
    }
720

721
    return currentStepIndex;
27✔
722
}
723

724
void ChaserRunner::setPause(bool enable, QList<Universe *> universes)
2✔
725
{
726
    // Nothing to do
727
    if (m_chaser->stepsCount() == 0)
2✔
728
        return;
×
729

730
    qDebug() << "[ChaserRunner] processing pause request:" << enable;
2✔
731

732
    foreach (ChaserRunnerStep *step, m_runnerSteps)
4✔
733
        step->m_function->setPause(enable);
4✔
734

735
    // there might be a Scene fading out, so request pause
736
    // to faders bound to the Scene ID running on universes
737
    Function *f = m_doc->function(m_lastFunctionID);
2✔
738
    if (f != NULL && f->type() == Function::SceneType)
2✔
739
    {
740
        foreach (Universe *universe, universes)
10✔
741
            universe->setFaderPause(m_lastFunctionID, enable);
10✔
742
    }
743
}
744

745
FunctionParent ChaserRunner::functionParent() const
234✔
746
{
747
    return FunctionParent(FunctionParent::Function, m_chaser->id());
234✔
748
}
749

750
bool ChaserRunner::write(MasterTimer *timer, QList<Universe *> universes)
386✔
751
{
752
    // Nothing to do
753
    if (m_chaser->stepsCount() == 0)
386✔
754
        return false;
1✔
755

756
    switch (m_pendingAction.m_action)
385✔
757
    {
758
        case ChaserNextStep:
19✔
759
        case ChaserPreviousStep:
760
            clearRunningList();
19✔
761
            // the actual action will be performed below, on startNewStep
762
        break;
19✔
763
        case ChaserSetStepIndex:
7✔
764
            if (m_pendingAction.m_stepIndex != -1)
7✔
765
            {
766
                clearRunningList();
6✔
767
                if (m_chaser->runOrder() == Function::Random)
6✔
768
                    m_lastRunStepIdx = randomStepIndex(m_pendingAction.m_stepIndex);
×
769
                else
770
                    m_lastRunStepIdx = m_pendingAction.m_stepIndex;
6✔
771

772
                qDebug() << "[ChaserRunner] Starting from step" << m_lastRunStepIdx << "@ offset" << m_startOffset;
6✔
773
                startNewStep(m_lastRunStepIdx, timer, m_pendingAction.m_masterIntensity,
6✔
774
                             m_pendingAction.m_stepIntensity, m_pendingAction.m_fadeMode);
775
                emit currentStepChanged(m_lastRunStepIdx);
6✔
776
            }
777
        break;
7✔
778
        case ChaserPauseRequest:
2✔
779
            setPause(m_pendingAction.m_fadeMode ? true : false, universes);
2✔
780
        break;
2✔
781
        default:
357✔
782
        break;
357✔
783
    }
784

785
    quint32 prevStepRoundElapsed = 0;
385✔
786

787
    foreach (ChaserRunnerStep *step, m_runnerSteps)
733✔
788
    {
789
        if (m_chaser->tempoType() == Function::Beats && timer->isBeat())
348✔
790
        {
791
            step->m_elapsedBeats += 1000;
×
792
            qDebug() << "[ChaserRunner] Function" << step->m_function->name() << "duration:" << step->m_duration << "beats:" << step->m_elapsedBeats;
×
793
        }
794

795
        if (step->m_duration != Function::infiniteSpeed() &&
597✔
796
            ((m_chaser->tempoType() == Function::Time && step->m_elapsed >= step->m_duration) ||
249✔
797
             (m_chaser->tempoType() == Function::Beats && step->m_elapsedBeats >= step->m_duration)))
173✔
798
        {
799
            if (step->m_duration != 0)
76✔
800
                prevStepRoundElapsed = step->m_elapsed % step->m_duration;
31✔
801

802
            m_lastFunctionID = step->m_function->type() == Function::SceneType ? step->m_function->id() : Function::invalidId();
76✔
803
            step->m_function->stop(functionParent(), m_chaser->type() == Function::SequenceType);
76✔
804
            m_runnerSteps.removeOne(step);
76✔
805
            delete step;
76✔
806
        }
807
        else
808
        {
809
            if (step->m_elapsed < UINT_MAX)
272✔
810
                step->m_elapsed += MasterTimer::tick();
272✔
811

812
            // When the speeds of the chaser change, they need to be updated to the lower
813
            // level (only current function) as well. Otherwise the new speeds would take
814
            // effect only on the next step change.
815
            if (m_updateOverrideSpeeds == true)
272✔
816
            {
817
                m_updateOverrideSpeeds = false;
×
818
                if (step->m_function != NULL)
×
819
                {
820
                    step->m_function->setOverrideFadeInSpeed(step->m_fadeIn);
×
821
                    step->m_function->setOverrideFadeOutSpeed(step->m_fadeOut);
×
822
                }
823
            }
824
        }
825
    }
385✔
826

827
    if (m_runnerSteps.isEmpty())
385✔
828
    {
829
        m_lastRunStepIdx = getNextStepIndex();
113✔
830
        if (m_lastRunStepIdx != -1)
113✔
831
        {
832
            int blend = m_pendingAction.m_action == ChaserNoAction ? Chaser::FromFunction : m_pendingAction.m_fadeMode;
109✔
833

834
            startNewStep(m_lastRunStepIdx, timer, m_pendingAction.m_masterIntensity,
109✔
835
                         m_pendingAction.m_stepIntensity, blend, prevStepRoundElapsed);
836
            emit currentStepChanged(m_lastRunStepIdx);
109✔
837
        }
838
        else
839
        {
840
            m_pendingAction.m_action = ChaserNoAction;
4✔
841
            return false;
4✔
842
        }
843
    }
844

845
    m_pendingAction.m_action = ChaserNoAction;
381✔
846
    return true;
381✔
847
}
848

849
void ChaserRunner::postRun(MasterTimer *timer, QList<Universe*> universes)
8✔
850
{
851
    Q_UNUSED(universes);
852
    Q_UNUSED(timer);
853

854
    qDebug() << Q_FUNC_INFO;
8✔
855
    clearRunningList();
8✔
856
}
8✔
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