Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/extensions/qlcplus-dev/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions MANUAL_REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,12 @@ Navigate to Virtual Console with VC editing access:
- **Song Manager — artist/BPM/key metadata:** Artist, BPM, and key fields in the song list are placeholders. The Show name embeds "Artist - Title" but structured extraction is not yet implemented.
- **Song Manager — folder path:** Songs are identified by Shows in the `Songs/` function folder. Manually placing non-song Shows in that folder will cause them to appear in the Song Manager.
- **Page input modes — multiple Normal pages:** If multiple Normal pages define mappings for the same MIDI channel/key, all matching Normal pages will fire when Inherit-mode fallback triggers. This is by design (they all contribute to the "global pool"), but can cause double-triggers if the same widget binding exists on two Normal pages.
- **HUEMatrix — existing workspaces lose audio algorithms:** The 41 HSV audio scripts moved from `resources/rgbscripts/` to `resources/huescripts/`, so pre-existing `RGBMatrix` functions that referenced them no longer resolve (56 functions across `GARAGE.qxw`, `LOADDDD.qxw`, `G2.qxw`). A warning naming both the script and the function is emitted on load. By design — no automatic migration; recreate them as `HUEMatrix` functions.
- **HUEMatrix — fork-only `<AudioProfileID>` is dropped from RGBMatrix:** `RGBMatrix` is now byte-identical to upstream, which does not know that tag, so it is discarded on load with an `Unknown RGB matrix tag` warning (5 functions in `GARAGE.qxw`). Reassign the audio profile on the recreated `HUEMatrix`.
- **HUEMatrix — "Audio Spectrum" script renamed:** The HSV script that shadowed the built-in `RGBAudio` algorithm is now "Audio Spectrum Bars", so the built-in is reachable again by name. Workspaces storing the old script name will not resolve it.
- **HUEMatrix — VC Animation widget icon:** A `HUEMatrix` can be assigned to a Virtual Console Animation widget (`HUEMatrix` IS-A `RGBMatrix`), but the widget icon is unconditionally the RGB Matrix icon. Cosmetic only.
- **HUEMatrix — editor algorithm list is not covered by tests:** `qmlui` builds an executable rather than a library and `FunctionEditor` depends on `Tardis`, so the editor cannot be constructed in a unit test. `HUEMatrixEditor::algorithms()` delegating to `HUEMatrix::availableAlgorithms()` is verified only at the cache boundary — §23.1 covers it manually.
- **HUEMatrix — shutdown drain:** Destroying a `HUEMatrix` waits up to 2 s per object for an in-flight async precompute task that never ran, then warns and continues. Many stuck matrices would add up at shutdown; not observed in practice.

---

Expand Down Expand Up @@ -1059,3 +1065,54 @@ the intended hardware.
| Area | Tester | Date | Pass / Fail | Notes |
|------|--------|------|-------------|-------|
| Live audio BPM, silence, beat timing, Aubio continuity, external lock | | | | §22.1–22.3 |
---

## 23. HUEMatrix fork and RGBMatrix upstream restore

> **Automated coverage:** `huematrix_test` (42 cases) owns the HSV `Float32Array`
> contract, the dual packed-uint contract, fork-property in-memory and XML
> round-trips, algorithm-list separation, icon-site enumeration, built-in
> reachability, bounded destructor drain, async-precompute generation checks,
> per-tick recompute for audio algorithms, and the unavailable-algorithm and
> `AudioProfileID` load warnings. `rgbmatrix_test` (9) and `rgbscript_test` (14)
> are upstream's own suites, unmodified, and prove the restore.
> `mcp_rgb_transform_test` (15) covers rotation/mirror/beat spatially.
> Do not repeat those mechanical assertions here.
>
> `engine/src/rgbmatrix.cpp` is byte-identical to `upstream/master`
> (`git diff upstream/master -- engine/src/rgbmatrix.cpp` is empty). The items
> below are the visual, hardware, and workspace-migration judgments automation
> cannot make.

