Untitled

 avatar
unknown
plain_text
a year ago
12 kB
13
Indexable
//==============================================================
// Jayden's Night Storm — TempleOS / HolyC (Super Version)
// By: Jayden + "Anita Max Win"
//==============================================================
// Graphics:
//  * 3D-ish rain (depth/parallax/wind shear/shimmer)
//  * Dark & Light modes, gradient sky, fractal lightning (dim blue)
// Audio (PC Speaker):
//  * Time-slice "mixer": layer multiple continuous voices
//  * Voices: White, Pink, Brown, Rain, Wind, Ocean, Box Fan, Oscillating Fan
//  * Thunder: one-shot overlay + screen flash
// Controls (top-left shows live help):
//  * 1 White   2 Pink   3 Brown
//  * 4 Thunder (one-shot)
//  * 5 Rain    6 Wind   7 Ocean
//  * 8 Fan     9 OscFan
//  * 0 Mute all
//  * m Toggle Dark/Light
//  * t Toggle auto-thunder
//  * w Toggle wind shear   ←/→ Adjust wind strength
//  * + / - Rain intensity
//  * q Quit
//==============================================================

enum Mode   { MODE_LIGHT=0, MODE_DARK=1 };
enum Voice  { V_WHITE=0, V_PINK, V_BROWN, V_RAIN, V_WIND, V_OCEAN, V_FAN, V_OSCFAN, V__COUNT };
#define BIT(v) (1<<(v))

I64  gWinW=800, gWinH=600;
Mode gMode=MODE_DARK;
volatile U8  gRun=TRUE;
volatile U8  gAutoThunder=TRUE;
volatile U8  gWindOn=TRUE;
volatile F64 gWind=0.6;
volatile I64 gRainCount=480;
volatile F64 gTargetFPS=60.0;

// Active voice bitmask for layered audio
volatile U32 gActiveMask = BIT(V_RAIN);   // start with rain on

// Thunder flags
volatile U8 gDoFlash=FALSE;
volatile U8 gThunderPending=FALSE;

//--------------- Math / RNG helpers ----------------
F64 Rng01() { return (F64)RandU32() / (F64)0xFFFFFFFF; }
F64 Lerp(F64 a,F64 b,F64 t){ return a+(b-a)*t; }
F64 Clamp(F64 x,F64 a,F64 b){ if(x<a) return a; if(x>b) return b; return x; }
I64 ClampI(I64 x,I64 a,I64 b){ if(x<a) return a; if(x>b) return b; return x; }

U32 SkyTopCol, SkyBotCol, RainNearCol, RainMidCol, RainFarCol, BoxCol, TextCol, BoltCol, TintCol;

U0 SetPalette(){
    if (gMode==MODE_DARK) {
        SkyTopCol  = RGB(8,14,28);
        SkyBotCol  = RGB(10,18,40);
        RainFarCol = RGB(70,100,150);
        RainMidCol = RGB(110,150,210);
        RainNearCol= RGB(160,190,235);
        BoxCol     = RGB(22,30,50);
        TextCol    = RGB(210,220,240);
        BoltCol    = RGB(120,160,220);
        TintCol    = RGB(20,40,90);  // dim dark-blue flash
    } else {
        SkyTopCol  = RGB(150,160,180);
        SkyBotCol  = RGB(180,190,205);
        RainFarCol = RGB(80,110,150);
        RainMidCol = RGB(95,135,180);
        RainNearCol= RGB(115,165,215);
        BoxCol     = RGB(230,235,240);
        TextCol    = RGB(20,30,40);
        BoltCol    = RGB(140,170,225);
        TintCol    = RGB(90,110,160);
    }
}

U32 GradCol(F64 t, U32 c0, U32 c1){
    I64 r0=(c0>>16)&255, g0=(c0>>8)&255, b0=c0&255;
    I64 r1=(c1>>16)&255, g1=(c1>>8)&255, b1=c1&255;
    I64 r=r0+(I64)((r1-r0)*t);
    I64 g=g0+(I64)((g1-g0)*t);
    I64 b=b0+(I64)((b1-b0)*t);
    return RGB(r,g,b);
}

