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
20 changes: 20 additions & 0 deletions demos/realtime_motion_graph_web/web/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ export interface RtmgConfigEngine {
* removes its trigger from the prompt when it's still at the head.
* Defaults to true. */
auto_prepend_lora_triggers?: boolean;
/** When true, the loop-focused workflow prepends a "loopiness" phrase
* (``loop_phrase``) onto the prompt ON THE WIRE — exactly like
* ``auto_prepend_lora_triggers`` — so the text encoder is told the
* section is a seamless loop. An offline A/B study found this lowers
* the loop-seam discontinuity ~12–14% on average, concentrated on
* rhythmic material (techno −22%, deep house −18%) and neutral on
* smooth pads, with no measured downside. The Tags A/B textareas stay
* the operator's clean text; the phrase is injected at send-time and
* stripped on the next send, so toggling it (config.json + refresh)
* immediately changes what the encoder sees. Default false (opt-in,
* pending ear confirmation). See scripts/experiments/loop_prompting/. */
auto_prepend_loop_phrase?: boolean;
/** The phrase prepended when ``auto_prepend_loop_phrase`` is true. The
* operator's prompt is appended after it, i.e. ``"<loop_phrase> <tags>"``.
* Defaults to ``"a short perfect loop of"`` (the study's best
* performer; it edged ``"seamless repeating loop of"``). Edit to
* experiment with phrasing. */
loop_phrase?: string;
/** When true, the LoRA library shows every entry regardless of
* whether its trained ``base_model_scale`` matches the active
* checkpoint. Useful for inspecting your full collection while
Expand Down Expand Up @@ -356,6 +374,8 @@ export const DEFAULT_CONFIG: RtmgConfig = {
time_signature: DEFAULT_TIME_SIGNATURE,
enabled_loras: [],
auto_prepend_lora_triggers: true,
auto_prepend_loop_phrase: false,
loop_phrase: "a short perfect loop of",
show_incompatible_loras: false,
},
prompts: {
Expand Down
65 changes: 65 additions & 0 deletions demos/realtime_motion_graph_web/web/lib/loopPhrase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"use client";

// Wire-side loop-phrase injection.
//
// The loop-focused workflow loops a single generated section. Telling
// the text encoder up front that the section is a *loop* measurably
// tightens the loop seam (offline A/B study in
// scripts/experiments/loop_prompting/: ~12–14% lower seam discontinuity
// on average, concentrated on rhythmic material, no downside).
//
// Like LoRA triggers, we do NOT store the phrase in promptA/promptB (the
// Tags A/B textareas stay the operator's clean text); we prepend it onto
// the WIRE at send-time in `wirePromptTransform` (lib/loraTriggers.ts).
// The prefix is recomputed on every send and stripped from the incoming
// text first, so there is no double-prepend and toggling the flag
// (engine.auto_prepend_loop_phrase via config.json + refresh) immediately
// changes what the encoder sees on the next send.
//
// Gated on `engine.auto_prepend_loop_phrase` (default false, opt-in). The
// phrase itself is `engine.loop_phrase` (default below).

import { getConfig } from "@/lib/config";

/** Fallback when `engine.loop_phrase` is absent. The study's best
* performer (it edged "seamless repeating loop of"). */
export const DEFAULT_LOOP_PHRASE = "a short perfect loop of";

function configuredPhrase(): string {
return (getConfig().engine.loop_phrase ?? DEFAULT_LOOP_PHRASE).trim();
}

/** The loop phrase with a trailing space, ready to concatenate ahead of
* a prompt — or "" when the flag is off or the phrase is empty. */
export function loopPhrasePrefix(): string {
if ((getConfig().engine.auto_prepend_loop_phrase ?? false) !== true) {
return "";
}
const phrase = configuredPhrase();
return phrase ? `${phrase} ` : "";
}

/** Strip a leading copy (or accidental stack) of the configured loop
* phrase off a prompt, returning the operator's clean text. Inverse of
* `loopPhrasePrefix`; case-insensitive; best-effort (only the currently
* configured phrase is recognised, mirroring the LoRA-trigger strip's
* best-effort contract). Requires a trailing space after the phrase so a
* bare prompt equal to the phrase is left untouched. */
export function stripLeadingLoopPhrase(text: string): string {
if (!text) return text;
const phrase = configuredPhrase();
if (!phrase) return text;
const needle = `${phrase.toLowerCase()} `;
let out = text;
// Bounded loop: drop repeated leading occurrences (prefix drift / a
// phrase change that stacked), capped so a pathological input can't spin.
for (let guard = 0; guard < 8; guard += 1) {
const lead = out.replace(/^\s+/, "");
if (!lead.toLowerCase().startsWith(needle)) {
out = lead;
break;
}
out = lead.slice(phrase.length).replace(/^\s+/, "");
}
return out;
}
8 changes: 7 additions & 1 deletion demos/realtime_motion_graph_web/web/lib/loraTriggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
// is empty.

import { getConfig } from "@/lib/config";
import { loopPhrasePrefix, stripLeadingLoopPhrase } from "@/lib/loopPhrase";
import { useLoraStore } from "@/store/useLoraStore";

/** Comma-joined trigger prefix for the currently-enabled LoRAs, with a
Expand Down Expand Up @@ -108,5 +109,10 @@ export function stripLeadingTriggers(text: string): string {
* disabled LoRA's trigger zero times and an enabled LoRA's trigger
* exactly once — per tag (A and B alike). */
export function wirePromptTransform(tags: string): string {
return enabledLoraTriggerPrefix() + stripLeadingTriggers(tags);
// Strip ANY prefix already on the text (stale/stacked LoRA triggers,
// and a leading loop phrase), then re-prepend the current prefixes.
// Wire order: <lora triggers><loop phrase><clean tags> — triggers stay
// at the head as activation tokens; the loop phrase wraps the prompt.
const clean = stripLeadingLoopPhrase(stripLeadingTriggers(tags));
return enabledLoraTriggerPrefix() + loopPhrasePrefix() + clean;
}
128 changes: 128 additions & 0 deletions demos/realtime_motion_graph_web/web/tests/unit/loopPhrase.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Loop-phrase wire injection: gating, strip/re-prepend idempotency, and
// composition with the LoRA-trigger prefix in `wirePromptTransform`.

import { beforeEach, describe, expect, it, vi } from "vitest";

// Mutable mock state, hoisted so the vi.mock factories (which run before
// the imports) can close over it. Each test tweaks these fields.
const state = vi.hoisted(() => ({
engine: {
auto_prepend_lora_triggers: false,
auto_prepend_loop_phrase: false,
loop_phrase: "a short perfect loop of",
} as Record<string, unknown>,
lora: { enabled: new Set<string>(), catalog: [] as Array<{ id: string; metadata?: { primary_trigger_word?: string } }> },
}));

vi.mock("@/lib/config", () => ({ getConfig: () => ({ engine: state.engine }) }));
vi.mock("@/store/useLoraStore", () => ({
useLoraStore: { getState: () => state.lora },
}));

import { loopPhrasePrefix, stripLeadingLoopPhrase } from "@/lib/loopPhrase";
import { wirePromptTransform } from "@/lib/loraTriggers";

beforeEach(() => {
state.engine = {
auto_prepend_lora_triggers: false,
auto_prepend_loop_phrase: false,
loop_phrase: "a short perfect loop of",
};
state.lora = { enabled: new Set<string>(), catalog: [] };
});

describe("loopPhrasePrefix", () => {
it("is empty when the flag is off (default)", () => {
expect(loopPhrasePrefix()).toBe("");
});

it("is the phrase + trailing space when on", () => {
state.engine.auto_prepend_loop_phrase = true;
expect(loopPhrasePrefix()).toBe("a short perfect loop of ");
});

it("honours a custom phrase", () => {
state.engine.auto_prepend_loop_phrase = true;
state.engine.loop_phrase = "seamless repeating loop of";
expect(loopPhrasePrefix()).toBe("seamless repeating loop of ");
});

it("is empty when the phrase is blank even if the flag is on", () => {
state.engine.auto_prepend_loop_phrase = true;
state.engine.loop_phrase = " ";
expect(loopPhrasePrefix()).toBe("");
});
});

describe("stripLeadingLoopPhrase", () => {
it("removes a single leading phrase", () => {
expect(stripLeadingLoopPhrase("a short perfect loop of driving techno")).toBe(
"driving techno",
);
});

it("removes accidental stacking", () => {
expect(
stripLeadingLoopPhrase(
"a short perfect loop of a short perfect loop of pads",
),
).toBe("pads");
});

it("is case-insensitive", () => {
expect(stripLeadingLoopPhrase("A Short Perfect Loop Of pads")).toBe("pads");
});

it("leaves a prompt without the phrase untouched", () => {
expect(stripLeadingLoopPhrase("driving techno")).toBe("driving techno");
});

it("does not strip a bare phrase with no trailing content", () => {
// No trailing space after the phrase -> not treated as a prefix.
expect(stripLeadingLoopPhrase("a short perfect loop of")).toBe(
"a short perfect loop of",
);
});
});

describe("wirePromptTransform (loop phrase)", () => {
it("passes the prompt through untouched when both features are off", () => {
expect(wirePromptTransform("driving techno")).toBe("driving techno");
});

it("prepends the loop phrase when enabled", () => {
state.engine.auto_prepend_loop_phrase = true;
expect(wirePromptTransform("driving techno")).toBe(
"a short perfect loop of driving techno",
);
});

it("is idempotent: re-sending its own output does not double-prepend", () => {
state.engine.auto_prepend_loop_phrase = true;
const once = wirePromptTransform("driving techno");
expect(wirePromptTransform(once)).toBe(once);
});

it("orders triggers before the loop phrase, then the clean prompt", () => {
state.engine.auto_prepend_lora_triggers = true;
state.engine.auto_prepend_loop_phrase = true;
state.lora = {
enabled: new Set(["phonk"]),
catalog: [{ id: "phonk", metadata: { primary_trigger_word: "phonk" } }],
};
expect(wirePromptTransform("driving techno")).toBe(
"phonk, a short perfect loop of driving techno",
);
});

it("stays idempotent with both prefixes active", () => {
state.engine.auto_prepend_lora_triggers = true;
state.engine.auto_prepend_loop_phrase = true;
state.lora = {
enabled: new Set(["phonk"]),
catalog: [{ id: "phonk", metadata: { primary_trigger_word: "phonk" } }],
};
const once = wirePromptTransform("driving techno");
expect(wirePromptTransform(once)).toBe(once);
});
});