feat(of): waveform circular fullscreen scope4

User: "WAVE fait la circulaire sur la timline et que ça prenne
tout l'écran" + "polar doit rester dans le centre du tunnel".

Approach:
- WaveformVis::drawCircular(cx, cy, baseR, maxR) : timeline mappée
  à l'angle (i/N → 0..2π depuis le haut), CH1 et CH2 sont des
  anneaux concentriques avec amplitude radiale, slow trace en
  arrière-plan plus large. Glow phosphor 3 passes par anneau.
- WaveformVis::draw délègue à drawCircular si circular_ = true.
- Scope4 : waveform circular fullscreen (toute l'écran), polar
  centré 55% par-dessus, spectro ring 0.27-0.36 autour du polar,
  lissajous additif fullscreen pour le glow. Tunnel intact en
  fond — son centre cinématique (uPan/uCurve) bouge l'intérieur
  sans déplacer le cadre, le polar reste pile au milieu.
- Reset circular_ à false en sortie de drawScope4 pour ne pas
  affecter le mode Waveform standard (touche 5).
This commit is contained in:
L'électron rare
2026-05-07 19:53:10 +02:00
parent a5d81a3489
commit 0306f77ace
3 changed files with 89 additions and 23 deletions
+28 -23
View File
@@ -319,41 +319,46 @@ void ofApp::drawPanelLabel(int x, int y, const char* title,
}
void ofApp::drawScope4(int W, int H) {
// 1) Tunnel fullscreen en fond — fréquences pilotent tile size / dir.
// 1) Tunnel fullscreen en fond — fréquences pilotent tile size / dir
// + curve. Le centre du tunnel reste pile au milieu de l'écran ;
// les uniformes uPan / uCurve modulent l'intérieur sans bouger le
// cadre, donc le polar central reste aligné avec la nef.
tunnel_->draw(0, 0, W, H);
const float cx = W * 0.5f;
const float cy = H * 0.5f;
const float minSide = static_cast<float>(std::min(W, H));
// 2) Polar VRAIMENT central, focal — taille = 70% du min(W,H), centré
// pile sur (cx, cy). Couleurs des pétales pilotées par les bands.
const int polarS = static_cast<int>(minSide * 0.70f);
// 2) WAVEFORM circulaire FULLSCREEN — les CH1/CH2/slow sont des anneaux
// concentriques qui font le tour complet (timeline → angle 0..2π).
// Mode circular activé, le draw remplit tout l'écran derrière le
// polar et le spectro ring.
waveform_->setCircular(true);
waveform_->draw(0, 0, W, H);
// 3) Polar central, focal, centré PILE sur le tunnel (cx, cy).
// Couleurs des pétales par bands (bass→bleu, lead→rouge).
const int polarS = static_cast<int>(minSide * 0.55f);
polar_->draw(static_cast<int>(cx) - polarS / 2,
static_cast<int>(cy) - polarS / 2,
polarS, polarS);
// 3) Spectrogramme circulaire en anneau juste à l'extérieur du Polar
// (PolarVis::draw utilise radius = polarS*0.4 pour ses pétales,
// soit 0.28 de minSide depuis le centre — l'anneau commence après).
const float ringInner = minSide * 0.32f;
const float ringOuter = minSide * 0.44f;
// 4) Spectrogramme circulaire en anneau autour du polar.
const float ringInner = minSide * 0.27f;
const float ringOuter = minSide * 0.36f;
spectro_->drawCircular(cx, cy, ringInner, ringOuter);
// 4) Deux satellites (Waveform + Lissajous) en orbite à 180°, situés
// AU-DELÀ de l'anneau spectro pour ne pas le couper.
const float bpm = osc_.bpm();
const float orbit = ofGetElapsedTimef() * (0.04f + bpm * 0.0003f);
const float orbitR = minSide * 0.54f;
const int satW = static_cast<int>(minSide * 0.18f);
const int satH = static_cast<int>(minSide * 0.14f);
oscope::Visualizer* sats[2] = { waveform_.get(), lissajous_.get() };
for (int i = 0; i < 2; ++i) {
const float a = orbit + i * PI;
const int sx = static_cast<int>(cx + std::cos(a) * orbitR - satW * 0.5f);
const int sy = static_cast<int>(cy + std::sin(a) * orbitR - satH * 0.5f);
sats[i]->draw(sx, sy, satW, satH);
}
// 5) Lissajous en arrière-plan additif fullscreen pour le glow.
ofEnableBlendMode(OF_BLENDMODE_ADD);
ofPushStyle();
ofSetColor(255, 255, 255, 90);
lissajous_->draw(0, 0, W, H);
ofPopStyle();
ofDisableBlendMode();
// Reset le flag circular pour que le mode Waveform standard (touche 5)
// garde son rendu CRT rectangulaire.
waveform_->setCircular(false);
}
void ofApp::draw() {
+56
View File
@@ -125,7 +125,63 @@ void WaveformVis::update(const VisFrame& frame) {
}
}
void WaveformVis::drawCircular(int cx, int cy, float baseR, float maxR) {
ofPushStyle();
ofPushMatrix();
ofTranslate(cx, cy);
// Cercle de référence (timeline) — discret, pour donner une sensation
// d'horloge.
ofNoFill();
ofSetColor(0, 80, 50, 80);
ofSetLineWidth(1);
ofDrawCircle(0, 0, baseR);
auto plotRing = [&](const std::vector<float>& trace, ofColor base,
float radius, float ampScale) {
const int n = static_cast<int>(trace.size());
if (n < 2) return;
// Glow phosphor 3 passes
for (int pass = 3; pass >= 1; --pass) {
ofColor c = base;
c.a = static_cast<unsigned char>(40 * pass);
ofSetColor(c);
ofSetLineWidth(2.0f * (4 - pass));
ofBeginShape();
for (int i = 0; i < n; ++i) {
const float t = static_cast<float>(i) / (n - 1);
const float a = t * TWO_PI - HALF_PI;
const float r = radius + trace[i] * ampScale;
ofVertex(std::cos(a) * r, std::sin(a) * r);
}
ofEndShape(true); // closed loop
}
};
const float band = (maxR - baseR);
// Ordre : slow trace en arrière-plan, puis CH1 puis CH2 par-dessus.
if (showSlowOverlay_ && !slowTrace_.empty()) {
plotRing(slowTrace_, ofColor(80, 160, 200), baseR + band * 0.05f,
band * 0.45f);
}
plotRing(trace1_, ofColor(0, 255, 140), baseR + band * 0.30f,
band * 0.20f);
plotRing(trace2_, ofColor(255, 200, 60), baseR + band * 0.65f,
band * 0.20f);
ofPopMatrix();
ofPopStyle();
}
void WaveformVis::draw(int x, int y, int w, int h) {
if (circular_) {
const float cx = x + w * 0.5f;
const float cy = y + h * 0.5f;
const float maxR = 0.46f * std::min(w, h);
const float baseR = maxR * 0.50f;
drawCircular(static_cast<int>(cx), static_cast<int>(cy), baseR, maxR);
return;
}
ofPushStyle();
ofPushMatrix();
+5
View File
@@ -24,6 +24,10 @@ public:
void setShowSlowOverlay(bool b) { showSlowOverlay_ = b; }
void setScrollSpeed(float v) { scrollSpeed_ = std::max(0.0f, std::min(1.0f, v)); }
void setSampleRate(float hz) { if (hz > 1.0f) sampleRateHz_ = hz; }
void setCircular(bool b) { circular_ = b; }
/// Render circulaire fullscreen — timeline mappée à l'angle (0..2π),
/// CH1/CH2/slow chacun sur un anneau concentrique. Pour Scope4.
void drawCircular(int cx, int cy, float baseR, float maxR);
private:
int w_ = 0, h_ = 0;
@@ -49,6 +53,7 @@ private:
// mais avec une fenêtre bien plus longue, superposé au trace audio.
float slowMsPerDiv_ = 50.0f;
bool showSlowOverlay_ = true;
bool circular_ = false;
// 0 = freeze (capture par trame), 1 = scroll continu (oscilloscope rolling).
// Défaut = 1 pour que le trace soit live tant que le user ne touche à rien.
float scrollSpeed_ = 1.0f;