// Precompute gradient for speed (rebuilt on mode change)
U32 *gSkyScan;
U0 BuildSky(){
    if (!gSkyScan) gSkyScan=MAlloc(sizeof(U32)*gWinH);
    I64 y; for (y=0;y<gWinH;y++){
        F64 t=(F64)y/(F64)gWinH;
        gSkyScan[y]=GradCol(t,SkyTopCol,SkyBotCol);
    }
}

//--------------- Rain system -----------------------
class Drop { F64 x,y,z,vy,len,jitter; I64 shade; };
I64 gDropsMax=1200;
Drop *gDrops;

U0 InitDrop(Drop *d){
    d->z = Clamp(0.05 + Rng01()*0.95,0.05,1.0);
    d->x = Rng01()*gWinW;
    d->y = -Rng01()*gWinH;
    d->vy = Lerp(3.0, 20.0, d->z);
    d->len= Lerp(5.0, 18.0, d->z);
    d->jitter = (Rng01()-0.5)*0.6;
    if (d->z<0.33) d->shade=RainFarCol;
    else if (d->z<0.66) d->shade=RainMidCol;
    else d->shade=RainNearCol;
}

U0 InitRain(I64 count){
    if (count>gDropsMax) count=gDropsMax;
    I64 i; for (i=0;i<count;i++) InitDrop(&gDrops[i]);
}

U0 StepDrop(Drop *d){
    F64 wind = gWindOn ? gWind : 0.0;
    d->x += wind + d->jitter;
    d->y += d->vy;
    if (d->y>gWinH+20 || d->x<-20 || d->x>gWinW+20) InitDrop(d);
}

U0 DrawDrop(Drop *d){
    F64 dx = (gWindOn? (gWind*2.2) : 0.0) + d->jitter*2.0;
    Line(d->x, d->y, d->x+dx, d->y - d->len, d->shade);
}

U0 DrawBackground(){
    I64 y;
    for (y=0;y<gWinH;y++) Line(0,y,gWinW,y,gSkyScan[y]);
}

U0 DrawRainLayered(){
    I64 i;
    for (i=0;i<gRainCount;i++) if (gDrops[i].z<0.33){ DrawDrop(&gDrops[i]); StepDrop(&gDrops[i]); }
    for (i=0;i<gRainCount;i++) if (gDrops[i].z>=0.33 && gDrops[i].z<0.66){ DrawDrop(&gDrops[i]); StepDrop(&gDrops[i]); }
    for (i=0;i<gRainCount;i++) if (gDrops[i].z>=0.66){ DrawDrop(&gDrops[i]); StepDrop(&gDrops[i]); }
}

//--------------- Lightning -------------------------
U0 FractalBolt(F64 x0,F64 y0,F64 x1,F64 y1,I64 depth){
    if (depth<=0){ Line(x0,y0,x1,y1,BoltCol); return; }
    F64 mx=(x0+x1)/2.0, my=(y0+y1)/2.0;
    F64 nx=-(y1-y0), ny=(x1-x0);
    F64 L=Sqrt(nx*nx+ny*ny); if (L>0){ nx/=L; ny/=L; }
    F64 jitter=(Rng01()-0.5)*Lerp(4,20,1.0/(depth+1));
    mx+=nx*jitter; my+=ny*jitter;
    FractalBolt(x0,y0,mx,my,depth-1);
    FractalBolt(mx,my,x1,y1,depth-1);
}

U0 LightningFlash(){
    Box(0,0,gWinW,gWinH,TintCol); // dim blue overlay
    F64 x0=Lerp(0,gWinW,Rng01());
    F64 x1=Lerp(0,gWinW,Rng01());
    FractalBolt(x0,0, Lerp(x0-80,x0+80,Rng01()), Lerp(gWinH*0.5,gWinH*0.9,Rng01()), 5);
    if (Rng01()<0.4)
        FractalBolt(x1,0, Lerp(x1-120,x1+120,Rng01()), Lerp(gWinH*0.4,gWinH*0.85,Rng01()), 5);
}

