-
Notifications
You must be signed in to change notification settings - Fork 0
Migrate from Processing to p5.js #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
ae24ce8
a01eed0
bdf36c3
dfeced1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>soothing chantal</title> | ||
| <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.3/p5.min.js"></script> | ||
| <style> | ||
| html, body { | ||
| margin: 0; | ||
| padding: 0; | ||
| overflow: hidden; | ||
| background: #000; | ||
| } | ||
| canvas { | ||
| display: block; | ||
| } | ||
| #start-overlay { | ||
| position: fixed; | ||
| top: 0; | ||
| left: 0; | ||
| width: 100%; | ||
| height: 100%; | ||
| background: rgba(0, 0, 0, 0.85); | ||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| z-index: 10; | ||
| cursor: pointer; | ||
| } | ||
| #start-overlay span { | ||
| color: #fff; | ||
| font-family: monospace; | ||
| font-size: 18px; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div id="start-overlay"> | ||
| <span>click to start — soothing chantal</span> | ||
| </div> | ||
| <script src="sketch.js"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| /** | ||
| * "soothing chantal" | ||
| * a generative lullaby | ||
| * by leandro estrella | ||
| * cargocollective.com/leandroestrella | ||
| * | ||
| * Migrated from Processing to p5.js | ||
| */ | ||
|
|
||
| const MAX_MOVIES = 6; | ||
| const VIDEO_SRC = "data/lala.oggtheora.ogv"; | ||
|
|
||
| let isPlaying = true; | ||
| let rand; | ||
| let oldX = 0; | ||
| let oldY = 0; | ||
|
|
||
| let firstClipVideo; | ||
| let myVideos = []; | ||
| let videosPlaying = []; | ||
| let started = false; | ||
| let lastFrameTime = -1; | ||
|
|
||
| function preload() { | ||
| // Videos are created in setup after user interaction | ||
| } | ||
|
|
||
| function setup() { | ||
| createCanvas(1100, 800); | ||
| background(0); | ||
| textFont("monospace"); | ||
|
|
||
| rand = floor(random(MAX_MOVIES)); | ||
|
|
||
| firstClipVideo = createVideo(VIDEO_SRC); | ||
| firstClipVideo.hide(); // hide the default HTML element | ||
| firstClipVideo.volume(0.5); | ||
|
|
||
| for (let i = 0; i < MAX_MOVIES; i++) { | ||
| myVideos[i] = createVideo(VIDEO_SRC); | ||
| myVideos[i].hide(); | ||
| myVideos[i].volume(0); | ||
| } | ||
|
|
||
| videosPlaying.push(firstClipVideo); | ||
|
|
||
| // Handle click-to-start (required by browser autoplay policies) | ||
| let overlay = document.getElementById("start-overlay"); | ||
| if (overlay) { | ||
| overlay.addEventListener("click", function () { | ||
| startPlayback(); | ||
| overlay.style.display = "none"; | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| function startPlayback() { | ||
| if (started) return; | ||
| started = true; | ||
| firstClipVideo.loop(); | ||
| } | ||
|
|
||
| function draw() { | ||
| if (!started) { | ||
| background(0); | ||
| return; | ||
| } | ||
|
|
||
| // Detect new frame availability (mirrors Processing's Movie.available()) | ||
| let currentTime = firstClipVideo.time(); | ||
| let frameAvailable = currentTime !== lastFrameTime && firstClipVideo.width > 0; | ||
|
|
||
| if (frameAvailable) { | ||
| lastFrameTime = currentTime; | ||
| let vidWidth = firstClipVideo.width; | ||
| let vidHeight = firstClipVideo.height; | ||
| tint(255, 50); | ||
| image(firstClipVideo, 0, 0, width, height); | ||
|
|
||
| // Search for the brightest pixel | ||
| firstClipVideo.loadPixels(); | ||
| if (firstClipVideo.pixels.length > 0) { | ||
| let brightestX = 0; | ||
| let brightestY = 0; | ||
| let brightestValue = 0; | ||
|
|
||
| for (let y = 0; y < vidHeight; y++) { | ||
| for (let x = 0; x < vidWidth; x++) { | ||
| let index = (y * vidWidth + x) * 4; | ||
| let r = firstClipVideo.pixels[index]; | ||
| let g = firstClipVideo.pixels[index + 1]; | ||
| let b = firstClipVideo.pixels[index + 2]; | ||
| let pixelBrightness = (r + g + b) / 3; | ||
|
|
||
| if (pixelBrightness > brightestValue) { | ||
| brightestValue = pixelBrightness; | ||
| brightestY = y; | ||
| brightestX = x; | ||
| } | ||
| } | ||
| } | ||
| oldX = brightestX; | ||
| oldY = brightestY; | ||
| } | ||
| } else if (oldX <= 200) { | ||
| rand = floor(random(MAX_MOVIES)); | ||
| videosPlaying.push(myVideos[rand]); | ||
| videosPlaying[videosPlaying.length - 1].loop(); | ||
| } | ||
|
Comment on lines
+109
to
+113
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Unbounded The Impact at low playback speedAt speed 0.0625x (the minimum from Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| // Draw all playing videos | ||
| for (let i = 0; i < videosPlaying.length; i++) { | ||
| let m = videosPlaying[i]; | ||
| if (m.width > 0 && m.height > 0) { | ||
| tint(255, 50); | ||
| image(m, 0, 0, width, height); | ||
| } | ||
| } | ||
|
|
||
| // Main video speed (clamped to valid HTML5 range: 0.0625 to 16) | ||
| let newSpeed = map(oldX, 0, width, 0.0625, 2); | ||
| try { | ||
| firstClipVideo.speed(newSpeed); | ||
| } catch (e) { | ||
| // Ignore speed errors from browser restrictions | ||
| } | ||
|
|
||
| // Main video volume (normalized to 0-1) | ||
| let newVolume = constrain(map(oldX + oldY, 0, width, 0, 1), 0, 1); | ||
| firstClipVideo.volume(newVolume); | ||
|
|
||
| // Second+ video speed | ||
| let newSpeedB = map(oldY, 0, height, 0.0625, 2); | ||
| try { | ||
| myVideos[rand].speed(newSpeedB); | ||
| } catch (e) { | ||
| // Ignore speed errors from browser restrictions | ||
| } | ||
|
|
||
| // Visualizers (HUD overlay) | ||
| noTint(); | ||
| textAlign(CENTER, TOP); | ||
| fill(255); | ||
| textSize(10); | ||
| text(nfc(newSpeed, 2) + " S", 30, 20); | ||
| text(nfc(oldY, 2) + " Y", 570, 20); | ||
| text(nfc(newSpeedB, 2) + " S", 1070, 20); | ||
| text(nfc(frameRate(), 2) + " F", 30, 785); | ||
| text(nfc(oldX, 2) + " X", 570, 785); | ||
| text(nfc(newVolume, 2) + " V", 1070, 785); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Frame availability detection using
time()comparison never triggers the else-if branch during playbackThe
currentTime !== lastFrameTimecheck on line 75 does not replicate Processing'sMovie.available()semantics. HTML5 video'scurrentTime(exposed by p5's.time()) advances continuously on the media timeline — it changes on virtually every animation frame during playback, even between decoded visual frames. This meansframeAvailableis alwaystrueduring normal playback, and theelse if (oldX <= 200)branch at line 109 effectively never fires.In the original Processing code (
soothingChantal.pde:54),available()returnstrueonly when the decoder delivers a new visual frame (~24-30fps), making itfalseon roughly half of draw() calls at 60fps. The else-if branch firing regularly is what creates the generative additive video-layering effect — the core artistic feature of the piece. With the current p5.js implementation, this layering never occurs during playback.Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.