### 23.1 Both matrix types are selectable and distinct [LOW RISK]

- ☐ Open the Add Function menu → both "RGB Matrix" and "HUE Matrix" are offered, with visibly different icons
- ☐ Filter the function tree by each type in turn → the two filter buttons show different icons and each lists only its own functions
- ☐ Create a HUE Matrix and open its editor → the algorithm dropdown lists the 41 audio effects **and** the upstream stock patterns
- ☐ Create an RGB Matrix and open its editor → the algorithm dropdown lists **only** upstream stock patterns, with no `Audio *` entries

### 23.2 RGBMatrix behaves as pristine upstream [MEDIUM RISK]

- ☐ Run several stock patterns (Stripes, Plasma, Gradient, Fireworks) on an RGB Matrix and compare against expected upstream behaviour → colour, motion, and step timing show no fork-specific artifacts
- ☐ Confirm the fork-only controls (rotation, mirror, beat effect, brightness, RGBW control modes) are **absent** from the RGB Matrix editor → they belong to HUE Matrix only

### 23.3 HUEMatrix audio effects on hardware [DMX] [MEDIUM RISK]

- ☐ Play familiar material through a HUE Matrix running Aurora, Equalizer, Fire, and Water → hue, saturation, and value read correctly on the physical fixture, with no banding or clipping introduced by the HSV→RGB conversion
- ☐ Exercise rotation, mirror + mirror blend, and a beat effect while audio is running → transforms compose with the audio response without tearing, stutter, or dropped frames
- ☐ Select the built-in "Audio Spectrum" (not the "Audio Spectrum Bars" script) on a HUE Matrix → it renders as the built-in algorithm

### 23.4 Existing workspace migration [HIGH RISK]

- ☐ Load a workspace that predates the fork (e.g. `GARAGE.qxw`) with the console visible → warnings name each function that lost its algorithm, and each dropped `AudioProfileID`; the app does not crash
- ☐ Recreate one affected effect as a HUE Matrix and confirm it renders as before → the migration path is workable by hand
- ☐ Save and reload the migrated workspace → the HUE Matrix keeps its algorithm, transforms, beat settings, and control mode

### 23.5 Sign-off

