diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..503c653a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +_site +.sass-cache +.jekyll-cache +.jekyll-metadata +vendor +.DS_Store \ No newline at end of file diff --git a/Freeverb/Freeverb.js b/Freeverb/Freeverb.js deleted file mode 100644 index a6c271cc..00000000 --- a/Freeverb/Freeverb.js +++ /dev/null @@ -1,588 +0,0 @@ -//============================================================================== -// -// This file contains a Javascript/Webassembly/WebAudio export of the Cmajor -// patch 'Freeverb.cmajorpatch'. -// -// This file was auto-generated by the Cmajor toolkit. -// -// To use it, import this module into your HTML/Javascript code and call -// `createAudioWorkletNodePatchConnection()` and -// `connectPatchToDefaultAudioAndMIDI()` to create an instance of the -// patch and to connect it to the web audio and MIDI devices. -// -// For more details about Cmajor, visit https://cmajor.dev -// -//============================================================================== - -import * as helpers from "./cmaj_api/cmaj_audio_worklet_helper.js" - - -//============================================================================== -/** This exports the patch's manifest, in case a caller needs to find out about its properties. - */ -export const manifest = -{ - "CmajorVersion": 1, - "ID": "dev.cmajor.examples.freeverb", - "version": "1.0", - "name": "Freeverb", - "description": "Yet another re-implementation of the perennial Freeverb algorithm", - "category": "effect", - "manufacturer": "Cmajor Software Ltd", - "isInstrument": false, - "source": "Freeverb.cmajor" -}; - -/** Returns the patch's output endpoint list */ -export function getOutputEndpoints() { return Freeverb.prototype.getOutputEndpoints(); } - -/** Returns the patch's input endpoint list */ -export function getInputEndpoints() { return Freeverb.prototype.getInputEndpoints(); } - -//============================================================================== -/** Creates an audio worklet node for the patch with the given name, attaches it - * to the audio context provided, and returns an object containing the node - * and a PatchConnection class to control it. - * - * @param {AudioContext} audioContext - a web audio AudioContext object - * @param {string} workletName - the name to give the new worklet that is created - * @returns {Object} an object containing the new AudioWorkletNode and PatchConnection - */ -export async function createAudioWorkletNodePatchConnection (audioContext, workletName) -{ - const node = await helpers.createAudioWorkletNode (Freeverb, audioContext, workletName, Date.now() & 0x7fffffff); - const connection = new helpers.AudioWorkletPatchConnection (node, manifest); - - if (manifest.worker?.length > 0) - { - connection.readResource = async (path) => - { - return fetch (path); - }; - - connection.readResourceAsAudioData = async (path) => - { - const response = await connection.readResource (path); - const buffer = await audioContext.decodeAudioData (await response.arrayBuffer()); - - let frames = []; - - for (let i = 0; i < buffer.length; ++i) - frames.push ([]); - - for (let chan = 0; chan < buffer.numberOfChannels; ++chan) - { - const src = buffer.getChannelData (chan); - - for (let i = 0; i < buffer.length; ++i) - frames[i].push (src[i]); - } - - return { - frames, - sampleRate: buffer.sampleRate - } - }; - - import (connection.getResourceAddress (manifest.worker)).then (module => - { - module.default (connection); - }); - } - - return { node, connection }; -} - -//============================================================================== -/** Takes an audio node and connection that were returned by `createAudioWorkletNodePatchConnection()` - * and attempts to hook them up to the default audio and MIDI channels. - * - * @param {AudioWorkletNode} node - the audio node - * @param {PatchConnection} connection - the PatchConnection object created by `createAudioWorkletNodePatchConnection()` - * @param {AudioContext} audioContext - a web audio AudioContext object - */ -export async function connectPatchToDefaultAudioAndMIDI (node, connection, audioContext) -{ - helpers.connectDefaultAudioAndMIDI ({ node, connection, audioContext, patchInputList: getInputEndpoints() }); -} - -/*********************************************************************************** - * - * A Javascript/Webassembly implementation of the Cmajor processor 'Freeverb'. - * - * This class was auto-generated by the Cmajor toolkit. - * - * To use it, construct an instance of this class, and call `initialise()` to - * asynchronously prepare it for use. Once initialised, the class provides - * appropriate setter/getter methods for reading/writing data to its endpoints, - * and an `advance()` method to render the next block. - * - * This roughly mirrors functionality of the cmajor Performer API - see the - * C++ API classes and Cmajor docs for more information about how this is used. - */ -class Freeverb -{ - /** After constructing one of these objects, call its - * initialise() method to prepare it for use. - */ - constructor() - { - } - - //============================================================================== - /** Prepares this processor for use. - * - * @param {number} sessionID - A unique integer ID which will be used for `processor.session`. - * @param {number} frequency - The frequency in Hz that the processor will be expected to run at. - */ - async initialise (sessionID, frequency) - { - if (! ((sessionID ^ 0) > 1)) - throw new Error ("initialise() requires a valid non-zero session ID argument"); - - if (! (frequency > 1)) - throw new Error ("initialise() requires a valid frequency argument"); - - const memory = new WebAssembly.Memory ({ initial: 4 }); - const stack = new WebAssembly.Global ({ value: "i32", mutable: true }, 71216); - - const imports = { - env: { - __linear_memory: memory, - __memory_base: 0, - __stack_pointer: stack, - __table_base: 0, - memcpy: (dst, src, len) => { this.byteMemory.copyWithin (dst, src, src + len); return dst; }, - memmove: (dst, src, len) => { this.byteMemory.copyWithin (dst, src, src + len); return dst; }, - memset: (dst, value, len) => { this.byteMemory.fill (value, dst, dst + len); return dst; } - }, - }; - - const result = await WebAssembly.instantiate (this._getWasmBytes(), imports); - this.instance = result.instance; - const exports = this.instance.exports; - - const memoryBuffer = exports.memory?.buffer || memory.buffer; - this.byteMemory = new Uint8Array (memoryBuffer); - this.memoryDataView = new DataView (memoryBuffer); - - if (exports.advanceBlock) - this._advance = numFrames => exports.advanceBlock (71216, 173408, numFrames); - else - this._advance = () => exports.advanceOneFrame (71216, 173408); - - exports.initialise?.(71216, sessionID, frequency); - return true; - } - - //============================================================================== - /** Advances the processor by a number of frames. - * - * Before calling `advance()` you should use the appropriate functions to - * push data and events into the processor's input endpoints. After calling - * `advance()` you can use its output endpoint access functions to read the - * results. - * - * @param {number} numFrames - An integer number of frames to advance. - * This must be greater than zero. - */ - advance (numFrames) - { - this.byteMemory.fill (0, 175456, 175456 + numFrames * 8); - this._advance (numFrames); - } - - //============================================================================== - /** Returns an object which encapsulates the state of the patch at this point. - * The state can be restored by passing this object to `restoreState()`. - */ - getState() - { - return { memory: this.byteMemory.slice(0) }; - } - - /** Restores the patch to a state that was previously saved by a call to `getState()` - */ - restoreState (savedState) - { - if (savedState?.memory && savedState.memory?.length === this.byteMemory.length) - this.byteMemory.set (savedState.memory); - else - throw Error ("restoreState(): not a valid state object"); - } - - /** Returns a list of the output endpoints that this processor exposes. - * @returns {Array} - */ - getOutputEndpoints() - { - return [ - { - "endpointID": "audioOut", - "endpointType": "stream", - "dataType": { - "type": "vector", - "element": { - "type": "float32" - }, - "size": 2 - }, - "purpose": "audio out", - "numAudioChannels": 2 - } - ]; - } - - /** Returns a list of the input endpoints that this processor exposes. - * @returns {Array} - */ - getInputEndpoints() - { - return [ - { - "endpointID": "audioIn", - "endpointType": "stream", - "dataType": { - "type": "float32" - }, - "purpose": "audio in", - "numAudioChannels": 1 - }, - { - "endpointID": "roomSize", - "endpointType": "event", - "dataType": { - "type": "float32" - }, - "annotation": { - "name": "Room Size", - "min": 0, - "max": 100, - "init": 80, - "text": "Tiny|Small|Medium|Large|Hall" - }, - "purpose": "parameter" - }, - { - "endpointID": "damping", - "endpointType": "event", - "dataType": { - "type": "float32" - }, - "annotation": { - "name": "Damping Factor", - "min": 0, - "max": 100, - "init": 50, - "unit": "%", - "step": 1 - }, - "purpose": "parameter" - }, - { - "endpointID": "width", - "endpointType": "event", - "dataType": { - "type": "float32" - }, - "annotation": { - "name": "Width", - "min": 0, - "max": 100, - "init": 100, - "unit": "%", - "step": 1 - }, - "purpose": "parameter" - }, - { - "endpointID": "wetLevel", - "endpointType": "event", - "dataType": { - "type": "float32" - }, - "annotation": { - "name": "Wet Level", - "min": 0, - "max": 100, - "init": 33, - "unit": "%", - "step": 1 - }, - "purpose": "parameter" - }, - { - "endpointID": "dryLevel", - "endpointType": "event", - "dataType": { - "type": "float32" - }, - "annotation": { - "name": "Dry Level", - "min": 0, - "max": 100, - "init": 40, - "unit": "%", - "step": 1 - }, - "purpose": "parameter" - } - ]; - } - - /** Stores frames for the input to endpoint "audioIn" - * - * @param {Array} sourceChannelArrays - An array of channel arrays to read - * @param {number} numFramesToWrite - The number of frames to copy - */ - setInputStreamFrames_audioIn (sourceChannelArrays, numFramesToWrite) - { - try - { - if (numFramesToWrite > 512) - numFramesToWrite = 512; - - let dest = 173408; - - if (sourceChannelArrays[0].length === undefined) // If the input is a single channel - { - for (let frame = 0; frame < numFramesToWrite; ++frame) - { - const sourceSample = sourceChannelArrays[frame] || 0; - - for (let channel = 0; channel < 1; ++channel) - this._pack_f32 (dest + 4 * channel, sourceSample); - - dest += 4; - } - } - else - { - const numSourceChannels = sourceChannelArrays.length; - - for (let frame = 0; frame < numFramesToWrite; ++frame) - { - for (let channel = 0; channel < 1; ++channel) - { - const sourceSample = channel < numSourceChannels ? (sourceChannelArrays[channel][frame] || 0) : 0; - this._pack_f32 (dest + 4 * channel, sourceSample); - } - - dest += 4; - } - } - } - catch (error) - { - // Sometimes, often at startup, Web Audio provides an empty buffer - causing TypeError on attempt to dereference - if (!(error instanceof TypeError)) - throw(error); - } - } - - /** Sends an event of type `float32` to endpoint "roomSize". - * @param {number} eventValue - The event to be added to the queue for this endpoint. - */ - sendInputEvent_roomSize (eventValue) - { - this.instance.exports._sendEvent_roomSize (71216, eventValue); - } - - /** Sends an event of type `float32` to endpoint "damping". - * @param {number} eventValue - The event to be added to the queue for this endpoint. - */ - sendInputEvent_damping (eventValue) - { - this.instance.exports._sendEvent_damping (71216, eventValue); - } - - /** Sends an event of type `float32` to endpoint "width". - * @param {number} eventValue - The event to be added to the queue for this endpoint. - */ - sendInputEvent_width (eventValue) - { - this.instance.exports._sendEvent_width (71216, eventValue); - } - - /** Sends an event of type `float32` to endpoint "wetLevel". - * @param {number} eventValue - The event to be added to the queue for this endpoint. - */ - sendInputEvent_wetLevel (eventValue) - { - this.instance.exports._sendEvent_wetLevel (71216, eventValue); - } - - /** Sends an event of type `float32` to endpoint "dryLevel". - * @param {number} eventValue - The event to be added to the queue for this endpoint. - */ - sendInputEvent_dryLevel (eventValue) - { - this.instance.exports._sendEvent_dryLevel (71216, eventValue); - } - - /** Returns a frame from the output stream "audioOut" - * - * @param {number} frameIndex - the index of the frame to fetch - */ - getOutputFrame_audioOut (frameIndex) - { - return this._unpack_V2_f32 (175456 + frameIndex * 8); - } - - /** Copies frames from the output stream "audioOut" into a destination array. - * - * @param {Array} destChannelArrays - An array of arrays (one per channel) into - * which the samples will be copied - * @param {number} maxNumFramesToRead - The maximum number of frames to copy - */ - getOutputFrames_audioOut (destChannelArrays, maxNumFramesToRead) - { - let source = 175456; - let numDestChans = destChannelArrays.length; - - if (maxNumFramesToRead > 512) - maxNumFramesToRead = 512; - - if (numDestChans < 2) - { - for (let frame = 0; frame < maxNumFramesToRead; ++frame) - { - for (let channel = 0; channel < numDestChans; ++channel) - destChannelArrays[channel][frame] = this.memoryDataView.getFloat32 (source + 4 * channel, true); - - source += 8; - } - } - else if (numDestChans > 2) - { - for (let frame = 0; frame < maxNumFramesToRead; ++frame) - { - let lastSample; - - for (let channel = 0; channel < 2; ++channel) - { - lastSample = this.memoryDataView.getFloat32 (source + 4 * channel, true); - destChannelArrays[channel][frame] = lastSample; - } - - for (let channel = 2; channel < numDestChans; ++channel) - destChannelArrays[channel][frame] = lastSample; - - source += 8; - } - } - else - { - for (let frame = 0; frame < maxNumFramesToRead; ++frame) - { - for (let channel = 0; channel < 2; ++channel) - destChannelArrays[channel][frame] = this.memoryDataView.getFloat32 (source + 4 * channel, true); - - source += 8; - } - } - } - - //============================================================================== - // Code beyond this point is private internal implementation detail - - //============================================================================== - /** @access private */ - _pack_f32 (address, newValue) - { - this.memoryDataView.setFloat32 (address, newValue, true); - } - - /** @access private */ - _unpack_V2_f32 (address) - { - return [this.memoryDataView.getFloat32 (address, true), this.memoryDataView.getFloat32 (address + 4, true)]; - } - - /** @access private */ - _getWasmBytes() - { - return new Uint8Array([0,97,115,109,1,0,0,0,1,146,128,128,128,0,3,96,2,127,125,0,96,3,127,127,124,0,96,3,127,127,127,0,2,152,128,128,128,0,1,3,101,110,118,15,95,95,108,105,110,101,97,114,95,109,101,109, - 111,114,121,2,0,1,3,136,128,128,128,0,7,0,0,0,0,0,1,2,7,135,129,128,128,0,7,19,95,115,101,110,100,69,118,101,110,116,95,114,111,111,109,83,105,122,101,0,0,18,95,115,101,110,100,69,118,101,110,116,95,100, - 97,109,112,105,110,103,0,1,16,95,115,101,110,100,69,118,101,110,116,95,119,105,100,116,104,0,2,19,95,115,101,110,100,69,118,101,110,116,95,119,101,116,76,101,118,101,108,0,3,19,95,115,101,110,100,69,118, - 101,110,116,95,100,114,121,76,101,118,101,108,0,4,10,105,110,105,116,105,97,108,105,115,101,0,5,12,97,100,118,97,110,99,101,66,108,111,99,107,0,6,12,129,128,128,128,0,1,10,179,168,128,128,0,7,178,1,3,1, - 127,2,125,2,127,32,0,65,236,0,106,32,1,67,41,92,143,62,148,67,0,0,200,66,149,67,51,51,51,63,146,34,1,56,2,0,32,0,65,232,0,106,34,2,42,2,0,33,3,32,2,32,1,56,2,0,2,64,2,64,67,10,215,163,60,68,0,0,0,0,0,0, - 240,63,65,0,43,3,128,128,128,128,0,163,182,149,34,4,139,67,0,0,0,79,93,69,13,0,32,4,168,33,2,12,1,11,65,128,128,128,128,120,33,2,11,32,0,65,244,0,106,34,5,40,2,0,33,6,32,5,32,2,65,1,32,2,65,1,74,27,34, - 2,54,2,0,32,0,65,240,0,106,34,0,32,1,32,3,32,0,42,2,0,32,6,178,148,147,147,32,2,178,149,56,2,0,11,172,1,3,1,127,2,125,2,127,32,0,65,216,0,106,32,1,67,205,204,204,62,148,67,0,0,200,66,149,34,1,56,2,0,32, - 0,65,212,0,106,34,2,42,2,0,33,3,32,2,32,1,56,2,0,2,64,2,64,67,10,215,163,60,68,0,0,0,0,0,0,240,63,65,0,43,3,128,128,128,128,0,163,182,149,34,4,139,67,0,0,0,79,93,69,13,0,32,4,168,33,2,12,1,11,65,128,128, - 128,128,120,33,2,11,32,0,65,224,0,106,34,5,40,2,0,33,6,32,5,32,2,65,1,32,2,65,1,74,27,34,2,54,2,0,32,0,65,220,0,106,34,0,32,1,32,3,32,0,42,2,0,32,6,178,148,147,147,32,2,178,149,56,2,0,11,166,1,3,1,127, - 2,125,2,127,32,0,65,196,0,106,32,1,67,0,0,200,66,149,34,1,56,2,0,32,0,65,192,0,106,34,2,42,2,0,33,3,32,2,32,1,56,2,0,2,64,2,64,67,10,215,163,60,68,0,0,0,0,0,0,240,63,65,0,43,3,128,128,128,128,0,163,182, - 149,34,4,139,67,0,0,0,79,93,69,13,0,32,4,168,33,2,12,1,11,65,128,128,128,128,120,33,2,11,32,0,65,204,0,106,34,5,40,2,0,33,6,32,5,32,2,65,1,32,2,65,1,74,27,34,2,54,2,0,32,0,65,200,0,106,34,0,32,1,32,3,32, - 0,42,2,0,32,6,178,148,147,147,32,2,178,149,56,2,0,11,168,1,3,1,127,2,125,2,127,32,0,65,28,106,32,1,67,0,0,192,63,148,67,0,0,200,66,149,34,1,56,2,0,32,0,65,24,106,34,2,42,2,0,33,3,32,2,32,1,56,2,0,2,64, - 2,64,67,10,215,163,60,68,0,0,0,0,0,0,240,63,65,0,43,3,128,128,128,128,0,163,182,149,34,4,139,67,0,0,0,79,93,69,13,0,32,4,168,33,2,12,1,11,65,128,128,128,128,120,33,2,11,32,0,65,36,106,34,5,40,2,0,33,6, - 32,5,32,2,65,1,32,2,65,1,74,27,34,2,54,2,0,32,0,65,32,106,34,0,32,1,32,3,32,0,42,2,0,32,6,178,148,147,147,32,2,178,149,56,2,0,11,165,1,3,1,127,2,125,2,127,32,0,65,48,106,32,1,32,1,146,67,0,0,200,66,149, - 34,1,56,2,0,32,0,65,44,106,34,2,42,2,0,33,3,32,2,32,1,56,2,0,2,64,2,64,67,10,215,163,60,68,0,0,0,0,0,0,240,63,65,0,43,3,128,128,128,128,0,163,182,149,34,4,139,67,0,0,0,79,93,69,13,0,32,4,168,33,2,12,1, - 11,65,128,128,128,128,120,33,2,11,32,0,65,56,106,34,5,40,2,0,33,6,32,5,32,2,65,1,32,2,65,1,74,27,34,2,54,2,0,32,0,65,52,106,34,0,32,1,32,3,32,0,42,2,0,32,6,178,148,147,147,32,2,178,149,56,2,0,11,119,0, - 65,0,32,2,57,3,128,128,128,128,0,32,0,65,32,106,66,0,55,2,0,32,0,65,24,106,66,0,55,2,0,32,0,65,44,106,66,0,55,2,0,32,0,65,52,106,66,0,55,2,0,32,0,65,192,0,106,66,0,55,2,0,32,0,65,200,0,106,66,0,55,2,0, - 32,0,65,212,0,106,66,0,55,2,0,32,0,65,220,0,106,66,0,55,2,0,32,0,65,232,0,106,66,0,55,2,0,32,0,65,240,0,106,66,0,55,2,0,11,221,32,2,25,127,16,125,2,64,32,0,40,2,0,34,3,32,2,70,13,0,32,0,65,144,140,6,106, - 33,4,32,0,65,200,253,5,106,33,5,32,0,65,144,242,5,106,33,6,32,0,65,168,234,5,106,33,7,32,0,65,252,182,5,106,33,8,32,0,65,192,133,5,106,33,9,32,0,65,140,214,4,106,33,10,32,0,65,236,168,4,106,33,11,32,0, - 65,212,253,3,106,33,12,32,0,65,248,212,3,106,33,13,32,0,65,128,175,3,106,33,14,32,0,65,168,139,3,106,33,15,32,0,65,240,249,2,106,33,16,32,0,65,132,236,2,106,33,17,32,0,65,168,225,2,106,33,18,32,0,65,156, - 218,2,106,33,19,32,0,65,204,167,2,106,33,20,32,0,65,236,246,1,106,33,21,32,0,65,148,200,1,106,33,22,32,0,65,208,155,1,106,33,23,32,0,65,148,241,0,106,33,24,32,0,65,148,201,0,106,33,25,32,0,65,248,35,106, - 33,26,32,0,65,252,0,106,33,27,3,64,32,1,32,3,65,2,116,106,42,2,0,33,28,67,0,0,0,0,33,29,2,64,32,0,40,2,40,65,127,70,13,0,2,64,2,64,32,0,40,2,36,34,3,65,1,72,13,0,32,0,32,3,65,127,106,34,3,54,2,36,32,0, - 42,2,24,32,0,42,2,32,32,3,178,148,147,33,30,12,1,11,32,0,42,2,24,33,30,11,32,0,65,1,54,2,40,32,30,67,0,0,0,0,146,33,29,11,67,0,0,0,0,33,31,67,0,0,0,0,33,32,2,64,32,0,40,2,60,65,127,70,13,0,2,64,2,64,32, - 0,40,2,56,34,3,65,1,72,13,0,32,0,32,3,65,127,106,34,3,54,2,56,32,0,42,2,44,32,0,42,2,52,32,3,178,148,147,33,30,12,1,11,32,0,42,2,44,33,30,11,32,0,65,1,54,2,60,32,30,67,0,0,0,0,146,33,32,11,2,64,32,0,40, - 2,80,65,127,70,13,0,2,64,2,64,32,0,40,2,76,34,3,65,1,72,13,0,32,0,32,3,65,127,106,34,3,54,2,76,32,0,42,2,64,32,0,42,2,72,32,3,178,148,147,33,30,12,1,11,32,0,42,2,64,33,30,11,32,0,65,1,54,2,80,32,30,67, - 0,0,0,0,146,33,31,11,67,0,0,0,0,33,30,67,0,0,0,0,33,33,2,64,32,0,40,2,100,65,127,70,13,0,2,64,2,64,32,0,40,2,96,34,3,65,1,72,13,0,32,0,32,3,65,127,106,34,3,54,2,96,32,0,42,2,84,32,0,42,2,92,32,3,178,148, - 147,33,33,12,1,11,32,0,42,2,84,33,33,11,32,0,65,1,54,2,100,32,33,67,0,0,0,0,146,33,33,11,2,64,32,0,40,2,120,65,127,70,13,0,2,64,2,64,32,0,40,2,116,34,3,65,1,72,13,0,32,0,32,3,65,127,106,34,3,54,2,116,32, - 0,42,2,104,32,0,42,2,112,32,3,178,148,147,33,30,12,1,11,32,0,42,2,104,33,30,11,32,0,65,1,54,2,120,32,30,67,0,0,0,0,146,33,30,11,67,0,0,0,0,33,34,32,30,67,0,0,0,0,146,33,35,32,33,67,0,0,0,0,146,33,30,32, - 28,67,0,0,0,0,146,33,33,67,0,0,0,0,33,36,2,64,32,0,40,2,244,35,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,27,32,0,40,2,236,35,65,2,116,106,34,3,42,2,0,34,28,148,32,30,32,0,42,2,240,35,148,146,34,37, - 56,2,240,35,32,3,32,33,67,143,194,117,60,148,32,35,32,37,148,146,56,2,0,32,0,65,1,54,2,244,35,32,0,32,0,40,2,236,35,65,1,106,65,220,8,111,34,3,65,220,8,106,32,3,32,3,65,0,72,27,54,2,236,35,32,28,67,0,0, - 0,0,146,33,36,11,2,64,32,0,40,2,144,73,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,26,32,0,40,2,136,73,65,2,116,106,34,3,42,2,0,34,28,148,32,30,32,0,42,2,140,73,148,146,34,37,56,2,140,73,32,3,32,33, - 67,143,194,117,60,148,32,35,32,37,148,146,56,2,0,32,0,65,1,54,2,144,73,32,0,32,0,40,2,136,73,65,1,106,65,164,9,111,34,3,65,164,9,106,32,3,32,3,65,0,72,27,54,2,136,73,32,28,67,0,0,0,0,146,33,34,11,67,0, - 0,0,0,33,38,67,0,0,0,0,33,39,2,64,32,0,40,2,144,113,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,25,32,0,40,2,136,113,65,2,116,106,34,3,42,2,0,34,28,148,32,30,32,0,42,2,140,113,148,146,34,37,56,2,140, - 113,32,3,32,33,67,143,194,117,60,148,32,35,32,37,148,146,56,2,0,32,0,65,1,54,2,144,113,32,0,32,0,40,2,136,113,65,1,106,65,253,9,111,34,3,65,253,9,106,32,3,32,3,65,0,72,27,54,2,136,113,32,28,67,0,0,0,0, - 146,33,39,11,2,64,32,0,40,2,204,155,1,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,24,32,0,40,2,196,155,1,65,2,116,106,34,3,42,2,0,34,28,148,32,30,32,0,42,2,200,155,1,148,146,34,37,56,2,200,155,1,32, - 3,32,33,67,143,194,117,60,148,32,35,32,37,148,146,56,2,0,32,0,65,1,54,2,204,155,1,32,0,32,0,40,2,196,155,1,65,1,106,65,204,10,111,34,3,65,204,10,106,32,3,32,3,65,0,72,27,54,2,196,155,1,32,28,67,0,0,0,0, - 146,33,38,11,67,0,0,0,0,33,40,67,0,0,0,0,33,41,2,64,32,0,40,2,144,200,1,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,23,32,0,40,2,136,200,1,65,2,116,106,34,3,42,2,0,34,28,148,32,30,32,0,42,2,140,200, - 1,148,146,34,37,56,2,140,200,1,32,3,32,33,67,143,194,117,60,148,32,35,32,37,148,146,56,2,0,32,0,65,1,54,2,144,200,1,32,0,32,0,40,2,136,200,1,65,1,106,65,142,11,111,34,3,65,142,11,106,32,3,32,3,65,0,72, - 27,54,2,136,200,1,32,28,67,0,0,0,0,146,33,41,11,2,64,32,0,40,2,232,246,1,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,22,32,0,40,2,224,246,1,65,2,116,106,34,3,42,2,0,34,28,148,32,30,32,0,42,2,228,246, - 1,148,146,34,37,56,2,228,246,1,32,3,32,33,67,143,194,117,60,148,32,35,32,37,148,146,56,2,0,32,0,65,1,54,2,232,246,1,32,0,32,0,40,2,224,246,1,65,1,106,65,211,11,111,34,3,65,211,11,106,32,3,32,3,65,0,72, - 27,54,2,224,246,1,32,28,67,0,0,0,0,146,33,40,11,67,0,0,0,0,33,42,67,0,0,0,0,33,43,2,64,32,0,40,2,200,167,2,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,21,32,0,40,2,192,167,2,65,2,116,106,34,3,42,2,0, - 34,28,148,32,30,32,0,42,2,196,167,2,148,146,34,37,56,2,196,167,2,32,3,32,33,67,143,194,117,60,148,32,35,32,37,148,146,56,2,0,32,0,65,1,54,2,200,167,2,32,0,32,0,40,2,192,167,2,65,1,106,65,149,12,111,34, - 3,65,149,12,106,32,3,32,3,65,0,72,27,54,2,192,167,2,32,28,67,0,0,0,0,146,33,43,11,2,64,32,0,40,2,152,218,2,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,20,32,0,40,2,144,218,2,65,2,116,106,34,3,42,2,0, - 34,28,148,32,30,32,0,42,2,148,218,2,148,146,34,37,56,2,148,218,2,32,3,32,33,67,143,194,117,60,148,32,35,32,37,148,146,56,2,0,32,0,65,1,54,2,152,218,2,32,0,32,0,40,2,144,218,2,65,1,106,65,209,12,111,34, - 3,65,209,12,106,32,3,32,3,65,0,72,27,54,2,144,218,2,32,28,67,0,0,0,0,146,33,42,11,67,0,0,0,0,33,28,67,0,0,0,0,33,37,2,64,32,0,40,2,164,225,2,65,127,70,13,0,32,19,32,0,40,2,160,225,2,65,2,116,106,34,3,32, - 36,32,34,146,32,39,146,32,38,146,32,41,146,32,40,146,32,43,146,32,42,146,34,37,32,3,42,2,0,34,34,67,0,0,0,63,148,146,56,2,0,32,0,65,1,54,2,164,225,2,32,0,32,0,40,2,160,225,2,65,1,106,65,225,1,111,34,3, - 65,225,1,106,32,3,32,3,65,0,72,27,54,2,160,225,2,32,34,32,37,147,67,0,0,0,0,146,33,37,11,2,64,32,0,40,2,128,236,2,65,127,70,13,0,32,18,32,0,40,2,252,235,2,65,2,116,106,34,3,32,37,32,3,42,2,0,34,28,67,0, - 0,0,63,148,146,56,2,0,32,0,65,1,54,2,128,236,2,32,0,32,0,40,2,252,235,2,65,1,106,65,213,2,111,34,3,65,213,2,106,32,3,32,3,65,0,72,27,54,2,252,235,2,32,28,32,37,147,67,0,0,0,0,146,33,28,11,67,0,0,0,0,33, - 37,67,0,0,0,0,33,34,2,64,32,0,40,2,236,249,2,65,127,70,13,0,32,17,32,0,40,2,232,249,2,65,2,116,106,34,3,32,28,32,3,42,2,0,34,34,67,0,0,0,63,148,146,56,2,0,32,0,65,1,54,2,236,249,2,32,0,32,0,40,2,232,249, - 2,65,1,106,65,185,3,111,34,3,65,185,3,106,32,3,32,3,65,0,72,27,54,2,232,249,2,32,34,32,28,147,67,0,0,0,0,146,33,34,11,2,64,32,0,40,2,164,139,3,65,127,70,13,0,32,16,32,0,40,2,160,139,3,65,2,116,106,34,3, - 32,34,32,3,42,2,0,34,28,67,0,0,0,63,148,146,56,2,0,32,0,65,1,54,2,164,139,3,32,0,32,0,40,2,160,139,3,65,1,106,65,172,4,111,34,3,65,172,4,106,32,3,32,3,65,0,72,27,54,2,160,139,3,32,28,32,34,147,67,0,0,0, - 0,146,33,37,11,67,0,0,0,0,33,28,67,0,0,0,0,33,34,2,64,32,0,40,2,252,174,3,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,15,32,0,40,2,244,174,3,65,2,116,106,34,3,42,2,0,34,34,148,32,30,32,0,42,2,248,174, - 3,148,146,34,36,56,2,248,174,3,32,3,32,33,67,143,194,117,60,148,32,35,32,36,148,146,56,2,0,32,0,65,1,54,2,252,174,3,32,0,32,0,40,2,244,174,3,65,1,106,65,243,8,111,34,3,65,243,8,106,32,3,32,3,65,0,72,27, - 54,2,244,174,3,32,34,67,0,0,0,0,146,33,34,11,2,64,32,0,40,2,244,212,3,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,14,32,0,40,2,236,212,3,65,2,116,106,34,3,42,2,0,34,28,148,32,30,32,0,42,2,240,212,3, - 148,146,34,36,56,2,240,212,3,32,3,32,33,67,143,194,117,60,148,32,35,32,36,148,146,56,2,0,32,0,65,1,54,2,244,212,3,32,0,32,0,40,2,236,212,3,65,1,106,65,187,9,111,34,3,65,187,9,106,32,3,32,3,65,0,72,27,54, - 2,236,212,3,32,28,67,0,0,0,0,146,33,28,11,67,0,0,0,0,33,36,67,0,0,0,0,33,38,2,64,32,0,40,2,208,253,3,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,13,32,0,40,2,200,253,3,65,2,116,106,34,3,42,2,0,34,38, - 148,32,30,32,0,42,2,204,253,3,148,146,34,39,56,2,204,253,3,32,3,32,33,67,143,194,117,60,148,32,35,32,39,148,146,56,2,0,32,0,65,1,54,2,208,253,3,32,0,32,0,40,2,200,253,3,65,1,106,65,148,10,111,34,3,65,148, - 10,106,32,3,32,3,65,0,72,27,54,2,200,253,3,32,38,67,0,0,0,0,146,33,38,11,2,64,32,0,40,2,232,168,4,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,12,32,0,40,2,224,168,4,65,2,116,106,34,3,42,2,0,34,36,148, - 32,30,32,0,42,2,228,168,4,148,146,34,39,56,2,228,168,4,32,3,32,33,67,143,194,117,60,148,32,35,32,39,148,146,56,2,0,32,0,65,1,54,2,232,168,4,32,0,32,0,40,2,224,168,4,65,1,106,65,227,10,111,34,3,65,227,10, - 106,32,3,32,3,65,0,72,27,54,2,224,168,4,32,36,67,0,0,0,0,146,33,36,11,67,0,0,0,0,33,39,67,0,0,0,0,33,40,2,64,32,0,40,2,136,214,4,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,11,32,0,40,2,128,214,4,65, - 2,116,106,34,3,42,2,0,34,40,148,32,30,32,0,42,2,132,214,4,148,146,34,41,56,2,132,214,4,32,3,32,33,67,143,194,117,60,148,32,35,32,41,148,146,56,2,0,32,0,65,1,54,2,136,214,4,32,0,32,0,40,2,128,214,4,65,1, - 106,65,165,11,111,34,3,65,165,11,106,32,3,32,3,65,0,72,27,54,2,128,214,4,32,40,67,0,0,0,0,146,33,40,11,2,64,32,0,40,2,188,133,5,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147,32,10,32,0,40,2,180,133,5,65, - 2,116,106,34,3,42,2,0,34,39,148,32,30,32,0,42,2,184,133,5,148,146,34,41,56,2,184,133,5,32,3,32,33,67,143,194,117,60,148,32,35,32,41,148,146,56,2,0,32,0,65,1,54,2,188,133,5,32,0,32,0,40,2,180,133,5,65,1, - 106,65,234,11,111,34,3,65,234,11,106,32,3,32,3,65,0,72,27,54,2,180,133,5,32,39,67,0,0,0,0,146,33,39,11,67,0,0,0,0,33,41,67,0,0,0,0,33,42,2,64,32,0,40,2,248,182,5,65,127,70,13,0,32,0,67,0,0,128,63,32,30, - 147,32,9,32,0,40,2,240,182,5,65,2,116,106,34,3,42,2,0,34,42,148,32,30,32,0,42,2,244,182,5,148,146,34,43,56,2,244,182,5,32,3,32,33,67,143,194,117,60,148,32,35,32,43,148,146,56,2,0,32,0,65,1,54,2,248,182, - 5,32,0,32,0,40,2,240,182,5,65,1,106,65,172,12,111,34,3,65,172,12,106,32,3,32,3,65,0,72,27,54,2,240,182,5,32,42,67,0,0,0,0,146,33,42,11,2,64,32,0,40,2,164,234,5,65,127,70,13,0,32,0,67,0,0,128,63,32,30,147, - 32,8,32,0,40,2,156,234,5,65,2,116,106,34,3,42,2,0,34,41,148,32,30,32,0,42,2,160,234,5,148,146,34,30,56,2,160,234,5,32,3,32,33,67,143,194,117,60,148,32,35,32,30,148,146,56,2,0,32,0,65,1,54,2,164,234,5,32, - 0,32,0,40,2,156,234,5,65,1,106,65,232,12,111,34,3,65,232,12,106,32,3,32,3,65,0,72,27,54,2,156,234,5,32,41,67,0,0,0,0,146,33,41,11,67,0,0,0,0,33,30,67,0,0,0,0,33,35,2,64,32,0,40,2,140,242,5,65,127,70,13, - 0,32,7,32,0,40,2,136,242,5,65,2,116,106,34,3,32,34,32,28,146,32,38,146,32,36,146,32,40,146,32,39,146,32,42,146,32,41,146,34,35,32,3,42,2,0,34,28,67,0,0,0,63,148,146,56,2,0,32,0,65,1,54,2,140,242,5,32,0, - 32,0,40,2,136,242,5,65,1,106,65,248,1,111,34,3,65,248,1,106,32,3,32,3,65,0,72,27,54,2,136,242,5,32,28,32,35,147,67,0,0,0,0,146,33,35,11,2,64,32,0,40,2,196,253,5,65,127,70,13,0,32,6,32,0,40,2,192,253,5, - 65,2,116,106,34,3,32,35,32,3,42,2,0,34,30,67,0,0,0,63,148,146,56,2,0,32,0,65,1,54,2,196,253,5,32,0,32,0,40,2,192,253,5,65,1,106,65,236,2,111,34,3,65,236,2,106,32,3,32,3,65,0,72,27,54,2,192,253,5,32,30, - 32,35,147,67,0,0,0,0,146,33,30,11,67,0,0,0,0,33,35,67,0,0,0,0,33,28,2,64,32,0,40,2,140,140,6,65,127,70,13,0,32,5,32,0,40,2,136,140,6,65,2,116,106,34,3,32,30,32,3,42,2,0,34,28,67,0,0,0,63,148,146,56,2,0, - 32,0,65,1,54,2,140,140,6,32,0,32,0,40,2,136,140,6,65,1,106,65,208,3,111,34,3,65,208,3,106,32,3,32,3,65,0,72,27,54,2,136,140,6,32,28,32,30,147,67,0,0,0,0,146,33,28,11,2,64,32,0,40,2,160,158,6,65,127,70, - 13,0,32,4,32,0,40,2,156,158,6,65,2,116,106,34,3,32,28,32,3,42,2,0,34,30,67,0,0,0,63,148,146,56,2,0,32,0,65,1,54,2,160,158,6,32,0,32,0,40,2,156,158,6,65,1,106,65,195,4,111,34,3,65,195,4,106,32,3,32,3,65, - 0,72,27,54,2,156,158,6,32,30,32,28,147,67,0,0,0,0,146,33,35,11,2,64,2,64,32,0,40,2,164,158,6,65,127,71,13,0,67,0,0,0,0,33,30,67,0,0,0,0,33,33,12,1,11,32,0,65,1,54,2,164,158,6,32,33,32,32,67,0,0,0,0,146, - 148,34,30,32,29,67,0,0,0,0,146,34,33,67,0,0,128,63,32,31,67,0,0,0,0,146,34,28,147,148,34,34,32,37,148,32,33,32,28,67,0,0,128,63,146,148,34,28,32,35,148,146,146,67,0,0,0,0,146,33,33,32,30,32,28,32,37,148, - 32,34,32,35,148,146,146,67,0,0,0,0,146,33,30,11,32,1,32,0,40,2,0,65,3,116,106,34,3,65,132,16,106,32,33,67,0,0,0,0,146,56,2,0,32,3,65,128,16,106,32,30,67,0,0,0,0,146,56,2,0,32,0,32,0,40,2,0,65,1,106,34, - 3,54,2,0,32,3,32,2,71,13,0,11,11,32,0,65,0,54,2,0,11,11,142,128,128,128,0,1,0,65,0,11,8,0,0,0,0,0,0,0,0,0,202,129,128,128,0,7,108,105,110,107,105,110,103,2,8,160,129,128,128,0,8,0,32,0,19,95,115,101,110, - 100,69,118,101,110,116,95,114,111,111,109,83,105,122,101,1,2,12,46,76,95,102,114,101,113,117,101,110,99,121,0,0,8,0,32,1,18,95,115,101,110,100,69,118,101,110,116,95,100,97,109,112,105,110,103,0,32,2,16, - 95,115,101,110,100,69,118,101,110,116,95,119,105,100,116,104,0,32,3,19,95,115,101,110,100,69,118,101,110,116,95,119,101,116,76,101,118,101,108,0,32,4,19,95,115,101,110,100,69,118,101,110,116,95,100,114, - 121,76,101,118,101,108,0,32,5,10,105,110,105,116,105,97,108,105,115,101,0,32,6,12,97,100,118,97,110,99,101,66,108,111,99,107,5,149,128,128,128,0,1,17,46,98,115,115,46,46,76,95,102,114,101,113,117,101,110, - 99,121,3,0,0,170,128,128,128,0,10,114,101,108,111,99,46,67,79,68,69,5,6,3,83,1,0,3,129,2,1,0,3,169,3,1,0,3,213,4,1,0,3,252,5,1,0,3,228,6,1,0,]); - } -} diff --git a/Freeverb/README.md b/Freeverb/README.md deleted file mode 100644 index e7dc108a..00000000 --- a/Freeverb/README.md +++ /dev/null @@ -1,23 +0,0 @@ -### Auto-generated HTML & Javascript for Cmajor Patch "Freeverb" - -This folder contains some self-contained HTML/Javascript files that play and show a Cmajor -patch using WebAssembly and WebAudio. - -For `index.html` to display correctly, this folder needs to be served as HTTP, so if you're -running it locally, you'll need to start a webserver that serves this folder, and then -point your browser at whatever URL your webserver provides. For example, you could run -`python3 -m http.server` in this folder, and then browse to the address it chooses. - -The files have all been generated using the Cmajor command-line tool: -``` -cmaj generate --target=webaudio --output= -``` - -- `index.html` is a minimal page that creates the javascript object that implements the patch, - connects it to the default audio and MIDI devices, and displays its view. -- `Freeverb.js` - this is the Javascript wrapper class for the patch, encapsulating its - DSP as webassembly, and providing an API that is used to both render the audio and - control its properties. -- `cmaj_api` - this folder contains javascript helper modules and resources. - -To learn more about Cmajor, visit [cmajor.dev](cmajor.dev) diff --git a/Freeverb/cmaj_api/cmaj-event-listener-list.js b/Freeverb/cmaj_api/cmaj-event-listener-list.js deleted file mode 100644 index 0c13ea9e..00000000 --- a/Freeverb/cmaj_api/cmaj-event-listener-list.js +++ /dev/null @@ -1,112 +0,0 @@ -// -// ,ad888ba, 88 -// d8"' "8b -// d8 88,dba,,adba, ,aPP8A.A8 88 -// Y8, 88 88 88 88 88 88 -// Y8a. .a8P 88 88 88 88, ,88 88 (C)2024 Cmajor Software Ltd -// '"Y888Y"' 88 88 88 '"8bbP"Y8 88 https://cmajor.dev -// ,88 -// 888P" -// -// This file may be used under the terms of the ISC license: -// -// Permission to use, copy, modify, and/or distribute this software for any purpose with or -// without fee is hereby granted, provided that the above copyright notice and this permission -// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL -// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR -// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - -/** This event listener management class allows listeners to be attached and - * removed from named event types. - */ -export class EventListenerList -{ - constructor() - { - this.listenersPerType = {}; - } - - /** Adds a listener for a specifc event type. - * If the listener is already registered, this will simply add it again. - * Each call to addEventListener() must be paired with a removeventListener() - * call to remove it. - * - * @param {string} type - */ - addEventListener (type, listener) - { - if (type && listener) - { - const list = this.listenersPerType[type]; - - if (list) - list.push (listener); - else - this.listenersPerType[type] = [listener]; - } - } - - /** Removes a listener that was previously added for the given event type. - * @param {string} type - */ - removeEventListener (type, listener) - { - if (type && listener) - { - const list = this.listenersPerType[type]; - - if (list) - { - const i = list.indexOf (listener); - - if (i >= 0) - list.splice (i, 1); - } - } - } - - /** Attaches a callback function that will be automatically unregistered - * the first time it is invoked. - * - * @param {string} type - */ - addSingleUseListener (type, listener) - { - const l = message => - { - this.removeEventListener (type, l); - listener?.(message); - }; - - this.addEventListener (type, l); - } - - /** Synchronously dispatches an event object to all listeners - * that are registered for the given type. - * - * @param {string} type - */ - dispatchEvent (type, event) - { - const list = this.listenersPerType[type]; - - if (list) - for (const listener of list) - listener?.(event); - } - - /** Returns the number of listeners that are currently registered - * for the given type of event. - * - * @param {string} type - */ - getNumListenersForType (type) - { - const list = this.listenersPerType[type]; - return list ? list.length : 0; - } -} diff --git a/Freeverb/cmaj_api/cmaj-generic-patch-view.js b/Freeverb/cmaj_api/cmaj-generic-patch-view.js deleted file mode 100644 index ab8fa46a..00000000 --- a/Freeverb/cmaj_api/cmaj-generic-patch-view.js +++ /dev/null @@ -1,178 +0,0 @@ -// -// ,ad888ba, 88 -// d8"' "8b -// d8 88,dba,,adba, ,aPP8A.A8 88 -// Y8, 88 88 88 88 88 88 -// Y8a. .a8P 88 88 88 88, ,88 88 (C)2024 Cmajor Software Ltd -// '"Y888Y"' 88 88 88 '"8bbP"Y8 88 https://cmajor.dev -// ,88 -// 888P" -// -// This file may be used under the terms of the ISC license: -// -// Permission to use, copy, modify, and/or distribute this software for any purpose with or -// without fee is hereby granted, provided that the above copyright notice and this permission -// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL -// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR -// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -import * as Controls from "./cmaj-parameter-controls.js" - -//============================================================================== -/** A simple, generic view which can control any type of patch */ -class GenericPatchView extends HTMLElement -{ - /** Creates a view for a patch. - * @param {PatchConnection} patchConnection - the connection to the target patch - */ - constructor (patchConnection) - { - super(); - - this.patchConnection = patchConnection; - - this.statusListener = status => - { - this.status = status; - this.createControlElements(); - }; - - this.attachShadow ({ mode: "open" }); - this.shadowRoot.innerHTML = this.getHTML(); - - this.titleElement = this.shadowRoot.getElementById ("patch-title"); - this.parametersElement = this.shadowRoot.getElementById ("patch-parameters"); - } - - //============================================================================== - /** @private */ - connectedCallback() - { - this.patchConnection.addStatusListener (this.statusListener); - this.patchConnection.requestStatusUpdate(); - } - - /** @private */ - disconnectedCallback() - { - this.patchConnection.removeStatusListener (this.statusListener); - } - - /** @private */ - createControlElements() - { - this.parametersElement.innerHTML = ""; - this.titleElement.innerText = this.status?.manifest?.name ?? "Cmajor"; - - for (const endpointInfo of this.status?.details?.inputs) - { - if (! endpointInfo.annotation?.hidden) - { - const control = Controls.createLabelledControl (this.patchConnection, endpointInfo); - - if (control) - this.parametersElement.appendChild (control); - } - } - } - - /** @private */ - getHTML() - { - return ` - - -
-
- -

