@signalsandsorcery/plugin-sdk 2.24.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.mjs CHANGED
@@ -2465,6 +2465,8 @@ function parseCrossfadePairs(sceneData) {
2465
2465
  sliderPos: g.origin.meta.sliderPos,
2466
2466
  originDbId: g.origin.dbId,
2467
2467
  targetDbId: g.target.dbId,
2468
+ originSourceDbId: g.origin.meta.sourceTrackDbId,
2469
+ targetSourceDbId: g.target.meta.sourceTrackDbId,
2468
2470
  originSourceName: g.origin.meta.sourceName,
2469
2471
  originSoundLabel: g.origin.meta.soundLabel,
2470
2472
  targetSourceName: g.target.meta.sourceName,
@@ -2473,6 +2475,25 @@ function parseCrossfadePairs(sceneData) {
2473
2475
  }
2474
2476
  return pairs;
2475
2477
  }
2478
+ var FADE_FLOOR_DB = -80;
2479
+ function gainToDb(gain) {
2480
+ return gain <= 1e-4 ? FADE_FLOOR_DB : Math.max(FADE_FLOOR_DB, 20 * Math.log10(gain));
2481
+ }
2482
+ function buildCrossfadeVolumeCurves(bars, bpm, sliderPos, steps = 32) {
2483
+ const durationSeconds = bars * 4 * 60 / Math.max(1, bpm);
2484
+ const s = Math.min(0.98, Math.max(0.02, sliderPos));
2485
+ const round = (n) => Math.round(n * 1e3) / 1e3;
2486
+ const origin = [];
2487
+ const target = [];
2488
+ for (let i = 0; i <= steps; i++) {
2489
+ const x = i / steps;
2490
+ const time = round(x * durationSeconds);
2491
+ const theta = x <= s ? x / s * (Math.PI / 4) : Math.PI / 4 + (x - s) / (1 - s) * (Math.PI / 4);
2492
+ origin.push({ time, db: Math.round(gainToDb(Math.cos(theta)) * 100) / 100 });
2493
+ target.push({ time, db: Math.round(gainToDb(Math.sin(theta)) * 100) / 100 });
2494
+ }
2495
+ return { origin, target };
2496
+ }
2476
2497
 
2477
2498
  // src/crossfade-inpaint.ts
2478
2499
  var PITCH_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
