Skip to main content

Overview

Channel Modifiers allow you to transform DMX values sent to specific fixture channels. This is useful for correcting non-linear dimming curves, inverting channel behavior, or creating custom response curves for individual fixtures.

Understanding Channel Modifiers

A Channel Modifier maps input DMX values to output DMX values:
// From channelmodifier.h:20-79
class ChannelModifier final
{
public:
    enum Type {
        SystemTemplate = 0,  // Built-in templates
        UserTemplate = 1     // User-created templates
    };
    
    QString name() const;    // Modifier name
    Type type() const;       // Template type
    
    // Map of input → output DMX values
    QList<QPair<uchar, uchar>> modifierMap() const;
    
    // Get modified output for given input
    uchar getValue(uchar dmxValue) const;
};

How It Works

  1. Input Value: QLC+ generates a DMX value (0-255)
  2. Modifier Applied: The modifier transforms the value
  3. Output Value: The modified value is sent to the fixture
Input DMX: 128 → [Modifier] → Output DMX: 180

Use Cases

Dimmer Curve Correction

Some LED fixtures have non-linear dimming:
  • At low values, dimming is too abrupt
  • At high values, changes are barely noticeable
A modifier can create a custom curve to compensate.

Channel Inversion

Invert a channel’s response:
  • Input 0 → Output 255
  • Input 128 → Output 127
  • Input 255 → Output 0
Useful when:
  • A fixture is physically mounted upside-down
  • Pan/tilt needs to be reversed
  • You want opposite behavior without reprogramming

Fine-Tuning Color Matching

Adjust individual color channels to match fixtures from different manufacturers or batches.

Custom Response Curves

Create exponential, logarithmic, or S-curve responses for specific effects.

Creating a Channel Modifier

Modifier Map Structure

A modifier is defined by a list of control points:
// From channelmodifier.cpp:53-88
void ChannelModifier::setModifierMap(QList<QPair<uchar, uchar>> map)
{
    m_map = map;
    m_values.fill(0, 256);
    
    QPair<uchar, uchar> lastDMXPair;
    for (int i = 0; i < m_map.count(); i++)
    {
        QPair<uchar, uchar> dmxPair = m_map.at(i);
        m_values[dmxPair.first] = dmxPair.second;
        
        if (i != 0)
        {
            // Calculate linear interpolation between points
            float dmxInc = 0;
            if (dmxPair.first - lastDMXPair.first > 0)
                dmxInc = (float)(dmxPair.second - lastDMXPair.second) / 
                         (float)(dmxPair.first - lastDMXPair.first);
            
            // Fill intermediate values
            float floatVal = lastDMXPair.second;
            for (int p = lastDMXPair.first; p < dmxPair.first; p++)
            {
                m_values[p] = floatVal;
                floatVal += dmxInc;
            }
        }
        lastDMXPair = dmxPair;
    }
}
Values between control points are automatically interpolated linearly. You only need to define key points in the curve.

Example: Simple Invert Modifier

Two control points create a full inversion:
Control Points:
  Input 0   → Output 255
  Input 255 → Output 0

Interpolated:
  Input 0   → Output 255
  Input 64  → Output 191
  Input 128 → Output 127
  Input 192 → Output 63
  Input 255 → Output 0

Example: Dimmer Curve

Multiple points create a custom curve:
Control Points:
  Input 0   → Output 0    (Off stays off)
  Input 25  → Output 60   (Boost low values)
  Input 128 → Output 140  (Slightly boost mid)
  Input 255 → Output 255  (Full stays full)
This makes low-end dimming smoother while preserving full brightness.

Applying Modifiers to Fixtures

Assignment

Modifiers are assigned per-channel, per-fixture:
// From fixture.h:323-328
void setChannelModifier(quint32 idx, ChannelModifier *mod);
ChannelModifier *channelModifier(quint32 idx);
1

Select Fixture

Choose the fixture you want to modify in the Fixture Manager.
2

Choose Channel

Select the specific channel to apply the modifier to (e.g., channel 0 = Dimmer).
3

Select or Create Modifier

  • Use a system template
  • Load a user template
  • Create a new custom modifier
4

Apply and Test

Apply the modifier and test the fixture’s response.

Storage

Modifiers are stored with the fixture in the project:
// From fixture.h:352-356
QMap<quint32, ChannelModifier*> m_channelModifiers;
Each fixture can have different modifiers on different channels.

Modifier Templates

System Templates

QLC+ includes built-in modifier templates for common use cases:
enum Type {
    SystemTemplate = 0,  // Pre-installed modifiers
    UserTemplate = 1     // User-created
};
System templates might include:
  • Invert: Full 0-255 inversion
  • Square Law: Quadratic dimming curve
  • Logarithmic: Log-based dimming
  • Exponential: Exponential response

User Templates

