灵符lingfu

crypto_donuts

↓ 0 downloads

Linux · x86-64 ✕Web · WASM ✕
lingfu install crypto_donuts
Authorsling-lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (9)

src/main.ling
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# CRYPTO DONUT  —  type a string (or record your voice) → a knotwork donut
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# The seed is SHA3-256 hashed into a (p,q) torus knot (geo crypto module). The
# knot curve IS the donut; its collider is the (invisible) orientation-aware
# torus the knot wraps. Shape, bounce, spin & colour all come from the hash.
#
#   ⌨  type to edit the seed        [ DROP ] rebuild donut from the seed
#   🎙 hold [ MIC ] to record sound → the audio's hash becomes the donut
#   ⏺  [ REC ] toggles PPM frame capture → ./recordings/
#   ␣  SPACE re-drop      R re-roll velocity
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

bind seed = "灵 · ling · my cryptographic donut"

bind BOX     = 5.5
bind DONUT_R = 1.8
bind GRAVITY = 15.0
bind DT      = 0.016
bind DAMP    = 0.997

bind start = do {
    open_window(1100, 760)
    set_ambient(0.2)
    set_zdist(18.0)        # pull the camera back outside the box for a full view
    text_set(seed)
    mic_open()
    audio_volume(0.55)

    # professional per-language UI font (Orbitron — holographic Latin display).
    # font_load resolves the path against both the cwd and the script's directory,
    # so this works whether you run from the repo root or the example folder.
    # weight 600 pins the variable wght axis → bold, solid strokes (not thin/hollow)
    bind uiFont = font_load("assets/fonts/Orbitron.ttf", 600.0)
    if uiFont < 0.0 { bind uiFont = font_load("../../assets/fonts/Orbitron.ttf", 600.0) }
    # Noto Sans SC covers Latin + CJK + Thai, so the seed line renders 灵 and any language
    bind bodyFont = font_load("assets/fonts/NotoSansSC.ttf", 500.0)
    if bodyFont < 0.0 { bind bodyFont = font_load("../../assets/fonts/NotoSansSC.ttf", 500.0) }

    bind needBuild = 1.0
    bind recording = 0.0
    bind micRec = 0.0
    bind wasDown = 0.0

    while window_is_open() {
        bind t = time_now()
        # ── input ──
        bind typed = text_poll()
        bind mx = mouse_x()
        bind my = mouse_y()
        bind nd = 0.0
        if mouse_down() { bind nd = 1.0 }
        bind clicked = 0.0
        if nd > 0.5 { if wasDown < 0.5 { bind clicked = 1.0 } }

        # ── (re)build the donut from the current seed ──
        if needBuild > 0.5 {
            bind src = text_get()
            bind label = knot_label(src)
            bind pts = knot_points(src)
            bind n = len(pts) / 3.0
            bind maxRad = 0.0
            bind minRad = 9999.0
            bind bi = 0.0
            while bi < n {
                bind bk = bi * 3.0
                bind bx = pts[bk]
                bind bz = pts[bk + 2.0]
                bind brad = sqrt(bx * bx + bz * bz)
                if brad > maxRad { bind maxRad = brad }
                if brad < minRad { bind minRad = brad }
                bind bi = bi + 1.0
            }
            bind s = DONUT_R / maxRad
            bind majR = (maxRad + minRad) * 0.5 * s
            bind tubeR = (maxRad - minRad) * 0.5 * s
            if tubeR < 0.15 { bind tubeR = 0.15 }
            bind hh1 = sin(pts[0] * 12.9898 + pts[1] * 4.1414) * 43758.5453
            bind f1 = hh1 - floor(hh1)
            bind hh2 = sin(pts[3] * 19.19 + pts[5] * 7.77) * 31718.13
            bind f2 = hh2 - floor(hh2)
            bind hh3 = sin(maxRad * 8.8 + minRad * 3.3) * 12345.678
            bind f3 = hh3 - floor(hh3)
            bind restitution = 0.6 + f1 * 0.35
            bind cr = (sin(f1 * 6.2831 + 0.0) * 0.5 + 0.5) * 215.0 + 40.0
            bind cg = (sin(f1 * 6.2831 + 2.0) * 0.5 + 0.5) * 215.0 + 40.0
            bind cb = (sin(f1 * 6.2831 + 4.0) * 0.5 + 0.5) * 215.0 + 40.0
            bind px = 0.0
            bind py = 0.0 - BOX + DONUT_R + 0.3
            bind pz = 0.0
            bind vx = (f2 - 0.5) * 11.0
            bind vy = 0.0
            bind vz = (f3 - 0.5) * 11.0
            bind ax = 0.0  bind ay = 0.0  bind az = 0.0
            bind wx = (f1 - 0.5) * 4.5  bind wy = (f2 - 0.5) * 4.5  bind wz = (f3 - 0.5) * 4.5
            bind flash = 0.0
            # ── crypto-seeded audio: the hash chooses the donut's "voice" ──
            # f1 picks a root note on a pentatonic-ish ladder (low, warm range);
            # f2 sets the 4D shimmer; f3 sets the resting vibrato speed.
            bind baseFreq = 98.0 + floor(f1 * 10.0) * 24.0   # 98–314 Hz, quantized steps
            bind voiceW   = 0.5 + f2 * 3.5                    # 4D cross-mod depth
            bind lfoBase  = 0.8 + f3 * 3.0                    # resting vibrato Hz
            # initialise the two tones (carrier + a fifth) at their resting voice
            audio_tone(0.0, px, py, pz, voiceW, baseFreq,        0.05, lfoBase, 0.02)
            audio_tone(1.0, px, py, pz, voiceW, baseFreq * 1.5,  0.03, lfoBase * 0.7, 0.015)
            bind needBuild = 0.0
        }

        # ── physics ──
        bind vy = vy + GRAVITY * DT
        bind vx = vx * DAMP  bind vy = vy * DAMP  bind vz = vz * DAMP
        bind px = px + vx * DT  bind py = py + vy * DT  bind pz = pz + vz * DT
        bind ax = ax + wx * DT  bind ay = ay + wy * DT  bind az = az + wz * DT

        bind cax = cos(ax)  bind sax = sin(ax)
        bind cay = cos(ay)  bind say = sin(ay)
        bind caz = cos(az)  bind saz = sin(az)

        bind nx = sax * say * caz - cax * saz
        bind ny = sax * say * saz + cax * caz
        bind nz = sax * cay

        bind gx = 1.0 - nx * nx
        if gx < 0.0 { bind gx = 0.0 }
        bind gy = 1.0 - ny * ny
        if gy < 0.0 { bind gy = 0.0 }
        bind gz = 1.0 - nz * nz
        if gz < 0.0 { bind gz = 0.0 }
        bind hwx = majR * sqrt(gx) + tubeR
        bind hwy = majR * sqrt(gy) + tubeR
        bind hwz = majR * sqrt(gz) + tubeR
        bind clx = BOX - hwx
        bind cly = BOX - hwy
        bind clz = BOX - hwz
        if px > clx       { bind px = clx          bind vx = 0.0 - vx * restitution  bind flash = 1.0  bind wy = wy + vz * 0.5 }
        if px < 0.0 - clx { bind px = 0.0 - clx    bind vx = 0.0 - vx * restitution  bind flash = 1.0 }
        if py > cly       { bind py = cly          bind vy = 0.0 - vy * restitution  bind flash = 1.0  bind wx = wx + vx * 0.6 }
        if py < 0.0 - cly { bind py = 0.0 - cly    bind vy = 0.0 - vy * restitution  bind flash = 1.0 }
        if pz > clz       { bind pz = clz          bind vz = 0.0 - vz * restitution  bind flash = 1.0  bind wx = wx + vx * 0.5 }
        if pz < 0.0 - clz { bind pz = 0.0 - clz    bind vz = 0.0 - vz * restitution  bind flash = 1.0 }
        bind flash = flash * 0.86

        bind sp = vx * vx + vy * vy + vz * vz
        if sp < 1.0 { if py > cly - 0.5 { bind vy = 0.0 - 11.0 - f2 * 6.0  bind vx = (f3 - 0.5) * 9.0  bind vz = (f1 - 0.5) * 9.0 } }
        if key_pressed("space") { bind px = 0.0  bind pz = 0.0  bind py = 0.0 - cly  bind vx = (f2-0.5)*11.0  bind vy = 0.0  bind vz = (f3-0.5)*11.0 }
        if key_pressed("r") { bind vx = sin(t * 7.0) * 10.0  bind vz = cos(t * 5.0) * 10.0  bind vy = 0.0 - 8.0 }

        # ── audio: the crypto voice reacts to the physics ──
        # The hash set the resting pitch/shimmer/vibrato; motion now bends them:
        #   • height       → pitch glides up as the donut rises (theremin-like)
        #   • impact flash  → tone swells loud on every wall bounce
        #   • linear speed  → vibrato depth deepens with velocity
        #   • spin rate     → vibrato (LFO) speeds up with tumbling
        #   • world x/y/z   → spatial pan/attenuation follows the donut in the box
        bind spd     = sqrt(sp)
        bind spinMag = sqrt(wx * wx + wy * wy + wz * wz)
        bind topness = (cly - py) / (cly * 2.0 + 0.001)
        bind fNow    = baseFreq * (1.0 + topness * 0.5 + flash * 0.35)
        bind ampNow  = 0.04 + flash * 0.45 + spd * 0.010
        if ampNow > 0.5 { bind ampNow = 0.5 }
        bind lfoNow  = lfoBase + spinMag * 0.6
        bind depNow  = 0.01 + spd * 0.004
        if depNow > 0.08 { bind depNow = 0.08 }
        audio_tone(0.0, px, py, pz, voiceW, fNow,       ampNow,       lfoNow,       depNow)
        audio_tone(1.0, px, py, pz, voiceW, fNow * 1.5, ampNow * 0.6, lfoNow * 0.7, depNow * 0.7)
        audio_listener(cos(t * 0.22), sin(t * 0.22), cos(0.5), sin(0.5))

        # ── draw 3-D scene ──
        set_color(7, 8, 14)
        fill()
        set_camera(cos(t * 0.22), sin(t * 0.22), cos(0.5), sin(0.5))
        set_ambient(0.2)

        set_color(44, 62, 100)
        draw_line_3d(0.0-BOX,0.0-BOX,0.0-BOX,  BOX,0.0-BOX,0.0-BOX)
        draw_line_3d(BOX,0.0-BOX,0.0-BOX,  BOX,BOX,0.0-BOX)
        draw_line_3d(BOX,BOX,0.0-BOX,  0.0-BOX,BOX,0.0-BOX)
        draw_line_3d(0.0-BOX,BOX,0.0-BOX,  0.0-BOX,0.0-BOX,0.0-BOX)
        draw_line_3d(0.0-BOX,0.0-BOX,BOX,  BOX,0.0-BOX,BOX)
        draw_line_3d(BOX,0.0-BOX,BOX,  BOX,BOX,BOX)
        draw_line_3d(BOX,BOX,BOX,  0.0-BOX,BOX,BOX)
        draw_line_3d(0.0-BOX,BOX,BOX,  0.0-BOX,0.0-BOX,BOX)
        draw_line_3d(0.0-BOX,0.0-BOX,0.0-BOX,  0.0-BOX,0.0-BOX,BOX)
        draw_line_3d(BOX,0.0-BOX,0.0-BOX,  BOX,0.0-BOX,BOX)
        draw_line_3d(BOX,BOX,0.0-BOX,  BOX,BOX,BOX)
        draw_line_3d(0.0-BOX,BOX,0.0-BOX,  0.0-BOX,BOX,BOX)

        # knotwork donut
        bind plx = pts[0]  bind ply = pts[1]  bind plz = pts[2]
        bind a1 = ply * cax - plz * sax
        bind b1 = ply * sax + plz * cax
        bind a2 = plx * cay + b1 * say
        bind b2 = (0.0 - plx) * say + b1 * cay
        bind a3 = a2 * caz - a1 * saz
        bind c3 = a2 * saz + a1 * caz
        bind pwx = px + a3 * s
        bind pwy = py + c3 * s
        bind pwz = pz + b2 * s
        bind i = 1.0
        while i < n {
            bind k = i * 3.0
            bind qlx = pts[k]  bind qly = pts[k + 1.0]  bind qlz = pts[k + 2.0]
            bind d1 = qly * cax - qlz * sax
            bind e1 = qly * sax + qlz * cax
            bind d2 = qlx * cay + e1 * say
            bind e2 = (0.0 - qlx) * say + e1 * cay
            bind d3 = d2 * caz - d1 * saz
            bind f3b = d2 * saz + d1 * caz
            bind cwx = px + d3 * s
            bind cwy = py + f3b * s
            bind cwz = pz + e2 * s
            bind br = sin(i / n * 14.0 + t * 2.5) * 0.35 + 0.65
            set_color(cr*br + (255.0-cr*br)*flash, cg*br + (255.0-cg*br)*flash, cb*br + (255.0-cb*br)*flash)
            draw_line_3d(pwx, pwy, pwz, cwx, cwy, cwz)
            bind pwx = cwx  bind pwy = cwy  bind pwz = cwz
            bind i = i + 1.0
        }

        # 3-D vector label floating above the box — same Orbitron outlines as the
        # UI, now emitted as depth-sorted 3D lines so it tumbles with the scene (and
        # projects through the 4D camera). Colour pulses with the donut's voice.
        if uiFont < 0.0 { } else {
            bind lw = font_width(uiFont, 0.62, label)
            set_color(0, 200 + flash*55.0, 255)
            font_text_3d(uiFont, 0.0-lw*0.5, 0.0-BOX*0.5, 0.0,  1.0,0.0,0.0,  0.0,0.0-1.0,0.0, 0.62, label)
        }

        flush_3d()

        # ── 2-D holographic UI on top ──
        set_blend(1)
        set_color(0, 210, 255)
        if uiFont < 0.0 { ui_text(40.0, 22.0, 22.0, "CRYPTO DONUT") } else { font_text_fill(uiFont, 40.0, 20.0, 26.0, "CRYPTO DONUT") }

        # live "voice" readout — shows the crypto-seeded frequency reacting to physics
        set_color(70, 160, 200)
        if uiFont < 0.0 { ui_text(330.0, 26.0, 13.0, "VOICE") } else { font_text_fill(uiFont, 360.0, 26.0, 15.0, "VOICE") }
        bind hzBar = fNow * 0.6
        set_color(0, 200, 255)
        draw_line(360.0, 48.0, 360.0 + hzBar, 48.0)

        # seed input box (Noto Sans SC → renders 灵 / any language)
        set_color(40, 110, 150)
        ui_bevel(40.0, 60.0, 720.0, 40.0, 10.0)
        set_color(160, 220, 245)
        if bodyFont < 0.0 { ui_text(54.0, 70.0, 16.0, typed) } else { font_text_fill(bodyFont, 54.0, 66.0, 22.0, typed) }

        # DROP button
        bind overDrop = 0.0
        if ui_hot(780.0, 60.0, 130.0, 40.0) { bind overDrop = 1.0 }
        set_color(40 + overDrop*60.0, 200, 150 + overDrop*80.0)
        ui_bevel(780.0, 60.0, 130.0, 40.0, 10.0)
        if uiFont < 0.0 { ui_text(800.0, 70.0, 18.0, "DROP") } else { font_text_fill(uiFont, 808.0, 70.0, 20.0, "DROP") }
        if clicked > 0.5 { if overDrop > 0.5 { bind needBuild = 1.0 } }

        # MIC button (hold to record sound → donut)
        bind overMic = 0.0
        if ui_hot(930.0, 60.0, 130.0, 40.0) { bind overMic = 1.0 }
        if micRec > 0.5 { set_color(255, 80, 90) } else { set_color(40 + overMic*60.0, 160, 230) }
        ui_bevel(930.0, 60.0, 130.0, 40.0, 10.0)
        if uiFont < 0.0 { ui_text(950.0, 70.0, 18.0, "MIC") } else { font_text_fill(uiFont, 962.0, 70.0, 20.0, "MIC") }
        # while held over MIC: capture; on release: build donut from the sound
        if nd > 0.5 { if overMic > 0.5 { mic_capture()  bind micRec = 1.0 } }
        if nd < 0.5 { if micRec > 0.5 { text_set(mic_seed())  mic_clear()  bind micRec = 1.0  bind needBuild = 1.0  bind micRec = 0.0 } }
        # live level meter while recording
        if micRec > 0.5 {
            bind lvl = mic_rms() * 600.0
            set_color(255, 90, 90)
            draw_line(930.0, 112.0, 930.0 + lvl, 112.0)
        }

        # REC toggle (PPM frame capture)
        bind overRec = 0.0
        if ui_hot(40.0, 700.0, 130.0, 44.0) { bind overRec = 1.0 }
        if clicked > 0.5 { if overRec > 0.5 { bind recording = 1.0 - recording } }
        set_color(60, 90, 120)
        ui_frame(40.0, 700.0, 130.0, 44.0, 12.0)
        if recording > 0.5 {
            bind blink = sin(t * 6.0) * 0.5 + 0.5
            set_color(255, 40 + blink*60.0, 40 + blink*60.0)
            draw_disc(64.0, 722.0, 8.0)
            if uiFont < 0.0 { ui_text(82.0, 712.0, 16.0, "REC") } else { font_text_fill(uiFont, 84.0, 712.0, 18.0, "REC") }
            record_frame()
        } else {
            set_color(120, 120, 140)
            if uiFont < 0.0 { ui_text(60.0, 712.0, 16.0, "REC OFF") } else { font_text_fill(uiFont, 60.0, 712.0, 16.0, "REC OFF") }
        }

        set_blend(0)
        bind wasDown = nd
        present()
    }
}

← Back to index