diff --git a/Resources/oscilloscope/noise.jpg b/Resources/oscilloscope/noise.jpg new file mode 100644 index 0000000..8066386 Binary files /dev/null and b/Resources/oscilloscope/noise.jpg differ diff --git a/Resources/oscilloscope/oscilloscope.html b/Resources/oscilloscope/oscilloscope.html new file mode 100644 index 0000000..ffd3015 --- /dev/null +++ b/Resources/oscilloscope/oscilloscope.html @@ -0,0 +1,662 @@ + + + + + + + + + + + + + + + + +
+ + + [CLICK TO START] + + + +
+ + XXY OSCILLOSCOPE + +
+ +
+ + + + + + + + + + + + + + + +
+ + + + +
Gain
+0.00
+
+ + + + +
Intensity
+0.0
+
+ + + + +
Audio volume
1.00
+
+ + + +
  + Swap x / y +
  + Invert x and y +
+
+ + + + +
+ +

+ SWEEP + + + + + + + +
+ + + + +
Trigger value
+0.00
+
+ + + + +
Milliseconds/div
1
+
+ + +


+ +

+ SIGNAL GENERATOR + +

 x = +
+ + y = +
+ + + + + + + + + + + + + + + + + + + + + +
Parameter a x1

1.00
Parameter b x1
1.00
+ +

+ +
+ +

+ MICROPHONE + + + +

+ +
+ + + + + + +
PLAY FILE
+ +

+ + + +


+ + + + + + + + +
+ + + + + + + + + +
Hue
125
Persistence
0.00
+
+ + + + +
Freeze image +
Disable upsampling +
Hide graticule +
+
+ + +[reset all] + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/oscilloscope/oscilloscope.js b/Resources/oscilloscope/oscilloscope.js new file mode 100644 index 0000000..7c2343b --- /dev/null +++ b/Resources/oscilloscope/oscilloscope.js @@ -0,0 +1,942 @@ + +var AudioSystem = +{ + microphoneActive : false, + + init : function (bufferSize) + { + window.AudioContext = window.AudioContext||window.webkitAudioContext; + this.audioContext = new window.AudioContext(); + this.sampleRate = this.audioContext.sampleRate; + this.bufferSize = bufferSize; + this.timePerSample = 1/this.sampleRate; + this.oldXSamples = new Float32Array(this.bufferSize); + this.oldYSamples = new Float32Array(this.bufferSize); + this.smoothedXSamples = 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() + { + var audioElement = document.getElementById("audioElement"); + 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; 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); + if (controls.disableFilter) gl.uniform1f(program.uIntensity, 0.005*(Filter.steps+1.5)); + // +1.5 needed above for some reason for the brightness to match + else gl.uniform1f(program.uIntensity, 0.005); + 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 1.1 && belowTrigger && ySamples[i]>=triggerValue) + sweepPosition =-1.3; + belowTrigger = ySamples[i] 0) { - g.setColour(waveformColour); - paintXY(g, r.withSizeKeepingCentre(minDim, minDim)); - } - } - +// g.setColour(backgroundColour); +// g.fillRoundedRectangle(getLocalBounds().toFloat(), OscirenderLookAndFeel::RECT_RADIUS); +// +// auto r = getLocalBounds().toFloat(); +// auto minDim = juce::jmin(r.getWidth(), r.getHeight()); +// +// { +// juce::CriticalSection::ScopedLockType scope(lock); +// if (buffer.size() > 0) { +// g.setColour(waveformColour); +// paintXY(g, r.withSizeKeepingCentre(minDim, minDim)); +// } +// } +// if (!active) { // add translucent layer g.setColour(juce::Colours::black.withAlpha(0.5f)); @@ -264,6 +267,7 @@ void VisualiserComponent::resetBuffer() { } void VisualiserComponent::resized() { + browser.setBounds(getLocalBounds()); auto area = getLocalBounds(); area.removeFromBottom(5); auto buttonRow = area.removeFromBottom(25); diff --git a/Source/components/VisualiserComponent.h b/Source/components/VisualiserComponent.h index 1e74bea..85e23d4 100644 --- a/Source/components/VisualiserComponent.h +++ b/Source/components/VisualiserComponent.h @@ -66,6 +66,31 @@ private: 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 }; + juce::WebBrowserComponent::ResourceProvider provider = [](const juce::String& path) { + juce::String mimeType; + if (path.endsWith(".html")) { + mimeType = "text/html"; + } else if (path.endsWith(".jpg")) { + mimeType = "image/jpeg"; + } else if (path.endsWith(".js")) { + mimeType = "text/javascript"; + } + std::vector data; + int size; + const char* file = BinaryData::getNamedResource(path.substring(1).replaceCharacter('.', '_').toRawUTF8(), size); + for (int i = 0; i < size; i++) { + data.push_back((std::byte) file[i]); + } + juce::WebBrowserComponent::Resource resource = { data, mimeType }; + return resource; + }; + + juce::WebBrowserComponent browser = juce::WebBrowserComponent( + juce::WebBrowserComponent::Options() + .withNativeIntegrationEnabled() + .withResourceProvider(provider) + ); + std::vector tempBuffer; int precision = 4; diff --git a/osci-render.jucer b/osci-render.jucer index 2fca074..b558775 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -22,6 +22,12 @@ + + + + +