// THIS CODE HAS BEEN HEAVILY ADAPTED FROM https://dood.al/oscilloscope/ AS PERMITTED BY THE AUTHOR import * as Juce from "./index.js"; var AudioSystem = { microphoneActive : false, init : function (bufferSize) { this.bufferSize = bufferSize; this.timePerSample = 1/externalSampleRate; this.oldXSamples = new Float32Array(this.bufferSize); this.oldYSamples = new Float32Array(this.bufferSize); this.smoothedXSamples = new Float32Array(Filter.nSmoothedSamples); this.smoothedYSamples = new Float32Array(Filter.nSmoothedSamples); }, startSound : function() { window.__JUCE__.backend.addEventListener("audioUpdated", doScriptProcessor); } } var Filter = { lanczosTweak : 1.5, init : function(bufferSize, a, steps) { this.bufferSize = bufferSize; this.a = a; this.steps = steps; this.radius = a * steps; this.nSmoothedSamples = this.bufferSize*this.steps + 1; this.allSamples = new Float32Array(2*this.bufferSize); this.createLanczosKernel(); }, generateSmoothedSamples : function (oldSamples, samples, smoothedSamples) { //this.createLanczosKernel(); var bufferSize = this.bufferSize; var allSamples = this.allSamples; var nSmoothedSamples = this.nSmoothedSamples; var a = this.a; var steps = this.steps; var K = this.K; for (var i=0; i0) { var xDelta = xPoints[i]-xPoints[i-1]; if (xDelta<0) xDelta = -xDelta; var yDelta = yPoints[i]-yPoints[i-1]; if (yDelta<0) yDelta = -yDelta; this.totalLength += xDelta + yDelta; }*/ } //testOutputElement.value = this.totalLength; gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, scratchVertices, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); var program = this.lineShader; gl.useProgram(program); gl.enableVertexAttribArray(program.aStart); gl.enableVertexAttribArray(program.aEnd); gl.enableVertexAttribArray(program.aIdx); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.vertexAttribPointer(program.aStart, 2, gl.FLOAT, false, 0, 0); gl.vertexAttribPointer(program.aEnd, 2, gl.FLOAT, false, 0, 8*4); gl.bindBuffer(gl.ARRAY_BUFFER, this.quadIndexBuffer); gl.vertexAttribPointer(program.aIdx, 1, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.screenTexture); gl.uniform1i(program.uScreen, 0); gl.uniform1f(program.uSize, 0.015); gl.uniform1f(program.uGain, Math.pow(2.0,controls.mainGain)*450/512); if (controls.invertXY) gl.uniform1f(program.uInvert, -1.0); else gl.uniform1f(program.uInvert, 1.0); var intensity = controls.intensity * (41000 / externalSampleRate); if (controls.disableFilter) gl.uniform1f(program.uIntensity, intensity *(Filter.steps+1.5)); // +1.5 needed above for some reason for the brightness to match else gl.uniform1f(program.uIntensity, intensity); gl.uniform1f(program.uFadeAmount, this.fadeAmount); gl.uniform1f(program.uNEdges, this.nEdges); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.vertexIndexBuffer); var nEdgesThisTime = (xPoints.length-1); /*if (this.totalLength > 300) { nEdgesThisTime *= 300/this.totalLength; nEdgesThisTime = Math.floor(nEdgesThisTime); }*/ gl.drawElements(gl.TRIANGLES, nEdgesThisTime * 6, gl.UNSIGNED_SHORT, 0); gl.disableVertexAttribArray(program.aStart); gl.disableVertexAttribArray(program.aEnd); gl.disableVertexAttribArray(program.aIdx); }, fade : function(alpha) { this.setNormalBlending(); var program = this.simpleShader; gl.useProgram(program); gl.enableVertexAttribArray(program.vertexPosition); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.fullScreenQuad, gl.STATIC_DRAW); gl.vertexAttribPointer(program.vertexPosition, 2, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.uniform4fv(program.colour, [0.0, 0.0, 0.0, this.fadeAmount]); gl.drawArrays(gl.TRIANGLES, 0, 6); gl.disableVertexAttribArray(program.vertexPosition); }, loadTexture : function(src) { var texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // Fill with grey pixel, as placeholder until loaded gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([128, 128, 128, 255])); // Asynchronously load an image var image = new Image(); image.crossOrigin = "anonymous"; image.src = src; image.addEventListener('load', function() { // Now that the image has loaded make copy it to the texture. gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.generateMipmap(gl.TEXTURE_2D); //hardcoded: texture.width = texture.height = 512; if (controls.grid) Render.drawGrid(texture); }); return texture; }, drawGrid : function(texture) { this.activateTargetTexture(texture); this.setNormalBlending(); this.setShader(this.simpleShader); gl.colorMask(true, false, false, true); var data = []; for (var i=0; i<11; i++) { var step = 45; var s = i*step; data.splice(0,0, 0, s, 10*step, s); data.splice(0,0, s, 0, s, 10*step); if (i!=0 && i!=10) { for (var j=0; j<51; j++) { t = j*step/5; if (i!=5) { data.splice(0,0, t, s-2, t, s+1); data.splice(0,0, s-2, t, s+1, t); } else { data.splice(0,0, t, s-5, t, s+4); data.splice(0,0, s-5, t, s+4, t); } } } } for (var j=0; j<51; j++) { var t = j*step/5; if (t%5 == 0) continue; data.splice(0,0, t-2, 2.5*step, t+2, 2.5*step); data.splice(0,0, t-2, 7.5*step, t+2, 7.5*step); } var vertices = new Float32Array(data); for (var i=0; i { controls.brightness = settings.brightness; controls.intensity = settings.intensity; controls.persistence = settings.persistence; controls.hue = settings.hue; controls.disableFilter = !settings.upsampling; if (controls.grid !== settings.graticule) { controls.grid = settings.graticule; const image = controls.noise ? 'noise.jpg' : 'empty.jpg'; Render.screenTexture = Render.loadTexture(image); } if (controls.noise !== settings.smudges) { controls.noise = settings.smudges; const image = controls.noise ? 'noise.jpg' : 'empty.jpg'; Render.screenTexture = Render.loadTexture(image); } }); if (controls.sweepOn) { var gain = Math.pow(2.0, controls.mainGain); var sweepMinTime = controls.sweepMsDiv * 10 / 1000; var triggerValue = controls.sweepTriggerValue; for (var i = 0; i < xSamples.length; i++) { xSamples[i] = sweepPosition / gain; sweepPosition += 2 * AudioSystem.timePerSample / sweepMinTime; if (sweepPosition > 1.1 && belowTrigger && ySamples[i] >= triggerValue) sweepPosition = -1.3; belowTrigger = ySamples[i] < triggerValue; } } if (!controls.freezeImage) { if (!controls.disableFilter) { Filter.generateSmoothedSamples(AudioSystem.oldXSamples, xSamples, AudioSystem.smoothedXSamples); Filter.generateSmoothedSamples(AudioSystem.oldYSamples, ySamples, AudioSystem.smoothedYSamples); if (!controls.swapXY) Render.drawLineTexture(AudioSystem.smoothedXSamples, AudioSystem.smoothedYSamples); else Render.drawLineTexture(AudioSystem.smoothedYSamples, AudioSystem.smoothedXSamples); } else { if (!controls.swapXY) Render.drawLineTexture(xSamples, ySamples); else Render.drawLineTexture(ySamples, xSamples); } } for (var i = 0; i < xSamples.length; i++) { AudioSystem.oldXSamples[i] = xSamples[i]; AudioSystem.oldYSamples[i] = ySamples[i]; } requestAnimationFrame(drawCRTFrame); } req.send(); } function drawCRTFrame(timeStamp) { Render.drawCRT(); } var xSamples = new Float32Array(externalBufferSize); var ySamples = new Float32Array(externalBufferSize); Juce.getNativeFunction("bufferSize")().then(bufferSize => { externalBufferSize = bufferSize; Juce.getNativeFunction("sampleRate")().then(sampleRate => { externalSampleRate = sampleRate; xSamples = new Float32Array(externalBufferSize); ySamples = new Float32Array(externalBufferSize); Render.init(); Filter.init(externalBufferSize, 8, 6); AudioSystem.init(externalBufferSize); Render.setupArrays(Filter.nSmoothedSamples); AudioSystem.startSound(); requestAnimationFrame(drawCRTFrame); }); });