@@ -2721,6 +2742,7 @@ function CrossfadeModal({
2721
2742
  toSceneId,
2722
2743
  fromSceneName,
2723
2744
  toSceneName,
2745
+ excludeSourceDbIds,
2724
2746
  onClose,
2725
2747
  onCreate,
2726
2748
  testIdPrefix = "crossfade-modal"
@@ -2730,6 +2752,8 @@ function CrossfadeModal({
2730
2752
  const [targetDbId, setTargetDbId] = useState8("");
2731
2753
  const [isCreating, setIsCreating] = useState8(false);
2732
2754
  const [error, setError] = useState8(null);
2755
+ const [fromName, setFromName] = useState8(null);
2756
+ const [toName, setToName] = useState8(null);
2733
2757
  const cancelRef = useRef7(null);
2734
2758
  const refresh = useCallback5(async () => {
2735
2759
  if (!host.listSceneFamilyTracks) {
@@ -2738,12 +2762,15 @@ function CrossfadeModal({
2738
2762
  }
2739
2763
  setLoad({ status: "loading" });
2740
2764
  try {
2741
- const [origin, target] = await Promise.all([
2765
+ const [origin, target, fName, tName] = await Promise.all([
2742
2766
  host.listSceneFamilyTracks(fromSceneId),
2743
- host.listSceneFamilyTracks(toSceneId)
2767
+ host.listSceneFamilyTracks(toSceneId),
2768
+ host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),
2769
+ host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null)
2744
2770
  ]);
2771
+ setFromName(fName);
2772
+ setToName(tName);
2745
2773
  setLoad({ status: "ready", origin, target });
2746
- setOriginDbId(origin[0]?.dbId ?? "");
2747
2774
  } catch (err) {
2748
2775
  setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
2749
2776
  }
@@ -2757,21 +2784,26 @@ function CrossfadeModal({
2757
2784
  void refresh();
2758
2785
  }
2759
2786
  }, [open, refresh]);
2760
- const originTrack = useMemo3(
2761
- () => load.status === "ready" ? load.origin.find((t) => t.dbId === originDbId) ?? null : null,
2762
- [load, originDbId]
2787
+ const excludeSet = useMemo3(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
2788
+ const originCandidates = useMemo3(
2789
+ () => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
2790
+ [load, excludeSet]
2791
+ );
2792
+ const targetCandidates = useMemo3(
2793
+ () => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
2794
+ [load, excludeSet]
2763
2795
  );
2764
- const originRole = originTrack?.role;
2765
- const targetCandidates = useMemo3(() => {
2766
- if (load.status !== "ready") return [];
2767
- if (!originRole) return load.target;
2768
- return load.target.filter((t) => t.role === originRole);
2769
- }, [load, originRole]);
2796
+ useEffect7(() => {
2797
+ if (!originCandidates.some((t) => t.dbId === originDbId)) {
2798
+ setOriginDbId(originCandidates[0]?.dbId ?? "");
2799
+ }
2800
+ }, [originCandidates, originDbId]);
2770
2801
  useEffect7(() => {
2771
2802
  if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
2772
2803
  setTargetDbId(targetCandidates[0]?.dbId ?? "");
2773
2804
  }
2774
2805
  }, [targetCandidates, targetDbId]);
2806
+ const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
2775
2807
  const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
2776
2808
  const canCreate = !isCreating && !!originTrack && !!targetTrack;
2777
2809
  const handleClose = useCallback5(() => {
@@ -2792,6 +2824,8 @@ function CrossfadeModal({
2792
2824
  setIsCreating(false);
2793
2825
  }
2794
2826
  }, [originTrack, targetTrack, onCreate, onClose]);
2827
+ const fromLabel = fromName ?? fromSceneName ?? null;
2828
+ const toLabel = toName ?? toSceneName ?? null;
2795
2829
  if (!open) return null;
2796
2830
  return /* @__PURE__ */ jsx14(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ jsxs10(
2797
2831
  "div",
@@ -2804,24 +2838,31 @@ function CrossfadeModal({
2804
2838
  /* @__PURE__ */ jsxs10("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
2805
2839
  "Bridge a track from",
2806
2840
  " ",
2807
- /* @__PURE__ */ jsx14("span", { className: "text-sas-text", children: fromSceneName ?? "the origin scene" }),
2841
+ /* @__PURE__ */ jsx14("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
2808
2842
  " into one from",
2809
2843
  " ",
2810
- /* @__PURE__ */ jsx14("span", { className: "text-sas-text", children: toSceneName ?? "the target scene" }),
2844
+ /* @__PURE__ */ jsx14("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
2811
2845
  ". Both layers share one generated part; each keeps its own preset."
2812
2846
  ] }),
2813
2847
  load.status === "loading" && /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
2814
2848
  load.status === "error" && /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
2815
- load.status === "ready" && (load.origin.length === 0 ? /* @__PURE__ */ jsx14(
2849
+ load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ jsxs10(
2816
2850
  "div",
2817
2851
  {
2818
2852
  className: "text-xs text-sas-muted py-4 text-center",
2819
2853
  "data-testid": `${testIdPrefix}-empty-origin`,
2820
- children: "No matching tracks in the origin scene. Add one there first."
2854
+ children: [
2855
+ "No available tracks in ",
2856
+ fromLabel ?? "the origin scene",
2857
+ ". Add one (or free one from another crossfade) first."
2858
+ ]
2821
2859
  }
2822
2860
  ) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
2823
2861
  /* @__PURE__ */ jsxs10("label", { className: "block", children: [
2824
- /* @__PURE__ */ jsx14("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "Origin (top)" }),
2862
+ /* @__PURE__ */ jsxs10("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
2863
+ "Origin ",
2864
+ fromLabel ? `(${fromLabel})` : "(top)"
2865
+ ] }),
2825
2866
  /* @__PURE__ */ jsx14(
2826
2867
  "select",
2827
2868
  {
@@ -2830,7 +2871,7 @@ function CrossfadeModal({
2830
2871
  onChange: (e) => setOriginDbId(e.target.value),
2831
2872
  disabled: isCreating,
2832
2873
  className: "sas-input w-full mt-0.5 text-xs",
2833
- children: load.origin.map((t) => /* @__PURE__ */ jsxs10("option", { value: t.dbId, children: [
2874
+ children: originCandidates.map((t) => /* @__PURE__ */ jsxs10("option", { value: t.dbId, children: [
2834
2875
  t.name,
2835
2876
  t.role ? ` \xB7 ${t.role}` : ""
2836
2877
  ] }, t.dbId))
@@ -2839,13 +2880,13 @@ function CrossfadeModal({
2839
2880
  ] }),
2840
2881
  /* @__PURE__ */ jsxs10("label", { className: "block", children: [
2841
2882
  /* @__PURE__ */ jsxs10("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
2842
- "Target (bottom)",
2843
- originRole ? ` \xB7 ${originRole}` : ""
2883
+ "Target ",
2884
+ toLabel ? `(${toLabel})` : "(bottom)"
2844
2885
  ] }),
2845
2886
  targetCandidates.length === 0 ? /* @__PURE__ */ jsxs10("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
2846
- "No ",
2847
- originRole ?? "matching",
2848
- " track in the target scene to crossfade into."
2887
+ "No available tracks in ",
2888
+ toLabel ?? "the target scene",
2889
+ " to crossfade into."
2849
2890
  ] }) : /* @__PURE__ */ jsx14(
2850
2891
  "select",
2851
2892
  {
@@ -3764,7 +3805,7 @@ function useTrackReorder({
3764
3805
  }
3765
3806
 
3766
3807
  // src/constants/sdk-version.ts
3767
- var PLUGIN_SDK_VERSION = "2.24.0";
3808
+ var PLUGIN_SDK_VERSION = "2.26.0";
3768
3809
 
3769
3810
  // src/utils/format-concurrent-tracks.ts
3770
3811
  function formatConcurrentTracks(ctx) {
@@ -3950,6 +3991,7 @@ export {
3950
3991
  analyzeWavPeak,
3951
3992
  asCrossfadeMeta,
3952
3993
  buildCrossfadeInpaintPrompt,
3994
+ buildCrossfadeVolumeCurves,
3953
3995
  calculateTimeBasedTarget,
3954
3996
  cellToPx,
3955
3997
  centerScrollTop,