UNPKG

wam-community

Version:

A collection of prebuilt Web Audio Modules ready for use

252 lines (224 loc) 6.66 kB
import { WamNode, addFunctionModule, initializeWamHost } from '../../../utils/sdk/dist/index.js'; import getCustomProcessor from './CustomProcessor.js'; export default class PedalBoardNode extends WamNode { /** * Register scripts required for the processor. Must be called before constructor. * @param {BaseAudioContext} audioContext * @param {string} moduleId */ static async addModules(audioContext, moduleId) { const { audioWorklet } = audioContext; await super.addModules(audioContext, moduleId); await addFunctionModule(audioWorklet, getCustomProcessor, moduleId); } nodes = {}; pedalBoardInfos = {}; lastParameterValue = {}; MAX_NODES = 30; constructor(plugin) { super(plugin, { processorOptions: { numberOfInputs: 1, numberOfOutputs: 1, }, }); this._supportedEventTypes.add('wam-info'); } /** * Create the subGroup used by the plugins and initialize the PedalBoard hidden AudioNodes. * @author Quentin Beauchet */ async _initialize() { await super._initialize(); // const { initializeWamHost } = await import( // '../../../sdk/src/initializeWamHost.js' // ); let [subGroupId, subGroupKey] = await initializeWamHost( this.module.audioContext ); this.subGroupId = subGroupId; this.port.postMessage({ request: 'set/init', content: { subGroupId, subGroupKey }, }); this.createNodes(); this.connectNodes([]); } /** * Create the input and output nodes of the PedalBoard. * @author Quentin Beauchet */ createNodes() { this._input = this.context.createGain(); super.connect(this._input); this._output = this.context.createAnalyser(); this._output.minDecibels = -90; this._output.maxDecibels = -10; this._output.smoothingTimeConstant = 0.85; } connect(destination, outputIndex, inputIndex) { return this._output.connect(destination, outputIndex, inputIndex); } disconnect(destination, outputIndex, inputIndex) { if (destination) { return this._output.disconnect( destination, outputIndex, inputIndex ); } else { return this._output.disconnect(); } } /** * Connect every nodes from the board of the Gui. * @param {HTMLCollection} nodes * @author Quentin Beauchet */ connectNodes(nodes) { this.lastNode = this._input; nodes.forEach((el) => { let audioNode = this.nodes[el.id].node; this.lastNode.connect(audioNode); this.lastNode = audioNode; }); this.lastNode.connect(this._output); this.updateInfos(); } /** * Disconnects every nodes from the board of the Gui. It then check the nodes stored in this.nodes and * if they aren't needed anymore it delete them from the object. * The callback is used when changing the order of the plugins on the board or when removing a plugin. * The boolean forced is needed when using setState(). * @param {HTMLCollection} nodes * @author Quentin Beauchet */ disconnectNodes(nodes, forced, callback) { this.lastNode = this._input; nodes.forEach((el) => { let audioNode = this.nodes[el.id].node; this.lastNode.disconnect(); this.lastNode = audioNode; }); this.lastNode.disconnect(); if (callback) { callback(); } this.cleanup(nodes, forced); } /** * Act like a garbage collector, it remove wap that aren't in the board anymore. * @param {HTMLCollection} nodes * @param {boolean} forced remove every wap if true * @author Quentin Beauchet */ cleanup(nodes, forced) { if (forced) { for (let key in this.nodes) { this.nodes[key].node.destroy(); delete this.nodes[key]; } this.nodes = {}; this.connectNodes([]); } else { let ids = Array.from(nodes).map((el) => el.id); for (let key in this.nodes) { if (!ids.includes(key)) { this.nodes[key].node.destroy(); delete this.nodes[key]; } } this.connectNodes(nodes); } } /** * Connect audioNode at the end of the PedalBoard beetween this.lastNode and this._output. * @param {WamNode} audioNode * @author Quentin Beauchet */ connectPlugin(audioNode) { this.lastNode.disconnect(this._output); this.lastNode.connect(audioNode); audioNode.connect(this._output); this.lastNode = audioNode; } /** * Add the audioNode the the audio of the PedalBoard,then it calls updateInfos() to refresh the automation labels. * @param {WamNode} audioNode The audioNode. * @param {string} pedalName The name of the node. * @param {int} id The unique id of the node, it help to map the audioNode to it's Gui. * @author Quentin Beauchet */ addPlugin(audioNode, pedalName, id) { this.connectPlugin(audioNode); this.nodes[id] = { name: pedalName, node: audioNode }; this.updateInfos(); audioNode.addEventListener('wam-info', () => { this.updateInfos(); }); } /** * Returns the state of the PedalBoard, it's an object containing the state of each of it's nodes plus the output node. * @returns The state of the PedalBoard * @author Quentin Beauchet, Yann Forner */ async getState() { let gui = this.module.gui; let ids = Array.from(gui.board.childNodes).map((el) => el.id); let states = await Promise.all( ids.map((id) => this.nodes[id].node.getState()) ); let current = states.map((el, index) => ({ name: this.nodes[ids[index]].name, state: el, })); let presets = JSON.parse(JSON.stringify(gui.PresetsBank)); return { current, presets }; } /** * This function clear the board, disconnect all the modules, add the new modules from the param and set their states * @param {Object} state * @author Yann Forner */ async setState(state) { this.module.loadPreset(state.current); await this.module.gui.reloadPresets(state.presets); } /** * Call setState with the initial state passed at the initialisation. It needs to be called inside the gui because * the audio and the gui of the pedalboard are strongly connected. * @author Quentin Beauchet */ async initState() { await this.setState(this.initialState); } /** * Trigger an event to inform the ParamMgrNode of a change in order or an addition/deletion of the nodes in the PedalBoard. * @author Quentin Beauchet */ updateInfos() { let nodes = Object.entries(this.nodes); this.port.postMessage({ request: 'set/nodes', content: { nodes: nodes.map(([key, value]) => { return { name: value.name, nodeId: value.node.instanceId, customId: key, }; }), }, }); this.dispatchEvent( new CustomEvent('wam-info', { detail: { data: this }, }) ); if (this.module?.gui) { this.module.gui.setPreviewFullness( nodes.length >= this.MAX_NODES || this.module.gui.loadingPreset ); } } }