Add support for recording oscilloscope visualiser

pull/263/head
James H Ball 2024-10-12 19:17:35 +01:00
rodzic 925f2a7b80
commit f30ac1823e
9 zmienionych plików z 586 dodań i 440 usunięć

Wyświetl plik

@ -2,467 +2,514 @@
<!DOCTYPE html>
<head>
<style>
body {
font-family: Sans-Serif;
font-size: 14px;
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */
}
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
}
canvas {
width: min(100vw, 100vh);
height: min(100vw, 100vh);
position: absolute;
top: calc(calc(100vh - min(100vw, 100vh)) / 2);
left: calc(calc(100vw - min(100vw, 100vh)) / 2);
display: block;
margin: auto;
}
#overlay {
background-color: rgba(0,0,0,0.5);
height: 100vh;
width: 100vw;
position: absolute;
display: none;
justify-content: center;
align-items: center;
cursor: default;
z-index: 100;
}
table {
border-spacing:0;
border-collapse: collapse;
}
#buttonRow {
position: fixed;
bottom: 0;
right: 0;
display: none;
z-index: 99;
}
#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>
body {
font-family: Sans-Serif;
font-size: 14px;
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */
}
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
}
canvas {
width: min(100vw, 100vh);
height: min(100vw, 100vh);
position: absolute;
top: calc(calc(100vh - min(100vw, 100vh)) / 2);
left: calc(calc(100vw - min(100vw, 100vh)) / 2);
display: block;
margin: auto;
}
#overlay {
background-color: rgba(0,0,0,0.5);
height: 100vh;
width: 100vw;
position: absolute;
display: none;
justify-content: center;
align-items: center;
cursor: default;
z-index: 100;
}
table {
border-spacing:0;
border-collapse: collapse;
}
#buttonRow {
position: fixed;
bottom: 0;
right: 0;
display: none;
z-index: 99;
}
#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%);
}
#download {
background: url(download.svg) no-repeat;
}
#fullscreen {
background: url(fullscreen.svg) no-repeat;
}
#popout {
background: url(open_in_new.svg) no-repeat;
}
#settings {
background: url(cog.svg) no-repeat;
}
</style>
</head>
<body bgcolor="black" text="white" autocomplete="off" style="margin: 0px;">
<div id="buttonRow">
<button id="fullscreen"/>
<button id="popout"/>
<button id="settings"/>
</div>
<script>
var controls=
{
swapXY : false,
sweepOn : false,
sweepMsDiv : 1,
sweepTriggerValue : 0,
mainGain : 0.0,
brightness : 0.0,
intensity: 0.02,
saturation: 1.0,
focus: 0.01,
hue : 125,
invertXY : false,
grid : true,
noise : true,
persistence : 0,
disableFilter : false,
}
let timeout;
document.addEventListener("mousemove", function() {
const buttons = document.getElementById('buttonRow');
buttons.style.display = "block";
if (timeout) {
clearTimeout(timeout);
<div id="buttonRow">
<button onClick="toggleRecording()" id="download"/>
<button id="fullscreen"/>
<button id="popout"/>
<button id="settings"/>
</div>
<script>
var controls=
{
swapXY : false,
sweepOn : false,
sweepMsDiv : 1,
sweepTriggerValue : 0,
mainGain : 0.0,
brightness : 0.0,
intensity: 0.02,
saturation: 1.0,
focus: 0.01,
hue : 125,
invertXY : false,
grid : true,
noise : true,
persistence : 0,
disableFilter : false,
}
timeout = setTimeout(function() {
buttons.style.display = "none";
}, 1000)
});
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)
});
let isDebug = true;
let paused = false;
let openInAnotherWindow = false;
let externalSampleRate = 96000;
let externalBufferSize = 1920;
let recording = false;
let mediaRecorder = undefined;
let downloadCallback = undefined;
const toggleRecording = () => {
recording = !recording;
if (recording) {
const canvas = document.getElementById("crtCanvas");
const data = [];
const stream = canvas.captureStream(60);
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (e) => data.push(e.data);
mediaRecorder.onstop = (e) => {
const div = document.getElementById("buttonRow");
var a = document.createElement("a");
const video = new Blob(data, { type: "video/webm;codecs=h264" });
var reader = new FileReader();
reader.readAsDataURL(video);
reader.onloadend = function() {
var dataUrl = reader.result;
var base64 = dataUrl.split(',')[1];
downloadCallback(base64);
}
};
mediaRecorder.start();
} else {
mediaRecorder.stop();
}
};
</script>
let isDebug = true;
let paused = false;
let openInAnotherWindow = false;
let externalSampleRate = 96000;
let externalBufferSize = 1920;
</script>
<script type="module">
import * as Juce from "./index.js";
const fullscreen = document.getElementById('fullscreen');
const toggleFullscreen = Juce.getNativeFunction("toggleFullscreen");
fullscreen.onclick = toggleFullscreen;
const popout = document.getElementById('popout');
const popoutFn = Juce.getNativeFunction("popout");
popout.onclick = popoutFn;
const settings = document.getElementById('settings');
const settingsFn = Juce.getNativeFunction("settings");
settings.onclick = settingsFn;
const mainScreen = document.getElementById('mainScreen');
const overlay = document.getElementById('overlay');
const pauseFn = Juce.getNativeFunction("pause");
mainScreen.onclick = function() {
if (!openInAnotherWindow) {
pauseFn();
paused = !paused;
if (paused) {
<script type="module">
import * as Juce from "./index.js";
const fullscreen = document.getElementById('fullscreen');
const toggleFullscreen = Juce.getNativeFunction("toggleFullscreen");
fullscreen.onclick = toggleFullscreen;
const popout = document.getElementById('popout');
const popoutFn = Juce.getNativeFunction("popout");
popout.onclick = popoutFn;
const settings = document.getElementById('settings');
const settingsFn = Juce.getNativeFunction("settings");
settings.onclick = settingsFn;
const mainScreen = document.getElementById('mainScreen');
const overlay = document.getElementById('overlay');
const pauseFn = Juce.getNativeFunction("pause");
mainScreen.onclick = function() {
if (!openInAnotherWindow) {
pauseFn();
paused = !paused;
if (paused) {
overlay.style.display = "flex";
} else {
overlay.style.display = "none";
}
}
};
const isDebugFn = Juce.getNativeFunction("isDebug");
isDebugFn().then(debug => {
isDebug = debug;
if (!debug) {
document.addEventListener('contextmenu', event => event.preventDefault());
}
});
const isOverlayFn = Juce.getNativeFunction("isOverlay");
isOverlayFn().then(overlay => {
if (overlay) {
popout.remove();
fullscreen.remove();
}
});
Juce.getNativeFunction("isVisualiserOnly")().then(visualiserOnly => {
if (visualiserOnly) {
popout.remove();
fullscreen.remove();
settings.remove();
}
});
window.__JUCE__.backend.addEventListener("childPresent", hasChild => {
openInAnotherWindow = hasChild;
if (hasChild) {
overlay.style.display = "flex";
overlay.innerText = "Open in separate window";
} else {
overlay.style.display = "none";
overlay.innerText = "Paused";
}
});
window.__JUCE__.backend.addEventListener("toggleRecording", hasChild => {
toggleRecording();
});
document.addEventListener("dblclick", function() {
toggleFullscreen();
});
downloadCallback = (base64) => {
Juce.getNativeFunction("downloadVideo")(base64);
};
</script>
<div id="mainScreen">
<div id="overlay">Paused</div>
<canvas id="crtCanvas" width="800" height="800"></canvas>
</div>
<script id="vertex" type="x-shader">
attribute vec2 vertexPosition;
void main()
{
gl_Position = vec4(vertexPosition, 0.0, 1.0);
}
};
</script>
const isDebugFn = Juce.getNativeFunction("isDebug");
isDebugFn().then(debug => {
isDebug = debug;
if (!debug) {
document.addEventListener('contextmenu', event => event.preventDefault());
<script id="fragment" type="x-shader">
precision highp float;
uniform vec4 colour;
void main()
{
gl_FragColor = colour;
}
});
</script>
const isOverlayFn = Juce.getNativeFunction("isOverlay");
isOverlayFn().then(overlay => {
if (overlay) {
popout.remove();
fullscreen.remove();
<!-- The Gaussian line-drawing code, the next two shaders, is adapted
from woscope by e1ml : https://github.com/m1el/woscope -->
<script id="gaussianVertex" type="x-shader">
#define EPS 1E-6
uniform float uInvert;
uniform float uSize;
uniform float uNEdges;
uniform float uFadeAmount;
uniform float uIntensity;
uniform float uGain;
attribute vec3 aStart, aEnd;
attribute float aIdx;
varying vec4 uvl;
varying vec2 vTexCoord;
varying float vLen;
varying float vSize;
void main () {
float tang;
vec2 current;
// All points in quad contain the same data:
// segment start point and segment end point.
// We determine point position using its index.
float idx = mod(aIdx,4.0);
vec2 aStartPos = aStart.xy;
vec2 aEndPos = aEnd.xy;
float aStartBrightness = aStart.z;
float aEndBrightness = aEnd.z;
// `dir` vector is storing the normalized difference
// between end and start
vec2 dir = (aEndPos-aStartPos)*uGain;
uvl.z = length(dir);
if (uvl.z > EPS) {
dir = dir / uvl.z;
} else {
// If the segment is too short, just draw a square
dir = vec2(1.0, 0.0);
}
vSize = uSize;
float intensity = 0.015 * uIntensity / uSize;
vec2 norm = vec2(-dir.y, dir.x);
if (idx >= 2.0) {
current = aEndPos*uGain;
tang = 1.0;
uvl.x = -vSize;
uvl.w = aEndBrightness;
} else {
current = aStartPos*uGain;
tang = -1.0;
uvl.x = uvl.z + vSize;
uvl.w = aStartBrightness;
}
// `side` corresponds to shift to the "right" or "left"
float side = (mod(idx, 2.0)-0.5)*2.0;
uvl.y = side * vSize;
uvl.w *= intensity * mix(1.0-uFadeAmount, 1.0, floor(aIdx / 4.0 + 0.5)/uNEdges);
vec4 pos = vec4((current+(tang*dir+norm*side)*vSize)*uInvert,0.0,1.0);
gl_Position = pos;
vTexCoord = 0.5*pos.xy+0.5;
//float seed = floor(aIdx/4.0);
//seed = mod(sin(seed*seed), 7.0);
//if (mod(seed/2.0, 1.0)<0.5) gl_Position = vec4(10.0);
}
</script>
<script id="gaussianFragment" type="x-shader">
#define EPS 1E-6
#define TAU 6.283185307179586
#define TAUR 2.5066282746310002
#define SQRT2 1.4142135623730951
precision highp float;
uniform float uSize;
uniform float uIntensity;
uniform sampler2D uScreen;
varying float vSize;
varying vec4 uvl;
varying vec2 vTexCoord;
// A standard gaussian function, used for weighting samples
float gaussian(float x, float sigma)
{
return exp(-(x * x) / (2.0 * sigma * sigma)) / (TAUR * sigma);
}
// This approximates the error function, needed for the gaussian integral
float erf(float x)
{
float s = sign(x), a = abs(x);
x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
x *= x;
return s - s / (x * x);
}
void main (void)
{
float len = uvl.z;
vec2 xy = uvl.xy;
float brightness;
float sigma = vSize/5.0;
if (len < EPS)
{
// If the beam segment is too short, just calculate intensity at the position.
brightness = gaussian(length(xy), sigma);
}
else
{
// Otherwise, use analytical integral for accumulated intensity.
brightness = erf(xy.x/SQRT2/sigma) - erf((xy.x-len)/SQRT2/sigma);
brightness *= exp(-xy.y*xy.y/(2.0*sigma*sigma))/2.0/len;
}
brightness *= uvl.w;
gl_FragColor = 2.0 * texture2D(uScreen, vTexCoord) * brightness;
gl_FragColor.a = 1.0;
}
</script>
<script id="texturedVertex" type="x-shader">
precision highp float;
attribute vec2 aPos;
varying vec2 vTexCoord;
void main (void)
{
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = (0.5*aPos+0.5);
}
});
</script>
Juce.getNativeFunction("isVisualiserOnly")().then(visualiserOnly => {
if (visualiserOnly) {
popout.remove();
fullscreen.remove();
settings.remove();
<script id="texturedVertexWithResize" type="x-shader">
precision highp float;
attribute vec2 aPos;
varying vec2 vTexCoord;
uniform float uResizeForCanvas;
void main (void)
{
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = (0.5*aPos+0.5)*uResizeForCanvas;
}
});
</script>
window.__JUCE__.backend.addEventListener("childPresent", hasChild => {
openInAnotherWindow = hasChild;
if (hasChild) {
overlay.style.display = "flex";
overlay.innerText = "Open in separate window";
} else {
overlay.style.display = "none";
overlay.innerText = "Paused";
<script id="texturedFragment" type="x-shader">
precision highp float;
uniform sampler2D uTexture0;
varying vec2 vTexCoord;
void main (void)
{
gl_FragColor = texture2D(uTexture0, vTexCoord);
gl_FragColor.a= 1.0;
}
});
</script>
document.addEventListener("dblclick", function() {
toggleFullscreen();
});
</script>
<div id="mainScreen">
<div id="overlay">Paused</div>
<canvas id="crtCanvas" width="800" height="800"></canvas>
</div>
<script id="vertex" type="x-shader">
attribute vec2 vertexPosition;
void main()
{
gl_Position = vec4(vertexPosition, 0.0, 1.0);
}
</script>
<script id="fragment" type="x-shader">
precision highp float;
uniform vec4 colour;
void main()
{
gl_FragColor = colour;
}
</script>
<!-- The Gaussian line-drawing code, the next two shaders, is adapted
from woscope by e1ml : https://github.com/m1el/woscope -->
<script id="gaussianVertex" type="x-shader">
#define EPS 1E-6
uniform float uInvert;
uniform float uSize;
uniform float uNEdges;
uniform float uFadeAmount;
uniform float uIntensity;
uniform float uGain;
attribute vec3 aStart, aEnd;
attribute float aIdx;
varying vec4 uvl;
varying vec2 vTexCoord;
varying float vLen;
varying float vSize;
void main () {
float tang;
vec2 current;
// All points in quad contain the same data:
// segment start point and segment end point.
// We determine point position using its index.
float idx = mod(aIdx,4.0);
vec2 aStartPos = aStart.xy;
vec2 aEndPos = aEnd.xy;
float aStartBrightness = aStart.z;
float aEndBrightness = aEnd.z;
// `dir` vector is storing the normalized difference
// between end and start
vec2 dir = (aEndPos-aStartPos)*uGain;
uvl.z = length(dir);
if (uvl.z > EPS) {
dir = dir / uvl.z;
} else {
// If the segment is too short, just draw a square
dir = vec2(1.0, 0.0);
<script id="blurFragment" type="x-shader">
precision highp float;
uniform sampler2D uTexture0;
uniform vec2 uOffset;
varying vec2 vTexCoord;
void main (void)
{
vec4 sum = vec4(0.0);
sum += texture2D(uTexture0, vTexCoord - uOffset*8.0) * 0.000078;
sum += texture2D(uTexture0, vTexCoord - uOffset*7.0) * 0.000489;
sum += texture2D(uTexture0, vTexCoord - uOffset*6.0) * 0.002403;
sum += texture2D(uTexture0, vTexCoord - uOffset*5.0) * 0.009245;
sum += texture2D(uTexture0, vTexCoord - uOffset*4.0) * 0.027835;
sum += texture2D(uTexture0, vTexCoord - uOffset*3.0) * 0.065592;
sum += texture2D(uTexture0, vTexCoord - uOffset*2.0) * 0.12098;
sum += texture2D(uTexture0, vTexCoord - uOffset*1.0) * 0.17467;
sum += texture2D(uTexture0, vTexCoord + uOffset*0.0) * 0.19742;
sum += texture2D(uTexture0, vTexCoord + uOffset*1.0) * 0.17467;
sum += texture2D(uTexture0, vTexCoord + uOffset*2.0) * 0.12098;
sum += texture2D(uTexture0, vTexCoord + uOffset*3.0) * 0.065592;
sum += texture2D(uTexture0, vTexCoord + uOffset*4.0) * 0.027835;
sum += texture2D(uTexture0, vTexCoord + uOffset*5.0) * 0.009245;
sum += texture2D(uTexture0, vTexCoord + uOffset*6.0) * 0.002403;
sum += texture2D(uTexture0, vTexCoord + uOffset*7.0) * 0.000489;
sum += texture2D(uTexture0, vTexCoord + uOffset*8.0) * 0.000078;
gl_FragColor = sum;
}
vSize = uSize;
float intensity = 0.015 * uIntensity / uSize;
vec2 norm = vec2(-dir.y, dir.x);
if (idx >= 2.0) {
current = aEndPos*uGain;
tang = 1.0;
uvl.x = -vSize;
uvl.w = aEndBrightness;
} else {
current = aStartPos*uGain;
tang = -1.0;
uvl.x = uvl.z + vSize;
uvl.w = aStartBrightness;
</script>
<script id="outputVertex" type="x-shader">
precision highp float;
attribute vec2 aPos;
varying vec2 vTexCoord;
varying vec2 vTexCoordCanvas;
uniform float uResizeForCanvas;
void main (void)
{
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = (0.5*aPos+0.5);
vTexCoordCanvas = vTexCoord*uResizeForCanvas;
}
// `side` corresponds to shift to the "right" or "left"
float side = (mod(idx, 2.0)-0.5)*2.0;
uvl.y = side * vSize;
uvl.w *= intensity * mix(1.0-uFadeAmount, 1.0, floor(aIdx / 4.0 + 0.5)/uNEdges);
vec4 pos = vec4((current+(tang*dir+norm*side)*vSize)*uInvert,0.0,1.0);
gl_Position = pos;
vTexCoord = 0.5*pos.xy+0.5;
//float seed = floor(aIdx/4.0);
//seed = mod(sin(seed*seed), 7.0);
//if (mod(seed/2.0, 1.0)<0.5) gl_Position = vec4(10.0);
}
</script>
<script id="gaussianFragment" type="x-shader">
#define EPS 1E-6
#define TAU 6.283185307179586
#define TAUR 2.5066282746310002
#define SQRT2 1.4142135623730951
precision highp float;
uniform float uSize;
uniform float uIntensity;
uniform sampler2D uScreen;
varying float vSize;
varying vec4 uvl;
varying vec2 vTexCoord;
// A standard gaussian function, used for weighting samples
float gaussian(float x, float sigma)
{
return exp(-(x * x) / (2.0 * sigma * sigma)) / (TAUR * sigma);
}
// This approximates the error function, needed for the gaussian integral
float erf(float x)
{
float s = sign(x), a = abs(x);
x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
x *= x;
return s - s / (x * x);
}
void main (void)
{
float len = uvl.z;
vec2 xy = uvl.xy;
float brightness;
float sigma = vSize/5.0;
if (len < EPS)
{
// If the beam segment is too short, just calculate intensity at the position.
brightness = gaussian(length(xy), sigma);
}
else
{
// Otherwise, use analytical integral for accumulated intensity.
brightness = erf(xy.x/SQRT2/sigma) - erf((xy.x-len)/SQRT2/sigma);
brightness *= exp(-xy.y*xy.y/(2.0*sigma*sigma))/2.0/len;
}
brightness *= uvl.w;
gl_FragColor = 2.0 * texture2D(uScreen, vTexCoord) * brightness;
gl_FragColor.a = 1.0;
}
</script>
<script id="texturedVertex" type="x-shader">
precision highp float;
attribute vec2 aPos;
varying vec2 vTexCoord;
void main (void)
{
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = (0.5*aPos+0.5);
}
</script>
<script id="texturedVertexWithResize" type="x-shader">
precision highp float;
attribute vec2 aPos;
varying vec2 vTexCoord;
uniform float uResizeForCanvas;
void main (void)
{
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = (0.5*aPos+0.5)*uResizeForCanvas;
}
</script>
<script id="texturedFragment" type="x-shader">
precision highp float;
uniform sampler2D uTexture0;
varying vec2 vTexCoord;
void main (void)
{
gl_FragColor = texture2D(uTexture0, vTexCoord);
gl_FragColor.a= 1.0;
}
</script>
<script id="blurFragment" type="x-shader">
precision highp float;
uniform sampler2D uTexture0;
uniform vec2 uOffset;
varying vec2 vTexCoord;
void main (void)
{
vec4 sum = vec4(0.0);
sum += texture2D(uTexture0, vTexCoord - uOffset*8.0) * 0.000078;
sum += texture2D(uTexture0, vTexCoord - uOffset*7.0) * 0.000489;
sum += texture2D(uTexture0, vTexCoord - uOffset*6.0) * 0.002403;
sum += texture2D(uTexture0, vTexCoord - uOffset*5.0) * 0.009245;
sum += texture2D(uTexture0, vTexCoord - uOffset*4.0) * 0.027835;
sum += texture2D(uTexture0, vTexCoord - uOffset*3.0) * 0.065592;
sum += texture2D(uTexture0, vTexCoord - uOffset*2.0) * 0.12098;
sum += texture2D(uTexture0, vTexCoord - uOffset*1.0) * 0.17467;
sum += texture2D(uTexture0, vTexCoord + uOffset*0.0) * 0.19742;
sum += texture2D(uTexture0, vTexCoord + uOffset*1.0) * 0.17467;
sum += texture2D(uTexture0, vTexCoord + uOffset*2.0) * 0.12098;
sum += texture2D(uTexture0, vTexCoord + uOffset*3.0) * 0.065592;
sum += texture2D(uTexture0, vTexCoord + uOffset*4.0) * 0.027835;
sum += texture2D(uTexture0, vTexCoord + uOffset*5.0) * 0.009245;
sum += texture2D(uTexture0, vTexCoord + uOffset*6.0) * 0.002403;
sum += texture2D(uTexture0, vTexCoord + uOffset*7.0) * 0.000489;
sum += texture2D(uTexture0, vTexCoord + uOffset*8.0) * 0.000078;
gl_FragColor = sum;
}
</script>
<script id="outputVertex" type="x-shader">
precision highp float;
attribute vec2 aPos;
varying vec2 vTexCoord;
varying vec2 vTexCoordCanvas;
uniform float uResizeForCanvas;
void main (void)
{
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = (0.5*aPos+0.5);
vTexCoordCanvas = vTexCoord*uResizeForCanvas;
}
</script>
<script id="outputFragment" type="x-shader">
precision highp float;
uniform sampler2D uTexture0; //line
uniform sampler2D uTexture1; //tight glow
uniform sampler2D uTexture2; //big glow
uniform sampler2D uTexture3; //screen
uniform float uExposure;
uniform float uSaturation;
uniform vec3 uColour;
varying vec2 vTexCoord;
varying vec2 vTexCoordCanvas;
</script>
vec3 desaturate(vec3 color, float factor) {
vec3 lum = vec3(0.299, 0.587, 0.114);
vec3 gray = vec3(dot(lum, color));
return vec3(mix(color, gray, factor));
}
<script id="outputFragment" type="x-shader">
precision highp float;
uniform sampler2D uTexture0; //line
uniform sampler2D uTexture1; //tight glow
uniform sampler2D uTexture2; //big glow
uniform sampler2D uTexture3; //screen
uniform float uExposure;
uniform float uSaturation;
uniform vec3 uColour;
varying vec2 vTexCoord;
varying vec2 vTexCoordCanvas;
vec3 desaturate(vec3 color, float factor) {
vec3 lum = vec3(0.299, 0.587, 0.114);
vec3 gray = vec3(dot(lum, color));
return vec3(mix(color, gray, factor));
}
/* Gradient noise from Jorge Jimenez's presentation: */
/* http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare */
float gradientNoise(in vec2 uv) {
return fract(52.9829189 * fract(dot(uv, vec2(0.06711056, 0.00583715))));
}
void main (void) {
vec4 line = texture2D(uTexture0, vTexCoordCanvas);
// r components have grid; g components do not.
vec4 screen = texture2D(uTexture3, vTexCoord);
vec4 tightGlow = texture2D(uTexture1, vTexCoord);
vec4 scatter = texture2D(uTexture2, vTexCoord)+0.35;
float light = line.r + 1.5*screen.g*screen.g*tightGlow.r;
light += 0.4*scatter.g * (2.0+1.0*screen.g + 0.5*screen.r);
float tlight = 1.0-pow(2.0, -uExposure*light);
float tlight2 = tlight*tlight*tlight;
gl_FragColor.rgb = mix(uColour, vec3(1.0), 0.3+tlight2*tlight2*0.5)*tlight;
gl_FragColor.rgb = desaturate(gl_FragColor.rgb, 1.0 - uSaturation);
gl_FragColor.rgb += (1.0 / 255.0) * gradientNoise(gl_FragCoord.xy) - (0.5 / 255.0);
gl_FragColor.a = 1.0;
}
</script>
void main (void)
{
vec4 line = texture2D(uTexture0, vTexCoordCanvas);
// r components have grid; g components do not.
vec4 screen = texture2D(uTexture3, vTexCoord);
vec4 tightGlow = texture2D(uTexture1, vTexCoord);
vec4 scatter = texture2D(uTexture2, vTexCoord)+0.35;
float light = line.r + 1.5*screen.g*screen.g*tightGlow.r;
light += 0.4*scatter.g * (2.0+1.0*screen.g + 0.5*screen.r);
float tlight = 1.0-pow(2.0, -uExposure*light);
float tlight2 = tlight*tlight*tlight;
gl_FragColor.rgb = mix(uColour, vec3(1.0), 0.3+tlight2*tlight2*0.5)*tlight;
gl_FragColor.rgb = desaturate(gl_FragColor.rgb, 1.0 - uSaturation);
gl_FragColor.a = 1.0;
}
</script>
<script src="oscilloscope.js" type="module"></script>
<script src="oscilloscope.js" type="module"></script>

Wyświetl plik

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0,12a12,12 0 1,0 24,0a12,12 0 1,0 -24,0Z" /></svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 120 B

Wyświetl plik

@ -39,6 +39,12 @@ SosciPluginEditor::SosciPluginEditor(SosciAudioProcessor& p)
openVisualiserSettings();
};
addAndMakeVisible(record);
record.setPulseAnimation(true);
record.onClick = [this] {
visualiser.toggleRecording();
};
addAndMakeVisible(visualiser);
visualiser.openSettings = [this] {
@ -48,6 +54,10 @@ SosciPluginEditor::SosciPluginEditor(SosciAudioProcessor& p)
visualiser.closeSettings = [this] {
visualiserSettingsWindow.setVisible(false);
};
visualiser.recordingHalted = [this] {
record.setToggleState(false, juce::NotificationType::dontSendNotification);
};
visualiserSettingsWindow.setResizable(false, false);
#if JUCE_WINDOWS
@ -80,6 +90,7 @@ void SosciPluginEditor::resized() {
auto topBar = area.removeFromTop(25);
settings.setBounds(topBar.removeFromRight(25));
record.setBounds(topBar.removeFromRight(25));
menuBar.setBounds(topBar);
visualiser.setBounds(area);

Wyświetl plik

@ -39,6 +39,7 @@ public:
juce::TooltipWindow tooltipWindow{nullptr, 0};
SvgButton record{"Record", BinaryData::record_2_svg, juce::Colours::red, juce::Colours::red.withAlpha(0.01f)};
SvgButton settings{"Settings", BinaryData::cog_svg, juce::Colours::white, juce::Colours::white};
bool usingNativeMenuBar = false;

Wyświetl plik

@ -24,6 +24,8 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
downImageOn = juce::Drawable::createFromSVG(*doc);
changeSvgColour(doc.get(), colourOn.withBrightness(0.3f));
disabledImageOn = juce::Drawable::createFromSVG(*doc);
path = normalImage->getOutlineAsPath();
getLookAndFeel().setColour(juce::DrawableButton::backgroundOnColourId, juce::Colours::transparentWhite);
@ -37,6 +39,8 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
setToggleState(toggle->getBoolValue(), juce::NotificationType::dontSendNotification);
setTooltip(toggle->getDescription());
}
updater.addAnimator(pulse);
}
SvgButton(juce::String name, juce::String svg, juce::Colour colour) : SvgButton(name, svg, colour, colour) {}
@ -69,6 +73,30 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
juce::DrawableButton::mouseExit(e);
setMouseCursor(juce::MouseCursor::NormalCursor);
}
void setPulseAnimation(bool pulseUsed) {
this->pulseUsed = pulseUsed;
}
void paintOverChildren(juce::Graphics& g) override {
if (pulseUsed && getToggleState()) {
g.setColour(juce::Colours::black.withAlpha(colourFade / 1.5f));
g.fillPath(path);
}
}
void buttonStateChanged() override {
juce::DrawableButton::buttonStateChanged();
if (pulseUsed && getToggleState() != prevToggleState) {
if (getToggleState()) {
pulse.start();
} else {
pulse.complete();
colourFade = 1.0;
}
prevToggleState = getToggleState();
}
}
private:
std::unique_ptr<juce::Drawable> normalImage;
@ -82,6 +110,21 @@ private:
std::unique_ptr<juce::Drawable> disabledImageOn;
BooleanParameter* toggle;
juce::VBlankAnimatorUpdater updater{this};
float colourFade = 0.0;
bool pulseUsed = false;
bool prevToggleState = false;
juce::Path path;
juce::Animator pulse = juce::ValueAnimatorBuilder {}
.withEasing([] (float t) { return std::sin(3.14159 * t) / 2 + 0.5; })
.withDurationMs(500)
.runningInfinitely()
.withValueChangedCallback([this] (auto value) {
colourFade = value;
repaint();
})
.build();
void changeSvgColour(juce::XmlElement* xml, juce::Colour colour) {
forEachXmlChildElement(*xml, xmlnode) {

Wyświetl plik

@ -270,6 +270,9 @@ void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle<float> area
}
void VisualiserComponent::initialiseBrowser() {
if (recordingHalted != nullptr) {
recordingHalted();
}
oldBrowser = std::move(browser);
if (oldBrowser != nullptr) {
removeChildComponent(oldBrowser.get());
@ -323,6 +326,21 @@ void VisualiserComponent::initialiseBrowser() {
.withNativeFunction("isVisualiserOnly", [this](auto& var, auto complete) {
complete(visualiserOnly);
})
.withNativeFunction("downloadVideo", [this](const juce::Array<juce::var>& args, auto complete) {
juce::String base64 = args[0].toString();
chooser = std::make_unique<juce::FileChooser>("Save video", juce::File::getSpecialLocation(juce::File::SpecialLocationType::userDesktopDirectory).getChildFile("osci-render.webm"), "*.webm");
chooser->launchAsync(juce::FileBrowserComponent::saveMode,
[base64](const juce::FileChooser& chooser) {
juce::File result = chooser.getResult();
if (result.getFullPathName().isNotEmpty()) {
juce::FileOutputStream stream(result);
stream.setPosition(0);
stream.truncate();
juce::Base64::convertFromBase64(stream, base64);
stream.flush();
}
});
})
);
addAndMakeVisible(*browser);
@ -366,6 +384,13 @@ void VisualiserComponent::handleAsyncUpdate() {
}
}
void VisualiserComponent::toggleRecording() {
if (oldVisualiser) {
return;
}
browser->emitEventIfBrowserIsVisible("toggleRecording", juce::var());
}
void VisualiserComponent::resized() {
if (!oldVisualiser) {
browser->setBounds(getLocalBounds());
@ -390,10 +415,14 @@ void VisualiserComponent::childChanged() {
}
void VisualiserComponent::popoutWindow() {
if (recordingHalted != nullptr) {
recordingHalted();
}
auto visualiser = new VisualiserComponent(sampleRateManager, consumerManager, settings, this, oldVisualiser);
visualiser->settings.setLookAndFeel(&getLookAndFeel());
visualiser->openSettings = openSettings;
visualiser->closeSettings = closeSettings;
visualiser->recordingHalted = recordingHalted;
child = visualiser;
childChanged();
popOutButton.setVisible(false);

Wyświetl plik

@ -42,12 +42,15 @@ public:
void setFullScreen(bool fullScreen);
void setVisualiserType(bool oldVisualiser);
void handleAsyncUpdate() override;
void toggleRecording();
VisualiserComponent* parent = nullptr;
VisualiserComponent* child = nullptr;
std::unique_ptr<VisualiserWindow> popout = nullptr;
std::atomic<bool> active = true;
std::function<void()> recordingHalted;
private:
// 60fps
@ -120,6 +123,8 @@ private:
// keeping this around for memory management reasons
std::unique_ptr<juce::WebBrowserComponent> oldBrowser = nullptr;
std::unique_ptr<juce::FileChooser> chooser;
void initialiseBrowser();
void resetBuffer();
void popoutWindow();

Wyświetl plik

@ -658,6 +658,7 @@
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
<MODULEPATH id="juce_animation" path="../JUCE/modules"/>
</MODULEPATHS>
</LINUX_MAKE>
<VS2022 targetFolder="Builds/VisualStudio2022" smallIcon="pSc1mq" bigIcon="pSc1mq">
@ -681,6 +682,7 @@
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
<MODULEPATH id="juce_animation" path="../JUCE/modules"/>
</MODULEPATHS>
</VS2022>
<XCODE_MAC targetFolder="Builds/MacOSX" extraLinkerFlags="-Wl,-weak_reference_mismatches,weak"
@ -705,10 +707,12 @@
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
<MODULEPATH id="juce_animation" path="../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
<MODULES>
<MODULE id="juce_animation" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>

Wyświetl plik

@ -44,6 +44,7 @@
<FILE id="PFc2q2" name="random.svg" compile="0" resource="1" file="Resources/svg/random.svg"/>
<FILE id="CE6di2" name="range.svg" compile="0" resource="1" file="Resources/svg/range.svg"/>
<FILE id="n79IAy" name="record.svg" compile="0" resource="1" file="Resources/svg/record.svg"/>
<FILE id="TWt5MY" name="record_2.svg" compile="0" resource="1" file="Resources/svg/record_2.svg"/>
<FILE id="OaqZb1" name="right_arrow.svg" compile="0" resource="1" file="Resources/svg/right_arrow.svg"/>
<FILE id="rXjNlx" name="threshold.svg" compile="0" resource="1" file="Resources/svg/threshold.svg"/>
<FILE id="rFYmV8" name="timer.svg" compile="0" resource="1" file="Resources/svg/timer.svg"/>
@ -139,6 +140,7 @@
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
<MODULEPATH id="juce_animation" path="../../../JUCE/modules"/>
</MODULEPATHS>
</LINUX_MAKE>
<VS2022 targetFolder="Builds/sosci/VisualStudio2022" smallIcon="pSc1mq"
@ -163,6 +165,7 @@
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
<MODULEPATH id="juce_animation" path="../../../JUCE/modules"/>
</MODULEPATHS>
</VS2022>
<XCODE_MAC targetFolder="Builds/sosci/MacOSX" extraLinkerFlags="-Wl,-weak_reference_mismatches,weak"
@ -187,10 +190,12 @@
<MODULEPATH id="juce_gui_extra" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_opengl" path="../../../JUCE/modules"/>
<MODULEPATH id="juce_dsp" path="../../JUCE/modules"/>
<MODULEPATH id="juce_animation" path="../../../JUCE/modules"/>
</MODULEPATHS>
</XCODE_MAC>
</EXPORTFORMATS>
<MODULES>
<MODULE id="juce_animation" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
<MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>