(90, 16).withCentre ({ outputColumn.getCentreX(), 98 }).toFloat(),
- theme.palette, juce::Justification::centred);
+ affine::render::jewel (g, inputClip, 11.0f, theme.palette.danger, inputClipped ? 1.0f : 0.0f);
+ affine::render::jewel (g, outputClip, 11.0f, theme.palette.danger, outputClipped ? 1.0f : 0.0f);
}
diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h
index 5d68382..5e3ae58 100644
--- a/Source/PluginEditor.h
+++ b/Source/PluginEditor.h
@@ -5,7 +5,7 @@
namespace SuppressorTheme
{
-/** Graphite anodising with a glacier-cyan emission: the Suppressor finish of the Affine family. */
+/** Broadcast finish: hammertone enamel, bakelite knobs and amber-lit meters. */
affine::Theme theme();
}
@@ -25,7 +25,7 @@ class SuppressorEditor final : public juce::AudioProcessorEditor, private juce::
void visibilityChanged() override;
int getControlParameterIndex (juce::Component&) override;
- static constexpr int width = 800, height = 530;
+ static constexpr int width = 880, height = 560;
private:
void timerCallback() override;
@@ -37,15 +37,14 @@ class SuppressorEditor final : public juce::AudioProcessorEditor, private juce::
affine::Knob threshold, strength, release;
ListenButton listen;
juce::AudioProcessorValueTreeState::ButtonAttachment listenAttachment;
- affine::NeedleMeter reduction;
- affine::LadderMeter inputLadder, outputLadder;
- affine::DisplayLabel status, inputPeak, outputPeak, hostSettings;
+ affine::NeedleMeter inputMeter, reduction, outputMeter;
+ affine::DisplayLabel status, hostSettings;
juce::Label meterSummary;
juce::TooltipWindow tooltips { this, 700 };
suppressor::MeterSnapshot meter;
uint32_t lastSequence = 0;
double lastUpdateMs = 0.0;
- bool fresh = false, auditionShown = false;
+ bool fresh = false, inputClipped = false, outputClipped = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SuppressorEditor)
};
diff --git a/Tests/test_ui.cpp b/Tests/test_ui.cpp
index fd89f15..12b9b5f 100644
--- a/Tests/test_ui.cpp
+++ b/Tests/test_ui.cpp
@@ -116,9 +116,10 @@ TEST_CASE ("theme retains bundled fonts and accessible contrast")
CHECK (contrast (palette.silkscreen, plate) >= 4.5f);
CHECK (contrast (palette.silkscreenDim, plate) >= 4.5f);
}
- // Emitted readouts and lamps on display glass.
- for (auto colour : { palette.accent, palette.attention, palette.danger, palette.silkscreen })
- CHECK (contrast (colour, palette.glass) >= 4.5f);
+ // Readouts and the status window: dark lettering on lit glass.
+ CHECK (contrast (palette.readoutInk, palette.readoutBacklight) >= 4.5f);
+ for (auto lamp : { juce::Colour (0xfff4eedc), juce::Colour (0xff8ae39a), juce::Colour (0xffff7b6b), palette.attention })
+ CHECK (contrast (palette.readoutInk, lamp) >= 4.5f);
}
TEST_CASE ("four controls preserve the complete host parameter and saved-state contract")
diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md
index f4ce33b..397e074 100644
--- a/docs/UI_DESIGN.md
+++ b/docs/UI_DESIGN.md
@@ -1,11 +1,11 @@
# Suppressor interface
The editor implements the shared [Affine design language](../libs/affine-ui/DESIGN_LANGUAGE.md)
-(v1.0) through the vendored `affine_ui` JUCE module. This document records only the
-Suppressor-specific decisions.
+(v2.0) in its **Broadcast** finish through the vendored `affine_ui` JUCE module. This
+document records only the Suppressor-specific decisions.
-
+
## Product
@@ -14,8 +14,10 @@ Suppressor-specific decisions.
high-gain amplification.
- **Primary task:** remove high-band noise between notes without losing body, sustain or
pick attack, and check what was removed when in doubt.
-- **Finish:** graphite anodising, glacier-cyan emission, an incandescent meter card.
-- **Editor:** fixed 800 × 530 logical pixels; renders natively at 100–200% and on Retina.
+- **Finish:** a 1960s studio rack panel: warm grey hammertone enamel between rack ears,
+ a riveted name plate, bakelite knobs, chrome-bezel VU meters with amber cards, jewel
+ lamps and amber backlit readouts.
+- **Editor:** fixed 880 × 560 logical pixels; renders natively at 100–200% and on Retina.
## Layout
@@ -23,10 +25,10 @@ The signal runs left to right across the meter bridge, and the controls sit belo
| Zone | Contents |
|---|---|
-| Header | Wordmark; processing-status display with lamp; *Host settings active* lamp underneath when non-default legacy settings exist |
-| Meter bridge | **IN** peak ladder → backlit **reduction** meter → **OUT** peak ladder (relabelled **REMOVED** while auditioning), each ladder with a clip cell and a peak readout |
-| Suppression frame | Threshold, Strength, Release: medium knobs with calibrated scales and glass readouts |
-| Monitor frame | *Listen removed* latching key with an amber LED |
+| Header | Engraved name plate and descriptor; backlit status window lit in the state's colour; *Host settings active* line underneath when non-default legacy settings exist |
+| Meter bridge | **INPUT** VU meter → large **reduction** meter → **OUTPUT** VU meter (relabelled **REMOVED** while auditioning), each small meter with a clip jewel |
+| Suppression frame | Threshold, Strength, Release: medium bakelite knobs with calibrated scales and backlit readouts |
+| Monitor frame | *Listen removed* as a chrome bat-handle toggle: up is **REMOVED**, down is **PROCESSED** |
## Telemetry contract
@@ -36,28 +38,28 @@ Listen and band-mode settings, so an old block is never relabelled as the new mo
| Display | Source | Presentation |
|---|---|---|
-| IN / OUT ladders | Peak across channels, 300 ms decay | −60 to 0 dBFS in 3 dB segments: cyan, amber from −12 dBFS, red from −3 dBFS |
-| Clip cells | Processor clip flags, held for one second | Separate red cell above each column |
-| Peak readouts | Same peaks | dBFS to 0.1 dB, `-inf` for silence, `--` when not live |
+| INPUT / OUTPUT meters | Peak across channels, 300 ms decay | dBFS, printed −40 to 0 with the scale opened up towards 0; red zone from −6 dBFS |
+| Clip jewels | Processor clip flags, held for one second | Red jewel under each meter |
| Reduction meter | Deepest high-band gain reduction, 120 ms decay | 0–60 dB on a square-root scale so small reductions stay readable; lamp dims and needle rests when reduction is unavailable (no audio, no input, learning) |
-| Status | Snapshot flags | *No audio*, *No input*, *Passing signal*, *Suppressing*, *Listening to difference*, *Bypassed*, *Learning bands* |
+| Status window | Snapshot flags | *No audio*, *No input*, *Passing signal*, *Suppressing*, *Listening to difference*, *Bypassed*, *Learning bands* |
-The needle's spring-damper ballistics (about 300 ms to settle) are presentation only.
+Needle ballistics are presentation only: the reduction meter moves like a VU meter
+(about 300 ms to settle); the level meters are quicker so peaks stay visible.
## States
| State | Presentation |
|---|---|
-| Suppressing | Cyan status lamp, needle on the reduction, ladders live |
-| Listen removed | Amber key LED and status, **REMOVED** legend and amber readout on the output column |
-| Multiband topology | Meter caption reads **MAX BAND REDUCTION**; Threshold and Strength are disabled (veiled) because that mode uses its own splits and learned thresholds |
-| Learning bands | Amber status; reduction marked unavailable, so the meter lamp dims |
-| Bypassed | Needle at rest with the lamp lit (no reduction is applied) |
-| Stopped audio | Lamp dims, needle rests, ladders dark, readouts `--` |
+| Suppressing | Green status window, needle on the reduction, level meters live |
+| Listen removed | Toggle up; amber status window; the output meter reads **REMOVED** |
+| Multiband topology | Meter caption reads **MAX BAND REDUCTION**; Threshold and Strength are disabled because that mode uses its own splits and learned thresholds |
+| Learning bands | Amber status window; reduction marked unavailable, so the meter lamp dims |
+| Bypassed | Red status window; reduction needle at rest with the lamp lit (no reduction is applied) |
+| Stopped audio | Status window unlit, meter lamps dim, needles rest |
-
-
+
+
## Parameter contract
@@ -65,7 +67,7 @@ The needle's spring-damper ballistics (about 300 ms to settle) are presentation
Unchanged: all 20 parameters keep their IDs, order, version hints, ranges, defaults,
tapers, text conversion and automation. The editor exposes Threshold, Strength, Release
and Delta Audition; the remaining engine settings stay available in the host's parameter
-view and are summarised by the *Host settings active* lamp's tooltip.
+view and are summarised by the *Host settings active* tooltip.
## Interaction
@@ -74,9 +76,9 @@ view and are summarised by the *Host settings active* lamp's tooltip.
readout (Return commits, Escape cancels, malformed text never changes the value),
Alt/Option-click or Home to reset, arrows to step (Strength 1%, Threshold and Release
0.1), right-click for *Enter value*, *Reset to default* and the host's parameter menu.
-- *Listen removed* toggles with a click, Space or Return, as one host gesture.
+- The *Listen removed* toggle flips with a click, Space or Return, as one host gesture.
- Tab order: Threshold, Strength, Release, Listen removed. Focus shows as an illuminated
- ring at the foot of the knob or an outline around the key.
+ ring at the foot of the knob or an outline around the toggle.
@@ -86,12 +88,12 @@ view and are summarised by the *Host settings active* lamp's tooltip.
- Every control exposes its parameter name, formatted value with unit, and a description.
- The *Signal meters* label carries a live summary (input and output dBFS, reduction or
- why it is unavailable, clip warnings); the per-column readouts are hidden from assistive
- technology so values are not announced twice.
-- State is never colour-only: every lamp has a text legend and every mode change has a
- printed or displayed label.
-- Printed text meets 4.5:1 on the plate, including its darker lower edge; emitted
- colours meet 4.5:1 on display glass. Both are unit-tested.
+ why it is unavailable, clip warnings).
+- State is never colour-only: the status window always spells the state out, and every
+ mode change has a printed or displayed label.
+- Printed ink meets 4.5:1 on the enamel, including its darker lower edge; the dark ink of
+ the readouts and the status window meets 4.5:1 on each of their lit colours. Both are
+ unit-tested.
## Verification
diff --git a/docs/images/suppressor-delta-audition.png b/docs/images/suppressor-delta-audition.png
index cc70073..97ad1c1 100644
Binary files a/docs/images/suppressor-delta-audition.png and b/docs/images/suppressor-delta-audition.png differ
diff --git a/docs/images/suppressor-exact-entry.png b/docs/images/suppressor-exact-entry.png
index 8999688..f405454 100644
Binary files a/docs/images/suppressor-exact-entry.png and b/docs/images/suppressor-exact-entry.png differ
diff --git a/docs/images/suppressor-learning.png b/docs/images/suppressor-learning.png
index f578b53..c57a645 100644
Binary files a/docs/images/suppressor-learning.png and b/docs/images/suppressor-learning.png differ
diff --git a/docs/images/suppressor-ui.png b/docs/images/suppressor-ui.png
index 478894a..9bc70a6 100644
Binary files a/docs/images/suppressor-ui.png and b/docs/images/suppressor-ui.png differ
diff --git a/libs/affine-ui/DESIGN_LANGUAGE.md b/libs/affine-ui/DESIGN_LANGUAGE.md
index 1af08c4..40051bd 100644
--- a/libs/affine-ui/DESIGN_LANGUAGE.md
+++ b/libs/affine-ui/DESIGN_LANGUAGE.md
@@ -1,14 +1,19 @@
# Affine design language
-**Version 1.0** · shared by every Affine plug-in · implemented by the `affine_ui` JUCE module in this folder
+**Version 2.0** · shared by every Affine plug-in · implemented by the `affine_ui` JUCE module in this folder
-Affine plug-ins are **rendered laboratory instruments**. Each product is a believable piece of
-precision hardware from the same bench: an anodised faceplate, machined knobs with calibrated
-scales, backlit meters, glowing displays, all lit by one studio light. The hardware is a
-vehicle for clarity, not decoration: every lamp, needle and digit reports real state.
+Affine plug-ins are **rendered instruments**. Each product is a believable piece of
+hardware from the same bench: a real faceplate, knobs with calibrated scales, lit meters
+and displays, all lit by one studio light. The hardware is a vehicle for clarity, not
+decoration: every lamp, needle and digit reports real state.
+
+The kit renders four **finishes**: Laboratory, Broadcast, Hi-Fi and Stealth. A finish
+sets the materials, lamps and display technology. The principles, interaction contract,
+states and telemetry rules below apply to every finish, and all products in a release
+share one finish.
-
+
## Principles
@@ -16,19 +21,19 @@ vehicle for clarity, not decoration: every lamp, needle and digit reports real s
1. **One light.** A single softbox sits above and slightly left of the player. Every
highlight, reflection and shadow in every product agrees with it, so the instruments
read as physical objects and sit together on screen.
-2. **Real materials, rendered.** Anodised aluminium plate, spun-aluminium knob caps,
- knurled black grips, a backlit meter card, smoked display glass, neon and phosphor.
- Materials are shaded procedurally at the display's physical resolution, never
- stretched bitmaps.
+2. **Real materials, rendered.** Anodised aluminium, hammered enamel, powder coat and
+ black glass; spun and polished aluminium, bakelite and rubber; meter cards, Nixie
+ tubes, phosphor and LEDs. Materials are shaded procedurally at the display's physical
+ resolution, never stretched bitmaps.
3. **Truth before spectacle.** An instrument only shows what the processor actually did.
- Stale readings go dark (lamps dim, needles rest, tubes extinguish); nothing is
- extrapolated or animated for effect. Needle ballistics and glows are presentation only
- and never feed back into processing.
-4. **Printed like an instrument.** Labels are silkscreened, scales are calibrated through
- the parameter's own mapping (a log control gets a log scale), and controls are grouped
- by printed frames that follow the signal flow from left to right.
-5. **One hardware kit.** Products share knobs, keys, lamps, meters, displays, screws and
- typography. They differ in faceplate anodising, emission colour and layout.
+ Stale readings go dark (lamps dim, needles rest, tubes extinguish, plots break);
+ nothing is extrapolated or animated for effect. Needle ballistics and glows are
+ presentation only and never feed back into processing.
+4. **Printed like an instrument.** Labels are printed on the front, scales are calibrated
+ through the parameter's own mapping (a log control gets a log scale), and controls are
+ grouped by stage in signal-flow order, left to right.
+5. **One hardware kit.** Products share knobs, keys, lamps, meters, displays and
+ typography. Within a finish they differ only in colour and layout.
6. **Native behaviour is part of the design.** Every control has the same drag, fine,
exact-entry, reset, keyboard and host-menu behaviour, and every edit is one host gesture.
7. **Accessible by construction.** Every control has a role, name, formatted value and
@@ -44,7 +49,7 @@ vehicle for clarity, not decoration: every lamp, needle and digit reports real s
│ ◎ ◎ │
│ WORDMARK primary mode / status │ header band
│ DESCRIPTOR │
-│ ════════════════════════════════════════════════════════════════════ │ machined groove
+│ ──────────────────────────────────────────────────────────────────── │
│ instrument windows: meters, counters, displays (the hero) │ what is happening
│ │
│ ╭─ STAGE ───────────╮ ╭─ STAGE ─────────────────╮ ╭─ OUTPUT ─╮ │ what you can change,
@@ -55,50 +60,69 @@ vehicle for clarity, not decoration: every lamp, needle and digit reports real s
```
- **Header band:** wordmark and descriptor on the left; the product's primary mode or
- status on the right; a machined groove underneath.
+ status on the right.
- **Instrument windows:** the largest area shows what the plug-in is doing. Choose the
- display technology by the quantity (see below).
-- **Controls:** printed frames group controls by stage, in signal-flow order. Knob axes in
- a row share one horizontal line, as they would on a real panel.
-- **Maker's mark** bottom left; four corner screws at a 14 px inset; 40 px outer margins.
-
-## Tokens
-
-### Shared palette
-
-| Role | Value | Use |
+ display technology by the quantity and the finish (see below).
+- **Controls:** grouped by stage, in signal-flow order, with printed frames or titled
+ hairlines. Knob axes in a row share one horizontal line, as they would on a real panel.
+- **Maker's mark** bottom left. How the front is held is part of the finish: corner
+ screws (Laboratory), rack ears (Broadcast), end caps (Hi-Fi), nothing visible (Stealth).
+
+## Finishes
+
+| | Laboratory | Broadcast | Hi-Fi | Stealth |
+|---|---|---|---|---|
+| Front | Dark anodised aluminium, bead-blasted or brushed | Light hammertone enamel with rack ears and a riveted name plate | Black glass between polished aluminium end caps | Matte black powder coat |
+| Knobs | Spun-aluminium cap on a knurled black grip | Glossy bakelite with a ribbed skirt and a white pointer | Spun cap on a polished aluminium body | Dark spun cap, white pointer, white LED ring |
+| Keys | Anodised caps with LED windows | Piano keys; a chrome bat-handle toggle | Polished square buttons whose legends light | Rubber soft keys with LED strips |
+| Lamps | LEDs behind domed lenses | Faceted jewels in chrome bezels | Legends lit from behind the glass | LEDs |
+| Displays | Backlit meter card, LED ladders, Nixie tubes, VFD | Chrome-bezel VU meters, Nixie tubes, a phosphor scope | Blue-lit flush meters, a tuning dial, VFD, horizontal ladders | One colour screen: history plots, scopes, bars and numerals |
+| Readouts | Glowing characters on glass | Dark ink in amber-lit windows | Glowing characters on glass | Glowing characters on glass |
+| Printing | Light silkscreen, Michroma wordmark | Dark ink; the wordmark engraved on the name plate | Printed on the back of the glass and lit; green wordmark | Light silkscreen; wide-tracked condensed wordmark |
+
+A finish is a `Theme`: `PanelFinish` (texture, base colour, grain, mottle, sheen, and
+the hammertone dimple size), `KnobFinish` (cap and body materials and colours, cap and
+grip proportions, dome, skirt ridges) and a `Palette` (printing, emission, lamp style,
+readout style). Editors add the finish's fixing hardware and display choices.
+
+### Product colours
+
+| Finish | Suppressor | HDN Ring Modulator |
|---|---|---|
-| Silkscreen | `#ECE8DF` | Labels, legends, frames (at 30%), scale numbers |
-| Silkscreen dim | `#AEAAA1` | Descriptors, secondary legends |
-| Attention | `#FFB238` | Engaged monitoring or learning states, hot ladder segments |
-| Danger | `#FF4A3D` | Clip cells only |
-| Glass | `#07090B` | Display and readout windows |
+| Laboratory | Graphite `#2A3037`; glacier cyan `#4FD6FF`; incandescent meter card `#F2E4C4` | Petrol `#1F3538`; neon orange `#FF6A1C` Nixie carrier; VFD cyan `#5CF2D6` input |
+| Broadcast | Warm grey `#B3B0A6`; signal green `#2F9E4F`; VU card `#F2D48A` | Blue grey `#A9B0B5`; neon orange `#FF6A1C` Nixies; phosphor `#7DFFA0` scope |
+| Hi-Fi | Black glass `#050607`; meter light `#3A8EF0`; green legends `#54F28C` | The same glass and meter light; blue `#4AA8FF` dial pointer and readout; green VFD |
+| Stealth | Powder coat `#17181B`; white LEDs `#EEF4FF`; teal screen `#35E0C8` | The same coat and LEDs; amber screen `#FFB23F` |
-Printed text is at least 4.5:1 against the plate (checked against the plate's darker lower
-edge too) and emitted colours are at least 4.5:1 against glass; both are unit-tested.
+## Tokens
-### Product finishes
+### Palette roles
-A product chooses an anodising, one emission accent, and optionally a second display
-technology. Everything else is shared.
+Every finish defines the same roles; only the values change.
-| Product | Anodising | Emission accent | Other emitters |
-|---|---|---|---|
-| Suppressor | Graphite `#2A3037` | Glacier cyan `#4FD6FF` | Incandescent meter card `#F2E4C4` |
-| HDN Ring Modulator | Petrol `#1F3538` | Neon orange `#FF6A1C` (Nixie carrier) | VFD cyan `#5CF2D6` (input display) |
+| Role | Use |
+|---|---|
+| Silkscreen | Labels, legends, frames, scale numbers |
+| Silkscreen dim | Descriptors, secondary legends |
+| Accent | The product's emission: lamps, displays, LED rings, focus |
+| Attention | Amber `#FFB238` in every finish: engaged monitoring or learning states, hot segments |
+| Danger | Red in every finish: clip cells only |
+| Glass | Display and readout windows |
+| Readout backlight and ink | Backlit readouts only (Broadcast) |
-New products should pick an anodising that is distinct in hue but equally dark, and an
-accent that stays at least 4.5:1 on glass.
+Printed text is at least 4.5:1 against the plate (checked against the plate's darker lower
+edge too). Emitted colours are at least 4.5:1 against glass, and backlit readouts'
+ink at least 4.5:1 against their window. All three are unit-tested per product.
### Typography
| Role | Face | Size |
|---|---|---|
-| Wordmark | Michroma, tracked 0.18 | 21 px |
-| Frame titles, control labels | Barlow Condensed SemiBold, tracked 0.16–0.24 | 11.5–12.5 px, capitals |
+| Wordmark | Michroma (Laboratory, Hi-Fi) or Barlow Condensed SemiBold tracked 0.36–0.45 (Broadcast, Stealth) | 21–30 px |
+| Frame titles, control labels | Barlow Condensed SemiBold, tracked 0.16–0.26 | 11.5–12.5 px, capitals |
| Scale numbers | Barlow Condensed SemiBold | 9.5–10 px |
-| Readouts and status | Share Tech Mono | 13.5–15 px |
-| VFD characters | DSEG14 Classic Bold | 40–48 px |
+| Readouts and status | Share Tech Mono | 13.5–15 px; screen numerals up to 54 px |
+| VFD characters | DSEG14 Classic Bold | 30–48 px |
| Nixie cathodes | Nixie One, fitted to the cathode stack | tube height |
All faces are bundled under the SIL Open Font License; see `fonts/licenses`.
@@ -115,48 +139,54 @@ All faces are bundled under the SIL Open Font License; see `fonts/licenses`.
## Choosing a display
-| Quantity | Instrument | Component |
-|---|---|---|
-| A magnitude that moves musically (gain reduction) | Backlit moving-coil meter with mirror scale | `NeedleMeter` |
-| Peak level with clipping | LED ladder with a separate clip cell | `LadderMeter` |
-| A frequency or count | Nixie tube counter | `NixieDisplay` |
-| A symbol (note name) or short status | Vacuum-fluorescent characters | `DisplayLabel`, `GlowText` with DSEG14 |
-| A set value | Glass readout under its knob | built into `Knob` |
-| On/off state | Jewel lamp | `render::lamp` |
+| Quantity | Laboratory | Broadcast | Hi-Fi | Stealth |
+|---|---|---|---|---|
+| A magnitude that moves musically (gain reduction) | Backlit `NeedleMeter` with mirror scale | Chrome-bezel VU `NeedleMeter` | Flush blue `NeedleMeter` | Scrolling history plot on the screen |
+| Peak level with clipping | Vertical `LadderMeter` | VU `NeedleMeter` with clip jewel | Horizontal `LadderMeter` | Segmented bars on the screen |
+| A frequency or count | `NixieDisplay` | `NixieDisplay` | `TuningDial` with a readout | Screen numerals |
+| A note name or short status | `DisplayLabel`, `GlowText` in DSEG14 | Alphanumeric `NixieDisplay`; backlit `DisplayLabel` | VFD `GlowText`; `IlluminatedLegends` | Screen numerals and captions |
+| A set value | Glass readout under its knob | Backlit readout under its knob | Glass readout under its knob | Glass readout under its knob |
+| On/off state | LED `render::indicator` | Jewel `render::indicator` | Lit legend | LED |
+
+Screens are drawn by the product on `render::screenGlass`; they plot only stored readings
+and leave gaps where there were none.
## Components
| Component | Contract |
|---|---|
-| `Knob` | Rendered knob with printed calibrated scale, label and glass readout. Drag anywhere (up/right increases), Shift for fine, double-click or Return for exact entry (Return commits, Escape cancels, malformed text never changes the value), Alt/Option-click or Home resets, arrows step, right-click opens the parameter menu with host items. Optional activity lamp for *inactive but editable* states. |
-| `KeyButton` | Latching key with an LED window and printed legend; Space and Return operate it. |
-| `SelectorKeys` | One lit key per choice of a choice parameter, in a row or grid; each press is one complete gesture; arrows move the selection. |
-| `NeedleMeter` | Lamp dims and needle rests when the reading is not live; spring-damper ballistics settle in about 300 ms. |
-| `LadderMeter` | Accent / attention / danger zones, smooth top segment, clip cell apart from the scale. |
-| `NixieDisplay` | Right-aligned digits, decimal cathode, ghost cathodes and honeycomb anode always visible; dark when the reading is absent. |
-| `DisplayLabel` | A `juce::Label` that renders as a glowing display with a status lamp; the accessible text stays the label text. |
-| `Faceplate` | Caches the plate texture, edge, screws and everything silkscreened on it per physical scale. |
-| `silkscreen::*` | Wordmark, groove, frame, legend, maker's mark, screws. |
+| `Knob` | Rendered knob with a printed calibrated scale or an LED ring, a label and a readout. Drag anywhere (up/right increases), Shift for fine, double-click or Return for exact entry (Return commits, Escape cancels, malformed text never changes the value), Alt/Option-click or Home resets, arrows step, right-click opens the parameter menu with host items. Optional activity lamp for *inactive but editable* states. |
+| `KeyButton` | Latching key in one of five styles (anodised cap, piano key, bat-handle toggle, polished square, soft key) with a printed legend and optional glyph; Space and Return operate it. |
+| `SelectorKeys` | One key per choice of a choice parameter, in a row or grid; each press is one complete gesture; arrows move the selection. |
+| `NeedleMeter` | Moulded, chrome or flush housing; lamp dims and needle rests when the reading is not live; spring-damper ballistics (VU-like by default); optional coloured zone and centre rest position. |
+| `LadderMeter` | Vertical or horizontal; accent, attention and danger zones; smooth top segment; clip cell apart from the scale. |
+| `NixieDisplay` | Numeral or alphanumeric tubes; right-aligned text, decimal cathode, ghost cathodes and honeycomb anode always visible; dark when the reading is absent. |
+| `TuningDial` | A lit frequency dial with a gliding pointer that parks at the left stop without a reading. |
+| `IlluminatedLegends` | A `juce::Label` shown as a row of printed legends of which the current one lights; the accessible text stays the label text. |
+| `DisplayLabel` | A `juce::Label` shown as glowing characters on glass or dark ink on a lit window, with a status lamp; the accessible text stays the label text. |
+| `Faceplate` | Caches the plate texture, edge, screws and everything printed on it per physical scale. |
+| `render::*` | Faceplate textures, screws, recesses, lit windows, jewels, rack ears, name plates, end caps, screen glass, lamps, glow and halos. |
+| `silkscreen::*` | Wordmark, groove, frame, legend, lit legend, maker's mark, screws. |
| `LookAndFeel` | Menus, tooltips and text entry in the family style. |
## States
| State | Presentation |
|---|---|
-| Live | Lamps lit, needles move, digits glow |
-| No data or stale (no processed audio for 400–500 ms) | Lamps dim, needle rests at its stop, tubes and segments go dark, readouts show `--` |
-| Inactive but editable (the control does not act in the current mode) | Activity lamp off, label and readout dimmed; the control stays fully operable |
-| Disabled | Knob veiled, scale dimmed; not operable |
+| Live | Lamps lit, needles move, digits glow, plots advance |
+| No data or stale (no processed audio for 400–500 ms) | Lamps dim, needles rest at their stop, tubes and segments go dark, readouts show `--`, plots break |
+| Inactive but editable (the control does not act in the current mode) | Activity lamp off, label, readout and LED ring dimmed; the control stays fully operable |
+| Disabled | Knob veiled, scale or ring dark; not operable |
| Attention | Amber emission plus explicit text (e.g. `LISTENING TO DIFFERENCE`) |
-| Clip | Red clip cell, held by the processor for one second, plus accessible text |
+| Clip | Red clip cell or jewel, held by the processor for one second, plus accessible text |
| Keyboard focus | Illuminated ring at the foot of the knob, outline around keys |
## Adding a product
1. Copy this folder unchanged into the plug-in as `libs/affine-ui`, `include()` its
`AffineUI.cmake` after JUCE and link `affine_ui`.
-2. Define the product's `affine::Theme`: anodising, accent, and any second emitter.
-3. Lay out the header, instrument windows and framed control stages as above.
+2. Define the product's `affine::Theme` in the release's finish, with its own colours.
+3. Lay out the header, instrument windows and control stages as above.
4. Publish telemetry from the processor through atomics with a freshness signal (a block
counter or sequence) and present stale data as dark.
5. Add tests for contrast, control mapping and focus order, and a capture test that writes
diff --git a/libs/affine-ui/README.md b/libs/affine-ui/README.md
index e7aebf7..64209ec 100644
--- a/libs/affine-ui/README.md
+++ b/libs/affine-ui/README.md
@@ -18,7 +18,8 @@ target_link_libraries(MyPlugin PRIVATE affine_ui)
```cpp
#include
-affine::Theme theme; // shared palette, per-product finish
+affine::Theme theme; // the release's finish, in product colours
+theme.panel.texture = affine::PanelFinish::Texture::anodised;
theme.panel.base = juce::Colour (0xff2a3037);
theme.palette.accent = juce::Colour (0xff4fd6ff);
@@ -35,15 +36,16 @@ Requires JUCE 8 and C++17. The module depends on `juce_gui_basics` and
| Path | What |
|---|---|
-| `affine_ui/render` | Procedural renderers: knob shader, anodised faceplate, glow and blur |
-| `affine_ui/components` | `Knob`, `KeyButton`, `SelectorKeys`, `NeedleMeter`, `LadderMeter`, `NixieDisplay`, `DisplayLabel`, `Faceplate` and silkscreen helpers |
+| `affine_ui/render` | Procedural renderers: knob materials, faceplate textures (anodised, hammertone, powder, glass), panel hardware, glow and blur |
+| `affine_ui/components` | `Knob`, `KeyButton`, `SelectorKeys`, `NeedleMeter`, `LadderMeter`, `NixieDisplay`, `TuningDial`, `IlluminatedLegends`, `DisplayLabel`, `Faceplate` and silkscreen helpers |
| `affine_ui/theme` | Palette, metrics, fonts, `LookAndFeel` |
+| `docs/finishes.png` | Both products in each of the four finishes |
| `fonts` | Barlow Condensed, Michroma, Nixie One, DSEG14 Classic, Share Tech Mono (all SIL OFL 1.1; licences in `fonts/licenses`) |
-| `DESIGN_LANGUAGE.md` | Principles, tokens, anatomy, component contracts, states |
+| `DESIGN_LANGUAGE.md` | Principles, finishes, tokens, anatomy, component contracts, states |
## Rendering notes
-Knobs, the faceplate and meter faces are shaded per pixel at the display's physical
+Knobs, faceplates and meter faces are shaded per pixel at the display's physical
scale and cached, so they stay sharp at 100–200% and on Retina displays. Only the parts
that move are redrawn: the knob's knurled grip band is re-shaded (about a millisecond at
2x) when its value changes, and meters repaint their own bounds. Nothing here runs on the
diff --git a/libs/affine-ui/affine_ui/affine_ui.cpp b/libs/affine-ui/affine_ui/affine_ui.cpp
index 35bb236..8661d89 100644
--- a/libs/affine-ui/affine_ui/affine_ui.cpp
+++ b/libs/affine-ui/affine_ui/affine_ui.cpp
@@ -20,4 +20,5 @@
#include "components/affine_Keys.cpp"
#include "components/affine_Nixie.cpp"
#include "components/affine_Faceplate.cpp"
+#include "components/affine_Legends.cpp"
#include "theme/affine_LookAndFeel.cpp"
diff --git a/libs/affine-ui/affine_ui/affine_ui.h b/libs/affine-ui/affine_ui/affine_ui.h
index 63df2e8..92583aa 100644
--- a/libs/affine-ui/affine_ui/affine_ui.h
+++ b/libs/affine-ui/affine_ui/affine_ui.h
@@ -3,7 +3,7 @@
ID: affine_ui
vendor: affine
- version: 1.0.0
+ version: 2.0.0
name: Affine instrument UI
description: Shared rendered-instrument design language for Affine plug-ins
license: GPL-3.0-or-later
@@ -26,6 +26,7 @@
#include
#include
#include
+#include