diff --git a/oscope-sphere/bin/data/shaders/sphere.frag b/oscope-sphere/bin/data/shaders/sphere.frag new file mode 100644 index 0000000..dc9a2d3 --- /dev/null +++ b/oscope-sphere/bin/data/shaders/sphere.frag @@ -0,0 +1,37 @@ +#version 150 + +uniform sampler2D spectroTex; // width = time, height = freq, R32F [0,1] +uniform float scrollOffset; +uniform int colormapId; + +in vec2 vSphereUV; +out vec4 fragColor; + +// Polynomial colormap fits (public domain, Matt Zucker). +vec3 magma(float t) { + const vec3 c0 = vec3(-0.002136485053939,-0.000749655052795,-0.005386127855323); + const vec3 c1 = vec3( 0.251660540737164, 0.677523243683767, 2.494026599312351); + const vec3 c2 = vec3( 8.353717279216625,-3.577719514958484, 0.314467903013257); + const vec3 c3 = vec3(-27.66873308576866, 14.26473078096533,-13.64921318813922); + const vec3 c4 = vec3( 52.17613981234068,-27.94360607168351, 12.94416944238394); + const vec3 c5 = vec3(-50.76852536473588, 29.04658282127291, 4.234152993845980); + const vec3 c6 = vec3( 18.65570506591883,-11.48977351997711,-5.601961508734096); + return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6))))); +} +vec3 viridis(float t) { + const vec3 c0 = vec3( 0.277727327223418, 0.005407344544967, 0.334099805335306); + const vec3 c1 = vec3( 0.105093043108577, 1.404613529898575, 1.384590162594685); + const vec3 c2 = vec3(-0.330861828725556, 0.214847559468213, 0.095095163028237); + const vec3 c3 = vec3(-4.634230498983486,-5.799100973351585,-19.33244095627987); + const vec3 c4 = vec3( 6.228269936347081,14.17993336680509, 56.69055260068105); + const vec3 c5 = vec3( 4.776384997670288,-13.74514537774601,-65.35303263337234); + const vec3 c6 = vec3(-5.435455855934631, 4.645852612178535, 26.3124352495832); + return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6))))); +} + +void main() { + float u = fract(vSphereUV.x - scrollOffset); + float mag = clamp(texture(spectroTex, vec2(u, vSphereUV.y)).r, 0.0, 1.0); + vec3 col = (colormapId == 0) ? magma(mag) : viridis(mag); + fragColor = vec4(col, 1.0); +} diff --git a/oscope-sphere/bin/data/shaders/sphere.vert b/oscope-sphere/bin/data/shaders/sphere.vert new file mode 100644 index 0000000..f4d8535 --- /dev/null +++ b/oscope-sphere/bin/data/shaders/sphere.vert @@ -0,0 +1,16 @@ +#version 150 + +uniform mat4 modelViewProjectionMatrix; +in vec4 position; + +out vec2 vSphereUV; // x = longitude [0,1], y = latitude [0,1] + +const float PI = 3.14159265359; + +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; + vSphereUV = vec2(lon, lat); +} diff --git a/oscope-sphere/src/SphereViz.cpp b/oscope-sphere/src/SphereViz.cpp new file mode 100644 index 0000000..20326b5 --- /dev/null +++ b/oscope-sphere/src/SphereViz.cpp @@ -0,0 +1,42 @@ +#include "SphereViz.h" +#include + +void SphereViz::setup(int icoIterations, int spectroWidth, int spectroHeight) { + spectro_ = std::make_unique(spectroWidth, + spectroHeight); + + ofIcoSpherePrimitive ico(baseRadius_, icoIterations); + ofMesh src = ico.getMesh(); + mesh_.clear(); + mesh_.addVertices(src.getVertices()); + mesh_.addNormals(src.getNormals()); + mesh_.addIndices(src.getIndices()); + + ofDisableArbTex(); + spectroPix_.allocate(spectroWidth, spectroHeight, OF_PIXELS_GRAY); + spectroPix_.set(0.0f); + spectroTex_.allocate(spectroPix_); + spectroTex_.setTextureWrap(GL_REPEAT, GL_CLAMP_TO_EDGE); + spectroTex_.setTextureMinMagFilter(GL_LINEAR, GL_LINEAR); + + shader_.load("shaders/sphere"); +} + +void SphereViz::pushSpectrogramColumn(const std::vector& magCh1, + const std::vector& magCh2) { + spectro_->pushColumn(magCh1, magCh2); + const std::vector& d = spectro_->data(); + std::copy(d.begin(), d.end(), spectroPix_.getData()); + spectroTex_.loadData(spectroPix_); + scrollOffset_ = static_cast(spectro_->writeIndex()) / + static_cast(spectro_->width()); +} + +void SphereViz::drawSkin() { + shader_.begin(); + shader_.setUniformTexture("spectroTex", spectroTex_, 0); + shader_.setUniform1f("scrollOffset", scrollOffset_); + shader_.setUniform1i("colormapId", colormapId_); + mesh_.draw(); + shader_.end(); +} diff --git a/oscope-sphere/src/SphereViz.h b/oscope-sphere/src/SphereViz.h new file mode 100644 index 0000000..282048a --- /dev/null +++ b/oscope-sphere/src/SphereViz.h @@ -0,0 +1,29 @@ +#pragma once +#include "ofMain.h" +#include "SpectrogramBuffer.h" +#include +#include + +// GL visual unit: an icosphere whose skin is the scrolling spectrogram. +// The northern hemisphere shows CH1, the southern shows CH2. +class SphereViz { +public: + void setup(int icoIterations, int spectroWidth, int spectroHeight); + + // Advances the spectrogram by one column and uploads it. + void pushSpectrogramColumn(const std::vector& magCh1, + const std::vector& magCh2); + + void drawSkin(); + void setColormap(int id) { colormapId_ = id; } + +private: + std::unique_ptr spectro_; + ofVboMesh mesh_; + ofShader shader_; + ofTexture spectroTex_; + ofFloatPixels spectroPix_; + float baseRadius_ = 200.0f; + float scrollOffset_ = 0.0f; + int colormapId_ = 0; +}; diff --git a/oscope-sphere/src/ofApp.cpp b/oscope-sphere/src/ofApp.cpp index 6a6ab0c..a1bc2f2 100644 --- a/oscope-sphere/src/ofApp.cpp +++ b/oscope-sphere/src/ofApp.cpp @@ -2,12 +2,82 @@ void ofApp::setup() { ofSetFrameRate(60); + ofSetVerticalSync(true); ofBackground(6, 6, 10); + ofEnableDepthTest(); + glEnable(GL_PROGRAM_POINT_SIZE); + + cam_.setDistance(750.0f); + cam_.setNearClip(1.0f); + cam_.setFarClip(5000.0f); + + sphere_.setup(5, 512, 256); + + hantek_.setSampleRate(16000000u); + const oscope::HantekStatus st = hantek_.start(); + if (st == oscope::HantekStatus::Ok) { + demoMode_ = false; + statusText_ = "SCOPE OK"; + } else { + demoMode_ = true; + statusText_ = (st == oscope::HantekStatus::FirmwareNeeded) + ? "DEMO - firmware needed (see docs/HANTEK_SETUP.md)" + : "DEMO - scope not found"; + } } -void ofApp::update() {} +void ofApp::update() { + if (frozen_) return; + + if (demoMode_) { + demo_.next(buf1_, buf2_, 8192); + } else { + hantek_.ring().readLatest(buf1_, buf2_, 8192); + if (hantek_.status() != oscope::HantekStatus::Ok) { + demoMode_ = true; + statusText_ = "DEMO - scope lost"; + } + } + + const float sr = demoMode_ ? 48000.0f : scopeSr_; + analyzerCh1_.update(buf1_, buf1_, sr); + analyzerCh2_.update(buf2_, buf2_, sr); + + sphere_.setColormap(colormap_); + sphere_.pushSpectrogramColumn(analyzerCh1_.magDown(), + analyzerCh2_.magDown()); +} void ofApp::draw() { + cam_.begin(); + ofPushMatrix(); + ofRotateYDeg(ofGetElapsedTimef() * 6.0f); + if (layerA_) sphere_.drawSkin(); + ofPopMatrix(); + cam_.end(); + drawHud(); +} + +void ofApp::drawHud() { + ofDisableDepthTest(); ofSetColor(230); - ofDrawBitmapString("oscope-sphere skeleton", 16, 24); + std::string hud = statusText_ + "\n"; + hud += std::string("[1] skin ") + (layerA_ ? "on" : "off") + "\n"; + hud += std::string("[2] rings ") + (layerB_ ? "on" : "off") + "\n"; + hud += std::string("[3] points ") + (layerC_ ? "on" : "off") + "\n"; + hud += std::string("[c] colormap [space] ") + + (frozen_ ? "frozen" : "live"); + ofDrawBitmapString(hud, 16, 24); + ofEnableDepthTest(); +} + +void ofApp::keyPressed(int key) { + switch (key) { + case '1': layerA_ = !layerA_; break; + case '2': layerB_ = !layerB_; break; + case '3': layerC_ = !layerC_; break; + case 'c': + case 'C': colormap_ = (colormap_ + 1) % 2; break; + case ' ': frozen_ = !frozen_; break; + } } diff --git a/oscope-sphere/src/ofApp.h b/oscope-sphere/src/ofApp.h index a9197b9..34024c2 100644 --- a/oscope-sphere/src/ofApp.h +++ b/oscope-sphere/src/ofApp.h @@ -1,9 +1,39 @@ #pragma once #include "ofMain.h" +#include "HantekDevice.h" +#include "AudioAnalyzer.h" +#include "DemoSignal.h" +#include "SphereViz.h" +#include +#include class ofApp : public ofBaseApp { public: void setup() override; void update() override; void draw() override; + void keyPressed(int key) override; + +private: + void drawHud(); + + oscope::HantekDevice hantek_; + oscope::AudioAnalyzer analyzerCh1_; + oscope::AudioAnalyzer analyzerCh2_; + oscope::DemoSignal demo_{48000.0f}; + + SphereViz sphere_; + ofEasyCam cam_; + + std::vector buf1_; + std::vector buf2_; + + bool demoMode_ = false; + bool frozen_ = false; + bool layerA_ = true; + bool layerB_ = true; + bool layerC_ = true; + int colormap_ = 0; + float scopeSr_ = 16.0e6f; + std::string statusText_; };