Skip to main content

Overview

A Show is the most advanced function type in QLC+. It provides a timeline-based approach where multiple functions can be placed on separate tracks with precise start times and durations. Shows are perfect for pre-programmed performances, theatrical productions, and synchronized displays.
Shows replace the need for complex chaser hierarchies by providing a visual timeline where you can see and control when each function plays.

When to Use Shows

Theatrical Productions

Program complete lighting cues with precise timing

Music Synchronization

Sync lighting to pre-recorded music tracks

Automated Displays

Create self-running light shows for permanent installations

Complex Sequences

Build multi-layer effects with overlapping functions

Show Structure

Shows are organized into tracks:
Show
├── Track 1: Stage Wash
│   ├── Function at 0ms (5000ms duration)
│   ├── Function at 6000ms (3000ms duration)
│   └── Function at 10000ms (2000ms duration)
├── Track 2: Moving Heads
│   ├── Function at 2000ms (8000ms duration)
│   └── Function at 11000ms (4000ms duration)
└── Track 3: Effects
    └── Function at 5000ms (10000ms duration)

Time Division Modes

Shows support different timing systems:
enum TimeDivision {
    Time,       // Millisecond-based timing
    BPM_4_4,    // 4/4 time signature
    BPM_3_4,    // 3/4 time signature
    BPM_2_4,    // 2/4 time signature
    Invalid
};

Time-Based Shows

timeDivisionType
Time
Functions placed at specific millisecond timestamps
Best for:
  • Pre-programmed sequences
  • Syncing to recorded audio
  • Fixed-duration shows

Beat-Based Shows

timeDivisionType
BPM_4_4 | BPM_3_4 | BPM_2_4
Functions placed at beat positions
timeDivisionBPM
int
default:"120"
Beats per minute for beat-based timing
Best for:
  • Music-synced shows
  • Live performance with tempo changes
  • Rhythmic lighting patterns

Class Structure

The Show class (show.h:37) provides:

Time Division Control

// Set time division mode and BPM
void setTimeDivision(TimeDivision type, int BPM);

// Get time division settings
TimeDivision timeDivisionType();
int timeDivisionBPM();
int beatsDivision();  // Returns 2, 3, or 4

// Convert between string and enum
static QString tempoToString(TimeDivision type);
static TimeDivision stringToTempo(QString tempo);

Track Management

// Add a track
bool addTrack(Track *track, quint32 id = Track::invalidId());

// Remove a track
bool removeTrack(quint32 id);

// Get track by ID
Track *track(quint32 id) const;

// Get all tracks
QList<Track*> tracks() const;

// Get track count
int getTracksCount();

// Reorder tracks
void moveTrack(Track *track, int direction);

Show Function Access

// Get unique ShowFunction ID for creation
quint32 getLatestShowFunctionId();

// Get ShowFunction by ID
ShowFunction *showFunction(quint32 id);

// Get track containing a ShowFunction
Track *getTrackFromShowFunctionID(quint32 id);

// Get track bound to a scene
Track *getTrackFromSceneID(quint32 id);

Track System

Each track in a show has:
class Track {
public:
    quint32 m_id;              // Track ID
    QString m_name;            // Track name
    quint32 m_sceneID;         // Optional bound scene
    QList<ShowFunction*> m_showFunctions;  // Functions on track
};

Track Types

1

Standard Track

Can contain any function type (scenes, chasers, EFX, etc.)
2

Sequence Track

Bound to a scene, contains sequence data
3

Audio Track

Contains audio playback function

ShowFunction Structure

Functions placed on the timeline:
class ShowFunction {
public:
    quint32 m_id;              // Unique ShowFunction ID
    quint32 m_functionID;      // Actual function to play
    quint32 m_startTime;       // Start time (ms or beats)
    quint32 m_duration;        // Duration override
    QColor m_color;            // Display color in UI
    bool m_locked;             // Prevent editing
};

Timing Control

startTime
quint32
When the function starts (milliseconds from show start, or beat number)
duration
quint32
Override function duration. If 0, uses function’s own duration.
locked
bool
If true, prevents moving or editing the ShowFunction in the UI

Duration Calculation

quint32 totalDuration() {
    quint32 totalDuration = 0;
    
    foreach (Track *track, m_tracks) {
        foreach (ShowFunction *sf, track->showFunctions()) {
            quint32 endTime = sf->startTime() + sf->duration();
            if (endTime > totalDuration)
                totalDuration = endTime;
        }
    }
    
    return totalDuration;
}
The show’s total duration is determined by the latest-ending ShowFunction across all tracks.

Intensity Control