| Area | Tester | Date | Pass / Fail | Notes |
|------|--------|------|-------------|-------|
| Type selection and editor lists | | | | §23.1 |
| RGBMatrix upstream parity | | | | §23.2 |
| HUEMatrix audio on hardware | | | | §23.3 |
| Legacy workspace migration | | | | §23.4 |
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
> - **Enhanced Function Wizard** — rainbow/warm/cool palettes, beat-synced chasers, movement patterns, prism/focus/zoom detection, per-fixture VC pages, QLCPalette generation
> - Launchpad controller integration support
> - Audio capture / BPM detection for scripts
> - **22 audio-reactive RGB scripts** (LedFX-ported atmospheric effects, strobes, motion, EQ visualizers)
> - **RGB Matrix rotation & mirroring** (engine-level, all algorithm types)
> - **RGB Matrix low-latency step transitions** (~3ms vs ~22ms previously)
> - **41 audio-reactive HSV scripts** (LedFX-ported atmospheric effects, strobes, motion, EQ visualizers) — in `resources/huescripts/`, offered to **HUE Matrix** only
> - **HUE Matrix — a new function type forked from RGB Matrix.** `RGBMatrix` is restored byte-identical to upstream QLC+; all fork behaviour (HSV script contract, rotation/mirror, beat transforms, brightness, RGBW modes, audio scripts) lives in `HUEMatrix`, which inherits from it. Both are selectable side by side.
> - **HUE Matrix rotation & mirroring** (engine-level, all algorithm types)
> - **HUE Matrix low-latency step transitions** (~3ms vs ~22ms previously)
> - **Blend mode ordering fix** for Mask/Subtractive blend modes
> - **Enhanced OS2L plugin** — Bonjour/mDNS auto-discovery, song metadata, connection status LED, web diagnostics dashboard
> - **Auto-reload last workspace** on startup (no `--openlast` flag needed)
Expand All @@ -42,7 +43,7 @@
> - **FineFractions for all editors** — RGB Matrix, EFX, Scene, and Speed Dial preset editors now show 1/4, 1/8, 1/16 beat subdivisions (previously limited to 1/1 and 1/2)
> - **Update Scene from Live** — DMX Dump dialog: "Update only scene channels from live" button captures current pre-GM DMX values into an existing scene, scoped to only the channels already in the scene (preserves layer separation, with Tardis undo)
> - **Speed Dial multiply mode** — factor buttons (1/16x–16x) multiply existing function speeds instead of replacing them; preserves authored fadeIn/hold/fadeOut ratios; one-click reset to originals; works with both Time and Beats mode functions
> - **RGB Matrix RGBW mode** — new `RGBW (Accurate)` and `RGBW (Brighter)` control modes drive R, G, B, AND White channels simultaneously. Accurate extracts white (`W=min(R,G,B)`, subtract from RGB); Brighter keeps RGB full and adds white on top. Works with any RGBW fixture — not fixture-specific.
> - **HUE Matrix RGBW mode** — `RGBW (Accurate)` and `RGBW (Brighter)` control modes drive R, G, B, AND White channels simultaneously. Accurate extracts white (`W=min(R,G,B)`, subtract from RGB); Brighter keeps RGB full and adds white on top. Works with any RGBW fixture — not fixture-specific.
> - **Keyboard shortcuts** — 20+ shortcuts ported from v4: Ctrl+N/O/S (New/Open/Save), Ctrl+Z/Shift+Z (Undo/Redo), Ctrl+Shift+Esc (Panic/Stop All), F11 (Fullscreen), Alt+1–6 (view switching), Ctrl+PgUp/PgDown (cycle views), Ctrl+[/] (drawer toggle), Function Manager (Delete/Clone/Wizard), Show Manager (Space/Ctrl+Space play/stop, copy/paste). Platform-aware tooltips (⌘ on macOS). Guards for text editing, popups, kiosk mode.
> - **DDP multi-universe sync fix** — eliminated frame-queue desync that caused 4+ DDP universes to display out of order. Replaced unreliable cross-thread batching with immediate per-universe send (sub-millisecond gap, PUSH per universe — matches Art-Net behavior).
>
Expand All @@ -64,8 +65,9 @@
> 1/1 and 1/2 beat subdivisions (`ByTwoFractions`). All four now use `FineFractions`,
> exposing the full 1/1, 1/2, 1/4, 1/8, 1/16 range — matching Chaser editor behavior.
>
> #### RGB Matrix RGBW Control Modes
> Two new control modes for RGBW fixtures:
> #### HUE Matrix RGBW Control Modes
> Two control modes for RGBW fixtures, available on **HUE Matrix** (`RGBMatrix` is
> byte-identical to upstream and does not offer them):
>
> | Mode | Algorithm | Best for |
> |------|-----------|----------|
Expand Down Expand Up @@ -101,9 +103,10 @@
> Each universe's data is sent as soon as it arrives (PUSH flag per universe).
> Inter-universe gap is sub-millisecond. Matches Art-Net behavior (no sync packet).
>
> #### RGB Matrix Rotation & Mirroring
> Rotation and mirroring are now engine-level properties on `RGBMatrix`, available
> for **all** algorithm types (Plain, Script, Text, Image, Audio).
> #### HUE Matrix Rotation & Mirroring
> Rotation and mirroring are engine-level properties on `HUEMatrix`, available
> for **all** algorithm types (Plain, Script, Text, Image, Audio). They are not
> present on `RGBMatrix`, which is byte-identical to upstream.
>
> | Property | Values | Description |
> |----------|--------|-------------|
Expand Down Expand Up @@ -794,6 +797,10 @@ cd build
# Engine tests (beat quantization)
cmake --build . --target beatquantize_test -j8 && ./engine/test/beatquantize/beatquantize_test

