diff --git a/co.meldstudio.streamdeck.sdPlugin/actions/show-scene/plugin.js b/co.meldstudio.streamdeck.sdPlugin/actions/show-scene/plugin.js
index e9d90c3..7f136d8 100644
--- a/co.meldstudio.streamdeck.sdPlugin/actions/show-scene/plugin.js
+++ b/co.meldstudio.streamdeck.sdPlugin/actions/show-scene/plugin.js
@@ -1,10 +1,5 @@
class ShowScene extends MeldStudioPlugin {
- errorState = [
- {
- image: "assets/sceneOn",
- title: "",
- },
- ];
+ sceneRequested = null;
constructor() {
super("co.meldstudio.streamdeck.show-scene");
@@ -13,22 +8,37 @@ class ShowScene extends MeldStudioPlugin {
const { scene } = this.getSettings(context);
if (!scene) return;
+ this.sceneRequested = scene;
+ this.updateState(context);
+
if ($MS.meld?.showScene) $MS.meld.showScene(scene);
});
$MS.on("sessionChanged", (session) => {
+ this.sceneRequested = null;
+
this.forAllContexts((context, settings) => {
- const { scene } = settings;
- if (!scene) return;
+ this.updateState(context);
+ });
+ });
+ }
- if (!session.items[scene])
- return $SD.setState(context, 0);
+ updateState(context) {
+ const { scene } = this.getSettings(context);
+ const session = $MS?.meld?.session;
- const state = session.items[scene].current ? 1 : 0;
+ const state = (() => {
+ if (!scene) return 0;
+ if (!session || !session?.items) return 0;
- $SD.setState(context, state);
- });
- });
+ const item = session.items[scene];
+
+ if (!item) return 0;
+ if (scene == this.sceneRequested) return 1;
+ return item.current ? 1 : 0;
+ })();
+
+ $SD.setState(context, state);
}
}
diff --git a/co.meldstudio.streamdeck.sdPlugin/actions/toggle-layer/plugin.js b/co.meldstudio.streamdeck.sdPlugin/actions/toggle-layer/plugin.js
index e6056e1..ec11ee1 100644
--- a/co.meldstudio.streamdeck.sdPlugin/actions/toggle-layer/plugin.js
+++ b/co.meldstudio.streamdeck.sdPlugin/actions/toggle-layer/plugin.js
@@ -13,7 +13,7 @@ class ToggleLayer extends MeldStudioPlugin {
this.forAllContexts((context, settings) => {
const { layer } = settings;
if (!layer) return;
- if (!session.items[layer])
+ if (!session?.items || !session?.items[layer])
return $SD.setState(context, 0);
const state = session.items[layer].visible ? 1 : 0;
diff --git a/co.meldstudio.streamdeck.sdPlugin/actions/volume-stepper/inspector.html b/co.meldstudio.streamdeck.sdPlugin/actions/volume-stepper/inspector.html
new file mode 100644
index 0000000..0e5d504
--- /dev/null
+++ b/co.meldstudio.streamdeck.sdPlugin/actions/volume-stepper/inspector.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+ co.meldstudio.streamdeck.volume-stepper Property Inspector
+
+
+
+
+
+
+
+
+
+
Connecting to Meld Studio...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/co.meldstudio.streamdeck.sdPlugin/actions/volume-stepper/plugin.js b/co.meldstudio.streamdeck.sdPlugin/actions/volume-stepper/plugin.js
new file mode 100644
index 0000000..7ddbdaf
--- /dev/null
+++ b/co.meldstudio.streamdeck.sdPlugin/actions/volume-stepper/plugin.js
@@ -0,0 +1,195 @@
+class VolumeStepper extends MeldStudioPlugin {
+ trackInfo = {};
+ unregisterCallbacks = {};
+
+ constructor() {
+ super("co.meldstudio.streamdeck.volume-stepper");
+
+ this.action.onKeyUp(({ action, context, device, event, payload }) => {
+ const { track } = this.getSettings(context);
+ if (!track) return;
+
+ if ($MS.meld?.toggleMute) $MS.meld.toggleMute(track);
+ });
+
+ this.action.onDialRotate(({ context, payload }) => {
+ const { track, stepsize: stepString } = this.getSettings(context);
+ const stepsize = 0.01 * parseFloat(stepString);
+ const info = this.trackInfo[track];
+
+ if (!track) return;
+
+ let gain = +stepsize * payload.ticks + (info?.gain ?? 0.0);
+ gain = gain < 0 ? 0 : gain;
+ gain = gain > 1 ? 1 : gain;
+
+ // Store the new gain until the callback fires.
+ this.trackInfo[track] = { ...info, gain };
+
+ if ($MS.meld?.setGain) $MS.meld?.setGain(track, gain);
+ });
+
+ this.action.onDialPress(({ action, context, device, event, payload }) => {
+ if (!payload.pressed) return;
+
+ const { track } = this.getSettings(context);
+ if (!track) return;
+
+ if ($MS.meld?.toggleMute) $MS.meld.toggleMute(track);
+ });
+
+ this.action.onTouchTap(({ action, context, device, event, payload }) => {
+ if (payload.hold) return;
+
+ const { tapPos } = payload;
+ const { track } = this.getSettings(context);
+ if (!track) return;
+
+ // Indicator starts at x=76 and is 108 pixels wide.
+ if (tapPos[0] > 80) {
+ if (tapPos[0] > 180) return;
+
+ const info = this.trackInfo[track];
+ const gain = (tapPos[0] - 80) / 100;
+
+ // Store the new gain until the callback fires.
+ this.trackInfo[track] = { ...info, gain };
+
+ if ($MS.meld?.setGain) $MS.meld?.setGain(track, gain);
+ } else {
+ if ($MS.meld?.toggleMute) $MS.meld.toggleMute(track);
+ }
+ });
+
+ this.action.onDidReceiveSettings(({ context, payload }) => {
+ this.setSettings(context, payload?.settings ?? {});
+
+ if ($MS.ready) {
+ this.onReady(context, payload?.settings);
+ } else {
+ // If we lose connection, we may need to reinitialize.
+ $MS.on("ready", () => {
+ this.onReady(context, payload?.settings);
+ });
+
+ this.setGainAndMute(context, {
+ gain: 0.0,
+ muted: true,
+ });
+ }
+ });
+
+ $MS.on("sessionChanged", (session) => {
+ this.forAllContexts((context, { track }) => {
+ if (!track) return;
+ if (!session?.items) return;
+ if (!session?.items[track]) return;
+
+ const { name, muted } = session.items[track];
+ const state = muted ? 0 : 1;
+
+ this.trackInfo[track] = { ...this.trackInfo[track], name, muted };
+
+ this.setGainAndMute(context, this.trackInfo[track]);
+ });
+ });
+
+ $MS.on("gainChanged", ({ trackId, gain, muted }) => {
+ let info = this.trackInfo[trackId];
+ info = { ...info, gain, muted };
+ this.trackInfo[trackId] = info;
+
+ this.forAllContexts((context, { track }) => {
+ if (!track || trackId != track) return;
+ this.setGainAndMute(context, info);
+ });
+ });
+
+ $MS.on("closed", () => {
+ // reset all registrations.
+ this.unregisterCallbacks = {};
+ });
+ }
+
+ setGainAndMute(context, { gain, muted, name }) {
+ // meter colors -
+ // green: #6DDE92
+ // orange: #FB923C
+ // red: #F04A4A
+
+ const info = (() => {
+ if (!muted) {
+ if (gain > 0.4) return { icon: "assets/iconAudioTrack" };
+ if (gain > 0.0) return { icon: "assets/audioUnmuted40" };
+ }
+ return { icon: "assets/audioMute" };
+ })();
+
+ $SD.setFeedback(context, {
+ ...info,
+ title: name ?? "Adjust Volume",
+ value: `${parseInt(gain * 100)}%`,
+ indicator: {
+ value: gain * 100,
+ enabled: true,
+ bar_bg_c: muted ? "0:#666666,1:#666666" : "0:#6DDE92,1:#6DDE92",
+ },
+ });
+ }
+
+ register(context, track) {
+ console.assert($MS.meld);
+
+ const callbackInfo = this.unregisterCallbacks[context];
+ // Only register once.
+ if (callbackInfo?.track === track) return;
+
+ this.maybeUnregister(context, track);
+ $MS.meld.registerTrackObserver(context, track);
+
+ this.unregisterCallbacks[context] = {
+ callback: () => {
+ if ($MS.meld) $MS.meld.unregisterTrackObserver(context, track);
+ },
+ track,
+ };
+ }
+
+ maybeUnregister(context) {
+ const callbackInfo = this.unregisterCallbacks[context];
+ if (!callbackInfo) return;
+
+ callbackInfo.callback();
+ this.unregisterCallbacks[context] = undefined;
+ }
+
+ getNameForTrack(track) {
+ const defaultName = "Adjust Volume";
+ if (!$MS?.meld?.session?.items) return defaultName;
+
+ const name = $MS.meld.session.items[track]?.name;
+ return name ? name : defaultName;
+ }
+
+ connectGain(context, track) {
+ this.trackInfo[track] = {
+ gain: 0.0,
+ muted: false,
+ name: this.getNameForTrack(track),
+ };
+
+ this.setGainAndMute(context, this.trackInfo[track]);
+ this.register(context, track);
+
+ this.action.onWillDisappear(({ context }) => {
+ this.maybeUnregister(context);
+ });
+ }
+
+ onReady(context, { track }) {
+ console.assert($MS.ready);
+ if (this.isEncoder(context)) this.connectGain(context, track);
+ }
+}
+
+const volumeStepper = new VolumeStepper();
diff --git a/co.meldstudio.streamdeck.sdPlugin/app.html b/co.meldstudio.streamdeck.sdPlugin/app.html
index 308114b..7acc506 100644
--- a/co.meldstudio.streamdeck.sdPlugin/app.html
+++ b/co.meldstudio.streamdeck.sdPlugin/app.html
@@ -33,5 +33,6 @@
+