webpd
Version:
WebPd is a compiler for audio programming language Pure Data allowing to run .pd patches on web pages.
1 lines • 101 kB
JavaScript
var WEBPD_RUNTIME_CODE = "var WebPdRuntime = (function (exports) {\n 'use strict';\n\n var WEB_PD_WORKLET_PROCESSOR_CODE = \"/*\\n * Copyright (c) 2022-2023 Sébastien Piquemal <sebpiq@protonmail.com>, Chris McCormick.\\n *\\n * This file is part of WebPd\\n * (see https://github.com/sebpiq/WebPd).\\n *\\n * This program is free software: you can redistribute it and/or modify\\n * it under the terms of the GNU Lesser General Public License as published by\\n * the Free Software Foundation, either version 3 of the License, or\\n * (at your option) any later version.\\n *\\n * This program is distributed in the hope that it will be useful,\\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\\n * GNU Lesser General Public License for more details.\\n *\\n * You should have received a copy of the GNU Lesser General Public License\\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\\n */\\nconst FS_CALLBACK_NAMES = [\\n 'onReadSoundFile',\\n 'onOpenSoundReadStream',\\n 'onWriteSoundFile',\\n 'onOpenSoundWriteStream',\\n 'onSoundStreamData',\\n 'onCloseSoundStream',\\n];\\nclass WasmWorkletProcessor extends AudioWorkletProcessor {\\n constructor() {\\n super();\\n this.port.onmessage = this.onMessage.bind(this);\\n this.settings = {\\n blockSize: null,\\n sampleRate,\\n };\\n this.dspConfigured = false;\\n this.engine = null;\\n }\\n process(inputs, outputs) {\\n const output = outputs[0];\\n const input = inputs[0];\\n if (!this.dspConfigured) {\\n if (!this.engine) {\\n return true;\\n }\\n this.settings.blockSize = output[0].length;\\n this.engine.initialize(this.settings.sampleRate, this.settings.blockSize);\\n this.dspConfigured = true;\\n }\\n this.engine.dspLoop(input, output);\\n return true;\\n }\\n onMessage(messageEvent) {\\n const message = messageEvent.data;\\n switch (message.type) {\\n case 'code:WASM':\\n this.setWasm(message.payload.wasmBuffer);\\n break;\\n case 'code:JS':\\n this.setJsCode(message.payload.jsCode);\\n break;\\n case 'io:messageReceiver':\\n this.engine.io.messageReceivers[message.payload.nodeId][message.payload.portletId](message.payload.message);\\n break;\\n case 'fs':\\n const returned = this.engine.globals.fs[message.payload.functionName].apply(null, message.payload.arguments);\\n this.port.postMessage({\\n type: 'fs',\\n payload: {\\n functionName: message.payload.functionName + '_return',\\n operationId: message.payload.arguments[0],\\n returned,\\n },\\n });\\n break;\\n case 'destroy':\\n this.destroy();\\n break;\\n default:\\n new Error(`unknown message type ${message.type}`);\\n }\\n }\\n // TODO : control for channelCount of wasmModule\\n setWasm(wasmBuffer) {\\n return AssemblyScriptWasmBindings.createEngine(wasmBuffer).then((engine) => this.setEngine(engine));\\n }\\n setJsCode(code) {\\n const engine = JavaScriptBindings.createEngine(code);\\n this.setEngine(engine);\\n }\\n setEngine(engine) {\\n if (engine.globals.fs) {\\n FS_CALLBACK_NAMES.forEach((functionName) => {\\n engine.globals.fs[functionName] = (...args) => {\\n // We don't use transferables, because that would imply reallocating each time new array in the engine.\\n this.port.postMessage({\\n type: 'fs',\\n payload: {\\n functionName,\\n arguments: args,\\n },\\n });\\n };\\n });\\n }\\n Object.entries(engine.metadata.settings.io.messageSenders).forEach(([nodeId, portletIds]) => {\\n portletIds.forEach((portletId) => {\\n engine.io.messageSenders[nodeId][portletId] = (message) => {\\n this.port.postMessage({\\n type: 'io:messageSender',\\n payload: {\\n nodeId,\\n portletId,\\n message,\\n },\\n });\\n };\\n });\\n });\\n this.engine = engine;\\n this.dspConfigured = false;\\n }\\n destroy() {\\n this.process = () => false;\\n }\\n}\\nregisterProcessor('webpd-node', WasmWorkletProcessor);\\n\";\n\n var ASSEMBLY_SCRIPT_WASM_BINDINGS_CODE = \"var AssemblyScriptWasmBindings = (function (exports) {\\n 'use strict';\\n\\n const _proxyGetHandlerThrowIfKeyUnknown = (target, key, path) => {\\n if (!(key in target)) {\\n if ([\\n 'toJSON',\\n 'Symbol(Symbol.toStringTag)',\\n 'constructor',\\n '$typeof',\\n '$$typeof',\\n '@@__IMMUTABLE_ITERABLE__@@',\\n '@@__IMMUTABLE_RECORD__@@',\\n 'then',\\n ].includes(key)) {\\n return true;\\n }\\n throw new Error(`namespace${path ? ` <${path.keys.join('.')}>` : ''} doesn't know key \\\"${String(key)}\\\"`);\\n }\\n return false;\\n };\\n\\n const getFloatArrayType = (bitDepth) => bitDepth === 64 ? Float64Array : Float32Array;\\n const proxyAsModuleWithBindings = (rawModule, bindings) => new Proxy({}, {\\n get: (_, k) => {\\n if (bindings.hasOwnProperty(k)) {\\n const key = String(k);\\n const bindingSpec = bindings[key];\\n switch (bindingSpec.type) {\\n case 'raw':\\n if (k in rawModule) {\\n return rawModule[key];\\n }\\n else {\\n throw new Error(`Key ${String(key)} doesn't exist in raw module`);\\n }\\n case 'proxy':\\n case 'callback':\\n return bindingSpec.value;\\n }\\n }\\n else {\\n return undefined;\\n }\\n },\\n has: function (_, k) {\\n return k in bindings;\\n },\\n set: (_, k, newValue) => {\\n if (bindings.hasOwnProperty(String(k))) {\\n const key = String(k);\\n const bindingSpec = bindings[key];\\n if (bindingSpec.type === 'callback') {\\n bindingSpec.value = newValue;\\n }\\n else {\\n throw new Error(`Binding key ${String(key)} is read-only`);\\n }\\n }\\n else {\\n throw new Error(`Key ${String(k)} is not defined in bindings`);\\n }\\n return true;\\n },\\n });\\n const proxyWithEngineNameMapping = (rawModule, variableNamesIndex) => proxyWithNameMapping(rawModule, {\\n globals: variableNamesIndex.globals,\\n io: variableNamesIndex.io,\\n });\\n const proxyWithNameMapping = (rawModule, variableNamesIndex) => {\\n if (typeof variableNamesIndex === 'string') {\\n return rawModule[variableNamesIndex];\\n }\\n else if (typeof variableNamesIndex === 'object') {\\n return new Proxy(rawModule, {\\n get: (_, k) => {\\n const key = String(k);\\n if (key in rawModule) {\\n return Reflect.get(rawModule, key);\\n }\\n else if (key in variableNamesIndex) {\\n const nextVariableNames = variableNamesIndex[key];\\n return proxyWithNameMapping(rawModule, nextVariableNames);\\n }\\n else if (_proxyGetHandlerThrowIfKeyUnknown(rawModule, key)) {\\n return undefined;\\n }\\n },\\n has: function (_, k) {\\n return k in rawModule || k in variableNamesIndex;\\n },\\n set: (_, k, value) => {\\n const key = String(k);\\n if (key in variableNamesIndex) {\\n const variableName = variableNamesIndex[key];\\n if (typeof variableName !== 'string') {\\n throw new Error(`Failed to set value for key ${String(k)}: variable name is not a string`);\\n }\\n return Reflect.set(rawModule, variableName, value);\\n }\\n else {\\n throw new Error(`Key ${String(k)} is not defined in raw module`);\\n }\\n },\\n });\\n }\\n else {\\n throw new Error(`Invalid name mapping`);\\n }\\n };\\n\\n const liftString = (rawModule, pointer) => {\\n if (!pointer) {\\n throw new Error('Cannot lift a null pointer');\\n }\\n pointer = pointer >>> 0;\\n const end = (pointer +\\n new Uint32Array(rawModule.memory.buffer)[(pointer - 4) >>> 2]) >>>\\n 1;\\n const memoryU16 = new Uint16Array(rawModule.memory.buffer);\\n let start = pointer >>> 1;\\n let string = '';\\n while (end - start > 1024) {\\n string += String.fromCharCode(...memoryU16.subarray(start, (start += 1024)));\\n }\\n return string + String.fromCharCode(...memoryU16.subarray(start, end));\\n };\\n const lowerString = (rawModule, value) => {\\n if (value == null) {\\n throw new Error('Cannot lower a null string');\\n }\\n const length = value.length, pointer = rawModule.__new(length << 1, 1) >>> 0, memoryU16 = new Uint16Array(rawModule.memory.buffer);\\n for (let i = 0; i < length; ++i)\\n memoryU16[(pointer >>> 1) + i] = value.charCodeAt(i);\\n return pointer;\\n };\\n const readTypedArray = (rawModule, constructor, pointer) => {\\n if (!pointer) {\\n throw new Error('Cannot lift a null pointer');\\n }\\n const memoryU32 = new Uint32Array(rawModule.memory.buffer);\\n return new constructor(rawModule.memory.buffer, memoryU32[(pointer + 4) >>> 2], memoryU32[(pointer + 8) >>> 2] / constructor.BYTES_PER_ELEMENT);\\n };\\n const lowerFloatArray = (rawModule, bitDepth, data) => {\\n const arrayType = getFloatArrayType(bitDepth);\\n const arrayPointer = rawModule.globals.core.createFloatArray(data.length);\\n const array = readTypedArray(rawModule, arrayType, arrayPointer);\\n array.set(data);\\n return { array, arrayPointer };\\n };\\n const lowerListOfFloatArrays = (rawModule, bitDepth, data) => {\\n const arraysPointer = rawModule.globals.core.x_createListOfArrays();\\n data.forEach((array) => {\\n const { arrayPointer } = lowerFloatArray(rawModule, bitDepth, array);\\n rawModule.globals.core.x_pushToListOfArrays(arraysPointer, arrayPointer);\\n });\\n return arraysPointer;\\n };\\n const readListOfFloatArrays = (rawModule, bitDepth, listOfArraysPointer) => {\\n const listLength = rawModule.globals.core.x_getListOfArraysLength(listOfArraysPointer);\\n const arrays = [];\\n const arrayType = getFloatArrayType(bitDepth);\\n for (let i = 0; i < listLength; i++) {\\n const arrayPointer = rawModule.globals.core.x_getListOfArraysElem(listOfArraysPointer, i);\\n arrays.push(readTypedArray(rawModule, arrayType, arrayPointer));\\n }\\n return arrays;\\n };\\n\\n const instantiateWasmModule = async (wasmBuffer, wasmImports = {}) => {\\n const instanceAndModule = await WebAssembly.instantiate(wasmBuffer, {\\n env: {\\n abort: (messagePointer, _, lineNumber, columnNumber) => {\\n const message = liftString(wasmExports, messagePointer);\\n lineNumber = lineNumber;\\n columnNumber = columnNumber;\\n (() => {\\n throw Error(`${message} at ${lineNumber}:${columnNumber}`);\\n })();\\n },\\n seed: () => {\\n return (() => {\\n return Date.now() * Math.random();\\n })();\\n },\\n 'console.log': (textPointer) => {\\n console.log(liftString(wasmExports, textPointer));\\n },\\n },\\n ...wasmImports,\\n });\\n const wasmExports = instanceAndModule.instance\\n .exports;\\n return instanceAndModule.instance;\\n };\\n\\n const updateWasmInOuts = ({ refs, cache, }) => {\\n cache.wasmOutput = readTypedArray(refs.rawModule, cache.arrayType, refs.rawModule.globals.core.x_getOutput());\\n cache.wasmInput = readTypedArray(refs.rawModule, cache.arrayType, refs.rawModule.globals.core.x_getInput());\\n };\\n const createEngineLifecycleBindings = (engineContext) => {\\n const { refs, cache, metadata } = engineContext;\\n return {\\n initialize: {\\n type: 'proxy',\\n value: (sampleRate, blockSize) => {\\n metadata.settings.audio.blockSize = blockSize;\\n metadata.settings.audio.sampleRate = sampleRate;\\n cache.blockSize = blockSize;\\n refs.rawModule.initialize(sampleRate, blockSize);\\n updateWasmInOuts(engineContext);\\n },\\n },\\n dspLoop: {\\n type: 'proxy',\\n value: (input, output) => {\\n for (let channel = 0; channel < input.length; channel++) {\\n cache.wasmInput.set(input[channel], channel * cache.blockSize);\\n }\\n updateWasmInOuts(engineContext);\\n refs.rawModule.dspLoop();\\n updateWasmInOuts(engineContext);\\n for (let channel = 0; channel < output.length; channel++) {\\n output[channel].set(cache.wasmOutput.subarray(cache.blockSize * channel, cache.blockSize * (channel + 1)));\\n }\\n },\\n },\\n };\\n };\\n\\n const createCommonsBindings = (engineContext) => {\\n const { refs, cache } = engineContext;\\n return {\\n getArray: {\\n type: 'proxy',\\n value: (arrayName) => {\\n const arrayNamePointer = lowerString(refs.rawModule, arrayName);\\n const arrayPointer = refs.rawModule.globals.commons.getArray(arrayNamePointer);\\n return readTypedArray(refs.rawModule, cache.arrayType, arrayPointer);\\n },\\n },\\n setArray: {\\n type: 'proxy',\\n value: (arrayName, array) => {\\n const stringPointer = lowerString(refs.rawModule, arrayName);\\n const { arrayPointer } = lowerFloatArray(refs.rawModule, cache.bitDepth, array);\\n refs.rawModule.globals.commons.setArray(stringPointer, arrayPointer);\\n updateWasmInOuts(engineContext);\\n },\\n },\\n };\\n };\\n\\n const readMetadata = async (wasmBuffer) => {\\n const inputImports = {};\\n const wasmModule = WebAssembly.Module.imports(new WebAssembly.Module(wasmBuffer));\\n wasmModule\\n .filter((imprt) => imprt.module === 'input' && imprt.kind === 'function')\\n .forEach((imprt) => (inputImports[imprt.name] = () => undefined));\\n const wasmInstance = await instantiateWasmModule(wasmBuffer, {\\n input: inputImports,\\n });\\n const rawModule = wasmInstance.exports;\\n const stringPointer = rawModule.metadata.valueOf();\\n const metadataJSON = liftString(rawModule, stringPointer);\\n return JSON.parse(metadataJSON);\\n };\\n\\n const mapArray = (src, func) => {\\n const dest = {};\\n src.forEach((srcValue, i) => {\\n const [key, destValue] = func(srcValue, i);\\n dest[key] = destValue;\\n });\\n return dest;\\n };\\n\\n const liftMessage = (rawModule, messagePointer) => {\\n const messageTokenTypesPointer = rawModule.globals.msg.x_getTokenTypes(messagePointer);\\n const messageTokenTypes = readTypedArray(rawModule, Int32Array, messageTokenTypesPointer);\\n const message = [];\\n messageTokenTypes.forEach((tokenType, tokenIndex) => {\\n if (tokenType === rawModule.globals.msg.FLOAT_TOKEN.valueOf()) {\\n message.push(rawModule.globals.msg.readFloatToken(messagePointer, tokenIndex));\\n }\\n else if (tokenType === rawModule.globals.msg.STRING_TOKEN.valueOf()) {\\n const stringPointer = rawModule.globals.msg.readStringToken(messagePointer, tokenIndex);\\n message.push(liftString(rawModule, stringPointer));\\n }\\n });\\n return message;\\n };\\n const lowerMessage = (rawModule, message) => {\\n const template = message.reduce((template, value) => {\\n if (typeof value === 'number') {\\n template.push(rawModule.globals.msg.FLOAT_TOKEN.valueOf());\\n }\\n else if (typeof value === 'string') {\\n template.push(rawModule.globals.msg.STRING_TOKEN.valueOf());\\n template.push(value.length);\\n }\\n else {\\n throw new Error(`invalid message value ${value}`);\\n }\\n return template;\\n }, []);\\n const templateArrayPointer = rawModule.globals.msg.x_createTemplate(template.length);\\n const loweredTemplateArray = readTypedArray(rawModule, Int32Array, templateArrayPointer);\\n loweredTemplateArray.set(template);\\n const messagePointer = rawModule.globals.msg.x_create(templateArrayPointer);\\n message.forEach((value, index) => {\\n if (typeof value === 'number') {\\n rawModule.globals.msg.writeFloatToken(messagePointer, index, value);\\n }\\n else if (typeof value === 'string') {\\n const stringPointer = lowerString(rawModule, value);\\n rawModule.globals.msg.writeStringToken(messagePointer, index, stringPointer);\\n }\\n });\\n return messagePointer;\\n };\\n\\n const createIoMessageReceiversBindings = ({ metadata, refs, }) => Object.entries(metadata.settings.io.messageReceivers).reduce((bindings, [nodeId, spec]) => ({\\n ...bindings,\\n [nodeId]: {\\n type: 'proxy',\\n value: mapArray(spec, (inletId) => [\\n inletId,\\n (message) => {\\n const messagePointer = lowerMessage(refs.rawModule, message);\\n refs.rawModule.io.messageReceivers[nodeId][inletId](messagePointer);\\n },\\n ]),\\n },\\n }), {});\\n const createIoMessageSendersBindings = ({ metadata, }) => Object.entries(metadata.settings.io.messageSenders).reduce((bindings, [nodeId, spec]) => ({\\n ...bindings,\\n [nodeId]: {\\n type: 'proxy',\\n value: mapArray(spec, (outletId) => [\\n outletId,\\n (_) => undefined,\\n ]),\\n },\\n }), {});\\n const ioMsgSendersImports = ({ metadata, refs, }) => {\\n const wasmImports = {};\\n const { variableNamesIndex } = metadata.compilation;\\n Object.entries(metadata.settings.io.messageSenders).forEach(([nodeId, spec]) => {\\n spec.forEach((outletId) => {\\n const listenerName = variableNamesIndex.io.messageSenders[nodeId][outletId];\\n wasmImports[listenerName] = (messagePointer) => {\\n const message = liftMessage(refs.rawModule, messagePointer);\\n refs.engine.io.messageSenders[nodeId][outletId](message);\\n };\\n });\\n });\\n return wasmImports;\\n };\\n\\n const createFsBindings = (engineContext) => {\\n const { refs, cache, metadata } = engineContext;\\n const fsExportedNames = metadata.compilation.variableNamesIndex.globals.fs;\\n return {\\n sendReadSoundFileResponse: {\\n type: 'proxy',\\n value: 'x_onReadSoundFileResponse' in fsExportedNames\\n ? (operationId, status, sound) => {\\n let soundPointer = 0;\\n if (sound) {\\n soundPointer = lowerListOfFloatArrays(refs.rawModule, cache.bitDepth, sound);\\n }\\n refs.rawModule.globals.fs.x_onReadSoundFileResponse(operationId, status, soundPointer);\\n updateWasmInOuts(engineContext);\\n }\\n : undefined,\\n },\\n sendWriteSoundFileResponse: {\\n type: 'proxy',\\n value: 'x_onWriteSoundFileResponse' in fsExportedNames\\n ? refs.rawModule.globals.fs.x_onWriteSoundFileResponse\\n : undefined,\\n },\\n sendSoundStreamData: {\\n type: 'proxy',\\n value: 'x_onSoundStreamData' in fsExportedNames\\n ? (operationId, sound) => {\\n const soundPointer = lowerListOfFloatArrays(refs.rawModule, cache.bitDepth, sound);\\n const writtenFrameCount = refs.rawModule.globals.fs.x_onSoundStreamData(operationId, soundPointer);\\n updateWasmInOuts(engineContext);\\n return writtenFrameCount;\\n }\\n : undefined,\\n },\\n closeSoundStream: {\\n type: 'proxy',\\n value: 'x_onCloseSoundStream' in fsExportedNames\\n ? refs.rawModule.globals.fs.x_onCloseSoundStream\\n : undefined,\\n },\\n onReadSoundFile: { type: 'callback', value: () => undefined },\\n onWriteSoundFile: { type: 'callback', value: () => undefined },\\n onOpenSoundReadStream: { type: 'callback', value: () => undefined },\\n onOpenSoundWriteStream: { type: 'callback', value: () => undefined },\\n onSoundStreamData: { type: 'callback', value: () => undefined },\\n onCloseSoundStream: { type: 'callback', value: () => undefined },\\n };\\n };\\n const createFsImports = (engineContext) => {\\n const wasmImports = {};\\n const { cache, metadata, refs } = engineContext;\\n const exportedNames = metadata.compilation.variableNamesIndex.globals;\\n if ('fs' in exportedNames) {\\n const nameMapping = proxyWithNameMapping(wasmImports, exportedNames.fs);\\n if ('i_readSoundFile' in exportedNames.fs) {\\n nameMapping.i_readSoundFile = (operationId, urlPointer, infoPointer) => {\\n const url = liftString(refs.rawModule, urlPointer);\\n const info = liftMessage(refs.rawModule, infoPointer);\\n refs.engine.globals.fs.onReadSoundFile(operationId, url, info);\\n };\\n }\\n if ('i_writeSoundFile' in exportedNames.fs) {\\n nameMapping.i_writeSoundFile = (operationId, soundPointer, urlPointer, infoPointer) => {\\n const sound = readListOfFloatArrays(refs.rawModule, cache.bitDepth, soundPointer);\\n const url = liftString(refs.rawModule, urlPointer);\\n const info = liftMessage(refs.rawModule, infoPointer);\\n refs.engine.globals.fs.onWriteSoundFile(operationId, sound, url, info);\\n };\\n }\\n if ('i_openSoundReadStream' in exportedNames.fs) {\\n nameMapping.i_openSoundReadStream = (operationId, urlPointer, infoPointer) => {\\n const url = liftString(refs.rawModule, urlPointer);\\n const info = liftMessage(refs.rawModule, infoPointer);\\n updateWasmInOuts(engineContext);\\n refs.engine.globals.fs.onOpenSoundReadStream(operationId, url, info);\\n };\\n }\\n if ('i_openSoundWriteStream' in exportedNames.fs) {\\n nameMapping.i_openSoundWriteStream = (operationId, urlPointer, infoPointer) => {\\n const url = liftString(refs.rawModule, urlPointer);\\n const info = liftMessage(refs.rawModule, infoPointer);\\n refs.engine.globals.fs.onOpenSoundWriteStream(operationId, url, info);\\n };\\n }\\n if ('i_sendSoundStreamData' in exportedNames.fs) {\\n nameMapping.i_sendSoundStreamData = (operationId, blockPointer) => {\\n const block = readListOfFloatArrays(refs.rawModule, cache.bitDepth, blockPointer);\\n refs.engine.globals.fs.onSoundStreamData(operationId, block);\\n };\\n }\\n if ('i_closeSoundStream' in exportedNames.fs) {\\n nameMapping.i_closeSoundStream = (...args) => refs.engine.globals.fs.onCloseSoundStream(...args);\\n }\\n }\\n return wasmImports;\\n };\\n\\n const createEngine = async (wasmBuffer, additionalBindings) => {\\n const metadata = await readMetadata(wasmBuffer);\\n const bitDepth = metadata.settings.audio.bitDepth;\\n const arrayType = getFloatArrayType(bitDepth);\\n const engineContext = {\\n refs: {},\\n metadata: metadata,\\n cache: {\\n wasmOutput: new arrayType(0),\\n wasmInput: new arrayType(0),\\n arrayType,\\n bitDepth,\\n blockSize: 0,\\n },\\n };\\n const wasmImports = {\\n ...createFsImports(engineContext),\\n ...ioMsgSendersImports(engineContext),\\n };\\n const wasmInstance = await instantiateWasmModule(wasmBuffer, {\\n input: wasmImports,\\n });\\n engineContext.refs.rawModule = proxyWithEngineNameMapping(wasmInstance.exports, metadata.compilation.variableNamesIndex);\\n const engineBindings = createEngineBindings(engineContext);\\n const engine = proxyAsModuleWithBindings(engineContext.refs.rawModule, {\\n ...engineBindings,\\n ...(additionalBindings || {}),\\n });\\n engineContext.refs.engine = engine;\\n return engine;\\n };\\n const createEngineBindings = (engineContext) => {\\n const { metadata, refs } = engineContext;\\n const exportedNames = metadata.compilation.variableNamesIndex.globals;\\n const io = {\\n messageReceivers: proxyAsModuleWithBindings(refs.rawModule, createIoMessageReceiversBindings(engineContext)),\\n messageSenders: proxyAsModuleWithBindings(refs.rawModule, createIoMessageSendersBindings(engineContext)),\\n };\\n const globalsBindings = {\\n commons: {\\n type: 'proxy',\\n value: proxyAsModuleWithBindings(refs.rawModule, createCommonsBindings(engineContext)),\\n },\\n };\\n if ('fs' in exportedNames) {\\n const fs = proxyAsModuleWithBindings(refs.rawModule, createFsBindings(engineContext));\\n globalsBindings.fs = { type: 'proxy', value: fs };\\n }\\n return {\\n ...createEngineLifecycleBindings(engineContext),\\n metadata: { type: 'proxy', value: metadata },\\n globals: {\\n type: 'proxy',\\n value: proxyAsModuleWithBindings(refs.rawModule, globalsBindings),\\n },\\n io: { type: 'proxy', value: io },\\n };\\n };\\n\\n exports.createEngine = createEngine;\\n exports.createEngineBindings = createEngineBindings;\\n\\n return exports;\\n\\n})({});\\n\";\n\n var JAVA_SCRIPT_BINDINGS_CODE = \"var JavaScriptBindings = (function (exports) {\\n 'use strict';\\n\\n const _proxyGetHandlerThrowIfKeyUnknown = (target, key, path) => {\\n if (!(key in target)) {\\n if ([\\n 'toJSON',\\n 'Symbol(Symbol.toStringTag)',\\n 'constructor',\\n '$typeof',\\n '$$typeof',\\n '@@__IMMUTABLE_ITERABLE__@@',\\n '@@__IMMUTABLE_RECORD__@@',\\n 'then',\\n ].includes(key)) {\\n return true;\\n }\\n throw new Error(`namespace${path ? ` <${path.keys.join('.')}>` : ''} doesn't know key \\\"${String(key)}\\\"`);\\n }\\n return false;\\n };\\n\\n const getFloatArrayType = (bitDepth) => bitDepth === 64 ? Float64Array : Float32Array;\\n const proxyAsModuleWithBindings = (rawModule, bindings) => new Proxy({}, {\\n get: (_, k) => {\\n if (bindings.hasOwnProperty(k)) {\\n const key = String(k);\\n const bindingSpec = bindings[key];\\n switch (bindingSpec.type) {\\n case 'raw':\\n if (k in rawModule) {\\n return rawModule[key];\\n }\\n else {\\n throw new Error(`Key ${String(key)} doesn't exist in raw module`);\\n }\\n case 'proxy':\\n case 'callback':\\n return bindingSpec.value;\\n }\\n }\\n else {\\n return undefined;\\n }\\n },\\n has: function (_, k) {\\n return k in bindings;\\n },\\n set: (_, k, newValue) => {\\n if (bindings.hasOwnProperty(String(k))) {\\n const key = String(k);\\n const bindingSpec = bindings[key];\\n if (bindingSpec.type === 'callback') {\\n bindingSpec.value = newValue;\\n }\\n else {\\n throw new Error(`Binding key ${String(key)} is read-only`);\\n }\\n }\\n else {\\n throw new Error(`Key ${String(k)} is not defined in bindings`);\\n }\\n return true;\\n },\\n });\\n const proxyWithEngineNameMapping = (rawModule, variableNamesIndex) => proxyWithNameMapping(rawModule, {\\n globals: variableNamesIndex.globals,\\n io: variableNamesIndex.io,\\n });\\n const proxyWithNameMapping = (rawModule, variableNamesIndex) => {\\n if (typeof variableNamesIndex === 'string') {\\n return rawModule[variableNamesIndex];\\n }\\n else if (typeof variableNamesIndex === 'object') {\\n return new Proxy(rawModule, {\\n get: (_, k) => {\\n const key = String(k);\\n if (key in rawModule) {\\n return Reflect.get(rawModule, key);\\n }\\n else if (key in variableNamesIndex) {\\n const nextVariableNames = variableNamesIndex[key];\\n return proxyWithNameMapping(rawModule, nextVariableNames);\\n }\\n else if (_proxyGetHandlerThrowIfKeyUnknown(rawModule, key)) {\\n return undefined;\\n }\\n },\\n has: function (_, k) {\\n return k in rawModule || k in variableNamesIndex;\\n },\\n set: (_, k, value) => {\\n const key = String(k);\\n if (key in variableNamesIndex) {\\n const variableName = variableNamesIndex[key];\\n if (typeof variableName !== 'string') {\\n throw new Error(`Failed to set value for key ${String(k)}: variable name is not a string`);\\n }\\n return Reflect.set(rawModule, variableName, value);\\n }\\n else {\\n throw new Error(`Key ${String(k)} is not defined in raw module`);\\n }\\n },\\n });\\n }\\n else {\\n throw new Error(`Invalid name mapping`);\\n }\\n };\\n\\n const createFsModule = (rawModule) => {\\n const fsExportedNames = rawModule.metadata.compilation.variableNamesIndex.globals.fs;\\n const fs = proxyAsModuleWithBindings(rawModule, {\\n onReadSoundFile: { type: 'callback', value: () => undefined },\\n onWriteSoundFile: { type: 'callback', value: () => undefined },\\n onOpenSoundReadStream: { type: 'callback', value: () => undefined },\\n onOpenSoundWriteStream: { type: 'callback', value: () => undefined },\\n onSoundStreamData: { type: 'callback', value: () => undefined },\\n onCloseSoundStream: { type: 'callback', value: () => undefined },\\n sendReadSoundFileResponse: {\\n type: 'proxy',\\n value: 'x_onReadSoundFileResponse' in fsExportedNames\\n ? rawModule.globals.fs.x_onReadSoundFileResponse\\n : undefined,\\n },\\n sendWriteSoundFileResponse: {\\n type: 'proxy',\\n value: 'x_onWriteSoundFileResponse' in fsExportedNames\\n ? rawModule.globals.fs.x_onWriteSoundFileResponse\\n : undefined,\\n },\\n sendSoundStreamData: {\\n type: 'proxy',\\n value: 'x_onSoundStreamData' in fsExportedNames\\n ? rawModule.globals.fs.x_onSoundStreamData\\n : undefined,\\n },\\n closeSoundStream: {\\n type: 'proxy',\\n value: 'x_onCloseSoundStream' in fsExportedNames\\n ? rawModule.globals.fs.x_onCloseSoundStream\\n : undefined,\\n },\\n });\\n if ('i_openSoundWriteStream' in fsExportedNames) {\\n rawModule.globals.fs.i_openSoundWriteStream = (...args) => fs.onOpenSoundWriteStream(...args);\\n }\\n if ('i_sendSoundStreamData' in fsExportedNames) {\\n rawModule.globals.fs.i_sendSoundStreamData = (...args) => fs.onSoundStreamData(...args);\\n }\\n if ('i_openSoundReadStream' in fsExportedNames) {\\n rawModule.globals.fs.i_openSoundReadStream = (...args) => fs.onOpenSoundReadStream(...args);\\n }\\n if ('i_closeSoundStream' in fsExportedNames) {\\n rawModule.globals.fs.i_closeSoundStream = (...args) => fs.onCloseSoundStream(...args);\\n }\\n if ('i_writeSoundFile' in fsExportedNames) {\\n rawModule.globals.fs.i_writeSoundFile = (...args) => fs.onWriteSoundFile(...args);\\n }\\n if ('i_readSoundFile' in fsExportedNames) {\\n rawModule.globals.fs.i_readSoundFile = (...args) => fs.onReadSoundFile(...args);\\n }\\n return fs;\\n };\\n\\n const createCommonsModule = (rawModule, metadata) => {\\n const floatArrayType = getFloatArrayType(metadata.settings.audio.bitDepth);\\n return proxyAsModuleWithBindings(rawModule, {\\n getArray: {\\n type: 'proxy',\\n value: (arrayName) => rawModule.globals.commons.getArray(arrayName),\\n },\\n setArray: {\\n type: 'proxy',\\n value: (arrayName, array) => rawModule.globals.commons.setArray(arrayName, new floatArrayType(array)),\\n },\\n });\\n };\\n\\n const compileRawModule = (code) => new Function(`\\n ${code}\\n return exports\\n `)();\\n const createEngineBindings = (rawModule) => {\\n const exportedNames = rawModule.metadata.compilation.variableNamesIndex.globals;\\n const globalsBindings = {\\n commons: {\\n type: 'proxy',\\n value: createCommonsModule(rawModule, rawModule.metadata),\\n },\\n };\\n if ('fs' in exportedNames) {\\n globalsBindings.fs = { type: 'proxy', value: createFsModule(rawModule) };\\n }\\n return {\\n metadata: { type: 'raw' },\\n initialize: { type: 'raw' },\\n dspLoop: { type: 'raw' },\\n io: { type: 'raw' },\\n globals: {\\n type: 'proxy',\\n value: proxyAsModuleWithBindings(rawModule, globalsBindings),\\n },\\n };\\n };\\n const createEngine = (code, additionalBindings) => {\\n const rawModule = compileRawModule(code);\\n const rawModuleWithNameMapping = proxyWithEngineNameMapping(rawModule, rawModule.metadata.compilation.variableNamesIndex);\\n return proxyAsModuleWithBindings(rawModule, {\\n ...createEngineBindings(rawModuleWithNameMapping),\\n ...(additionalBindings || {}),\\n });\\n };\\n\\n exports.compileRawModule = compileRawModule;\\n exports.createEngine = createEngine;\\n exports.createEngineBindings = createEngineBindings;\\n\\n return exports;\\n\\n})({});\\n\";\n\n var fetchRetry$1 = function (fetch, defaults) {\n defaults = defaults || {};\n if (typeof fetch !== 'function') {\n throw new ArgumentError('fetch must be a function');\n }\n\n if (typeof defaults !== 'object') {\n throw new ArgumentError('defaults must be an object');\n }\n\n if (defaults.retries !== undefined && !isPositiveInteger(defaults.retries)) {\n throw new ArgumentError('retries must be a positive integer');\n }\n\n if (defaults.retryDelay !== undefined && !isPositiveInteger(defaults.retryDelay) && typeof defaults.retryDelay !== 'function') {\n throw new ArgumentError('retryDelay must be a positive integer or a function returning a positive integer');\n }\n\n if (defaults.retryOn !== undefined && !Array.isArray(defaults.retryOn) && typeof defaults.retryOn !== 'function') {\n throw new ArgumentError('retryOn property expects an array or function');\n }\n\n var baseDefaults = {\n retries: 3,\n retryDelay: 1000,\n retryOn: [],\n };\n\n defaults = Object.assign(baseDefaults, defaults);\n\n return function fetchRetry(input, init) {\n var retries = defaults.retries;\n var retryDelay = defaults.retryDelay;\n var retryOn = defaults.retryOn;\n\n if (init && init.retries !== undefined) {\n if (isPositiveInteger(init.retries)) {\n retries = init.retries;\n } else {\n throw new ArgumentError('retries must be a positive integer');\n }\n }\n\n if (init && init.retryDelay !== undefined) {\n if (isPositiveInteger(init.retryDelay) || (typeof init.retryDelay === 'function')) {\n retryDelay = init.retryDelay;\n } else {\n throw new ArgumentError('retryDelay must be a positive integer or a function returning a positive integer');\n }\n }\n\n if (init && init.retryOn) {\n if (Array.isArray(init.retryOn) || (typeof init.retryOn === 'function')) {\n retryOn = init.retryOn;\n } else {\n throw new ArgumentError('retryOn property expects an array or function');\n }\n }\n\n // eslint-disable-next-line no-undef\n return new Promise(function (resolve, reject) {\n var wrappedFetch = function (attempt) {\n // As of node 18, this is no longer needed since node comes with native support for fetch:\n /* istanbul ignore next */\n var _input =\n typeof Request !== 'undefined' && input instanceof Request\n ? input.clone()\n : input;\n fetch(_input, init)\n .then(function (response) {\n if (Array.isArray(retryOn) && retryOn.indexOf(response.status) === -1) {\n resolve(response);\n } else if (typeof retryOn === 'function') {\n try {\n // eslint-disable-next-line no-undef\n return Promise.resolve(retryOn(attempt, null, response))\n .then(function (retryOnResponse) {\n if(retryOnResponse) {\n retry(attempt, null, response);\n } else {\n resolve(response);\n }\n }).catch(reject);\n } catch (error) {\n reject(error);\n }\n } else {\n if (attempt < retries) {\n retry(attempt, null, response);\n } else {\n resolve(response);\n }\n }\n })\n .catch(function (error) {\n if (typeof retryOn === 'function') {\n try {\n // eslint-disable-next-line no-undef\n Promise.resolve(retryOn(attempt, error, null))\n .then(function (retryOnResponse) {\n if(retryOnResponse) {\n retry(attempt, error, null);\n } else {\n reject(error);\n }\n })\n .catch(function(error) {\n reject(error);\n });\n } catch(error) {\n reject(error);\n }\n } else if (attempt < retries) {\n retry(attempt, error, null);\n } else {\n reject(error);\n }\n });\n };\n\n function retry(attempt, error, response) {\n var delay = (typeof retryDelay === 'function') ?\n retryDelay(attempt, error, response) : retryDelay;\n setTimeout(function () {\n wrappedFetch(++attempt);\n }, delay);\n }\n\n wrappedFetch(0);\n });\n };\n };\n\n function isPositiveInteger(value) {\n return Number.isInteger(value) && value >= 0;\n }\n\n function ArgumentError(message) {\n this.name = 'ArgumentError';\n this.message = message;\n }\n\n /*\n * Copyright (c) 2022-2023 Sébastien Piquemal <sebpiq@protonmail.com>, Chris McCormick.\n *\n * This file is part of WebPd\n * (see https://github.com/sebpiq/WebPd).\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n const fetchRetry = fetchRetry$1(fetch);\n /**\n * Note : the audio worklet feature is available only in secure context.\n * This function will fail when used in insecure context (non-https, etc ...)\n * @see https://developer.mozilla.org/en-US/docs/Web/API/AudioWorklet\n */\n const addModule = async (context, processorCode) => {\n const blob = new Blob([processorCode], { type: 'text/javascript' });\n const workletProcessorUrl = URL.createObjectURL(blob);\n return context.audioWorklet.addModule(workletProcessorUrl);\n };\n // TODO : testing\n const fetchFile = async (url) => {\n let response;\n try {\n response = await fetchRetry(url, { retries: 3 });\n }\n catch (err) {\n throw new FileError(response.status, err.toString());\n }\n if (!response.ok) {\n const responseText = await response.text();\n throw new FileError(response.status, responseText);\n }\n return response.arrayBuffer();\n };\n const audioBufferToArray = (audioBuffer) => {\n const sound = [];\n for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {\n sound.push(audioBuffer.getChannelData(channel));\n }\n return sound;\n };\n // TODO : testing\n const fixSoundChannelCount = (sound, targetChannelCount) => {\n if (sound.length === 0) {\n throw new Error(`Received empty sound`);\n }\n const floatArrayType = sound[0].constructor;\n const frameCount = sound[0].length;\n const fixedSound = sound.slice(0, targetChannelCount);\n while (sound.length < targetChannelCount) {\n fixedSound.push(new floatArrayType(frameCount));\n }\n return fixedSound;\n };\n const resolveRelativeUrl = (rootUrl, relativeUrl) => {\n return new URL(relativeUrl, rootUrl).href;\n };\n class FileError extends Error {\n constructor(status, msg) {\n super(`Error ${status} : ${msg}`);\n }\n }\n\n /*\n * Copyright (c) 2022-2023 Sébastien Piquemal <sebpiq@protonmail.com>, Chris McCormick.\n *\n * This file is part of WebPd\n * (see https://github.com/sebpiq/WebPd).\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n // TODO : manage transferables\n class WebPdWorkletNode extends AudioWorkletNode {\n constructor(context) {\n super(context, 'webpd-node', {\n numberOfOutputs: 1,\n outputChannelCount: [2],\n });\n }\n destroy() {\n this.port.postMessage({\n type: 'destroy',\n payload: {},\n });\n }\n }\n // Concatenate WorkletProcessor code with the Wasm bindings it needs\n const WEBPD_WORKLET_PROCESSOR_CODE = ASSEMBLY_SCRIPT_WASM_BINDINGS_CODE +\n ';\\n' +\n JAVA_SCRIPT_BINDINGS_CODE +\n ';\\n' +\n WEB_PD_WORKLET_PROCESSOR_CODE;\n const registerWebPdWorkletNode = (context) => {\n return addModule(context, WEBPD_WORKLET_PROCESSOR_CODE);\n };\n\n /*\n * Copyright (c) 2022-2023 Sébastien Piquemal <sebpiq@protonmail.com>, Chris McCormick.\n *\n * This file is part of WebPd\n * (see https://github.com/sebpiq/WebPd).\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n const FILES = {};\n const STREAMS = {};\n class FakeStream {\n constructor(url, sound) {\n this.url = url;\n this.sound = sound;\n this.frameCount = sound[0].length;\n this.readPosition = 0;\n }\n }\n const read = async (url) => {\n if (FILES[url]) {\n return FILES[url];\n }\n const arrayBuffer = await fetchFile(url);\n return {\n type: 'binary',\n data: arrayBuffer,\n };\n };\n