# HUE Matrix / RGB Matrix (run from each suite's own build dir - resource paths are cwd-relative)
cmake --build . --target huematrix_test -j8 && (cd engine/test/huematrix && ./huematrix_test)
cmake --build . --target rgbmatrix_test -j8 && (cd engine/test/rgbmatrix && ./rgbmatrix_test)

# MCP tests
cmake --build . --target mcp_conversions_test -j8 && ./mcp/test/mcp_conversions_test
cmake --build . --target mcp_vc_query_filter_test -j8 && ./mcp/test/mcp_vc_query_filter_test
Expand All @@ -806,6 +813,10 @@ cmake --build . --target mcp_vc_validation_test -j8 && ./mcp/test/mcp_vc_validat
| `mcp_conversions_test` | 43 | Beat string parsing/formatting, decimal precision, off-grid snapping, GCD reduction, round-trips |
| `mcp_vc_query_filter_test` | — | VC widget query filtering and pagination |
| `mcp_vc_validation_test` | — | Widget type/field validation |
| `huematrix_test` | 42 | HUE Matrix: HSV `Float32Array` contract, dual packed-uint contract, algorithm-list separation, fork properties + XML round-trip, icon-site enumeration, bounded destructor drain, async precompute, per-tick audio recompute, load warnings |
| `rgbmatrix_test` | 9 | Upstream's own suite, unmodified — proves the `RGBMatrix` restore |
| `rgbscript_test` | 14 | Upstream's own suite, unmodified — packed-uint script contract |
| `mcp_rgb_transform_test` | 15 | Rotation, mirror + blend, and beat transforms (spatial) |

### E2E tests (Web DMX Control Panel)

Expand Down
5 changes: 5 additions & 0 deletions engine/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ add_library(${module_name} SHARED
qlcphysical.cpp qlcphysical.h
qlcpoint.cpp qlcpoint.h
rgbalgorithm.cpp rgbalgorithm.h
rgbaudio.cpp rgbaudio.h
huematrix.cpp huematrix.h
huescript.cpp huescript.h
huescriptscache.cpp huescriptscache.h
huecolor.h jsthread_p.h
rgbimage.cpp rgbimage.h
rgbmatrix.cpp rgbmatrix.h
rgbplain.cpp rgbplain.h
Expand Down
10 changes: 10 additions & 0 deletions engine/src/doc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "oscaudiosource.h"
#include "audiochannel.h"
#include "rgbscriptscache.h"
#include "huescriptscache.h"
#include "channelsgroup.h"
#include "scriptwrapper.h"
#include "collection.h"
Expand Down Expand Up @@ -68,6 +69,7 @@ Doc::Doc(QObject* parent, int universes)
, m_fixtureDefCache(new QLCFixtureDefCache)
, m_modifiersCache(new QLCModifiersCache)
, m_rgbScriptsCache(new RGBScriptsCache(this))
, m_hueScriptsCache(new HUEScriptsCache(this))
, m_ioPluginCache(new IOPluginCache(this))
, m_audioPluginCache(new AudioPluginCache(this))
, m_masterTimer(new MasterTimer(this))
Expand Down Expand Up @@ -128,6 +130,9 @@ Doc::~Doc()

delete m_rgbScriptsCache;
m_rgbScriptsCache = NULL;

delete m_hueScriptsCache;
m_hueScriptsCache = NULL;
}

void Doc::clearContents()
Expand Down Expand Up @@ -276,6 +281,11 @@ RGBScriptsCache* Doc::rgbScriptsCache() const
return m_rgbScriptsCache;
}

HUEScriptsCache* Doc::hueScriptsCache() const
{
return m_hueScriptsCache;
}