Create and save your own templates for reuse:
// From channelmodifier.h:67-72
QFile::FileError saveXML(const QString& fileName) const;
QFile::FileError loadXML(const QString& fileName, Type type);

XML File Format

Modifiers are saved as XML files:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ChannelModifier>
<ChannelModifier>
  <Name>Custom Dimmer Curve</Name>
  <Handler Original="0" Modified="0"/>
  <Handler Original="25" Modified="60"/>
  <Handler Original="128" Modified="140"/>
  <Handler Original="255" Modified="255"/>
</ChannelModifier>

XML Structure

// From channelmodifier.h:32-38 and channelmodifier.cpp:100-137
#define KXMLQLCChannelModifierDocument "ChannelModifier"
#define KXMLQLCChannelModName          "Name"
#define KXMLQLCChannelModHandler       "Handler"
#define KXMLQLCChannelModOriginalDMX   "Original"
#define KXMLQLCChannelModModifiedDMX   "Modified"
The modifier’s descriptive name:
<Name>Custom Dimmer Curve</Name>
Each handler defines one control point:
<Handler Original="128" Modified="140"/>
  • Original: Input DMX value (0-255)
  • Modified: Output DMX value (0-255)

Creating Custom Modifiers

Linear Modification

1

Define Start and End

<Handler Original="0" Modified="0"/>
<Handler Original="255" Modified="255"/>
This creates no modification (identity).
2

Add Intermediate Points

<Handler Original="0" Modified="0"/>
<Handler Original="128" Modified="160"/>
<Handler Original="255" Modified="255"/>
Boosts mid-range values.

Inversion Modifier

<ChannelModifier>
  <Name>Invert</Name>
  <Handler Original="0" Modified="255"/>
  <Handler Original="255" Modified="0"/>
</ChannelModifier>

S-Curve Modifier

Smooth transition with slow start/end, fast middle:
<ChannelModifier>
  <Name>S-Curve</Name>
  <Handler Original="0" Modified="0"/>
  <Handler Original="64" Modified="32"/>
  <Handler Original="128" Modified="128"/>
  <Handler Original="192" Modified="223"/>
  <Handler Original="255" Modified="255"/>
</ChannelModifier>

Compression Modifier

Limit output range (e.g., never go below 20% or above 90%):
<ChannelModifier>
  <Name>20-90% Range</Name>
  <Handler Original="0" Modified="51"/>    <!-- 20% -->
  <Handler Original="255" Modified="229"/>  <!-- 90% -->
</ChannelModifier>

Loading and Saving Modifiers

Save XML

// From channelmodifier.cpp:100-137
QFile::FileError ChannelModifier::saveXML(const QString &fileName) const
{
    if (fileName.isEmpty())
        return QFile::OpenError;
    
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly))
        return file.error();
    
    QXmlStreamWriter doc(&file);
    doc.setAutoFormatting(true);
    doc.writeTextElement(KXMLQLCChannelModName, m_name);
    
    for (int i = 0; i < m_map.count(); i++)
    {
        QPair<uchar, uchar> mapElement = m_map.at(i);
        doc.writeStartElement(KXMLQLCChannelModHandler);
        doc.writeAttribute(KXMLQLCChannelModOriginalDMX, 
                          QString::number(mapElement.first));
        doc.writeAttribute(KXMLQLCChannelModModifiedDMX, 
                          QString::number(mapElement.second));
        doc.writeEndElement();
    }
    
    file.close();
    return QFile::NoError;
}

Load XML

// From channelmodifier.cpp:139-212
QFile::FileError ChannelModifier::loadXML(const QString &fileName, Type type)
{
    if (fileName.isEmpty())
        return QFile::OpenError;
    
    QXmlStreamReader *doc = QLCFile::getXMLReader(fileName);
    if (doc == NULL || doc->hasError())
        return QFile::ReadError;
    
    QList<QPair<uchar, uchar>> modMap;
    
    while (!doc->atEnd())
    {
        if (doc->name() == KXMLQLCChannelModName)
        {
            setName(doc->readElementText());
        }
        else if (doc->name() == KXMLQLCChannelModHandler)
        {
            QPair<uchar, uchar> dmxPair(0, 0);
            QXmlStreamAttributes attrs = doc->attributes();
            if (attrs.hasAttribute(KXMLQLCChannelModOriginalDMX))
                dmxPair.first = attrs.value(KXMLQLCChannelModOriginalDMX)
                                     .toString().toUInt();
            if (attrs.hasAttribute(KXMLQLCChannelModModifiedDMX))
                dmxPair.second = attrs.value(KXMLQLCChannelModModifiedDMX)
                                      .toString().toUInt();
            modMap.append(dmxPair);
        }
    }
    
    if (modMap.count() > 0)
    {
        setType(type);
        setModifierMap(modMap);
    }
    
    return QFile::NoError;
}