-
-
-
-
`; - } -} - -window.customElements.define ("cmaj-generic-patch-view", GenericPatchView); - -//============================================================================== -/** Creates a generic view element which can be used to control any patch. - * @param {PatchConnection} patchConnection - the connection to the target patch - */ -export default function createPatchView (patchConnection) -{ - return new GenericPatchView (patchConnection); -} diff --git a/Freeverb/cmaj_api/cmaj-midi-helpers.js b/Freeverb/cmaj_api/cmaj-midi-helpers.js deleted file mode 100644 index 1cc4933b..00000000 --- a/Freeverb/cmaj_api/cmaj-midi-helpers.js +++ /dev/null @@ -1,181 +0,0 @@ -// -// ,ad888ba, 88 -// d8"' "8b -// d8 88,dba,,adba, ,aPP8A.A8 88 -// Y8, 88 88 88 88 88 88 -// Y8a. .a8P 88 88 88 88, ,88 88 (C)2024 Cmajor Software Ltd -// '"Y888Y"' 88 88 88 '"8bbP"Y8 88 https://cmajor.dev -// ,88 -// 888P" -// -// This file may be used under the terms of the ISC license: -// -// Permission to use, copy, modify, and/or distribute this software for any purpose with or -// without fee is hereby granted, provided that the above copyright notice and this permission -// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL -// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR -// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - -export function getByte0 (message) { return (message >> 16) & 0xff; } -export function getByte1 (message) { return (message >> 8) & 0xff; } -export function getByte2 (message) { return message & 0xff; } - -function isVoiceMessage (message, type) { return ((message >> 16) & 0xf0) == type; } -function get14BitValue (message) { return getByte1 (message) | (getByte2 (message) << 7); } - -export function getChannel0to15 (message) { return getByte0 (message) & 0x0f; } -export function getChannel1to16 (message) { return getChannel0to15 (message) + 1; } - -export function getMessageSize (message) -{ - const mainGroupLengths = (3 << 0) | (3 << 2) | (3 << 4) | (3 << 6) - | (2 << 8) | (2 << 10) | (3 << 12); - - const lastGroupLengths = (1 << 0) | (2 << 2) | (3 << 4) | (2 << 6) - | (1 << 8) | (1 << 10) | (1 << 12) | (1 << 14) - | (1 << 16) | (1 << 18) | (1 << 20) | (1 << 22) - | (1 << 24) | (1 << 26) | (1 << 28) | (1 << 30); - - const firstByte = getByte0 (message); - const group = (firstByte >> 4) & 7; - - return (group != 7 ? (mainGroupLengths >> (2 * group)) - : (lastGroupLengths >> (2 * (firstByte & 15)))) & 3; -} - -export function isNoteOn (message) { return isVoiceMessage (message, 0x90) && getVelocity (message) != 0; } -export function isNoteOff (message) { return isVoiceMessage (message, 0x80) || (isVoiceMessage (message, 0x90) && getVelocity (message) == 0); } - -export function getNoteNumber (message) { return getByte1 (message); } -export function getVelocity (message) { return getByte2 (message); } - -export function isProgramChange (message) { return isVoiceMessage (message, 0xc0); } -export function getProgramChangeNumber (message) { return getByte1 (message); } -export function isPitchWheel (message) { return isVoiceMessage (message, 0xe0); } -export function getPitchWheelValue (message) { return get14BitValue (message); } -export function isAftertouch (message) { return isVoiceMessage (message, 0xa0); } -export function getAfterTouchValue (message) { return getByte2 (message); } -export function isChannelPressure (message) { return isVoiceMessage (message, 0xd0); } -export function getChannelPressureValue (message) { return getByte1 (message); } -export function isController (message) { return isVoiceMessage (message, 0xb0); } -export function getControllerNumber (message) { return getByte1 (message); } -export function getControllerValue (message) { return getByte2 (message); } -export function isControllerNumber (message, number) { return getByte1 (message) == number && isController (message); } -export function isAllNotesOff (message) { return isControllerNumber (message, 123); } -export function isAllSoundOff (message) { return isControllerNumber (message, 120); } -export function isQuarterFrame (message) { return getByte0 (message) == 0xf1; } -export function isClock (message) { return getByte0 (message) == 0xf8; } -export function isStart (message) { return getByte0 (message) == 0xfa; } -export function isContinue (message) { return getByte0 (message) == 0xfb; } -export function isStop (message) { return getByte0 (message) == 0xfc; } -export function isActiveSense (message) { return getByte0 (message) == 0xfe; } -export function isMetaEvent (message) { return getByte0 (message) == 0xff; } -export function isSongPositionPointer (message) { return getByte0 (message) == 0xf2; } -export function getSongPositionPointerValue (message) { return get14BitValue (message); } - -export function getChromaticScaleIndex (note) { return (note % 12) & 0xf; } -export function getOctaveNumber (note, octaveForMiddleC) { return ((Math.floor (note / 12) + (octaveForMiddleC ? octaveForMiddleC : 3)) & 0xff) - 5; } -export function getNoteName (note) { const names = ["C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "Bb", "B"]; return names[getChromaticScaleIndex (note)]; } -export function getNoteNameWithSharps (note) { const names = ["C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "Bb", "B"]; return names[getChromaticScaleIndex (note)]; } -export function getNoteNameWithFlats (note) { const names = ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"]; return names[getChromaticScaleIndex (note)]; } -export function getNoteNameWithOctaveNumber (note) { return getNoteName (note) + getOctaveNumber (note); } -export function isNatural (note) { const nats = [true, false, true, false, true, true, false, true, false, true, false, true]; return nats[getChromaticScaleIndex (note)]; } -export function isAccidental (note) { return ! isNatural (note); } - -export function printHexMIDIData (message) -{ - const numBytes = getMessageSize (message); - - if (numBytes == 0) - return "[empty]"; - - let s = ""; - - for (let i = 0; i < numBytes; ++i) - { - if (i != 0) s += ' '; - - const byte = message >> (16 - 8 * i) & 0xff; - s += "0123456789abcdef"[byte >> 4]; - s += "0123456789abcdef"[byte & 15]; - } - - return s; -} - -export function getMIDIDescription (message) -{ - const channelText = " Channel " + getChannel1to16 (message); - function getNote (m) { const s = getNoteNameWithOctaveNumber (getNoteNumber (message)); return s.length < 4 ? s + " " : s; }; - - if (isNoteOn (message)) return "Note-On: " + getNote (message) + channelText + " Velocity " + getVelocity (message); - if (isNoteOff (message)) return "Note-Off: " + getNote (message) + channelText + " Velocity " + getVelocity (message); - if (isAftertouch (message)) return "Aftertouch: " + getNote (message) + channelText + ": " + getAfterTouchValue (message); - if (isPitchWheel (message)) return "Pitch wheel: " + getPitchWheelValue (message) + ' ' + channelText; - if (isChannelPressure (message)) return "Channel pressure: " + getChannelPressureValue (message) + ' ' + channelText; - if (isController (message)) return "Controller:" + channelText + ": " + getControllerName (getControllerNumber (message)) + " = " + getControllerValue (message); - if (isProgramChange (message)) return "Program change: " + getProgramChangeNumber (message) + ' ' + channelText; - if (isAllNotesOff (message)) return "All notes off:" + channelText; - if (isAllSoundOff (message)) return "All sound off:" + channelText; - if (isQuarterFrame (message)) return "Quarter-frame"; - if (isClock (message)) return "Clock"; - if (isStart (message)) return "Start"; - if (isContinue (message)) return "Continue"; - if (isStop (message)) return "Stop"; - if (isMetaEvent (message)) return "Meta-event: type " + getByte1 (message); - if (isSongPositionPointer (message)) return "Song Position: " + getSongPositionPointerValue (message); - - return printHexMIDIData (message); -} - -export function getControllerName (controllerNumber) -{ - if (controllerNumber < 128) - { - const controllerNames = [ - "Bank Select", "Modulation Wheel (coarse)", "Breath controller (coarse)", undefined, - "Foot Pedal (coarse)", "Portamento Time (coarse)", "Data Entry (coarse)", "Volume (coarse)", - "Balance (coarse)", undefined, "Pan position (coarse)", "Expression (coarse)", - "Effect Control 1 (coarse)", "Effect Control 2 (coarse)", undefined, undefined, - "General Purpose Slider 1", "General Purpose Slider 2", "General Purpose Slider 3", "General Purpose Slider 4", - undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, - "Bank Select (fine)", "Modulation Wheel (fine)", "Breath controller (fine)", undefined, - "Foot Pedal (fine)", "Portamento Time (fine)", "Data Entry (fine)", "Volume (fine)", - "Balance (fine)", undefined, "Pan position (fine)", "Expression (fine)", - "Effect Control 1 (fine)", "Effect Control 2 (fine)", undefined, undefined, - undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, - "Hold Pedal", "Portamento", "Sustenuto Pedal", "Soft Pedal", - "Legato Pedal", "Hold 2 Pedal", "Sound Variation", "Sound Timbre", - "Sound Release Time", "Sound Attack Time", "Sound Brightness", "Sound Control 6", - "Sound Control 7", "Sound Control 8", "Sound Control 9", "Sound Control 10", - "General Purpose Button 1", "General Purpose Button 2", "General Purpose Button 3", "General Purpose Button 4", - undefined, undefined, undefined, undefined, - undefined, undefined, undefined, "Reverb Level", - "Tremolo Level", "Chorus Level", "Celeste Level", "Phaser Level", - "Data Button increment", "Data Button decrement", "Non-registered Parameter (fine)", "Non-registered Parameter (coarse)", - "Registered Parameter (fine)", "Registered Parameter (coarse)", undefined, undefined, - undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, - "All Sound Off", "All Controllers Off", "Local Keyboard", "All Notes Off", - "Omni Mode Off", "Omni Mode On", "Mono Operation", "Poly Operation" - ]; - - const name = controllerNames[controllerNumber]; - - if (name) - return name; - } - - return controllerNumber.toString(); -} diff --git a/Freeverb/cmaj_api/cmaj-parameter-controls.js b/Freeverb/cmaj_api/cmaj-parameter-controls.js deleted file mode 100644 index c6290d05..00000000 --- a/Freeverb/cmaj_api/cmaj-parameter-controls.js +++ /dev/null @@ -1,844 +0,0 @@ -// -// ,ad888ba, 88 -// d8"' "8b -// d8 88,dba,,adba, ,aPP8A.A8 88 -// Y8, 88 88 88 88 88 88 -// Y8a. .a8P 88 88 88 88, ,88 88 (C)2024 Cmajor Software Ltd -// '"Y888Y"' 88 88 88 '"8bbP"Y8 88 https://cmajor.dev -// ,88 -// 888P" -// -// This file may be used under the terms of the ISC license: -// -// Permission to use, copy, modify, and/or distribute this software for any purpose with or -// without fee is hereby granted, provided that the above copyright notice and this permission -// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL -// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR -// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -import { PatchConnection } from "./cmaj-patch-connection.js"; - - -//============================================================================== -/** A base class for parameter controls, which automatically connects to a - * PatchConnection to monitor a parameter and provides methods to modify it. - */ -export class ParameterControlBase extends HTMLElement -{ - constructor() - { - super(); - - // prevent any clicks from focusing on this element - this.onmousedown = e => e.stopPropagation(); - } - - /** Attaches the control to a given PatchConnection and endpoint. - * - * @param {PatchConnection} patchConnection - the connection to connect to, or pass - * undefined to disconnect the control. - * @param {Object} endpointInfo - the endpoint details, as provided by a PatchConnection - * in its status callback. - */ - setEndpoint (patchConnection, endpointInfo) - { - this.detachListener(); - - this.patchConnection = patchConnection; - this.endpointInfo = endpointInfo; - this.defaultValue = endpointInfo.annotation?.init || endpointInfo.defaultValue || 0; - - if (this.isConnected) - this.attachListener(); - } - - /** Override this method in a child class, and it will be called when the parameter value changes, - * so you can update the GUI appropriately. - */ - valueChanged (newValue) {} - - /** Your GUI can call this when it wants to change the parameter value. */ - setValue (value) { this.patchConnection?.sendEventOrValue (this.endpointInfo.endpointID, value); } - - /** Call this before your GUI begins a modification gesture. - * You might for example call this if the user begins a mouse-drag operation. - */ - beginGesture() { this.patchConnection?.sendParameterGestureStart (this.endpointInfo.endpointID); } - - /** Call this after your GUI finishes a modification gesture */ - endGesture() { this.patchConnection?.sendParameterGestureEnd (this.endpointInfo.endpointID); } - - /** This calls setValue(), but sandwiches it between some start/end gesture calls. - * You should use this to make sure a DAW correctly records automatiion for individual value changes - * that are not part of a gesture. - */ - setValueAsGesture (value) - { - this.beginGesture(); - this.setValue (value); - this.endGesture(); - } - - /** Resets the parameter to its default value */ - resetToDefault() - { - if (this.defaultValue !== null) - this.setValueAsGesture (this.defaultValue); - } - - //============================================================================== - /** @private */ - connectedCallback() - { - this.attachListener(); - } - - /** @protected */ - disconnectedCallback() - { - this.detachListener(); - } - - /** @private */ - detachListener() - { - if (this.listener) - { - this.patchConnection?.removeParameterListener?.(this.listener.endpointID, this.listener); - this.listener = undefined; - } - } - - /** @private */ - attachListener() - { - if (this.patchConnection && this.endpointInfo) - { - this.detachListener(); - - this.listener = newValue => this.valueChanged (newValue); - this.listener.endpointID = this.endpointInfo.endpointID; - - this.patchConnection.addParameterListener (this.endpointInfo.endpointID, this.listener); - this.patchConnection.requestParameterValue (this.endpointInfo.endpointID); - } - } -} - -//============================================================================== -/** A simple rotary parameter knob control. */ -export class Knob extends ParameterControlBase -{ - constructor (patchConnection, endpointInfo) - { - super(); - this.setEndpoint (patchConnection, endpointInfo); - } - - setEndpoint (patchConnection, endpointInfo) - { - super.setEndpoint (patchConnection, endpointInfo); - - this.innerHTML = ""; - this.className = "knob-container"; - const min = endpointInfo?.annotation?.min || 0; - const max = endpointInfo?.annotation?.max || 1; - - const createSvgElement = tag => window.document.createElementNS ("http://www.w3.org/2000/svg", tag); - - const svg = createSvgElement ("svg"); - svg.setAttribute ("viewBox", "0 0 100 100"); - - const trackBackground = createSvgElement ("path"); - trackBackground.setAttribute ("d", "M20,76 A 40 40 0 1 1 80 76"); - trackBackground.classList.add ("knob-path"); - trackBackground.classList.add ("knob-track-background"); - - const maxKnobRotation = 132; - const isBipolar = min + max === 0; - const dashLength = isBipolar ? 251.5 : 184; - const valueOffset = isBipolar ? 0 : 132; - this.getDashOffset = val => dashLength - 184 / (maxKnobRotation * 2) * (val + valueOffset); - - this.trackValue = createSvgElement ("path"); - - this.trackValue.setAttribute ("d", isBipolar ? "M50.01,10 A 40 40 0 1 1 50 10" - : "M20,76 A 40 40 0 1 1 80 76"); - this.trackValue.setAttribute ("stroke-dasharray", dashLength); - this.trackValue.classList.add ("knob-path"); - this.trackValue.classList.add ("knob-track-value"); - - this.dial = document.createElement ("div"); - this.dial.className = "knob-dial"; - - const dialTick = document.createElement ("div"); - dialTick.className = "knob-dial-tick"; - this.dial.appendChild (dialTick); - - svg.appendChild (trackBackground); - svg.appendChild (this.trackValue); - - this.appendChild (svg); - this.appendChild (this.dial); - - const remap = (source, sourceFrom, sourceTo, targetFrom, targetTo) => - (targetFrom + (source - sourceFrom) * (targetTo - targetFrom) / (sourceTo - sourceFrom)); - - const toValue = (knobRotation) => remap (knobRotation, -maxKnobRotation, maxKnobRotation, min, max); - this.toRotation = (value) => remap (value, min, max, -maxKnobRotation, maxKnobRotation); - - this.rotation = this.toRotation (this.defaultValue); - this.setRotation (this.rotation, true); - - const onMouseMove = (event) => - { - event.preventDefault(); // avoid scrolling whilst dragging - - const nextRotation = (rotation, delta) => - { - const clamp = (v, min, max) => Math.min (Math.max (v, min), max); - return clamp (rotation - delta, -maxKnobRotation, maxKnobRotation); - }; - - const workaroundBrowserIncorrectlyCalculatingMovementY = event.movementY === event.screenY; - const movementY = workaroundBrowserIncorrectlyCalculatingMovementY ? event.screenY - this.previousScreenY - : event.movementY; - this.previousScreenY = event.screenY; - - const speedMultiplier = event.shiftKey ? 0.25 : 1.5; - this.accumulatedRotation = nextRotation (this.accumulatedRotation, movementY * speedMultiplier); - this.setValue (toValue (this.accumulatedRotation)); - }; - - const onMouseUp = (event) => - { - this.previousScreenY = undefined; - this.accumulatedRotation = undefined; - window.removeEventListener ("mousemove", onMouseMove); - window.removeEventListener ("mouseup", onMouseUp); - this.endGesture(); - }; - - const onMouseDown = (event) => - { - this.previousScreenY = event.screenY; - this.accumulatedRotation = this.rotation; - this.beginGesture(); - window.addEventListener ("mousemove", onMouseMove); - window.addEventListener ("mouseup", onMouseUp); - event.preventDefault(); - }; - - const onTouchStart = (event) => - { - this.previousClientY = event.changedTouches[0].clientY; - this.accumulatedRotation = this.rotation; - this.touchIdentifier = event.changedTouches[0].identifier; - this.beginGesture(); - window.addEventListener ("touchmove", onTouchMove); - window.addEventListener ("touchend", onTouchEnd); - event.preventDefault(); - }; - - const onTouchMove = (event) => - { - for (const touch of event.changedTouches) - { - if (touch.identifier == this.touchIdentifier) - { - const nextRotation = (rotation, delta) => - { - const clamp = (v, min, max) => Math.min (Math.max (v, min), max); - return clamp (rotation - delta, -maxKnobRotation, maxKnobRotation); - }; - - const movementY = touch.clientY - this.previousClientY; - this.previousClientY = touch.clientY; - - const speedMultiplier = event.shiftKey ? 0.25 : 1.5; - this.accumulatedRotation = nextRotation (this.accumulatedRotation, movementY * speedMultiplier); - this.setValue (toValue (this.accumulatedRotation)); - } - } - }; - - const onTouchEnd = (event) => - { - this.previousClientY = undefined; - this.accumulatedRotation = undefined; - window.removeEventListener ("touchmove", onTouchMove); - window.removeEventListener ("touchend", onTouchEnd); - this.endGesture(); - }; - - this.addEventListener ("mousedown", onMouseDown); - this.addEventListener ("dblclick", () => this.resetToDefault()); - this.addEventListener ('touchstart', onTouchStart); - } - - /** Returns true if this type of control is suitable for the given endpoint info */ - static canBeUsedFor (endpointInfo) - { - return endpointInfo.purpose === "parameter"; - } - - /** @override */ - valueChanged (newValue) { this.setRotation (this.toRotation (newValue), false); } - - /** Returns a string version of the given value */ - getDisplayValue (v) { return toFloatDisplayValueWithUnit (v, this.endpointInfo); } - - /** @private */ - setRotation (degrees, force) - { - if (force || this.rotation !== degrees) - { - this.rotation = degrees; - this.trackValue.setAttribute ("stroke-dashoffset", this.getDashOffset (this.rotation)); - this.dial.style.transform = `translate(-50%,-50%) rotate(${degrees}deg)`; - } - } - - /** @private */ - static getCSS() - { - return ` - .knob-container { - --knob-track-background-color: var(--background); - --knob-track-value-color: var(--foreground); - - --knob-dial-border-color: var(--foreground); - --knob-dial-background-color: var(--background); - --knob-dial-tick-color: var(--foreground); - - position: relative; - display: inline-block; - height: 5rem; - width: 5rem; - margin: 0; - padding: 0; - } - - .knob-path { - fill: none; - stroke-linecap: round; - stroke-width: 0.15rem; - } - - .knob-track-background { - stroke: var(--knob-track-background-color); - } - - .knob-track-value { - stroke: var(--knob-track-value-color); - } - - .knob-dial { - position: absolute; - text-align: center; - height: 60%; - width: 60%; - top: 50%; - left: 50%; - border: 0.15rem solid var(--knob-dial-border-color); - border-radius: 100%; - box-sizing: border-box; - transform: translate(-50%,-50%); - background-color: var(--knob-dial-background-color); - } - - .knob-dial-tick { - position: absolute; - display: inline-block; - - height: 1rem; - width: 0.15rem; - background-color: var(--knob-dial-tick-color); - }`; - } -} - -//============================================================================== -/** A boolean switch control */ -export class Switch extends ParameterControlBase -{ - constructor (patchConnection, endpointInfo) - { - super(); - this.setEndpoint (patchConnection, endpointInfo); - } - - setEndpoint (patchConnection, endpointInfo) - { - super.setEndpoint (patchConnection, endpointInfo); - - const outer = document.createElement ("div"); - outer.classList = "switch-outline"; - - const inner = document.createElement ("div"); - inner.classList = "switch-thumb"; - - this.innerHTML = ""; - this.currentValue = this.defaultValue > 0.5; - this.valueChanged (this.currentValue); - this.classList.add ("switch-container"); - - outer.appendChild (inner); - this.appendChild (outer); - this.addEventListener ("click", () => this.setValueAsGesture (this.currentValue ? 0 : 1.0)); - } - - /** Returns true if this type of control is suitable for the given endpoint info */ - static canBeUsedFor (endpointInfo) - { - return endpointInfo.purpose === "parameter" - && endpointInfo.annotation?.boolean; - } - - /** @override */ - valueChanged (newValue) - { - const b = newValue > 0.5; - this.currentValue = b; - this.classList.remove (! b ? "switch-on" : "switch-off"); - this.classList.add (b ? "switch-on" : "switch-off"); - } - - /** Returns a string version of the given value */ - getDisplayValue (v) { return `${v > 0.5 ? "On" : "Off"}`; } - - /** @private */ - static getCSS() - { - return ` - .switch-container { - --switch-outline-color: var(--foreground); - --switch-thumb-color: var(--foreground); - --switch-on-background-color: var(--background); - --switch-off-background-color: var(--background); - - position: relative; - display: flex; - align-items: center; - justify-content: center; - height: 100%; - width: 100%; - margin: 0; - padding: 0; - } - - .switch-outline { - position: relative; - display: inline-block; - height: 1.25rem; - width: 2.5rem; - border-radius: 10rem; - box-shadow: 0 0 0 0.15rem var(--switch-outline-color); - transition: background-color 0.1s cubic-bezier(0.5, 0, 0.2, 1); - } - - .switch-thumb { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%,-50%); - height: 1rem; - width: 1rem; - background-color: var(--switch-thumb-color); - border-radius: 100%; - transition: left 0.1s cubic-bezier(0.5, 0, 0.2, 1); - } - - .switch-off .switch-thumb { - left: 25%; - background: none; - border: var(--switch-thumb-color) solid 0.1rem; - height: 0.8rem; - width: 0.8rem; - } - .switch-on .switch-thumb { - left: 75%; - } - - .switch-off .switch-outline { - background-color: var(--switch-on-background-color); - } - .switch-on .switch-outline { - background-color: var(--switch-off-background-color); - }`; - } -} - -//============================================================================== -function toFloatDisplayValueWithUnit (v, endpointInfo) -{ - return `${v.toFixed (2)} ${endpointInfo.annotation?.unit ?? ""}`; -} - -//============================================================================== -/** A control that allows an item to be selected from a drop-down list of options */ -export class Options extends ParameterControlBase -{ - constructor (patchConnection, endpointInfo) - { - super(); - this.setEndpoint (patchConnection, endpointInfo); - } - - setEndpoint (patchConnection, endpointInfo) - { - super.setEndpoint (patchConnection, endpointInfo); - - const toValue = (min, step, index) => min + (step * index); - const toStepCount = count => count > 0 ? count - 1 : 1; - - const { min, max, options } = (() => - { - if (Options.hasTextOptions (endpointInfo)) - { - const optionList = endpointInfo.annotation.text.split ("|"); - const stepCount = toStepCount (optionList.length); - let min = 0, max = stepCount, step = 1; - - if (endpointInfo.annotation.min != null && endpointInfo.annotation.max != null) - { - min = endpointInfo.annotation.min; - max = endpointInfo.annotation.max; - step = (max - min) / stepCount; - } - - const options = optionList.map ((text, index) => ({ value: toValue (min, step, index), text })); - - return { min, max, options }; - } - - if (Options.isExplicitlyDiscrete (endpointInfo)) - { - const step = endpointInfo.annotation.step; - - const min = endpointInfo.annotation?.min || 0; - const max = endpointInfo.annotation?.max || 1; - - const numDiscreteOptions = (((max - min) / step) | 0) + 1; - - const options = new Array (numDiscreteOptions); - for (let i = 0; i < numDiscreteOptions; ++i) - { - const value = toValue (min, step, i); - options[i] = { value, text: toFloatDisplayValueWithUnit (value, endpointInfo) }; - } - - return { min, max, options }; - } - })(); - - this.options = options; - - const stepCount = toStepCount (this.options.length); - const normalise = value => (value - min) / (max - min); - this.toIndex = value => Math.min (stepCount, normalise (value) * this.options.length) | 0; - - this.innerHTML = ""; - - this.select = document.createElement ("select"); - - for (const option of this.options) - { - const optionElement = document.createElement ("option"); - optionElement.innerText = option.text; - this.select.appendChild (optionElement); - } - - this.selectedIndex = this.toIndex (this.defaultValue); - - this.select.addEventListener ("change", (e) => - { - const newIndex = e.target.selectedIndex; - - // prevent local state change. the caller will update us when the backend actually applies the update - e.target.selectedIndex = this.selectedIndex; - - this.setValueAsGesture (this.options[newIndex].value) - }); - - this.valueChanged (this.selectedIndex); - - this.className = "select-container"; - this.appendChild (this.select); - - const icon = document.createElement ("span"); - icon.className = "select-icon"; - this.appendChild (icon); - } - - /** Returns true if this type of control is suitable for the given endpoint info */ - static canBeUsedFor (endpointInfo) - { - return endpointInfo.purpose === "parameter" - && (this.hasTextOptions (endpointInfo) || this.isExplicitlyDiscrete (endpointInfo)); - } - - /** @override */ - valueChanged (newValue) - { - const index = this.toIndex (newValue); - this.selectedIndex = index; - this.select.selectedIndex = index; - } - - /** Returns a string version of the given value */ - getDisplayValue (v) { return this.options[this.toIndex(v)].text; } - - /** @private */ - static hasTextOptions (endpointInfo) - { - return endpointInfo.annotation?.text?.split?.("|").length > 1 - } - - /** @private */ - static isExplicitlyDiscrete (endpointInfo) - { - return endpointInfo.annotation?.discrete && endpointInfo.annotation?.step > 0; - } - - /** @private */ - static getCSS() - { - return ` - .select-container { - position: relative; - display: block; - font-size: 0.8rem; - width: 100%; - color: var(--foreground); - border: 0.15rem solid var(--foreground); - border-radius: 0.6rem; - margin: 0; - padding: 0; - } - - select { - background: none; - appearance: none; - -webkit-appearance: none; - font-family: inherit; - font-size: 0.8rem; - - overflow: hidden; - text-overflow: ellipsis; - - padding: 0 1.5rem 0 0.6rem; - - outline: none; - color: var(--foreground); - height: 2rem; - box-sizing: border-box; - margin: 0; - border: none; - - width: 100%; - } - - select option { - background: var(--background); - color: var(--foreground); - } - - .select-icon { - position: absolute; - right: 0.3rem; - top: 0.5rem; - pointer-events: none; - background-color: var(--foreground); - width: 1.4em; - height: 1.4em; - mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17,9.17a1,1,0,0,0-1.41,0L12,12.71,8.46,9.17a1,1,0,0,0-1.41,0,1,1,0,0,0,0,1.42l4.24,4.24a1,1,0,0,0,1.42,0L17,10.59A1,1,0,0,0,17,9.17Z'/%3E%3C/svg%3E"); - mask-repeat: no-repeat; - -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M17,9.17a1,1,0,0,0-1.41,0L12,12.71,8.46,9.17a1,1,0,0,0-1.41,0,1,1,0,0,0,0,1.42l4.24,4.24a1,1,0,0,0,1.42,0L17,10.59A1,1,0,0,0,17,9.17Z'/%3E%3C/svg%3E"); - -webkit-mask-repeat: no-repeat; - }`; - } -} - -//============================================================================== -/** A control which wraps a child control, adding a label and value display box below it */ -export class LabelledControlHolder extends ParameterControlBase -{ - constructor (patchConnection, endpointInfo, childControl) - { - super(); - this.childControl = childControl; - this.setEndpoint (patchConnection, endpointInfo); - } - - setEndpoint (patchConnection, endpointInfo) - { - super.setEndpoint (patchConnection, endpointInfo); - - this.innerHTML = ""; - this.className = "labelled-control"; - - const centeredControl = document.createElement ("div"); - centeredControl.className = "labelled-control-centered-control"; - - centeredControl.appendChild (this.childControl); - - const titleValueHoverContainer = document.createElement ("div"); - titleValueHoverContainer.className = "labelled-control-label-container"; - - const nameText = document.createElement ("div"); - nameText.classList.add ("labelled-control-name"); - nameText.innerText = endpointInfo.annotation?.name || endpointInfo.name || endpointInfo.endpointID || ""; - - this.valueText = document.createElement ("div"); - this.valueText.classList.add ("labelled-control-value"); - - titleValueHoverContainer.appendChild (nameText); - titleValueHoverContainer.appendChild (this.valueText); - - this.appendChild (centeredControl); - this.appendChild (titleValueHoverContainer); - } - - /** @override */ - valueChanged (newValue) - { - this.valueText.innerText = this.childControl?.getDisplayValue (newValue); - } - - /** @private */ - static getCSS() - { - return ` - .labelled-control { - --labelled-control-font-color: var(--foreground); - --labelled-control-font-size: 0.8rem; - - position: relative; - display: inline-block; - margin: 0 0.4rem 0.4rem; - vertical-align: top; - text-align: left; - padding: 0; - } - - .labelled-control-centered-control { - position: relative; - display: flex; - align-items: center; - justify-content: center; - - width: 5.5rem; - height: 5rem; - } - - .labelled-control-label-container { - position: relative; - display: block; - max-width: 5.5rem; - margin: -0.4rem auto 0.4rem; - text-align: center; - font-size: var(--labelled-control-font-size); - color: var(--labelled-control-font-color); - cursor: default; - } - - .labelled-control-name { - overflow: hidden; - text-overflow: ellipsis; - } - - .labelled-control-value { - position: absolute; - top: 0; - left: 0; - right: 0; - overflow: hidden; - text-overflow: ellipsis; - opacity: 0; - } - - .labelled-control:hover .labelled-control-name, - .labelled-control:active .labelled-control-name { - opacity: 0; - } - .labelled-control:hover .labelled-control-value, - .labelled-control:active .labelled-control-value { - opacity: 1; - }`; - } -} - -window.customElements.define ("cmaj-knob-control", Knob); -window.customElements.define ("cmaj-switch-control", Switch); -window.customElements.define ("cmaj-options-control", Options); -window.customElements.define ("cmaj-labelled-control-holder", LabelledControlHolder); - -//============================================================================== -/** Fetches all the CSS for the controls defined in this module */ -export function getAllCSS() -{ - return ` - ${Options.getCSS()} - ${Knob.getCSS()} - ${Switch.getCSS()} - ${LabelledControlHolder.getCSS()}`; -} - -//============================================================================== -/** Creates a suitable control for the given endpoint. - * - * @param {PatchConnection} patchConnection - the connection to connect to - * @param {Object} endpointInfo - the endpoint details, as provided by a PatchConnection - * in its status callback. -*/ -export function createControl (patchConnection, endpointInfo) -{ - if (Switch.canBeUsedFor (endpointInfo)) - return new Switch (patchConnection, endpointInfo); - - if (Options.canBeUsedFor (endpointInfo)) - return new Options (patchConnection, endpointInfo); - - if (Knob.canBeUsedFor (endpointInfo)) - return new Knob (patchConnection, endpointInfo); - - return undefined; -} - -//============================================================================== -/** Creates a suitable labelled control for the given endpoint. - * - * @param {PatchConnection} patchConnection - the connection to connect to - * @param {Object} endpointInfo - the endpoint details, as provided by a PatchConnection - * in its status callback. -*/ -export function createLabelledControl (patchConnection, endpointInfo) -{ - const control = createControl (patchConnection, endpointInfo); - - if (control) - return new LabelledControlHolder (patchConnection, endpointInfo, control); - - return undefined; -} - -//============================================================================== -/** Takes a patch connection and its current status object, and tries to create - * a control for the given endpoint ID. - * - * @param {PatchConnection} patchConnection - the connection to connect to - * @param {Object} status - the connection's current status - * @param {string} endpointID - the endpoint you'd like to control - */ -export function createLabelledControlForEndpointID (patchConnection, status, endpointID) -{ - for (const endpointInfo of status?.details?.inputs) - if (endpointInfo.endpointID == endpointID) - return createLabelledControl (patchConnection, endpointInfo); - - return undefined; -} diff --git a/Freeverb/cmaj_api/cmaj-patch-connection.js b/Freeverb/cmaj_api/cmaj-patch-connection.js deleted file mode 100644 index 2fff73c5..00000000 --- a/Freeverb/cmaj_api/cmaj-patch-connection.js +++ /dev/null @@ -1,215 +0,0 @@ -// -// ,ad888ba, 88 -// d8"' "8b -// d8 88,dba,,adba, ,aPP8A.A8 88 -// Y8, 88 88 88 88 88 88 -// Y8a. .a8P 88 88 88 88, ,88 88 (C)2024 Cmajor Software Ltd -// '"Y888Y"' 88 88 88 '"8bbP"Y8 88 https://cmajor.dev -// ,88 -// 888P" -// -// This file may be used under the terms of the ISC license: -// -// Permission to use, copy, modify, and/or distribute this software for any purpose with or -// without fee is hereby granted, provided that the above copyright notice and this permission -// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL -// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR -// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -import { EventListenerList } from "./cmaj-event-listener-list.js" - -//============================================================================== -/** This class implements the API and much of the logic for communicating with - * an instance of a patch that is running. - */ -export class PatchConnection extends EventListenerList -{ - constructor() - { - super(); - } - - //============================================================================== - // Status-handling methods: - - /** Calling this will trigger an asynchronous callback to any status listeners with the - * patch's current state. Use addStatusListener() to attach a listener to receive it. - */ - requestStatusUpdate() { this.sendMessageToServer ({ type: "req_status" }); } - - /** Attaches a listener function that will be called whenever the patch's status changes. - * The function will be called with a parameter object containing many properties describing the status, - * including whether the patch is loaded, any errors, endpoint descriptions, its manifest, etc. - */ - addStatusListener (listener) { this.addEventListener ("status", listener); } - - /** Removes a listener that was previously added with addStatusListener() - */ - removeStatusListener (listener) { this.removeEventListener ("status", listener); } - - /** Causes the patch to be reset to its "just loaded" state. */ - resetToInitialState() { this.sendMessageToServer ({ type: "req_reset" }); } - - //============================================================================== - // Methods for sending data to input endpoints: - - /** Sends a value to one of the patch's input endpoints. - * - * This can be used to send a value to either an 'event' or 'value' type input endpoint. - * If the endpoint is a 'value' type, then the rampFrames parameter can optionally be used to specify - * the number of frames over which the current value should ramp to the new target one. - * The value parameter will be coerced to the type that is expected by the endpoint. So for - * examples, numbers will be converted to float or integer types, javascript objects and arrays - * will be converted into more complex types in as good a fashion is possible. - */ - sendEventOrValue (endpointID, value, rampFrames, timeoutMillisecs) { this.sendMessageToServer ({ type: "send_value", id: endpointID, value, rampFrames, timeout: timeoutMillisecs }); } - - /** Sends a short MIDI message value to a MIDI endpoint. - * The value must be a number encoded with `(byte0 << 16) | (byte1 << 8) | byte2`. - */ - sendMIDIInputEvent (endpointID, shortMIDICode) { this.sendEventOrValue (endpointID, { message: shortMIDICode }); } - - /** Tells the patch that a series of changes that constitute a gesture is about to take place - * for the given endpoint. Remember to call sendParameterGestureEnd() after they're done! - */ - sendParameterGestureStart (endpointID) { this.sendMessageToServer ({ type: "send_gesture_start", id: endpointID }); } - - /** Tells the patch that a gesture started by sendParameterGestureStart() has finished. - */ - sendParameterGestureEnd (endpointID) { this.sendMessageToServer ({ type: "send_gesture_end", id: endpointID }); } - - //============================================================================== - // Stored state control methods: - - /** Requests a callback to any stored-state value listeners with the current value of a given key-value pair. - * To attach a listener to receive these events, use addStoredStateValueListener(). - * @param {string} key - */ - requestStoredStateValue (key) { this.sendMessageToServer ({ type: "req_state_value", key: key }); } - - /** Modifies a key-value pair in the patch's stored state. - * @param {string} key - * @param {Object} newValue - */ - sendStoredStateValue (key, newValue) { this.sendMessageToServer ({ type: "send_state_value", key: key, value: newValue }); } - - /** Attaches a listener function that will be called when any key-value pair in the stored state is changed. - * The listener function will receive a message parameter with properties 'key' and 'value'. - */ - addStoredStateValueListener (listener) { this.addEventListener ("state_key_value", listener); } - - /** Removes a listener that was previously added with addStoredStateValueListener(). - */ - removeStoredStateValueListener (listener) { this.removeEventListener ("state_key_value", listener); } - - /** Applies a complete stored state to the patch. - * To get the current complete state, use requestFullStoredState(). - */ - sendFullStoredState (fullState) { this.sendMessageToServer ({ type: "send_full_state", value: fullState }); } - - /** Asynchronously requests the full stored state of the patch. - * The listener function that is supplied will be called asynchronously with the state as its argument. - */ - requestFullStoredState (callback) - { - const replyType = "fullstate_response_" + (Math.floor (Math.random() * 100000000)).toString(); - this.addSingleUseListener (replyType, callback); - this.sendMessageToServer ({ type: "req_full_state", replyType: replyType }); - } - - //============================================================================== - // Listener methods: - - /** Attaches a listener function that will receive updates with the events or audio data - * that is being sent or received by an endpoint. - * - * If the endpoint is an event or value, the callback will be given an argument which is - * the new value. - * - * If the endpoint has the right shape to be treated as "audio" then the callback will receive - * a stream of updates of the min/max range of chunks of data that is flowing through it. - * There will be one callback per chunk of data, and the size of chunks is specified by - * the optional granularity parameter. - * - * @param {string} endpointID - * @param {number} granularity - if defined, this specifies the number of frames per callback - * @param {boolean} sendFullAudioData - if false, the listener will receive an argument object containing - * two properties 'min' and 'max', which are each an array of values, one element per audio - * channel. This allows you to find the highest and lowest samples in that chunk for each channel. - * If sendFullAudioData is true, the listener's argument will have a property 'data' which is an - * array containing one array per channel of raw audio samples data. - */ - addEndpointListener (endpointID, listener, granularity, sendFullAudioData) - { - listener.eventID = "event_" + endpointID + "_" + (Math.floor (Math.random() * 100000000)).toString(); - this.addEventListener (listener.eventID, listener); - this.sendMessageToServer ({ type: "add_endpoint_listener", endpoint: endpointID, replyType: - listener.eventID, granularity: granularity, fullAudioData: sendFullAudioData }); - } - - /** Removes a listener that was previously added with addEndpointListener() - * @param {string} endpointID - */ - removeEndpointListener (endpointID, listener) - { - this.removeEventListener (listener.eventID, listener); - this.sendMessageToServer ({ type: "remove_endpoint_listener", endpoint: endpointID, replyType: listener.eventID }); - } - - /** This will trigger an asynchronous callback to any parameter listeners that are - * attached, providing them with its up-to-date current value for the given endpoint. - * Use addAllParameterListener() to attach a listener to receive the result. - * @param {string} endpointID - */ - requestParameterValue (endpointID) { this.sendMessageToServer ({ type: "req_param_value", id: endpointID }); } - - /** Attaches a listener function which will be called whenever the value of a specific parameter changes. - * The listener function will be called with an argument which is the new value. - * @param {string} endpointID - */ - addParameterListener (endpointID, listener) { this.addEventListener ("param_value_" + endpointID.toString(), listener); } - - /** Removes a listener that was previously added with addParameterListener() - * @param {string} endpointID - */ - removeParameterListener (endpointID, listener) { this.removeEventListener ("param_value_" + endpointID.toString(), listener); } - - /** Attaches a listener function which will be called whenever the value of any parameter changes in the patch. - * The listener function will be called with an argument object with the fields 'endpointID' and 'value'. - */ - addAllParameterListener (listener) { this.addEventListener ("param_value", listener); } - - /** Removes a listener that was previously added with addAllParameterListener() - */ - removeAllParameterListener (listener) { this.removeEventListener ("param_value", listener); } - - /** This takes a relative path to an asset within the patch bundle, and converts it to a - * path relative to the root of the browser that is showing the view. - * - * You need you use this in your view code to translate your asset URLs to a form that - * can be safely used in your view's HTML DOM (e.g. in its CSS). This is needed because the - * host's HTTP server (which is delivering your view pages) may have a different '/' root - * than the root of your patch (e.g. if a single server is serving multiple patch GUIs). - * - * @param {string} path - */ - getResourceAddress (path) { return path; } - - //============================================================================== - // Private methods follow this point.. - - /** @private */ - deliverMessageFromServer (msg) - { - if (msg.type === "status") - this.manifest = msg.message?.manifest; - - if (msg.type == "param_value") - this.dispatchEvent ("param_value_" + msg.message.endpointID, msg.message.value); - - this.dispatchEvent (msg.type, msg.message); - } -} diff --git a/Freeverb/cmaj_api/cmaj-patch-view.js b/Freeverb/cmaj_api/cmaj-patch-view.js deleted file mode 100644 index 8052a30e..00000000 --- a/Freeverb/cmaj_api/cmaj-patch-view.js +++ /dev/null @@ -1,125 +0,0 @@ -// -// ,ad888ba, 88 -// d8"' "8b -// d8 88,dba,,adba, ,aPP8A.A8 88 -// Y8, 88 88 88 88 88 88 -// Y8a. .a8P 88 88 88 88, ,88 88 (C)2024 Cmajor Software Ltd -// '"Y888Y"' 88 88 88 '"8bbP"Y8 88 https://cmajor.dev -// ,88 -// 888P" -// -// This file may be used under the terms of the ISC license: -// -// Permission to use, copy, modify, and/or distribute this software for any purpose with or -// without fee is hereby granted, provided that the above copyright notice and this permission -// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL -// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR -// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -import { PatchConnection } from "./cmaj-patch-connection.js" - -/** Returns a list of types of view that can be created for this patch. - */ -export function getAvailableViewTypes (patchConnection) -{ - if (! patchConnection) - return []; - - if (patchConnection.manifest?.view?.src) - return ["custom", "generic"]; - - return ["generic"]; -} - -/** Creates and returns a HTMLElement view which can be shown to control this patch. - * - * If no preferredType argument is supplied, this will return either a custom patch-specific - * view (if the manifest specifies one), or a generic view if not. The preferredType argument - * can be used to choose one of the types of view returned by getAvailableViewTypes(). - * - * @param {PatchConnection} patchConnection - the connection to use - * @param {string} preferredType - the name of the type of view to open, e.g. "generic" - * or the name of one of the views in the manifest - * @returns {HTMLElement} a HTMLElement that can be displayed as the patch GUI - */ -export async function createPatchView (patchConnection, preferredType) -{ - if (patchConnection?.manifest) - { - let view = patchConnection.manifest.view; - - if (view && preferredType === "generic") - if (view.src) - view = undefined; - - const viewModuleURL = view?.src ? patchConnection.getResourceAddress (view.src) : "./cmaj-generic-patch-view.js"; - const viewModule = await import (viewModuleURL); - const patchView = await viewModule?.default (patchConnection); - - if (patchView) - { - patchView.style.display = "block"; - - if (view?.width > 10) - patchView.style.width = view.width + "px"; - else - patchView.style.width = undefined; - - if (view?.height > 10) - patchView.style.height = view.height + "px"; - else - patchView.style.height = undefined; - - return patchView; - } - } - - return undefined; -} - -/** If a patch view declares itself to be scalable, this will attempt to scale it to fit - * into a given parent element. - * - * @param {HTMLElement} view - the patch view - * @param {HTMLElement} parentToScale - the patch view's direct parent element, to which - * the scale factor will be applied - * @param {HTMLElement} parentContainerToFitTo - an outer parent of the view, whose bounds - * the view will be made to fit - */ -export function scalePatchViewToFit (view, parentToScale, parentContainerToFitTo) -{ - function getClientSize (view) - { - const clientStyle = getComputedStyle (view); - - return { - width: view.clientHeight - parseFloat (clientStyle.paddingTop) - parseFloat (clientStyle.paddingBottom), - height: view.clientWidth - parseFloat (clientStyle.paddingLeft) - parseFloat (clientStyle.paddingRight) - }; - } - - const scaleLimits = view.getScaleFactorLimits?.(); - - if (scaleLimits && (scaleLimits.minScale || scaleLimits.maxScale)) - { - const minScale = scaleLimits.minScale || 0.25; - const maxScale = scaleLimits.maxScale || 5.0; - - const targetSize = getClientSize (parentContainerToFitTo); - const clientSize = getClientSize (view); - - const scaleW = targetSize.width / clientSize.width; - const scaleH = targetSize.height / clientSize.height; - - const scale = Math.min (maxScale, Math.max (minScale, Math.min (scaleW, scaleH))); - - parentToScale.style.transform = `scale(${scale})`; - } - else - { - parentToScale.style.transform = "none"; - } -} diff --git a/Freeverb/cmaj_api/cmaj-server-session.js b/Freeverb/cmaj_api/cmaj-server-session.js deleted file mode 100644 index 813f5fcd..00000000 --- a/Freeverb/cmaj_api/cmaj-server-session.js +++ /dev/null @@ -1,452 +0,0 @@ -// -// ,ad888ba, 88 -// d8"' "8b -// d8 88,dba,,adba, ,aPP8A.A8 88 -// Y8, 88 88 88 88 88 88 -// Y8a. .a8P 88 88 88 88, ,88 88 (C)2024 Cmajor Software Ltd -// '"Y888Y"' 88 88 88 '"8bbP"Y8 88 https://cmajor.dev -// ,88 -// 888P" -// -// This file may be used under the terms of the ISC license: -// -// Permission to use, copy, modify, and/or distribute this software for any purpose with or -// without fee is hereby granted, provided that the above copyright notice and this permission -// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL -// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR -// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -import { PatchConnection } from "./cmaj-patch-connection.js" -import { EventListenerList } from "./cmaj-event-listener-list.js" - - -//============================================================================== -/* - * This class provides the API and manages the communication protocol between - * a javascript application and a Cmajor session running on some kind of server - * (which may be local or remote). - * - * This is an abstract base class: some kind of transport layer will create a - * subclass of ServerSession which a client application can then use to control - * and interact with the server. - */ -export class ServerSession extends EventListenerList -{ - /** A server session must be given a unique sessionID. - * @param {string} sessionID - this must be a unique string which is safe for - * use as an identifier or filename - */ - constructor (sessionID) - { - super(); - - this.sessionID = sessionID; - this.activePatchConnections = new Set(); - this.status = { connected: false, loaded: false }; - this.lastServerMessageTime = Date.now(); - this.checkForServerTimer = setInterval (() => this.checkServerStillExists(), 2000); - } - - /** Call `dispose()` when this session is no longer needed and should be released. */ - dispose() - { - if (this.checkForServerTimer) - { - clearInterval (this.checkForServerTimer); - this.checkForServerTimer = undefined; - } - - this.status = { connected: false, loaded: false }; - } - - //============================================================================== - // Session status methods: - - /** Attaches a listener function which will be called when the session status changes. - * The listener will be called with an argument object containing lots of properties - * describing the state, including any errors, loaded patch manifest, etc. - */ - addStatusListener (listener) { this.addEventListener ("session_status", listener); } - - /** Removes a listener that was previously added by `addStatusListener()` - */ - removeStatusListener (listener) { this.removeEventListener ("session_status", listener); } - - /** Asks the server to asynchronously send a status update message with the latest status. - */ - requestSessionStatus() { this.sendMessageToServer ({ type: "req_session_status" }); } - - /** Returns the session's last known status object. */ - getCurrentStatus() { return this.status; } - - //============================================================================== - // Patch loading: - - /** Asks the server to load the specified patch into our session. - */ - loadPatch (patchFileToLoad) - { - this.currentPatchLocation = patchFileToLoad; - this.sendMessageToServer ({ type: "load_patch", file: patchFileToLoad }); - } - - /** Tells the server to asynchronously generate a list of patches that it has access to. - * The function provided will be called back with an array of manifest objects describing - * each of the patches. - */ - requestAvailablePatchList (callbackFunction) - { - const replyType = this.createReplyID ("patchlist_"); - this.addSingleUseListener (replyType, callbackFunction); - this.sendMessageToServer ({ type: "req_patchlist", - replyType: replyType }); - } - - /** Creates and returns a new PatchConnection object which can be used to control the - * patch that this session has loaded. - */ - createPatchConnection() - { - class ServerPatchConnection extends PatchConnection - { - constructor (session) - { - super(); - this.session = session; - this.manifest = session.status?.manifest; - this.session.activePatchConnections.add (this); - } - - dispose() - { - this.session.activePatchConnections.delete (this); - this.session = undefined; - } - - sendMessageToServer (message) - { - this.session?.sendMessageToServer (message); - } - - getResourceAddress (path) - { - if (! this.session?.status?.httpRootURL) - return undefined; - - return this.session.status.httpRootURL - + (path.startsWith ("/") ? path.substr (1) : path); - } - } - - return new ServerPatchConnection (this); - } - - //============================================================================== - // Audio input source handling: - - /** - * Sets a custom audio input source for a particular endpoint. - * - * When a source is changed, a callback is sent to any audio input mode listeners (see - * `addAudioInputModeListener()`) - * - * @param {Object} endpointID - * @param {boolean} shouldMute - if true, the endpoint will be muted - * @param {Uint8Array | Array} fileDataToPlay - if this is some kind of array containing - * binary data that can be parsed as an audio file, then it will be sent across for the - * server to play as a looped input sample. - */ - setAudioInputSource (endpointID, shouldMute, fileDataToPlay) - { - const loopFile = "_audio_source_" + endpointID; - - if (fileDataToPlay) - { - this.registerFile (loopFile, - { - size: fileDataToPlay.byteLength, - read: (start, length) => { return new Blob ([fileDataToPlay.slice (start, start + length)]); } - }); - - this.sendMessageToServer ({ type: "set_custom_audio_input", - endpoint: endpointID, - file: loopFile }); - } - else - { - this.removeFile (loopFile); - - this.sendMessageToServer ({ type: "set_custom_audio_input", - endpoint: endpointID, - mute: !! shouldMute }); - } - } - - /** Attaches a listener function to be told when the input source for a particular - * endpoint is changed by a call to `setAudioInputSource()`. - */ - addAudioInputModeListener (endpointID, listener) { this.addEventListener ("audio_input_mode_" + endpointID, listener); } - - /** Removes a listener previously added with `addAudioInputModeListener()` */ - removeAudioInputModeListener (endpointID, listener) { this.removeEventListener ("audio_input_mode_" + endpointID, listener); } - - /** Asks the server to send an update with the latest status to any audio mode listeners that - * are attached to the given endpoint. - * @param {string} endpointID - */ - requestAudioInputMode (endpointID) { this.sendMessageToServer ({ type: "req_audio_input_mode", endpoint: endpointID }); } - - //============================================================================== - // Audio device methods: - - /** Enables or disables audio playback. - * When playback state changes, a status update is sent to any status listeners. - * @param {boolean} shouldBeActive - */ - setAudioPlaybackActive (shouldBeActive) { this.sendMessageToServer ({ type: "set_audio_playback_active", active: shouldBeActive }); } - - /** Asks the server to apply a new set of audio device properties. - * The properties object uses the same format as the object that is passed to the listeners - * (see `addAudioDevicePropertiesListener()`). - */ - setAudioDeviceProperties (newProperties) { this.sendMessageToServer ({ type: "set_audio_device_props", properties: newProperties }); } - - /** Attaches a listener function which will be called when the audio device properties are - * changed. - * - * You can remove the listener when it's no longer needed with `removeAudioDevicePropertiesListener()`. - * - * @param listener - this callback will receive an argument object containing all the - * details about the device. - */ - addAudioDevicePropertiesListener (listener) { this.addEventListener ("audio_device_properties", listener); } - - /** Removes a listener that was added with `addAudioDevicePropertiesListener()` */ - removeAudioDevicePropertiesListener (listener) { this.removeEventListener ("audio_device_properties", listener); } - - /** Causes an asynchronous callback to any audio device listeners that are registered. */ - requestAudioDeviceProperties() { this.sendMessageToServer ({ type: "req_audio_device_props" }); } - - //============================================================================== - /** Asks the server to asynchronously generate some code from the currently loaded patch. - * - * @param {string} codeType - this must be one of the strings that are listed in the - * status's `codeGenTargets` property. For example, "cpp" - * would request a C++ version of the patch. - * @param {Object} [extraOptions] - this optionally provides target-specific properties. - * @param callbackFunction - this function will be called with the result when it has - * been generated. Its argument will be an object containing the - * code, errors and other metadata about the patch. - */ - requestGeneratedCode (codeType, extraOptions, callbackFunction) - { - const replyType = this.createReplyID ("codegen_"); - this.addSingleUseListener (replyType, callbackFunction); - this.sendMessageToServer ({ type: "req_codegen", - codeType: codeType, - options: extraOptions, - replyType: replyType }); - } - - //============================================================================== - // File change monitoring: - - /** Attaches a listener to be told when a file change is detected in the currently-loaded - * patch. The function will be called with an object that gives rough details about the - * type of change, i.e. whether it's a manifest or asset file, or a cmajor file, but it - * won't provide any information about exactly which files are involved. - */ - addFileChangeListener (listener) { this.addEventListener ("patch_source_changed", listener); } - - /** Removes a listener that was previously added with `addFileChangeListener()`. - */ - removeFileChangeListener (listener) { this.removeEventListener ("patch_source_changed", listener); } - - //============================================================================== - // CPU level monitoring methods: - - /** Attaches a listener function which will be sent messages containing CPU info. - * To remove the listener, call `removeCPUListener()`. To change the rate of these - * messages, use `setCPULevelUpdateRate()`. - */ - addCPUListener (listener) { this.addEventListener ("cpu_info", listener); this.updateCPULevelUpdateRate(); } - - /** Removes a listener that was previously attached with `addCPUListener()`. */ - removeCPUListener (listener) { this.removeEventListener ("cpu_info", listener); this.updateCPULevelUpdateRate(); } - - /** Changes the frequency at which CPU level update messages are sent to listeners. */ - setCPULevelUpdateRate (framesPerUpdate) { this.cpuFramesPerUpdate = framesPerUpdate; this.updateCPULevelUpdateRate(); } - - /** Attaches a listener to be told when a file change is detected in the currently-loaded - * patch. The function will be called with an object that gives rough details about the - * type of change, i.e. whether it's a manifest or asset file, or a cmajor file, but it - * won't provide any information about exactly which files are involved. - */ - addInfiniteLoopListener (listener) { this.addEventListener ("infinite_loop_detected", listener); } - - /** Removes a listener that was previously added with `addFileChangeListener()`. */ - removeInfiniteLoopListener (listener) { this.removeEventListener ("infinite_loop_detected", listener); } - - //============================================================================== - /** Registers a virtual file with the server, under the given name. - * - * @param {string} filename - the full path name of the file - * @param {Object} contentProvider - this object must have a property called `size` which is a - * constant size in bytes for the file, and a method `read (offset, size)` which - * returns an array (or UInt8Array) of bytes for the data in a given chunk of the file. - * The server may repeatedly call this method at any time until `removeFile()` is - * called to deregister the file. - */ - registerFile (filename, contentProvider) - { - if (! this.files) - this.files = new Map(); - - this.files.set (filename, contentProvider); - - this.sendMessageToServer ({ type: "register_file", - filename: filename, - size: contentProvider.size }); - } - - /** Removes a file that was previously registered with `registerFile()`. */ - removeFile (filename) - { - this.sendMessageToServer ({ type: "remove_file", - filename: filename }); - this.files?.delete (filename); - } - - //============================================================================== - // Private methods from this point... - - /** An implementation subclass must call this when the session first connects - * @private - */ - handleSessionConnection() - { - if (! this.status.connected) - { - this.requestSessionStatus(); - this.requestAudioDeviceProperties(); - - if (this.currentPatchLocation) - { - this.loadPatch (this.currentPatchLocation); - this.currentPatchLocation = undefined; - } - } - } - - /** An implementation subclass must call this when a message arrives - * @private - */ - handleMessageFromServer (msg) - { - this.lastServerMessageTime = Date.now(); - const type = msg.type; - const message = msg.message; - - switch (type) - { - case "cpu_info": - case "audio_device_properties": - case "patch_source_changed": - case "infinite_loop_detected": - this.dispatchEvent (type, message); - break; - - case "session_status": - message.connected = true; - this.setNewStatus (message); - break; - - case "req_file_read": - this.handleFileReadRequest (message); - break; - - case "ping": - this.sendMessageToServer ({ type: "ping" }); - break; - - default: - if (type.startsWith ("audio_input_mode_") || type.startsWith ("reply_")) - { - this.dispatchEvent (type, message); - break; - } - - for (const c of this.activePatchConnections) - c.deliverMessageFromServer (msg); - - break; - } - } - - /** @private */ - checkServerStillExists() - { - if (Date.now() > this.lastServerMessageTime + 10000) - this.setNewStatus ({ - connected: false, - loaded: false, - status: "Cannot connect to the Cmajor server" - }); - } - - /** @private */ - setNewStatus (newStatus) - { - this.status = newStatus; - this.dispatchEvent ("session_status", this.status); - this.updateCPULevelUpdateRate(); - } - - /** @private */ - updateCPULevelUpdateRate() - { - const rate = this.getNumListenersForType ("cpu_info") > 0 ? (this.cpuFramesPerUpdate || 15000) : 0; - this.sendMessageToServer ({ type: "set_cpu_info_rate", - framesPerCallback: rate }); - } - - /** @private */ - handleFileReadRequest (request) - { - const contentProvider = this.files?.get (request?.file); - - if (contentProvider && request.offset !== null && request.size != 0) - { - const data = contentProvider.read (request.offset, request.size); - const reader = new FileReader(); - - reader.onloadend = (e) => - { - const base64 = e.target?.result?.split?.(",", 2)[1]; - - if (base64) - this.sendMessageToServer ({ type: "file_content", - file: request.file, - data: base64, - start: request.offset }); - }; - - reader.readAsDataURL (data); - } - } - - /** @private */ - createReplyID (stem) - { - return "reply_" + stem + this.createRandomID(); - } - - /** @private */ - createRandomID() - { - return (Math.floor (Math.random() * 100000000)).toString(); - } -} diff --git a/Freeverb/cmaj_api/cmaj_audio_worklet_helper.js b/Freeverb/cmaj_api/cmaj_audio_worklet_helper.js deleted file mode 100644 index 8eab660e..00000000 --- a/Freeverb/cmaj_api/cmaj_audio_worklet_helper.js +++ /dev/null @@ -1,660 +0,0 @@ - -import { PatchConnection } from "./cmaj-patch-connection.js" - -//============================================================================== -// N.B. code will be serialised to a string, so all `registerWorkletProcessor`s -// dependencies must be self contained and not capture things in the outer scope -async function serialiseWorkletProcessorFactoryToDataURI (WrapperClass, workletName) -{ - const serialisedInvocation = `(${registerWorkletProcessor.toString()}) ("${workletName}", ${WrapperClass.toString()});` - - let reader = new FileReader(); - reader.readAsDataURL (new Blob ([serialisedInvocation], { type: "text/javascript" })); - - return await new Promise (res => { reader.onloadend = () => res (reader.result); }); -} - -function registerWorkletProcessor (workletName, WrapperClass) -{ - function makeConsumeOutputEvents ({ wrapper, eventOutputs, dispatchOutputEvent }) - { - const outputEventHandlers = eventOutputs.map (({ endpointID }) => - { - const readCount = wrapper[`getOutputEventCount_${endpointID}`]?.bind (wrapper); - const reset = wrapper[`resetOutputEventCount_${endpointID}`]?.bind (wrapper); - const readEventAtIndex = wrapper[`getOutputEvent_${endpointID}`]?.bind (wrapper); - - return () => - { - const count = readCount(); - for (let i = 0; i < count; ++i) - dispatchOutputEvent (endpointID, readEventAtIndex (i)); - - reset(); - }; - }); - - return () => outputEventHandlers.forEach ((consume) => consume() ); - } - - function setInitialParameterValues (parametersMap) - { - for (const { initialise } of Object.values (parametersMap)) - initialise(); - } - - function makeEndpointMap (wrapper, endpoints, initialValueOverrides) - { - const toKey = ({ endpointType, endpointID }) => - { - switch (endpointType) - { - case "event": return `sendInputEvent_${endpointID}`; - case "value": return `setInputValue_${endpointID}`; - } - - throw "Unhandled endpoint type"; - }; - - const lookup = {}; - for (const { endpointID, endpointType, annotation, purpose } of endpoints) - { - const key = toKey ({ endpointType, endpointID }); - const wrapperUpdate = wrapper[key]?.bind (wrapper); - - const snapAndConstrainValue = (value) => - { - if (annotation.step != null) - value = Math.round (value / annotation.step) * annotation.step; - - if (annotation.min != null && annotation.max != null) - value = Math.min (Math.max (value, annotation.min), annotation.max); - - return value; - }; - - const update = (value, rampFrames) => - { - // N.B. value clamping and rampFrames from annotations not currently applied - const entry = lookup[endpointID]; - entry.cachedValue = value; - wrapperUpdate (value, rampFrames); - }; - - if (update) - { - const initialValue = initialValueOverrides[endpointID] ?? annotation?.init; - - lookup[endpointID] = { - snapAndConstrainValue, - update, - initialise: initialValue != null ? () => update (initialValue) : () => {}, - purpose, - cachedValue: undefined, - }; - } - } - - return lookup; - } - - function makeStreamEndpointHandler ({ wrapper, toEndpoints, wrapperMethodNamePrefix }) - { - const endpoints = toEndpoints (wrapper); - if (endpoints.length === 0) - return () => {}; - - // N.B. we just take the first for now (and do the same when creating the node). - // we can do better, and should probably align with something similar to what the patch player does - const first = endpoints[0]; - const handleFrames = wrapper[`${wrapperMethodNamePrefix}_${first.endpointID}`]?.bind (wrapper); - if (! handleFrames) - return () => {}; - - return (channels, blockSize) => handleFrames (channels, blockSize); - } - - function makeInputStreamEndpointHandler (wrapper) - { - return makeStreamEndpointHandler ({ - wrapper, - toEndpoints: wrapper => wrapper.getInputEndpoints().filter (({ purpose }) => purpose === "audio in"), - wrapperMethodNamePrefix: "setInputStreamFrames", - }); - } - - function makeOutputStreamEndpointHandler (wrapper) - { - return makeStreamEndpointHandler ({ - wrapper, - toEndpoints: wrapper => wrapper.getOutputEndpoints().filter (({ purpose }) => purpose === "audio out"), - wrapperMethodNamePrefix: "getOutputFrames", - }); - } - - class WorkletProcessor extends AudioWorkletProcessor - { - static get parameterDescriptors() - { - return []; - } - - constructor ({ processorOptions, ...options }) - { - super (options); - - this.processImpl = undefined; - this.consumeOutputEvents = undefined; - - const { sessionID = Date.now() & 0x7fffffff, initialValueOverrides = {} } = processorOptions; - - const wrapper = new WrapperClass(); - - wrapper.initialise (sessionID, sampleRate) - .then (() => this.initialisePatch (wrapper, initialValueOverrides)) - .catch (error => { throw new Error (error)}); - } - - process (inputs, outputs) - { - const input = inputs[0]; - const output = outputs[0]; - - this.processImpl?.(input, output); - this.consumeOutputEvents?.(); - - return true; - } - - sendPatchMessage (payload) - { - this.port.postMessage ({ type: "patch", payload }); - } - - sendParameterValueChanged (endpointID, value) - { - this.sendPatchMessage ({ - type: "param_value", - message: { endpointID, value } - }); - } - - initialisePatch (wrapper, initialValueOverrides) - { - try - { - const inputParameters = wrapper.getInputEndpoints().filter (({ purpose }) => purpose === "parameter"); - const parametersMap = makeEndpointMap (wrapper, inputParameters, initialValueOverrides); - - setInitialParameterValues (parametersMap); - - const toParameterValuesWithKey = (endpointKey, parametersMap) => - { - const toValue = ([endpoint, { cachedValue }]) => ({ [endpointKey]: endpoint, value: cachedValue }); - return Object.entries (parametersMap).map (toValue); - }; - - const initialValues = toParameterValuesWithKey ("endpointID", parametersMap); - const initialState = wrapper.getState(); - - const resetState = () => - { - wrapper.restoreState (initialState); - - // N.B. update cache used for `req_param_value` messages (we don't currently read from the wasm heap) - setInitialParameterValues (parametersMap); - }; - - const isNonAudioOrParameterEndpoint = ({ purpose }) => ! ["audio in", "parameter"].includes (purpose); - const otherInputs = wrapper.getInputEndpoints().filter (isNonAudioOrParameterEndpoint); - const otherInputEndpointsMap = makeEndpointMap (wrapper, otherInputs, initialValueOverrides); - - const isEvent = ({ endpointType }) => endpointType === "event"; - const eventInputs = wrapper.getInputEndpoints().filter (isEvent); - const eventOutputs = wrapper.getOutputEndpoints().filter (isEvent); - - const makeEndpointListenerMap = (eventEndpoints) => - { - const listeners = {}; - - for (const { endpointID } of eventEndpoints) - listeners[endpointID] = []; - - return listeners; - }; - - const inputEventListeners = makeEndpointListenerMap (eventInputs); - const outputEventListeners = makeEndpointListenerMap (eventOutputs); - - this.consumeOutputEvents = makeConsumeOutputEvents ({ - eventOutputs, - wrapper, - dispatchOutputEvent: (endpointID, event) => - { - for (const { replyType } of outputEventListeners[endpointID] ?? []) - { - this.sendPatchMessage ({ - type: replyType, - message: event.event, // N.B. chucking away frame and typeIndex info for now - }); - } - }, - }); - - const blockSize = 128; - const prepareInputFrames = makeInputStreamEndpointHandler (wrapper); - const processOutputFrames = makeOutputStreamEndpointHandler (wrapper); - - this.processImpl = (input, output) => - { - prepareInputFrames (input, blockSize); - wrapper.advance (blockSize); - processOutputFrames (output, blockSize); - }; - - // N.B. the message port makes things straightforward, but it allocates (when sending + receiving). - // so, we aren't doing ourselves any favours. we probably ought to marshal raw bytes over to the gui in - // a pre-allocated lock-free message queue (using `SharedArrayBuffer` + `Atomic`s) and transform the raw - // messages there. - this.port.addEventListener ("message", e => - { - if (e.data.type !== "patch") - return; - - const msg = e.data.payload; - - switch (msg.type) - { - case "req_status": - { - this.sendPatchMessage ({ - type: "status", - message: { - details: { - inputs: wrapper.getInputEndpoints(), - outputs: wrapper.getOutputEndpoints(), - }, - sampleRate, - }, - }); - break; - } - - case "req_reset": - { - resetState(); - initialValues.forEach (v => this.sendParameterValueChanged (v.endpointID, v.value)); - break; - } - - case "req_param_value": - { - // N.B. keep a local cache here so that we can send the values back when requested. - // we could instead have accessors into the wasm heap. - const endpointID = msg.id; - const parameter = parametersMap[endpointID]; - if (! parameter) - return; - - const value = parameter.cachedValue; - this.sendParameterValueChanged (endpointID, value); - break; - } - - case "send_value": - { - const endpointID = msg.id; - const parameter = parametersMap[endpointID]; - - if (parameter) - { - const newValue = parameter.snapAndConstrainValue (msg.value); - parameter.update (newValue, msg.rampFrames); - - this.sendParameterValueChanged (endpointID, newValue); - return; - } - - const inputEndpoint = otherInputEndpointsMap[endpointID]; - - if (inputEndpoint) - { - inputEndpoint.update (msg.value); - - for (const { replyType } of inputEventListeners[endpointID] ?? []) - { - this.sendPatchMessage ({ - type: replyType, - message: inputEndpoint.cachedValue, - }); - } - } - break; - } - - case "send_gesture_start": break; - case "send_gesture_end": break; - - case "req_full_state": - this.sendPatchMessage ({ - type: msg?.replyType, - message: { - parameters: toParameterValuesWithKey ("name", parametersMap), - }, - }); - break; - - case "send_full_state": - { - const { parameters = [] } = e.data.payload?.value || []; - - for (const [endpointID, parameter] of Object.entries (parametersMap)) - { - const namedNextValue = parameters.find (({ name }) => name === endpointID); - - if (namedNextValue) - parameter.update (namedNextValue.value); - else - parameter.initialise(); - - this.sendParameterValueChanged (endpointID, parameter.cachedValue); - } - break; - } - - case "add_endpoint_listener": - { - const insertIfValidEndpoint = (lookup, msg) => - { - const endpointID = msg?.endpoint; - const listeners = lookup[endpointID] - - if (! listeners) - return false; - - return listeners.push ({ replyType: msg?.replyType }) > 0; - }; - - if (! insertIfValidEndpoint (inputEventListeners, msg)) - insertIfValidEndpoint (outputEventListeners, msg) - - break; - } - - case "remove_endpoint_listener": - { - const removeIfValidReplyType = (lookup, msg) => - { - const endpointID = msg?.endpoint; - const listeners = lookup[endpointID]; - - if (! listeners) - return false; - - const index = listeners.indexOf (msg?.replyType); - - if (index === -1) - return false; - - return listeners.splice (index, 1).length === 1; - }; - - if (! removeIfValidReplyType (inputEventListeners, msg)) - removeIfValidReplyType (outputEventListeners, msg) - - break; - } - - default: - break; - } - }); - - this.port.postMessage ({ type: "initialised" }); - this.port.start(); - } - catch (e) - { - this.port.postMessage (e.toString()); - } - } - } - - registerProcessor (workletName, WorkletProcessor); -} - -//============================================================================== -/** Creates an AudioWorkletNode that contains the - * - * @param {Object} WrapperClass - the generated Cmajor class - * @param {AudioContext} audioContext - a web audio AudioContext object - * @param {string} workletName - the name to give the new worklet that is created - * @param {number} sessionID - an integer to use for the session ID - * @param {Array} patchInputList - a list of the input endpoints that the patch provides - * @param {Object} initialValueOverrides - optional initial values for parameter endpoints - */ -export async function createAudioWorkletNode (WrapperClass, - audioContext, - workletName, - sessionID, - initialValueOverrides) -{ - const dataURI = await serialiseWorkletProcessorFactoryToDataURI (WrapperClass, workletName); - await audioContext.audioWorklet.addModule (dataURI); - - const audioInputEndpoints = WrapperClass.prototype.getInputEndpoints().filter (({ purpose }) => purpose === "audio in"); - const audioOutputEndpoints = WrapperClass.prototype.getOutputEndpoints().filter (({ purpose }) => purpose === "audio out"); - - // N.B. we just take the first for now (and do the same in the processor too). - // we can do better, and should probably align with something similar to what the patch player does - const pickFirstEndpointChannelCount = (endpoints) => endpoints.length ? endpoints[0].numAudioChannels : 0; - - const inputChannelCount = pickFirstEndpointChannelCount (audioInputEndpoints); - const outputChannelCount = pickFirstEndpointChannelCount (audioOutputEndpoints); - - const hasInput = inputChannelCount > 0; - const hasOutput = outputChannelCount > 0; - - const node = new AudioWorkletNode (audioContext, workletName, { - numberOfInputs: +hasInput, - numberOfOutputs: +hasOutput, - channelCountMode: "explicit", - channelCount: hasInput ? inputChannelCount : undefined, - outputChannelCount: hasOutput ? [outputChannelCount] : [], - - processorOptions: - { - sessionID, - initialValueOverrides - } - }); - - const waitUntilWorkletInitialised = async () => - { - return new Promise ((resolve) => - { - const filterForInitialised = (e) => - { - if (e.data.type === "initialised") - { - node.port.removeEventListener ("message", filterForInitialised); - resolve(); - } - }; - - node.port.addEventListener ("message", filterForInitialised); - }); - }; - - node.port.start(); - - await waitUntilWorkletInitialised(); - - return node; -} - -//============================================================================== -/** This class provides a PatchConnection that controls a Cmajor audio worklet - * node. - */ -export class AudioWorkletPatchConnection extends PatchConnection -{ - constructor (audioNode, manifest) - { - super(); - - this.manifest = manifest; - this.audioNode = audioNode; - - audioNode.port.addEventListener ("message", e => - { - if (e.data.type === "patch") - { - const msg = e.data.payload; - - if (msg?.type === "status") - msg.message = { manifest, ...msg.message }; - - this.deliverMessageFromServer (msg) - } - }); - - this.cachedState = {}; - } - - sendMessageToServer (msg) - { - this.audioNode.port.postMessage ({ type: "patch", payload: msg }); - } - - requestStoredStateValue (key) - { - this.dispatchEvent ("state_key_value", { key, value: this.cachedState[key] }); - } - - sendStoredStateValue (key, newValue) - { - const changed = this.cachedState[key] != newValue; - - if (changed) - { - const shouldRemove = newValue == null; - if (shouldRemove) - { - delete this.cachedState[key]; - return; - } - - this.cachedState[key] = newValue; - // N.B. notifying the client only when updating matches behaviour of the patch player - this.dispatchEvent ("state_key_value", { key, value: newValue }); - } - } - - sendFullStoredState (fullState) - { - const currentStateCleared = (() => - { - const out = {}; - Object.keys (this.cachedState).forEach (k => out[k] = undefined); - return out; - })(); - - const incomingStateValues = fullState.values ?? {}; - const nextStateValues = { ...currentStateCleared, ...incomingStateValues }; - - Object.entries (nextStateValues).forEach (([key, value]) => this.sendStoredStateValue (key, value)); - - // N.B. worklet will handle the `parameters` part - super.sendFullStoredState (fullState); - } - - requestFullStoredState (callback) - { - // N.B. the worklet only handles the `parameters` part, so we patch the key-value state in here - super.requestFullStoredState (msg => callback ({ values: { ...this.cachedState }, ...msg })); - } - - getResourceAddress (path) - { - return path.startsWith ("/") ? path : ("/" + path); - } -} - - -//============================================================================== -async function connectToAudioIn (audioContext, node) -{ - try - { - const input = await navigator.mediaDevices.getUserMedia ({ - audio: { - echoCancellation: false, - noiseSuppression: false, - autoGainControl: false, - }}); - - if (! input) - throw new Error(); - - const source = audioContext.createMediaStreamSource (input); - - if (! source) - throw new Error(); - - source.connect (node); - } - catch (e) - { - console.warn (`Could not open audio input`); - } -} - -async function connectToMIDI (connection) -{ - try - { - if (! navigator.requestMIDIAccess) - throw new Error ("Web MIDI API not supported."); - - const midiAccess = await navigator.requestMIDIAccess ({ sysex: true, software: true }); - - for (const input of midiAccess.inputs.values()) - { - input.onmidimessage = ({ data }) => - connection.sendMIDIInputEvent ("midiIn", data[2] | (data[1] << 8) | (data[0] << 16)); - } - } - catch (e) - { - console.warn (`Could not open MIDI devices: ${e}`); - } -} - -/** Takes an audio node and connection that were returned by `createAudioWorkletNodePatchConnection()` - * and attempts to hook them up to the default audio and MIDI channels. - * - * @param {AudioWorkletNode} node - the audio node - * @param {PatchConnection} connection - the PatchConnection object created by `createAudioWorkletNodePatchConnection()` - * @param {AudioContext} audioContext - a web audio AudioContext object - * @param {Array} patchInputList - a list of the input endpoints that the patch provides - */ -export async function connectDefaultAudioAndMIDI ({ node, connection, audioContext, patchInputList }) -{ - function hasInputWithPurpose (purpose) - { - for (const i of patchInputList) - if (i.purpose === purpose) - return true; - - return false; - } - - if (hasInputWithPurpose ("midi in")) - connectToMIDI (connection); - - if (hasInputWithPurpose ("audio in")) - connectToAudioIn (audioContext, node); - - node.connect (audioContext.destination); -} diff --git a/Freeverb/index.html b/Freeverb/index.html deleted file mode 100644 index 9a05947b..00000000 --- a/Freeverb/index.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - Cmajor Patch - - - -
-
- -
-
- - - - - - diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..ae06c8ad --- /dev/null +++ b/Gemfile @@ -0,0 +1,40 @@ +source "https://rubygems.org" +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! +#gem "jekyll", "~> 4.3.3" +gem "github-pages", "~> 231", group: :jekyll_plugins + + +# This is the default theme for new Jekyll sites. You may change this to anything you like. +gem "minima", "~> 2.5" +# If you want to use GitHub Pages, remove the "gem "jekyll"" above and +# uncomment the line below. To upgrade, run `bundle update github-pages`. +# gem "github-pages", group: :jekyll_plugins +# If you have any plugins, put them here! +group :jekyll_plugins do + gem "jekyll-feed", "~> 0.12" +end + +# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem +# and associated library. +platforms :mingw, :x64_mingw, :mswin, :jruby do + gem "tzinfo", ">= 1", "< 3" + gem "tzinfo-data" +end + +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] + +# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem +# do not have a Java counterpart. +gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] + +gem "webrick", "~> 1.8" + +gem "csv", "~> 3.2" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..b6bc6aa4 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,296 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (7.1.3.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + base64 (0.2.0) + bigdecimal (3.1.6) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + colorator (1.1.0) + commonmarker (0.23.10) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + csv (3.2.8) + dnsruby (1.70.0) + simpleidn (~> 0.2.1) + drb (2.2.1) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) + eventmachine (1.2.7) + execjs (2.9.1) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http + ffi (1.16.3) + forwardable-extended (2.6.0) + gemoji (4.1.0) + github-pages (231) + github-pages-health-check (= 1.18.2) + jekyll (= 3.9.5) + jekyll-avatar (= 0.8.0) + jekyll-coffeescript (= 1.2.2) + jekyll-commonmark-ghpages (= 0.4.0) + jekyll-default-layout (= 0.1.5) + jekyll-feed (= 0.17.0) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.16.1) + jekyll-include-cache (= 0.2.1) + jekyll-mentions (= 1.6.0) + jekyll-optional-front-matter (= 0.3.2) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.16.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.3) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.8.0) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.13.0) + kramdown (= 2.4.0) + kramdown-parser-gfm (= 1.1.0) + liquid (= 4.0.4) + mercenary (~> 0.3) + minima (= 2.5.1) + nokogiri (>= 1.13.6, < 2.0) + rouge (= 3.30.0) + terminal-table (~> 1.4) + github-pages-health-check (1.18.2) + addressable (~> 2.3) + dnsruby (~> 1.60) + octokit (>= 4, < 8) + public_suffix (>= 3.0, < 6.0) + typhoeus (~> 1.3) + html-pipeline (2.14.3) + activesupport (>= 2) + nokogiri (>= 1.4) + http_parser.rb (0.8.0) + i18n (1.14.3) + concurrent-ruby (~> 1.0) + racc (~> 1.7) + jekyll (3.9.5) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (>= 0.7, < 2) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (>= 1.17, < 3) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-avatar (0.8.0) + jekyll (>= 3.0, < 5.0) + jekyll-coffeescript (1.2.2) + coffee-script (~> 2.2) + coffee-script-source (~> 1.12) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) + rouge (>= 2.0, < 5.0) + jekyll-default-layout (0.1.5) + jekyll (>= 3.0, < 5.0) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-gist (1.5.0) + octokit (~> 4.2) + jekyll-github-metadata (2.16.1) + jekyll (>= 3.4, < 5.0) + octokit (>= 4, < 7, != 4.4.0) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-mentions (1.6.0) + html-pipeline (~> 2.3) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.6.0) + jekyll (> 3.5, < 5.0) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + jemoji (0.13.0) + gemoji (>= 3, < 5) + html-pipeline (~> 2.2) + jekyll (>= 3.0, < 5.0) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.3.6) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.22.2) + mutex_m (0.2.0) + net-http (0.4.1) + uri + nokogiri (1.16.2-aarch64-linux) + racc (~> 1.4) + nokogiri (1.16.2-arm-linux) + racc (~> 1.4) + nokogiri (1.16.2-arm64-darwin) + racc (~> 1.4) + nokogiri (1.16.2-x86-linux) + racc (~> 1.4) + nokogiri (1.16.2-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.16.2-x86_64-linux) + racc (~> 1.4) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (5.0.4) + racc (1.7.3) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.6) + rouge (3.30.0) + rubyzip (2.3.2) + safe_yaml (1.0.5) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + simpleidn (0.2.1) + unf (~> 0.1.4) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + typhoeus (1.4.1) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9.1) + unicode-display_width (1.8.0) + uri (0.13.0) + webrick (1.8.1) + +PLATFORMS + aarch64-linux + arm-linux + arm64-darwin + x86-linux + x86_64-darwin + x86_64-linux + +DEPENDENCIES + csv (~> 3.2) + github-pages (~> 231) + http_parser.rb (~> 0.6.0) + jekyll-feed (~> 0.12) + minima (~> 2.5) + tzinfo (>= 1, < 3) + tzinfo-data + wdm (~> 0.1.1) + webrick (~> 1.8) + +BUNDLED WITH + 2.5.6 diff --git a/_includes/js/custom.js b/_includes/js/custom.js index b3c2facf..3d207e97 100644 --- a/_includes/js/custom.js +++ b/_includes/js/custom.js @@ -6,8 +6,8 @@ if ( !window.jQuery ) { var s = document.createElement('script'); s.setAttribute('src', '//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js'); - document.body.appendChild(s); + document.body?.appendChild(s); console.log('jquery loaded!'); } - + })(); \ No newline at end of file diff --git a/_includes/title.html b/_includes/title.html index dd13e50f..8cfd2c9c 100644 --- a/_includes/title.html +++ b/_includes/title.html @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/Freeverb/cmaj_api/assets/cmajor-logo.svg b/assets/example_patches/hello_world/cmaj_api/assets/cmajor-logo.svg similarity index 100% rename from Freeverb/cmaj_api/assets/cmajor-logo.svg rename to assets/example_patches/hello_world/cmaj_api/assets/cmajor-logo.svg diff --git a/docs/LanguageGuide/annotations/using-the--main--annotation.md b/docs/LanguageGuide/annotations/using-the--main--annotation.md index c90af345..d74f277a 100644 --- a/docs/LanguageGuide/annotations/using-the--main--annotation.md +++ b/docs/LanguageGuide/annotations/using-the--main--annotation.md @@ -1,6 +1,6 @@ --- layout: default -title: Using the `[[ main ]]` Annotation +title: "Using the `[[ main ]]` Annotation" permalink: /docs/LanguageReference#using-the--main--annotation parent: Annotations grand_parent: Language Reference diff --git a/docs/LanguageGuide/control-flow-and-loops/break--continue.md b/docs/LanguageGuide/control-flow-and-loops/break--continue.md index 79beac55..26ccad69 100644 --- a/docs/LanguageGuide/control-flow-and-loops/break--continue.md +++ b/docs/LanguageGuide/control-flow-and-loops/break--continue.md @@ -1,6 +1,6 @@ --- layout: default -title: `break` / `continue` +title: "`break` / `continue`" permalink: /docs/LanguageReference#break--continue parent: Control-flow and Loops grand_parent: Language Reference diff --git a/docs/LanguageGuide/control-flow-and-loops/for.md b/docs/LanguageGuide/control-flow-and-loops/for.md index 3412b94b..68d0f428 100644 --- a/docs/LanguageGuide/control-flow-and-loops/for.md +++ b/docs/LanguageGuide/control-flow-and-loops/for.md @@ -1,6 +1,6 @@ --- layout: default -title: `for` +title: "`for`" permalink: /docs/LanguageReference#for parent: Control-flow and Loops grand_parent: Language Reference diff --git a/docs/LanguageGuide/control-flow-and-loops/if-const.md b/docs/LanguageGuide/control-flow-and-loops/if-const.md index 97352a8d..e570bd39 100644 --- a/docs/LanguageGuide/control-flow-and-loops/if-const.md +++ b/docs/LanguageGuide/control-flow-and-loops/if-const.md @@ -1,6 +1,6 @@ --- layout: default -title: `if const` +title: "`if const`" permalink: /docs/LanguageReference#if-const parent: Control-flow and Loops grand_parent: Language Reference diff --git a/docs/LanguageGuide/control-flow-and-loops/loop.md b/docs/LanguageGuide/control-flow-and-loops/loop.md index 37fe7d3f..639a705f 100644 --- a/docs/LanguageGuide/control-flow-and-loops/loop.md +++ b/docs/LanguageGuide/control-flow-and-loops/loop.md @@ -1,6 +1,6 @@ --- layout: default -title: `loop` +title: "`loop`" permalink: /docs/LanguageReference#loop parent: Control-flow and Loops grand_parent: Language Reference diff --git a/docs/LanguageGuide/control-flow-and-loops/while.md b/docs/LanguageGuide/control-flow-and-loops/while.md index c99d9080..9ef16b4b 100644 --- a/docs/LanguageGuide/control-flow-and-loops/while.md +++ b/docs/LanguageGuide/control-flow-and-loops/while.md @@ -1,6 +1,6 @@ --- layout: default -title: `while` +title: "`while`" permalink: /docs/LanguageReference#while parent: Control-flow and Loops grand_parent: Language Reference diff --git a/docs/LanguageGuide/inputoutput-endpoint-declarations/hoisted-endpoints.md b/docs/LanguageGuide/inputoutput-endpoint-declarations/hoisted-endpoints.md index 02138627..0b8d9a9f 100644 --- a/docs/LanguageGuide/inputoutput-endpoint-declarations/hoisted-endpoints.md +++ b/docs/LanguageGuide/inputoutput-endpoint-declarations/hoisted-endpoints.md @@ -1,6 +1,6 @@ --- layout: default -title: "Hoisted" Endpoints +title: ""Hoisted" Endpoints" permalink: /docs/LanguageReference#hoisted-endpoints parent: Input/Output Endpoint Declarations grand_parent: Language Reference diff --git a/docs/LanguageGuide/variables-and-constants/external-constants.md b/docs/LanguageGuide/variables-and-constants/external-constants.md index a904af46..dd2a06bb 100644 --- a/docs/LanguageGuide/variables-and-constants/external-constants.md +++ b/docs/LanguageGuide/variables-and-constants/external-constants.md @@ -1,6 +1,6 @@ --- layout: default -title: `external` Constants +title: "`external` Constants" permalink: /docs/LanguageReference#external-constants parent: Variables and Constants grand_parent: Language Reference diff --git a/docs/Licence/license-terms/important-notice-please-read-carefully-before-downloading-installing-or-using-the-cmajor-library.md b/docs/Licence/license-terms/important-notice-please-read-carefully-before-downloading-installing-or-using-the-cmajor-library.md index dcd09487..bf3a7a8f 100644 --- a/docs/Licence/license-terms/important-notice-please-read-carefully-before-downloading-installing-or-using-the-cmajor-library.md +++ b/docs/Licence/license-terms/important-notice-please-read-carefully-before-downloading-installing-or-using-the-cmajor-library.md @@ -1,6 +1,6 @@ --- layout: default -title: IMPORTANT NOTICE: PLEASE READ CAREFULLY BEFORE DOWNLOADING, INSTALLING OR USING THE CMAJOR LIBRARY: +title: "IMPORTANT NOTICE: PLEASE READ CAREFULLY BEFORE DOWNLOADING, INSTALLING OR USING THE CMAJOR LIBRARY:" permalink: /docs/Licence#important-notice-please-read-carefully-before-downloading-installing-or-using-the-cmajor-library parent: License Terms grand_parent: Licence diff --git a/docs/PatchFormat/patch-guis/the-patchconnection-object.md b/docs/PatchFormat/patch-guis/the-patchconnection-object.md index 22396933..2710a1ca 100644 --- a/docs/PatchFormat/patch-guis/the-patchconnection-object.md +++ b/docs/PatchFormat/patch-guis/the-patchconnection-object.md @@ -1,6 +1,6 @@ --- layout: default -title: The `PatchConnection` object +title: "The `PatchConnection` object" permalink: /docs/PatchFormat#the-patchconnection-object parent: Patch GUIs grand_parent: Patch Format diff --git a/docs/PatchFormat/the-cmajorpatch-manifest-file/the-cmajorpatch-manifest-file.md b/docs/PatchFormat/the-cmajorpatch-manifest-file/the-cmajorpatch-manifest-file.md index 0833a41e..9bdea439 100644 --- a/docs/PatchFormat/the-cmajorpatch-manifest-file/the-cmajorpatch-manifest-file.md +++ b/docs/PatchFormat/the-cmajorpatch-manifest-file/the-cmajorpatch-manifest-file.md @@ -1,5 +1,5 @@ --- -title: The `.cmajorpatch` manifest file +title: "The `.cmajorpatch` manifest file" permalink: /docs/PatchFormat#the-cmajorpatch-manifest-file parent: Patch Format has_children: False diff --git a/docs/QuckStart/coding-in-cmajor-high-level-overview/coding-in-cmajor-high-level-overview.md b/docs/QuckStart/coding-in-cmajor-high-level-overview/coding-in-cmajor-high-level-overview.md index a395f528..42df8536 100644 --- a/docs/QuckStart/coding-in-cmajor-high-level-overview/coding-in-cmajor-high-level-overview.md +++ b/docs/QuckStart/coding-in-cmajor-high-level-overview/coding-in-cmajor-high-level-overview.md @@ -1,5 +1,5 @@ --- -title: Coding in Cmajor: High-Level Overview +title: "Coding in Cmajor: High-Level Overview" permalink: /docs/GettingStarted#coding-in-cmajor-high-level-overview parent: Getting Started has_children: False diff --git a/docs/QuckStart/installing-the-cmajor-command-line-tools/the-cmaj-command-line-tool.md b/docs/QuckStart/installing-the-cmajor-command-line-tools/the-cmaj-command-line-tool.md index fa09b30c..39f36e8d 100644 --- a/docs/QuckStart/installing-the-cmajor-command-line-tools/the-cmaj-command-line-tool.md +++ b/docs/QuckStart/installing-the-cmajor-command-line-tools/the-cmaj-command-line-tool.md @@ -1,6 +1,6 @@ --- layout: default -title: The `cmaj` command-line tool +title: "The `cmaj` command-line tool" permalink: /docs/GettingStarted#the-cmaj-command-line-tool parent: Installing the Cmajor command-line tools grand_parent: Getting Started diff --git a/docs/TestFileFormat/-runscript/-runscript.md b/docs/TestFileFormat/-runscript/-runscript.md index 178490eb..4650b68d 100644 --- a/docs/TestFileFormat/-runscript/-runscript.md +++ b/docs/TestFileFormat/-runscript/-runscript.md @@ -1,5 +1,5 @@ --- -title: `-- runScript()` +title: "`-- runScript()`" permalink: /docs/TestFileFormat#-runscript parent: Test File Format has_children: False diff --git a/docs/TestFileFormat/-runscript/input-event-file-format.md b/docs/TestFileFormat/-runscript/input-event-file-format.md index 4e81e6d0..cc269393 100644 --- a/docs/TestFileFormat/-runscript/input-event-file-format.md +++ b/docs/TestFileFormat/-runscript/input-event-file-format.md @@ -2,7 +2,7 @@ layout: default title: input event file format permalink: /docs/TestFileFormat#input-event-file-format -parent: `## runScript()` +parent: "`-- runScript()`" grand_parent: Test File Format nav_order: 2 --- diff --git a/docs/TestFileFormat/-runscript/input-stream-file-format.md b/docs/TestFileFormat/-runscript/input-stream-file-format.md index d4fcd78c..ec93ffab 100644 --- a/docs/TestFileFormat/-runscript/input-stream-file-format.md +++ b/docs/TestFileFormat/-runscript/input-stream-file-format.md @@ -2,7 +2,7 @@ layout: default title: input stream file format permalink: /docs/TestFileFormat#input-stream-file-format -parent: `## runScript()` +parent: "`-- runScript()`" grand_parent: Test File Format nav_order: 4 --- diff --git a/docs/TestFileFormat/-runscript/input-value-file-format.md b/docs/TestFileFormat/-runscript/input-value-file-format.md index 810e571a..6334f720 100644 --- a/docs/TestFileFormat/-runscript/input-value-file-format.md +++ b/docs/TestFileFormat/-runscript/input-value-file-format.md @@ -2,7 +2,7 @@ layout: default title: input value file format permalink: /docs/TestFileFormat#input-value-file-format -parent: `## runScript()` +parent: "`-- runScript()`" grand_parent: Test File Format nav_order: 3 --- diff --git a/docs/TestFileFormat/-runscript/output-file-formats.md b/docs/TestFileFormat/-runscript/output-file-formats.md index c6c630c5..60f8e874 100644 --- a/docs/TestFileFormat/-runscript/output-file-formats.md +++ b/docs/TestFileFormat/-runscript/output-file-formats.md @@ -2,7 +2,7 @@ layout: default title: output file formats permalink: /docs/TestFileFormat#output-file-formats -parent: `## runScript()` +parent: "`-- runScript()`" grand_parent: Test File Format nav_order: 5 --- diff --git a/docs/TestFileFormat/-runscript/runscript-options.md b/docs/TestFileFormat/-runscript/runscript-options.md index 97b4738e..b4377876 100644 --- a/docs/TestFileFormat/-runscript/runscript-options.md +++ b/docs/TestFileFormat/-runscript/runscript-options.md @@ -2,7 +2,7 @@ layout: default title: runScript options permalink: /docs/TestFileFormat#runscript-options -parent: `## runScript()` +parent: "`-- runScript()`" grand_parent: Test File Format nav_order: 1 --- diff --git a/docs/TestFileFormat/-runscript/stream-output-comparison.md b/docs/TestFileFormat/-runscript/stream-output-comparison.md index 3dd627ed..fee7da68 100644 --- a/docs/TestFileFormat/-runscript/stream-output-comparison.md +++ b/docs/TestFileFormat/-runscript/stream-output-comparison.md @@ -2,7 +2,7 @@ layout: default title: stream output comparison permalink: /docs/TestFileFormat#stream-output-comparison -parent: `## runScript()` +parent: "`-- runScript()`" grand_parent: Test File Format nav_order: 6 --- diff --git a/docs/TestFileFormat/-testpatch/-testpatch.md b/docs/TestFileFormat/-testpatch/-testpatch.md index b7fb5d19..9d47c743 100644 --- a/docs/TestFileFormat/-testpatch/-testpatch.md +++ b/docs/TestFileFormat/-testpatch/-testpatch.md @@ -1,5 +1,5 @@ --- -title: `-- testPatch()` +title: "`-- testPatch()`" permalink: /docs/TestFileFormat#-testpatch parent: Test File Format has_children: False diff --git a/docs/TestFileFormat/built-in-test-functions/-expecterror-expected-error-message.md b/docs/TestFileFormat/built-in-test-functions/-expecterror-expected-error-message.md index f1c37079..d20d4170 100644 --- a/docs/TestFileFormat/built-in-test-functions/-expecterror-expected-error-message.md +++ b/docs/TestFileFormat/built-in-test-functions/-expecterror-expected-error-message.md @@ -1,6 +1,6 @@ --- layout: default -title: `-- expectError ("")` +title: "`-- expectError ("")`" permalink: /docs/TestFileFormat#-expecterror-expected-error-message parent: Built-in Test Functions grand_parent: Test File Format diff --git a/docs/TestFileFormat/built-in-test-functions/-testcompile.md b/docs/TestFileFormat/built-in-test-functions/-testcompile.md index 3ceafbde..9cf72b0f 100644 --- a/docs/TestFileFormat/built-in-test-functions/-testcompile.md +++ b/docs/TestFileFormat/built-in-test-functions/-testcompile.md @@ -1,6 +1,6 @@ --- layout: default -title: `-- testCompile()` +title: "`-- testCompile()`" permalink: /docs/TestFileFormat#-testcompile parent: Built-in Test Functions grand_parent: Test File Format diff --git a/docs/TestFileFormat/built-in-test-functions/-testconsole.md b/docs/TestFileFormat/built-in-test-functions/-testconsole.md index 965fb23d..7e06f9d1 100644 --- a/docs/TestFileFormat/built-in-test-functions/-testconsole.md +++ b/docs/TestFileFormat/built-in-test-functions/-testconsole.md @@ -1,6 +1,6 @@ --- layout: default -title: `-- testConsole()` +title: "`-- testConsole()`" permalink: /docs/TestFileFormat#-testconsole parent: Built-in Test Functions grand_parent: Test File Format diff --git a/docs/TestFileFormat/built-in-test-functions/-testfunction.md b/docs/TestFileFormat/built-in-test-functions/-testfunction.md index 848156f3..cb197d08 100644 --- a/docs/TestFileFormat/built-in-test-functions/-testfunction.md +++ b/docs/TestFileFormat/built-in-test-functions/-testfunction.md @@ -1,6 +1,6 @@ --- layout: default -title: `-- testFunction` +title: "`-- testFunction`" permalink: /docs/TestFileFormat#-testfunction parent: Built-in Test Functions grand_parent: Test File Format diff --git a/docs/TestFileFormat/built-in-test-functions/-testprocessor.md b/docs/TestFileFormat/built-in-test-functions/-testprocessor.md index ff7f2cc1..a328a481 100644 --- a/docs/TestFileFormat/built-in-test-functions/-testprocessor.md +++ b/docs/TestFileFormat/built-in-test-functions/-testprocessor.md @@ -1,6 +1,6 @@ --- layout: default -title: `-- testProcessor()` +title: "`-- testProcessor()`" permalink: /docs/TestFileFormat#-testprocessor parent: Built-in Test Functions grand_parent: Test File Format diff --git a/docs/TestFileFormat/special-section-delimiters/-disabled-test.md b/docs/TestFileFormat/special-section-delimiters/-disabled-test.md index 50165928..5e257475 100644 --- a/docs/TestFileFormat/special-section-delimiters/-disabled-test.md +++ b/docs/TestFileFormat/special-section-delimiters/-disabled-test.md @@ -1,6 +1,6 @@ --- layout: default -title: `-- disabled [test...]` +title: "`-- disabled [test...]`" permalink: /docs/TestFileFormat#-disabled-test parent: Special Section Delimiters grand_parent: Test File Format diff --git a/docs/TestFileFormat/special-section-delimiters/-global.md b/docs/TestFileFormat/special-section-delimiters/-global.md index 18587b4e..240f673f 100644 --- a/docs/TestFileFormat/special-section-delimiters/-global.md +++ b/docs/TestFileFormat/special-section-delimiters/-global.md @@ -1,6 +1,6 @@ --- layout: default -title: `-- global` +title: "`-- global`" permalink: /docs/TestFileFormat#-global parent: Special Section Delimiters grand_parent: Test File Format diff --git a/docs/Tools/CppAPI/helper-classes/cmajaudiomidiperformer.md b/docs/Tools/CppAPI/helper-classes/cmajaudiomidiperformer.md index d74f02dd..b85aa408 100644 --- a/docs/Tools/CppAPI/helper-classes/cmajaudiomidiperformer.md +++ b/docs/Tools/CppAPI/helper-classes/cmajaudiomidiperformer.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::AudioMIDIPerformer` +title: "`cmaj::AudioMIDIPerformer`" permalink: /docs/Tools/C++API#cmajaudiomidiperformer parent: Helper classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/helper-classes/cmajgeneratedcppengine.md b/docs/Tools/CppAPI/helper-classes/cmajgeneratedcppengine.md index 5cd7299b..0f20c7fe 100644 --- a/docs/Tools/CppAPI/helper-classes/cmajgeneratedcppengine.md +++ b/docs/Tools/CppAPI/helper-classes/cmajgeneratedcppengine.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::GeneratedCppEngine` +title: "`cmaj::GeneratedCppEngine`" permalink: /docs/Tools/C++API#cmajgeneratedcppengine parent: Helper classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/helper-classes/cmajjucepluginbase-and-cmajjucepluginformat.md b/docs/Tools/CppAPI/helper-classes/cmajjucepluginbase-and-cmajjucepluginformat.md index 1e40e560..ed751286 100644 --- a/docs/Tools/CppAPI/helper-classes/cmajjucepluginbase-and-cmajjucepluginformat.md +++ b/docs/Tools/CppAPI/helper-classes/cmajjucepluginbase-and-cmajjucepluginformat.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::JUCEPluginBase` and `cmaj::JUCEPluginFormat` +title: "`cmaj::JUCEPluginBase` and `cmaj::JUCEPluginFormat`" permalink: /docs/Tools/C++API#cmajjucepluginbase-and-cmajjucepluginformat parent: Helper classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/helper-classes/cmajpatch.md b/docs/Tools/CppAPI/helper-classes/cmajpatch.md index b50e99c5..92d2424d 100644 --- a/docs/Tools/CppAPI/helper-classes/cmajpatch.md +++ b/docs/Tools/CppAPI/helper-classes/cmajpatch.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::Patch` +title: "`cmaj::Patch`" permalink: /docs/Tools/C++API#cmajpatch parent: Helper classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/helper-classes/cmajpatchmanifest.md b/docs/Tools/CppAPI/helper-classes/cmajpatchmanifest.md index 109ffe70..1385edc4 100644 --- a/docs/Tools/CppAPI/helper-classes/cmajpatchmanifest.md +++ b/docs/Tools/CppAPI/helper-classes/cmajpatchmanifest.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::PatchManifest` +title: "`cmaj::PatchManifest`" permalink: /docs/Tools/C++API#cmajpatchmanifest parent: Helper classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/main-api-classes/cmajbuildsettings.md b/docs/Tools/CppAPI/main-api-classes/cmajbuildsettings.md index b1adce41..da29e26d 100644 --- a/docs/Tools/CppAPI/main-api-classes/cmajbuildsettings.md +++ b/docs/Tools/CppAPI/main-api-classes/cmajbuildsettings.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::BuildSettings` +title: "`cmaj::BuildSettings`" permalink: /docs/Tools/C++API#cmajbuildsettings parent: Main API classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/main-api-classes/cmajdiagnosticmessagelist.md b/docs/Tools/CppAPI/main-api-classes/cmajdiagnosticmessagelist.md index 2fc9d83d..ffbf5686 100644 --- a/docs/Tools/CppAPI/main-api-classes/cmajdiagnosticmessagelist.md +++ b/docs/Tools/CppAPI/main-api-classes/cmajdiagnosticmessagelist.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::DiagnosticMessageList` +title: "`cmaj::DiagnosticMessageList`" permalink: /docs/Tools/C++API#cmajdiagnosticmessagelist parent: Main API classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/main-api-classes/cmajendpointdetails-cmajendpointdetailslist.md b/docs/Tools/CppAPI/main-api-classes/cmajendpointdetails-cmajendpointdetailslist.md index 79269887..ae6f18ae 100644 --- a/docs/Tools/CppAPI/main-api-classes/cmajendpointdetails-cmajendpointdetailslist.md +++ b/docs/Tools/CppAPI/main-api-classes/cmajendpointdetails-cmajendpointdetailslist.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::EndpointDetails`, `cmaj::EndpointDetailsList` +title: "`cmaj::EndpointDetails`, `cmaj::EndpointDetailsList`" permalink: /docs/Tools/C++API#cmajendpointdetails-cmajendpointdetailslist parent: Main API classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/main-api-classes/cmajengine.md b/docs/Tools/CppAPI/main-api-classes/cmajengine.md index c2c4cb30..2168ee1e 100644 --- a/docs/Tools/CppAPI/main-api-classes/cmajengine.md +++ b/docs/Tools/CppAPI/main-api-classes/cmajengine.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::Engine` +title: "`cmaj::Engine`" permalink: /docs/Tools/C++API#cmajengine parent: Main API classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/main-api-classes/cmajperformer.md b/docs/Tools/CppAPI/main-api-classes/cmajperformer.md index 56b445f0..ba38ebcc 100644 --- a/docs/Tools/CppAPI/main-api-classes/cmajperformer.md +++ b/docs/Tools/CppAPI/main-api-classes/cmajperformer.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::Performer` +title: "`cmaj::Performer`" permalink: /docs/Tools/C++API#cmajperformer parent: Main API classes grand_parent: C++ API diff --git a/docs/Tools/CppAPI/main-api-classes/cmajprogram.md b/docs/Tools/CppAPI/main-api-classes/cmajprogram.md index cf7ee0e1..9407e909 100644 --- a/docs/Tools/CppAPI/main-api-classes/cmajprogram.md +++ b/docs/Tools/CppAPI/main-api-classes/cmajprogram.md @@ -1,6 +1,6 @@ --- layout: default -title: `cmaj::Program` +title: "`cmaj::Program`" permalink: /docs/Tools/C++API#cmajprogram parent: Main API classes grand_parent: C++ API diff --git a/docs/Tools/Inference/onnx/currently-supported-onnx-operators.md b/docs/Tools/Inference/onnx/currently-supported-onnx-operators.md index ac8ab79a..ee80d92b 100644 --- a/docs/Tools/Inference/onnx/currently-supported-onnx-operators.md +++ b/docs/Tools/Inference/onnx/currently-supported-onnx-operators.md @@ -1,6 +1,6 @@ --- layout: default -title: Currently supported ONNX Operators: +title: "Currently supported ONNX Operators:" permalink: /docs/Tools/MachineLearning#currently-supported-onnx-operators parent: ONNX grand_parent: Machine Learning diff --git a/run_local_server.sh b/run_local_server.sh new file mode 100755 index 00000000..74cfad12 --- /dev/null +++ b/run_local_server.sh @@ -0,0 +1 @@ +bundle exec jekyll serve --incremental --watch