UNPKG

@webaudiomodules/sdk

Version:
1,454 lines (1,444 loc) 59.9 kB
// src/WebAudioModule.js var WebAudioModule = class { static get isWebAudioModuleConstructor() { return true; } static createInstance(groupId, audioContext, initialState) { return new this(groupId, audioContext).initialize(initialState); } constructor(groupId, audioContext) { this._groupId = groupId; this._audioContext = audioContext; this._initialized = false; this._audioNode = void 0; this._timestamp = performance.now(); this._guiModuleUrl = void 0; this._descriptorUrl = "./descriptor.json"; this._descriptor = { identifier: `com.webaudiomodule.default`, name: `WebAudioModule_${this.constructor.name}`, vendor: "WebAudioModuleVendor", description: "", version: "0.0.0", apiVersion: "2.0.0", thumbnail: "", keywords: [], isInstrument: false, website: "", hasAudioInput: true, hasAudioOutput: true, hasAutomationInput: true, hasAutomationOutput: true, hasMidiInput: true, hasMidiOutput: true, hasMpeInput: true, hasMpeOutput: true, hasOscInput: true, hasOscOutput: true, hasSysexInput: true, hasSysexOutput: true }; } get isWebAudioModule() { return true; } get groupId() { return this._groupId; } get moduleId() { return this.descriptor.identifier; } get instanceId() { return this.moduleId + this._timestamp; } get descriptor() { return this._descriptor; } get identifier() { return this.descriptor.identifier; } get name() { return this.descriptor.name; } get vendor() { return this.descriptor.vendor; } get audioContext() { return this._audioContext; } get audioNode() { if (!this.initialized) console.warn("WAM should be initialized before getting the audioNode"); return this._audioNode; } set audioNode(node) { this._audioNode = node; } get initialized() { return this._initialized; } set initialized(value) { this._initialized = value; } async createAudioNode(initialState) { throw new TypeError("createAudioNode() not provided"); } async initialize(state) { if (!this._audioNode) this.audioNode = await this.createAudioNode(); this.initialized = true; return this; } async _loadGui() { const url = this._guiModuleUrl; if (!url) throw new TypeError("Gui module not found"); return import( /* webpackIgnore: true */ url ); } async _loadDescriptor() { const url = this._descriptorUrl; if (!url) throw new TypeError("Descriptor not found"); const response = await fetch(url); const descriptor = await response.json(); Object.assign(this._descriptor, descriptor); return this._descriptor; } async createGui() { if (!this.initialized) console.warn("Plugin should be initialized before getting the gui"); if (!this._guiModuleUrl) return void 0; const { createElement } = await this._loadGui(); return createElement(this); } destroyGui() { } }; var WebAudioModule_default = WebAudioModule; // src/RingBuffer.js var getRingBuffer = (moduleId) => { const audioWorkletGlobalScope = globalThis; class RingBuffer2 { static getStorageForCapacity(capacity, Type) { if (!Type.BYTES_PER_ELEMENT) { throw new Error("Pass in a ArrayBuffer subclass"); } const bytes = 8 + (capacity + 1) * Type.BYTES_PER_ELEMENT; return new SharedArrayBuffer(bytes); } constructor(sab, Type) { if (!Type.BYTES_PER_ELEMENT) { throw new Error("Pass a concrete typed array class as second argument"); } this._Type = Type; this._capacity = (sab.byteLength - 8) / Type.BYTES_PER_ELEMENT; this.buf = sab; this.write_ptr = new Uint32Array(this.buf, 0, 1); this.read_ptr = new Uint32Array(this.buf, 4, 1); this.storage = new Type(this.buf, 8, this._capacity); } get type() { return this._Type.name; } push(elements) { const rd = Atomics.load(this.read_ptr, 0); const wr = Atomics.load(this.write_ptr, 0); if ((wr + 1) % this._storageCapacity() === rd) { return 0; } const toWrite = Math.min(this._availableWrite(rd, wr), elements.length); const firstPart = Math.min(this._storageCapacity() - wr, toWrite); const secondPart = toWrite - firstPart; this._copy(elements, 0, this.storage, wr, firstPart); this._copy(elements, firstPart, this.storage, 0, secondPart); Atomics.store(this.write_ptr, 0, (wr + toWrite) % this._storageCapacity()); return toWrite; } pop(elements) { const rd = Atomics.load(this.read_ptr, 0); const wr = Atomics.load(this.write_ptr, 0); if (wr === rd) { return 0; } const isArray = !Number.isInteger(elements); const toRead = Math.min(this._availableRead(rd, wr), isArray ? elements.length : elements); if (isArray) { const firstPart = Math.min(this._storageCapacity() - rd, toRead); const secondPart = toRead - firstPart; this._copy(this.storage, rd, elements, 0, firstPart); this._copy(this.storage, 0, elements, firstPart, secondPart); } Atomics.store(this.read_ptr, 0, (rd + toRead) % this._storageCapacity()); return toRead; } get empty() { const rd = Atomics.load(this.read_ptr, 0); const wr = Atomics.load(this.write_ptr, 0); return wr === rd; } get full() { const rd = Atomics.load(this.read_ptr, 0); const wr = Atomics.load(this.write_ptr, 0); return (wr + 1) % this._capacity !== rd; } get capacity() { return this._capacity - 1; } get availableRead() { const rd = Atomics.load(this.read_ptr, 0); const wr = Atomics.load(this.write_ptr, 0); return this._availableRead(rd, wr); } get availableWrite() { const rd = Atomics.load(this.read_ptr, 0); const wr = Atomics.load(this.write_ptr, 0); return this._availableWrite(rd, wr); } _availableRead(rd, wr) { if (wr > rd) { return wr - rd; } return wr + this._storageCapacity() - rd; } _availableWrite(rd, wr) { let rv = rd - wr - 1; if (wr >= rd) { rv += this._storageCapacity(); } return rv; } _storageCapacity() { return this._capacity; } _copy(input, offsetInput, output, offsetOutput, size) { for (let i = 0; i < size; i++) { output[offsetOutput + i] = input[offsetInput + i]; } } } if (audioWorkletGlobalScope.AudioWorkletProcessor) { const ModuleScope = audioWorkletGlobalScope.webAudioModules.getModuleScope(moduleId); if (!ModuleScope.RingBuffer) ModuleScope.RingBuffer = RingBuffer2; } return RingBuffer2; }; var RingBuffer_default = getRingBuffer; // src/WamArrayRingBuffer.js var getWamArrayRingBuffer = (moduleId) => { const audioWorkletGlobalScope = globalThis; class WamArrayRingBuffer { static DefaultArrayCapacity = 2; static getStorageForEventCapacity(RingBuffer2, arrayLength, arrayType, maxArrayCapacity = void 0) { if (maxArrayCapacity === void 0) maxArrayCapacity = WamArrayRingBuffer.DefaultArrayCapacity; else maxArrayCapacity = Math.max(maxArrayCapacity, WamArrayRingBuffer.DefaultArrayCapacity); if (!arrayType.BYTES_PER_ELEMENT) { throw new Error("Pass in a ArrayBuffer subclass"); } const capacity = arrayLength * maxArrayCapacity; return RingBuffer2.getStorageForCapacity(capacity, arrayType); } constructor(RingBuffer2, sab, arrayLength, arrayType, maxArrayCapacity = void 0) { if (!arrayType.BYTES_PER_ELEMENT) { throw new Error("Pass in a ArrayBuffer subclass"); } this._arrayLength = arrayLength; this._arrayType = arrayType; this._arrayElementSizeBytes = arrayType.BYTES_PER_ELEMENT; this._arraySizeBytes = this._arrayLength * this._arrayElementSizeBytes; this._sab = sab; if (maxArrayCapacity === void 0) maxArrayCapacity = WamArrayRingBuffer.DefaultArrayCapacity; else maxArrayCapacity = Math.max(maxArrayCapacity, WamArrayRingBuffer.DefaultArrayCapacity); this._arrayArray = new arrayType(this._arrayLength); this._rb = new RingBuffer2(this._sab, arrayType); } write(array) { if (array.length !== this._arrayLength) return false; const elementsAvailable = this._rb.availableWrite; if (elementsAvailable < this._arrayLength) return false; let success = true; const elementsWritten = this._rb.push(array); if (elementsWritten != this._arrayLength) success = false; return success; } read(array, newest) { if (array.length !== this._arrayLength) return false; const elementsAvailable = this._rb.availableRead; if (elementsAvailable < this._arrayLength) return false; if (newest && elementsAvailable > this._arrayLength) this._rb.pop(elementsAvailable - this._arrayLength); let success = false; const elementsRead = this._rb.pop(array); if (elementsRead === this._arrayLength) success = true; return success; } } if (audioWorkletGlobalScope.AudioWorkletProcessor) { const ModuleScope = audioWorkletGlobalScope.webAudioModules.getModuleScope(moduleId); if (!ModuleScope.WamArrayRingBuffer) ModuleScope.WamArrayRingBuffer = WamArrayRingBuffer; } return WamArrayRingBuffer; }; var WamArrayRingBuffer_default = getWamArrayRingBuffer; // src/WamEnv.js var initializeWamEnv = (apiVersion) => { const audioWorkletGlobalScope = globalThis; if (audioWorkletGlobalScope.AudioWorkletProcessor && audioWorkletGlobalScope.webAudioModules) return; const moduleScopes = /* @__PURE__ */ new Map(); const groups = /* @__PURE__ */ new Map(); class WamEnv { constructor() { } get apiVersion() { return apiVersion; } getModuleScope(moduleId) { if (!moduleScopes.has(moduleId)) moduleScopes.set(moduleId, {}); return moduleScopes.get(moduleId); } getGroup(groupId, groupKey) { const group = groups.get(groupId); if (group.validate(groupKey)) return group; else throw "Invalid key"; } addGroup(group) { if (!groups.has(group.groupId)) groups.set(group.groupId, group); } removeGroup(group) { groups.delete(group.groupId); } addWam(wam) { const group = groups.get(wam.groupId); group.addWam(wam); } removeWam(wam) { const group = groups.get(wam.groupId); group.removeWam(wam); } connectEvents(groupId, fromId, toId, output = 0) { const group = groups.get(groupId); group.connectEvents(fromId, toId, output); } disconnectEvents(groupId, fromId, toId, output) { const group = groups.get(groupId); group.disconnectEvents(fromId, toId, output); } emitEvents(from, ...events) { const group = groups.get(from.groupId); group.emitEvents(from, ...events); } } if (audioWorkletGlobalScope.AudioWorkletProcessor) { if (!audioWorkletGlobalScope.webAudioModules) audioWorkletGlobalScope.webAudioModules = new WamEnv(); } }; var WamEnv_default = initializeWamEnv; // src/WamGroup.js var initializeWamGroup = (groupId, groupKey) => { const audioWorkletGlobalScope = globalThis; class WamGroup { constructor(groupId2, groupKey2) { this._groupId = groupId2; this._validate = (key) => { return key == groupKey2; }; this._processors = /* @__PURE__ */ new Map(); this._eventGraph = /* @__PURE__ */ new Map(); } get groupId() { return this._groupId; } get processors() { return this._processors; } get eventGraph() { return this._eventGraph; } validate(groupKey2) { return this._validate(groupKey2); } addWam(wam) { this._processors.set(wam.instanceId, wam); } removeWam(wam) { if (this._eventGraph.has(wam)) this._eventGraph.delete(wam); this._eventGraph.forEach((outputMap) => { outputMap.forEach((set) => { if (set && set.has(wam)) set.delete(wam); }); }); this._processors.delete(wam.instanceId); } connectEvents(fromId, toId, output) { const from = this._processors.get(fromId); const to = this._processors.get(toId); let outputMap; if (this._eventGraph.has(from)) { outputMap = this._eventGraph.get(from); } else { outputMap = []; this._eventGraph.set(from, outputMap); } if (outputMap[output]) { outputMap[output].add(to); } else { const set = /* @__PURE__ */ new Set(); set.add(to); outputMap[output] = set; } } disconnectEvents(fromId, toId, output) { const from = this._processors.get(fromId); if (!this._eventGraph.has(from)) return; const outputMap = this._eventGraph.get(from); if (typeof toId === "undefined") { outputMap.forEach((set) => { if (set) set.clear(); }); return; } const to = this._processors.get(toId); if (typeof output === "undefined") { outputMap.forEach((set) => { if (set) set.delete(to); }); return; } if (!outputMap[output]) return; outputMap[output].delete(to); } emitEvents(from, ...events) { if (!this._eventGraph.has(from)) return; const downstream = this._eventGraph.get(from); downstream.forEach((set) => { if (set) set.forEach((wam) => wam.scheduleEvents(...events)); }); } } if (audioWorkletGlobalScope.AudioWorkletProcessor) { audioWorkletGlobalScope.webAudioModules.addGroup(new WamGroup(groupId, groupKey)); } }; var WamGroup_default = initializeWamGroup; // src/WamEventRingBuffer.js var getWamEventRingBuffer = (moduleId) => { const audioWorkletGlobalScope = globalThis; class WamEventRingBuffer2 { static DefaultExtraBytesPerEvent = 64; static WamEventBaseBytes = 4 + 1 + 8; static WamAutomationEventBytes = WamEventRingBuffer2.WamEventBaseBytes + 2 + 8 + 1; static WamTransportEventBytes = WamEventRingBuffer2.WamEventBaseBytes + 4 + 8 + 8 + 1 + 1 + 1; static WamMidiEventBytes = WamEventRingBuffer2.WamEventBaseBytes + 1 + 1 + 1; static WamBinaryEventBytes = WamEventRingBuffer2.WamEventBaseBytes + 4; static getStorageForEventCapacity(RingBuffer2, eventCapacity, maxBytesPerEvent = void 0) { if (maxBytesPerEvent === void 0) maxBytesPerEvent = WamEventRingBuffer2.DefaultExtraBytesPerEvent; else maxBytesPerEvent = Math.max(maxBytesPerEvent, WamEventRingBuffer2.DefaultExtraBytesPerEvent); const capacity = (Math.max(WamEventRingBuffer2.WamAutomationEventBytes, WamEventRingBuffer2.WamTransportEventBytes, WamEventRingBuffer2.WamMidiEventBytes, WamEventRingBuffer2.WamBinaryEventBytes) + maxBytesPerEvent) * eventCapacity; return RingBuffer2.getStorageForCapacity(capacity, Uint8Array); } constructor(RingBuffer2, sab, parameterIds, maxBytesPerEvent = void 0) { this._eventSizeBytes = {}; this._encodeEventType = {}; this._decodeEventType = {}; const wamEventTypes = ["wam-automation", "wam-transport", "wam-midi", "wam-sysex", "wam-mpe", "wam-osc", "wam-info"]; wamEventTypes.forEach((type, encodedType) => { let byteSize = 0; switch (type) { case "wam-automation": byteSize = WamEventRingBuffer2.WamAutomationEventBytes; break; case "wam-transport": byteSize = WamEventRingBuffer2.WamTransportEventBytes; break; case "wam-mpe": case "wam-midi": byteSize = WamEventRingBuffer2.WamMidiEventBytes; break; case "wam-osc": case "wam-sysex": case "wam-info": byteSize = WamEventRingBuffer2.WamBinaryEventBytes; break; default: break; } this._eventSizeBytes[type] = byteSize; this._encodeEventType[type] = encodedType; this._decodeEventType[encodedType] = type; }); this._parameterCode = 0; this._parameterCodes = {}; this._encodeParameterId = {}; this._decodeParameterId = {}; this.setParameterIds(parameterIds); this._sab = sab; if (maxBytesPerEvent === void 0) maxBytesPerEvent = WamEventRingBuffer2.DefaultExtraBytesPerEvent; else maxBytesPerEvent = Math.max(maxBytesPerEvent, WamEventRingBuffer2.DefaultExtraBytesPerEvent); this._eventBytesAvailable = Math.max(WamEventRingBuffer2.WamAutomationEventBytes, WamEventRingBuffer2.WamTransportEventBytes, WamEventRingBuffer2.WamMidiEventBytes, WamEventRingBuffer2.WamBinaryEventBytes) + maxBytesPerEvent; this._eventBytes = new ArrayBuffer(this._eventBytesAvailable); this._eventBytesView = new DataView(this._eventBytes); this._rb = new RingBuffer2(this._sab, Uint8Array); this._eventSizeArray = new Uint8Array(this._eventBytes, 0, 4); this._eventSizeView = new DataView(this._eventBytes, 0, 4); } _writeHeader(byteSize, type, time) { let byteOffset = 0; this._eventBytesView.setUint32(byteOffset, byteSize); byteOffset += 4; this._eventBytesView.setUint8(byteOffset, this._encodeEventType[type]); byteOffset += 1; this._eventBytesView.setFloat64(byteOffset, Number.isFinite(time) ? time : -1); byteOffset += 8; return byteOffset; } _encode(event) { let byteOffset = 0; const { type, time } = event; switch (event.type) { case "wam-automation": { if (!(event.data.id in this._encodeParameterId)) break; const byteSize = this._eventSizeBytes[type]; byteOffset = this._writeHeader(byteSize, type, time); const { data } = event; const encodedParameterId = this._encodeParameterId[data.id]; const { value, normalized } = data; this._eventBytesView.setUint16(byteOffset, encodedParameterId); byteOffset += 2; this._eventBytesView.setFloat64(byteOffset, value); byteOffset += 8; this._eventBytesView.setUint8(byteOffset, normalized ? 1 : 0); byteOffset += 1; } break; case "wam-transport": { const byteSize = this._eventSizeBytes[type]; byteOffset = this._writeHeader(byteSize, type, time); const { data } = event; const { currentBar, currentBarStarted, tempo, timeSigNumerator, timeSigDenominator, playing } = data; this._eventBytesView.setUint32(byteOffset, currentBar); byteOffset += 4; this._eventBytesView.setFloat64(byteOffset, currentBarStarted); byteOffset += 8; this._eventBytesView.setFloat64(byteOffset, tempo); byteOffset += 8; this._eventBytesView.setUint8(byteOffset, timeSigNumerator); byteOffset += 1; this._eventBytesView.setUint8(byteOffset, timeSigDenominator); byteOffset += 1; this._eventBytesView.setUint8(byteOffset, playing ? 1 : 0); byteOffset += 1; } break; case "wam-mpe": case "wam-midi": { const byteSize = this._eventSizeBytes[type]; byteOffset = this._writeHeader(byteSize, type, time); const { data } = event; const { bytes } = data; let b = 0; while (b < 3) { this._eventBytesView.setUint8(byteOffset, bytes[b]); byteOffset += 1; b++; } } break; case "wam-osc": case "wam-sysex": case "wam-info": { let bytes = null; if (event.type === "wam-info") { const { data } = event; bytes = new TextEncoder().encode(data.instanceId); } else { const { data } = event; bytes = data.bytes; } const numBytes = bytes.length; const byteSize = this._eventSizeBytes[type]; byteOffset = this._writeHeader(byteSize + numBytes, type, time); this._eventBytesView.setUint32(byteOffset, numBytes); byteOffset += 4; const bytesRequired = byteOffset + numBytes; if (bytesRequired > this._eventBytesAvailable) console.error(`Event requires ${bytesRequired} bytes but only ${this._eventBytesAvailable} have been allocated!`); const buffer = new Uint8Array(this._eventBytes, byteOffset, numBytes); buffer.set(bytes); byteOffset += numBytes; } break; default: break; } return new Uint8Array(this._eventBytes, 0, byteOffset); } _decode() { let byteOffset = 0; const type = this._decodeEventType[this._eventBytesView.getUint8(byteOffset)]; byteOffset += 1; let time = this._eventBytesView.getFloat64(byteOffset); if (time === -1) time = void 0; byteOffset += 8; switch (type) { case "wam-automation": { const encodedParameterId = this._eventBytesView.getUint16(byteOffset); byteOffset += 2; const value = this._eventBytesView.getFloat64(byteOffset); byteOffset += 8; const normalized = !!this._eventBytesView.getUint8(byteOffset); byteOffset += 1; if (!(encodedParameterId in this._decodeParameterId)) break; const id = this._decodeParameterId[encodedParameterId]; const event = { type, time, data: { id, value, normalized } }; return event; } case "wam-transport": { const currentBar = this._eventBytesView.getUint32(byteOffset); byteOffset += 4; const currentBarStarted = this._eventBytesView.getFloat64(byteOffset); byteOffset += 8; const tempo = this._eventBytesView.getFloat64(byteOffset); byteOffset += 8; const timeSigNumerator = this._eventBytesView.getUint8(byteOffset); byteOffset += 1; const timeSigDenominator = this._eventBytesView.getUint8(byteOffset); byteOffset += 1; const playing = this._eventBytesView.getUint8(byteOffset) == 1; byteOffset += 1; const event = { type, time, data: { currentBar, currentBarStarted, tempo, timeSigNumerator, timeSigDenominator, playing } }; return event; } case "wam-mpe": case "wam-midi": { const bytes = [0, 0, 0]; let b = 0; while (b < 3) { bytes[b] = this._eventBytesView.getUint8(byteOffset); byteOffset += 1; b++; } const event = { type, time, data: { bytes } }; return event; } case "wam-osc": case "wam-sysex": case "wam-info": { const numBytes = this._eventBytesView.getUint32(byteOffset); byteOffset += 4; const bytes = new Uint8Array(numBytes); bytes.set(new Uint8Array(this._eventBytes, byteOffset, numBytes)); byteOffset += numBytes; if (type === "wam-info") { const instanceId = new TextDecoder().decode(bytes); const data = { instanceId }; return { type, time, data }; } else { const data = { bytes }; return { type, time, data }; } } default: break; } return false; } write(...events) { const numEvents = events.length; let bytesAvailable = this._rb.availableWrite; let numSkipped = 0; let i = 0; while (i < numEvents) { const event = events[i]; const bytes = this._encode(event); const eventSizeBytes = bytes.byteLength; let bytesWritten = 0; if (bytesAvailable >= eventSizeBytes) { if (eventSizeBytes === 0) numSkipped++; else bytesWritten = this._rb.push(bytes); } else break; bytesAvailable -= bytesWritten; i++; } return i - numSkipped; } read() { if (this._rb.empty) return []; const events = []; let bytesAvailable = this._rb.availableRead; let bytesRead = 0; while (bytesAvailable > 0) { bytesRead = this._rb.pop(this._eventSizeArray); bytesAvailable -= bytesRead; const eventSizeBytes = this._eventSizeView.getUint32(0); const eventBytes = new Uint8Array(this._eventBytes, 0, eventSizeBytes - 4); bytesRead = this._rb.pop(eventBytes); bytesAvailable -= bytesRead; const decodedEvent = this._decode(); if (decodedEvent) events.push(decodedEvent); } return events; } setParameterIds(parameterIds) { this._encodeParameterId = {}; this._decodeParameterId = {}; parameterIds.forEach((parameterId) => { let parameterCode = -1; if (parameterId in this._parameterCodes) parameterCode = this._parameterCodes[parameterId]; else { parameterCode = this._generateParameterCode(); this._parameterCodes[parameterId] = parameterCode; } this._encodeParameterId[parameterId] = parameterCode; this._decodeParameterId[parameterCode] = parameterId; }); } _generateParameterCode() { if (this._parameterCode > 65535) throw Error("Too many parameters have been registered!"); return this._parameterCode++; } } if (audioWorkletGlobalScope.AudioWorkletProcessor) { const ModuleScope = audioWorkletGlobalScope.webAudioModules.getModuleScope(moduleId); if (!ModuleScope.WamEventRingBuffer) ModuleScope.WamEventRingBuffer = WamEventRingBuffer2; } return WamEventRingBuffer2; }; var WamEventRingBuffer_default = getWamEventRingBuffer; // src/addFunctionModule.js var addFunctionModule = (audioWorklet, processorFunction, ...injection) => { const text = `(${processorFunction.toString()})(${injection.map((s) => JSON.stringify(s)).join(", ")});`; const url = URL.createObjectURL(new Blob([text], { type: "text/javascript" })); return audioWorklet.addModule(url); }; var addFunctionModule_default = addFunctionModule; // src/WamParameter.js var getWamParameter = (moduleId) => { const audioWorkletGlobalScope = globalThis; class WamParameter { constructor(info) { this.info = info; this._value = info.defaultValue; } set value(value) { this._value = value; } get value() { return this._value; } set normalizedValue(valueNorm) { this.value = this.info.denormalize(valueNorm); } get normalizedValue() { return this.info.normalize(this.value); } } if (audioWorkletGlobalScope.AudioWorkletProcessor) { const ModuleScope = audioWorkletGlobalScope.webAudioModules.getModuleScope(moduleId); if (!ModuleScope.WamParameter) ModuleScope.WamParameter = WamParameter; } return WamParameter; }; var WamParameter_default = getWamParameter; // src/WamParameterInfo.js var getWamParameterInfo = (moduleId) => { const audioWorkletGlobalScope = globalThis; const normExp = (x, e) => e === 0 ? x : x ** 1.5 ** -e; const denormExp = (x, e) => e === 0 ? x : x ** 1.5 ** e; const normalize = (x, min, max, e = 0) => min === 0 && max === 1 ? normExp(x, e) : normExp((x - min) / (max - min) || 0, e); const denormalize = (x, min, max, e = 0) => min === 0 && max === 1 ? denormExp(x, e) : denormExp(x, e) * (max - min) + min; const inRange = (x, min, max) => x >= min && x <= max; class WamParameterInfo { constructor(id, config = {}) { let { type, label, defaultValue, minValue, maxValue, discreteStep, exponent, choices, units } = config; if (type === void 0) type = "float"; if (label === void 0) label = ""; if (defaultValue === void 0) defaultValue = 0; if (choices === void 0) choices = []; if (type === "boolean" || type === "choice") { discreteStep = 1; minValue = 0; if (choices.length) maxValue = choices.length - 1; else maxValue = 1; } else { if (minValue === void 0) minValue = 0; if (maxValue === void 0) maxValue = 1; if (discreteStep === void 0) discreteStep = 0; if (exponent === void 0) exponent = 0; if (units === void 0) units = ""; } const errBase = `Param config error | ${id}: `; if (minValue >= maxValue) throw Error(errBase.concat("minValue must be less than maxValue")); if (!inRange(defaultValue, minValue, maxValue)) throw Error(errBase.concat("defaultValue out of range")); if (discreteStep % 1 || discreteStep < 0) { throw Error(errBase.concat("discreteStep must be a non-negative integer")); } else if (discreteStep > 0 && (minValue % 1 || maxValue % 1 || defaultValue % 1)) { throw Error(errBase.concat("non-zero discreteStep requires integer minValue, maxValue, and defaultValue")); } if (type === "choice" && !choices.length) { throw Error(errBase.concat("choice type parameter requires list of strings in choices")); } this.id = id; this.label = label; this.type = type; this.defaultValue = defaultValue; this.minValue = minValue; this.maxValue = maxValue; this.discreteStep = discreteStep; this.exponent = exponent; this.choices = choices; this.units = units; } normalize(value) { return normalize(value, this.minValue, this.maxValue, this.exponent); } denormalize(valueNorm) { return denormalize(valueNorm, this.minValue, this.maxValue, this.exponent); } valueString(value) { if (this.choices) return this.choices[value]; if (this.units !== "") return `${value} ${this.units}`; return `${value}`; } } if (audioWorkletGlobalScope.AudioWorkletProcessor) { const ModuleScope = audioWorkletGlobalScope.webAudioModules.getModuleScope(moduleId); if (!ModuleScope.WamParameterInfo) ModuleScope.WamParameterInfo = WamParameterInfo; } return WamParameterInfo; }; var WamParameterInfo_default = getWamParameterInfo; // src/WamParameterInterpolator.js var getWamParameterInterpolator = (moduleId) => { const audioWorkletGlobalScope = globalThis; const samplesPerQuantum = 128; const nullTableKey = "0_0"; class WamParameterInterpolator { static _tables; static _tableReferences; constructor(info, samplesPerInterpolation, skew = 0) { if (!WamParameterInterpolator._tables) { WamParameterInterpolator._tables = { nullTableKey: new Float32Array(0) }; WamParameterInterpolator._tableReferences = { nullTableKey: [] }; } this.info = info; this.values = new Float32Array(samplesPerQuantum); this._tableKey = nullTableKey; this._table = WamParameterInterpolator._tables[this._tableKey]; this._skew = 2; const { discreteStep } = info; this._discrete = !!discreteStep; this._N = this._discrete ? 0 : samplesPerInterpolation; this._n = 0; this._startValue = info.defaultValue; this._endValue = info.defaultValue; this._currentValue = info.defaultValue; this._deltaValue = 0; this._inverted = false; this._changed = true; this._filled = 0; if (!this._discrete) this.setSkew(skew); else this._skew = 0; this.setStartValue(this._startValue); } _removeTableReference(oldKey) { if (oldKey === nullTableKey) return; const { id } = this.info; const references = WamParameterInterpolator._tableReferences[oldKey]; if (references) { const index = references.indexOf(id); if (index !== -1) references.splice(index, 1); if (references.length === 0) { delete WamParameterInterpolator._tables[oldKey]; delete WamParameterInterpolator._tableReferences[oldKey]; } } } setSkew(skew) { if (this._skew === skew || this._discrete) return; if (skew < -1 || skew > 1) throw Error("skew must be in range [-1.0, 1.0]"); const newKey = [this._N, skew].join("_"); const oldKey = this._tableKey; const { id } = this.info; if (newKey === oldKey) return; if (WamParameterInterpolator._tables[newKey]) { const references = WamParameterInterpolator._tableReferences[newKey]; if (references) references.push(id); else WamParameterInterpolator._tableReferences[newKey] = [id]; } else { let e = Math.abs(skew); e = Math.pow(3 - e, e * (e + 2)); const linear = e === 1; const N = this._N; const table = new Float32Array(N + 1); if (linear) for (let n = 0; n <= N; ++n) table[n] = n / N; else for (let n = 0; n <= N; ++n) table[n] = (n / N) ** e; WamParameterInterpolator._tables[newKey] = table; WamParameterInterpolator._tableReferences[newKey] = [id]; } this._removeTableReference(oldKey); this._skew = skew; this._tableKey = newKey; this._table = WamParameterInterpolator._tables[this._tableKey]; } setStartValue(value, fill = true) { this._n = this._N; this._startValue = value; this._endValue = value; this._currentValue = value; this._deltaValue = 0; this._inverted = false; if (fill) { this.values.fill(value); this._changed = true; this._filled = this.values.length; } else { this._changed = false; this._filled = 0; } } setEndValue(value) { if (value === this._endValue) return; this._n = 0; this._startValue = this._currentValue; this._endValue = value; this._deltaValue = this._endValue - this._startValue; this._inverted = this._deltaValue > 0 && this._skew >= 0 || this._deltaValue <= 0 && this._skew < 0; this._changed = false; this._filled = 0; } process(startSample, endSample) { if (this.done) return; const length = endSample - startSample; let fill = 0; const change = this._N - this._n; if (this._discrete || !change) fill = length; else { if (change < length) { fill = Math.min(length - change, samplesPerQuantum); endSample -= fill; } if (endSample > startSample) { if (this._inverted) { for (let i = startSample; i < endSample; ++i) { const tableValue = 1 - this._table[this._N - ++this._n]; this.values[i] = this._startValue + tableValue * this._deltaValue; } } else { for (let i = startSample; i < endSample; ++i) { const tableValue = this._table[++this._n]; this.values[i] = this._startValue + tableValue * this._deltaValue; } } } if (fill > 0) { startSample = endSample; endSample += fill; } } if (fill > 0) { this.values.fill(this._endValue, startSample, endSample); this._filled += fill; } this._currentValue = this.values[endSample - 1]; if (this._n === this._N) { if (!this._changed) this._changed = true; else if (this._filled >= this.values.length) { this.setStartValue(this._endValue, false); this._changed = true; this._filled = this.values.length; } } } get done() { return this._changed && this._filled === this.values.length; } is(value) { return this._endValue === value && this.done; } destroy() { this._removeTableReference(this._tableKey); } } if (audioWorkletGlobalScope.AudioWorkletProcessor) { const ModuleScope = audioWorkletGlobalScope.webAudioModules.getModuleScope(moduleId); if (!ModuleScope.WamParameterInterpolator) ModuleScope.WamParameterInterpolator = WamParameterInterpolator; } return WamParameterInterpolator; }; var WamParameterInterpolator_default = getWamParameterInterpolator; // src/WamProcessor.js var getWamProcessor = (moduleId) => { const audioWorkletGlobalScope = globalThis; const { AudioWorkletProcessor, webAudioModules } = audioWorkletGlobalScope; const ModuleScope = audioWorkletGlobalScope.webAudioModules.getModuleScope(moduleId); const { RingBuffer: RingBuffer2, WamEventRingBuffer: WamEventRingBuffer2, WamParameter, WamParameterInterpolator } = ModuleScope; class WamProcessor extends AudioWorkletProcessor { constructor(options) { super(); const { groupId, moduleId: moduleId2, instanceId, useSab } = options.processorOptions; if (!moduleId2) throw Error("must provide moduleId argument in processorOptions!"); if (!instanceId) throw Error("must provide instanceId argument in processorOptions!"); this.groupId = groupId; this.moduleId = moduleId2; this.instanceId = instanceId; this._samplesPerQuantum = 128; this._compensationDelay = 0; this._parameterInfo = {}; this._parameterState = {}; this._parameterInterpolators = {}; this._eventQueue = []; this._pendingResponses = {}; this._useSab = !!useSab && !!globalThis.SharedArrayBuffer; this._eventSabReady = false; this._audioToMainEventSab = null; this._mainToAudioEventSab = null; this._eventWriter = null; this._eventReader = null; this._initialized = false; this._destroyed = false; this._eventQueueRequiresSort = false; webAudioModules.addWam(this); this.port.onmessage = this._onMessage.bind(this); if (this._useSab) this._configureSab(); } getCompensationDelay() { return this._compensationDelay; } scheduleEvents(...events) { let i = 0; while (i < events.length) { this._eventQueue.push({ id: 0, event: events[i] }); i++; } this._eventQueueRequiresSort = this._eventQueue.length > 1; } emitEvents(...events) { webAudioModules.emitEvents(this, ...events); } clearEvents() { this._eventQueue = []; } process(inputs, outputs, parameters) { if (!this._initialized) return true; if (this._destroyed) return false; if (this._eventSabReady) this.scheduleEvents(...this._eventReader.read()); const processingSlices = this._getProcessingSlices(); let i = 0; while (i < processingSlices.length) { const { range, events } = processingSlices[i]; const [startSample, endSample] = range; let j = 0; while (j < events.length) { this._processEvent(events[j]); j++; } this._interpolateParameterValues(startSample, endSample); this._process(startSample, endSample, inputs, outputs, parameters); i++; } return true; } destroy() { this._destroyed = true; this.port.close(); webAudioModules.removeWam(this); } _generateWamParameterInfo() { return {}; } _initialize() { this._parameterState = {}; this._parameterInterpolators = {}; this._parameterInfo = this._generateWamParameterInfo(); Object.keys(this._parameterInfo).forEach((parameterId) => { const info = this._parameterInfo[parameterId]; this._parameterState[parameterId] = new WamParameter(this._parameterInfo[parameterId]); this._parameterInterpolators[parameterId] = new WamParameterInterpolator(info, 256); }); } _configureSab() { const eventCapacity = 2 ** 10; const parameterIds = Object.keys(this._parameterInfo); if (this._eventSabReady) { this._eventWriter.setParameterIds(parameterIds); this._eventReader.setParameterIds(parameterIds); } this.port.postMessage({ eventSab: { eventCapacity, parameterIds } }); } async _onMessage(message) { if (message.data.request) { const { id, request, content } = message.data; const response = { id, response: request }; const requestComponents = request.split("/"); const verb = requestComponents[0]; const noun = requestComponents[1]; response.content = "error"; if (verb === "get") { if (noun === "parameterInfo") { let { parameterIds } = content; if (!parameterIds.length) parameterIds = Object.keys(this._parameterInfo); const parameterInfo = {}; let i = 0; while (i < parameterIds.length) { const parameterId = parameterIds[i]; parameterInfo[parameterId] = this._parameterInfo[parameterId]; i++; } response.content = parameterInfo; } else if (noun === "parameterValues") { let { normalized, parameterIds } = content; response.content = this._getParameterValues(normalized, parameterIds); } else if (noun === "state") { response.content = this._getState(); } else if (noun === "compensationDelay") { response.content = this.getCompensationDelay(); } } else if (verb === "set") { if (noun === "parameterValues") { const { parameterValues } = content; this._setParameterValues(parameterValues, true); delete response.content; } else if (noun === "state") { const { state } = content; this._setState(state); delete response.content; } } else if (verb === "add") { if (noun === "event") { const { event } = content; this._eventQueue.push({ id, event }); this._eventQueueRequiresSort = this._eventQueue.length > 1; return; } } else if (verb === "remove") { if (noun === "events") { const ids = this._eventQueue.map((queued) => queued.id); this.clearEvents(); response.content = ids; } } else if (verb === "connect") { if (noun === "events") { const { wamInstanceId, output } = content; this._connectEvents(wamInstanceId, output); delete response.content; } } else if (verb === "disconnect") { if (noun === "events") { const { wamInstanceId, output } = content; this._disconnectEvents(wamInstanceId, output); delete response.content; } } else if (verb === "initialize") { if (noun === "processor") { this._initialize(); this._initialized = true; delete response.content; } else if (noun === "eventSab") { const { mainToAudioEventSab, audioToMainEventSab } = content; this._audioToMainEventSab = audioToMainEventSab; this._mainToAudioEventSab = mainToAudioEventSab; const parameterIds = Object.keys(this._parameterInfo); this._eventWriter = new WamEventRingBuffer2(RingBuffer2, this._audioToMainEventSab, parameterIds); this._eventReader = new WamEventRingBuffer2(RingBuffer2, this._mainToAudioEventSab, parameterIds); this._eventSabReady = true; delete response.content; } } this.port.postMessage(response); } else if (message.data.destroy) { this.destroy(); } } _onTransport(transportData) { console.error("_onTransport not implemented!"); } _onMidi(midiData) { console.error("_onMidi not implemented!"); } _onSysex(sysexData) { console.error("_onMidi not implemented!"); } _onMpe(mpeData) { console.error("_onMpe not implemented!"); } _onOsc(oscData) { console.error("_onOsc not implemented!"); } _setState(state) { if (state.parameterValues) this._setParameterValues(state.parameterValues, false); } _getState() { return { parameterValues: this._getParameterValues(false) }; } _getParameterValues(normalized, parameterIds) { const parameterValues = {}; if (!parameterIds || !parameterIds.length) parameterIds = Object.keys(this._parameterState); let i = 0; while (i < parameterIds.length) { const id = parameterIds[i]; const parameter = this._parameterState[id]; parameterValues[id] = { id, value: normalized ? parameter.normalizedValue : parameter.value, normalized }; i++; } return parameterValues; } _setParameterValues(parameterUpdates, interpolate) { const parameterIds = Object.keys(parameterUpdates); let i = 0; while (i < parameterIds.length) { this._setParameterValue(parameterUpdates[parameterIds[i]], interpolate); i++; } } _setParameterValue(parameterUpdate, interpolate) { const { id, value, normalized } = parameterUpdate; const parameter = this._parameterState[id]; if (!parameter) return; if (!normalized) parameter.value = value; else parameter.normalizedValue = value; const interpolator = this._parameterInterpolators[id]; if (interpolate) interpolator.setEndValue(parameter.value); else interpolator.setStartValue(parameter.value); } _interpolateParameterValues(startIndex, endIndex) { const parameterIds = Object.keys(this._parameterInterpolators); let i = 0; while (i < parameterIds.length) { this._parameterInterpolators[parameterIds[i]].process(startIndex, endIndex); i++; } } _connectEvents(wamInstanceId, output) { webAudioModules.connectEvents(this.groupId, this.instanceId, wamInstanceId, output); } _disconnectEvents(wamInstanceId, output) { if (typeof wamInstanceId === "undefined") { webAudioModules.disconnectEvents(this.groupId, this.instanceId); return; } webAudioModules.disconnectEvents(this.groupId, this.instanceId, wamInstanceId, output); } _getProcessingSlices() { const response = "add/event"; const { currentTime, sampleRate } = audioWorkletGlobalScope; const eventsBySampleIndex = {}; if (this._eventQueueRequiresSort) { this._eventQueue.sort((a, b) => a.event.time - b.event.time); this._eventQueueRequiresSort = false; } let i = 0; while (i < this._eventQueue.length) { const { id, event } = this._eventQueue[i]; const offsetSec = event.time - currentTime; const sampleIndex = offsetSec > 0 ? Math.round(offsetSec * sampleRate) : 0; if (sampleIndex < this._samplesPerQuantum) { if (eventsBySampleIndex[sampleIndex]) eventsBySampleIndex[sampleIndex].push(event); else eventsBySampleIndex[sampleIndex] = [event]; if (id) this.port.postMessage({ id, response }); else if (this._eventSabReady) this._eventWriter.write(event); else this.port.postMessage({ event }); this._eventQueue.shift(); i = -1; } else break; i++; } const processingSlices = []; const keys = Object.keys(eventsBySampleIndex); if (keys[0] !== "0") { keys.unshift("0"); eventsBySampleIndex["0"] = []; } const lastIndex = keys.length - 1; i = 0; while (i < keys.length) { const key = keys[i]; const startSample = parseInt(key); const endSample = i < lastIndex ? parseInt(keys[i + 1]) : this._samplesPerQuantum; processingSlices.push({ range: [startSample, endSample], events: eventsBySampleIndex[key] }); i++; } return processingSlices; } _processEvent(event) { switch (event.type) { case "wam-automation": this._setParameterValue(event.data, true); break; case "wam-transport": this._onTransport(event.data); break; case "wam-midi": this._onMidi(event.data); break; case "wam-sysex": this._onSysex(event.data); break; case "wam-mpe": this._onMpe(event.data); break; case "wam-osc": this._onOsc(event.data