osci-render/Resources/oscilloscope/juce/index.js

493 wiersze
14 KiB
JavaScript

/*
==============================================================================
This file is part of the JUCE framework.
Copyright (c) Raw Material Software Limited
JUCE is an open source framework subject to commercial or open source
licensing.
By downloading, installing, or using the JUCE framework, or combining the
JUCE framework with any other source code, object code, content or any other
copyrightable work, you agree to the terms of the JUCE End User Licence
Agreement, and all incorporated terms including the JUCE Privacy Policy and
the JUCE Website Terms of Service, as applicable, which will bind you. If you
do not agree to the terms of these agreements, we will not license the JUCE
framework to you, and you must discontinue the installation or download
process and cease use of the JUCE framework.
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
Or:
You may also use this code under the terms of the AGPLv3:
https://www.gnu.org/licenses/agpl-3.0.en.html
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
import "./check_native_interop.js";
class PromiseHandler {
constructor() {
this.lastPromiseId = 0;
this.promises = new Map();
window.__JUCE__.backend.addEventListener(
"__juce__complete",
({ promiseId, result }) => {
if (this.promises.has(promiseId)) {
this.promises.get(promiseId).resolve(result);
this.promises.delete(promiseId);
}
}
);
}
createPromise() {
const promiseId = this.lastPromiseId++;
const result = new Promise((resolve, reject) => {
this.promises.set(promiseId, { resolve: resolve, reject: reject });
});
return [promiseId, result];
}
}
const promiseHandler = new PromiseHandler();
/**
* Returns a function object that calls a function registered on the JUCE backend and forwards all
* parameters to it.
*
* The provided name should be the same as the name argument passed to
* WebBrowserComponent::Options.withNativeFunction() on the backend.
*
* @param {String} name
*/
function getNativeFunction(name) {
if (!window.__JUCE__.initialisationData.__juce__functions.includes(name))
console.warn(
`Creating native function binding for '${name}', which is unknown to the backend`
);
const f = function () {
const [promiseId, result] = promiseHandler.createPromise();
window.__JUCE__.backend.emitEvent("__juce__invoke", {
name: name,
params: Array.prototype.slice.call(arguments),
resultId: promiseId,
});
return result;
};
return f;
}
//==============================================================================
class ListenerList {
constructor() {
this.listeners = new Map();
this.listenerId = 0;
}
addListener(fn) {
const newListenerId = this.listenerId++;
this.listeners.set(newListenerId, fn);
return newListenerId;
}
removeListener(id) {
if (this.listeners.has(id)) {
this.listeners.delete(id);
}
}
callListeners(payload) {
for (const [, value] of this.listeners) {
value(payload);
}
}
}
const BasicControl_valueChangedEventId = "valueChanged";
const BasicControl_propertiesChangedId = "propertiesChanged";
class SliderState {
constructor(name) {
if (!window.__JUCE__.initialisationData.__juce__sliders.includes(name))
console.warn(
"Creating SliderState for '" +
name +
"', which is unknown to the backend"
);
this.name = name;
this.identifier = "__juce__slider" + this.name;
this.scaledValue = 0;
this.properties = {
start: 0,
end: 1,
skew: 1,
name: "",
label: "",
numSteps: 100,
interval: 0,
parameterIndex: -1,
};
this.valueChangedEvent = new ListenerList();
this.propertiesChangedEvent = new ListenerList();
window.__JUCE__.backend.addEventListener(this.identifier, (event) =>
this.handleEvent(event)
);
window.__JUCE__.backend.emitEvent(this.identifier, {
eventType: "requestInitialUpdate",
});
}
setNormalisedValue(newValue) {
this.scaledValue = this.snapToLegalValue(
this.normalisedToScaledValue(newValue)
);
window.__JUCE__.backend.emitEvent(this.identifier, {
eventType: BasicControl_valueChangedEventId,
value: this.scaledValue,
});
}
sliderDragStarted() {}
sliderDragEnded() {}
handleEvent(event) {
if (event.eventType == BasicControl_valueChangedEventId) {
this.scaledValue = event.value;
this.valueChangedEvent.callListeners();
}
if (event.eventType == BasicControl_propertiesChangedId) {
// eslint-disable-next-line no-unused-vars
let { eventType: _, ...rest } = event;
this.properties = rest;
this.propertiesChangedEvent.callListeners();
}
}
getScaledValue() {
return this.scaledValue;
}
getNormalisedValue() {
return Math.pow(
(this.scaledValue - this.properties.start) /
(this.properties.end - this.properties.start),
this.properties.skew
);
}
normalisedToScaledValue(normalisedValue) {
return (
Math.pow(normalisedValue, 1 / this.properties.skew) *
(this.properties.end - this.properties.start) +
this.properties.start
);
}
snapToLegalValue(value) {
const interval = this.properties.interval;
if (interval == 0) return value;
const start = this.properties.start;
const clamp = (val, min = 0, max = 1) => Math.max(min, Math.min(max, val));
return clamp(
start + interval * Math.floor((value - start) / interval + 0.5),
this.properties.start,
this.properties.end
);
}
}
const sliderStates = new Map();
for (const sliderName of window.__JUCE__.initialisationData.__juce__sliders)
sliderStates.set(sliderName, new SliderState(sliderName));
/**
* Returns a SliderState object that is connected to the backend WebSliderRelay object that was
* created with the same name argument.
*
* To register a WebSliderRelay object create one with the right name and add it to the
* WebBrowserComponent::Options struct using withOptionsFrom.
*
* @param {String} name
*/
function getSliderState(name) {
if (!sliderStates.has(name)) sliderStates.set(name, new SliderState(name));
return sliderStates.get(name);
}
class ToggleState {
constructor(name) {
if (!window.__JUCE__.initialisationData.__juce__toggles.includes(name))
console.warn(
"Creating ToggleState for '" +
name +
"', which is unknown to the backend"
);
this.name = name;
this.identifier = "__juce__toggle" + this.name;
this.value = false;
this.properties = {
name: "",
parameterIndex: -1,
};
this.valueChangedEvent = new ListenerList();
this.propertiesChangedEvent = new ListenerList();
window.__JUCE__.backend.addEventListener(this.identifier, (event) =>
this.handleEvent(event)
);
window.__JUCE__.backend.emitEvent(this.identifier, {
eventType: "requestInitialUpdate",
});
}
getValue() {
return this.value;
}
setValue(newValue) {
this.value = newValue;
window.__JUCE__.backend.emitEvent(this.identifier, {
eventType: BasicControl_valueChangedEventId,
value: this.value,
});
}
handleEvent(event) {
if (event.eventType == BasicControl_valueChangedEventId) {
this.value = event.value;
this.valueChangedEvent.callListeners();
}
if (event.eventType == BasicControl_propertiesChangedId) {
// eslint-disable-next-line no-unused-vars
let { eventType: _, ...rest } = event;
this.properties = rest;
this.propertiesChangedEvent.callListeners();
}
}
}
const toggleStates = new Map();
for (const name of window.__JUCE__.initialisationData.__juce__toggles)
toggleStates.set(name, new ToggleState(name));
/**
* Returns a ToggleState object that is connected to the backend WebToggleButtonRelay object that was
* created with the same name argument.
*
* To register a WebToggleButtonRelay object create one with the right name and add it to the
* WebBrowserComponent::Options struct using withOptionsFrom.
*
* @param {String} name
*/
function getToggleState(name) {
if (!toggleStates.has(name)) toggleStates.set(name, new ToggleState(name));
return toggleStates.get(name);
}
class ComboBoxState {
constructor(name) {
if (!window.__JUCE__.initialisationData.__juce__comboBoxes.includes(name))
console.warn(
"Creating ComboBoxState for '" +
name +
"', which is unknown to the backend"
);
this.name = name;
this.identifier = "__juce__comboBox" + this.name;
this.value = 0.0;
this.properties = {
name: "",
parameterIndex: -1,
choices: [],
};
this.valueChangedEvent = new ListenerList();
this.propertiesChangedEvent = new ListenerList();
window.__JUCE__.backend.addEventListener(this.identifier, (event) =>
this.handleEvent(event)
);
window.__JUCE__.backend.emitEvent(this.identifier, {
eventType: "requestInitialUpdate",
});
}
getChoiceIndex() {
return Math.round(this.value * (this.properties.choices.length - 1));
}
setChoiceIndex(index) {
const numItems = this.properties.choices.length;
this.value = numItems > 1 ? index / (numItems - 1) : 0.0;
window.__JUCE__.backend.emitEvent(this.identifier, {
eventType: BasicControl_valueChangedEventId,
value: this.value,
});
}
handleEvent(event) {
if (event.eventType == BasicControl_valueChangedEventId) {
this.value = event.value;
this.valueChangedEvent.callListeners();
}
if (event.eventType == BasicControl_propertiesChangedId) {
// eslint-disable-next-line no-unused-vars
let { eventType: _, ...rest } = event;
this.properties = rest;
this.propertiesChangedEvent.callListeners();
}
}
}
const comboBoxStates = new Map();
for (const name of window.__JUCE__.initialisationData.__juce__comboBoxes)
comboBoxStates.set(name, new ComboBoxState(name));
/**
* Returns a ComboBoxState object that is connected to the backend WebComboBoxRelay object that was
* created with the same name argument.
*
* To register a WebComboBoxRelay object create one with the right name and add it to the
* WebBrowserComponent::Options struct using withOptionsFrom.
*
* @param {String} name
*/
function getComboBoxState(name) {
if (!comboBoxStates.has(name))
comboBoxStates.set(name, new ComboBoxState(name));
return comboBoxStates.get(name);
}
/**
* Appends a platform-specific prefix to the path to ensure that a request sent to this address will
* be received by the backend's ResourceProvider.
* @param {String} path
*/
function getBackendResourceAddress(path) {
const platform =
window.__JUCE__.initialisationData.__juce__platform.length > 0
? window.__JUCE__.initialisationData.__juce__platform[0]
: "";
if (platform == "windows" || platform == "android")
return "https://juce.backend/" + path;
if (platform == "macos" || platform == "ios" || platform == "linux")
return "juce://juce.backend/" + path;
console.warn(
"getBackendResourceAddress() called, but no JUCE native backend is detected."
);
return path;
}
/**
* This helper class is intended to aid the implementation of
* AudioProcessorEditor::getControlParameterIndex() for editors using a WebView interface.
*
* Create an instance of this class and call its handleMouseMove() method in each mousemove event.
*
* This class can be used to continuously report the controlParameterIndexAnnotation attribute's
* value related to the DOM element that is currently under the mouse pointer.
*
* This value is defined at all times as follows
* * the annotation attribute's value for the DOM element directly under the mouse, if it has it,
* * the annotation attribute's value for the first parent element, that has it,
* * -1 otherwise.
*
* Whenever there is a change in this value, an event is emitted to the frontend with the new value.
* You can use a ControlParameterIndexReceiver object on the backend to listen to these events.
*
* @param {String} controlParameterIndexAnnotation
*/
class ControlParameterIndexUpdater {
constructor(controlParameterIndexAnnotation) {
this.controlParameterIndexAnnotation = controlParameterIndexAnnotation;
this.lastElement = null;
this.lastControlParameterIndex = null;
}
handleMouseMove(event) {
const currentElement = document.elementFromPoint(
event.clientX,
event.clientY
);
if (currentElement === this.lastElement) return;
this.lastElement = currentElement;
let controlParameterIndex = -1;
if (currentElement !== null)
controlParameterIndex = this.#getControlParameterIndex(currentElement);
if (controlParameterIndex === this.lastControlParameterIndex) return;
this.lastControlParameterIndex = controlParameterIndex;
window.__JUCE__.backend.emitEvent(
"__juce__controlParameterIndexChanged",
controlParameterIndex
);
}
//==============================================================================
#getControlParameterIndex(element) {
const isValidNonRootElement = (e) => {
return e !== null && e !== document.documentElement;
};
while (isValidNonRootElement(element)) {
if (element.hasAttribute(this.controlParameterIndexAnnotation)) {
return element.getAttribute(this.controlParameterIndexAnnotation);
}
element = element.parentElement;
}
return -1;
}
}
export {
getNativeFunction,
getSliderState,
getToggleState,
getComboBoxState,
getBackendResourceAddress,
ControlParameterIndexUpdater,
};