From eeb5726103bc7e237dd24ecc8fa4c977696587f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Mon, 18 May 2026 17:22:19 +0200 Subject: [PATCH] feat: displace sphere with live waveform Upload the per-channel waveform as a texture and displace icosphere vertices radially in the vertex shader: CH1 drives the northern hemisphere, CH2 the southern. --- oscope-sphere/bin/data/shaders/sphere.vert | 11 ++++++- oscope-sphere/src/SphereViz.cpp | 34 +++++++++++++++++++++- oscope-sphere/src/SphereViz.h | 21 ++++++++----- oscope-sphere/src/ofApp.cpp | 3 +- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/oscope-sphere/bin/data/shaders/sphere.vert b/oscope-sphere/bin/data/shaders/sphere.vert index f4d8535..437ad55 100644 --- a/oscope-sphere/bin/data/shaders/sphere.vert +++ b/oscope-sphere/bin/data/shaders/sphere.vert @@ -1,6 +1,10 @@ #version 150 uniform mat4 modelViewProjectionMatrix; +uniform sampler2D waveformTex; // width = samples, height = 2 (row0 CH1, row1 CH2) +uniform float displaceAmount; +uniform float baseRadius; + in vec4 position; out vec2 vSphereUV; // x = longitude [0,1], y = latitude [0,1] @@ -11,6 +15,11 @@ void main() { vec3 dir = normalize(position.xyz); float lon = atan(dir.z, dir.x) / (2.0 * PI) + 0.5; float lat = asin(clamp(dir.y, -1.0, 1.0)) / PI + 0.5; - gl_Position = modelViewProjectionMatrix * position; + + float row = (dir.y >= 0.0) ? 0.25 : 0.75; // CH1 north, CH2 south + float wave = texture(waveformTex, vec2(lon, row)).r; // [-1,1] + float r = baseRadius * (1.0 + displaceAmount * wave); + + gl_Position = modelViewProjectionMatrix * vec4(dir * r, 1.0); vSphereUV = vec2(lon, lat); } diff --git a/oscope-sphere/src/SphereViz.cpp b/oscope-sphere/src/SphereViz.cpp index c459507..dc6de46 100644 --- a/oscope-sphere/src/SphereViz.cpp +++ b/oscope-sphere/src/SphereViz.cpp @@ -1,7 +1,9 @@ #include "SphereViz.h" #include -void SphereViz::setup(int icoIterations, int spectroWidth, int spectroHeight) { +void SphereViz::setup(int icoIterations, int spectroWidth, int spectroHeight, + int waveformLen) { + waveformLen_ = waveformLen; spectro_ = std::make_unique(spectroWidth, spectroHeight); @@ -23,6 +25,12 @@ void SphereViz::setup(int icoIterations, int spectroWidth, int spectroHeight) { spectroTex_.setTextureWrap(GL_REPEAT, GL_CLAMP_TO_EDGE); spectroTex_.setTextureMinMagFilter(GL_LINEAR, GL_LINEAR); + wavePix_.allocate(waveformLen, 2, OF_PIXELS_GRAY); + wavePix_.set(0.0f); + waveTex_.allocate(wavePix_); + waveTex_.setTextureWrap(GL_REPEAT, GL_CLAMP_TO_EDGE); + waveTex_.setTextureMinMagFilter(GL_LINEAR, GL_LINEAR); + if (!shader_.load("shaders/sphere")) ofLogError("SphereViz") << "failed to load shaders/sphere"; } @@ -37,10 +45,34 @@ void SphereViz::pushSpectrogramColumn(const std::vector& magCh1, static_cast(spectro_->width()); } +void SphereViz::setWaveform(const std::vector& ch1, + const std::vector& ch2) { + float* px = wavePix_.getData(); + const int L = waveformLen_; + auto fillRow = [&](const std::vector& src, int row) { + const int n = static_cast(src.size()); + for (int i = 0; i < L; ++i) { + float v = 0.0f; + if (n > 0) { + int idx = n - L + i; // newest L samples + if (idx < 0) idx = 0; + v = src[idx]; + } + px[row * L + i] = v; + } + }; + fillRow(ch1, 0); + fillRow(ch2, 1); + waveTex_.loadData(wavePix_); +} + void SphereViz::drawSkin() { shader_.begin(); shader_.setUniformTexture("spectroTex", spectroTex_, 0); + shader_.setUniformTexture("waveformTex", waveTex_, 1); shader_.setUniform1f("scrollOffset", scrollOffset_); + shader_.setUniform1f("displaceAmount", displace_); + shader_.setUniform1f("baseRadius", baseRadius_); shader_.setUniform1i("colormapId", colormapId_); mesh_.draw(); shader_.end(); diff --git a/oscope-sphere/src/SphereViz.h b/oscope-sphere/src/SphereViz.h index 282048a..7fefbff 100644 --- a/oscope-sphere/src/SphereViz.h +++ b/oscope-sphere/src/SphereViz.h @@ -4,26 +4,33 @@ #include #include -// GL visual unit: an icosphere whose skin is the scrolling spectrogram. -// The northern hemisphere shows CH1, the southern shows CH2. +// GL visual unit: an icosphere skinned by the scrolling spectrogram and +// displaced radially by the live waveform. Northern hemisphere = CH1, +// southern = CH2. class SphereViz { public: - void setup(int icoIterations, int spectroWidth, int spectroHeight); + void setup(int icoIterations, int spectroWidth, int spectroHeight, + int waveformLen); - // Advances the spectrogram by one column and uploads it. void pushSpectrogramColumn(const std::vector& magCh1, const std::vector& magCh2); + void setWaveform(const std::vector& ch1, + const std::vector& ch2); void drawSkin(); void setColormap(int id) { colormapId_ = id; } private: std::unique_ptr spectro_; - ofVboMesh mesh_; - ofShader shader_; - ofTexture spectroTex_; + ofVboMesh mesh_; + ofShader shader_; + ofTexture spectroTex_; + ofTexture waveTex_; ofFloatPixels spectroPix_; + ofFloatPixels wavePix_; + int waveformLen_ = 0; float baseRadius_ = 200.0f; float scrollOffset_ = 0.0f; + float displace_ = 0.18f; int colormapId_ = 0; }; diff --git a/oscope-sphere/src/ofApp.cpp b/oscope-sphere/src/ofApp.cpp index a1bc2f2..ea89b2d 100644 --- a/oscope-sphere/src/ofApp.cpp +++ b/oscope-sphere/src/ofApp.cpp @@ -11,7 +11,7 @@ void ofApp::setup() { cam_.setNearClip(1.0f); cam_.setFarClip(5000.0f); - sphere_.setup(5, 512, 256); + sphere_.setup(5, 512, 256, 1024); hantek_.setSampleRate(16000000u); const oscope::HantekStatus st = hantek_.start(); @@ -46,6 +46,7 @@ void ofApp::update() { sphere_.setColormap(colormap_); sphere_.pushSpectrogramColumn(analyzerCh1_.magDown(), analyzerCh2_.magDown()); + sphere_.setWaveform(buf1_, buf2_); } void ofApp::draw() {