Value Retrieval

At runtime, modifiers transform values:
// From channelmodifier.cpp:95-98
uchar ChannelModifier::getValue(uchar dmxValue) const
{
    return m_values.at(dmxValue);
}
The pre-calculated lookup table (m_values) provides fast O(1) value transformation.

Practical Examples

Example 1: Fix LED Dimming

Problem: LED fixture jumps from 0 to bright at low values. Solution: Create a modifier that spreads low values:
<ChannelModifier>
  <Name>Smooth LED Dimmer</Name>
  <Handler Original="0" Modified="0"/>
  <Handler Original="10" Modified="30"/>
  <Handler Original="50" Modified="80"/>
  <Handler Original="128" Modified="150"/>
  <Handler Original="255" Modified="255"/>
</ChannelModifier>

Example 2: Match Fixture Brightness

Problem: Two fixtures of different models; one is brighter than the other at the same DMX value. Solution: Reduce maximum output of the brighter fixture:
<ChannelModifier>
  <Name>Reduce Brightness 20%</Name>
  <Handler Original="0" Modified="0"/>
  <Handler Original="255" Modified="204"/>  <!-- 80% of 255 -->
</ChannelModifier>

Example 3: Reverse Pan Direction

Problem: Moving head mounted backwards needs inverted pan. Solution: Simple inversion:
<ChannelModifier>
  <Name>Invert Pan</Name>
  <Handler Original="0" Modified="255"/>
  <Handler Original="255" Modified="0"/>
</ChannelModifier>
Apply to the Pan channel only; Tilt remains normal.

Example 4: Limit Range for Safety

Problem: Don’t want a motor-driven effect to reach extreme positions. Solution: Compress to safe range (e.g., 25%-75%):
<ChannelModifier>
  <Name>Safe Range Limit</Name>
  <Handler Original="0" Modified="64"/>    <!-- 25% -->
  <Handler Original="255" Modified="191"/>  <!-- 75% -->
</ChannelModifier>

Modifier Storage in Projects

Channel modifiers are saved per-fixture in the project XML:
<Fixture ID="1" Name="LED Par 1" Universe="0" Address="0" Channels="4">
  <Modifier Channel="0" Name="Smooth LED Dimmer"/>
</Fixture>
The modifier definition itself is stored in:
  • System templates: QLC+ installation directory
  • User templates: User configuration directory

Advanced Techniques

Multi-Point Curves

Use many control points for precise curves:
<ChannelModifier>
  <Name>Precise Curve</Name>
  <Handler Original="0" Modified="0"/>
  <Handler Original="10" Modified="25"/>
  <Handler Original="20" Modified="45"/>
  <Handler Original="40" Modified="75"/>
  <Handler Original="80" Modified="120"/>
  <Handler Original="128" Modified="160"/>
  <Handler Original="192" Modified="210"/>
  <Handler Original="255" Modified="255"/>
</ChannelModifier>

Step Functions

Create discrete steps (though interpolation makes true steps difficult):
<ChannelModifier>
  <Name>Quarter Steps</Name>
  <Handler Original="0" Modified="0"/>
  <Handler Original="63" Modified="0"/>
  <Handler Original="64" Modified="85"/>
  <Handler Original="127" Modified="85"/>
  <Handler Original="128" Modified="170"/>
  <Handler Original="191" Modified="170"/>
  <Handler Original="192" Modified="255"/>
  <Handler Original="255" Modified="255"/>
</ChannelModifier>

Best Practices

  • Test with actual fixtures before using in production
  • Use QLC+‘s DMX monitor to verify output values
  • Document why each modifier was created
  • Keep backup copies of working modifiers
  • Use descriptive names: “Invert Pan”, “Smooth Dimmer”
  • Include fixture model if fixture-specific
  • Reference the channel: “Red Channel Boost”
  • Date complex modifiers: “LED Bar Fix 2024-03”
  • Always define handlers at 0 and 255
  • Use more points for complex curves
  • Space points where curve changes most
  • Test edge cases (0, 1, 254, 255)
  • Save useful modifiers as templates
  • Create a library of common corrections
  • Share modifiers with team members
  • Document parameters in the modifier name

Troubleshooting

Modifier Not Applied

  • Verify modifier is assigned to correct channel number
  • Check that fixture has the channel you’re modifying
  • Reload the project or refresh fixtures

Unexpected Output Values

  • Review all control points in the modifier
  • Check for typos in Original/Modified values
  • Remember: values are interpolated between points
  • Use DMX monitor to see actual output

Performance Issues

Modifiers use pre-calculated lookup tables, so performance impact is minimal. If you experience issues:
  • Check total number of fixtures with modifiers
  • Verify QLC+ version supports modifiers properly
  • Test with fewer fixtures to isolate the problem

Next Steps