{"name":"pixels_demo.nu","source":"// pixels_demo.nu — minimal canvas animation: a plasma-like color gradient\n//\n// Opens a WxH pixel window, animates a moving sinusoidal color field\n// frame by frame, and closes cleanly when the window is closed\n// (native) or the Stop button is pressed (browser playground).\n//\n// The canvas framebuffer is w*h i64 slots; the low 32 bits of each\n// slot are an ARGB8888 pixel (0xAARRGGBB). Alpha = 0xFF = opaque.\n//\n// Demonstrates the full canvas API:\n//     canvas_open        — allocate + show the window, get framebuffer\n//     canvas_present     — blit framebuffer to the visible surface\n//     canvas_sleep       — frame pacing; yields to the host event loop\n//     canvas_should_close— non-zero when the user closes the window\n//     canvas_close       — teardown\n\n& `canvas` @ canvas_open i w i h → *i\n\n& `canvas` @ canvas_present → v\n\n& `canvas` @ canvas_sleep i ms → v\n\n& `canvas` @ canvas_should_close → i\n\n& `canvas` @ canvas_close → v\n\n: i W 160\n: i H 90\n: i FPS 30\n\n// Sine look-up table: indexed by t in milliradians in [0, LUT_SIZE).\n// Populated once at startup from isin(); the inner render loop then\n// only does array reads, turning three trig calls per pixel into\n// three memory loads.\n: i LUT_SIZE 6283\n\n// ── crude fixed-point sine: returns sin(x / 1000) * 1000 as an int.\n// Uses the identity sin(x) ≈ x - x^3/6 for |x| < 1 and wraps into\n// that range. Not precise, but shapes a nice plasma.\n@ isin i t → i {\n    // Wrap t into [-3141, 3141]   (≈ -π..π, in milliradians)\n    : ~ i x % t 6283\n    ? > x 3141 { = x - x 6283 } {}\n    ? < x - 0 3141 { = x + x 6283 } {}\n    // Use Bhaskara's approx: sin(x) ≈ 4x(π-|x|) / (5π² - x(π-|x|))  (in milli)\n    : i absx ? < x 0 - 0 x x\n    : i k - 3141 absx\n    : i num * 4 * x k\n    : i den - * 5 * 3141 3141 * x k\n    // guard against divide by zero on small kernels\n    ? == den 0 { ^ 0 } {}\n    ^ / * num 1000 den\n}\n\n// Positive modulo that handles any sign of t. NURL's % is C srem,\n// which can return negative on negative inputs — wrap via (t%L + L) % L.\n@ pmod i t i L → i { ^ % + % t L L L }\n\n@ main → i {\n    // Build three channel-specific LUTs up-front. Each lut_X entry\n    // already has its byte shifted into the final ARGB position\n    // (r<<16, g<<8, b<<0) with the 0xFF alpha baked into lut_r.\n    // That turns the inner-loop pixel formula into:\n    //     pixel = lut_r[i1] | lut_g[i2] | lut_b[i3]\n    // i.e. three loads, three ORs, one store — no multiplies on the\n    // hot path, no trig, no division. ~150 KiB total table size.\n    : *i lut_r # *i ( malloc * LUT_SIZE 8 )\n    : *i lut_g # *i ( malloc * LUT_SIZE 8 )\n    : *i lut_b # *i ( malloc * LUT_SIZE 8 )\n    : ~ i k 0\n    ~ < k LUT_SIZE {\n        : i v + 128 / * ( isin k ) 120 1000\n        = . lut_r k + 4278190080 * v 65536  // 0xFF<<24 | v<<16\n        = . lut_g k * v 256  //            v<<8\n        = . lut_b k v  //            v<<0\n        = k + k 1\n    }\n\n    : *i fb ( canvas_open W H )\n    : i frame_ms / 1000 FPS\n    : ~ i t 0\n\n    ~ == 0 ( canvas_should_close ) {\n        // Per-frame t-phases for the three channels.\n        : i p1 t\n        : i p2 * t 2\n        : i p3 * t 3\n\n        : ~ i y 0\n        ~ < y H {\n            // Per-row base indices (positive mod once).\n            // Channel 1 has no y term: base is constant across rows.\n            : i base1 ( pmod p1 LUT_SIZE )\n            : i base2 ( pmod + p2 * y 50 LUT_SIZE )\n            : i base3 ( pmod + p3 * y - 0 30 LUT_SIZE )\n\n            // Per-x stride of each channel's sine argument.\n            // Using straight add + conditional sub for cheap wrap-around.\n            : ~ i i1 base1\n            : ~ i i2 base2\n            : ~ i i3 base3\n            : i row_off * y W\n\n            : ~ i x 0\n            ~ < x W {\n                // Pre-shifted LUTs already embed alpha/r/g/b into\n                // their ARGB slots → one OR per channel yields the pixel.\n                = . fb + row_off x | | . lut_r i1 . lut_g i2 . lut_b i3\n\n                // Advance LUT indices by each channel's x-stride,\n                // wrapping modulo LUT_SIZE with a single compare+sub.\n                = i1 + i1 40\n                ? >= i1 LUT_SIZE { = i1 - i1 LUT_SIZE } {}\n                = i2 + i2 20\n                ? >= i2 LUT_SIZE { = i2 - i2 LUT_SIZE } {}\n                = i3 + i3 30\n                ? >= i3 LUT_SIZE { = i3 - i3 LUT_SIZE } {}\n\n                = x + x 1\n            }\n            = y + y 1\n        }\n\n        ( canvas_present )\n        ( canvas_sleep frame_ms )\n        = t + t 1\n    }\n\n    ( canvas_close )\n    ^ 0\n}\n","bytes":4601}