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:
+28
-23
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user