//--------------- UI --------------------------------
U0 DrawControlBox(){
    Box(8,8,310,210,BoxCol);
    I64 y=20;
    PrintAt(14,y, "Rain Controls"); y+=14;
    PrintAt(14,y, "[1] White  [%c]  [2] Pink  [%c]  [3] Brown [%c]",
        (gActiveMask&BIT(V_WHITE))?'X':' ',
        (gActiveMask&BIT(V_PINK ))?'X':' ',
        (gActiveMask&BIT(V_BROWN))?'X':' '); y+=12;
    PrintAt(14,y, "[4] Thunder (one-shot)"); y+=12;
    PrintAt(14,y, "[5] Rain   [%c]  [6] Wind  [%c]  [7] Ocean [%c]",
        (gActiveMask&BIT(V_RAIN ))?'X':' ',
        (gActiveMask&BIT(V_WIND ))?'X':' ',
        (gActiveMask&BIT(V_OCEAN))?'X':' '); y+=12;
    PrintAt(14,y, "[8] Fan    [%c]  [9] OscFan[%c]  [0] Mute All",
        (gActiveMask&BIT(V_FAN   ))?'X':' ',
        (gActiveMask&BIT(V_OSCFAN))?'X':' '); y+=12;
    PrintAt(14,y, "[m] Mode: %s   [t] Auto-Thunder: %s",
        (gMode==MODE_DARK)?"Dark":"Light",
        gAutoThunder?"On":"Off"); y+=12;
    PrintAt(14,y, "[w] Wind Shear: %s  (←/→ to adjust, %.2f)",
        gWindOn?"On":"Off", gWind); y+=12;
    PrintAt(14,y, "[+/-] Rain Intensity: %d", gRainCount); y+=12;
    PrintAt(14,y, "[q] Quit");
}

//--------------- Audio: voice states & mixer --------
// Each voice provides a short "slice" (frequency + tiny duration).
// We interleave slices across active voices to approximate mixing.

class VoiceState {
    F64 phase, a, b;   // generic params (e.g., oscillator, drift)
    I64 i0,i1;         // generic ints (e.g., counters)
    I64 freq;          // last computed freq
};

// Per-voice states
VoiceState gVS[V__COUNT];

// Voice slice duration (ms). Smaller = smoother blend, more CPU.
I64 gSliceMs = 4;

// Individual voice slice generators
U0 V_White(VoiceState *s){
    s->freq = 200 + (I64)(Rng01()*7600);
}
U0 V_Pink(VoiceState *s){
    s->freq = 120 + (I64)(Rng01()*1200);
}
U0 V_Brown(VoiceState *s){
    if (s->freq==0) s->freq=180;
    s->freq += (I64)((Rng01()-0.5)*80);
    s->freq = ClampI(s->freq,60,600);
}
U0 V_Rain(VoiceState *s){
    s->freq = 700 + (I64)(Rng01()*900);
}
U0 V_Wind(VoiceState *s){
    if (s->i0==0) s->i0=100;
    I64 base = 90 + (I64)(Rng01()*50);
    s->freq = base + (I64)(Rng01()*120);
}
U0 V_Ocean(VoiceState *s){
    // Low rumble + occasional "crash" swell
    if (s->i0>0){ // in crash
        s->freq = 200 + (I64)(Rng01()*300);
        s->i0 -= gSliceMs;
    } else {
        s->freq = 40 + (I64)(Rng01()*60);
        if ((RandU32()%100) < 3) s->i0 = 150 + (RandU32()%200); // start crash (ms)
    }
}
U0 V_Fan(VoiceState *s){
    // Alternate base and harmonic
    s->i0 ^= 1;
    s->freq = s->i0 ? 120 : 240;
}
U0 V_OscFan(VoiceState *s){
    // Slow sinus wobble around 150 Hz
    s->phase += 0.05;
    F64 wob = 20*Sin(s->phase);
    s->freq = 150 + (I64)wob;
}

// Thunder one-shot implemented as a temporary "overlay" voice
class ThunderState { I64 remainMs; };
ThunderState gTH;

U0 ThunderStart(){
    gTH.remainMs = 420;   // total rumble time
    gDoFlash = TRUE;
}

U8 ThunderActive(){ return gTH.remainMs>0; }

U0 ThunderSlice(){
    // Low rumble with little tremor
    Sound(40 + (I64)(Rng01()*60));
    Sleep(gSliceMs);
    gTH.remainMs -= gSliceMs;
}

