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/stadbeest.mp4
Binary file not shown.
Binary file added docs/assets/reference/akai-apc40-mkii.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/reference/behringer-x-touch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/reference/icon-qcon-pro-g2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions docs/how-to/control-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ Every route lands on the same surface, so a desk, a phone and the web UI stay in
3. A desk: Chrome asks to use MIDI devices the first time; allow it. A pad: press any button, and the browser lists it.
4. Assign the surface's controls on the Control card, such as `fader2` to a game's paddle.

A Mackie desk in MC mode needs no setup: faders, knobs, SELECT buttons, touch sensors, motors and lights are mapped. A gamepad comes with default rows for its sticks and its A button.
A Mackie desk in MC mode needs no setup: faders, knobs, SELECT buttons, touch sensors, motors and lights are mapped. An Akai APC40 mkII needs one choice: set the MIDI service's `profile` to Akai APC40 mkII, and its faders, knobs, activator buttons and clip pads are mapped, with their lights. A gamepad comes with default rows for its sticks and its A button.

<img src="../assets/reference/akai-apc40-mkii.png" width="320" alt="The Akai APC40 mkII"> <img src="../assets/reference/microsoft-xbox-series-controller.png" width="200" alt="A Microsoft Xbox Series controller">

The browser offers MIDI and gamepads on a secure origin only. The desktop app's page on `localhost` is one; a board's own page, such as `http://192.168.1.158`, needs its address marked as secure once, as [MIDI, details](../moonmodules/core/services.md#midi-details) shows.

Expand Down Expand Up @@ -211,11 +213,12 @@ Each control sends to an address naming **the surface**, not the thing it drives
/mm/switch/1 … /mm/switch/8
/mm/encoder/1 … /mm/encoder/8
/mm/fader/1 … /mm/fader/8
/mm/pad/1 … /mm/pad/64
```

The device decides what each one drives. That is deliberate: reassign `fader3` from brightness to speed, and the layout does not change, because the layout never knew. It also means a hardware desk added later lands on the same addresses.

The session also draws a **pad grid**. Those pads are inert for now: `/mm/pad/N` has no route yet, so pressing one sends a message nothing reads. It ships because the grid is the layout a preset launcher will want.
A pad applies the preset stored on that pad of the Control card's grid, counted from the top left.

---

Expand Down
183 changes: 122 additions & 61 deletions docs/how-to/multi-board-installation.md

Large diffs are not rendered by default.

32 changes: 21 additions & 11 deletions docs/moonmodules/core/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
- `hosts`: (unicast) addresses or names; empty answers whoever wrote last.
- `group`: (multicast) the group feedback goes to and this board joins, `239.255.77.78` by default.
- `feedbackPort`: where the client listens, 9001 by default.
- `follow`: this board follows another, taking its feedback and showing its presets.
- `status`: listening, off, or why the port could not be opened.

Detail: [technical](moxygen/OscModule.md)
Expand Down Expand Up @@ -115,7 +116,7 @@

A Service added per board: **a gamepad's buttons and sticks**, each driving a control through the same rows the other input services use. The pad plugs into the computer showing the interface, over USB or Bluetooth, and the browser reports it, so it works on every chip.

<img src="../../assets/core/GamepadService.png" width="300" alt="Gamepad service controls">
<img src="../../assets/core/GamepadService.png" width="300" alt="Gamepad service controls"> <img src="../../assets/reference/microsoft-xbox-series-controller.png" width="200" alt="A Microsoft Xbox Series controller, the pad the service is verified with">

- `inputs`: the rows, each a button or stick of the standard layout, a `learn` and a target.

Expand All @@ -127,11 +128,14 @@

### MIDI

A Service added per board: **a MIDI control desk** driving the control surface, decoded as Mackie Control. The desk plugs into the computer showing the interface and the browser reports it, so it works on every chip; a Mackie desk such as the iCON QCon or the Behringer X-Touch in MC mode works without setup.
A Service added per board: **a MIDI control desk** driving the control surface. The desk plugs into the computer showing the interface and the browser reports it, so it works on every chip. A Mackie desk such as the iCON QCon or the Behringer X-Touch in MC mode works without setup, an Akai APC40 mkII after one choice.

<img src="../../assets/core/MidiService.png" width="300" alt="MIDI service controls">

Faders move the surface's faders, the knobs turn its encoders, and each channel's SELECT button flips its switch. The desk follows the surface back: motors, SELECT lights and knob rings. A hand on a fader holds its motor still. ⌄ details.
- `profile`: the desk's layout, `Mackie Control` or `Akai APC40 mkII`.
- `usb`: the desk is on this board's own USB port; S3 boards with 8 MB of flash or more.

Faders move the surface's faders, the knobs turn its encoders, and each channel's SELECT button flips its switch. The desk follows the surface back: motors, SELECT lights and knob rings. A hand on a fader holds its motor still. An APC40's clip pads apply presets. ⌄ details.

Detail: [technical](moxygen/MidiService.md)

Expand Down Expand Up @@ -182,6 +186,10 @@

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 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).

**A desk on the board's own USB port.** With `usb` on, the desk plugs into the board instead of the computer, and the board is the USB host. It reads the desk as USB-MIDI 1.0 packets, greets it, and sends it its state. The port then carries no serial log and no USB flashing, so a board with a second USB port keeps that one for the computer. The desk takes its power from the port, so the board supplies 5 V there, or a powered hub between them does; an APC40 mkII draws about 0.15 A.

## Audio, details
#### Microphone wiring

Expand Down Expand Up @@ -234,24 +242,26 @@
Prior art: the WLED-MM audio-reactive usermod by **Frank ([@softhack007](https://github.com/softhack007))**, the most-used open-source audio-reactive LED implementation, whose adaptive noise-gate concept the analysis here descends from (analyzed with his permission); and **[@troyhacks](https://github.com/troyhacks/WLED)**, who reworked that DSP onto Espressif's [esp-dsp](https://github.com/espressif/esp-dsp) FFT, the same choice this service makes. The line-in path exists because **wladi ([myhome-control](https://shop.myhome-control.de))** supplied the hardware and pinout for the [MHC-WLED ESP32-P4 shield](../../reference/hardware/mhc-wled-esp32-p4-shield.md): its onboard PCM1808 I2S ADC is what `mclkPin` is for.

## OSC, details
**Feedback: the device answers.** With `feedback` on, a control that changes anywhere (the web UI, a
preset recall, an audio-reactive effect) is mirrored back to the surface, which is what keeps a client honest and what moves a motorized fader. `addressing` picks where it goes: `unicast` to each of `hosts`, addresses or names, or to whoever last wrote when `hosts` is empty; `multicast` to `group`, which every listening board with the same `group` joins; the default, `239.255.77.78`, sits beside discovery's own group, so multicast works with nothing to fill in, and a second rig on the same network picks another. `feedbackPort` is where that client LISTENS, which is not the port we listen on (Open Stage Control calls its own `osc-port`).
**Feedback: the device answers.** With `feedback` on, a control that changes anywhere (the web UI, a preset recall, an audio-reactive effect) is mirrored back to the surface, which is what keeps a client honest and what moves a motorized fader. `addressing` picks where it goes: `unicast` to each of `hosts`, addresses or names, or to whoever last wrote when `hosts` is empty; `multicast` to `group`, which every listening board with the same `group` joins; the default, `239.255.77.78`, sits beside discovery's own group, so multicast works with nothing to fill in, and a second rig on the same network picks another. `feedbackPort` is where that client LISTENS, which is not the port we listen on (Open Stage Control calls its own `osc-port`).

Check warning on line 245 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":245,"column":259},"end":{"line":245,"column":264}}},"severity":"INFO","code":{"value":"MoonLight.SentenceLength"}}

Check warning on line 245 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":245,"column":35},"end":{"line":245,"column":39}}},"severity":"INFO","code":{"value":"MoonLight.SentenceLength"}}

A client learns the current state three ways: when it first writes to us from a new address, when its address changes, and whenever it sends **`/mm/hello`**. The last one exists because a client restarting on the SAME address is invisible to the other two, and most controllers send nothing of their own on load, so every widget would show its layout file's defaults until the user moved one.
The shipped session has a `sync from device` button for exactly this.
Every value also goes out again every 30 seconds, one every 20 ms, so a datagram lost on WiFi, or a client that rebooted, is repaired within that time.
A pad's state goes out as an int on its own address: 0 for an empty pad, 1 for a stored preset, 2 for the one applied.
A value is never sent back to the host it came from, so two boards feeding each other cannot echo it between them.

**Following another board.** With `follow` on, what arrives is another board's feedback: this board takes its values, and a desk's pads here show that board's presets. That is what tells a pad's state from a press, since both arrive on `/mm/pad/N`. The Control card's own grid keeps showing this board's presets. With `feedback` on as well, pointed at that board's `port`, a change made here goes there, and a pad pressed on the desk applies that board's preset. A follower asks for every value with `/mm/hello` when it starts, and sends only what changes here, since the board it follows owns the state. A control moved here ignores feedback for half a second, so echoes of its earlier positions cannot pull it back. `hosts` can stay empty: the follower then answers the board it follows once that board's first datagram arrives. A desk at the side of a room drives an installation in the middle this way, with no cable between them. On the desk's board, set `listen` on port 9001, `follow` on, and `feedback` to port 9000.

**Setting one up**, from installing the app to using it from a phone, is its own page:
[Connecting a control surface](../../how-to/control-surface.md). It needs no checkout and no tooling, just the app and the session file from the latest release.
**Setting one up**, from installing the app to using it from a phone, is its own page: [Connecting a control surface](../../how-to/control-surface.md). It needs no checkout and no tooling: the app and the session file from the latest release do.

**Addresses.** These are a public contract: a TouchOSC layout built against them keeps working, so
they stay small and boring.
**Addresses.** These are a public contract: a TouchOSC layout built against them keeps working, so they stay small and boring.

| address | argument | drives |
|---|---|---|
| `/mm/fader/1` .. `/mm/fader/8` | float 0..1 or int 0..255 | the Control surface's faders |
| `/mm/encoder/1` .. `/mm/encoder/8` | float 0..1 or int 0..255 | its rotary encoders |
| `/mm/switch/1` .. `/mm/switch/8` | float 0..1 or int 0..255 | its on/off switch row (nonzero = on) |
| `/mm/pad/1` .. `/mm/pad/64` | nonzero = press, zero ignored | applies the preset on that pad of the grid, counted from the top left; an empty pad does nothing |
| `/mm/hello` | anything, or nothing | resend every value to the sender |

Both argument forms are accepted because controllers disagree: apps send a float in 0..1, hardware bridges send an int in the target's range. Out-of-range values are clamped rather than ignored, so a controller sending 0..127 does something sensible instead of appearing dead.
Expand Down Expand Up @@ -290,9 +300,9 @@

Driving the device from that session, beside the Control card it mirrors.

It binds only to `/mm/switch/N`, `/mm/encoder/N` and `/mm/fader/N`, N being 1 to 8, on purpose. A surface addresses the SURFACE, and [Control](system.md#control) decides what each one drives, so one layout keeps working as assignments change and a hardware desk lands on the same bindings. By default `switch1` drives `Drivers.on` and `fader1` drives `Drivers.brightness`; the Control card assigns the rest.
It binds only to `/mm/switch/N`, `/mm/encoder/N` and `/mm/fader/N`, N being 1 to 8, and to `/mm/pad/N`, N being 1 to 64, on purpose. A surface addresses the SURFACE, and [Control](system.md#control) decides what each one drives, so one layout keeps working as assignments change and a hardware desk lands on the same bindings. By default `switch1` drives `Drivers.on` and `fader1` drives `Drivers.brightness`; the Control card assigns the rest.

The session also carries a **pad grid**, and those pads are inert: `/mm/pad/N` has no route in the OSC module yet, so pressing one sends a message nothing reads. It ships anyway because the grid is the layout a preset launcher wants and the addresses are the ones it will use; treat it as a placeholder rather than as part of the contract above.
The session also carries a **pad grid** of the 64 preset pads.

**A Mackie desk reaches the surface through the [MIDI service](#midi)**, since the X-Touch and QCon Pro G2 speak Mackie Control over MIDI rather than OSC.

Expand Down
4 changes: 2 additions & 2 deletions docs/reference/examples/open-stage-control.json
Original file line number Diff line number Diff line change
Expand Up @@ -1038,8 +1038,8 @@
"colorBg": "#16213e",
"colorStroke": "#6b5b9a",
"colorText": "#a0a0b0",
"address": "/mm/pad/#{$}",
"label": "#{$}",
"address": "/mm/pad/#{$+1}",
"label": "#{$+1}",
"on": 1,
"off": 0
},
Expand Down
Loading
Loading