Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { useEffect, useState, type ReactNode } from "react";

import { LABELS } from "@demon/client";

import { useCapability } from "@/hooks/useCapability";
import { useIsMobile } from "@/hooks/useIsMobile";
import { useCurveStore } from "@/store/useCurveStore";
Expand Down Expand Up @@ -327,7 +329,7 @@ function StylesTab({ spread }: { spread: boolean }) {
<PromptsTile />
</CollapsibleTile>
{canLora && (
<CollapsibleTile title="LoRA Library">
<CollapsibleTile title={LABELS.library}>
<LibraryTile />
</CollapsibleTile>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export function HeroMacros() {
aria-pressed={curveOpen}
aria-label="Toggle curve editor"
data-midi-learn="schedule_curves_toggle"
data-dd-tooltip="Draw param automation curves against the track timeline. Curves drive denoise / structure / timbre / LoRA strengths / etc. over time so the model performs an arrangement instead of a static patch. Right-click to MIDI-learn."
data-dd-tooltip="Draw param automation curves against the track timeline. Curves drive denoise / structure / timbre / Trained Style strengths / etc. over time so the model performs an arrangement instead of a static patch. Right-click to MIDI-learn."
data-dd-tooltip-wide=""
data-dd-tooltip-title="Curve Editor"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ import { useMidiStore } from "@/store/useMidiStore";
import { usePerformanceStore } from "@/store/usePerformanceStore";
import { useSessionStore } from "@/store/useSessionStore";
import { LORA_SLIDER_MAX } from "@/types/engine";
import type { LoraCatalogEntry, LoraMetadata } from "@demon/client";
import {
LABELS,
TRAINED_STYLES,
type LoraCatalogEntry,
type LoraMetadata,
} from "@demon/client";

// LoRA library tile — redesigned for a large catalog (40+ genre LoRAs).
//
Expand Down Expand Up @@ -410,7 +415,7 @@ function ActiveLoraRow({ entry }: { entry: LoraCatalogEntry }) {
aria-busy={isRefitPending}
title={
isRefitPending
? "Applying… (LoRA refit in progress)"
? LABELS.refitInProgress
: undefined
}
>
Expand Down Expand Up @@ -457,7 +462,7 @@ function BrowseLoraRow({ entry }: { entry: LoraCatalogEntry }) {
// disabled-and-cap-reached rows become inert.
const capBlocked = atCap && !enabled;
const rowTitle = capBlocked
? `Maximum ${cap} LoRAs active — disable one to enable this`
? `Maximum ${cap} ${TRAINED_STYLES} active — disable one to enable this`
: displayName;

return (
Expand Down Expand Up @@ -652,23 +657,23 @@ export function LibraryTile() {
if (catalog.length === 0) {
return (
<div className="mixer-tile" data-tile="library">
<div className="mixer-tile-label">LoRA Library</div>
<div className="lora-empty">no LoRAs found</div>
<div className="mixer-tile-label">{LABELS.library}</div>
<div className="lora-empty">{LABELS.noneFound}</div>
</div>
);
}

return (
<div className="mixer-tile" data-tile="library">
<div className="mixer-tile-label">LoRA Library</div>
<div className="mixer-tile-label">{LABELS.library}</div>
<div className="lora-search">
<input
type="text"
className="lora-search-input"
placeholder="search LoRAs"
placeholder={LABELS.searchPlaceholder}
value={query}
onChange={(e) => setQuery(e.target.value)}
aria-label="Search LoRA library"
aria-label={LABELS.searchAria}
/>
</div>

Expand All @@ -687,7 +692,7 @@ export function LibraryTile() {
<div className="lora-section-head">
{searching
? `Results · ${filtered.length}`
: `All LoRAs · ${compatible.length}`}
: `All ${TRAINED_STYLES} · ${compatible.length}`}
</div>
<div className="lora-browse">
{filtered.length === 0 ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { useCallback, useEffect, useRef } from "react";

import { LABELS } from "@demon/client";

import { useLoraStore } from "@/store/useLoraStore";
import { usePerformanceStore } from "@/store/usePerformanceStore";
import { useSessionStore } from "@/store/useSessionStore";
Expand Down Expand Up @@ -211,7 +213,7 @@ export function MobileLoraBlendStepper() {
side="right"
param="lora_blend"
max={1.0}
label="LoRA Blend"
label={LABELS.blend}
sublabelTop={a ?? undefined}
sublabelBottom={b ?? undefined}
invert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,11 +365,11 @@ export function OperatorStrip() {
<button
type="button"
className="pause-btn"
data-dd-tooltip="Reset all sliders + LoRAs to defaults. Does NOT touch MIDI mapping, automation curves, or persisted UI prefs."
data-dd-tooltip="Reset all sliders + Trained Styles to defaults. Does NOT touch MIDI mapping, automation curves, or persisted UI prefs."
onClick={async () => {
const ok = await confirm({
title: "Reset",
message: "Reset sliders and LoRAs to defaults?",
message: "Reset sliders and Trained Styles to defaults?",
confirmLabel: "Reset",
variant: "danger",
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ export function ScheduleCurvesOverlay() {
ensureCurve(tab.param);
onTabContext(e, tab.param);
}}
data-dd-tooltip={`LoRA ${tab.label} — ${
data-dd-tooltip={`Trained Style ${tab.label} — ${
isEnabled ? "active" : "drawn but not driving"
} (right-click for presets)`}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use client";

import { sharedTooltipFor, TOOLTIPS } from "@demon/client";

import { SliderGroup } from "./SliderGroup";

// Generic mixer tile that wraps a row of sliders. Replaces the dynamic
Expand Down Expand Up @@ -42,12 +44,11 @@ const DISPLAY_NAMES: Record<string, string> = {
// data-dd-tooltip-wide (white-space: normal, max-width 280px).
const PARAM_TOOLTIPS: Record<string, string> = {
// ── Main remix controls ──
denoise:
"How much the model reshapes the source audio. Keep it low for a subtle remix that stays close to the original; push it high to fully transform the track into something new. The most expressive knob — try sweeping it during playback.",
hint_strength:
"How closely the model follows the original song's structure — sections, rhythm, dynamics. Crank it up to keep the arrangement intact; drop it to let the model rearrange more freely.",
timbre_strength:
"How much of the source's instrument character (tone, color) carries into the output. High keeps the original instruments recognizable; low frees the model to swap them for whatever fits the prompt.",
// The three macros are cross-client controls; their copy is the shared
// source of truth in @demon/client so the VST and Radio read identically.
denoise: TOOLTIPS.strength,
hint_strength: TOOLTIPS.structure,
timbre_strength: TOOLTIPS.timbre,

// ── Engine internals ──
feedback:
Expand Down Expand Up @@ -100,15 +101,10 @@ for (const p of NAMED_CHANNELS) {
}

export function tooltipFor(param: string): string | undefined {
// LoRA strength sliders (param like `lora_str_<id>`) get a generic
// tooltip rather than per-LoRA copy — the row already shows the
// LoRA's name as its visible label.
if (param.startsWith("lora_str_")) {
return "How strongly this LoRA shapes the output. LoRAs are little style packs — set a low value for a subtle flavor, crank past 1.0 to make this LoRA dominate the sound. Multiple LoRAs stack — turn several on at once for combined styles.";
}
if (param === "lora_blend") {
return "Crossfade between LoRA A and LoRA B. 0 = A only, 1 = B only, 0.5 = both at half strength. Use this to morph between two styles smoothly.";
}
// Trained Style controls (per-style strength `lora_str_<id>` + the A/B
// blend) share their copy with every other client via @demon/client.
const shared = sharedTooltipFor(param);
if (shared) return shared;
// Manual steering tooltips share copy across all slots.
if (param.startsWith("man_src_")) {
return "Catalog index of the steering vector this slot fires. The catalog enumerates every pre-built (axis, build_layer, build_step) cell on disk in stable axis-major order. Double-click the readout to type an exact index; query the MCP list_manual_steering_vectors tool for the full table. Has no effect until α is non-zero.";
Expand Down
8 changes: 8 additions & 0 deletions packages/demon-client/copy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Shared user-facing copy: canonical terminology, short UI labels, and
// the tooltip text for cross-client controls. Plain string constants with
// no runtime deps — safe to import from any frontend (browser or native).
// See ./terms for the "LoRA" → "Trained Styles" naming decision.

export * from "./terms";
export * from "./labels";
export * from "./tooltips";
27 changes: 27 additions & 0 deletions packages/demon-client/copy/labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Short user-facing labels derived from the canonical terminology in
// ./terms. Defined once so the web demo, public demo (Radio + vendored
// Performance UI), and the VST webui all render identical wording. Each
// client maps these onto its own widgets — there is no shared component
// layer, only this shared string surface.

import { TRAINED_STYLE, TRAINED_STYLES } from "./terms";

export const LABELS = {
/** "Trained Styles" / "Trained Style" — re-exported for convenience. */
plural: TRAINED_STYLES,
singular: TRAINED_STYLE,

/** Library panel / accordion header. */
library: `${TRAINED_STYLES} Library`,
/** Empty-state copy when no styles are available. */
noneFound: `no ${TRAINED_STYLES} found`,
/** Search box placeholder (lowercase — reads as a hint). */
searchPlaceholder: `search ${TRAINED_STYLES.toLowerCase()}`,
/** Search box accessible name. */
searchAria: `Search ${TRAINED_STYLES} library`,

/** Crossfade control label (slot A ↔ slot B). */
blend: `${TRAINED_STYLE} Blend`,
/** Status shown while the engine reloads adapters. */
refitInProgress: `Applying… (${TRAINED_STYLE} refit in progress)`,
} as const;
15 changes: 15 additions & 0 deletions packages/demon-client/copy/terms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Canonical user-facing terminology, shared across every DEMON frontend.
//
// The engine, wire protocol, config files, and MIDI map all call these
// style-adapter packs "LoRAs" — that's the honest technical name and it
// stays everywhere internal (param ids like `lora_blend`, config keys
// like `enabled_loras`, the admin training studio). But player-facing UI
// reads better with a plain-language term, so every client renders the
// concept as "Trained Styles". Keep that one decision here so the three
// clients never drift on wording.

/** Player-facing name for the LoRA concept (plural). */
export const TRAINED_STYLES = "Trained Styles";

/** Player-facing name for a single LoRA. */
export const TRAINED_STYLE = "Trained Style";
45 changes: 45 additions & 0 deletions packages/demon-client/copy/tooltips.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Shared tooltip copy for the controls that exist in EVERY client — the
// three remix macros (strength / structure / timbre) and the Trained
// Style knobs (per-style strength + A/B blend). Each tooltip is a 1–2
// second read: when to reach for the knob and what musical outcome it
// produces, not the diffusion-process plumbing underneath.
//
// Client-specific controls (web's activation-steering, latent-channel,
// and DCW sliders; the VST's sequencer) keep their tooltips in their own
// codebase — only the cross-client controls live here. Render however
// the host does it: the web demo feeds these into its `data-dd-tooltip`
// system, the VST into plain `title=` attributes, the Radio into its
// info-bar.

export const TOOLTIPS = {
// ── Remix macros ──
strength:
"How much the model reshapes the source audio. Keep it low for a subtle remix that stays close to the original; push it high to fully transform the track into something new. The most expressive knob — try sweeping it during playback.",
structure:
"How closely the model follows the original song's structure — sections, rhythm, dynamics. Crank it up to keep the arrangement intact; drop it to let the model rearrange more freely.",
timbre:
"How much of the source's instrument character (tone, color) carries into the output. High keeps the original instruments recognizable; low frees the model to swap them for whatever fits the prompt.",

// ── Trained Styles ──
trainedStyleStrength:
"How strongly this Trained Style shapes the output. Trained Styles are little style packs — set a low value for a subtle flavor, crank past 1.0 to make this style dominate the sound. Multiple Trained Styles stack — turn several on at once for combined styles.",
trainedStyleBlend:
"Crossfade between Trained Style A and Trained Style B. 0 = A only, 1 = B only, 0.5 = both at half strength. Use this to morph between two styles smoothly.",
} as const;

// Mapping from engine param id → shared tooltip, for the cross-client
// controls. Per-style strength sliders are dynamic (`lora_str_<id>`) so
// they're matched by prefix rather than listed. Returns undefined for
// params a given client handles with its own copy.
const SHARED_PARAM_TOOLTIPS: Record<string, string> = {
denoise: TOOLTIPS.strength,
hint_strength: TOOLTIPS.structure,
timbre_strength: TOOLTIPS.timbre,
lora_blend: TOOLTIPS.trainedStyleBlend,
};

/** Shared tooltip for an engine param id, or undefined if not cross-client. */
export function sharedTooltipFor(param: string): string | undefined {
if (param.startsWith("lora_str_")) return TOOLTIPS.trainedStyleStrength;
return SHARED_PARAM_TOOLTIPS[param];
}
49 changes: 49 additions & 0 deletions packages/demon-client/dist/demon-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,50 @@ function captureConfigFromState(snapshot, base) {
);
}

// copy/terms.ts
var TRAINED_STYLES = "Trained Styles";
var TRAINED_STYLE = "Trained Style";

// copy/labels.ts
var LABELS = {
/** "Trained Styles" / "Trained Style" — re-exported for convenience. */
plural: TRAINED_STYLES,
singular: TRAINED_STYLE,
/** Library panel / accordion header. */
library: `${TRAINED_STYLES} Library`,
/** Empty-state copy when no styles are available. */
noneFound: `no ${TRAINED_STYLES} found`,
/** Search box placeholder (lowercase — reads as a hint). */
searchPlaceholder: `search ${TRAINED_STYLES.toLowerCase()}`,
/** Search box accessible name. */
searchAria: `Search ${TRAINED_STYLES} library`,
/** Crossfade control label (slot A ↔ slot B). */
blend: `${TRAINED_STYLE} Blend`,
/** Status shown while the engine reloads adapters. */
refitInProgress: `Applying\u2026 (${TRAINED_STYLE} refit in progress)`
};

// copy/tooltips.ts
var TOOLTIPS = {
// ── Remix macros ──
strength: "How much the model reshapes the source audio. Keep it low for a subtle remix that stays close to the original; push it high to fully transform the track into something new. The most expressive knob \u2014 try sweeping it during playback.",
structure: "How closely the model follows the original song's structure \u2014 sections, rhythm, dynamics. Crank it up to keep the arrangement intact; drop it to let the model rearrange more freely.",
timbre: "How much of the source's instrument character (tone, color) carries into the output. High keeps the original instruments recognizable; low frees the model to swap them for whatever fits the prompt.",
// ── Trained Styles ──
trainedStyleStrength: "How strongly this Trained Style shapes the output. Trained Styles are little style packs \u2014 set a low value for a subtle flavor, crank past 1.0 to make this style dominate the sound. Multiple Trained Styles stack \u2014 turn several on at once for combined styles.",
trainedStyleBlend: "Crossfade between Trained Style A and Trained Style B. 0 = A only, 1 = B only, 0.5 = both at half strength. Use this to morph between two styles smoothly."
};
var SHARED_PARAM_TOOLTIPS = {
denoise: TOOLTIPS.strength,
hint_strength: TOOLTIPS.structure,
timbre_strength: TOOLTIPS.timbre,
lora_blend: TOOLTIPS.trainedStyleBlend
};
function sharedTooltipFor(param) {
if (param.startsWith("lora_str_")) return TOOLTIPS.trainedStyleStrength;
return SHARED_PARAM_TOOLTIPS[param];
}

// inputs.ts
function writeAscii(view, offset, text) {
for (let i = 0; i < text.length; i++) {
Expand Down Expand Up @@ -3259,6 +3303,7 @@ export {
EVENT_NAMES,
KNOB_SCHEMA_VERSION,
KNOWN_TOP_LEVEL_KEYS,
LABELS,
LOOP_GRID_ORDER,
PREEMPTED_CLOSE_CODE,
PROTOCOL_VERSION,
Expand All @@ -3269,6 +3314,9 @@ export {
SLICE_FLAG_RAW,
SLICE_HDR_SIZE,
T,
TOOLTIPS,
TRAINED_STYLE,
TRAINED_STYLES,
VALID_TIME_SIGNATURES,
WsReconnector,
applyConfigToState,
Expand Down Expand Up @@ -3300,5 +3348,6 @@ export {
rtmgConfigToSessionConfig,
selectVariant,
serializeConfig,
sharedTooltipFor,
withUnknownKeys
};
5 changes: 5 additions & 0 deletions packages/demon-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export * from "./types/protocol";
// /api/knobs and /api/protocol contracts above. See ./config and README.
export * from "./config";

// Shared user-facing copy: canonical "Trained Styles" terminology, UI
// labels, and cross-client tooltip text. Pure string constants — the one
// place all three frontends agree on wording. See ./copy.
export * from "./copy";

// Portable inputs codec: SerializedInput(s) shape + the shared WAV/base64
// codec the DemonExport `inputs` field rides on (the store/DOM capture/apply
// stays per-client). See ./inputs.
Expand Down