IOPluginCache* Doc::ioPluginCache() const
{
return m_ioPluginCache;
Expand Down
4 changes: 4 additions & 0 deletions engine/src/doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class AudioCapture;
class AudioAnalyzer;
class OscAudioSource;
class RGBScriptsCache;
class HUEScriptsCache;
class AudioPluginCache;
class MonitorProperties;

Expand Down Expand Up @@ -138,6 +139,8 @@ class Doc final : public QObject
/** Get the RGB scripts cache object */
RGBScriptsCache *rgbScriptsCache() const;

HUEScriptsCache *hueScriptsCache() const;

/** Get the I/O plugin cache object */
IOPluginCache *ioPluginCache() const;

Expand Down Expand Up @@ -169,6 +172,7 @@ class Doc final : public QObject
QLCFixtureDefCache *m_fixtureDefCache;
QLCModifiersCache *m_modifiersCache;
RGBScriptsCache *m_rgbScriptsCache;
HUEScriptsCache *m_hueScriptsCache;
IOPluginCache *m_ioPluginCache;
AudioPluginCache *m_audioPluginCache;
MasterTimer *m_masterTimer;
Expand Down
17 changes: 17 additions & 0 deletions engine/src/function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "mastertimer.h"
#include "collection.h"
#include "rgbmatrix.h"
#include "huematrix.h"
#include "function.h"
#include "sequence.h"
#include "chaser.h"
Expand All @@ -50,6 +51,7 @@ const QString KEFXString ( "EFX" );
const QString KCollectionString ( "Collection" );
const QString KScriptString ( "Script" );
const QString KRGBMatrixString ( "RGBMatrix" );
const QString KHUEMatrixString ( "HUEMatrix" );
const QString KShowString ( "Show" );
const QString KSequenceString ( "Sequence" );
const QString KAudioString ( "Audio" );
Expand Down Expand Up @@ -248,6 +250,7 @@ QString Function::typeToString(Type type)
case CollectionType: return KCollectionString;
case ScriptType: return KScriptString;
case RGBMatrixType: return KRGBMatrixString;
case HUEMatrixType: return KHUEMatrixString;
case ShowType: return KShowString;
case SequenceType: return KSequenceString;
case AudioType: return KAudioString;
Expand All @@ -272,6 +275,8 @@ Function::Type Function::stringToType(const QString& string)
return ScriptType;
else if (string == KRGBMatrixString)
return RGBMatrixType;
else if (string == KHUEMatrixString)
return HUEMatrixType;
else if (string == KShowString)
return ShowType;
else if (string == KSequenceString)
Expand Down Expand Up @@ -1117,6 +1122,8 @@ bool Function::loader(QXmlStreamReader &root, Doc* doc)
function = new class Script(doc);
else if (type == Function::RGBMatrixType)
function = new class RGBMatrix(doc);
else if (type == Function::HUEMatrixType)
function = new class HUEMatrix(doc);
else if (type == Function::ShowType)
function = new class Show(doc);
else if (type == Function::SequenceType)
Expand All @@ -1136,6 +1143,16 @@ bool Function::loader(QXmlStreamReader &root, Doc* doc)
function->setLastEdited(lastEdited);
if (function->loadXML(root) == true)
{
// A matrix whose stored algorithm is unavailable to its type loads
// silently to nothing; say which function lost its algorithm. The
// missing algorithm's name is reported by the scripts cache.
RGBMatrix *matrix = qobject_cast<RGBMatrix*> (function);
if (matrix != NULL && matrix->algorithm() == NULL)
qWarning() << "Function" << name << "of type"
<< Function::typeToString(type)
<< "lost its algorithm: the stored algorithm is not"
<< "available to this function type.";

if (doc->addFunction(function, id) == true)
{
/* Success */
Expand Down
3 changes: 2 additions & 1 deletion engine/src/function.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ class Function : public QObject
ShowType = 1 << 6,
SequenceType = 1 << 7,
AudioType = 1 << 8,
VideoType = 1 << 9
VideoType = 1 << 9,
HUEMatrixType = 1 << 10
};
Q_ENUM(Type)

Expand Down
Loading
Loading