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
Binary file added docs/assets/how-to/multi-board/qConControl.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 27 additions & 11 deletions docs/how-to/multi-board-installation.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
# Building a multi-device installation
# The StadBeest: a multi-device installation

> Saw the StadBeest at the Museumnacht? [Leave a message](https://tally.so/r/68X5xO), or join us on [Discord](https://discord.gg/TC8NSUSCdV) or [Reddit](https://reddit.com/r/moonmodules).
**Meet the StadBeest**, a creature made of what the city throws away.
Its body is a chandelier on ten legs of light, its eyes sit in 3D-printed light bulbs, and its mouth is an ordinary WiFi lamp.
It listens to the music around it, takes a step and looks around on every beat, and picks its own colors and moods through the night.
A handful of small computers run it together on MoonLight, free lighting software made by volunteers.

<video src="../assets/how-to/multi-board/stadbeest.mp4" width="300" autoplay loop muted playsinline title="The StadBeest walking: ten glowing legs on a chandelier body and two ring-disc eyes"></video>

**What did you think of it?** A few words are enough, and we read every one.

[:material-message-text: Leave us a message](https://tally.so/r/68X5xO){ .md-button .md-button--primary } &nbsp; [:fontawesome-brands-discord: Discord](https://discord.gg/TC8NSUSCdV){ .md-button } &nbsp; [:fontawesome-brands-reddit: Reddit](https://reddit.com/r/moonmodules){ .md-button }

**Curious how it is made, or want to build your own?** Read on for the whole recipe, from the hardware to the code.

---

An installation is several MoonLight devices that act as one piece: one creature, one sculpture, one room.
The worked example is the StadBeest, a walking beast whose legs, eyes and mouth run on four devices.
The worked example is the StadBeest, whose legs, eyes and mouth run on devices of their own.
Each section names a choice that makes it work; borrow what fits your own piece.

> **Start simple.** The StadBeest shows most of what MoonLight can do in one piece, so it can look overwhelmingly complex.
Expand All @@ -15,8 +28,6 @@ Each section names a choice that makes it work; borrow what fits your own piece.
>
> Start with the first, see it run, and add the next step only when you miss it. The autopilot, the desk, the looks and the mouth came last, one at a time.

<video src="../assets/how-to/multi-board/stadbeest.mp4" width="300" autoplay loop muted playsinline title="The StadBeest walking: ten glowing legs on a chandelier body and two ring-disc eyes"></video>

> New here? Start with **[Install & first light](../gettingstarted.md)** and **[your first script](../tutorials/first-script.md)**. What follows assumes devices that run MoonLight on one network.

---
Expand Down Expand Up @@ -170,14 +181,15 @@ A slot assigned by hand keeps its target, which is how the look switches and the
| `fader1` | `Drivers.brightness` | `Drivers.brightness` | `Drivers.brightness` |
| `fader2` | `bpm` | `bpm` | `bpm` |
| `fader3` | `sensitivity` | `sensitivity` | `sensitivity` |
| `fader4` and on | the look's next numbers | the look's next numbers | `rest` |
| `fader4` to `fader7` | the look's next numbers | the look's next numbers | `rest` |
| `fader8` | autopilot `seconds` | | |
| `encoder1` | `Drivers.palette` | `Drivers.palette` | `Drivers.palette` |
| `encoder2` and on | the look's choices, such as the orb's `shape` | the same | |

A slot holds the value of the control it drives, so `fader2` at 33 means `bpm` 33.
Each look is an effect of its own on every device, named after its script without the `stadbeest-` prefix, and `switch3` to `switch5` turn them on and off through their `enabled`.
So one switch changes the look on the legs and on both eyes at once, and the desk follows it.
Assign a slot by hand from the card, or with `POST /api/control` and `{"module":"Control","control":"switch3Target","value":"walk.enabled"}`.
Assign a slot by hand from the Control card.

---

Expand Down Expand Up @@ -246,9 +258,9 @@ A [MoonLiveService](../moonmodules/core/services.md#moonliveservice) on the lead

- **Scenes:** every `seconds`, three minutes by default, it picks a new scene.
A scene moves on to the next look, walking, the orb or the thrusters, by switching `switch3` to `switch5`.
It picks a built-in palette, or for the thrusters [`stadbeest-fire.mlp`](https://github.com/MoonModules/MoonLight/blob/main/moonlive/palettes/stadbeest-fire.mlp), a mood from calm to wild, how much the music counts and `pulse` on or off.
It picks a built-in palette, in a third of the thruster scenes [`stadbeest-fire.mlp`](https://github.com/MoonModules/MoonLight/blob/main/moonlive/palettes/stadbeest-fire.mlp), a mood from calm to wild, how much the music counts and `pulse` on or off.
That palette is computed every frame: louder music burns hotter, a beat flares the hot end white, and the flames flicker.
A palette has the same number on every device, since every factory palette keeps its place in the list whether the device holds it or not: `stadbeest-fire` is 64.
A palette has the same number on every device, so the eyes and the mouth pick the same one.
- **Breathing:** within a scene the energy rises and falls once a minute, through `fader2`, the tempo of whatever look runs.
- **Through the surface:** it writes the shared slots with `setControl("fader2", value)`, exactly as a person moves a fader.
The legs follow on the leader, and the eyes and the mouth over OSC.
Expand Down Expand Up @@ -277,7 +289,8 @@ The browser is the bridge between desk and device, so the laptop keeps the leade

| APC40 | the StadBeest |
|---|---|
| track faders 1-8 | brightness, then the look's `bpm`, `sensitivity` and its own numbers |
| track faders 1-7 | brightness, then the look's `bpm`, `sensitivity` and its own numbers |
| track fader 8 | the autopilot's `seconds`, how long a scene lasts |
| track knobs 1-8 | the palette, then the look's choices and the numbers past the faders |
| activator 1 | on and off |
| activator 2 | `pulse`, legs and eyes together |
Expand All @@ -304,7 +317,10 @@ The bridge is a cable over RTP-MIDI, the standard for MIDI on a network, and kee
So the desk works on the leader as if plugged into it.
The leader greets it, moves its motors, lights its pads with its presets and holds a motor still under a hand.
A Mackie desk's display shows the leader's display line: the last change for five seconds, then the device's name.
An APC40 mkII takes its power from USB, which this board's port does not supply, so it stays on the laptop or goes through an active, powered USB hub. A hub needs the firmware's hub support, `CONFIG_USB_HOST_HUBS_SUPPORTED`, switched on in the S3 images.

<img src="../assets/how-to/multi-board/qConControl.jpg" width="300" alt="The iCON QCon through the bridge: its display reads MoonLight, its motorized faders stand at the leader's levels, and two pads glow red">

An APC40 mkII takes its power from USB, which this board's port does not supply, so it stays on the laptop or goes through an active, powered USB hub.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/moonmodules/core/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
2. Enter the device's address, such as `http://192.168.1.158`, set the flag to Enabled, and click Relaunch.
3. Reload the device's page: Chrome now asks to use MIDI devices.

The decoding follows Mackie Control: a fader is 14-bit pitch bend on its own channel, its touch sensor a note from 0x68, a knob a relative turn on a control change from 0x10, and SELECT a note from 0x18. The master fader has no slot on the surface and is ignored. The way back uses the same messages: pitch bend moves a motor, a SELECT note at full velocity lights its button, and a control change from 0x30 fills a knob's ring. The device keeps what the desk should show and pushes it up to 25 times a second; the browser sends only what changed, so a page opened later moves the motors to where the surface already is.
The decoding follows Mackie Control: a fader is 14-bit pitch bend on its own channel, its touch sensor a note from 0x68, a knob a relative turn on a control change from 0x10, and SELECT a note from 0x18. The master fader has no slot on the surface and is ignored. The way back uses the same messages: pitch bend moves a motor, a SELECT note at full velocity lights its button, and a control change from 0x30 fills a knob's ring over the range of what the knob drives, so it is full at that control's top. The device keeps what the desk should show and pushes it up to 25 times a second; the browser sends only what changed, so a page opened later moves the motors to where the surface already is.

Check warning on line 191 in docs/moonmodules/core/services.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐢 [MoonLight.SentenceLength] Sentence runs past 30 words; it is probably two thoughts joined by a comma. Raw Output: {"message":"[MoonLight.SentenceLength] Sentence runs past 30 words; it is probably two thoughts joined by a comma.","location":{"path":"docs/moonmodules/core/services.md","range":{"start":{"line":191,"column":506},"end":{"line":191,"column":509}}},"severity":"INFO","code":{"value":"MoonLight.SentenceLength"}}

Check warning on line 191 in docs/moonmodules/core/services.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐢 [MoonLight.SentenceLength] Sentence runs past 30 words; it is probably two thoughts joined by a comma. Raw Output: {"message":"[MoonLight.SentenceLength] Sentence runs past 30 words; it is probably two thoughts joined by a comma.","location":{"path":"docs/moonmodules/core/services.md","range":{"start":{"line":191,"column":265},"end":{"line":191,"column":268}}},"severity":"INFO","code":{"value":"MoonLight.SentenceLength"}}

Check warning on line 191 in docs/moonmodules/core/services.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐢 [MoonLight.SentenceLength] Sentence runs past 30 words; it is probably two thoughts joined by a comma. Raw Output: {"message":"[MoonLight.SentenceLength] Sentence runs past 30 words; it is probably two thoughts joined by a comma.","location":{"path":"docs/moonmodules/core/services.md","range":{"start":{"line":191,"column":1},"end":{"line":191,"column":4}}},"severity":"INFO","code":{"value":"MoonLight.SentenceLength"}}

**The Akai APC40 mkII profile.** The browser greets the desk with the SysEx that puts it in Alternate Ableton Live mode, where every light is the host's. Chrome asks once to allow SysEx for it. Track faders 1-8 move the surface's faders and track knobs 1-8 set its encoders, each reading 0 to 127. Activator buttons 1-8 flip its switches. The 40 clip pads apply presets 1-40, the top-left pad being preset 1 as on the Control card. The way back lights each activator while its switch is on and fills each knob's ring like a meter. Each pad is dark when empty, dim white when a preset is stored, and green for the one applied. The faders have no motors. Every message is in [the APC40 reference](../../reference/hardware/control-surfaces.md#akai-apc40-mkii).

Expand Down
14 changes: 7 additions & 7 deletions docs/reference/metrics/code.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Generated by [`moondeck/check/check_code.py`](../../../moondeck/check/check_code

Every function, block and file over a limit the [coding standards](../../contributing/coding-standards.md#static-checks) set. Current state only: the trend is this file's git history. The list only shrinks, per rule and in total, and a touched file clears its own rows.

**1620 finding(s)** across 318 file(s). 2.88% of all lines are duplicated, 3735 lines.
**1620 finding(s)** across 318 file(s). 2.88% of all lines are duplicated, 3733 lines.

## By rule

Expand Down Expand Up @@ -103,7 +103,7 @@ The next function worth simplifying is at the top of each file's list.
| `test/unit/light/unit_Effects_container.cpp` | 10 | `lines 188-199, also in test/unit/light/unit_Effects_container.cpp:139`, duplicated block 12 |
| `src/core/services/InfraredService.h` | 9 | `lines 113-135, also in src/core/services/GamepadService.h:108`, duplicated block 23 |
| `src/core/services/OscModule.h` | 9 | `OscModule::handle`, deeply nested 5 |
| `src/core/system/ControlModule.h` | 9 | `ControlModule::driveSwitch`, complex function 15 |
| `src/core/system/ControlModule.h` | 9 | `ControlModule::moveListRow`, complex function 15 |
| `src/light/drivers/PanelCardDriver.h` | 9 | `lines 187-195, also in src/light/drivers/NetworkSendDriver.h:210`, duplicated block 9 |
| `test/unit/light/unit_ParallelLedDriver_swap.cpp` | 9 | `lines 176-184, also in test/unit/light/unit_ParallelLedDriver_swap.cpp:112`, duplicated block 9 |
| `src/core/system/WiFiModule.h` | 8 | `lines 352-359, also in src/light/drivers/FixtureProfilesModule.h:245`, duplicated block 8 |
Expand Down Expand Up @@ -132,6 +132,7 @@ The next function worth simplifying is at the top of each file's list.
| `src/light/drivers/FixtureProfilesModule.h` | 6 | `lines 245-252, also in src/core/system/WiFiModule.h:352`, duplicated block 8 |
| `src/light/drivers/NetworkSendDriver.h` | 6 | `NetworkSendDriver::tick, line 190`, large stack frame 1760 |
| `src/light/layers/BlendMap.h` | 6 | `blendMap`, complex function 35 |
| `src/platform/desktop/moonlive_asm_x86_64.cpp` | 6 | `lines 557-564, also in src/platform/desktop/moonlive_asm_x86_64.cpp:515`, duplicated block 8 |
| `src/platform/esp32/platform_esp32_fs.cpp` | 6 | `lines 211-221, also in src/platform/esp32/platform_esp32_fs.cpp:161`, duplicated block 11 |
| `src/platform/esp32/platform_esp32_improv.cpp` | 6 | `platform::improvTask`, complex function 22 |
| `test/unit/core/unit_AccessPointModule.cpp` | 6 | `lines 340-348, also in test/unit/core/unit_AccessPointModule.cpp:311`, duplicated block 9 |
Expand All @@ -155,8 +156,6 @@ The next function worth simplifying is at the top of each file's list.
| `src/light/layouts/CarLightsLayout.h` | 5 | `lines 49-65, also in src/light/layouts/TorontoBarGourdsLayout.h:52`, duplicated block 17 |
| `src/light/layouts/RingLayout.h` | 5 | `lines 85-100, also in src/light/layouts/CarLightsLayout.h:95`, duplicated block 16 |
| `src/light/layouts/TorontoBarGourdsLayout.h` | 5 | `lines 52-68, also in src/light/layouts/CarLightsLayout.h:49`, duplicated block 17 |
| `src/platform/desktop/moonlive_asm_x86_64.cpp` | 5 | `lines 570-577, also in src/platform/desktop/moonlive_asm_x86_64.cpp:528`, duplicated block 8 |
| `src/platform/esp32/moonlive_asm_riscv.cpp` | 5 | `lines 40-47, also in src/platform/desktop/moonlive_asm_arm64.cpp:34`, duplicated block 8 |
| `src/platform/esp32/platform_esp32_h264.cpp` | 5 | `platform::hlsSegment`, complex function 26 |
| `test/unit/light/unit_Drivers_rendersplit.cpp` | 5 | `(static local variable) in tick`, blocking call on the render path 1 |
| `test/unit/light/unit_FireEffect.cpp` | 5 | `lines 29-42, also in test/unit/light/unit_RainbowEffect.cpp:69`, duplicated block 14 |
Expand Down Expand Up @@ -184,6 +183,7 @@ The next function worth simplifying is at the top of each file's list.
| `src/light/util/RtspSession.h` | 4 | `rtsp::parseTransport`, complex function 28 |
| `src/main.cpp` | 4 | `mm_main`, complex function 27 |
| `src/platform/desktop/main_desktop.cpp` | 4 | `main`, deeply nested 5 |
| `src/platform/esp32/moonlive_asm_riscv.cpp` | 4 | `lines 297-303, also in src/platform/esp32/moonlive_asm_xtensa.cpp:481`, duplicated block 7 |
| `test/js/ui-mooncloud.test.mjs` | 4 | `lines 6-17, also in test/js/installer-s31-webflash.test.mjs:11`, duplicated block 12 |
| `test/unit/light/unit_BlurzEffect.cpp` | 4 | `lines 137-155, also in test/unit/light/unit_GEQEffect.cpp:159`, duplicated block 19 |
| `test/unit/light/unit_BouncingBallsEffect.cpp` | 4 | `lines 75-88, also in test/unit/light/unit_RubiksCubeEffect.cpp:103`, duplicated block 14 |
Expand All @@ -208,7 +208,7 @@ The next function worth simplifying is at the top of each file's list.
| `src/light/effects/PongEffect.h` | 3 | `PongEffect::render`, complex function 15 |
| `src/light/effects/SolidEffect.h` | 3 | `SolidEffect::static_cast<lengthType>`, complex function 36 |
| `src/light/layouts/GridLayout.h` | 3 | `GridLayout in 86 files`, name spread 86 |
| `src/platform/desktop/moonlive_asm_arm64.cpp` | 3 | `lines 34-41, also in src/platform/esp32/moonlive_asm_riscv.cpp:40`, duplicated block 8 |
| `src/platform/desktop/moonlive_asm_arm64.cpp` | 3 | `lines 35-40, also in src/platform/esp32/moonlive_asm_riscv.cpp:34`, duplicated block 6 |
| `test/unit/core/unit_NetworkModule_ethernet.cpp` | 3 | `lines 276-285, also in test/unit/core/unit_NetworkModule_ethernet.cpp:193`, duplicated block 10 |
| `test/unit/light/unit_AuroraEffect.cpp` | 3 | `lines 40-48, also in test/unit/light/unit_PolarLut_equivalence.cpp:40`, duplicated block 9 |
| `test/unit/light/unit_MoonLiveLayout.cpp` | 3 | `lines 685-935, also in test/unit/light/unit_MoonLiveLayout.cpp:403`, duplicated block 251 |
Expand Down Expand Up @@ -346,7 +346,7 @@ The next function worth simplifying is at the top of each file's list.
| `src/platform/esp32/platform_config.h` | 1 | `lines 236-250, also in src/platform/desktop/platform_config.h:71`, duplicated block 15 |
| `src/platform/esp32/platform_esp32_i2s.cpp` | 1 | `platform::audioMicInit`, complex function 11 |
| `src/platform/esp32/platform_esp32_tasks.cpp` | 1 | `platform::taskSnapshot`, complex function 12 |
| `src/platform/platform.h` | 1 | `platform.h`, large file 1302 |
| `src/platform/platform.h` | 1 | `platform.h`, large file 1301 |
| `src/ui/install-picker.js` | 1 | `install-picker.js`, large file 1048 |
| `src/ui/preview3d.js` | 1 | `preview3d.js`, large file 1431 |
| `test/js/installer-eth-only.test.mjs` | 1 | `lines 13-23, also in test/js/ui-mooncloud.test.mjs:6`, duplicated block 11 |
Expand All @@ -360,7 +360,7 @@ The next function worth simplifying is at the top of each file's list.
| `test/js/ui-visibility.test.mjs` | 1 | `lines 7-16, also in test/js/ui-ws-scheme.test.mjs:13`, duplicated block 10 |
| `test/python/test_check_docgen.py` | 1 | `test_check_docgen.py`, large file 1596 |
| `test/scenario_runner.cpp` | 1 | `scenario_runner.cpp`, large file 1075 |
| `test/unit/core/unit_ControlModule.cpp` | 1 | `unit_ControlModule.cpp`, large file 2082 |
| `test/unit/core/unit_ControlModule.cpp` | 1 | `unit_ControlModule.cpp`, large file 2152 |
| `test/unit/core/unit_Control_apply_absent_key.cpp` | 1 | `lines 92-103, also in test/unit/core/unit_HttpServerModule_apply.cpp:40`, duplicated block 12 |
| `test/unit/core/unit_moonlive_compiler.cpp` | 1 | `unit_moonlive_compiler.cpp`, large file 1865 |
| `test/unit/core/unit_moonlive_fill.cpp` | 1 | `unit_moonlive_fill.cpp`, large file 1380 |
Expand Down
12 changes: 6 additions & 6 deletions docs/reference/metrics/docgen.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Generated by [`moondeck/check/check_docgen.py`](../../../moondeck/check/check_do

Every place the generated documentation breaks the shape [the standards](../../contributing/documentation-standards.md#the-card) define. Current state only: the trend is this file's git history. The list only shrinks.

**0 error(s)** and **4287 warning(s)** across 413 page(s).
**0 error(s)** and **4286 warning(s)** across 413 page(s).

**5460 appendix line(s)** after `@moreinfo`. This only falls as well: a cleanup cuts first, and moves only what neither the code, the test name nor the spec says.

Expand All @@ -17,7 +17,7 @@ The split is temporary. It stages the sweep rather than ranking the two kinds of
| Rule | Errors | Warnings |
|---|---:|---:|
| split paragraphs | 0 | 1791 |
| over-wide comment lines | 0 | 1583 |
| over-wide comment lines | 0 | 1582 |
| appendix sections nothing refers to | 0 | 369 |
| multi-line comment blocks | 0 | 317 |
| over-long sentences | 0 | 82 |
Expand All @@ -31,7 +31,7 @@ The unit a sweep runs in: one summary page and the headers it owns, as the [hier
| Summary page | Errors | Warnings |
|---|---:|---:|
| `(tests, no card)` | 0 | 2064 |
| `platform/index.md` | 0 | 744 |
| `platform/index.md` | 0 | 743 |
| `light/effects.md` | 0 | 403 |
| `core/system.md` | 0 | 203 |
| `core/supporting.md` | 0 | 181 |
Expand Down Expand Up @@ -71,7 +71,7 @@ By rule, warnings: 1360 over-wide comment lines, 271 split paragraphs, 197 multi

### platform/index.md

**0 error(s)** and **744 warning(s)** across 31 file(s).
**0 error(s)** and **743 warning(s)** across 31 file(s).

| Findings | File | |
|---:|---|---|
Expand All @@ -80,14 +80,14 @@ By rule, warnings: 1360 over-wide comment lines, 271 split paragraphs, 197 multi
| 68 | `src/platform/esp32/moonlive_asm_xtensa.cpp` | warning |
| 67 | `src/platform/esp32/platform_esp32_i80.cpp` | warning |
| 61 | `src/platform/esp32/platform_esp32_rmt.cpp` | warning |
| 43 | `src/platform/desktop/moonlive_asm_arm64.cpp` | warning |
| 42 | `src/platform/desktop/moonlive_asm_arm64.cpp` | warning |
| 33 | `src/platform/esp32/platform_esp32_parlio.cpp` | warning |
| 32 | `src/platform/esp32/moonlive_asm_riscv.cpp` | warning |
| 23 | `src/platform/esp32/platform_esp32_i2s.cpp` | warning |
| 20 | `src/platform/esp32/platform_esp32_improv.cpp` | warning |
| 1-19 each | *21 more warning files, 146 findings* | |

By rule, warnings: 398 split paragraphs, 218 over-wide comment lines, 114 multi-line comment blocks, 14 appendix sections nothing refers to.
By rule, warnings: 398 split paragraphs, 217 over-wide comment lines, 114 multi-line comment blocks, 14 appendix sections nothing refers to.

### light/effects.md

Expand Down
4 changes: 2 additions & 2 deletions docs/reference/metrics/prose.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ This number is the debt behind that gate, and it RATCHETS: a rise fails the chec
docgen.md does. A comment is prose wherever it lives, so a `//` in the web interface and a
`#` in a script are counted beside a `///` in a header.

**2950 finding(s)** across 370 file(s).
**2946 finding(s)** across 370 file(s).

| rule | findings |
|---|---|
| `MoonLight.EmDash` | 1532 |
| `MoonLight.EmDash` | 1528 |
| `MoonLight.Weasel` | 688 |
| `MoonLight.SentenceLength` | 430 |
| `MoonLight.Temporal` | 215 |
Expand Down
Loading
Loading