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.
This commit is contained in:
L'électron rare
2026-05-18 17:22:19 +02:00
parent 4cca03db9f
commit eeb5726103
4 changed files with 59 additions and 10 deletions
+10 -1
View File
@@ -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);
}
+33 -1
View File
@@ -1,7 +1,9 @@
#include "SphereViz.h"
#include <algorithm>
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<oscope::SpectrogramBuffer>(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<float>& magCh1,
static_cast<float>(spectro_->width());
}
void SphereViz::setWaveform(const std::vector<float>& ch1,
const std::vector<float>& ch2) {
float* px = wavePix_.getData();
const int L = waveformLen_;
auto fillRow = [&](const std::vector<float>& src, int row) {
const int n = static_cast<int>(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();
+14 -7
View File
@@ -4,26 +4,33 @@
#include <memory>
#include <vector>
// 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<float>& magCh1,
const std::vector<float>& magCh2);
void setWaveform(const std::vector<float>& ch1,
const std::vector<float>& ch2);
void drawSkin();
void setColormap(int id) { colormapId_ = id; }
private:
std::unique_ptr<oscope::SpectrogramBuffer> 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;
};
+2 -1
View File
@@ -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() {