From c4e5007d86e464f2d5ee9bd06b361461f862108e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Thu, 7 May 2026 18:34:49 +0200 Subject: [PATCH] fix(of): clamp postfx + scope4 + wave roll Context: FX mode rendered pure white (bloom + grain + feedback saturating), sample rate slider was unwired, and there was no combined scope-style view. Approach: - Tone-map (Reinhard soft-knee) + clamp final color in postfx.frag. - Lower default bloom 0.4 -> 0.15 to match the new tone-map. - Wire sampleRateHz_ slider to HantekDevice::setSampleRate (initial + on change) so the rate actually applies. - WaveformVis: ring buffer (16x window) + timebase ms/div + scrollSpeed (0=freeze, >0=roll) + setSampleRate hook. - New Scope4 mode (key 'q'): waveform / spectrogram / polar / lissajous in 2x2 grid. - Also track previously-ignored shaders (postfx, plasma, tunnel) so other machines pick them up after pull. --- .gitignore | 9 +- oscope-of/bin/data/shaders/plasma.frag | 49 +++++++ oscope-of/bin/data/shaders/plasma.vert | 5 + oscope-of/bin/data/shaders/postfx.frag | 163 ++++++++++++++++++++++ oscope-of/bin/data/shaders/postfx.vert | 9 ++ oscope-of/bin/data/shaders/tunnel.frag | 65 +++++++++ oscope-of/bin/data/shaders/tunnel.vert | 3 + oscope-of/src/PostFx.h | 2 +- oscope-of/src/ofApp.cpp | 26 ++++ oscope-of/src/ofApp.h | 4 + oscope-of/src/visualizers/WaveformVis.cpp | 50 +++++-- oscope-of/src/visualizers/WaveformVis.h | 20 +++ 12 files changed, 392 insertions(+), 13 deletions(-) create mode 100644 oscope-of/bin/data/shaders/plasma.frag create mode 100644 oscope-of/bin/data/shaders/plasma.vert create mode 100644 oscope-of/bin/data/shaders/postfx.frag create mode 100644 oscope-of/bin/data/shaders/postfx.vert create mode 100644 oscope-of/bin/data/shaders/tunnel.frag create mode 100644 oscope-of/bin/data/shaders/tunnel.vert diff --git a/.gitignore b/.gitignore index 90532f3..45847a3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,13 @@ launcher/**/xcuserdata/ launcher/**/*.xcuserstate launcher/**/Package.resolved -# openFrameworks -oscope-of/bin/ +# openFrameworks — on garde les shaders + settings.json pour qu'ils +# arrivent sur les autres machines, mais on ignore les binaires. +oscope-of/bin/* +!oscope-of/bin/data/ +oscope-of/bin/data/* +!oscope-of/bin/data/shaders/ +!oscope-of/bin/data/settings.json oscope-of/obj/ oscope-of/.vscode/ oscope-of/Project.xcconfig diff --git a/oscope-of/bin/data/shaders/plasma.frag b/oscope-of/bin/data/shaders/plasma.frag new file mode 100644 index 0000000..865a3e8 --- /dev/null +++ b/oscope-of/bin/data/shaders/plasma.frag @@ -0,0 +1,49 @@ +#version 120 + +uniform vec2 uRes; +uniform float uTime; +uniform float uBpm; +uniform float uBass; +uniform float uLead; +uniform float uKick; +uniform float uPad; + +vec3 palette(float t) { + // Inigo Quilez-style cosine palette, hue tilted toward magenta/cyan + vec3 a = vec3(0.5, 0.5, 0.5); + vec3 b = vec3(0.5, 0.5, 0.5); + vec3 c = vec3(1.0, 1.0, 1.0); + vec3 d = vec3(0.0, 0.33, 0.67); + return a + b * cos(6.28318 * (c * t + d)); +} + +void main() { + vec2 p = (gl_FragCoord.xy / uRes) * 2.0 - 1.0; + p.x *= uRes.x / uRes.y; + + float t = uTime * (0.4 + uBpm * 0.005); + float turb = 1.0 + uLead * 1.8 + uKick * 1.0; + + float v = 0.0; + v += sin(p.x * 4.0 * turb + t * 1.6); + v += sin(p.y * 5.0 * turb - t * 1.3); + v += sin((p.x + p.y) * 3.0 * turb + t * 2.1); + float r = length(p); + v += sin(r * 8.0 - t * 2.0 + uBass * 6.0); + v *= 0.25; + + // Bass swells push the hue + float hue = v + t * 0.15 + uBass * 0.3; + vec3 col = palette(hue); + + // Pad amplitude → glow toward white + col = mix(col, vec3(1.0), uPad * 0.4 * smoothstep(0.0, 1.5, abs(v))); + + // Kick → darker contrast pulse + col *= mix(1.0, 1.6, uKick); + + // Soft radial vignette + col *= smoothstep(1.5, 0.2, r); + + gl_FragColor = vec4(col, 1.0); +} diff --git a/oscope-of/bin/data/shaders/plasma.vert b/oscope-of/bin/data/shaders/plasma.vert new file mode 100644 index 0000000..0c26e34 --- /dev/null +++ b/oscope-of/bin/data/shaders/plasma.vert @@ -0,0 +1,5 @@ +#version 120 +uniform mat4 modelViewProjectionMatrix; +void main() { + gl_Position = modelViewProjectionMatrix * gl_Vertex; +} diff --git a/oscope-of/bin/data/shaders/postfx.frag b/oscope-of/bin/data/shaders/postfx.frag new file mode 100644 index 0000000..ec8fd82 --- /dev/null +++ b/oscope-of/bin/data/shaders/postfx.frag @@ -0,0 +1,163 @@ +#version 120 +#extension GL_ARB_texture_rectangle : enable + +// Composite post-processing pass. Each effect is gated by an intensity +// uniform — set to 0 to skip its branch. Order matters: spatial / +// kaleido first (work in UV), then color / chromatic / glitch. + +uniform sampler2DRect uScene; +uniform sampler2DRect uPrev; +uniform vec2 uRes; +uniform float uTime; + +uniform float uChroma; +uniform float uBloom; +uniform float uRgbShift; +uniform float uSat; +uniform float uScan; +uniform float uVignette; +uniform float uGrain; +uniform float uPixelate; +uniform float uKaleido; +uniform float uFeedback; +uniform float uFbZoom; +uniform float uFbRot; +uniform float uGlitch; +uniform float uGlitchProb; + +varying vec2 vTex; + +const float TAU = 6.2831853; + +float rand(vec2 p) { + return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); +} + +vec2 rot2(vec2 v, float a) { + float c = cos(a), s = sin(a); + return vec2(c*v.x - s*v.y, s*v.x + c*v.y); +} + +vec3 hueRotate(vec3 col, float a) { + // Approximate hue rotation in YIQ space — fast and visually OK + float c1 = cos(a), s1 = sin(a); + mat3 m = mat3( + 0.299 + 0.701*c1 + 0.168*s1, 0.587 - 0.587*c1 + 0.330*s1, 0.114 - 0.114*c1 - 0.497*s1, + 0.299 - 0.299*c1 - 0.328*s1, 0.587 + 0.413*c1 + 0.035*s1, 0.114 - 0.114*c1 + 0.292*s1, + 0.299 - 0.300*c1 + 1.250*s1, 0.587 - 0.588*c1 - 1.050*s1, 0.114 + 0.886*c1 - 0.203*s1 + ); + return clamp(m * col, 0.0, 1.5); +} + +void main() { + vec2 px = gl_FragCoord.xy; + vec2 uv = px; + vec2 c = (px / uRes) - 0.5; + + // 1. Pixelate (snap to block centers) + if (uPixelate > 0.001) { + float ps = mix(1.0, 64.0, uPixelate); + uv = floor(uv / ps) * ps + ps * 0.5; + c = (uv / uRes) - 0.5; + } + + // 2. Kaleidoscope radial mirror + if (uKaleido > 0.001) { + float n = floor(mix(2.0, 12.0, uKaleido)); + float ang = atan(c.y, c.x); + float seg = TAU / n; + ang = abs(mod(ang + seg*0.5, seg) - seg*0.5); + float r = length(c); + c = vec2(cos(ang), sin(ang)) * r; + uv = (c + 0.5) * uRes; + } + + // 3. Glitch — block displacement on horizontal slices + vec2 glUv = uv; + if (uGlitch > 0.001) { + float blockY = floor(uv.y / 14.0); + float t = floor(uTime * 24.0); + float r = rand(vec2(blockY, t)); + float prob = mix(0.0, uGlitchProb * 2.5, uGlitch); + if (r < prob) { + float shift = (rand(vec2(blockY, t + 1.0)) - 0.5) * 240.0 * uGlitch; + glUv.x += shift; + } + } + glUv = clamp(glUv, vec2(0.0), uRes - 1.0); + + // 4. Chromatic aberration : RGB channels sampled at radial offsets + vec2 dir = c * uChroma * 14.0; + vec3 col; + col.r = texture2DRect(uScene, clamp(glUv + dir, vec2(0.0), uRes - 1.0)).r; + col.g = texture2DRect(uScene, glUv).g; + col.b = texture2DRect(uScene, clamp(glUv - dir, vec2(0.0), uRes - 1.0)).b; + + // 5. Glitch channel swap + if (uGlitch > 0.55) { + float swap = step(0.5, rand(vec2(floor(uTime * 12.0), 1.0))); + col = mix(col, col.brg, swap); + } + + // 6. Bloom — cheap 5x5 bright-pass average added back + if (uBloom > 0.001) { + vec3 bl = vec3(0.0); + float w = 4.0 + uBloom * 12.0; + for (int i = -2; i <= 2; i++) { + for (int j = -2; j <= 2; j++) { + vec3 s = texture2DRect(uScene, + clamp(uv + vec2(i, j) * w, vec2(0.0), uRes - 1.0)).rgb; + float lum = max(max(s.r, s.g), s.b); + bl += s * smoothstep(0.45, 1.0, lum); + } + } + bl /= 25.0; + col += bl * uBloom * 1.6; + } + + // 7. Hue rotation + if (uRgbShift > 0.001) { + col = hueRotate(col, uRgbShift * TAU); + } + + // 8. Saturation + { + float lum = dot(col, vec3(0.299, 0.587, 0.114)); + col = mix(vec3(lum), col, uSat); + } + + // 9. Feedback (zoom + rotate previous frame) + if (uFeedback > 0.001) { + vec2 fc = uv - uRes * 0.5; + fc /= uFbZoom; + fc = rot2(fc, uFbRot); + fc += uRes * 0.5; + fc = clamp(fc, vec2(0.0), uRes - 1.0); + vec3 prev = texture2DRect(uPrev, fc).rgb * 0.96; + col = max(col, prev * uFeedback * 1.5); + } + + // 10. Scanlines + if (uScan > 0.001) { + float sl = sin(px.y * 3.14159) * 0.5 + 0.5; + col *= mix(1.0, 0.45 + sl * 0.55, uScan); + } + + // 11. Film grain + if (uGrain > 0.001) { + float g = (rand(px + uTime * vec2(13.0, 7.0)) - 0.5) * uGrain * 0.6; + col += vec3(g); + } + + // 12. Vignette + if (uVignette > 0.001) { + float v = smoothstep(0.95, 0.35, length(c) * 1.4); + col *= mix(1.0, v, uVignette); + } + + // Final tone-map / clamp — without this, bloom + grain + feedback + // saturate to pure white very quickly on bright visualizers. + col = col / (col + vec3(0.6)); // Reinhard-ish soft knee + col = clamp(col, 0.0, 1.0); + gl_FragColor = vec4(col, 1.0); +} diff --git a/oscope-of/bin/data/shaders/postfx.vert b/oscope-of/bin/data/shaders/postfx.vert new file mode 100644 index 0000000..a1d511e --- /dev/null +++ b/oscope-of/bin/data/shaders/postfx.vert @@ -0,0 +1,9 @@ +#version 120 +// openFrameworks injects modelViewProjectionMatrix and the standard +// gl_Vertex / gl_MultiTexCoord0 inputs in compatibility mode. +uniform mat4 modelViewProjectionMatrix; +varying vec2 vTex; +void main() { + vTex = gl_MultiTexCoord0.xy; + gl_Position = modelViewProjectionMatrix * gl_Vertex; +} diff --git a/oscope-of/bin/data/shaders/tunnel.frag b/oscope-of/bin/data/shaders/tunnel.frag new file mode 100644 index 0000000..56dda05 --- /dev/null +++ b/oscope-of/bin/data/shaders/tunnel.frag @@ -0,0 +1,65 @@ +#version 120 + +// Cylindrical pseudo-3D tunnel. Closed-form mapping from screen UV to +// (angle, depth) — no real raymarching needed for an infinite straight +// tunnel. Twist is added per-depth slice based on lead amplitude. + +uniform vec2 uRes; +uniform float uTime; +uniform float uTravel; +uniform float uBpm; +uniform float uKick; +uniform float uBass; +uniform float uLead; +uniform float uPad; + +vec3 palette(float t) { + return 0.5 + 0.5 * cos(6.28318 * (vec3(1.0) * t + vec3(0.0, 0.33, 0.67))); +} + +void main() { + vec2 p = (gl_FragCoord.xy / uRes) * 2.0 - 1.0; + p.x *= uRes.x / uRes.y; + + // Polar coords -> tunnel space + float r = length(p); + float a = atan(p.y, p.x); + if (r < 0.001) r = 0.001; + + // depth z = 1/r so r→0 is far. Travel scrolls slices. + float z = 1.0 / r + uTravel; + // Twist: deeper = more rotation, modulated by lead + a += z * (0.15 + uLead * 0.6); + + // Slice index for repeating bands + float slice = floor(z * 2.0); + float frac = fract(z * 2.0); + + // Brick / ring pattern — checker on (slice, angle) + float ang = a / 6.28318 * 16.0; + float wall = mod(floor(ang) + slice, 2.0); + + // Base color cycles along depth + slight bass push + vec3 col = palette(slice * 0.07 + uTime * 0.05 + uBass * 0.2); + + // Bright "lamps" every 4 slices + float lampMask = step(0.92, fract(slice / 4.0)); + float lamp = (1.0 - smoothstep(0.0, 0.4, abs(frac - 0.5))) * lampMask; + col += vec3(1.0, 0.9, 0.7) * lamp * (0.6 + uKick * 1.5); + + // Wall vs floor stripes alternate brightness + col *= mix(0.45, 1.0, wall); + + // Add edges between bricks (subtle dark line) + float edge = smoothstep(0.0, 0.05, abs(frac - 0.5)); + col *= mix(0.7, 1.0, edge); + + // Distance fog : pixels close to center (far away) fade to background + float fog = smoothstep(0.0, 0.85, r); + col = mix(vec3(0.0, 0.0, 0.05 + uPad * 0.15), col, fog); + + // Outer ring vignette + col *= smoothstep(2.0, 0.4, length(p)); + + gl_FragColor = vec4(col, 1.0); +} diff --git a/oscope-of/bin/data/shaders/tunnel.vert b/oscope-of/bin/data/shaders/tunnel.vert new file mode 100644 index 0000000..819a4e7 --- /dev/null +++ b/oscope-of/bin/data/shaders/tunnel.vert @@ -0,0 +1,3 @@ +#version 120 +uniform mat4 modelViewProjectionMatrix; +void main() { gl_Position = modelViewProjectionMatrix * gl_Vertex; } diff --git a/oscope-of/src/PostFx.h b/oscope-of/src/PostFx.h index 02b4343..c750388 100644 --- a/oscope-of/src/PostFx.h +++ b/oscope-of/src/PostFx.h @@ -14,7 +14,7 @@ public: struct Params { // Color float chroma = 0.0f; // chromatic aberration radius - float bloom = 0.4f; // bright-pass blur strength + float bloom = 0.15f; // bright-pass blur strength (was 0.4 — saturated) float rgbShift = 0.0f; // hue rotation 0..1 = 0..2π float saturation = 1.0f; // 0..2 (1 = neutral) diff --git a/oscope-of/src/ofApp.cpp b/oscope-of/src/ofApp.cpp index e5d789b..298154e 100644 --- a/oscope-of/src/ofApp.cpp +++ b/oscope-of/src/ofApp.cpp @@ -46,6 +46,7 @@ const char* modeName(ofApp::Mode m) { case ofApp::Mode::Kaleido: return "Kaleido"; case ofApp::Mode::Tunnel: return "Tunnel 3D"; case ofApp::Mode::Mesh: return "Mesh 3D"; + case ofApp::Mode::Scope4: return "Scope 4-up"; case ofApp::Mode::Hybrid: return "Hybrid"; } return "?"; @@ -100,6 +101,10 @@ void ofApp::setup() { gui_.add(gainCh2_.setup("CH2 V/div", 1.0f, 0.25f, 5.0f)); gui_.add(trailFade_.setup("Trail fade", 0.07f, 0.0f, 1.0f)); gui_.add(sampleRateHz_.setup("Sample rate", 8000000, 1000000, 48000000)); + gui_.add(timeMsPerDiv_.setup("Time ms/div", 5.0f, 0.05f, 200.0f)); + gui_.add(scrollSpeed_.setup("Scroll", 0.0f, 0.0f, 1.0f)); + lastSampleRateApplied_ = sampleRateHz_; + scope_.setSampleRate(static_cast(lastSampleRateApplied_)); // PostFx panel — placed to the right of the main GUI fxGui_.setup("post-fx", "fx-settings.xml", 230, 10); @@ -145,6 +150,8 @@ void ofApp::loadSettings() { else if (m == "kaleido") mode_ = Mode::Kaleido; else if (m == "tunnel") mode_ = Mode::Tunnel; else if (m == "mesh") mode_ = Mode::Mesh; + else if (m == "scope4" || + m == "quad") mode_ = Mode::Scope4; else mode_ = Mode::Hybrid; } @@ -183,6 +190,15 @@ void ofApp::applyOscFx() { void ofApp::update() { osc_.update(); + // Re-apply sample rate to the scope when the slider changes. + if (sampleRateHz_ != lastSampleRateApplied_) { + lastSampleRateApplied_ = sampleRateHz_; + scope_.setSampleRate(static_cast(lastSampleRateApplied_)); + } + // Push live timebase + scroll into WaveformVis. + waveform_->setTimeMsPerDiv(timeMsPerDiv_); + waveform_->setScrollSpeed(scrollSpeed_); + waveform_->setSampleRate(static_cast(lastSampleRateApplied_)); scope_.ring().readLatest(ch1_, ch2_, static_cast(bufferSize_)); oscope::VisFrame frame{ch1_, ch2_, osc_}; @@ -241,6 +257,15 @@ void ofApp::drawMode(Mode m, int x, int y, int w, int h) { case Mode::Kaleido: kaleido_->draw(x, y, w, h); break; case Mode::Tunnel: tunnel_->draw(x, y, w, h); break; case Mode::Mesh: mesh_->draw(x, y, w, h); break; + case Mode::Scope4: { + const int hw = w / 2; + const int hh = h / 2; + waveform_->draw (x, y, hw, hh); + spectro_->draw (x + hw, y, hw, hh); + polar_->draw (x, y + hh, hw, hh); + lissajous_->draw(x + hw, y + hh, hw, hh); + break; + } case Mode::Hybrid: drawHybrid(w, h); break; } } @@ -364,6 +389,7 @@ void ofApp::keyPressed(int key) { case '9': mode_ = Mode::Kaleido; break; case '0': mode_ = Mode::Tunnel; break; case '-': mode_ = Mode::Mesh; break; + case 'q': mode_ = Mode::Scope4; break; case 'f': fullscreen_ = !fullscreen_; ofSetFullscreen(fullscreen_); diff --git a/oscope-of/src/ofApp.h b/oscope-of/src/ofApp.h index 88d45b9..802bf1c 100644 --- a/oscope-of/src/ofApp.h +++ b/oscope-of/src/ofApp.h @@ -32,6 +32,7 @@ public: Lissajous, Spectrogram, Reactive, Waveform, Polar, Plasma, Particles, Kaleido, Tunnel, Mesh, + Scope4, // waveform + spectrogram + polar pulsing + lissajous Hybrid }; @@ -76,6 +77,9 @@ private: ofxFloatSlider gainCh1_, gainCh2_; ofxFloatSlider trailFade_; ofxIntSlider sampleRateHz_; + ofxFloatSlider timeMsPerDiv_; + ofxFloatSlider scrollSpeed_; + int lastSampleRateApplied_ = 0; ofxLabel scopeStatusLabel_; ofxLabel oscStatusLabel_; ofxLabel modeLabel_; diff --git a/oscope-of/src/visualizers/WaveformVis.cpp b/oscope-of/src/visualizers/WaveformVis.cpp index 2ab0639..475f3ac 100644 --- a/oscope-of/src/visualizers/WaveformVis.cpp +++ b/oscope-of/src/visualizers/WaveformVis.cpp @@ -9,6 +9,11 @@ void WaveformVis::setup(int w, int h) { h_ = h; trace1_.assign(syntheticSize_, 0.0f); trace2_.assign(syntheticSize_, 0.0f); + // Ring d'historique : ~16x la fenêtre pour pouvoir scroller en arrière + // sans trou même avec un timebase lent. + ring1_.assign(syntheticSize_ * 16, 0.0f); + ring2_.assign(syntheticSize_ * 16, 0.0f); + ringHead_ = 0; } void WaveformVis::update(const VisFrame& frame) { @@ -22,16 +27,38 @@ void WaveformVis::update(const VisFrame& frame) { } beatPhase_ *= 0.92f; - // Si Hantek a fourni des samples, on les copie tels quels + // Si Hantek a fourni des samples, on alimente le ring d'historique if (frame.ch1.size() > 16) { - std::size_t n = std::min(frame.ch1.size(), trace1_.size()); + const std::size_t cap = ring1_.size(); + const std::size_t n = std::min(frame.ch1.size(), cap); for (std::size_t i = 0; i < n; ++i) { - trace1_[i] = frame.ch1[i]; - trace2_[i] = (i < frame.ch2.size()) ? frame.ch2[i] : 0.0f; + ring1_[ringHead_] = frame.ch1[i]; + ring2_[ringHead_] = (i < frame.ch2.size()) ? frame.ch2[i] : 0.0f; + ringHead_ = (ringHead_ + 1) % cap; } - if (n < trace1_.size()) { - std::fill(trace1_.begin() + n, trace1_.end(), 0.0f); - std::fill(trace2_.begin() + n, trace2_.end(), 0.0f); + + // Mode FREEZE — scrollSpeed_ ~ 0 : on n'actualise pas la fenêtre + // affichée, le trace gèle tel qu'il était (utile pour analyser). + if (scrollSpeed_ < 0.01f) return; + + // Largeur de la fenêtre = timebase × divisions, convertie en samples. + // (sampleRate * (ms/div × divX) / 1000) + const float winSec = (timeMsPerDiv_ * 0.001f) * divX_; + std::size_t winSamples = static_cast( + std::max(64.0f, std::min(static_cast(cap), + winSec * sampleRateHz_))); + winSamples = std::min(winSamples, trace1_.size()); + + // Position de lecture : tail = head - winSamples (mode freeze) ou + // tail décalé pour un effet de scroll continu. + const std::size_t off = (cap + ringHead_ - winSamples) % cap; + for (std::size_t i = 0; i < trace1_.size(); ++i) { + const float t = static_cast(i) / + static_cast(trace1_.size() - 1); + const std::size_t k = static_cast(t * (winSamples - 1)); + const std::size_t idx = (off + k) % cap; + trace1_[i] = ring1_[idx]; + trace2_[i] = ring2_[idx]; } return; } @@ -119,11 +146,14 @@ void WaveformVis::draw(int x, int y, int w, int h) { ofSetColor(160, 220, 180); const float vdiv1 = 0.5f; const float vdiv2 = 0.5f; - const float tdiv = 5.0f; // ms/div approximatif ofDrawBitmapString("CH1 " + ofToString(vdiv1, 2) + " V/div", x + 10, y + 16); ofDrawBitmapString("CH2 " + ofToString(vdiv2, 2) + " V/div", x + 10, y + 32); - ofDrawBitmapString("Time " + ofToString(tdiv, 1) + " ms/div", x + 10, y + 48); - ofDrawBitmapString("Trig auto", x + 10, y + 64); + ofDrawBitmapString("Time " + ofToString(timeMsPerDiv_, 2) + " ms/div", + x + 10, y + 48); + ofDrawBitmapString(scrollSpeed_ < 0.01f ? "FROZEN" : "ROLL", + x + 10, y + 64); + ofDrawBitmapString("Sr " + ofToString(sampleRateHz_ * 1e-6f, 1) + " MS/s", + x + 10, y + 80); ofPopMatrix(); ofPopStyle(); diff --git a/oscope-of/src/visualizers/WaveformVis.h b/oscope-of/src/visualizers/WaveformVis.h index 82679dc..6076300 100644 --- a/oscope-of/src/visualizers/WaveformVis.h +++ b/oscope-of/src/visualizers/WaveformVis.h @@ -7,6 +7,9 @@ #include "Visualizer.h" #include "ofMain.h" +#include +#include + namespace oscope { class WaveformVis : public Visualizer { @@ -15,16 +18,33 @@ public: void update(const VisFrame& frame) override; void draw(int x, int y, int w, int h) override; + // Live tweaks (driven by GUI sliders / OSC). + void setTimeMsPerDiv(float v) { timeMsPerDiv_ = std::max(0.05f, v); } + void setScrollSpeed(float v) { scrollSpeed_ = std::max(0.0f, std::min(1.0f, v)); } + void setSampleRate(float hz) { if (hz > 1.0f) sampleRateHz_ = hz; } + private: int w_ = 0, h_ = 0; float phase_ = 0.0f; float beatPhase_ = 0.0f; int prevBeat_ = 0; + // trace1_/trace2_ : fenêtre actuelle affichée (taille fixe) std::vector trace1_; std::vector trace2_; + // Ring d'historique pour le scrolling (plus long, on lit la queue) + std::vector ring1_; + std::vector ring2_; + std::size_t ringHead_ = 0; int syntheticSize_ = 2048; float divX_ = 8.0f; // grid divisions float divY_ = 8.0f; + + // Time base — combien de millisecondes représente une division horizontale. + float timeMsPerDiv_ = 5.0f; + // 0 = freeze (capture par trame), 1 = scroll continu (oscilloscope rolling). + float scrollSpeed_ = 0.0f; + // Hz — utilisé pour convertir ms/div en samples/div. + float sampleRateHz_ = 8'000'000.0f; }; } // namespace oscope