diff --git a/oscope-of/src/DemoFx.cpp b/oscope-of/src/DemoFx.cpp index 399a3a7..2ea20fb 100644 --- a/oscope-of/src/DemoFx.cpp +++ b/oscope-of/src/DemoFx.cpp @@ -58,31 +58,88 @@ void DemoFx::drawScroller(int W, int H) { ofPushStyle(); ofEnableBlendMode(OF_BLENDMODE_ADD); - // 4x scale via push matrix per char ; chaque char a un offset Y sin. const float scale = 3.0f; - const float charW = 8.0f * scale; // bitmap 8px wide + const float charW = 8.0f * scale; const float baseY = H - 60.0f; const float amp = 14.0f; - // Wrap : quand tout le texte est sorti à gauche, on remet à droite. const float fullW = charW * static_cast(text_.size()); if (scrollX_ < -fullW) scrollX_ = static_cast(W); + auto drawChar = [&](char ch, float x, float y, float sx, float sy, + float rot, ofColor col) { + ofSetColor(col); + ofPushMatrix(); + ofTranslate(x, y); + if (rot != 0.0f) ofRotateRad(rot); + ofScale(sx, sy, 1.0f); + const char buf[2] = { ch, 0 }; + ofDrawBitmapString(buf, 0, 0); + ofPopMatrix(); + }; + for (std::size_t i = 0; i < text_.size(); ++i) { const float cx = scrollX_ + i * charW; if (cx < -charW || cx > W + charW) continue; const float wob = std::sin(t_ * 2.5f + i * 0.28f) * amp; - // Couleur cycle phosphor → ambre par index - const float hue = std::fmod(i * 4.0f + t_ * 25.0f, 60.0f) + 80.0f; - ofColor c; - c.setHsb(hue, 200.0f, 255.0f); - ofSetColor(c, 230); - ofPushMatrix(); - ofTranslate(cx, baseY + wob); - ofScale(scale, scale, 1.0f); - const char buf[2] = { text_[i], 0 }; - ofDrawBitmapString(buf, 0, 0); - ofPopMatrix(); + + switch (scrollerStyle_) { + case ScrollerStyle::Classic: { + const float hue = std::fmod(i * 4.0f + t_ * 25.0f, 60.0f) + 80.0f; + ofColor c; c.setHsb(hue, 200.0f, 255.0f); c.a = 230; + drawChar(text_[i], cx, baseY + wob, scale, scale, 0.0f, c); + break; + } + case ScrollerStyle::Wavy3D: { + const float pulse = 0.5f + 0.5f * std::sin(t_ * 3.0f + i * 0.45f); + const float sxv = scale * (0.6f + pulse * 0.9f); + const float syv = scale * (1.0f - pulse * 0.3f); + ofColor c; c.setHsb(140.0f + pulse * 60.0f, 200.0f, 255.0f); c.a = 220; + drawChar(text_[i], cx, baseY + wob, sxv, syv, 0.0f, c); + break; + } + case ScrollerStyle::Rainbow: { + const float hue = std::fmod(i * 12.0f + t_ * 60.0f, 255.0f); + ofColor c; c.setHsb(hue, 230.0f, 255.0f); c.a = 240; + drawChar(text_[i], cx, baseY + wob, scale, scale, 0.0f, c); + break; + } + case ScrollerStyle::Mirror: { + const float hue = std::fmod(i * 4.0f + t_ * 25.0f, 60.0f) + 80.0f; + ofColor c; c.setHsb(hue, 200.0f, 255.0f); + ofColor cTop = c; cTop.a = 230; + ofColor cBot = c; cBot.a = 90; + drawChar(text_[i], cx, baseY + wob, scale, scale, 0.0f, cTop); + // Reflet : translate Y de 2× scale + flip Y + drawChar(text_[i], cx, baseY + wob + 14.0f * scale, + scale, -scale * 0.7f, 0.0f, cBot); + break; + } + case ScrollerStyle::Glitch: { + const float jx = std::sin(t_ * 60.0f + i * 1.7f) * 6.0f; + const float jy = std::cos(t_ * 73.0f + i * 2.1f) * 4.0f; + const bool corrupt = (std::sin(t_ * 11.0f + i * 0.6f) > 0.85f); + ofColor c; + c.setHsb(corrupt ? 0.0f : 180.0f, 230.0f, + 200.0f + 55.0f * (corrupt ? 1.0f : 0.0f)); + c.a = 230; + drawChar(text_[i], cx + jx, baseY + wob + jy, scale, scale, 0.0f, c); + break; + } + case ScrollerStyle::Neon: { + // Outline cyan large + cœur magenta fin + ofColor outline(80, 220, 255, 110); + for (int dx = -1; dx <= 1; ++dx) + for (int dy = -1; dy <= 1; ++dy) { + if (dx == 0 && dy == 0) continue; + drawChar(text_[i], cx + dx * 2.0f, baseY + wob + dy * 2.0f, + scale, scale, 0.0f, outline); + } + ofColor core(255, 100, 200, 240); + drawChar(text_[i], cx, baseY + wob, scale, scale, 0.0f, core); + break; + } + } } ofDisableBlendMode(); ofPopStyle(); diff --git a/oscope-of/src/DemoFx.h b/oscope-of/src/DemoFx.h index 8045e02..8cec058 100644 --- a/oscope-of/src/DemoFx.h +++ b/oscope-of/src/DemoFx.h @@ -15,6 +15,15 @@ namespace oscope { +enum class ScrollerStyle { + Classic, // sine wave wobble (par défaut) + Wavy3D, // sine + scale per char, effet de respiration 3D + Rainbow, // hue cycle large par char (cycle complet sur le texte) + Mirror, // classic + reflet vertical en bas avec gradient alpha + Glitch, // jitter horizontal aléatoire + couleurs cassées + Neon // double passe : outline cyan + cœur magenta +}; + class DemoFx { public: void setup(const std::string& greetingsPath); @@ -22,6 +31,8 @@ public: /// Scroller bas d'écran. Lit text_, défile, sine wave par char. void drawScroller(int W, int H); + void setScrollerStyle(ScrollerStyle s) { scrollerStyle_ = s; } + ScrollerStyle scrollerStyle() const { return scrollerStyle_; } /// Starfield 3D fullscreen (peut être utilisé en place du tunnel). void drawStarfield(int W, int H); @@ -42,6 +53,7 @@ private: std::string text_; float scrollX_ = 0.0f; bool scrollerOn_ = true; + ScrollerStyle scrollerStyle_ = ScrollerStyle::Classic; // Starfield struct Star { float x, y, z; }; diff --git a/oscope-of/src/ofApp.cpp b/oscope-of/src/ofApp.cpp index ca5cfc7..d8d0675 100644 --- a/oscope-of/src/ofApp.cpp +++ b/oscope-of/src/ofApp.cpp @@ -94,6 +94,7 @@ void ofApp::setup() { postfx_.setup(W, H); demo_.setup(ofToDataPath("greetings.txt", true)); + initPresets(); gui_.setup("oscope-of"); gui_.add(modeLabel_.setup("Mode", modeName(mode_))); @@ -308,6 +309,59 @@ void ofApp::drawHybrid(int W, int H) { spectro_->draw(0, H - sh, W, sh); } +void ofApp::initPresets() { + using SS = oscope::ScrollerStyle; + auto allOn = []{ return ScopeToggles{}; }; + auto onlyTunnel = []{ + ScopeToggles s{}; + s.starfield = false; s.spectroRing = false; s.polar = false; + s.waveform = false; s.bobs = false; s.copperBars = false; + return s; + }; + auto cleanScope = []{ + ScopeToggles s{}; + s.tunnel = false; s.starfield = false; s.copperBars = false; + s.bobs = false; s.tunnelHud = false; + return s; + }; + auto starOnly = []{ + ScopeToggles s{}; + s.tunnel = false; s.starfield = true; s.copperBars = false; + s.bobs = false; s.tunnelHud = false; s.polar = false; + s.spectroRing = false; s.waveform = true; + return s; + }; + + // 10 presets demoparty — chacun avec son ambiance + track SC associée. + presets_[0] = {"AMIGA", allOn(), SS::Classic, "A"}; // acid + presets_[1] = {"C64", allOn(), SS::Wavy3D, "M"}; // chiptune + presets_[2] = {"OLDSKOOL", allOn(), SS::Mirror, "G"}; // detroit + presets_[3] = {"NEWSKOOL", allOn(), SS::Rainbow, "L"}; // future garage + presets_[4] = {"TUNNEL", onlyTunnel(), SS::Classic, "P"}; // industrial + presets_[5] = {"STARFIELD", starOnly(), SS::Neon, "J"}; // ambient + presets_[6] = {"SCOPE", cleanScope(), SS::Classic, "I"}; // dnb liquide + presets_[7] = {"CYBER", allOn(), SS::Glitch, "V"}; // glitch idm + presets_[8] = {"VAPOR", allOn(), SS::Mirror, "U"}; // vocal trance + presets_[9] = {"RAVE", allOn(), SS::Rainbow, "T"}; // hardcore gabber + + // Defaults : appliquer preset 0 au boot. + applyPreset(0); +} + +void ofApp::applyPreset(int idx) { + if (idx < 0 || idx >= 10) return; + presetIdx_ = idx; + const auto& p = presets_[idx]; + scope4_ = p.toggles; + demo_.setScrollerStyle(p.scroller); + if (p.albumLetter) { + osc_.sendControl("/control/playAlbum", + std::string(p.albumLetter)); + } + ofLogNotice("ofApp") << "preset " << idx << " : " << p.name + << " -> album " << (p.albumLetter ?: "none"); +} + void ofApp::drawTunnelHud(int W, int H) { const float now = ofGetElapsedTimef(); const float dt = ofGetLastFrameTime(); @@ -498,9 +552,13 @@ void ofApp::drawScope4(int W, int H) { ofSetColor(120, 200, 100, 130); ofDrawRectangle(sx + barW * 0.5f - 0.5f, rowY + 14, 1, 6); } - // Hint reset à droite + // Hint reset à droite + nom du preset courant en gros ofSetColor(140, 200, 200, 180); - ofDrawBitmapString("[w] reset", W - 90, rowY); + ofDrawBitmapString("[w] reset", W - 100, rowY); + ofSetColor(255, 100, 200, 200); + ofDrawBitmapString(std::string("[") + + ofToString(presetIdx_ < 9 ? presetIdx_ + 1 : 0) + "] " + + presets_[presetIdx_].name, W - 220, rowY - 18); ofDisableBlendMode(); ofPopStyle(); } @@ -601,19 +659,19 @@ void ofApp::windowResized(int w, int h) { } void ofApp::keyPressed(int key) { - // Toggles Scope4 — touches 1..9 chacune un effet demoscene, 0 = reset. - auto resetToggles = [&]() { scope4_ = ScopeToggles{}; }; + // Touches 1..9 + 0 — lance un preset demoparty (visuel + track SC). + // 'q' reste libre pour le mode live (pas de preset, contrôle manuel). switch (key) { - case '1': scope4_.tunnel = !scope4_.tunnel; break; - case '2': scope4_.starfield = !scope4_.starfield; break; - case '3': scope4_.scroller = !scope4_.scroller; break; - case '4': scope4_.copperBars = !scope4_.copperBars; break; - case '5': scope4_.bobs = !scope4_.bobs; break; - case '6': scope4_.tunnelHud = !scope4_.tunnelHud; break; - case '7': scope4_.spectroRing = !scope4_.spectroRing; break; - case '8': scope4_.polar = !scope4_.polar; break; - case '9': scope4_.waveform = !scope4_.waveform; break; - case '0': resetToggles(); break; + case '1': applyPreset(0); break; // AMIGA + acid + case '2': applyPreset(1); break; // C64 + chiptune + case '3': applyPreset(2); break; // OLDSKOOL + detroit + case '4': applyPreset(3); break; // NEWSKOOL + future garage + case '5': applyPreset(4); break; // TUNNEL + industrial + case '6': applyPreset(5); break; // STARFIELD + ambient + case '7': applyPreset(6); break; // SCOPE + dnb liquide + case '8': applyPreset(7); break; // CYBER + glitch idm + case '9': applyPreset(8); break; // VAPOR + vocal trance + case '0': applyPreset(9); break; // RAVE + hardcore gabber case 'f': fullscreen_ = !fullscreen_; ofSetFullscreen(fullscreen_); diff --git a/oscope-of/src/ofApp.h b/oscope-of/src/ofApp.h index 7d89e4c..c19feec 100644 --- a/oscope-of/src/ofApp.h +++ b/oscope-of/src/ofApp.h @@ -73,19 +73,32 @@ private: oscope::AudioAnalyzer audio_; oscope::DemoFx demo_; - // Toggles Scope4 — touches 1-9 + 0 reset. + // Toggles Scope4 — modulés par les presets demoparty 0-9. struct ScopeToggles { - bool tunnel = true; // 1 — tunnel 3D background - bool starfield = false; // 2 — starfield (remplace le tunnel) - bool scroller = true; // 3 — sine scroller greetings.txt - bool copperBars = true; // 4 — copper bars haut - bool bobs = true; // 5 — logo bobs - bool tunnelHud = true; // 6 — HUD aléatoire sub-10Hz - bool spectroRing = true; // 7 — spectro circular ring - bool polar = true; // 8 — polar central - bool waveform = true; // 9 — waveform circular CRT + bool tunnel = true; + bool starfield = false; + bool scroller = true; + bool copperBars = true; + bool bobs = true; + bool tunnelHud = true; + bool spectroRing = true; + bool polar = true; + bool waveform = true; } scope4_; + // 10 demoparty presets — appliqués via touches 1..9 + 0. + // Chaque preset = nom + état toggles + style scroller + lettre album SC. + struct DemoPreset { + const char* name; + ScopeToggles toggles; + oscope::ScrollerStyle scroller; + const char* albumLetter; // A..W ou nullptr + }; + DemoPreset presets_[10]; + int presetIdx_ = 0; + void initPresets(); + void applyPreset(int idx); + std::unique_ptr lissajous_; std::unique_ptr spectro_; std::unique_ptr reactive_;