« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/patches.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/patches.js')
-rw-r--r--src/data/patches.js616
1 files changed, 317 insertions, 299 deletions
diff --git a/src/data/patches.js b/src/data/patches.js
index 3ed4fad0..0ff56ad0 100644
--- a/src/data/patches.js
+++ b/src/data/patches.js
@@ -1,291 +1,309 @@
 // --> Patch
 
 export class Patch {
-    static INPUT_NONE = 0;
-    static INPUT_CONSTANT = 1;
-    static INPUT_DIRECT_CONNECTION = 2;
-    static INPUT_MANAGED_CONNECTION = 3;
+  static INPUT_NONE = 0;
+  static INPUT_CONSTANT = 1;
+  static INPUT_DIRECT_CONNECTION = 2;
+  static INPUT_MANAGED_CONNECTION = 3;
 
-    static INPUT_UNAVAILABLE = 0;
-    static INPUT_AVAILABLE = 1;
+  static INPUT_UNAVAILABLE = 0;
+  static INPUT_AVAILABLE = 1;
 
-    static OUTPUT_UNAVAILABLE = 0;
-    static OUTPUT_AVAILABLE = 1;
+  static OUTPUT_UNAVAILABLE = 0;
+  static OUTPUT_AVAILABLE = 1;
 
-    static inputNames = []; inputNames = null;
-    static outputNames = []; outputNames = null;
+  static inputNames = [];
+  inputNames = null;
+  static outputNames = [];
+  outputNames = null;
 
-    manager = null;
-    inputs = Object.create(null);
+  manager = null;
+  inputs = Object.create(null);
 
-    constructor({
-        manager,
+  constructor({
+    manager,
 
-        inputNames,
-        outputNames,
+    inputNames,
+    outputNames,
 
-        inputs,
-    } = {}) {
-        this.inputNames = inputNames ?? this.constructor.inputNames;
-        this.outputNames = outputNames ?? this.constructor.outputNames;
+    inputs,
+  } = {}) {
+    this.inputNames = inputNames ?? this.constructor.inputNames;
+    this.outputNames = outputNames ?? this.constructor.outputNames;
 
-        manager?.addManagedPatch(this);
+    manager?.addManagedPatch(this);
 
-        if (inputs) {
-            Object.assign(this.inputs, inputs);
-        }
-
-        this.initializeInputs();
+    if (inputs) {
+      Object.assign(this.inputs, inputs);
     }
 
-    initializeInputs() {
-        for (const inputName of this.inputNames) {
-            if (!this.inputs[inputName]) {
-                this.inputs[inputName] = [Patch.INPUT_NONE];
-            }
-        }
-    }
+    this.initializeInputs();
+  }
 
-    computeInputs() {
-        const inputs = Object.create(null);
-
-        for (const inputName of this.inputNames) {
-            const input = this.inputs[inputName];
-            switch (input[0]) {
-                case Patch.INPUT_NONE:
-                    inputs[inputName] = [Patch.INPUT_UNAVAILABLE];
-                    break;
-
-                case Patch.INPUT_CONSTANT:
-                    inputs[inputName] = [Patch.INPUT_AVAILABLE, input[1]];
-                    break;
-
-                case Patch.INPUT_DIRECT_CONNECTION: {
-                    const patch = input[1];
-                    const outputName = input[2];
-                    const output = patch.computeOutputs()[outputName];
-                    switch (output[0]) {
-                        case Patch.OUTPUT_UNAVAILABLE:
-                            inputs[inputName] = [Patch.INPUT_UNAVAILABLE];
-                            break;
-                        case Patch.OUTPUT_AVAILABLE:
-                            inputs[inputName] = [Patch.INPUT_AVAILABLE, output[1]];
-                            break;
-                    }
-                    throw new Error('Unreachable');
-                }
-
-                case Patch.INPUT_MANAGED_CONNECTION: {
-                    if (!this.manager) {
-                        inputs[inputName] = [Patch.INPUT_UNAVAILABLE];
-                        break;
-                    }
-
-                    inputs[inputName] = this.manager.getManagedInput(input[1]);
-                    break;
-                }
-            }
+  initializeInputs() {
+    for (const inputName of this.inputNames) {
+      if (!this.inputs[inputName]) {
+        this.inputs[inputName] = [Patch.INPUT_NONE];
+      }
+    }
+  }
+
+  computeInputs() {
+    const inputs = Object.create(null);
+
+    for (const inputName of this.inputNames) {
+      const input = this.inputs[inputName];
+      switch (input[0]) {
+        case Patch.INPUT_NONE:
+          inputs[inputName] = [Patch.INPUT_UNAVAILABLE];
+          break;
+
+        case Patch.INPUT_CONSTANT:
+          inputs[inputName] = [Patch.INPUT_AVAILABLE, input[1]];
+          break;
+
+        case Patch.INPUT_DIRECT_CONNECTION: {
+          const patch = input[1];
+          const outputName = input[2];
+          const output = patch.computeOutputs()[outputName];
+          switch (output[0]) {
+            case Patch.OUTPUT_UNAVAILABLE:
+              inputs[inputName] = [Patch.INPUT_UNAVAILABLE];
+              break;
+            case Patch.OUTPUT_AVAILABLE:
+              inputs[inputName] = [Patch.INPUT_AVAILABLE, output[1]];
+              break;
+          }
+          throw new Error("Unreachable");
         }
 
-        return inputs;
-    }
+        case Patch.INPUT_MANAGED_CONNECTION: {
+          if (!this.manager) {
+            inputs[inputName] = [Patch.INPUT_UNAVAILABLE];
+            break;
+          }
 
-    computeOutputs() {
-        const inputs = this.computeInputs();
-        const outputs = Object.create(null);
-        console.log(`Compute: ${this.constructor.name}`);
-        this.compute(inputs, outputs);
-        return outputs;
+          inputs[inputName] = this.manager.getManagedInput(input[1]);
+          break;
+        }
+      }
     }
 
-    compute(inputs, outputs) {
-        // No-op. Return all outputs as unavailable. This should be overridden
-        // in subclasses.
+    return inputs;
+  }
 
-        for (const outputName of this.constructor.outputNames) {
-            outputs[outputName] = [Patch.OUTPUT_UNAVAILABLE];
-        }
-    }
+  computeOutputs() {
+    const inputs = this.computeInputs();
+    const outputs = Object.create(null);
+    console.log(`Compute: ${this.constructor.name}`);
+    this.compute(inputs, outputs);
+    return outputs;
+  }
 
-    attachToManager(manager) {
-        manager.addManagedPatch(this);
+  compute(inputs, outputs) {
+    // No-op. Return all outputs as unavailable. This should be overridden
+    // in subclasses.
+
+    for (const outputName of this.constructor.outputNames) {
+      outputs[outputName] = [Patch.OUTPUT_UNAVAILABLE];
     }
+  }
 
-    detachFromManager() {
-        if (this.manager) {
-            this.manager.removeManagedPatch(this);
-        }
+  attachToManager(manager) {
+    manager.addManagedPatch(this);
+  }
+
+  detachFromManager() {
+    if (this.manager) {
+      this.manager.removeManagedPatch(this);
     }
+  }
 }
 
 // --> PatchManager
 
 export class PatchManager extends Patch {
-    managedPatches = [];
-    managedInputs = {};
-
-    #externalInputPatch = null;
-    #externalOutputPatch = null;
-
-    constructor(...args) {
-        super(...args);
-
-        this.#externalInputPatch = new PatchManagerExternalInputPatch({manager: this});
-        this.#externalOutputPatch = new PatchManagerExternalOutputPatch({manager: this});
+  managedPatches = [];
+  managedInputs = {};
+
+  #externalInputPatch = null;
+  #externalOutputPatch = null;
+
+  constructor(...args) {
+    super(...args);
+
+    this.#externalInputPatch = new PatchManagerExternalInputPatch({
+      manager: this,
+    });
+    this.#externalOutputPatch = new PatchManagerExternalOutputPatch({
+      manager: this,
+    });
+  }
+
+  addManagedPatch(patch) {
+    if (patch.manager === this) {
+      return false;
     }
 
-    addManagedPatch(patch) {
-        if (patch.manager === this) {
-            return false;
-        }
-
-        patch.detachFromManager();
-        patch.manager = this;
+    patch.detachFromManager();
+    patch.manager = this;
 
-        if (patch.manager === this) {
-            this.managedPatches.push(patch);
-            return true;
-        } else {
-            return false;
-        }
+    if (patch.manager === this) {
+      this.managedPatches.push(patch);
+      return true;
+    } else {
+      return false;
     }
+  }
 
-    removeManagedPatch(patch) {
-        if (patch.manager !== this) {
-            return false;
-        }
-
-        patch.manager = null;
-
-        if (patch.manager === this) {
-            return false;
-        }
-
-        for (const inputNames of patch.inputNames) {
-            const input = patch.inputs[inputName];
-            if (input[0] === Patch.INPUT_MANAGED_CONNECTION) {
-                this.dropManagedInput(input[1]);
-                patch.inputs[inputName] = [Patch.INPUT_NONE];
-            }
-        }
-
-        this.managedPatches.splice(this.managedPatches.indexOf(patch), 1);
-
-        return true;
+  removeManagedPatch(patch) {
+    if (patch.manager !== this) {
+      return false;
     }
 
-    addManagedInput(patchWithInput, inputName, patchWithOutput, outputName) {
-        if (patchWithInput.manager !== this || patchWithOutput.manager !== this) {
-            throw new Error(`Input and output patches must belong to same manager (this)`);
-        }
-
-        const input = patchWithInput.inputs[inputName];
-        if (input[0] === Patch.INPUT_MANAGED_CONNECTION) {
-            this.managedInputs[input[1]] = [patchWithOutput, outputName, {}];
-        } else {
-            const key = this.getManagedConnectionIdentifier();
-            this.managedInputs[key] = [patchWithOutput, outputName, {}];
-            patchWithInput.inputs[inputName] = [Patch.INPUT_MANAGED_CONNECTION, key];
-        }
+    patch.manager = null;
 
-        return true;
+    if (patch.manager === this) {
+      return false;
     }
 
-    dropManagedInput(identifier) {
-        return delete this.managedInputs[key];
+    for (const inputNames of patch.inputNames) {
+      const input = patch.inputs[inputName];
+      if (input[0] === Patch.INPUT_MANAGED_CONNECTION) {
+        this.dropManagedInput(input[1]);
+        patch.inputs[inputName] = [Patch.INPUT_NONE];
+      }
     }
 
-    getManagedInput(identifier) {
-        const connection = this.managedInputs[identifier];
-        const patch = connection[0];
-        const outputName = connection[1];
-        const memory = connection[2];
-        return this.computeManagedInput(patch, outputName, memory);
-    }
-
-    computeManagedInput(patch, outputName, memory) {
-        // Override this function in subclasses to alter behavior of the "wire"
-        // used for connecting patches.
-
-        const output = patch.computeOutputs()[outputName];
-        switch (output[0]) {
-            case Patch.OUTPUT_UNAVAILABLE:
-                return [Patch.INPUT_UNAVAILABLE];
-            case Patch.OUTPUT_AVAILABLE:
-                return [Patch.INPUT_AVAILABLE, output[1]];
-        }
-    }
+    this.managedPatches.splice(this.managedPatches.indexOf(patch), 1);
 
-    #managedConnectionIdentifier = 0;
-    getManagedConnectionIdentifier() {
-        return this.#managedConnectionIdentifier++;
-    }
+    return true;
+  }
 
-    addExternalInput(patchWithInput, patchInputName, managerInputName) {
-        return this.addManagedInput(patchWithInput, patchInputName, this.#externalInputPatch, managerInputName);
+  addManagedInput(patchWithInput, inputName, patchWithOutput, outputName) {
+    if (patchWithInput.manager !== this || patchWithOutput.manager !== this) {
+      throw new Error(
+        `Input and output patches must belong to same manager (this)`
+      );
     }
 
-    setExternalOutput(managerOutputName, patchWithOutput, patchOutputName) {
-        return this.addManagedInput(this.#externalOutputPatch, managerOutputName, patchWithOutput, patchOutputName);
+    const input = patchWithInput.inputs[inputName];
+    if (input[0] === Patch.INPUT_MANAGED_CONNECTION) {
+      this.managedInputs[input[1]] = [patchWithOutput, outputName, {}];
+    } else {
+      const key = this.getManagedConnectionIdentifier();
+      this.managedInputs[key] = [patchWithOutput, outputName, {}];
+      patchWithInput.inputs[inputName] = [Patch.INPUT_MANAGED_CONNECTION, key];
     }
 
-    compute(inputs, outputs) {
-        Object.assign(outputs, this.#externalOutputPatch.computeOutputs());
+    return true;
+  }
+
+  dropManagedInput(identifier) {
+    return delete this.managedInputs[key];
+  }
+
+  getManagedInput(identifier) {
+    const connection = this.managedInputs[identifier];
+    const patch = connection[0];
+    const outputName = connection[1];
+    const memory = connection[2];
+    return this.computeManagedInput(patch, outputName, memory);
+  }
+
+  computeManagedInput(patch, outputName, memory) {
+    // Override this function in subclasses to alter behavior of the "wire"
+    // used for connecting patches.
+
+    const output = patch.computeOutputs()[outputName];
+    switch (output[0]) {
+      case Patch.OUTPUT_UNAVAILABLE:
+        return [Patch.INPUT_UNAVAILABLE];
+      case Patch.OUTPUT_AVAILABLE:
+        return [Patch.INPUT_AVAILABLE, output[1]];
     }
+  }
+
+  #managedConnectionIdentifier = 0;
+  getManagedConnectionIdentifier() {
+    return this.#managedConnectionIdentifier++;
+  }
+
+  addExternalInput(patchWithInput, patchInputName, managerInputName) {
+    return this.addManagedInput(
+      patchWithInput,
+      patchInputName,
+      this.#externalInputPatch,
+      managerInputName
+    );
+  }
+
+  setExternalOutput(managerOutputName, patchWithOutput, patchOutputName) {
+    return this.addManagedInput(
+      this.#externalOutputPatch,
+      managerOutputName,
+      patchWithOutput,
+      patchOutputName
+    );
+  }
+
+  compute(inputs, outputs) {
+    Object.assign(outputs, this.#externalOutputPatch.computeOutputs());
+  }
 }
 
 class PatchManagerExternalInputPatch extends Patch {
-    constructor({manager, ...rest}) {
-        super({
-            manager,
-            inputNames: manager.inputNames,
-            outputNames: manager.inputNames,
-            ...rest
-        });
-    }
-
-    computeInputs() {
-        return this.manager.computeInputs();
-    }
-
-    compute(inputs, outputs) {
-        for (const name of this.inputNames) {
-            const input = inputs[name];
-            switch (input[0]) {
-                case Patch.INPUT_UNAVAILABLE:
-                    outputs[name] = [Patch.OUTPUT_UNAVAILABLE];
-                    break;
-                case Patch.INPUT_AVAILABLE:
-                    outputs[name] = [Patch.INPUT_AVAILABLE, input[1]];
-                    break;
-            }
-        }
+  constructor({ manager, ...rest }) {
+    super({
+      manager,
+      inputNames: manager.inputNames,
+      outputNames: manager.inputNames,
+      ...rest,
+    });
+  }
+
+  computeInputs() {
+    return this.manager.computeInputs();
+  }
+
+  compute(inputs, outputs) {
+    for (const name of this.inputNames) {
+      const input = inputs[name];
+      switch (input[0]) {
+        case Patch.INPUT_UNAVAILABLE:
+          outputs[name] = [Patch.OUTPUT_UNAVAILABLE];
+          break;
+        case Patch.INPUT_AVAILABLE:
+          outputs[name] = [Patch.INPUT_AVAILABLE, input[1]];
+          break;
+      }
     }
+  }
 }
 
 class PatchManagerExternalOutputPatch extends Patch {
-    constructor({manager, ...rest}) {
-        super({
-            manager,
-            inputNames: manager.outputNames,
-            outputNames: manager.outputNames,
-            ...rest
-        });
-    }
-
-    compute(inputs, outputs) {
-        for (const name of this.inputNames) {
-            const input = inputs[name];
-            switch (input[0]) {
-                case Patch.INPUT_UNAVAILABLE:
-                    outputs[name] = [Patch.OUTPUT_UNAVAILABLE];
-                    break;
-                case Patch.INPUT_AVAILABLE:
-                    outputs[name] = [Patch.INPUT_AVAILABLE, input[1]];
-                    break;
-            }
-        }
+  constructor({ manager, ...rest }) {
+    super({
+      manager,
+      inputNames: manager.outputNames,
+      outputNames: manager.outputNames,
+      ...rest,
+    });
+  }
+
+  compute(inputs, outputs) {
+    for (const name of this.inputNames) {
+      const input = inputs[name];
+      switch (input[0]) {
+        case Patch.INPUT_UNAVAILABLE:
+          outputs[name] = [Patch.OUTPUT_UNAVAILABLE];
+          break;
+        case Patch.INPUT_AVAILABLE:
+          outputs[name] = [Patch.INPUT_AVAILABLE, input[1]];
+          break;
+      }
     }
+  }
 }
 
 // --> demo
@@ -295,84 +313,84 @@ const common = Symbol();
 const hsmusic = Symbol();
 
 Patch[caches] = {
-    WireCachedPatchManager: class extends PatchManager {
-        // "Wire" caching for PatchManager: Remembers the last outputs to come
-        // from each patch. As long as the inputs for a patch do not change, its
-        // cached outputs are reused.
-
-        // TODO: This has a unique cache for each managed input. It should
-        // re-use a cache for the same patch and output name. How can we ensure
-        // the cache is dropped when the patch is removed, though? (Spoilers:
-        // probably just override removeManagedPatch)
-        computeManagedInput(patch, outputName, memory) {
-            let cache = true;
-
-            const { previousInputs } = memory;
-            const { inputs } = patch;
-            if (memory.previousInputs) {
-                for (const inputName of patch.inputNames) {
-                    // TODO: This doesn't account for connections whose values
-                    // have changed (analogous to bubbling cache invalidation).
-                    if (inputs[inputName] !== previousInputs[inputName]) {
-                        cache = false;
-                        break;
-                    }
-                }
-            } else {
-                cache = false;
-            }
-
-            if (cache) {
-                return memory.previousOutputs[outputName];
-            }
-
-            const outputs = patch.computeOutputs();
-            memory.previousOutputs = outputs;
-            memory.previousInputs = {...inputs};
-            return outputs[outputName];
+  WireCachedPatchManager: class extends PatchManager {
+    // "Wire" caching for PatchManager: Remembers the last outputs to come
+    // from each patch. As long as the inputs for a patch do not change, its
+    // cached outputs are reused.
+
+    // TODO: This has a unique cache for each managed input. It should
+    // re-use a cache for the same patch and output name. How can we ensure
+    // the cache is dropped when the patch is removed, though? (Spoilers:
+    // probably just override removeManagedPatch)
+    computeManagedInput(patch, outputName, memory) {
+      let cache = true;
+
+      const { previousInputs } = memory;
+      const { inputs } = patch;
+      if (memory.previousInputs) {
+        for (const inputName of patch.inputNames) {
+          // TODO: This doesn't account for connections whose values
+          // have changed (analogous to bubbling cache invalidation).
+          if (inputs[inputName] !== previousInputs[inputName]) {
+            cache = false;
+            break;
+          }
         }
-    },
+      } else {
+        cache = false;
+      }
+
+      if (cache) {
+        return memory.previousOutputs[outputName];
+      }
+
+      const outputs = patch.computeOutputs();
+      memory.previousOutputs = outputs;
+      memory.previousInputs = { ...inputs };
+      return outputs[outputName];
+    }
+  },
 };
 
 Patch[common] = {
-    Stringify: class extends Patch {
-        static inputNames = ['value'];
-        static outputNames = ['value'];
-
-        compute(inputs, outputs) {
-            if (inputs.value[0] === Patch.INPUT_AVAILABLE) {
-                outputs.value = [Patch.OUTPUT_AVAILABLE, inputs.value[1].toString()];
-            } else {
-                outputs.value = [Patch.OUTPUT_UNAVAILABLE];
-            }
-        }
-    },
-
-    Echo: class extends Patch {
-        static inputNames = ['value'];
-        static outputNames = ['value'];
-
-        compute(inputs, outputs) {
-            if (inputs.value[0] === Patch.INPUT_AVAILABLE) {
-                outputs.value = [Patch.OUTPUT_AVAILABLE, inputs.value[1]];
-            } else {
-                outputs.value = [Patch.OUTPUT_UNAVAILABLE];
-            }
-        }
-    },
+  Stringify: class extends Patch {
+    static inputNames = ["value"];
+    static outputNames = ["value"];
+
+    compute(inputs, outputs) {
+      if (inputs.value[0] === Patch.INPUT_AVAILABLE) {
+        outputs.value = [Patch.OUTPUT_AVAILABLE, inputs.value[1].toString()];
+      } else {
+        outputs.value = [Patch.OUTPUT_UNAVAILABLE];
+      }
+    }
+  },
+
+  Echo: class extends Patch {
+    static inputNames = ["value"];
+    static outputNames = ["value"];
+
+    compute(inputs, outputs) {
+      if (inputs.value[0] === Patch.INPUT_AVAILABLE) {
+        outputs.value = [Patch.OUTPUT_AVAILABLE, inputs.value[1]];
+      } else {
+        outputs.value = [Patch.OUTPUT_UNAVAILABLE];
+      }
+    }
+  },
 };
 
 const PM = new Patch[caches].WireCachedPatchManager({
-    inputNames: ['externalInput'],
-    outputNames: ['externalOutput'],
+  inputNames: ["externalInput"],
+  outputNames: ["externalOutput"],
 });
 
-const P1 = new Patch[common].Stringify({manager: PM});
-const P2 = new Patch[common].Echo({manager: PM});
+const P1 = new Patch[common].Stringify({ manager: PM });
+const P2 = new Patch[common].Echo({ manager: PM });
 
-PM.addExternalInput(P1, 'value', 'externalInput');
-PM.addManagedInput(P2, 'value', P1, 'value');
-PM.setExternalOutput('externalOutput', P2, 'value');
+PM.addExternalInput(P1, "value", "externalInput");
+PM.addManagedInput(P2, "value", P1, "value");
+PM.setExternalOutput("externalOutput", P2, "value");
 
 PM.inputs.externalInput = [Patch.INPUT_CONSTANT, 123];
 console.log(PM.computeOutputs());