Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

chore: checkpoint os, audio, and paper updates

+535 -33
+112
fedac/native/src/audio-decode.c
··· 396 396 d->fmt_ctx = NULL; 397 397 } 398 398 399 + // Free peaks 400 + if (d->peaks) { 401 + free(d->peaks); 402 + d->peaks = NULL; 403 + d->peak_count = 0; 404 + } 405 + 399 406 d->loaded = 0; 400 407 d->finished = 0; 401 408 d->decoding = 0; 402 409 d->ring_write = 0; 403 410 d->ring_read = 0; 411 + } 412 + 413 + // Generate decimated max-amplitude peaks for the loaded file. 414 + // Opens a SECOND independent FFmpeg context (so it doesn't disturb playback) 415 + // and decodes the entire file once, recording max abs value per chunk. 416 + int deck_decoder_generate_peaks(ACDeckDecoder *d, int target_count) { 417 + if (!d || !d->loaded || !d->path[0]) return -1; 418 + if (d->peaks) { free(d->peaks); d->peaks = NULL; d->peak_count = 0; } 419 + if (target_count <= 0) target_count = 1024; 420 + 421 + AVFormatContext *fmt = NULL; 422 + if (avformat_open_input(&fmt, d->path, NULL, NULL) < 0) return -1; 423 + if (avformat_find_stream_info(fmt, NULL) < 0) { 424 + avformat_close_input(&fmt); 425 + return -1; 426 + } 427 + int sidx = av_find_best_stream(fmt, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); 428 + if (sidx < 0) { avformat_close_input(&fmt); return -1; } 429 + AVStream *st = fmt->streams[sidx]; 430 + const AVCodec *codec = avcodec_find_decoder(st->codecpar->codec_id); 431 + if (!codec) { avformat_close_input(&fmt); return -1; } 432 + AVCodecContext *cctx = avcodec_alloc_context3(codec); 433 + avcodec_parameters_to_context(cctx, st->codecpar); 434 + if (avcodec_open2(cctx, codec, NULL) < 0) { 435 + avcodec_free_context(&cctx); 436 + avformat_close_input(&fmt); 437 + return -1; 438 + } 439 + 440 + // Estimate total samples for chunking 441 + double duration = d->duration; 442 + if (duration <= 0) duration = 60.0; // fallback 443 + int sample_rate = cctx->sample_rate; 444 + long total_samples = (long)(duration * sample_rate); 445 + if (total_samples < target_count) total_samples = target_count; 446 + long samples_per_chunk = total_samples / target_count; 447 + if (samples_per_chunk < 1) samples_per_chunk = 1; 448 + 449 + d->peaks = (float *)calloc(target_count, sizeof(float)); 450 + if (!d->peaks) { 451 + avcodec_free_context(&cctx); 452 + avformat_close_input(&fmt); 453 + return -1; 454 + } 455 + d->peak_count = target_count; 456 + 457 + // Setup converter to mono float 458 + SwrContext *swr = NULL; 459 + AVChannelLayout out_layout = AV_CHANNEL_LAYOUT_MONO; 460 + swr_alloc_set_opts2(&swr, &out_layout, AV_SAMPLE_FMT_FLT, sample_rate, 461 + &cctx->ch_layout, cctx->sample_fmt, cctx->sample_rate, 462 + 0, NULL); 463 + swr_init(swr); 464 + 465 + AVPacket *pkt = av_packet_alloc(); 466 + AVFrame *frame = av_frame_alloc(); 467 + float chunk_max = 0.0f; 468 + long sample_idx = 0; 469 + int peak_idx = 0; 470 + float *outbuf = (float *)malloc(sample_rate * sizeof(float)); 471 + int outbuf_size = sample_rate; 472 + 473 + while (av_read_frame(fmt, pkt) >= 0 && peak_idx < target_count) { 474 + if (pkt->stream_index != sidx) { av_packet_unref(pkt); continue; } 475 + if (avcodec_send_packet(cctx, pkt) < 0) { av_packet_unref(pkt); continue; } 476 + while (avcodec_receive_frame(cctx, frame) >= 0) { 477 + int max_out = frame->nb_samples + 256; 478 + if (max_out > outbuf_size) { 479 + outbuf = (float *)realloc(outbuf, max_out * sizeof(float)); 480 + outbuf_size = max_out; 481 + } 482 + uint8_t *out_ptr[1] = { (uint8_t *)outbuf }; 483 + int out_n = swr_convert(swr, out_ptr, max_out, 484 + (const uint8_t **)frame->data, frame->nb_samples); 485 + for (int i = 0; i < out_n; i++) { 486 + float v = outbuf[i]; 487 + if (v < 0) v = -v; 488 + if (v > chunk_max) chunk_max = v; 489 + sample_idx++; 490 + if (sample_idx >= samples_per_chunk) { 491 + if (peak_idx < target_count) { 492 + d->peaks[peak_idx++] = chunk_max; 493 + } 494 + chunk_max = 0.0f; 495 + sample_idx = 0; 496 + } 497 + } 498 + } 499 + av_packet_unref(pkt); 500 + } 501 + // Final chunk 502 + if (peak_idx < target_count && chunk_max > 0) { 503 + d->peaks[peak_idx++] = chunk_max; 504 + } 505 + // Fill remaining 506 + while (peak_idx < target_count) d->peaks[peak_idx++] = 0; 507 + 508 + free(outbuf); 509 + av_frame_free(&frame); 510 + av_packet_free(&pkt); 511 + swr_free(&swr); 512 + avcodec_free_context(&cctx); 513 + avformat_close_input(&fmt); 514 + 515 + return target_count; 404 516 } 405 517 406 518 void deck_decoder_destroy(ACDeckDecoder *d) {
+10
fedac/native/src/audio-decode.h
··· 56 56 void *codec_ctx; // AVCodecContext* 57 57 void *swr; // SwrContext* 58 58 int stream_idx; // audio stream index 59 + 60 + // Waveform peaks (decimated max-amplitude samples for visualization) 61 + // Generated once on load by scanning the entire file via separate pass. 62 + float *peaks; // [0..1] amplitude peaks 63 + int peak_count; // number of peaks (typically 1024) 59 64 } ACDeckDecoder; 60 65 61 66 // Create a decoder instance for the given output sample rate ··· 72 77 73 78 // Unload current file and stop thread (decoder can be reused with another load) 74 79 void deck_decoder_unload(ACDeckDecoder *d); 80 + 81 + // Generate peaks for the loaded file (call after deck_decoder_load). 82 + // Decodes the entire file once and writes max-amplitude peaks per chunk. 83 + // Safe to call from main thread; takes a few hundred ms for typical tracks. 84 + int deck_decoder_generate_peaks(ACDeckDecoder *d, int target_count); 75 85 76 86 // Destroy decoder and free all resources 77 87 void deck_decoder_destroy(ACDeckDecoder *d);
+2
fedac/native/src/audio.c
··· 1741 1741 int ret = deck_decoder_load(dk->decoder, path); 1742 1742 if (ret == 0) { 1743 1743 dk->active = 1; 1744 + // Generate waveform peaks for visualization (decoded in background thread) 1745 + deck_decoder_generate_peaks(dk->decoder, 1024); 1744 1746 } 1745 1747 return ret; 1746 1748 }
+18
fedac/native/src/js-bindings.c
··· 2136 2136 return JS_UNDEFINED; 2137 2137 } 2138 2138 2139 + // sound.deck.getPeaks(deck) — returns Float32Array of normalized peak amplitudes 2140 + static JSValue js_deck_get_peaks(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 2141 + (void)this_val; 2142 + if (argc < 1 || !current_rt || !current_rt->audio) return JS_NULL; 2143 + int deck; JS_ToInt32(ctx, &deck, argv[0]); 2144 + if (deck < 0 || deck >= AUDIO_MAX_DECKS) return JS_NULL; 2145 + ACDeck *dk = &current_rt->audio->decks[deck]; 2146 + if (!dk->decoder || !dk->decoder->peaks || dk->decoder->peak_count <= 0) return JS_NULL; 2147 + 2148 + // Build a JS array from the peak data 2149 + JSValue arr = JS_NewArray(ctx); 2150 + for (int i = 0; i < dk->decoder->peak_count; i++) { 2151 + JS_SetPropertyUint32(ctx, arr, i, JS_NewFloat64(ctx, dk->decoder->peaks[i])); 2152 + } 2153 + return arr; 2154 + } 2155 + 2139 2156 // sound.deck.setVolume(deck, vol) 2140 2157 static JSValue js_deck_set_volume(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 2141 2158 (void)this_val; ··· 2287 2304 JS_SetPropertyStr(ctx, deck_obj, "setVolume", JS_NewCFunction(ctx, js_deck_set_volume, "setVolume", 2)); 2288 2305 JS_SetPropertyStr(ctx, deck_obj, "setCrossfader", JS_NewCFunction(ctx, js_deck_set_crossfader, "setCrossfader", 1)); 2289 2306 JS_SetPropertyStr(ctx, deck_obj, "setMasterVolume", JS_NewCFunction(ctx, js_deck_set_master_vol, "setMasterVolume", 1)); 2307 + JS_SetPropertyStr(ctx, deck_obj, "getPeaks", JS_NewCFunction(ctx, js_deck_get_peaks, "getPeaks", 1)); 2290 2308 2291 2309 // Deck state (read-only, rebuilt each frame) 2292 2310 JSValue decks_arr = JS_NewArray(ctx);
+6
lith/server.mjs
··· 155 155 "Access-Control-Allow-Headers", 156 156 "Content-Type, Authorization, X-Requested-With", 157 157 ); 158 + res.set( 159 + "Access-Control-Expose-Headers", 160 + "Content-Length, Content-Disposition, X-AC-OS-Requested-Layout, X-AC-OS-Layout, X-AC-OS-Fallback, X-AC-OS-Fallback-Reason, X-Build, X-Patch", 161 + ); 158 162 res.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); 159 163 if (req.method === "OPTIONS") return res.sendStatus(204); 160 164 next(); ··· 768 772 if (ovenRes.headers.get("x-ac-os-layout")) res.set("X-AC-OS-Layout", ovenRes.headers.get("x-ac-os-layout")); 769 773 if (ovenRes.headers.get("x-ac-os-fallback")) res.set("X-AC-OS-Fallback", ovenRes.headers.get("x-ac-os-fallback")); 770 774 if (ovenRes.headers.get("x-ac-os-fallback-reason")) res.set("X-AC-OS-Fallback-Reason", ovenRes.headers.get("x-ac-os-fallback-reason")); 775 + if (ovenRes.headers.get("x-build")) res.set("X-Build", ovenRes.headers.get("x-build")); 776 + if (ovenRes.headers.get("x-patch")) res.set("X-Patch", ovenRes.headers.get("x-patch")); 771 777 res.set("Access-Control-Allow-Origin", "*"); 772 778 const { Readable } = await import("stream"); 773 779 Readable.fromWeb(ovenRes.body).pipe(res);
+16 -1
oven-edge/worker.mjs
··· 18 18 const IDENTITY_BLOCK_SIZE = 32768; 19 19 const CONFIG_MARKER = '{"handle":"","piece":"notepat","sub":"","email":""}'; 20 20 const DEFAULT_CONFIG_PATCH_SIZE = 4096; 21 + const EXPOSED_HEADERS = [ 22 + "Content-Length", 23 + "Content-Disposition", 24 + "X-AC-OS-Requested-Layout", 25 + "X-AC-OS-Layout", 26 + "X-AC-OS-Fallback", 27 + "X-AC-OS-Fallback-Reason", 28 + "X-Build", 29 + "X-Patch", 30 + ].join(", "); 21 31 22 32 function edgeHeaders(request, extra = {}) { 23 33 return { 24 34 "Access-Control-Allow-Origin": "*", 25 35 "Access-Control-Allow-Methods": "GET, POST, OPTIONS", 26 36 "Access-Control-Allow-Headers": "Content-Type, Authorization", 37 + "Access-Control-Expose-Headers": EXPOSED_HEADERS, 27 38 "X-Edge-Pop": request.cf?.colo || "unknown", 28 39 ...extra, 29 40 }; ··· 174 185 175 186 // 2. Get manifest (has patch offsets) 176 187 const manifest = await getManifest(env); 188 + const hasImgManifest = 189 + manifest?.artifactType === "img" && 190 + Number.isFinite(manifest?.imageSize) && 191 + manifest.imageSize > 0; 177 192 const hasIdentity = Number.isFinite(manifest?.identityBlockOffset) && manifest.identityBlockOffset >= 0; 178 193 const configOffsets = Array.isArray(manifest?.configOffsets) ? manifest.configOffsets : []; 179 - if (!manifest || (!hasIdentity && configOffsets.length === 0)) { 194 + if (!manifest || !hasImgManifest || (!hasIdentity && configOffsets.length === 0)) { 180 195 // No manifest or no offsets — fall through to oven origin for legacy patching 181 196 const ovenRes = await fetch(ORIGIN + "/os-image" + url.search, { 182 197 headers: { Authorization: auth },
+63 -28
oven/server.mjs
··· 361 361 res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); 362 362 res.setHeader( 363 363 'Access-Control-Expose-Headers', 364 - 'Content-Length, Content-Disposition, X-AC-OS-Requested-Layout, X-AC-OS-Layout, X-AC-OS-Fallback, X-AC-OS-Fallback-Reason', 364 + 'Content-Length, Content-Disposition, X-AC-OS-Requested-Layout, X-AC-OS-Layout, X-AC-OS-Fallback, X-AC-OS-Fallback-Reason, X-Build, X-Patch', 365 365 ); 366 366 if (req.method === 'OPTIONS') { 367 367 return res.sendStatus(200); ··· 3300 3300 const configJson = JSON.stringify(configObj); 3301 3301 3302 3302 let imgData; 3303 + let fallbackImage = false; 3304 + let fallbackReason = ''; 3305 + const buildKernelFallback = async (reason) => { 3306 + fallbackReason = reason; 3307 + console.warn( 3308 + `[os-image] ${reason}; generating ${variant} EFI image for @${handle}`, 3309 + ); 3310 + imgData = await buildPersonalizedEfiImage({ 3311 + kernelUrl: kernelUrlForVariant(variant), 3312 + configJson, 3313 + }); 3314 + fallbackImage = true; 3315 + }; 3303 3316 try { 3304 3317 const template = await getTemplate(); 3305 3318 imgData = Buffer.from(template); 3306 3319 } catch (err) { 3307 - return res.status(503).json({ error: `Template not available: ${err.message}` }); 3320 + try { 3321 + await buildKernelFallback('template-unavailable'); 3322 + } catch (fallbackErr) { 3323 + return res.status(503).json({ 3324 + error: `Template not available (${err.message}) and fallback image build failed: ${fallbackErr.message}`, 3325 + }); 3326 + } 3308 3327 } 3309 3328 3310 - const identityMarkerBuf = Buffer.from(IDENTITY_MARKER + '\n'); 3311 - let idx = imgData.indexOf(identityMarkerBuf); 3312 3329 let identityPatchCount = 0; 3313 - while (idx !== -1) { 3314 - const block = Buffer.alloc(IDENTITY_BLOCK_SIZE, 0); 3315 - const header = Buffer.from(IDENTITY_MARKER + '\n' + configJson); 3316 - header.copy(block); 3317 - block.copy(imgData, idx); 3318 - identityPatchCount++; 3319 - idx = imgData.indexOf(identityMarkerBuf, idx + IDENTITY_BLOCK_SIZE); 3320 - } 3330 + let configPatchCount = 0; 3331 + 3332 + if (!fallbackImage) { 3333 + const identityMarkerBuf = Buffer.from(IDENTITY_MARKER + '\n'); 3334 + let idx = imgData.indexOf(identityMarkerBuf); 3335 + while (idx !== -1) { 3336 + const block = Buffer.alloc(IDENTITY_BLOCK_SIZE, 0); 3337 + const header = Buffer.from(IDENTITY_MARKER + '\n' + configJson); 3338 + header.copy(block); 3339 + block.copy(imgData, idx); 3340 + identityPatchCount++; 3341 + idx = imgData.indexOf(identityMarkerBuf, idx + IDENTITY_BLOCK_SIZE); 3342 + } 3343 + 3344 + const padded = configJson.length >= CONFIG_PAD_SIZE_LEGACY 3345 + ? configJson.slice(0, CONFIG_PAD_SIZE_LEGACY) 3346 + : configJson + ' '.repeat(CONFIG_PAD_SIZE_LEGACY - configJson.length); 3347 + const configBytes = Buffer.from(padded); 3348 + const legacyMarkerBuf = Buffer.from(CONFIG_MARKER_LEGACY); 3349 + idx = imgData.indexOf(legacyMarkerBuf); 3350 + while (idx !== -1) { 3351 + configBytes.copy(imgData, idx); 3352 + configPatchCount++; 3353 + idx = imgData.indexOf(legacyMarkerBuf, idx + CONFIG_PAD_SIZE_LEGACY); 3354 + } 3321 3355 3322 - const padded = configJson.length >= CONFIG_PAD_SIZE_LEGACY 3323 - ? configJson.slice(0, CONFIG_PAD_SIZE_LEGACY) 3324 - : configJson + ' '.repeat(CONFIG_PAD_SIZE_LEGACY - configJson.length); 3325 - const configBytes = Buffer.from(padded); 3326 - const legacyMarkerBuf = Buffer.from(CONFIG_MARKER_LEGACY); 3327 - let configPatchCount = 0; 3328 - idx = imgData.indexOf(legacyMarkerBuf); 3329 - while (idx !== -1) { 3330 - configBytes.copy(imgData, idx); 3331 - configPatchCount++; 3332 - idx = imgData.indexOf(legacyMarkerBuf, idx + CONFIG_PAD_SIZE_LEGACY); 3356 + if (identityPatchCount === 0 && configPatchCount === 0) { 3357 + try { 3358 + await buildKernelFallback('template-missing-config-placeholder'); 3359 + } catch (err) { 3360 + return res.status(500).json({ 3361 + error: `Template image missing config placeholder and fallback image build failed: ${err.message}`, 3362 + }); 3363 + } 3364 + } 3333 3365 } 3334 3366 3335 - if (identityPatchCount === 0 && configPatchCount === 0) { 3336 - return res.status(500).json({ error: 'Template image missing config placeholder' }); 3367 + if (!fallbackImage) { 3368 + console.log( 3369 + `[os-image] Patched ${identityPatchCount} identity block(s) and ${configPatchCount} config location(s) for @${handle}`, 3370 + ); 3337 3371 } 3338 - console.log( 3339 - `[os-image] Patched ${identityPatchCount} identity block(s) and ${configPatchCount} config location(s) for @${handle}`, 3340 - ); 3341 3372 3342 3373 addServerLog('success', '💿', `OS image for @${handle} (${(imgData.length / 1048576).toFixed(1)}MB)`); 3343 3374 res.setHeader('Content-Type', 'application/octet-stream'); ··· 3346 3377 res.setHeader('Expires', '0'); 3347 3378 res.setHeader('X-AC-OS-Requested-Layout', requestedLayout || 'img'); 3348 3379 res.setHeader('X-AC-OS-Layout', 'img'); 3380 + if (fallbackImage) { 3381 + res.setHeader('X-AC-OS-Fallback', 'kernel-efi-image'); 3382 + res.setHeader('X-AC-OS-Fallback-Reason', fallbackReason); 3383 + } 3349 3384 3350 3385 let releaseName = 'native'; 3351 3386 try {
+8 -4
papers/SCORE.md
··· 40 40 41 41 | Paper | Format | PDF | Source | 42 42 |-------|--------|-----|--------| 43 + | Aesthetic Computer Demo (C&C 2026) | ACM Demo (LaTeX) | `cc-demo-2026/demo.pdf` | `cc-demo-2026/demo.tex` | 43 44 | The URL Tradition | arXiv (LaTeX) | `arxiv-url-tradition/url-tradition.pdf` | `arxiv-url-tradition/url-tradition.tex` | 44 45 | The Potter and the Prompt | arXiv (LaTeX) | `arxiv-holden/holden.pdf` | `arxiv-holden/holden.tex` | 45 46 | Two Departments, One Building | arXiv (LaTeX) | `arxiv-ucla-arts/ucla-arts.pdf` | `arxiv-ucla-arts/ucla-arts.tex` | ··· 114 115 115 116 | Venue | Type | Deadline | Conference Date | Status | 116 117 |-------|------|----------|-----------------|--------| 117 - | [ACM C&C 2026](https://cc.acm.org/2026/) | Demos | Apr 16, 2026 | Jul 13–16, London | GO | 118 - | [ICCC 2026](https://computationalcreativity.net/iccc26/) | Short Papers | Apr 24, 2026 | Jun 29–Jul 3, Coimbra | GO | 118 + | [ACM C&C 2026](https://cc.acm.org/2026/demos/) | Demos | Apr 16, 2026 | Jul 13–16, London | DRAFTING (`cc-demo-2026/`) | 119 + | [ICCC 2026](https://computationalcreativity.net/iccc26/) | Short Papers | May 15, 2026 | Jun 29–Jul 3, Coimbra | GO | 120 + | [SIGGRAPH Asia 2026](https://asia.siggraph.org/2026/submissions/) | Art Gallery | Jun 18, 2026 | Dec 1–4, Kuala Lumpur | NEW | 121 + | [SIGGRAPH Asia 2026](https://asia.siggraph.org/2026/submissions/posters/) | Posters | Jul 31, 2026 | Dec 1–4, Kuala Lumpur | NEW | 122 + | [SIGGRAPH Asia 2026](https://asia.siggraph.org/2026/submissions/real-time-live/) | Real-Time Live! | Aug 7, 2026 | Dec 1–4, Kuala Lumpur | NEW | 123 + | [ArtsIT 2026](https://artsit.eai-conferences.org/2026/) | Full Papers | Jun 1, 2026 | Dec 2–4, Bratislava | NEW | 119 124 | [JOSS](https://joss.theoj.org/) | Software Paper | Rolling | Rolling | Anytime | 120 - | [Scores for Social Software](https://socialsoftware.art/) | Card Deck | Mar 31, 2026 | Apr 2, UCLA | GO | 121 125 122 126 ### Deadlines Passed (Track for Next Year) 123 127 124 - NIME, xCoAx, ACM CHI, SIGGRAPH, Prix Ars Electronica, S+T+ARTS — all missed for 2026. Track 2027 calls. 128 + NIME (Feb 12), xCoAx (Feb 15), ACM CHI, SIGGRAPH main, Prix Ars Electronica (Mar 9), S+T+ARTS, ISEA (Nov 2025), Creative Capital (Apr 2), Scores for Social Software (Mar 31) — all missed for 2026. Track 2027 calls. 125 129 126 130 ## Funding Sources 127 131
papers/cc-demo-2026/demo.pdf

This is a binary file and will not be displayed.

+219
papers/cc-demo-2026/demo.tex
··· 1 + % !TEX program = xelatex 2 + \documentclass[manuscript,anonymous,review]{acmart} 3 + 4 + % Remove ACM-specific metadata for submission 5 + \settopmatter{printacmref=false} 6 + \renewcommand\footnotetextcopyrightpermission[1]{} 7 + \pagestyle{plain} 8 + 9 + \usepackage{booktabs} 10 + \usepackage{listings} 11 + 12 + % === Code listing styles === 13 + \definecolor{kw}{RGB}{119,51,170} 14 + \definecolor{fn}{RGB}{0,136,170} 15 + \definecolor{str}{RGB}{170,120,0} 16 + \definecolor{num}{RGB}{204,0,102} 17 + \definecolor{cmt}{RGB}{102,102,102} 18 + 19 + \lstdefinelanguage{acjs}{ 20 + morekeywords=[1]{function,export,const,let,return,if,else}, 21 + morekeywords=[2]{wipe,ink,line,box,circle,write,screen,event}, 22 + sensitive=true, 23 + morecomment=[l]{//}, 24 + morestring=[b]", 25 + morestring=[b]', 26 + } 27 + 28 + \lstset{ 29 + language=acjs, 30 + basicstyle=\ttfamily\small, 31 + keywordstyle=[1]\color{kw}\bfseries, 32 + keywordstyle=[2]\color{fn}\bfseries, 33 + commentstyle=\color{cmt}\itshape, 34 + stringstyle=\color{str}, 35 + breaklines=true, 36 + frame=single, 37 + rulecolor=\color{gray!30}, 38 + backgroundcolor=\color{gray!5}, 39 + xleftmargin=0.5em, 40 + xrightmargin=0.5em, 41 + aboveskip=0.5em, 42 + belowskip=0.5em, 43 + } 44 + 45 + \begin{document} 46 + 47 + \title{Aesthetic Computer: A Prompt-Based Runtime for Creative Computing} 48 + \subtitle{Demo at ACM Creativity \& Cognition 2026} 49 + 50 + \author{Anonymous} 51 + \authornote{Submission anonymized for review.} 52 + \affiliation{\institution{Anonymous Institution}} 53 + 54 + \begin{abstract} 55 + We demonstrate Aesthetic Computer, a mobile-first runtime and social network for creative computing that replaces the conventional editor interface with a text prompt. Users type short, memorizable commands to launch interactive programs called \emph{pieces}---drawing tools, sound instruments, games, generative artworks, and social spaces---running entirely in the browser on any device. The demo invites attendees to interact with the system on their own phones: play a musical keyboard, draw collaboratively, write generative art in a minimal Lisp dialect, and experience how prompt-based navigation produces fluency through memorization rather than menu traversal. The system comprises 63,000 lines of open-source runtime code, 354 built-in pieces, 265 user-published works, and 2,800+ registered users. 56 + \end{abstract} 57 + 58 + \keywords{creative computing, creative coding, prompt interface, mobile-first, social computing, live coding, generative art} 59 + 60 + \maketitle 61 + 62 + % ============================================================ 63 + \section{Introduction} 64 + 65 + Creative coding platforms---Processing, p5.js, Scratch, OpenProcessing---share a common assumption: the user interacts with an \emph{editor}. Aesthetic Computer (AC) starts from a different premise. The primary interface is a text prompt into which users type short names to launch interactive programs. There is no file browser, no project panel, no menu bar. The experience is closer to a musical instrument than a development environment: users discover commands through exploration, build muscle memory through repetition, and improvise by recombining pieces fluently. 66 + 67 + This demo lets conference attendees experience that interaction model directly. Visitors will navigate the prompt on their own devices, discover pieces by typing guesses, play instruments, draw, write code, and share their work---all within minutes, with no installation and no prior knowledge. 68 + 69 + % ============================================================ 70 + \section{System Overview} 71 + 72 + AC is a browser-native platform comprising four subsystems: 73 + 74 + \begin{enumerate} 75 + \item \textbf{Boot loader} --- initializes WebSocket connections, service workers, and offline storage in parallel. Subsequent visits load from cache for near-instant startup. 76 + \item \textbf{BIOS} --- the runtime orchestrator (20,935 lines). Manages the 60fps rendering loop, routes input events (keyboard, touch, mouse, gamepad, MIDI), and coordinates piece lifecycle transitions. 77 + \item \textbf{Disk API} --- provides the complete API surface for pieces (15,879 lines). All graphics are immediate-mode: every pixel is drawn every frame, with no retained scene graph. 78 + \item \textbf{Module loader} --- streams JavaScript modules over WebSocket for fast loading, with IndexedDB caching and HTTP fallback. 79 + \end{enumerate} 80 + 81 + The system runs entirely client-side with no installation. Backend services handle authentication, storage, and real-time communication, but the creative runtime is the browser itself. 82 + 83 + % ============================================================ 84 + \section{The Piece Model} 85 + 86 + A piece is a single \texttt{.mjs} (JavaScript) or \texttt{.lisp} (KidLisp) file that exports lifecycle functions: 87 + 88 + \begin{lstlisting} 89 + function boot({ wipe, screen }) { 90 + // Runs once when piece loads 91 + } 92 + 93 + function paint({ wipe, ink, circle, screen }) { 94 + wipe("navy") 95 + ink("pink") 96 + circle(screen.width / 2, 97 + screen.height / 2, 50) 98 + } 99 + 100 + function act({ event: e }) { 101 + if (e.is("touch")) { /* respond */ } 102 + } 103 + 104 + export { boot, paint, act }; 105 + \end{lstlisting} 106 + 107 + The API provides graphics primitives (\texttt{wipe}, \texttt{ink}, \texttt{line}, \texttt{box}, \texttt{circle}, \texttt{poly}), text rendering, audio synthesis, input handling, networking, and UI components---all passed as destructured parameters to each lifecycle function. This follows Processing's sketchbook model---one file per explorable idea---combined with URL-addressability: every piece, parameter combination, and user's work is a shareable URL. 108 + 109 + The system includes 354 built-in pieces and 265 user-published works spanning drawing tools, musical instruments, generative artworks, multiplayer games, and social utilities. 110 + 111 + % ============================================================ 112 + \section{The Prompt as Instrument} 113 + 114 + The prompt is AC's primary navigation interface, implementing over 60 commands. Instead of browsing menus, users type: 115 + 116 + \begin{itemize} 117 + \item \texttt{notepat} --- a musical keyboard with synthesis and sequencing 118 + \item \texttt{wand} --- a freehand drawing tool 119 + \item \texttt{line} --- geometric drawing 120 + \item \texttt{chat} --- live text chat 121 + \item \texttt{mood} --- post a short status update 122 + \item \texttt{@user/piece} --- load someone else's published work 123 + \end{itemize} 124 + 125 + Returning to the prompt is always one gesture away. Users report discovering pieces by typing guesses---\texttt{piano}, \texttt{draw}, \texttt{rain}---and finding what exists. This accidental discovery is a deliberate design outcome of a flat namespace. Over time, users build a personal repertoire of memorized commands, and their navigation becomes improvisational---rapid, fluent transitions between creative contexts without loading screens or navigation hierarchies. 126 + 127 + The metaphor is literal: like learning an instrument, the initial experience is exploration and discovery; the mature experience is fluency and flow. 128 + 129 + % ============================================================ 130 + \section{KidLisp: A Language for Beginners and AI} 131 + 132 + KidLisp is a minimal Lisp dialect designed for generative art, with 118 built-in functions across 12 categories (drawing, color, transformation, math, animation, audio). A complete KidLisp program that draws concentric circles: 133 + 134 + \begin{lstlisting} 135 + (repeat i 12 136 + (ink (color (* i 20) 100 200)) 137 + (circle:filled (/ width 2) 138 + (/ height 2) 139 + (* i 20))) 140 + \end{lstlisting} 141 + 142 + The language has no file I/O, networking, or string manipulation, which makes user-submitted code safe to execute. Over 16,000 KidLisp programs have been written---many by children and beginners collaborating with language models. The constrained vocabulary means AI-generated code is readable and editable by novices: a human can change a number, see the result, and build intuition for what the code does. 143 + 144 + KidLisp programs are URL-addressable: short codes like \texttt{\$cow} resolve to stored programs and execute immediately. 145 + 146 + % ============================================================ 147 + \section{Social Infrastructure} 148 + 149 + Unlike platforms where social features are external services, AC integrates social infrastructure directly into the runtime: 150 + 151 + \begin{itemize} 152 + \item \textbf{Handles} --- users register names through the prompt (\texttt{@handle}), which serve as namespaces for published work. 153 + \item \textbf{Real-time chat} --- WebSocket-based messaging, with 18,000+ messages exchanged. 154 + \item \textbf{Moods} --- short text posts visible on profiles, dual-written to ATProto for federation with the broader social web. 155 + \item \textbf{Live profiles} --- real-time scorecards showing online status, current piece, and creative activity. 156 + \item \textbf{Multiplayer} --- collaborative drawing, music-making, and shared sessions via WebSocket + UDP channels. 157 + \end{itemize} 158 + 159 + 2,800+ users are registered. The social layer drives retention: users check moods, respond in chat, and discover pieces through social activity rather than search. 160 + 161 + % ============================================================ 162 + \section{Distribution: The Pack System} 163 + 164 + The \texttt{pack} command bundles any piece into a single, self-contained HTML file. The output includes the piece code, all runtime dependencies, and embedded fonts---a file that works offline with no server. This enables distribution via email, USB drives, QR codes, or blockchain minting. A packed piece is a permanent artifact that survives platform shutdown. 165 + 166 + % ============================================================ 167 + \section{Demo Experience} 168 + 169 + The demo station provides: 170 + 171 + \begin{enumerate} 172 + \item \textbf{A large display} running AC at full screen, showing a rotating selection of pieces via the merry sequencing system. 173 + \item \textbf{Attendees' own devices.} Visitors navigate to the URL on their phones and interact immediately---no app install, no account required. A printed card lists starting commands. 174 + \item \textbf{Guided activities:} 175 + \begin{itemize} 176 + \item \emph{Play:} Type \texttt{notepat} and perform a melody on the touchscreen keyboard. 177 + \item \emph{Draw:} Type \texttt{wand} or \texttt{line} and create a drawing. 178 + \item \emph{Code:} Enter a KidLisp short code (\texttt{\$cow}) and modify a generative artwork live. 179 + \item \emph{Share:} Type \texttt{chat} and send a message visible to all demo participants. 180 + \item \emph{Pack:} Bundle a piece into an offline HTML file and take it home. 181 + \end{itemize} 182 + \item \textbf{Collaborative moment.} Multiple attendees on the same piece simultaneously, drawing or making music together in real time. 183 + \end{enumerate} 184 + 185 + The demo requires only a browser and an internet connection. No accounts, downloads, or plugins are needed to participate. 186 + 187 + % ============================================================ 188 + \section{Relevance to Creativity for Change} 189 + 190 + AC's design proposes that creative software can be organized around memorization and improvisation rather than navigation and configuration. This produces a qualitatively different relationship between people and computation---one where the barriers to entry are curiosity and a URL, not software literacy and a desktop computer. 191 + 192 + The system is open source (ISC license), runs on any device with a browser, and packs creative work into permanent, self-contained files. KidLisp's role as a bridge between human beginners and AI collaborators demonstrates one model for how creative coding might evolve: not by making tools more complex, but by making languages simple enough that both humans and machines can read them. 193 + 194 + % ============================================================ 195 + 196 + \bibliographystyle{ACM-Reference-Format} 197 + \begin{thebibliography}{9} 198 + 199 + \bibitem{reas2007} 200 + Casey Reas and Ben Fry. 2007. \textit{Processing: A Programming Handbook for Visual Designers and Artists}. MIT Press. 201 + 202 + \bibitem{mccarthy2015} 203 + Lauren McCarthy. 2015. p5.js. \url{https://p5js.org} 204 + 205 + \bibitem{resnick2009} 206 + Mitchel Resnick et al. 2009. Scratch: Programming for All. \textit{Communications of the ACM} 52, 11 (2009), 60--67. 207 + 208 + \bibitem{hydra2019} 209 + Olivia Jack. 2019. Hydra: Live Coding Networked Visuals. In \textit{Proceedings of the International Conference on Live Coding (ICLC)}. 210 + 211 + \bibitem{roos2023} 212 + Felix Roos and Alex McLean. 2023. Strudel: Live Coding Patterns on the Web. In \textit{Proceedings of the International Conference on New Interfaces for Musical Expression (NIME)}. 213 + 214 + \bibitem{nelson1974} 215 + Theodor H. Nelson. 1974. \textit{Computer Lib / Dream Machines}. Self-published. 216 + 217 + \end{thebibliography} 218 + 219 + \end{document}
papers/cc-demo-2026/tech-requirements.pdf

This is a binary file and will not be displayed.

+67
papers/cc-demo-2026/tech-requirements.tex
··· 1 + % !TEX program = xelatex 2 + \documentclass[manuscript,anonymous,review]{acmart} 3 + 4 + \settopmatter{printacmref=false} 5 + \renewcommand\footnotetextcopyrightpermission[1]{} 6 + \pagestyle{plain} 7 + 8 + \begin{document} 9 + 10 + \title{Technical Demonstration Requirements} 11 + \subtitle{Aesthetic Computer --- ACM C\&C 2026 Demo} 12 + 13 + \author{Anonymous} 14 + \affiliation{\institution{Anonymous Institution}} 15 + 16 + \maketitle 17 + 18 + \section{Space and Setup} 19 + 20 + \begin{itemize} 21 + \item \textbf{Table space:} Standard demo table (approximately 180cm $\times$ 75cm). 22 + \item \textbf{Display:} One external monitor (24" or larger), provided by the authors if not available. HDMI input preferred. 23 + \item \textbf{Laptop:} Authors will bring a laptop running Chrome to drive the main display. 24 + \item \textbf{Audio:} Small powered speaker or headphones for the demo station. Musical pieces (notepat, tone) produce audio that is part of the demonstration. 25 + \item \textbf{Power:} Two power outlets (laptop + monitor or speaker). 26 + \end{itemize} 27 + 28 + \section{Network Requirements} 29 + 30 + \begin{itemize} 31 + \item \textbf{Internet connection:} Required. The system loads from a public URL. Wi-Fi is sufficient. 32 + \item \textbf{Bandwidth:} Minimal after initial load ($<$1 MB cached). Real-time features (chat, multiplayer) use WebSocket connections with low bandwidth overhead. 33 + \item \textbf{Ports:} Standard HTTPS (443) and WSS (443). No special firewall rules needed. 34 + \item \textbf{Fallback:} If internet is unreliable, the demo can run from a local development server on the presenter's laptop. Attendees would connect via a local Wi-Fi hotspot. 35 + \end{itemize} 36 + 37 + \section{Attendee Participation} 38 + 39 + \begin{itemize} 40 + \item Attendees use \textbf{their own phones, tablets, or laptops}---no dedicated hardware required. 41 + \item The system works in any modern browser (Chrome, Safari, Firefox, Edge). No app installation or account creation is needed. 42 + \item A printed reference card will list starting commands and a QR code for the URL. 43 + \item Participation is drop-in: attendees can join or leave at any time during the demo session. 44 + \end{itemize} 45 + 46 + \section{Accessibility} 47 + 48 + \begin{itemize} 49 + \item The system accepts keyboard, touch, mouse, and gamepad input. 50 + \item Text rendering uses high-contrast colors on solid backgrounds. 51 + \item The prompt interface is compatible with screen readers (standard HTML input element). 52 + \item Audio is not required for the visual/drawing portions of the demo. 53 + \end{itemize} 54 + 55 + \section{Setup and Teardown} 56 + 57 + \begin{itemize} 58 + \item \textbf{Setup time:} 10 minutes (open laptop, connect display, verify internet). 59 + \item \textbf{Teardown time:} 5 minutes. 60 + \item \textbf{No special installation:} The entire system runs in a web browser. No software needs to be installed on venue machines. 61 + \end{itemize} 62 + 63 + \section{Presenter Attendance} 64 + 65 + One author will be present throughout the demo session to guide attendees, explain the system, and facilitate collaborative moments (multi-user drawing and music sessions). 66 + 67 + \end{document}
+14
system/netlify/edge-functions/os-image.js
··· 3 3 // This edge function just forwards the auth header and streams the response. 4 4 5 5 const OVEN_BASE = "https://oven.aesthetic.computer/os-image"; 6 + const EXPOSED_HEADERS = [ 7 + "Content-Length", 8 + "Content-Disposition", 9 + "X-AC-OS-Requested-Layout", 10 + "X-AC-OS-Layout", 11 + "X-AC-OS-Fallback", 12 + "X-AC-OS-Fallback-Reason", 13 + "X-Build", 14 + "X-Patch", 15 + ].join(", "); 6 16 7 17 export default async (req) => { 8 18 if (req.method === "OPTIONS") { ··· 11 21 "Access-Control-Allow-Origin": "*", 12 22 "Access-Control-Allow-Headers": "Authorization", 13 23 "Access-Control-Allow-Methods": "GET, OPTIONS", 24 + "Access-Control-Expose-Headers": EXPOSED_HEADERS, 14 25 }, 15 26 }); 16 27 } ··· 47 58 "X-AC-OS-Fallback": ovenRes.headers.get("x-ac-os-fallback") || "", 48 59 "X-AC-OS-Fallback-Reason": 49 60 ovenRes.headers.get("x-ac-os-fallback-reason") || "", 61 + "X-Build": ovenRes.headers.get("x-build") || "", 62 + "X-Patch": ovenRes.headers.get("x-patch") || "", 50 63 "Access-Control-Allow-Origin": "*", 64 + "Access-Control-Expose-Headers": EXPOSED_HEADERS, 51 65 }, 52 66 }); 53 67 } catch (err) {