feat(of): 10 demoparty presets + scroller styles

User : "les touches 0 à 10 lanceront une démo party avec la
track supercollider associée + plus de styles de scroller +
q reste le mode live".

DemoFx::ScrollerStyle (6 variants) :
- Classic   sine wave wobble par défaut
- Wavy3D    sine + scale per char (effet respiration 3D)
- Rainbow   hue cycle large par char
- Mirror    classic + reflet vertical en bas (alpha gradient)
- Glitch    jitter horizontal + couleurs cassées (rouge/bleu)
- Neon      outline cyan large + cœur magenta fin

ofApp::DemoPreset (10 presets) :
  1 AMIGA      classic     -> album A (acid_journey)
  2 C64        wavy3d      -> album M (chiptune_madness)
  3 OLDSKOOL   mirror      -> album G (detroit_soulful)
  4 NEWSKOOL   rainbow     -> album L (future_garage)
  5 TUNNEL     classic     -> album P (industrial)
  6 STARFIELD  neon        -> album J (ambient_cinematic)
  7 SCOPE      classic     -> album I (liquid_dnb)
  8 CYBER      glitch      -> album V (glitch_idm)
  9 VAPOR      mirror      -> album U (vocal_trance)
  0 RAVE       rainbow     -> album T (hardcore_gabber)

Chaque preset = état toggles ScopeToggles + style scroller +
lettre album SC (envoyée via /control/playAlbum à sclang).

Touches 1..9 + 0 lancent les presets (au lieu de toggles
individuels). 'q' reste libre pour le mode live (contrôle
manuel des éléments via les autres touches a-z).

Overlay bas droite affiche le preset actif en gros (magenta).
This commit is contained in:
L'électron rare
2026-05-07 20:42:43 +02:00
parent 51fecc72b4
commit 9e17e95732
4 changed files with 178 additions and 38 deletions
+71 -14
View File
@@ -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<float>(text_.size());
if (scrollX_ < -fullW) scrollX_ = static_cast<float>(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();
+12
View File
@@ -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; };
+72 -14
View File
@@ -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_);
+23 -10
View File
@@ -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<oscope::LissajousVis> lissajous_;
std::unique_ptr<oscope::SpectrogramVis> spectro_;
std::unique_ptr<oscope::ReactiveVis> reactive_;