Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

macos: demo.sh \u2014 boot \u2192 prompt \u2192 notepat waltz, single mkv (Phase D)

End-to-end pipeline tying together the pieces from phases A\u2013C:

- AC_WIN_W / AC_WIN_H env overrides for SDL_CreateWindow so the demo
renders natively at 1280x800 instead of the 640x480 dev default.
Falls back to INITIAL_WIN_* when unset (notepat.app still launches
at its original size).

- scripts/demo.sh ties everything together:
1. Generates a "good afternoon <handle>. enjoy <city>." greeting
via /usr/bin/say (Samantha @ 165 wpm).
2. Runs ac-native with AC_BOOT_ANIM + AC_INJECT_SEQUENCE typing
'notepat<enter>', then an oom-pah-pah 3/4 waltz across four
bars in C minor (Cm \u2192 Fm \u2192 Gm \u2192 Cm) using notepat's z (low
bass), c/e/f/a/g/b (mid chord) keys. 12 notes, 500 ms/beat.
3. Frame-dumps during boot anim + main loop so the video is one
contiguous 60 fps h264 stream.
4. Mixes the synth WAV tap + TTS AIFF with ffmpeg amix (TTS
delayed 167 ms to line up with the boot-anim f==10 cue that
drives the greeting on Linux), then encodes mkv.

- Boot-animation prelude now also dumps frames when AC_FRAME_DUMP_DIR
is set; the piece loop seeds its frame index from there so both
halves of the run form one sequence.

