kopia lustrzana https://github.com/jameshball/osci-render
Get audio sent to visualiser, and add buttons
rodzic
0e62c7667a
commit
bda414de92
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof window.__JUCE__ !== "undefined" &&
|
||||||
|
typeof window.__JUCE__.getAndroidUserScripts !== "undefined" &&
|
||||||
|
typeof window.inAndroidUserScriptEval === "undefined"
|
||||||
|
) {
|
||||||
|
window.inAndroidUserScriptEval = true;
|
||||||
|
eval(window.__JUCE__.getAndroidUserScripts());
|
||||||
|
delete window.inAndroidUserScriptEval;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if (typeof window.__JUCE__ === "undefined") {
|
||||||
|
console.warn(
|
||||||
|
"The 'window.__JUCE__' object is undefined." +
|
||||||
|
" Native integration features will not work." +
|
||||||
|
" Defining a placeholder 'window.__JUCE__' object."
|
||||||
|
);
|
||||||
|
|
||||||
|
window.__JUCE__ = {
|
||||||
|
postMessage: function () {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window.__JUCE__.initialisationData === "undefined") {
|
||||||
|
window.__JUCE__.initialisationData = {
|
||||||
|
__juce__platform: [],
|
||||||
|
__juce__functions: [],
|
||||||
|
__juce__registeredGlobalEventIds: [],
|
||||||
|
__juce__sliders: [],
|
||||||
|
__juce__toggles: [],
|
||||||
|
__juce__comboBoxes: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventListenerList {
|
||||||
|
constructor() {
|
||||||
|
this.eventListeners = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener(eventId, fn) {
|
||||||
|
if (!this.eventListeners.has(eventId))
|
||||||
|
this.eventListeners.set(eventId, new ListenerList());
|
||||||
|
|
||||||
|
const id = this.eventListeners.get(eventId).addListener(fn);
|
||||||
|
|
||||||
|
return [eventId, id];
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEventListener([eventId, id]) {
|
||||||
|
if (this.eventListeners.has(eventId)) {
|
||||||
|
this.eventListeners.get(eventId).removeListener(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitEvent(eventId, object) {
|
||||||
|
if (this.eventListeners.has(eventId))
|
||||||
|
this.eventListeners.get(eventId).callListeners(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Backend {
|
||||||
|
constructor() {
|
||||||
|
this.listeners = new EventListenerList();
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener(eventId, fn) {
|
||||||
|
return this.listeners.addEventListener(eventId, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEventListener([eventId, id]) {
|
||||||
|
this.listeners.removeEventListener(eventId, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitEvent(eventId, object) {
|
||||||
|
window.__JUCE__.postMessage(
|
||||||
|
JSON.stringify({ eventId: eventId, payload: object })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitByBackend(eventId, object) {
|
||||||
|
this.listeners.emitEvent(eventId, JSON.parse(object));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window.__JUCE__.backend === "undefined")
|
||||||
|
window.__JUCE__.backend = new Backend();
|
||||||
|
}
|
|
@ -0,0 +1,492 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "juce-framework-frontend",
|
||||||
|
"version": "7.0.7"
|
||||||
|
}
|
|
@ -1,72 +1,146 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
audio { width: 100%; }
|
audio {
|
||||||
body{font: 12px Courier, Monospace;}
|
width: 100%;
|
||||||
canvas{margin-right: 10px;}
|
}
|
||||||
table {
|
|
||||||
border-spacing:0;
|
body {
|
||||||
border-collapse: collapse;
|
font: 12px Courier, Monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-spacing:0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
#buttonRow {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#buttonRow button {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: transparent;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
outline: none;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#buttonRow button:hover {
|
||||||
|
filter: brightness(70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#buttonRow button:active {
|
||||||
|
filter: brightness(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#fullscreen {
|
||||||
|
background: url(fullscreen.svg) no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popout {
|
||||||
|
background: url(open_in_new.svg) no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings {
|
||||||
|
background: url(cog.svg) no-repeat;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body bgcolor="silver" text="black" autocomplete="off" style="margin: 0px;">
|
<body bgcolor="silver" text="black" autocomplete="off" style="margin: 0px;">
|
||||||
|
|
||||||
|
<div id="buttonRow">
|
||||||
|
<button id="fullscreen"/>
|
||||||
|
<button id="popout"/>
|
||||||
|
<button id="settings"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var controls=
|
var controls=
|
||||||
{
|
{
|
||||||
swapXY : false,
|
swapXY : false,
|
||||||
sweepOn : false,
|
sweepOn : false,
|
||||||
sweepMsDiv : 1,
|
sweepMsDiv : 1,
|
||||||
sweepTriggerValue : 0,
|
sweepTriggerValue : 0,
|
||||||
signalGeneratorOn : false,
|
signalGeneratorOn : false,
|
||||||
mainGain : 0.0,
|
mainGain : 0.0,
|
||||||
exposureStops : 0.0,
|
exposureStops : 0.0,
|
||||||
audioVolume : 1.0,
|
audioVolume : 1.0,
|
||||||
hue : 125,
|
hue : 125,
|
||||||
freezeImage: false,
|
freezeImage: false,
|
||||||
disableFilter: false,
|
disableFilter: false,
|
||||||
aValue : 1.0,
|
aValue : 1.0,
|
||||||
aExponent : 0.0,
|
aExponent : 0.0,
|
||||||
bValue : 1.0,
|
bValue : 1.0,
|
||||||
bExponent :0.0,
|
bExponent :0.0,
|
||||||
invertXY : false,
|
invertXY : false,
|
||||||
grid : true,
|
grid : true,
|
||||||
persistence : 0,
|
persistence : 0,
|
||||||
xExpression : "sin(2*PI*a*t)*cos(2*PI*b*t)",
|
xExpression : "sin(2*PI*a*t)*cos(2*PI*b*t)",
|
||||||
yExpression : "cos(2*PI*a*t)*cos(2*PI*b*t)",
|
yExpression : "cos(2*PI*a*t)*cos(2*PI*b*t)",
|
||||||
}
|
}
|
||||||
|
|
||||||
Number.prototype.toFixedMinus = function(k)
|
Number.prototype.toFixedMinus = function(k)
|
||||||
{
|
{
|
||||||
if (this<0) return this.toFixed(k);
|
if (this<0) return this.toFixed(k);
|
||||||
//else return '\xa0'+this.toFixed(k);
|
//else return '\xa0'+this.toFixed(k);
|
||||||
else return '+'+this.toFixed(k);
|
else return '+'+this.toFixed(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
var toggleVisible = function(string)
|
|
||||||
{
|
|
||||||
var element = document.getElementById(string);
|
|
||||||
console.log(element.style.display);
|
|
||||||
if (element.style.display == "none") element.style.display="block";
|
|
||||||
else element.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var toggleVisible = function(string)
|
||||||
|
{
|
||||||
|
var element = document.getElementById(string);
|
||||||
|
console.log(element.style.display);
|
||||||
|
if (element.style.display == "none") element.style.display="block";
|
||||||
|
else element.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout;
|
||||||
|
document.addEventListener("mousemove", function() {
|
||||||
|
const buttons = document.getElementById('buttonRow');
|
||||||
|
buttons.style.display = "block";
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
timeout = setTimeout(function() {
|
||||||
|
buttons.style.display = "none";
|
||||||
|
}, 1000)
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import * as Juce from "./index.js";
|
||||||
|
|
||||||
|
const fullscreen = document.getElementById('fullscreen');
|
||||||
|
const toggleFullscreen = Juce.getNativeFunction("toggleFullscreen");
|
||||||
|
|
||||||
|
document.addEventListener("dblclick", function() {
|
||||||
|
toggleFullscreen();
|
||||||
|
});
|
||||||
|
|
||||||
|
fullscreen.onclick = function() {
|
||||||
|
toggleFullscreen();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<table align="center">
|
<table align="center">
|
||||||
<tr>
|
<tr>
|
||||||
|
|
||||||
<td valign="top" style="padding: 0px;">
|
<td valign="top" style="padding: 0px;">
|
||||||
<canvas id="crtCanvas" width="800" height="800"></canvas>
|
<canvas id="crtCanvas" width="800" height="800"></canvas>
|
||||||
<b id="clicktostart" style="position: absolute; z-index: 1; left: 100px; top: 50px; width:200px; height:40px; font-size:18px; color: rgb(116, 187, 116);">
|
|
||||||
[CLICK TO START]
|
|
||||||
</b>
|
|
||||||
</td>
|
</td>
|
||||||
<td width="360" valign="top">
|
<td width="360" valign="top">
|
||||||
|
|
||||||
|
@ -659,4 +733,4 @@ var Controls = {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="oscilloscope.js"></script>
|
<script src="oscilloscope.js" type="module"></script>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import * as Juce from "./index.js";
|
||||||
|
|
||||||
var AudioSystem =
|
var AudioSystem =
|
||||||
{
|
{
|
||||||
|
@ -5,160 +6,19 @@ var AudioSystem =
|
||||||
|
|
||||||
init : function (bufferSize)
|
init : function (bufferSize)
|
||||||
{
|
{
|
||||||
window.AudioContext = window.AudioContext||window.webkitAudioContext;
|
this.sampleRate = 96000;
|
||||||
this.audioContext = new window.AudioContext();
|
|
||||||
this.sampleRate = this.audioContext.sampleRate;
|
|
||||||
this.bufferSize = bufferSize;
|
this.bufferSize = bufferSize;
|
||||||
this.timePerSample = 1/this.sampleRate;
|
this.timePerSample = 1/this.sampleRate;
|
||||||
this.oldXSamples = new Float32Array(this.bufferSize);
|
this.oldXSamples = new Float32Array(this.bufferSize);
|
||||||
this.oldYSamples = new Float32Array(this.bufferSize);
|
this.oldYSamples = new Float32Array(this.bufferSize);
|
||||||
this.smoothedXSamples = new Float32Array(Filter.nSmoothedSamples);
|
this.smoothedXSamples = new Float32Array(Filter.nSmoothedSamples);
|
||||||
this.smoothedYSamples = new Float32Array(Filter.nSmoothedSamples);
|
this.smoothedYSamples = new Float32Array(Filter.nSmoothedSamples);
|
||||||
|
|
||||||
if (!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia))
|
|
||||||
{
|
|
||||||
microphoneOutput.value = " unavailable in this browser";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
startSound : function()
|
startSound : function()
|
||||||
{
|
{
|
||||||
var audioElement = document.getElementById("audioElement");
|
const audioUpdated = window.__JUCE__.backend.addEventListener("audioUpdated", doScriptProcessor);
|
||||||
this.source = this.audioContext.createMediaElementSource(audioElement);
|
}
|
||||||
this.audioVolumeNode = this.audioContext.createGain();
|
|
||||||
|
|
||||||
this.generator = this.audioContext.createScriptProcessor(this.bufferSize, 0, 2);
|
|
||||||
this.generator.onaudioprocess = SignalGenerator.generate;
|
|
||||||
|
|
||||||
this.scopeNode = this.audioContext.createScriptProcessor(this.bufferSize, 2, 2);
|
|
||||||
this.scopeNode.onaudioprocess = doScriptProcessor;
|
|
||||||
this.source.connect(this.scopeNode);
|
|
||||||
this.generator.connect(this.scopeNode);
|
|
||||||
|
|
||||||
this.scopeNode.connect(this.audioVolumeNode);
|
|
||||||
this.audioVolumeNode.connect(this.audioContext.destination);
|
|
||||||
},
|
|
||||||
|
|
||||||
tryToGetMicrophone : function()
|
|
||||||
{
|
|
||||||
if (this.microphoneActive)
|
|
||||||
{
|
|
||||||
AudioSystem.microphone.connect(AudioSystem.scopeNode);
|
|
||||||
audioVolume.value = 0.0;
|
|
||||||
audioVolume.oninput();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var constraints = {audio: { mandatory: { echoCancellation: false }}};
|
|
||||||
//var constraints = {audio: {echoCancellation: false} };
|
|
||||||
navigator.getUserMedia = navigator.getUserMedia ||
|
|
||||||
navigator.webkitGetUserMedia ||
|
|
||||||
navigator.mozGetUserMedia;
|
|
||||||
if (navigator.getUserMedia)
|
|
||||||
{
|
|
||||||
navigator.getUserMedia(constraints, onStream, function(){micCheckbox.checked = false;});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
micCheckbox.checked = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
disconnectMicrophone : function()
|
|
||||||
{
|
|
||||||
if (this.microphone) this.microphone.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onStream = function(stream)
|
|
||||||
{
|
|
||||||
AudioSystem.microphoneActive = true;
|
|
||||||
AudioSystem.microphone = AudioSystem.audioContext.createMediaStreamSource(stream);
|
|
||||||
AudioSystem.microphone.connect(AudioSystem.scopeNode);
|
|
||||||
|
|
||||||
audioVolume.value = 0.0;
|
|
||||||
audioVolume.oninput();
|
|
||||||
};
|
|
||||||
|
|
||||||
var SignalGenerator =
|
|
||||||
{
|
|
||||||
oldA : 1.0,
|
|
||||||
oldB : 1.0,
|
|
||||||
timeInSamples : 0,
|
|
||||||
|
|
||||||
generate : function(event)
|
|
||||||
{
|
|
||||||
var xOut = event.outputBuffer.getChannelData(0);
|
|
||||||
var yOut = event.outputBuffer.getChannelData(1);
|
|
||||||
var newA = controls.aValue * Math.pow(10.0, controls.aExponent);
|
|
||||||
var newB = controls.bValue * Math.pow(10.0, controls.bExponent);
|
|
||||||
var oldA = SignalGenerator.oldA;
|
|
||||||
var oldB = SignalGenerator.oldB;
|
|
||||||
var PI = Math.PI;
|
|
||||||
var cos = Math.cos;
|
|
||||||
var sin = Math.sin;
|
|
||||||
var xFunc = eval("(function xFunc(){return "+controls.xExpression+";})");
|
|
||||||
var yFunc = eval("(function yFunc(){return "+controls.yExpression+";})");
|
|
||||||
var bufferSize = AudioSystem.bufferSize;
|
|
||||||
var timeInSamples = SignalGenerator.timeInSamples;
|
|
||||||
var sampleRate = AudioSystem.sampleRate;
|
|
||||||
var x = 0.0;
|
|
||||||
var y = 0.0;
|
|
||||||
if (!controls.signalGeneratorOn)
|
|
||||||
{
|
|
||||||
for (var i=0; i<bufferSize; i++)
|
|
||||||
{
|
|
||||||
xOut[i] = 0;
|
|
||||||
yOut[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ((newA == oldA) && (newB == oldB))
|
|
||||||
{
|
|
||||||
var n = timeInSamples;
|
|
||||||
for (var i=0; i<bufferSize; i++)
|
|
||||||
{
|
|
||||||
var t = n/sampleRate;
|
|
||||||
var a = newA;
|
|
||||||
var b = newB;
|
|
||||||
x = xFunc();
|
|
||||||
y = yFunc();
|
|
||||||
xOut[i] = x;
|
|
||||||
yOut[i] = y;
|
|
||||||
n += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var n = timeInSamples;
|
|
||||||
for (var i=0; i<bufferSize; i++)
|
|
||||||
{
|
|
||||||
var t = n/sampleRate;
|
|
||||||
|
|
||||||
var a = oldA;
|
|
||||||
var b = oldB;
|
|
||||||
var oldX = xFunc();
|
|
||||||
var oldY = yFunc();
|
|
||||||
a = newA;
|
|
||||||
b = newB;
|
|
||||||
var newX = xFunc();
|
|
||||||
var newY = yFunc();
|
|
||||||
var alpha_z = i/bufferSize;
|
|
||||||
x = oldX*(1.0-alpha_z)+newX*alpha_z;
|
|
||||||
y = oldY*(1.0-alpha_z)+newY*alpha_z;
|
|
||||||
|
|
||||||
xOut[i] = x;
|
|
||||||
yOut[i] = y;
|
|
||||||
n += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SignalGenerator.timeInSamples += AudioSystem.bufferSize;
|
|
||||||
SignalGenerator.oldA = newA;
|
|
||||||
SignalGenerator.oldB = newB;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var Filter =
|
var Filter =
|
||||||
|
@ -756,32 +616,6 @@ var Render =
|
||||||
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
||||||
},
|
},
|
||||||
|
|
||||||
drawSimpleLine : function(xSamples, ySamples, colour)
|
|
||||||
{
|
|
||||||
var nVertices = xSamples.length;
|
|
||||||
var vertices = new Float32Array(2*nVertices);
|
|
||||||
for (var i=0; i<nVertices; i++)
|
|
||||||
{
|
|
||||||
vertices[2*i] = xSamples[i];
|
|
||||||
vertices[2*i+1] = ySamples[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setAdditiveBlending();
|
|
||||||
|
|
||||||
var program = this.simpleShader;
|
|
||||||
gl.useProgram(program);
|
|
||||||
gl.enableVertexAttribArray(program.vertexPosition);
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
|
||||||
gl.vertexAttribPointer(program.vertexPosition, 2, gl.FLOAT, false, 0, 0);
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
||||||
if (colour=="green") gl.uniform4fv(program.colour, [0.01, 0.1, 0.01, 1.0]);
|
|
||||||
else if (colour == "red") gl.uniform4fv(program.colour, [0.1, 0.01, 0.01, 1.0]);
|
|
||||||
|
|
||||||
gl.lineWidth(3.0);
|
|
||||||
gl.drawArrays(gl.LINE_STRIP, 0, nVertices);
|
|
||||||
},
|
|
||||||
|
|
||||||
setAdditiveBlending : function()
|
setAdditiveBlending : function()
|
||||||
{
|
{
|
||||||
//gl.blendEquation( gl.FUNC_ADD );
|
//gl.blendEquation( gl.FUNC_ADD );
|
||||||
|
@ -861,82 +695,68 @@ var Render =
|
||||||
var sweepPosition = -1;
|
var sweepPosition = -1;
|
||||||
var belowTrigger = false;
|
var belowTrigger = false;
|
||||||
|
|
||||||
function doScriptProcessor(event)
|
function doScriptProcessor(event) {
|
||||||
{
|
fetch(Juce.getBackendResourceAddress("audio"))
|
||||||
var xSamplesRaw = event.inputBuffer.getChannelData(0);
|
.then((response) => response.arrayBuffer())
|
||||||
var ySamplesRaw = event.inputBuffer.getChannelData(1);
|
.then((buffer) => {
|
||||||
var xOut = event.outputBuffer.getChannelData(0);
|
var dataView = new DataView(buffer);
|
||||||
var yOut = event.outputBuffer.getChannelData(1);
|
|
||||||
|
|
||||||
var length = xSamplesRaw.length;
|
for (var i = 0; i < xSamples.length; i++) {
|
||||||
for (var i=0; i<length; i++)
|
xSamples[i] = dataView.getFloat32(i * 4 * 2, true);
|
||||||
{
|
ySamples[i] = dataView.getFloat32(i * 4 * 2 + 4, true);
|
||||||
xSamples[i] = xSamplesRaw[i];// + (Math.random()-0.5)*controls.noise/2000;
|
}
|
||||||
ySamples[i] = ySamplesRaw[i];// + (Math.random()-0.5)*controls.noise/2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controls.sweepOn)
|
if (controls.sweepOn) {
|
||||||
{
|
var gain = Math.pow(2.0,controls.mainGain);
|
||||||
var gain = Math.pow(2.0,controls.mainGain);
|
var sweepMinTime = controls.sweepMsDiv*10/1000;
|
||||||
var sweepMinTime = controls.sweepMsDiv*10/1000;
|
var triggerValue = controls.sweepTriggerValue;
|
||||||
var triggerValue = controls.sweepTriggerValue;
|
for (var i=0; i<xSamples.length; i++)
|
||||||
for (var i=0; i<length; i++)
|
{
|
||||||
{
|
xSamples[i] = sweepPosition / gain;
|
||||||
xSamples[i] = sweepPosition / gain;
|
sweepPosition += 2*AudioSystem.timePerSample/sweepMinTime;
|
||||||
sweepPosition += 2*AudioSystem.timePerSample/sweepMinTime;
|
if (sweepPosition > 1.1 && belowTrigger && ySamples[i]>=triggerValue)
|
||||||
if (sweepPosition > 1.1 && belowTrigger && ySamples[i]>=triggerValue)
|
sweepPosition =-1.3;
|
||||||
sweepPosition =-1.3;
|
belowTrigger = ySamples[i]<triggerValue;
|
||||||
belowTrigger = ySamples[i]<triggerValue;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!controls.freezeImage)
|
if (!controls.freezeImage)
|
||||||
{
|
{
|
||||||
if (!controls.disableFilter)
|
if (!controls.disableFilter)
|
||||||
{
|
{
|
||||||
Filter.generateSmoothedSamples(AudioSystem.oldXSamples, xSamples, AudioSystem.smoothedXSamples);
|
Filter.generateSmoothedSamples(AudioSystem.oldXSamples, xSamples, AudioSystem.smoothedXSamples);
|
||||||
Filter.generateSmoothedSamples(AudioSystem.oldYSamples, ySamples, AudioSystem.smoothedYSamples);
|
Filter.generateSmoothedSamples(AudioSystem.oldYSamples, ySamples, AudioSystem.smoothedYSamples);
|
||||||
|
|
||||||
if (!controls.swapXY) Render.drawLineTexture(AudioSystem.smoothedXSamples, AudioSystem.smoothedYSamples);
|
if (!controls.swapXY) Render.drawLineTexture(AudioSystem.smoothedXSamples, AudioSystem.smoothedYSamples);
|
||||||
else Render.drawLineTexture(AudioSystem.smoothedYSamples, AudioSystem.smoothedXSamples);
|
else Render.drawLineTexture(AudioSystem.smoothedYSamples, AudioSystem.smoothedXSamples);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!controls.swapXY) Render.drawLineTexture(xSamples, ySamples);
|
if (!controls.swapXY) Render.drawLineTexture(xSamples, ySamples);
|
||||||
else Render.drawLineTexture(ySamples, xSamples);
|
else Render.drawLineTexture(ySamples, xSamples);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i<length; i++)
|
for (var i = 0; i<xSamples.length; i++) {
|
||||||
{
|
AudioSystem.oldXSamples[i] = xSamples[i];
|
||||||
AudioSystem.oldXSamples[i] = xSamples[i];
|
AudioSystem.oldYSamples[i] = ySamples[i];
|
||||||
AudioSystem.oldYSamples[i] = ySamples[i];
|
}
|
||||||
xOut[i] = xSamplesRaw[i];
|
|
||||||
yOut[i] = ySamplesRaw[i];
|
requestAnimationFrame(drawCRTFrame);
|
||||||
}
|
});
|
||||||
|
|
||||||
AudioSystem.audioVolumeNode.gain.value = controls.audioVolume;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawCRTFrame(timeStamp)
|
function drawCRTFrame(timeStamp) {
|
||||||
{
|
|
||||||
Render.drawCRT();
|
Render.drawCRT();
|
||||||
requestAnimationFrame(drawCRTFrame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var xSamples = new Float32Array(1024);
|
var xSamples = new Float32Array(1920);
|
||||||
var ySamples = new Float32Array(1024);
|
var ySamples = new Float32Array(1920);
|
||||||
UI.init();
|
UI.init();
|
||||||
Render.init();
|
Render.init();
|
||||||
|
Filter.init(1920, 8, 6);
|
||||||
document.onclick = function(){ // quick fix to get around autoplay rules, May 2022
|
AudioSystem.init(1920);
|
||||||
document.onclick = null;
|
Render.setupArrays(Filter.nSmoothedSamples);
|
||||||
document.getElementById("clicktostart").remove();
|
AudioSystem.startSound();
|
||||||
//Filter.init(512, 10, 4);
|
requestAnimationFrame(drawCRTFrame);
|
||||||
Filter.init(1024, 8, 6);
|
Controls.setupControls();
|
||||||
AudioSystem.init(1024);
|
|
||||||
Render.setupArrays(Filter.nSmoothedSamples);
|
|
||||||
AudioSystem.startSound();
|
|
||||||
requestAnimationFrame(drawCRTFrame);
|
|
||||||
Controls.setupControls();
|
|
||||||
};
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></svg>
|
<svg fill="#ffffff" xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></svg>
|
Przed Szerokość: | Wysokość: | Rozmiar: 986 B Po Szerokość: | Wysokość: | Rozmiar: 1003 B |
|
@ -1 +1 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5,5H10V7H7V10H5V5M14,5H19V10H17V7H14V5M17,14H19V19H14V17H17V14M10,17V19H5V14H7V17H10Z" /></svg>
|
<svg preserveAspectRatio="none" fill="#ffffff" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5,5H10V7H7V10H5V5M14,5H19V10H17V7H14V5M17,14H19V19H14V17H17V14M10,17V19H5V14H7V17H10Z" /></svg>
|
Przed Szerokość: | Wysokość: | Rozmiar: 165 B Po Szerokość: | Wysokość: | Rozmiar: 207 B |
|
@ -1 +1 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" /></svg>
|
<svg fill="#ffffff" xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32"><path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" /></svg>
|
Przed Szerokość: | Wysokość: | Rozmiar: 211 B Po Szerokość: | Wysokość: | Rozmiar: 228 B |
|
@ -109,6 +109,7 @@ void VisualiserComponent::paint(juce::Graphics& g) {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
|
||||||
if (!active) {
|
if (!active) {
|
||||||
// add translucent layer
|
// add translucent layer
|
||||||
g.setColour(juce::Colours::black.withAlpha(0.5f));
|
g.setColour(juce::Colours::black.withAlpha(0.5f));
|
||||||
|
@ -134,6 +135,7 @@ void VisualiserComponent::run() {
|
||||||
|
|
||||||
consumer = audioProcessor.consumerRegister(tempBuffer);
|
consumer = audioProcessor.consumerRegister(tempBuffer);
|
||||||
audioProcessor.consumerRead(consumer);
|
audioProcessor.consumerRead(consumer);
|
||||||
|
browser.emitEventIfBrowserIsVisible("audioUpdated", {});
|
||||||
setBuffer(tempBuffer);
|
setBuffer(tempBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,7 +210,7 @@ bool VisualiserComponent::keyPressed(const juce::KeyPress& key) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VisualiserComponent::setFullScreen(bool fullScreen) {
|
void VisualiserComponent::setFullScreen(bool fullScreen) {
|
||||||
// useful as a callback from parent if needed
|
browser.goToURL(juce::WebBrowserComponent::getResourceProviderRoot() + "oscilloscope.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
void VisualiserComponent::paintChannel(juce::Graphics& g, juce::Rectangle<float> area, int channel) {
|
void VisualiserComponent::paintChannel(juce::Graphics& g, juce::Rectangle<float> area, int channel) {
|
||||||
|
|
|
@ -66,14 +66,23 @@ private:
|
||||||
SvgButton popOutButton{ "popOut", BinaryData::open_in_new_svg, juce::Colours::white, juce::Colours::white };
|
SvgButton popOutButton{ "popOut", BinaryData::open_in_new_svg, juce::Colours::white, juce::Colours::white };
|
||||||
SvgButton settingsButton{ "settings", BinaryData::cog_svg, juce::Colours::white, juce::Colours::white };
|
SvgButton settingsButton{ "settings", BinaryData::cog_svg, juce::Colours::white, juce::Colours::white };
|
||||||
|
|
||||||
juce::WebBrowserComponent::ResourceProvider provider = [](const juce::String& path) {
|
juce::WebBrowserComponent::ResourceProvider provider = [this](const juce::String& path) {
|
||||||
juce::String mimeType;
|
juce::String mimeType;
|
||||||
if (path.endsWith(".html")) {
|
if (path.endsWith("audio")) {
|
||||||
|
mimeType = "application/octet-stream";
|
||||||
|
juce::CriticalSection::ScopedLockType scope(lock);
|
||||||
|
std::vector<std::byte> data(buffer.size() * sizeof(float));
|
||||||
|
std::memcpy(data.data(), buffer.data(), data.size());
|
||||||
|
juce::WebBrowserComponent::Resource resource = { data, mimeType };
|
||||||
|
return resource;
|
||||||
|
} else if (path.endsWith(".html")) {
|
||||||
mimeType = "text/html";
|
mimeType = "text/html";
|
||||||
} else if (path.endsWith(".jpg")) {
|
} else if (path.endsWith(".jpg")) {
|
||||||
mimeType = "image/jpeg";
|
mimeType = "image/jpeg";
|
||||||
} else if (path.endsWith(".js")) {
|
} else if (path.endsWith(".js")) {
|
||||||
mimeType = "text/javascript";
|
mimeType = "text/javascript";
|
||||||
|
} else if (path.endsWith(".svg")) {
|
||||||
|
mimeType = "image/svg+xml";
|
||||||
}
|
}
|
||||||
std::vector<std::byte> data;
|
std::vector<std::byte> data;
|
||||||
int size;
|
int size;
|
||||||
|
@ -89,6 +98,10 @@ private:
|
||||||
juce::WebBrowserComponent::Options()
|
juce::WebBrowserComponent::Options()
|
||||||
.withNativeIntegrationEnabled()
|
.withNativeIntegrationEnabled()
|
||||||
.withResourceProvider(provider)
|
.withResourceProvider(provider)
|
||||||
|
.withNativeFunction("toggleFullscreen", [this](auto& var, auto complete) {
|
||||||
|
enableFullScreen();
|
||||||
|
complete("toggleFullscreen");
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
std::vector<float> tempBuffer;
|
std::vector<float> tempBuffer;
|
||||||
|
|
|
@ -23,6 +23,12 @@
|
||||||
<FILE id="LbviBq" name="cube.obj" compile="0" resource="1" file="Resources/models/cube.obj"/>
|
<FILE id="LbviBq" name="cube.obj" compile="0" resource="1" file="Resources/models/cube.obj"/>
|
||||||
</GROUP>
|
</GROUP>
|
||||||
<GROUP id="{F3C16D02-63B4-E3DA-7498-901173C37D6C}" name="oscilloscope">
|
<GROUP id="{F3C16D02-63B4-E3DA-7498-901173C37D6C}" name="oscilloscope">
|
||||||
|
<GROUP id="{DD5ACF8F-4E4F-E277-176A-5FD4A9717037}" name="juce">
|
||||||
|
<FILE id="FCfppP" name="check_native_interop.js" compile="0" resource="1"
|
||||||
|
file="Resources/oscilloscope/juce/check_native_interop.js"/>
|
||||||
|
<FILE id="ZEUE5w" name="index.js" compile="0" resource="1" file="Resources/oscilloscope/juce/index.js"/>
|
||||||
|
<FILE id="hWk293" name="package.json" compile="0" resource="1" file="Resources/oscilloscope/juce/package.json"/>
|
||||||
|
</GROUP>
|
||||||
<FILE id="dNtZYs" name="noise.jpg" compile="0" resource="1" file="Resources/oscilloscope/noise.jpg"/>
|
<FILE id="dNtZYs" name="noise.jpg" compile="0" resource="1" file="Resources/oscilloscope/noise.jpg"/>
|
||||||
<FILE id="YPMnjq" name="oscilloscope.html" compile="0" resource="1"
|
<FILE id="YPMnjq" name="oscilloscope.html" compile="0" resource="1"
|
||||||
file="Resources/oscilloscope/oscilloscope.html"/>
|
file="Resources/oscilloscope/oscilloscope.html"/>
|
||||||
|
|
Ładowanie…
Reference in New Issue