+
{
:position="Position.Left"
class=""
/>
+
+
+
diff --git a/src/renderer/locales/en-US.json b/src/renderer/locales/en-US.json
index b9690c537..5c4f26d3c 100644
--- a/src/renderer/locales/en-US.json
+++ b/src/renderer/locales/en-US.json
@@ -43,8 +43,9 @@
"favorites.no_favorites": "You don't have any favorite tracks ( ´•︵•` )",
"output_diagram.title": "Output diagram",
"output_diagram.source.title": "Source",
- "output_diagram.decoder.title": "Decoder",
+ "output_diagram.decoder.title": "Decoder",
"output_diagram.dsp.title": "DSP",
+ "output_diagram.dsp.node_count": "processor nodes",
"output_diagram.audio_device.title": "Audio device",
"output_diagram.output.title": "Output",
"output_diagram.resampler.title": "Resampler",
@@ -245,8 +246,8 @@
"node.output.description": "To audio device",
"node.input.title": "Input",
"node.input.description": "From source",
- "node.filter.title": "12 dB/oct Filter",
- "node.panner.title": "Stereo Panner",
+ "node.filter.title": "Biquad filter",
+ "node.panner.title": "Stereo panner",
"node.gain.title": "Gain",
"queue.column.cover": "Cover",
"queue.column.favorite": "Favorite",
diff --git a/src/renderer/locales/fr-FR.json b/src/renderer/locales/fr-FR.json
index 0db1469ad..5a5aeed50 100644
--- a/src/renderer/locales/fr-FR.json
+++ b/src/renderer/locales/fr-FR.json
@@ -72,7 +72,7 @@
"settings.audio.title": "Audio",
"settings.audio.description": "Gérer les périphériques de sortie et les préréglages",
"settings.media_sources.title": "Sources média",
- "settings.media_sources.description": "Organiser les dossiers, se connecter aux serveurs, et plus encore",
+ "settings.media_sources.description": "organizer les dossiers, se connecter aux serveurs, et plus encore",
"settings.keybinds.title": "Raccourcis clavier",
"settings.keybinds.description": "Personnaliser les mappings clavier pour votre expérience",
"settings.integrations.title": "Intégrations",
diff --git a/src/renderer/logic/audio.ts b/src/renderer/logic/audio.ts
index 10590db93..c657ec6c4 100644
--- a/src/renderer/logic/audio.ts
+++ b/src/renderer/logic/audio.ts
@@ -5,6 +5,7 @@ import { markRaw } from "vue";
import { amethyst } from "@/amethyst.js";
import type { Connection, NodeProperties } from "@/logic/audioManager.js";
+import { C } from "vitest/dist/chunks/reporters.d.DG9VKi4m.js";
export interface NodeParameter
{
type: "number" | "string";
@@ -52,6 +53,15 @@ export class AmethystAudioNode {
return `node-${this.properties.type}`;
}
+ public getNodeDimensions() {
+ const element = document.getElementById(`node-${this.properties.id}`) as HTMLDivElement
+ if (!element ) throw new Error("Failed to get width, node somehow does not have a UI element");
+
+ const {width, height} = element.getBoundingClientRect()
+
+ return {width, height}
+ }
+
public autoConnectFromEdges() {
this.connections.forEach((edge) => {
const target = amethyst.player.nodeManager.nodes.value.find((node) => node.properties.id === edge.target);
diff --git a/src/renderer/logic/settings.ts b/src/renderer/logic/settings.ts
index 1c4122727..ccd56806e 100644
--- a/src/renderer/logic/settings.ts
+++ b/src/renderer/logic/settings.ts
@@ -36,6 +36,37 @@ export const SPECTRUM_TYPES = [
export type SPECTRUM_TYPES_STRING = "line" | "bars" | "spectrogram";
+export function deepMerge(target: T, source: Partial): T {
+ if (typeof target !== "object" || typeof source !== "object" || !target || !source) {
+ return source as T;
+ }
+
+ const result = Array.isArray(target) ? [...target] : { ...target };
+
+ for (const key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ const sourceVal = source[key];
+ const targetVal = (target as any)[key];
+
+ if (
+ sourceVal
+ && typeof sourceVal === "object"
+ && !Array.isArray(sourceVal)
+ && targetVal
+ && typeof targetVal === "object"
+ && !Array.isArray(targetVal)
+ ) {
+ (result as any)[key] = deepMerge(targetVal, sourceVal);
+ }
+ else {
+ (result as any)[key] = sourceVal;
+ }
+ }
+ }
+
+ return result as any;
+}
+
const DEFAULT_APPEARANCE_SETTINGS = {
appearance: {
animationDuration: 100,
diff --git a/src/renderer/modules/rewParser.test.ts b/src/renderer/modules/rewParser.test.ts
new file mode 100644
index 000000000..2555d1f03
--- /dev/null
+++ b/src/renderer/modules/rewParser.test.ts
@@ -0,0 +1,199 @@
+import { describe, expect, it } from "vitest";
+
+import {parseString, RewFilterType} from "./rewParser.js";
+
+const mockRewFilter = `
+Filter Settings file
+
+Room EQ V5.31.3
+Dated: May 18, 2025 4:13:30 AM
+
+Notes:
+
+Equaliser: Generic
+FL Mar 10
+Preamp: -10.30 dB
+Filter 1: ON PK Fc 53.90 Hz Gain 0.00 dB Q 10.000
+Filter 2: ON Modal Fc 89.40 Hz Gain 0.00 dB Q 12.197 T60 target 300 ms
+Filter 3: ON LP Fc 145.5 Hz
+Filter 4: ON HP Fc 166.0 Hz
+Filter 5: ON LP1 Fc 300.0 Hz
+Filter 6: ON HP1 Fc 370.0 Hz
+Filter 7: ON LP Q Fc 100.0 Hz Q 0.707
+Filter 8: ON HP Q Fc 100.0 Hz Q 0.707
+Filter 9: ON LS Fc 1325 Hz Gain 0.00 dB
+Filter 10: ON HS Fc 160.0 Hz Gain 0.00 dB
+Filter 11: ON LS 6dB Fc 200.0 Hz Gain 0.00 dB
+Filter 12: ON HS 6dB Fc 250.0 Hz Gain 0.00 dB
+Filter 13: ON LS 12dB Fc 315.0 Hz Gain 0.00 dB
+Filter 14: ON HS 12dB Fc 400.0 Hz Gain 0.00 dB
+Filter 15: ON LS Q Fc 500.0 Hz Gain 0.00 dB Q 0.707
+Filter 16: ON HS Q Fc 630.0 Hz Gain 0.00 dB Q 0.707
+Filter 17: ON Notch Fc 800.0 Hz
+Filter 18: ON Notch Q Fc 100.0 Hz Q 0.707
+Filter 19: ON All pass Fc 100.0 Hz Q 0.707
+Filter 20: ON L-T Fc 50.00 Hz Q 1.20 Fp 30 Qp 0.50
+`;
+
+describe("rewParser", () => {
+ it("should parse rew files correctly", () => {
+ const rewFilters = parseString(mockRewFilter);
+ expect(rewFilters.filters).toHaveLength(20);
+ expect(rewFilters.preamp).toBe(-10.3);
+ expect(rewFilters.filters[0]).toEqual({
+ type: RewFilterType.Peaking,
+ frequency: 53.9,
+ gain: 0,
+ q: 10,
+ });
+ expect(rewFilters.filters[1]).toEqual({
+ type: RewFilterType.Modal,
+ frequency: 89.4,
+ gain: 0,
+ q: 12.197,
+ });
+ expect(rewFilters.filters[2]).toEqual({
+ type: RewFilterType.LowPass,
+ frequency: 145.5,
+ });
+ expect(rewFilters.filters[3]).toEqual({
+ type: RewFilterType.HighPass,
+ frequency: 166,
+ });
+ expect(rewFilters.filters[4]).toEqual({
+ type: RewFilterType.LowPass1,
+ frequency: 300,
+ });
+ expect(rewFilters.filters[5]).toEqual({
+ type: RewFilterType.HighPass1,
+ frequency: 370,
+ });
+
+ expect(rewFilters.filters[6]).toEqual({
+ type: RewFilterType.LowPassQ,
+ frequency: 100,
+ q: 0.707,
+ });
+ expect(rewFilters.filters[7]).toEqual({
+ type: RewFilterType.HighPassQ,
+ frequency: 100,
+ q: 0.707,
+ });
+ expect(rewFilters.filters[8]).toEqual({
+ type: RewFilterType.LowShelf,
+ frequency: 1325,
+ gain: 0,
+ });
+ expect(rewFilters.filters[9]).toEqual({
+ type: RewFilterType.HighShelf,
+ frequency: 160,
+ gain: 0,
+ });
+ expect(rewFilters.filters[10]).toEqual({
+ type: RewFilterType.LowShelf6,
+ frequency: 200,
+ gain: 0,
+ });
+ expect(rewFilters.filters[11]).toEqual({
+ type: RewFilterType.HighShelf6,
+ frequency: 250,
+ gain: 0,
+ });
+ expect(rewFilters.filters[12]).toEqual({
+ type: RewFilterType.LowShelf12,
+ frequency: 315,
+ gain: 0,
+ });
+ expect(rewFilters.filters[13]).toEqual({
+ type: RewFilterType.HighShelf12,
+ frequency: 400,
+ gain: 0,
+ });
+ expect(rewFilters.filters[14]).toEqual({
+ type: RewFilterType.LowShelfQ,
+ frequency: 500,
+ gain: 0,
+ q: 0.707,
+ });
+ expect(rewFilters.filters[15]).toEqual({
+ type: RewFilterType.HighShelfQ,
+ frequency: 630,
+ gain: 0,
+ q: 0.707,
+ });
+ expect(rewFilters.filters[16]).toEqual({
+ type: RewFilterType.Notch,
+ frequency: 800,
+ });
+ expect(rewFilters.filters[17]).toEqual({
+ type: RewFilterType.NotchQ,
+ frequency: 100,
+ q: 0.707,
+ });
+ expect(rewFilters.filters[18]).toEqual({
+ type: RewFilterType.Allpass,
+ frequency: 100,
+ q: 0.707,
+ });
+ expect(rewFilters.filters[19]).toEqual({
+ type: RewFilterType.LinkwitzTransform,
+ frequency: 50,
+ q: 1.2,
+ fp: 30,
+ qp: 0.5,
+ });
+ });
+
+ it("should parse valid filters when possible and ignore invalid", () => {
+ const badData = `
+ Filter Settings file
+
+ Room EQ V5.31.3
+ Dated: May 18, 2025 4:13:30 AM
+
+ Notes:
+
+ Equaliser: Generic
+ FL Mar 10
+ Filter 1: ON aPK Fc 53.90 Hz Gain 0.f00 dB Q 10.000
+ Filter 2: ON Moxal Fc 85d9.40 Hz Ga.. ain 0.00 dB Q 1
+ Filter 3: ON LP Fc 145.5 Hz
+ `;
+ const rewFilters = parseString(badData);
+ expect(rewFilters.filters).toHaveLength(1);
+ expect(rewFilters.filters[0]).toEqual({
+ type: RewFilterType.LowPass,
+ frequency: 145.5,
+ });
+ });
+
+ it("should handle empty strings", () => {
+ const rewFilters = parseString("");
+ expect(rewFilters.filters).toHaveLength(0);
+ });
+
+ it("should ignore filters that are off", () => {
+ const data = `
+ Filter 1: OFF PK Fc 53.90 Hz Gain 0.00 dB Q 10.000
+ Filter 2: ON LP Fc 145.5 Hz
+ `;
+ const rewFilters = parseString(data);
+ expect(rewFilters.filters).toHaveLength(1);
+ expect(rewFilters.filters[0]).toEqual({
+ type: RewFilterType.LowPass,
+ frequency: 145.5,
+ });
+ });
+
+
+ it("should handle extra or irregular whitespace", () => {
+ const data = `Filter 1: ON PK Fc 53.90 Hz Gain 0.00 dB Q 10.000`;
+ const rewFilters = parseString(data);
+ expect(rewFilters.filters[0]).toEqual({
+ type: RewFilterType.Peaking,
+ frequency: 53.9,
+ gain: 0,
+ q: 10,
+ });
+ });
+});
diff --git a/src/renderer/modules/rewParser.ts b/src/renderer/modules/rewParser.ts
new file mode 100644
index 000000000..7a21a2c74
--- /dev/null
+++ b/src/renderer/modules/rewParser.ts
@@ -0,0 +1,222 @@
+export enum RewFilterType {
+ Peaking = "PK",
+ Modal = "Modal",
+ HighShelf = "HS",
+ HighShelf6 = "HS 6dB",
+ HighShelf12 = "HS 12dB",
+ HighShelfQ = "HS Q",
+ HighPassQ = "HP Q",
+ HighPass = "HP",
+ HighPass1 = "HP1",
+ LowPassQ = "LP Q",
+ LowPass1 = "LP1",
+ LowPass = "LP",
+ LowShelf = "LS",
+ LowShelf6 = "LS 6dB",
+ LowShelf12 = "LS 12dB",
+ LowShelfQ = "LS Q",
+ Notch = "Notch",
+ NotchQ = "Notch Q",
+ Allpass = "All pass",
+ LinkwitzTransform = "L-T",
+}
+
+export type PeakingFilter = {
+ type: RewFilterType.Peaking;
+ frequency: number;
+ gain: number;
+ q: number;
+};
+
+export type ModalFilter = {
+ type: RewFilterType.Modal;
+ frequency: number;
+ gain: number;
+ q: number;
+ // t60 ignored
+};
+
+export type LowPassFilter = {
+ type: RewFilterType.LowPass | RewFilterType.LowPass1;
+ frequency: number;
+};
+
+export type HighPassFilter = {
+ type: RewFilterType.HighPass | RewFilterType.HighPass1;
+ frequency: number;
+};
+
+export type LowPassQFilter = {
+ type: RewFilterType.LowPassQ;
+ frequency: number;
+ q: number;
+};
+
+export type HighPassQFilter = {
+ type: RewFilterType.HighPassQ;
+ frequency: number;
+ q: number;
+};
+
+export type HighShelfFilter = {
+ type: RewFilterType.HighShelf | RewFilterType.HighShelf6 | RewFilterType.HighShelf12;
+ frequency: number;
+ gain: number;
+};
+
+export type LowShelfFilter = {
+ type: RewFilterType.LowShelf | RewFilterType.LowShelf6 | RewFilterType.LowShelf12;
+ frequency: number;
+ gain: number;
+};
+
+export type HighShelfQFilter = {
+ type: RewFilterType.HighShelfQ;
+ frequency: number;
+ gain: number;
+ q: number;
+};
+
+export type LowShelfQFilter = {
+ type: RewFilterType.LowShelfQ;
+ frequency: number;
+ gain: number;
+ q: number;
+};
+
+export type NotchFilter = {
+ type: RewFilterType.Notch;
+ frequency: number;
+};
+
+export type NotchQFilter = {
+ type: RewFilterType.NotchQ;
+ frequency: number;
+ q: number;
+};
+
+export type AllpassFilter = {
+ type: RewFilterType.Allpass;
+ frequency: number;
+ q: number;
+};
+
+export type LinkwitzTransformFilter = {
+ type: RewFilterType.LinkwitzTransform;
+ frequency: number;
+ q: number;
+ fp: number;
+ qp: number;
+};
+
+export type RewFilter =
+ | PeakingFilter
+ | ModalFilter
+ | LowPassFilter
+ | HighPassFilter
+ | LowPassQFilter
+ | HighPassQFilter
+ | HighShelfFilter
+ | LowShelfFilter
+ | HighShelfQFilter
+ | LowShelfQFilter
+ | NotchFilter
+ | NotchQFilter
+ | AllpassFilter
+ | LinkwitzTransformFilter;
+
+export type RewFilters = RewFilter[];
+
+export interface EQCalibration {
+ preamp?: number;
+ filters: RewFilters;
+}
+
+const filterRegex = /Filter\s+(?\d+):\s+ON\s+(?[\w\s-]+?)\s+Fc\s+(?[\d.]+)\s+Hz(?:\s+Gain\s+(?[-\d.]+)\s+dB)?(?:\s+Q\s+(?[\d.]+))?(?:\s+T60 target\s+(?\d+)\s+ms)?(?:\s+Fp\s+(?[\d.]+))?(?:\s+Qp\s+(?[\d.]+))?/;
+
+/**
+ * Should be used to parse a REW 'as text' txt file.
+ */
+export const parseString = (data: string): EQCalibration => {
+ const lines = data.split("\n");
+ const filters: RewFilters = [];
+
+ // parse for filters
+ for (const line of lines) {
+ const match = line.match(filterRegex);
+ if (match) {
+ const type = match[2] as RewFilterType;
+ const frequency = parseFloat(match[3]);
+ const gain = match[4] ? parseFloat(match[4]) : undefined;
+ const q = match[5] ? parseFloat(match[5]) : undefined;
+ // const t60 = match[6] ? parseFloat(match[6]) : undefined;
+ const fp = match[7] ? parseFloat(match[7]) : undefined;
+ const qp = match[8] ? parseFloat(match[8]) : undefined;
+
+ let filter: RewFilter;
+
+ // Check if filter type is invalid
+ if (!Object.values(RewFilterType).includes(type)) continue;
+
+ switch (type) {
+ case RewFilterType.Peaking:
+ filter = { type, frequency, gain: gain!, q: q! };
+ break;
+ case RewFilterType.Modal:
+ filter = { type, frequency, gain: gain!, q: q! };
+ break;
+ case RewFilterType.LowPass:
+ case RewFilterType.LowPass1:
+ filter = { type, frequency };
+ break;
+ case RewFilterType.HighPass:
+ case RewFilterType.HighPass1:
+ filter = { type, frequency };
+ break;
+ case RewFilterType.LowPassQ:
+ filter = { type, frequency, q: q! };
+ break;
+ case RewFilterType.HighPassQ:
+ filter = { type, frequency, q: q! };
+ break;
+ case RewFilterType.HighShelf:
+ case RewFilterType.HighShelf6:
+ case RewFilterType.HighShelf12:
+ filter = { type, frequency, gain: gain! };
+ break;
+ case RewFilterType.LowShelf:
+ case RewFilterType.LowShelf6:
+ case RewFilterType.LowShelf12:
+ filter = { type, frequency, gain: gain! };
+ break;
+ case RewFilterType.HighShelfQ:
+ case RewFilterType.LowShelfQ:
+ filter = { type, frequency, gain: gain!, q: q! };
+ break;
+ case RewFilterType.Notch:
+ filter = { type, frequency };
+ break;
+ case RewFilterType.NotchQ:
+ filter = { type, frequency, q: q! };
+ break;
+ case RewFilterType.Allpass:
+ filter = { type, frequency, q: q! };
+ break;
+ case RewFilterType.LinkwitzTransform:
+ filter = { type, frequency, q: q!, fp: fp!, qp: qp! };
+ break;
+ default:
+ throw new Error(`Unknown filter type: ${type}`);
+ }
+
+ filters.push(filter);
+
+ }
+ }
+
+ // parse for preamp
+ const preampLine = lines.find(line => line.startsWith("Preamp:"));
+ const preampMatch = preampLine?.match(/Preamp:\s+(-?\d+(\.\d+)?)\s+dB/);
+
+ return { filters , preamp: preampMatch ? parseFloat(preampMatch[1]) : undefined };
+}
diff --git a/src/renderer/state.ts b/src/renderer/state.ts
index d4fc4510d..953277a66 100644
--- a/src/renderer/state.ts
+++ b/src/renderer/state.ts
@@ -6,40 +6,9 @@ import { reactive, ref, watch } from "vue";
import { EventEmitter } from "@/logic/eventEmitter.js";
import { createDefaultSettings } from "@/logic/settings.js";
import { ShaderManager } from "@/shaders/ShaderManager.js";
-
+import {deepMerge} from "@/logic/settings.js"
import { Amethyst } from "./amethyst.js";
-function deepMerge(target: T, source: Partial): T {
- if (typeof target !== "object" || typeof source !== "object" || !target || !source) {
- return source as T;
- }
-
- const result = Array.isArray(target) ? [...target] : { ...target };
-
- for (const key in source) {
- if (Object.prototype.hasOwnProperty.call(source, key)) {
- const sourceVal = source[key];
- const targetVal = (target as any)[key];
-
- if (
- sourceVal
- && typeof sourceVal === "object"
- && !Array.isArray(sourceVal)
- && targetVal
- && typeof targetVal === "object"
- && !Array.isArray(targetVal)
- ) {
- (result as any)[key] = deepMerge(targetVal, sourceVal);
- }
- else {
- (result as any)[key] = sourceVal;
- }
- }
- }
-
- return result as any;
-}
-
export interface IContextMenuOption {
title: string;
icon?: any;
@@ -81,6 +50,11 @@ export class State extends EventEmitter {
}).value,
});
+ public replaceSettings = (newSettings: ReturnType) => {
+ this.settings = reactive(deepMerge(this.settings, newSettings));
+ console.log("Replaced settings with imported settings", newSettings);
+ }
+
public shaders = ref(new ShaderManager());
public realtimeDevices = ref([]);
diff --git a/src/renderer/views/NodeEditorView.vue b/src/renderer/views/NodeEditorView.vue
index 6db4b716e..84f16bf05 100644
--- a/src/renderer/views/NodeEditorView.vue
+++ b/src/renderer/views/NodeEditorView.vue
@@ -4,7 +4,7 @@ import { Background, BackgroundVariant } from "@vue-flow/additional-components";
import type { Connection, EdgeMouseEvent, NodeDragEvent } from "@vue-flow/core";
import { VueFlow } from "@vue-flow/core";
import { onKeyStroke } from "@vueuse/core";
-import { computed, onMounted, onUnmounted, ref } from "vue";
+import { computed, nextTick, onMounted, onUnmounted, ref } from "vue";
import { amethyst } from "@/amethyst.js";
import BaseToolbar from "@/components/BaseToolbar.vue";
@@ -14,6 +14,7 @@ import { useContextMenu } from "@/components/ContextMenu";
import { useInspector } from "@/components/Inspector";
import type { AmethystAudioNode } from "@/logic/audio";
import { getThemeColorHex } from "@/logic/color";
+import { parseString, RewFilterType } from "@/modules/rewParser";
import { AmethystFilterNode, AmethystGainNode, AmethystPannerNode, AmethystSpectrumNode } from "@/nodes";
const dash = ref();
const nodeEditor = ref();
@@ -193,6 +194,101 @@ const handleOpenFile = async () => {
});
};
+const handleOpenRewFile = async () => {
+ const result = await amethyst.showOpenFileDialog({filters: [{name: "Text file", extensions: ["txt"]}]});
+ if (result.canceled) return;
+
+ fetch("file://" + result.filePaths[0])
+ .then(response => response.blob())
+ .then(blob => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsArrayBuffer(blob);
+ reader.onloadend = () => reader.result ? resolve(reader.result as ArrayBuffer) : reject("reader null");
+ });
+ })
+ .then(buffer => {
+ const decoder = new TextDecoder("utf-8");
+ const rewFilters = parseString( decoder.decode(buffer))
+
+ if (rewFilters.preamp) {
+ const gainNode = new AmethystGainNode(amethyst.player.nodeManager.context, computeNodePosition({ x: 0, y: 0 }));
+ gainNode.gain = rewFilters.preamp;
+ amethyst.player.nodeManager.addNode(gainNode);
+ }
+
+ const filterNodes = rewFilters.filters.map((filter, i) => {
+ const node = new AmethystFilterNode(amethyst.player.nodeManager.context, computeNodePosition({ x: 32 + i * 32, y: i * 0 }));
+
+ switch (filter.type) {
+ case RewFilterType.LowPass:
+ node.type = "lowpass";
+ node.frequency = filter.frequency;
+ break;
+ case RewFilterType.HighPass:
+ node.type = "lowpass";
+ break;
+ case RewFilterType.LowShelf:
+ node.type = "lowshelf";
+ node.frequency = filter.frequency;
+ node.gain = filter.gain;
+ break;
+ case RewFilterType.HighShelf:
+ node.type = "highshelf";
+ node.frequency = filter.frequency;
+ node.gain = filter.gain;
+ break;
+ case RewFilterType.Allpass:
+ node.type = "bandpass";
+ node.frequency = filter.frequency;
+ node.q = filter.q;
+ break;
+ case RewFilterType.Peaking:
+ node.type = "peaking";
+ node.frequency = filter.frequency;
+ node.gain = filter.gain;
+ node.q = filter.q;
+ break;
+ case RewFilterType.Notch:
+ node.type = "notch";
+ node.frequency = filter.frequency;
+ break;
+ default:
+ break;
+ }
+ return node;
+ });
+
+ // add and connect nodes in series
+
+ filterNodes.forEach((node, i) => {
+ amethyst.player.nodeManager.addNode(node);
+ if (i > 0) {
+ const prevNode = filterNodes[i - 1];
+ if(prevNode) {
+ prevNode.connectTo(node);
+ }
+ }
+ });
+
+
+ nextTick(() => {
+ organizeNodes()
+ })
+
+ });
+};
+
+const organizeNodes = () => {
+ const GAP = 8;
+ let offsetX = 0;
+
+ amethyst.player.nodeManager.nodes.value.forEach(node => {
+ const {width} = node.getNodeDimensions();
+ node.updatePosition({x: offsetX += width + GAP, y: 0})
+ })
+}
+
const handleSaveFile = async () => {
const serializedGraph = amethyst.player.nodeManager.serialize();
const dialog = await amethyst.showSaveFileDialog({
@@ -209,12 +305,14 @@ const handleReset = () => {
amethyst.player.setVolume(amethyst.player.volume);
};
-const removeSelectedNodes = dash.value?.getSelectedNodes.forEach((nodeElement: any) => {
- const node = amethyst.player.nodeManager.nodes.value
- .find((node) => node.properties.id === nodeElement.id);
+const removeSelectedNodes = () => {
+ dash.value?.getSelectedNodes.forEach((nodeElement: any) => {
+ const node = amethyst.player.nodeManager.nodes.value
+ .find((node) => node.properties.id === nodeElement.id);
- node && amethyst.player.nodeManager.removeNode(node);
-});
+ node && amethyst.player.nodeManager.removeNode(node);
+ });
+}
onKeyStroke("Delete", () => {
removeSelectedNodes();
@@ -244,6 +342,11 @@ onKeyStroke("Delete", () => {
@keydown.stop
>
+
{
+
+
+
+