Untitled
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