// Main audio mixer task
U0 AudioTask(){
    while (gRun){
        // If thunder requested, start overlay
        if (gThunderPending){
            gThunderPending=FALSE;
            ThunderStart();
        }

        U32 mask = gActiveMask;
        // If no voices active and no thunder, idle
        if (!mask && !ThunderActive()){
            Sound(0);
            Sleep(10);
            continue;
        }

        // Interleave slices across active voices
        I64 v;
        for (v=0; v<V__COUNT; v++){
            if (ThunderActive()){
                ThunderSlice();
                continue; // thunder slices can consume several cycles
            }
            if (!(mask & BIT(v))) continue;

            // compute next slice freq for voice v
            VoiceState *s=&gVS[v];
            switch(v){
                case V_WHITE:  V_White(s);  break;
                case V_PINK:   V_Pink(s);   break;
                case V_BROWN:  V_Brown(s);  break;
                case V_RAIN:   V_Rain(s);   break;
                case V_WIND:   V_Wind(s);   break;
                case V_OCEAN:  V_Ocean(s);  break;
                case V_FAN:    V_Fan(s);    break;
                case V_OSCFAN: V_OscFan(s); break;
            }
            Sound(s->freq);
            Sleep(gSliceMs);
        }
    }
    Sound(0);
}

// Auto-thunder trigger
U0 ThunderAutoTask(){
    while (gRun){
        if (gAutoThunder && (RandU32()%100)<2) gThunderPending=TRUE;
        Sleep(50);
    }
}

//--------------- Input -----------------------------
U0 ToggleVoice(I64 v){
    gActiveMask ^= BIT(v);
}

U0 HandleKey(I64 ch){
    switch(ch){
        case '1': ToggleVoice(V_WHITE);  break;
        case '2': ToggleVoice(V_PINK);   break;
        case '3': ToggleVoice(V_BROWN);  break;
        case '4': gThunderPending=TRUE;  break; // one-shot
        case '5': ToggleVoice(V_RAIN);   break;
        case '6': ToggleVoice(V_WIND);   break;
        case '7': ToggleVoice(V_OCEAN);  break;
        case '8': ToggleVoice(V_FAN);    break;
        case '9': ToggleVoice(V_OSCFAN); break;
        case '0': gActiveMask=0; Sound(0); break;

        case 'm': gMode = (gMode==MODE_DARK)?MODE_LIGHT:MODE_DARK; SetPalette(); BuildSky(); break;
        case 't': gAutoThunder = !gAutoThunder; break;
        case 'w': gWindOn = !gWindOn; break;
        case K_LEFT:  gWind = Clamp(gWind-0.15,-4.0,4.0); break;
        case K_RIGHT: gWind = Clamp(gWind+0.15,-4.0,4.0); break;
        case '+': gRainCount = ClampI(gRainCount+30,50,gDropsMax); break;
        case '-': gRainCount = ClampI(gRainCount-30,50,gDropsMax); break;
        case 'q': gRun=FALSE; break;
    }
}

//--------------- Render loop -----------------------
U0 RenderTask(){
    F64 frameMs = 1000.0/gTargetFPS;
    while (gRun){
        F64 t0=MSec();
        Cls();
        DrawBackground();
        DrawControlBox();
        DrawRainLayered();

        if (gDoFlash){
            LightningFlash();
            Sleep(20); // gentle afterglow without blinding
            gDoFlash=FALSE;
        }

        // Frame pacing
        F64 tElapsed=MSec()-t0;
        F64 toSleep=frameMs-tElapsed;
        if (toSleep>0) Sleep((I64)toSleep);

        while (KeyReady()) HandleKey(GetChar());
    }
}

//--------------- App entry -------------------------
U0 StormApp(){
    WinBorder(ON);
    WinMax();

    gRun=TRUE;
    gMode=MODE_DARK;
    SetPalette();
    BuildSky();

    gDropsMax=1200;
    gDrops = MAlloc(sizeof(Drop)*gDropsMax);
    InitRain(gRainCount);

    // reset voice states
    MemSet(gVS,0,sizeof(gVS));
    gActiveMask = BIT(V_RAIN); // default: Rain on

    I64 audTid = Spawn(&AudioTask);
    I64 thdTid = Spawn(&ThunderAutoTask);

    RenderTask();

    gRun=FALSE;
    Kill(audTid);
    Kill(thdTid);
    Sound(0);
    Free(gDrops);
}

U0 RainApp(){ StormApp(); }
Editor is loading...
Leave a Comment