Shows provide per-track intensity control:
int adjustAttribute(qreal fraction, int attributeId) {
    // Each track has its own attribute index
    QList<Track*> trackList = m_tracks.values();
    if (attributeId < trackList.count()) {
        Track *track = trackList.at(attributeId);
        m_runner->adjustIntensity(fraction, track);
    }
    return attributeId;
}
Unlike other functions, Show attributes map to tracks, not generic properties. Attribute 0 controls track 0’s intensity, attribute 1 controls track 1, etc.

ShowRunner

Shows use a dedicated runner for playback:
class ShowRunner {
public:
    // Start functions at the right time
    void write(MasterTimer *timer);
    
    // Adjust track intensity
    void adjustIntensity(qreal fraction, Track *track);
    
    // Pause control
    void setPause(bool enable);
};
The runner:
  • Monitors elapsed time
  • Starts functions when their start time arrives
  • Stops functions when their duration expires
  • Handles pause/resume
  • Manages track intensity

XML Structure

<Function Type="Show" ID="100" Name="Main Show">
  <TimeDivision Type="Time" BPM="120"/>
  
  <Track ID="0" Name="Stage Wash" SceneID="0">
    <ShowFunction ID="0" 
                  Function="1" 
                  StartTime="0" 
                  Duration="5000" 
                  Color="#ff0000"
                  Locked="0"/>
    <ShowFunction ID="1" 
                  Function="2" 
                  StartTime="6000" 
                  Duration="3000"/>
  </Track>
  
  <Track ID="1" Name="Moving Heads">
    <ShowFunction ID="2" 
                  Function="10" 
                  StartTime="2000" 
                  Duration="8000"/>
  </Track>
</Function>

Playback Control

Starting the Show

void preRun(MasterTimer* timer) {
    // Create and start the runner
    m_runner = new ShowRunner(doc(), id(), elapsed());
    
    // Set initial track intensities
    for (int i = 0; i < m_tracks.count(); i++) {
        m_runner->adjustIntensity(
            getAttributeValue(i), 
            m_tracks[i]
        );
    }
    
    m_runner->start();
}

Running the Show

void write(MasterTimer* timer, QList<Universe*> universes) {
    if (isPaused())
        return;
        
    // Let the runner handle function timing
    m_runner->write(timer);
}

Stopping the Show

void postRun(MasterTimer* timer, QList<Universe*> universes) {
    if (m_runner != NULL) {
        m_runner->stop();
        delete m_runner;
        m_runner = NULL;
    }
}

Signals

signals:
    void timeChanged(quint32);  // Current playback time
    void showFinished();        // Show reached the end
Use these signals to:
  • Update timeline cursor in UI
  • Trigger actions when show completes
  • Monitor playback progress

Best Practices

1

Plan Track Organization

Group related functions on the same track for easier management
2

Use Appropriate Time Division

Choose Time for fixed sequences, BPM for music-synced shows
3

Test Overlapping Functions

Verify that overlapping functions on different tracks interact correctly
4

Lock Critical Functions

Lock ShowFunctions that must not be accidentally moved
5

Color-Code Tracks

Use different colors to visually distinguish function types

Advanced Features

Track Scene Binding

Tracks can be bound to a scene:
Track *track = new Track(sceneID, show);
This is used for:
  • Sequence tracks (animate scene channels)
  • Fixture group organization
  • Consistent channel control

Copying Shows

Show copying duplicates all tracks and ShowFunctions:
bool copyFrom(const Function* function) {
    const Show* show = qobject_cast<const Show*>(function);
    
    // Copy tracks
    foreach (Track *track, show->tracks()) {
        Track *newTrack = new Track(track->sceneID(), this);
        addTrack(newTrack);
        
        // Copy ShowFunctions
        foreach (ShowFunction *sf, track->showFunctions()) {
            Function *copy = sf->function()->createCopy(doc());
            ShowFunction *newSF = newTrack->createShowFunction(copy->id());
            newSF->setStartTime(sf->startTime());
            newSF->setDuration(sf->duration());
        }
    }
}

Performance Considerations

  • Shows have minimal runtime overhead
  • ShowRunner only checks elapsed time each tick
  • Memory usage scales with number of ShowFunctions
  • Track intensity adjustments are lightweight
Shows with hundreds of overlapping functions may cause CPU spikes when many functions start simultaneously. Consider staggering start times by a few milliseconds.

Limitations

  1. No Live Editing: Cannot add/remove ShowFunctions while show is running
  2. Fixed Timeline: Cannot dynamically change durations during playback
  3. Track Intensity Only: Cannot adjust individual ShowFunction intensity
  4. No Nested Shows: Shows cannot contain other shows

Comparison with Chasers

FeatureShowChaser
TimingAbsolute timelineRelative steps
TracksMultiple parallelSingle sequence
FlexibilityFixed timelineDynamic playback
ComplexityHighMedium
Use CaseComplete showsSimple sequences

See Also

Build docs developers (and LLMs) love