@signalsandsorcery/plugin-sdk 2.25.1 → 2.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +21 -5
- package/dist/index.d.ts +21 -5
- package/dist/index.js +46 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +46 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -547,6 +547,11 @@ interface PluginHost {
|
|
|
547
547
|
key: string;
|
|
548
548
|
mode: string;
|
|
549
549
|
} | null>;
|
|
550
|
+
/**
|
|
551
|
+
* Read a scene's human display name by db id (for labelling a crossfade's
|
|
552
|
+
* origin/target scenes). Optional — callers MUST null-check. @since SDK 2.26.0
|
|
553
|
+
*/
|
|
554
|
+
getSceneName?(sceneDbId: string): Promise<string | null>;
|
|
550
555
|
/** Subscribe to transport state changes. Returns unsubscribe function. */
|
|
551
556
|
onTransportEvent(listener: TransportEventListener): UnsubscribeFn;
|
|
552
557
|
/** Subscribe to deck boundary events. Returns unsubscribe function. */
|
|
@@ -2602,6 +2607,10 @@ interface CrossfadePairMeta {
|
|
|
2602
2607
|
sliderPos: number;
|
|
2603
2608
|
originDbId: string;
|
|
2604
2609
|
targetDbId: string;
|
|
2610
|
+
/** DB id of the ORIGIN source track (in the from scene) — drives the "used once" exclusion. */
|
|
2611
|
+
originSourceDbId: string;
|
|
2612
|
+
/** DB id of the TARGET source track (in the to scene). */
|
|
2613
|
+
targetSourceDbId: string;
|
|
2605
2614
|
originSourceName: string;
|
|
2606
2615
|
originSoundLabel: string;
|
|
2607
2616
|
targetSourceName: string;
|
|
@@ -2826,8 +2835,9 @@ declare function ImportTrackModal({ host, open, onClose, onImported, title, test
|
|
|
2826
2835
|
*
|
|
2827
2836
|
* Shown only inside a `scene_type='transition'` scene. The user picks an ORIGIN
|
|
2828
2837
|
* track (from the transition's FROM scene) and a TARGET track (from its TO
|
|
2829
|
-
* scene)
|
|
2830
|
-
*
|
|
2838
|
+
* scene), in ANY order — the only constraint is same plugin/family (the picker is
|
|
2839
|
+
* per-panel). A source track already used in a crossfade is hidden (via
|
|
2840
|
+
* excludeSourceDbIds), so each source is used at most once.
|
|
2831
2841
|
*
|
|
2832
2842
|
* Self-fetching: given the scoped `host`, it calls `host.listSceneFamilyTracks`
|
|
2833
2843
|
* for both scenes (ungated — a transition deliberately bridges different keys).
|
|
@@ -2845,7 +2855,7 @@ interface CrossfadeSelection {
|
|
|
2845
2855
|
dbId: string;
|
|
2846
2856
|
/** Display name (for the row caption). */
|
|
2847
2857
|
name: string;
|
|
2848
|
-
/** Musical role
|
|
2858
|
+
/** Musical role of the source track (the panel uses the TARGET's for generation). */
|
|
2849
2859
|
role?: string;
|
|
2850
2860
|
}
|
|
2851
2861
|
interface CrossfadeModalProps {
|
|
@@ -2861,6 +2871,12 @@ interface CrossfadeModalProps {
|
|
|
2861
2871
|
fromSceneName?: string;
|
|
2862
2872
|
/** Display name for the target scene heading (optional). */
|
|
2863
2873
|
toSceneName?: string;
|
|
2874
|
+
/**
|
|
2875
|
+
* Source-track DB ids already used in a crossfade (origin + target of every
|
|
2876
|
+
* existing pair in this panel). Hidden from BOTH dropdowns so each source is
|
|
2877
|
+
* used at most once. @since SDK 2.26.0
|
|
2878
|
+
*/
|
|
2879
|
+
excludeSourceDbIds?: readonly string[];
|
|
2864
2880
|
/** Close handler (Escape, backdrop, Cancel, or after a successful create). */
|
|
2865
2881
|
onClose: () => void;
|
|
2866
2882
|
/** Build the crossfade pair. Should reject on failure so the modal shows it. */
|
|
@@ -2868,7 +2884,7 @@ interface CrossfadeModalProps {
|
|
|
2868
2884
|
/** data-testid prefix. */
|
|
2869
2885
|
testIdPrefix?: string;
|
|
2870
2886
|
}
|
|
2871
|
-
declare function CrossfadeModal({ host, open, fromSceneId, toSceneId, fromSceneName, toSceneName, onClose, onCreate, testIdPrefix, }: CrossfadeModalProps): React.ReactElement | null;
|
|
2887
|
+
declare function CrossfadeModal({ host, open, fromSceneId, toSceneId, fromSceneName, toSceneName, excludeSourceDbIds, onClose, onCreate, testIdPrefix, }: CrossfadeModalProps): React.ReactElement | null;
|
|
2872
2888
|
|
|
2873
2889
|
/**
|
|
2874
2890
|
* ConfirmDialog — styled in-app confirmation modal (SDK component).
|
|
@@ -3593,7 +3609,7 @@ declare function useSoundHistory(applySound: (trackId: string, descriptor: unkno
|
|
|
3593
3609
|
* Registry checks semver.gte(PLUGIN_SDK_VERSION, manifest.minHostVersion)
|
|
3594
3610
|
* during activation and marks incompatible plugins accordingly.
|
|
3595
3611
|
*/
|
|
3596
|
-
declare const PLUGIN_SDK_VERSION = "2.
|
|
3612
|
+
declare const PLUGIN_SDK_VERSION = "2.26.0";
|
|
3597
3613
|
|
|
3598
3614
|
/**
|
|
3599
3615
|
* FX Preset Definitions
|
package/dist/index.d.ts
CHANGED
|
@@ -547,6 +547,11 @@ interface PluginHost {
|
|
|
547
547
|
key: string;
|
|
548
548
|
mode: string;
|
|
549
549
|
} | null>;
|
|
550
|
+
/**
|
|
551
|
+
* Read a scene's human display name by db id (for labelling a crossfade's
|
|
552
|
+
* origin/target scenes). Optional — callers MUST null-check. @since SDK 2.26.0
|
|
553
|
+
*/
|
|
554
|
+
getSceneName?(sceneDbId: string): Promise<string | null>;
|
|
550
555
|
/** Subscribe to transport state changes. Returns unsubscribe function. */
|
|
551
556
|
onTransportEvent(listener: TransportEventListener): UnsubscribeFn;
|
|
552
557
|
/** Subscribe to deck boundary events. Returns unsubscribe function. */
|
|
@@ -2602,6 +2607,10 @@ interface CrossfadePairMeta {
|
|
|
2602
2607
|
sliderPos: number;
|
|
2603
2608
|
originDbId: string;
|
|
2604
2609
|
targetDbId: string;
|
|
2610
|
+
/** DB id of the ORIGIN source track (in the from scene) — drives the "used once" exclusion. */
|
|
2611
|
+
originSourceDbId: string;
|
|
2612
|
+
/** DB id of the TARGET source track (in the to scene). */
|
|
2613
|
+
targetSourceDbId: string;
|
|
2605
2614
|
originSourceName: string;
|
|
2606
2615
|
originSoundLabel: string;
|
|
2607
2616
|
targetSourceName: string;
|
|
@@ -2826,8 +2835,9 @@ declare function ImportTrackModal({ host, open, onClose, onImported, title, test
|
|
|
2826
2835
|
*
|
|
2827
2836
|
* Shown only inside a `scene_type='transition'` scene. The user picks an ORIGIN
|
|
2828
2837
|
* track (from the transition's FROM scene) and a TARGET track (from its TO
|
|
2829
|
-
* scene)
|
|
2830
|
-
*
|
|
2838
|
+
* scene), in ANY order — the only constraint is same plugin/family (the picker is
|
|
2839
|
+
* per-panel). A source track already used in a crossfade is hidden (via
|
|
2840
|
+
* excludeSourceDbIds), so each source is used at most once.
|
|
2831
2841
|
*
|
|
2832
2842
|
* Self-fetching: given the scoped `host`, it calls `host.listSceneFamilyTracks`
|
|
2833
2843
|
* for both scenes (ungated — a transition deliberately bridges different keys).
|
|
@@ -2845,7 +2855,7 @@ interface CrossfadeSelection {
|
|
|
2845
2855
|
dbId: string;
|
|
2846
2856
|
/** Display name (for the row caption). */
|
|
2847
2857
|
name: string;
|
|
2848
|
-
/** Musical role
|
|
2858
|
+
/** Musical role of the source track (the panel uses the TARGET's for generation). */
|
|
2849
2859
|
role?: string;
|
|
2850
2860
|
}
|
|
2851
2861
|
interface CrossfadeModalProps {
|
|
@@ -2861,6 +2871,12 @@ interface CrossfadeModalProps {
|
|
|
2861
2871
|
fromSceneName?: string;
|
|
2862
2872
|
/** Display name for the target scene heading (optional). */
|
|
2863
2873
|
toSceneName?: string;
|
|
2874
|
+
/**
|
|
2875
|
+
* Source-track DB ids already used in a crossfade (origin + target of every
|
|
2876
|
+
* existing pair in this panel). Hidden from BOTH dropdowns so each source is
|
|
2877
|
+
* used at most once. @since SDK 2.26.0
|
|
2878
|
+
*/
|
|
2879
|
+
excludeSourceDbIds?: readonly string[];
|
|
2864
2880
|
/** Close handler (Escape, backdrop, Cancel, or after a successful create). */
|
|
2865
2881
|
onClose: () => void;
|
|
2866
2882
|
/** Build the crossfade pair. Should reject on failure so the modal shows it. */
|
|
@@ -2868,7 +2884,7 @@ interface CrossfadeModalProps {
|
|
|
2868
2884
|
/** data-testid prefix. */
|
|
2869
2885
|
testIdPrefix?: string;
|
|
2870
2886
|
}
|
|
2871
|
-
declare function CrossfadeModal({ host, open, fromSceneId, toSceneId, fromSceneName, toSceneName, onClose, onCreate, testIdPrefix, }: CrossfadeModalProps): React.ReactElement | null;
|
|
2887
|
+
declare function CrossfadeModal({ host, open, fromSceneId, toSceneId, fromSceneName, toSceneName, excludeSourceDbIds, onClose, onCreate, testIdPrefix, }: CrossfadeModalProps): React.ReactElement | null;
|
|
2872
2888
|
|
|
2873
2889
|
/**
|
|
2874
2890
|
* ConfirmDialog — styled in-app confirmation modal (SDK component).
|
|
@@ -3593,7 +3609,7 @@ declare function useSoundHistory(applySound: (trackId: string, descriptor: unkno
|
|
|
3593
3609
|
* Registry checks semver.gte(PLUGIN_SDK_VERSION, manifest.minHostVersion)
|
|
3594
3610
|
* during activation and marks incompatible plugins accordingly.
|
|
3595
3611
|
*/
|
|
3596
|
-
declare const PLUGIN_SDK_VERSION = "2.
|
|
3612
|
+
declare const PLUGIN_SDK_VERSION = "2.26.0";
|
|
3597
3613
|
|
|
3598
3614
|
/**
|
|
3599
3615
|
* FX Preset Definitions
|
package/dist/index.js
CHANGED
|
@@ -2570,6 +2570,8 @@ function parseCrossfadePairs(sceneData) {
|
|
|
2570
2570
|
sliderPos: g.origin.meta.sliderPos,
|
|
2571
2571
|
originDbId: g.origin.dbId,
|
|
2572
2572
|
targetDbId: g.target.dbId,
|
|
2573
|
+
originSourceDbId: g.origin.meta.sourceTrackDbId,
|
|
2574
|
+
targetSourceDbId: g.target.meta.sourceTrackDbId,
|
|
2573
2575
|
originSourceName: g.origin.meta.sourceName,
|
|
2574
2576
|
originSoundLabel: g.origin.meta.soundLabel,
|
|
2575
2577
|
targetSourceName: g.target.meta.sourceName,
|
|
@@ -2845,6 +2847,7 @@ function CrossfadeModal({
|
|
|
2845
2847
|
toSceneId,
|
|
2846
2848
|
fromSceneName,
|
|
2847
2849
|
toSceneName,
|
|
2850
|
+
excludeSourceDbIds,
|
|
2848
2851
|
onClose,
|
|
2849
2852
|
onCreate,
|
|
2850
2853
|
testIdPrefix = "crossfade-modal"
|
|
@@ -2854,6 +2857,8 @@ function CrossfadeModal({
|
|
|
2854
2857
|
const [targetDbId, setTargetDbId] = (0, import_react12.useState)("");
|
|
2855
2858
|
const [isCreating, setIsCreating] = (0, import_react12.useState)(false);
|
|
2856
2859
|
const [error, setError] = (0, import_react12.useState)(null);
|
|
2860
|
+
const [fromName, setFromName] = (0, import_react12.useState)(null);
|
|
2861
|
+
const [toName, setToName] = (0, import_react12.useState)(null);
|
|
2857
2862
|
const cancelRef = (0, import_react12.useRef)(null);
|
|
2858
2863
|
const refresh = (0, import_react12.useCallback)(async () => {
|
|
2859
2864
|
if (!host.listSceneFamilyTracks) {
|
|
@@ -2862,12 +2867,15 @@ function CrossfadeModal({
|
|
|
2862
2867
|
}
|
|
2863
2868
|
setLoad({ status: "loading" });
|
|
2864
2869
|
try {
|
|
2865
|
-
const [origin, target] = await Promise.all([
|
|
2870
|
+
const [origin, target, fName, tName] = await Promise.all([
|
|
2866
2871
|
host.listSceneFamilyTracks(fromSceneId),
|
|
2867
|
-
host.listSceneFamilyTracks(toSceneId)
|
|
2872
|
+
host.listSceneFamilyTracks(toSceneId),
|
|
2873
|
+
host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),
|
|
2874
|
+
host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null)
|
|
2868
2875
|
]);
|
|
2876
|
+
setFromName(fName);
|
|
2877
|
+
setToName(tName);
|
|
2869
2878
|
setLoad({ status: "ready", origin, target });
|
|
2870
|
-
setOriginDbId(origin[0]?.dbId ?? "");
|
|
2871
2879
|
} catch (err) {
|
|
2872
2880
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
|
|
2873
2881
|
}
|
|
@@ -2881,21 +2889,26 @@ function CrossfadeModal({
|
|
|
2881
2889
|
void refresh();
|
|
2882
2890
|
}
|
|
2883
2891
|
}, [open, refresh]);
|
|
2884
|
-
const
|
|
2885
|
-
|
|
2886
|
-
|
|
2892
|
+
const excludeSet = (0, import_react12.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
2893
|
+
const originCandidates = (0, import_react12.useMemo)(
|
|
2894
|
+
() => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
2895
|
+
[load, excludeSet]
|
|
2887
2896
|
);
|
|
2888
|
-
const
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2897
|
+
const targetCandidates = (0, import_react12.useMemo)(
|
|
2898
|
+
() => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
2899
|
+
[load, excludeSet]
|
|
2900
|
+
);
|
|
2901
|
+
(0, import_react12.useEffect)(() => {
|
|
2902
|
+
if (!originCandidates.some((t) => t.dbId === originDbId)) {
|
|
2903
|
+
setOriginDbId(originCandidates[0]?.dbId ?? "");
|
|
2904
|
+
}
|
|
2905
|
+
}, [originCandidates, originDbId]);
|
|
2894
2906
|
(0, import_react12.useEffect)(() => {
|
|
2895
2907
|
if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
|
|
2896
2908
|
setTargetDbId(targetCandidates[0]?.dbId ?? "");
|
|
2897
2909
|
}
|
|
2898
2910
|
}, [targetCandidates, targetDbId]);
|
|
2911
|
+
const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
|
|
2899
2912
|
const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
|
|
2900
2913
|
const canCreate = !isCreating && !!originTrack && !!targetTrack;
|
|
2901
2914
|
const handleClose = (0, import_react12.useCallback)(() => {
|
|
@@ -2916,6 +2929,8 @@ function CrossfadeModal({
|
|
|
2916
2929
|
setIsCreating(false);
|
|
2917
2930
|
}
|
|
2918
2931
|
}, [originTrack, targetTrack, onCreate, onClose]);
|
|
2932
|
+
const fromLabel = fromName ?? fromSceneName ?? null;
|
|
2933
|
+
const toLabel = toName ?? toSceneName ?? null;
|
|
2919
2934
|
if (!open) return null;
|
|
2920
2935
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
2921
2936
|
"div",
|
|
@@ -2928,24 +2943,31 @@ function CrossfadeModal({
|
|
|
2928
2943
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
|
|
2929
2944
|
"Bridge a track from",
|
|
2930
2945
|
" ",
|
|
2931
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children:
|
|
2946
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
|
|
2932
2947
|
" into one from",
|
|
2933
2948
|
" ",
|
|
2934
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children:
|
|
2949
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
|
|
2935
2950
|
". Both layers share one generated part; each keeps its own preset."
|
|
2936
2951
|
] }),
|
|
2937
2952
|
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
|
|
2938
2953
|
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
|
|
2939
|
-
load.status === "ready" && (
|
|
2954
|
+
load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
2940
2955
|
"div",
|
|
2941
2956
|
{
|
|
2942
2957
|
className: "text-xs text-sas-muted py-4 text-center",
|
|
2943
2958
|
"data-testid": `${testIdPrefix}-empty-origin`,
|
|
2944
|
-
children:
|
|
2959
|
+
children: [
|
|
2960
|
+
"No available tracks in ",
|
|
2961
|
+
fromLabel ?? "the origin scene",
|
|
2962
|
+
". Add one (or free one from another crossfade) first."
|
|
2963
|
+
]
|
|
2945
2964
|
}
|
|
2946
2965
|
) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
|
|
2947
2966
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "block", children: [
|
|
2948
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.
|
|
2967
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
2968
|
+
"Origin ",
|
|
2969
|
+
fromLabel ? `(${fromLabel})` : "(top)"
|
|
2970
|
+
] }),
|
|
2949
2971
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2950
2972
|
"select",
|
|
2951
2973
|
{
|
|
@@ -2954,7 +2976,7 @@ function CrossfadeModal({
|
|
|
2954
2976
|
onChange: (e) => setOriginDbId(e.target.value),
|
|
2955
2977
|
disabled: isCreating,
|
|
2956
2978
|
className: "sas-input w-full mt-0.5 text-xs",
|
|
2957
|
-
children:
|
|
2979
|
+
children: originCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("option", { value: t.dbId, children: [
|
|
2958
2980
|
t.name,
|
|
2959
2981
|
t.role ? ` \xB7 ${t.role}` : ""
|
|
2960
2982
|
] }, t.dbId))
|
|
@@ -2963,13 +2985,13 @@ function CrossfadeModal({
|
|
|
2963
2985
|
] }),
|
|
2964
2986
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "block", children: [
|
|
2965
2987
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
2966
|
-
"Target
|
|
2967
|
-
|
|
2988
|
+
"Target ",
|
|
2989
|
+
toLabel ? `(${toLabel})` : "(bottom)"
|
|
2968
2990
|
] }),
|
|
2969
2991
|
targetCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
|
|
2970
|
-
"No ",
|
|
2971
|
-
|
|
2972
|
-
"
|
|
2992
|
+
"No available tracks in ",
|
|
2993
|
+
toLabel ?? "the target scene",
|
|
2994
|
+
" to crossfade into."
|
|
2973
2995
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2974
2996
|
"select",
|
|
2975
2997
|
{
|
|
@@ -3888,7 +3910,7 @@ function useTrackReorder({
|
|
|
3888
3910
|
}
|
|
3889
3911
|
|
|
3890
3912
|
// src/constants/sdk-version.ts
|
|
3891
|
-
var PLUGIN_SDK_VERSION = "2.
|
|
3913
|
+
var PLUGIN_SDK_VERSION = "2.26.0";
|
|
3892
3914
|
|
|
3893
3915
|
// src/utils/format-concurrent-tracks.ts
|
|
3894
3916
|
function formatConcurrentTracks(ctx) {
|