link to my project:
https://calistalu.github.io/ProjectB/
link to my code:
https://github.com/calistalu/ProjectB
- Part 1 for public readers
Calista Lu,
JOURNEY, 2023
an immersive experience of rhythmic visuals
- The Elevator Pitch.
My project is committed to exploring a journey of self-discovery, taking the form of interactive audio visualization. The user is invited to immerse themselves in a non-narrative journey, experiencing an attractive blend of rhythmic visual elements. - Abstract.
My project B is an abstract expression of the concept that everyone’s life experiences define their unique self, and invites users to think, engage, release and refresh, reflecting on their own journey of self-exploration. In this enchanting journey of audio-visual exploration, each step echoes with the heartbeat, and every scenery unfolds a symphony with the soul. It’s as if users are penning theirs unique existence on this vibrant canvas, striding forward like that solitary yet resolute silhouette. Hopefully users could be emotionally touched, purified and healed at the journey’s end.ready to start your journey - Part 2 for yourself (and your instructor)
1) Process: Design and Composition
After deciding to use audio visualization to narrate my story, I drew inspiration for my visual elements from this video:
I attempted to create a 3D music visualizer using WEBGL mode. However, I encountered a major challenge—the 3D canvas defaulted to a white background that covered everything. I turned to Professor Eric for help, who suggested me to use ‘CreateGraphics’ in p5, but that is only a way to combine P2D and WEBGL without resolving the overlapping issue. Despite trying various solutions, I ultimately abandoned the incorporation of my 3D music visualizer into the project.The website now consists of two webpages. The first serves as the initial interface and provides background information for users, while the second is the main music visualizer.
3D music visualizer 2) Process: Technical
I acquired advanced web development skills, including CSS animation and JS event listeners, from W3Schools with my intention of designing a visually appealing first webpage. Details on this aspect have been provided in my mini project7 documentation.
One of the significant technical challenges was showcasing the growth process of a person in a way that cannot be reversed, because simply creating several buttons to choose the character is not what I really want, and ultimately, I used a Boolean for each stage to control the whole process.
if (currentImage === "child") { if (start) { count1++; if (count1 < 150) { image(w1, map(count1, 0, 150, 0, width / 4), height - 100, 90, 120) stroke(255, 150) strokeWeight(10) line(0, height - 90, map(count1, 0, 150, 0, width / 4), height - 90) } else { count1 = 201 stroke(255, 150) strokeWeight(10) image(w1, width / 4, height - 100, 90, 120) line(0, height - 90, width / 4, height - 90) } image(child, width / 2, height / 2 - 100 - 150, 150, 200); } textAlign(CENTER, CENTER); textSize(20); fill(255); strokeWeight(1) text(displayText, width / 2, height - 50); } else if (currentImage === "teen") { count2++; if (count2 < 150) { image(w2, map(count2, 0, 150, width / 4, width / 2), height - 100, 90, 120) stroke(255, 150) strokeWeight(10) line(0, height - 90, map(count2, 0, 150, width / 4, width / 2), height - 90) } else { count2 = 201 stroke(255, 150) strokeWeight(10) image(w2, width / 2, height - 100, 90, 120) line(0, height - 90, width / 2, height - 90) } image(teen, width / 2, height / 2 - 100 - 150, 150, 200); } else if (currentImage === "adult") { count3++; if (count3 < 150) { image(w3, map(count3, 0, 150, width / 2, 3 * width / 4), height - 100, 90, 120) stroke(255, 150) strokeWeight(10) line(0, height - 90, map(count3, 0, 150, width / 2, 3 * width / 4), height - 90) } else { count3 = 201 stroke(255, 150) strokeWeight(10) image(w3, 3 * width / 4, height - 100, 90, 120) line(0, height - 90, 3 * width / 4, height - 90) } image(adult, width / 2, height / 2 - 100 - 150, 150, 200); } else if (currentImage === "old") { count4++; if (count4 < 150) { image(w4, map(count4, 0, 150, 3 * width / 4, width), height - 100, 90, 120) stroke(255, 150) strokeWeight(10) line(0, height - 90, map(count4, 0, 150, 3 * width / 4, width), height - 90) } else { count4 = 201 stroke(255, 150) strokeWeight(10) image(w4, width, height - 100, 90, 120) line(0, height - 90, width, height - 90) } image(old, width / 2, height / 2 - 100 - 150, 150, 200); } else if (currentImage === "end") { push() translate(width / 2, height / 2) image(end, 0, 0, width, height) pop() }
This part of code is still too long and I said I would shorten it in my presentation. So here is its optimized version:
//four stages + end function displayImage(imageIndex, imageRef, transitionCount, xPos, xEnd, y) { transitionCount++; if (transitionCount < 150) { const x = map(transitionCount, 0, 150, xPos, xEnd); image(imageRef, x, y - 100, 90, 120); stroke(255, 150); strokeWeight(10); line(0, y - 90, x, y - 90); } else { transitionCount = 201; stroke(255, 150); strokeWeight(10); image(imageRef, xEnd, y - 100, 90, 120); line(0, y - 90, xEnd, y - 90); } return transitionCount; } // Display images based on current stage imageMode(CENTER); if (currentImage === "child") { if (start) { count1 = displayImage("child", w1, count1, 0, width / 4, height); image(child, width / 2, height / 2 - 100 - 150, 150, 200); } textAlign(CENTER, CENTER); textSize(20); fill(255); strokeWeight(1) text(displayText, width / 2, height - 50); } else if (currentImage === "teen") { count2 = displayImage("teen", w2, count2, width / 4, width / 2, height); image(teen, width / 2, height / 2 - 100 - 150, 150, 200); } else if (currentImage === "adult") { count3 = displayImage("adult", w3, count3, width / 2, 3 * width / 4, height); image(adult, width / 2, height / 2 - 100 - 150, 150, 200); } else if (currentImage === "old") { count4 = displayImage("old", w4, count4, 3 * width / 4, width, height); image(old, width / 2, height / 2 - 100 - 150, 150, 200); } else if (currentImage === "end") { push(); translate(width / 2, height / 2); image(end, 0, 0, width, height); pop(); }
Another technical challenge involved making star particles sparkle more accurately in response to the song. The code triggers a blinking star when the amplitude level increases. I’ve experienced several versions and this one worked best.
//particles turn into star let Amp = amplitude.getLevel(); if (Amp - previousAmp > 0.05) { increasing = true } else { increasing = false } previousAmp = Amp; if (increasing) { randomIndex1 = floor(random(particles.length)); randomIndex2 = floor(random(particles.length)); randomIndex3 = floor(random(particles.length)); } if (particles[randomIndex1]) { push() translate(width / 2, height / 2 + 100) translate(particles[randomIndex1].x, particles[randomIndex1].y) rotate(frameCount) image(star, 0, 0, 30, 30); pop() } if (particles[randomIndex2]) { push() translate(width / 2, height / 2 + 100) translate(particles[randomIndex2].x, particles[randomIndex2].y) rotate(frameCount) image(star, 0, 0, 40, 40); pop() } if (particles[randomIndex3]) { push() translate(width / 2, height / 2 + 100) translate(particles[randomIndex3].x, particles[randomIndex3].y) rotate(frameCount) image(star, 0, 0, 50, 50); pop() }
3) Reflection and Future Development
From the project proposal to its current version, many compromises were made due to time and technical limitations. While the CSS design of the first webpage was successful, I am less satisfied with how I represented my narrative in the second webpage. The initial intention was for it to be healing and inspiring.
Feedback from the audience suggested adding more differences in different life stages, a point I had also considered. Guest professors highlighted the importance of consistency between the two webpages in terms of visual style, including the design of circles and the choice of font.
What’s more, once moved to another device with a different screen ratio, some elements of my web page would be overlapping each other, which is a direction for future improvement.
4) Credits & References
https://www.bilibili.com/video/BV11N411w7Ys/?share_source=copy_web&vd_source=f652fb5afdc7d621b2d6acee94aa961c
[visually inspiration]
https://www.bilibili.com/video/BV1pr4y127Yz/?share_source=copy_web&vd_source=f652fb5afdc7d621b2d6acee94aa961c
[concept inspiration; image credit]
https://www.youtube.com/watch?v=akM4wMZIBWg&t=193s
[ Technical help: how to use the fft object to get waveform data and store them in an array; how to draw the wave into a circular shape by looping through the waveform data.]