Output: ~/Desktop/ac-boot-shots/demo.mkv \u2014 ~14 s, 1280x800, 60 fps
h264 + 48 kHz stereo aac. Override HANDLE / CITY / HOUR / OUT for
other greetings; KEEP_STAGES=1 preserves /tmp/ac-demo-final for
debugging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+142 -2
+33 -2
fedac/native/macos/main.c
··· 558 558 | SDL_WINDOW_ALWAYS_ON_TOP; 559 559 } 560 560 561 + // AC_WIN_W / AC_WIN_H override the compile-time default so the demo 562 + // pipeline can record at 1280x800 without recompiling. FB size then 563 + // falls out of WIN/DENSITY so the piece still sees chunky-pixel 564 + // coordinates. 565 + int init_w = getenv("AC_WIN_W") ? atoi(getenv("AC_WIN_W")) : INITIAL_WIN_W; 566 + int init_h = getenv("AC_WIN_H") ? atoi(getenv("AC_WIN_H")) : INITIAL_WIN_H; 567 + if (init_w < 128) init_w = INITIAL_WIN_W; 568 + if (init_h < 128) init_h = INITIAL_WIN_H; 561 569 SDL_Window *win = SDL_CreateWindow("Notepat", 562 - INITIAL_WIN_W, INITIAL_WIN_H, 570 + init_w, init_h, 563 571 win_flags); 564 572 if (!win) { fprintf(stderr, "SDL_CreateWindow: %s\n", SDL_GetError()); SDL_Quit(); return 1; } 565 573 if (!fullscreen) SDL_SetWindowHitTest(win, hit_test_cmd_drag, NULL); ··· 618 626 .stride = fb_w, 619 627 }; 620 628 if (!fb.pixels) { fprintf(stderr, "fb alloc failed\n"); return 1; } 629 + 630 + // Frame-dump dir declared early so the boot-animation prelude below 631 + // can also contribute frames to the same sequence. Read once here; 632 + // the piece-loop block below re-reads it in the same variable. 633 + const char *early_frame_dir = getenv("AC_FRAME_DUMP_DIR"); 634 + int early_frame_idx = 0; 621 635 622 636 // Boot animation prelude — 2 s of the shared boot_anim renderer before 623 637 // the piece loads. Gated on AC_BOOT_ANIM (default off so existing ··· 660 674 SDL_RenderClear(ren); 661 675 SDL_RenderTexture(ren, tex, NULL, NULL); 662 676 SDL_RenderPresent(ren); 677 + if (early_frame_dir) { 678 + int d = density < 1 ? 1 : density; 679 + const uint32_t *src = fb.pixels; 680 + int out_w = fb.width, out_h = fb.height; 681 + uint32_t *scaled = NULL; 682 + if (d > 1) { 683 + scaled = upscale_nn(fb.pixels, fb.width, fb.height, d); 684 + if (scaled) { src = scaled; out_w *= d; out_h *= d; } 685 + } 686 + char path[1200]; 687 + snprintf(path, sizeof path, "%s/frame_%05d.png", 688 + early_frame_dir, early_frame_idx++); 689 + png_write_argb(path, src, out_w, out_h, out_w); 690 + free(scaled); 691 + } 663 692 // Drain events so the OS doesn't mark the window unresponsive. 664 693 SDL_Event ev; while (SDL_PollEvent(&ev)) { 665 694 if (ev.type == SDL_EVENT_QUIT) goto boot_anim_done; ··· 856 885 // into an mkv. Density-2 upscaling applied so the saved PNGs match 857 886 // what you see on screen (chunky retro pixels stay crisp). 858 887 const char *frame_dump_dir = getenv("AC_FRAME_DUMP_DIR"); 859 - int frame_dump_idx = 0; 888 + // Continue from the boot-animation prelude's index so the whole run 889 + // forms one contiguous frame_00000…frame_NNNNN sequence. 890 + int frame_dump_idx = early_frame_idx; 860 891 861 892 // AC_WAV_OUT=<path> — tap audio callback output into a float32 stereo 862 893 // @ 48 kHz WAVE file. Paired with frame-dump, ffmpeg can mux the two
+109
fedac/native/macos/scripts/demo.sh
··· 1 + #!/usr/bin/env bash 2 + # demo.sh — record the full ac-native first-demo: 3 + # boot animation → prompt → type "notepat" → waltz in notepat 4 + # into a single mkv (1280x800, 60 fps, h264 + aac) with synced audio. 5 + # 6 + # Audio is a stereo mix of: 7 + # - the host's CoreAudio WAV tap (synth voice playback for every 8 + # keystroke + notepat's waltz notes) 9 + # - a macOS TTS greeting rendered by `say` and delayed to match the 10 + # boot animation's f==10 cue (the Linux kernel does the same) 11 + # 12 + # Env overrides (optional): 13 + # HANDLE=jeffrey # the greeting handle (passed as AC_SHOT_HANDLE) 14 + # CITY="Los Angeles" 15 + # HOUR=13 16 + # OUT=~/Desktop/ac-boot-shots/demo.mkv 17 + # KEEP_STAGES=1 # keep /tmp/ac-demo-final after success 18 + # 19 + # Requires: ffmpeg (brew install ffmpeg), /usr/bin/say, and the 20 + # fedac/native/macos/build/ac-native-macos-core binary. 21 + 22 + set -euo pipefail 23 + 24 + HANDLE="${HANDLE:-jeffrey}" 25 + CITY="${CITY:-Los Angeles}" 26 + HOUR="${HOUR:-13}" 27 + OUT="${OUT:-$HOME/Desktop/ac-boot-shots/demo.mkv}" 28 + STAGE="${STAGE:-/tmp/ac-demo-final}" 29 + 30 + REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd) 31 + BIN="$REPO_ROOT/fedac/native/macos/build/ac-native-macos-core" 32 + PROMPT="$REPO_ROOT/fedac/native/pieces/prompt.mjs" 33 + 34 + [[ -x "$BIN" ]] || { echo "error: binary missing — run make in fedac/native/macos" >&2; exit 1; } 35 + [[ -f "$PROMPT" ]] || { echo "error: prompt.mjs not found at $PROMPT" >&2; exit 1; } 36 + 37 + rm -rf "$STAGE" 38 + mkdir -p "$STAGE/frames" 39 + 40 + # Typing timeline — all delays are cumulative offsets from prompt load. 41 + # Boot animation runs for ~2s before this, so the earliest keystroke is 42 + # effectively ~2.5s into the final video. 43 + # 44 + # Layout: 45 + # prompt typing: n o t e p a t <enter> (quick, ~1s) 46 + # ~1.4s pause while notepat loads + settles 47 + # waltz: 4 bars × 3 beats at 500ms/beat = 6s 48 + # z c e (Cm bar) 49 + # z f a (Fm bar) 50 + # z g b (Gm bar) 51 + # z c e (Cm bar back home) 52 + SEQ=( 53 + "n,500" "o,120" "t,120" "e,120" "p,120" "a,120" "t,120" "enter,400" 54 + "z,1380" "c,500" "e,500" 55 + "z,500" "f,500" "a,500" 56 + "z,500" "g,500" "b,500" 57 + "z,500" "c,500" "e,500" 58 + ) 59 + # Join with '|' separators (AC_INJECT_SEQUENCE syntax). 60 + IFS='|' SEQ_STR="${SEQ[*]}"; unset IFS 61 + 62 + # Generate the TTS greeting. -r 165 slightly slower than default so it 63 + # reads less urgent; -v Samantha is the long-time macOS US voice. 64 + say -v Samantha -r 165 \ 65 + -o "$STAGE/tts.aiff" \ 66 + "good afternoon $HANDLE. enjoy $CITY." 67 + 68 + echo "→ running ac-native for ~12 s …" 69 + AC_HEADLESS_MS=12000 \ 70 + AC_BOOT_ANIM=1 \ 71 + AC_WIN_W=1280 AC_WIN_H=800 \ 72 + AC_SHOT_HANDLE="$HANDLE" \ 73 + AC_SHOT_CITY="$CITY" \ 74 + AC_SHOT_HOUR="$HOUR" \ 75 + AC_FRAME_DUMP_DIR="$STAGE/frames" \ 76 + AC_WAV_OUT="$STAGE/synth.wav" \ 77 + AC_INJECT_SEQUENCE="$SEQ_STR" \ 78 + "$BIN" "$PROMPT" 2>&1 | sed 's/^/ [ac] /' 79 + 80 + N_FRAMES=$(ls "$STAGE/frames" | wc -l | tr -d ' ') 81 + echo "→ captured $N_FRAMES frames + $(du -h "$STAGE/synth.wav" | cut -f1) synth WAV" 82 + 83 + # Mix synth + TTS into one track. TTS starts at 167ms to match f==10 of 84 + # the boot animation (where Linux kicks the greeting off). `-filter_complex` 85 + # delays TTS and mixes equal-loudness; `duration=longest` keeps whichever 86 + # track runs longest as the final length. 87 + echo "→ mixing synth + TTS …" 88 + ffmpeg -y \ 89 + -i "$STAGE/synth.wav" \ 90 + -i "$STAGE/tts.aiff" \ 91 + -filter_complex "[1]adelay=167|167,volume=1.3[tts]; [0:a][tts]amix=inputs=2:duration=longest:dropout_transition=0[out]" \ 92 + -map "[out]" "$STAGE/mix.wav" 2>&1 | tail -2 93 + 94 + echo "→ encoding mkv …" 95 + mkdir -p "$(dirname "$OUT")" 96 + ffmpeg -y \ 97 + -framerate 60 -i "$STAGE/frames/frame_%05d.png" \ 98 + -i "$STAGE/mix.wav" \ 99 + -c:v libx264 -pix_fmt yuv420p -crf 18 -preset slow \ 100 + -c:a aac -b:a 192k \ 101 + "$OUT" 2>&1 | tail -2 102 + 103 + echo 104 + echo "demo ready: $OUT ($(du -h "$OUT" | cut -f1))" 105 + ffprobe -v error -show_entries format=duration,bit_rate "$OUT" 2>&1 | sed 's/^/ /' 106 + 107 + if [[ -z "${KEEP_STAGES:-}" ]]; then 108 + rm -rf "$STAGE" 109 + fi