Monorepo for Aesthetic.Computer aesthetic.computer
at main 221 lines 8.7 kB view raw view rendered
1# Cross-Platform Sample Storage Plan 2 3## Key Insight: Samples ARE Paintings 4 5AC already has `pixel-sample.mjs` which encodes audio samples as RGB pixel data 6in painting bitmaps. `stample.mjs` uses this today. Paintings have full 7infrastructure: upload, CDN, short codes (`#k3d`), @handle ownership, track-media 8API, and cross-platform rendering. 9 10**Samples use the same `#` sigil and PNG storage format as paintings, but live in 11a separate `samples` MongoDB collection with audio-specific metadata.** 12 13--- 14 15## Architecture Decision: Shared Sigil, Separate Collection 16 17### Why `#` (not a new sigil like `^`) 18- Samples and paintings share the same PNG storage format 19- A painting can BE a sample (load any image as audio via stample) 20- A sample IS a painting (the pixel-encoded waveform is visible art) 21- `%` and `&` are URL-unfriendly (`%` is URL escape, `&` is query separator) 22- Keeps the sigil set small: `@` people, `$` code, `#` media, `*` time 23 24### Why separate collection (not a flag on paintings) 25- Clean querying: "list my samples" vs "list my paintings" without filters 26- Separate indexes optimized for audio metadata (duration, sampleRate, etc.) 27- Separate counts/quotas per media type 28- Future-proof: audio-specific features (waveform preview, BPM detection) 29 30### Shared code namespace 31- `#` codes must be unique across BOTH `paintings` AND `samples` collections 32- `generateUniqueCode()` checks both collections before assigning 33- Other sigils (`$` KidLisp, `*` clock, tapes) have independent namespaces 34- The code resolver checks both collections to find which type a `#code` refers to 35 36--- 37 38## MongoDB Schema 39 40### Collection: `samples` 41```js 42{ 43 _id: ObjectId, 44 user: "auth0|63effeeb...", // owner (null for guest) 45 slug: "kick-drum", // user-friendly name 46 code: "k3d", // unique short code (shared with paintings) 47 when: ISODate, // upload timestamp 48 49 // Sample-specific metadata 50 v: 1, // pixel-sample encoding version 51 sampleRate: 48000, 52 sampleLength: 240000, // exact sample count 53 duration: 5.0, // seconds 54 channels: 1, 55 source: "native", // or "web" 56 57 // Standard media fields 58 ext: "png", // storage format 59 width: 256, // image dimensions 60 height: 313, 61} 62``` 63 64### Indexes 65```js 66// Unique code (shared namespace with paintings — enforced at generation time) 67await samples.createIndex({ code: 1 }, { unique: true, sparse: true }); 68 69// User queries: "list my samples" 70await samples.createIndex({ user: 1 }); 71 72// Audio-specific queries 73await samples.createIndex({ duration: 1 }); // sort by length 74await samples.createIndex({ "source": 1 }); // native vs web 75await samples.createIndex({ v: 1 }); // encoding version (for migration) 76await samples.createIndex({ when: -1 }); // recent first 77await samples.createIndex({ user: 1, slug: 1 }, { unique: true }); // per-user slugs 78``` 79 80### Encoding Version Contract 81``` 82v1 (current — pixel-sample.mjs): 83 - 3 audio samples per pixel (R, G, B channels) 84 - Float range -1.0..+1.0 mapped to 0..255 85 - A channel: 255 (opaque) 86 - Width: 256px (configurable) 87 - Height: ceil(sampleLength / 3 / width) 88 - Mono only 89 - The `v` field MUST be stored on every record so decoders know which algorithm to use 90``` 91 92--- 93 94## Implementation Plan 95 96### Phase 1: Backend — track-media + code generation 97**Files to modify:** 98 991. **`system/netlify/functions/track-media.mjs`** 100 - Add `mediaType: "sample"` branch for PNG uploads with sample metadata 101 - Route to `samples` collection instead of `paintings` 102 - Store `v`, `sampleRate`, `sampleLength`, `duration`, `channels`, `source` 103 1042. **`system/backend/generate-short-code.mjs`** 105 - `generateUniqueCode()` accepts optional `siblingCollections` array 106 - For `#` codes: checks both `paintings` AND `samples` before assigning 107 - Other types unchanged (single collection check) 108 1093. **`system/netlify/functions/painting-code.mjs`** (or equivalent resolver) 110 - When resolving `#code`, check `paintings` first, then `samples` 111 - Return `{ type: "painting" | "sample", ...record }` so the client knows 112 1134. **New: `system/netlify/functions/list-samples.mjs`** (or extend existing) 114 - `GET /api/samples/@handle` → list user's samples 115 - `GET /api/samples/@handle/:slug` → get specific sample 116 - Returns CDN URLs + metadata 117 118### Phase 2: Native pixel-sample bridge 119**Files to create/modify:** 120 1211. **`fedac/native/pieces/lib/pixel-sample-native.mjs`** 122 - Pure-JS encode/decode (no DOM, no Canvas — works in QuickJS) 123 - Same algorithm as web `pixel-sample.mjs` 124 - `encodeSampleToBitmap(float32Array, width)` → RGBA pixel array 125 - `decodeBitmapToSample(rgbaArray, width, height, sampleLength)` → float32[] 126 1272. **PNG write from native** 128 - Option A: Add `stb_image_write.h` (single-header, ~1KB) for PNG encoding in C 129 - Option B: Minimal PNG writer in JS (deflate + PNG header — ~100 lines) 130 - Option C: BMP format (simpler, no compression, server converts to PNG on upload) 131 132### Phase 3: Upload from native 133**Flow:** 134``` 135Record audio → float32[] → encodeSampleToBitmap → PNG bytes 136 → POST /api/track-media { ext:"png", mediaType:"sample", sampleMeta:{v:1,...} } 137 → GET presigned URL → PUT PNG to Spaces → #code returned 138``` 139 140**Files:** 141- `fedac/native/pieces/samples.mjs` — add upload key (`u`) 142- `fedac/native/src/js-bindings.c``system.uploadMedia()` binding if needed 143- Or use existing `system.fetch()` + `system.fetchPost()` for the API calls 144 145### Phase 4: Download to native 146**Flow:** 147``` 148Type #code → resolve via /api/painting-code → get CDN URL 149 → fetchBinary(url, /tmp/sample.png) → decode PNG → decodeBitmapToSample 150 → sound.sample.loadData(float32, rate) → play 151``` 152 153**Requires:** PNG decoding in native 154- Option A: `stb_image.h` (single-header PNG decoder) 155- Option B: Decode in JS (pure-JS PNG inflate) 156- Option C: Server endpoint that returns raw PCM (avoid client-side PNG decode) 157 158### Phase 5: Unified experience 159- **notepat.mjs**: type `#code` to load a sample from cloud into sample bank 160- **samples.mjs**: browse/record/upload/download on web and native 161- **stample.mjs**: already works, becomes the web sample player 162- **Gallery**: shows speaker icon on `#` codes that are samples 163- **Profile**: separate "samples" tab alongside "paintings" 164 165--- 166 167## Data Flow 168 169``` 170Native Record Web Record 171 | | 172 float32[] float32[] 173 | | 174 encodeSampleToBitmap encodeSampleToBitmap 175 | | 176 PNG bytes PNG bytes (via Canvas) 177 | | 178 POST /api/track-media POST /api/track-media 179 { mediaType:"sample" } { mediaType:"sample" } 180 | | 181 +----→ DO Spaces ←------+ 182 + MongoDB "samples" 183 + short code (#abc) 184 | 185 +------------+------------+ 186 | | 187 download PNG download PNG 188 | | 189 decodeBitmapToSample decodeBitmapToSample 190 | | 191 float32[] float32[] 192 | | 193 audio playback audio playback 194``` 195 196## Storage Math 197 198| Duration | Samples @48kHz | Pixels (3/px) | Image (256w) | PNG size | 199|----------|---------------|---------------|--------------|----------| 200| 1 sec | 48,000 | 16,000 | 256×63 | ~20 KB | 201| 5 sec | 240,000 | 80,000 | 256×313 | ~100 KB | 202| 10 sec | 480,000 | 160,000 | 256×625 | ~200 KB | 203 204## Key Files 205 206| Existing | Purpose | 207|----------|---------| 208| `system/public/aesthetic.computer/lib/pixel-sample.mjs` | Encode/decode samples↔bitmaps | 209| `system/public/aesthetic.computer/disks/stample.mjs` | Web sample-painting player | 210| `system/netlify/functions/track-media.mjs` | Media upload API | 211| `system/netlify/functions/painting-code.mjs` | Short code → slug resolver | 212| `system/backend/generate-short-code.mjs` | Unique code generation | 213| `system/netlify/functions/presigned-url.js` | CDN upload/download URLs | 214 215| New/Modified | Purpose | 216|-------------|---------| 217| `fedac/native/pieces/lib/pixel-sample-native.mjs` | Pure-JS encode/decode for QuickJS | 218| `fedac/native/pieces/samples.mjs` | Native sample browser + upload/download | 219| `system/public/aesthetic.computer/disks/samples.mjs` | Web sample browser piece | 220| `system/netlify/functions/track-media.mjs` | Add sample branch | 221| `system/backend/generate-short-code.mjs` | Cross-collection check for # codes |