Week 4 – Ghost of Words

Intro

This week, I adopted my production in week 2 – the dots silhouette according to the webcam input – to create a representation of the figure reflected in the webcam as well as the soul behind it in the background. It seems to me that the product in week 2 did lack some message/meaning – or whatsoever: What is the point of minoring the webcam with dots? Then, when it comes to the text generation of this week, the answer appeared to me to be the combination of text and my existent mechanism – as the mirroring mechanism symbolizes the entity, the phantom, the ghost, and the creator as well as the user, while the poems floating across the canvas reflects a piece of my soul. By doing so, the ghost of me (or you) becomes the exact pathway to discovering that piece of soul, adding incentives to the interaction.

process

I started simply by replacing the dots drawing in my week 2 product with text drawing – and obviously, the flashing of words can not stand for further meanings except for blinding the user even more compared to the dots, as we intuitively tend to read whatever the words presented in front of us.

Therefore, I tried another approach to displaying the poem’s lines anyway in the background and let the probability pixel matrix act as the alpha value matrix, this time to overlay on the text, thus resulting in the ghosty effect.

In the preload function, I’m ensuring that all external resources are loaded before the sketch runs –  Using loadTable, I import lines of text from textLines.csv, which will be used to generate the floating texts dynamically.

function preload() {
  // Load the CSV file
  textLines = loadTable('textLines.csv', 'csv', 'header'); // Adjust the path and options as needed
}

This time, I directly use the grayscale value as alpha value as they have the same range:

function drawAlphaFilter() {
  noStroke();
  
  // Iterate through each cell in the grid
  for (let y = 0; y < k; y++) {
    for (let x = 0; x < j; x++) {
      let index = x + y * j;
      let grayValue = pixelArray[index];
      
      // Calculate alpha value
      // Ensure alphaValue is within 0-250 for better visibility
      let alphaValue = constrain(grayValue, 0, 250); 
      
      // Set fill color to background color with the calculated alpha for overlay effect
      fill(17, 38, 56, alphaValue);
      
      // Calculate the position and size of each rectangle
      let rectWidth = windowWidth / j;
      let rectHeight = windowHeight / k;
      let rectX = x * rectWidth;
      let rectY = y * rectHeight;
      
      rect(rectX, rectY, rectWidth, rectHeight);
    }
  }
}

The RGB value used in this product is extracted from my personal website: Sloth’s Slumber | Xiaotian Fan’s Collection (sloth-slumber.com).

Then, the floating texts are managed through both class and helper functions, including:

function updateFloatingTexts() {
  // Update and display existing floating texts
  for (let i = floatingTexts.length - 1; i >= 0; i--) {
    let ft = floatingTexts[i];
    ft.update();
    ft.display();
    
    // Remove if off-screen
    if (ft.isOffScreen()) {
      floatingTexts.splice(i, 1);
      
      // Also remove from its slot
      let s = ft.slot;
      slots[s] = null; // Mark the slot as free
    }
  }
  
  // Iterate through each slot to manage floating texts
  for (let s = 0; s < totalSlots; s++) {
    if (slots[s] === null) {
      // If the slot is free, add a new floating text
      let newText = getNextText();
      if (newText) {
        let ft = new FloatingText(newText, s);
        floatingTexts.push(ft);
        slots[s] = ft; // Assign the floating text to the slot
      }
    } else {
      // If the slot is occupied, check if the tail has entered the screen
      let lastText = slots[s];
      
      if (lastText.direction === 'ltr') { // Left-to-Right
        // Check if the tail has entered the screen (x + width >= 0)
        if (lastText.x + lastText.getTextWidth() >= 0) {
          // Safe to add a new floating text
          let newText = getNextText();
          if (newText) {
            let ft = new FloatingText(newText, s);
            floatingTexts.push(ft);
            slots[s] = ft; // Replace the old floating text with the new one
          }
        }
      } else { // Right-to-Left
        // Check if the tail has entered the screen (x - width <= windowWidth)
        if (lastText.x - lastText.getTextWidth() <= windowWidth) {
          // Safe to add a new floating text
          let newText = getNextText();
          if (newText) {
            let ft = new FloatingText(newText, s);
            floatingTexts.push(ft);
            slots[s] = ft; // Replace the old floating text with the new one
          }
        }
      }
    }
  }
}

Another important function is to concatenate lines in order to fulfill across the windowWidth:

function getNextText() {
  // Reset index if end is reached
  if (currentLineIndex >= textLines.getRowCount()) {
    currentLineIndex = 0; // Reset to start
  }
  
  let combinedText = '';
  let estimatedWidth = 0;
  let tempIndex = currentLineIndex;
  let concatenationAttempts = 0;
  let maxAttempts = textLines.getRowCount(); // Prevent infinite loops
  
  // Loop to concatenate lines until the combined text is sufficiently long
  while (estimatedWidth < windowWidth * TEXT_MULTIPLIER && concatenationAttempts < maxAttempts) {
    let textLine = textLines.getString(tempIndex, 0);
    if (!textLine) break; // If no more lines available
    
    combinedText += (combinedText.length > 0 ? ' ' : '') + textLine;
    tempIndex++;
    
    // Reset if at the end of the table
    if (tempIndex >= textLines.getRowCount()) {
      tempIndex = 0;
    }
    
    // Estimate text width using p5.js's textWidth
    textSize(24); // Set a default size for estimation
    estimatedWidth = textWidth(combinedText);
    
    concatenationAttempts++;
    
    // Break if the same index is on loop to prevent infinite concatenation
    if (tempIndex === currentLineIndex) break;
  }
  
  // Update the currentLineIndex to tempIndex
  currentLineIndex = tempIndex;
  
  return combinedText;
}

Eventually, this time, when dealing with the full window canvas, I added a canvas resize function to respond to window resizing:

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  
  // Update Y positions of floating texts based on new window size
  for (let ft of floatingTexts) {
    let padding = 5; // Padding from top and bottom
    ft.y = map(ft.slot, 0, totalSlots - 1, padding, windowHeight - padding);
  }
}

To do & reflection

While this product is germinated from my previous product, I believe it has the potential to be further polished, including the varying text aesthetics, responsive relation between text and webcam (or audio level), etc.

On the other hand, I would say that this product is indeed an improvement compared to week 2 as I started to incorporate my own message and character into the code instead of creating fancy (or not) demos.

Assignment 4 – The Lyrical Video

Concept

For this project, I wanted to explore something simple yet engaging with text. My initial idea involved allowing user input, where text would fall to the ground. While that was a good starting point, I felt it needed more interactivity. Then, inspiration struck while I was listening to music: why not create a lyric video? And that’s how this project took shape – a lyric video with the text fading in and out, synchronized to the music playing in the background.

 

Code I’m Particularly Proud Of

In this simple project, the code I’m most proud of is the part that handles the fade-in and fade-out effect of the text. Normally, this would require a loop, but since the draw() function in p5.js acts as a natural loop, I managed it using a simple if statement combined with a counter that gradually changes the opacity of the text until it fully fades out. Here’s the core code snippet:

// Display the current line with a fade-in effect
  fill(255, fadeValue);
  text(lyrics[currentLine], width / 2, lineY); // Display the current line of lyrics at the center of the canvas

  // Gradually make the text appear by decreasing its opacity
  fadeValue -= 1;

  // When the text is fully faded, move to the next line
  if (fadeValue <= 0) 
  {
    currentLine = (currentLine + 1) % lyrics.length; // Move to the next line, looping back to the start if at the end
    
    currentColor = (currentColor + 1) % colors.length; // Change to the next background color, looping through the array
    
    fadeValue = 255; // Reset the fade value to fully opaque
  }

 

Final Product

The final product is available to experience, and you can interact with it by pressing the mouse button to move the lyrics forward. Otherwise, you can simply watch it as a lyric video with music in the background. Just a heads-up: the video includes audio, so be mindful before playing it.

 

 

Final Thoughts and Reflection

Working on this project was both intriguing and challenging. It was a lesson in embracing simplicity, as my initial ideas were quite ambitious. However, I realized that there’s a unique power in crafting something straightforward yet effective. While I’m pleased with the outcome, there are a few areas for improvement: I would like to synchronize the lyrics with the music more precisely, enhance the background visuals, and add more interactive elements to make it more engaging. This project has sparked new ideas, and I look forward to applying these insights to something even bigger and better. Stay tuned!

Week 4: Reading response

One thing that drives me crazy in design and has not been directly discussed in the reading  is the struggle to create works with meaningful intentions or justifications. For me a good design, besides its aesthetics, interactivity and the good user experience is what it carries. I am usually interested in the reasons why the creator of an art piece wanted to make it in the first place. Even further beyond art  As I think about my future works, I often question myself on what challenges I want to tackle or what messages I want my designs and works  to convey as I believe that impactful arts and works should emerge from genuine reasons. 

Many principles from the reading are directly relevant to interactive media, as the field is rooted in design. Concepts such as discoverability, understanding, affordances, and signifiers as discussed in the reading can be directly applied to create more engaging experiences while using interactive designs. One thing I hope to take into serious consideration for my future works is to develop human-centered designs. Inspired by the reading I aim to create interactive artworks that first and foremost have a clear and easy to understand purpose. I can achieve this by using simple design principles that can be directly implied by the user or using simple and clear instructions if need be. Second, I also intend to take into consideration the significance of feedback as the reading reminded me of scenarios when even feedback given subconsciously makes a great difference. I hope that with artworks that provide feedback users may want to continue exploring to find more interaction thus enrich their experiences with the designs. 

Week 4 – Generative Text

Concept

In this project, I created an interactive tool that lets users see how emotions can be expressed through emojis. Inspired by my brother’s misuse of emojis, each emoji represents a specific feeling, and by clicking on them, users can see a contextual message that describes when to use that emoji. 

Implementation

The implementation of this interactive tool uses p5.js to create an engaging experience for users. It starts with an array of emoji objects that include their symbols, emotions, and positions on the canvas. The `setup()` function initializes the canvas size and text settings. In the `draw()` function, emojis are displayed along with a gradient background that transitions from light blue to dark slate blue for visual appeal. When users click on an emoji, the `mousePressed()` function checks if the click is near an emoji, then shows a message above it explaining its meaning. Overall, this simple structure effectively helps users understand emoji meanings in a fun and interactive way.

Highlight

One aspect of the code that I’m particularly proud of is the text generation feature that dynamically displays contextual messages when users click on an emoji. By using simple logic in the `mousePressed()` function, the code checks the position of the mouse relative to each emoji and identifies the selected one. This triggers a specific message that appears above the emoji, explaining its meaning and when to use it. 

This dynamic text generation not only enhances interactivity but also provides an educational element, helping users understand the emotional context behind each emoji. I appreciate how this feature brings the project to life, making it more engaging and informative. The clarity of the messages ensures that users leave with a better understanding of emojis, which is the core goal of this tool. This is how i implemented it.

function mousePressed() {
  // Check if an emoji is clicked
  for (let emoji of emojis) {
    let d = dist(mouseX, mouseY, emoji.x, emoji.y);
    if (d < 30) {  // Check if the mouse is close enough to the emoji
      selectedEmoji = emoji;  // Set the selected emoji
      break;
    }
  }
}

When a user clicks near an emoji, the code checks the distance between the mouse pointer and the emoji’s position. If the distance is small enough, it sets that emoji as the selected emoji, allowing the following code in the draw() function to display the corresponding message:

if (selectedEmoji != null) {
  textSize(24);  // Smaller text for the message
  fill(0);
  text(`Use this emoji when you are ${selectedEmoji.emotion}.`, selectedEmoji.x, selectedEmoji.y - 50);  // Display above the emoji
}

Embedded Sketch

 

Future Reflections and Ideas for Future Work

Looking ahead, I plan to enhance this project by adding more emojis, including diverse options, and allowing users to submit their own. Additionally, I would like to incorporate a quiz feature to make learning about emojis more fun and engaging. These improvements will help create a more comprehensive tool for understanding emojis and their meanings.

Week 4 – A web of words

Concept

For this week’s assignment, I knew right off the bat that I wanted to do something with poetry. Having felt inspired by Camille Utterback’s Text Rain piece, I thought that an unexpected poem concept would be interesting and originally wanted to use a database of google searches to generate poetry. However, after not finding any databases that struck my interest I decided to take a break and come back to the assignment later. It was then that I started writing a poem inspired by the feeling of being homesick that I ended up having my idea. In the poem, I kind of jumped around a bit as I dissected how my homesickness portrayed itself in my life and ended up getting the idea of a word web. From there I decided that I wanted lines from my poem to connect to other random lines from poems I’d gathered on the internet.

Components 

Because I was somewhat combining the database option with the generative text output I decided I wanted to keep the design of both aspects to be relatively simple so as to keep my expectations fairly realistic. However, even what I thought would be simple to execute, took a lot of trial-and-error / re-starting for simplicity. Generating the typewriter like output is pretty simple in the code but took a lot of planning and replanning on my part as I kept getting stuck with the line breaks, and how the words were being stored.

Once I got that rolling I wanted to focus on how I would incorporate the database of poetry. I ended up creating a few different methods so that I could randomly generate information in my poem, and then have it located in the provided poems. Then, just for an aesthetic component I imported a font so that each line of poetry had a bit more uniqueness to it and added visual aspect. You can also click the mouse to generate more words with different excerpt of the poems which I think further instils the idea of how everything is connected in some megaweb (even in ways we could never imagine)

An Aspect I Am Proud Of 

I think in writing this program I put more of my ‘programming skills’ to the test than I have in the past. I needed to spend a lot of time on the design process because after hitting a few dead ends I realized the importance my algorithm played into the functionality of my code. Although this is not the part I struggled the most with, I’d like to highlight my functions to generate the random words and phrases seen below.

function randomWord() {
  let randLine = lines[int(random(0,23))]; //choose a line from my poem
  let words = split(randLine, ' '); //make each word
  //an element in an array
  let chosenWord = words[int(random(0, words.length))];
  //choosing a random word from the random line 
  wordInData(chosenWord); //find it in the data set
}
function wordInData(chosenWord) {
  let attempts = 0; //preventing max stack call error
  while (attempts < 1000) {
    let randomDataLine = dataPoems[int(random(0,dataPoems.length))];
    let words = split(randomDataLine, ' ');
    //doing the same thing from my poem
    if (words.includes(chosenWord)) { //if random word is found
      magazineLine = randomDataLine;
      return; //stops the function from continue 
    }
    attempts++;
  }
  // If no match found after maxAttempts, choose a random line
  // = random(dataPoems);
  randomWord();
}

I chose to highlight this code because it took me quite a long time to figure out how to parse the data from both text files to make them into arrays I could work with (although in the end it ended up being quite simply I just didn’t understand the structure of loading files).

Final Product (double click for the random poetry!)


(Also I know from this week’s reading that we should have to explicitly say what to do for it to be good design and user-friendly but Rome wasn’t built in a day!!!)

Reflection

This excerpt however, also brings me to what I’d like to improve about the program. I’m still not really sure if this is an issue with the data set of my program or just poor design on my part but I was running into a maximum stack error when attempting to find words in the data set of poems. Because the program would iterate over 1,000 times trying to find a word, it would end up crashing would also created a lot of stress and wasted time.

Therefore, in the future I’d like to find the root of the issue and redesign the program so that it is solved. Although I am proud of what I designed because of how far I came in terms of hardcoding my version, I know that this algorithm is far from ideal and definitely want to tweak it. Furthermore, I’d love to make it more interactive with maybe the user choosing a word to search for instead of the program generating it for them but just hadn’t gotten that far conceptually with this version.

Week 4 : Data Visualisation

Concept

This week I decided to implement a simple data visualisation. The simplest form of data visualisation techniques that came in mind were graphs. Out of many ways to visualise data I decided to implement four basic ones, Bar graph, Pie chart, scatterplot and Line graph. I was motivated to make my design interactive to the user, so I wanted to allow manipulation of  the default data from user input.

Implementation 

I managed to implement my design by creating four classes one for each of the four data visualisation method I chose. In each class, I defined data and categories attributes and implemented a display function that uses the data stored to decide how the graph is drawn. I also implemented a function outside all classes to decide what graph is plotted depending on the user input. I also added an input window, where user can add data and see them in the visualised.

Sketch

Below is my final sketch:

Piece of Code I am proud of

I am particularly proud with the design of the pie chart as it was hard to write labels and colour each pie section. Initially I used random colour generation but the colour appeared blinking so I decided to add a colour attribute so each section could have its colour. Below is the pie chart class definition :

class PieChart 
{
  constructor(data, categories) 
  {
    this.data = data;
    this.categories = categories;
    this.total = 0;
// Get sum of all data 
    for (let i = 0; i < data.length; i++) 
    {
      this.total += data[i]
    }
    this.colors = [];
    for (let i = 0; i < this.data.length; i++) 
    {
// Generate a unique color for each data section
      this.colors.push(color(random(255)%200, random(255), random(255)));
    }
  }
  
  display() 
  {
    let angleStart = 0;
    let radius = min(rectW, rectH) * 0.4; 
    let centerX = rectX + rectW / 2;
    let centerY = rectY + rectH / 2;

    for (let i = 0; i < this.data.length; i++) 
    {
      let angleEnd = angleStart + (this.data[i] / this.total) * TWO_PI;
      fill(this.colors[i]);
      arc(centerX, centerY, radius * 2, radius * 2, angleStart, angleEnd, PIE);
      
// Get positions for Categories lables 
      let midAngle = angleStart + (angleEnd - angleStart) / 2;
      let labelX = centerX + cos(midAngle) * (radius * 0.5); 
      let labelY = centerY + sin(midAngle) * (radius * 0.5);

      fill(0);  
      textAlign(CENTER, CENTER); 
      text(this.categories[i], labelX, labelY); 
    
      fill(0);  
      textAlign(CENTER, CENTER);  
      text(this.categories[i], labelX, labelY);
      angleStart = angleEnd;
    }
     
  }
}

Reflection and Improvements for future work

For future work, I would prefer adding more options with multiple kinds of data such as multi-categories data. For example data with temperatures of two cities. A visualisations techniques that allows for visual comparison between the two set of data.

Week 3 – Reading Reflection of Chris Crowford’s “The Art of Interactive Design”

Hi everyone! 👋

Chris Crowford’s “The Art of Interactive Design”, was certainly an interesting read. He takes a rather more strict approach to interactivity, perhaps due to the blatant misuse of the word on things undeserving to be called interactive, which in turn potentially makes it more useful, especially as the meaning is easier to grasp. By distilling it into 3 clear parts (listening, thinking, and speaking), it makes it easier for designers to assess where their piece could potentially perform better.

Although it seems a bit controversial, to be honest, I quite agree with his definition, but I can easily see how others might not. Many common forms of interactive media are dismissed by him, but he backs it with a point I liked, a distinction between “interactive” and “intense reaction”, which can so often be blurred.

Another point I really liked was “No Trading Off”. Previously, I would’ve assumed that if 2 parts of his interactive design’s defintion were done really well (while it wouldn’t be the same as all 3 being done well), it would come pretty close. However, he claims this is not the case, and it is a logical thing to believe.

Ultimately, I feel like his definition is most relevant to us in helping us better plan the components of our design, ensuring that it “listens”, “thinks”, and “speaks” well. This is something I could really use, as I often spend too much in the “thinking” and “speaking” part, designing something technically incredible, but ultimately, not as interactive.

 

One off-topic point, I like his style of writing, as it’s less formal / more personal, which makes it more enjoyable to read (particularly things like his explanation on books & films not being interactive, or the questions).

Week 3: OOP – Polyrhythmic Spiral

# Jump To:


# Introduction & Inspiration

Hey everyone, welcome back! 👋

Earlier in class, the professor showed us the Bloom app (by Brian Eno & Peter Chilvers), which I found really calming & elegant, with its simple and easy to understand function and interface. While thinking about it again at home that day, I remembered a few things I saw a while ago, that mainly related to the music that was coming out. One of them was ProjectJDM‘s videos (most notably his famous rainbow pendulum wave) and CodeCraftedPhysics‘s videos (such as this one). So, for this week’s project, I’d thought it been interesting to try and make something similar to the rainbow pendulum wave, also called, a polyrhymtic spiral (polyrhythm, due to the sound structure, and spiral, due to, it well, making spirals).

 

# Implementation

To start off, I began by just making some points along a line (plain and simple, without any classes for now).

v1

 
translate(width/2, height/2)

circle(0, 0, 10) // Center circle, filled (just for reference)
let n = 7 // Number of circles
for (let i = 0; i < n; i++) {
  circle(width/15 + i*4*width/(10*n), 0, 10) // Outer dots, outlined
}
 

I’m just drawing a few circles in a straight line in a loop. The spacing is a bit interesting, as width/15 is the offset from the center, and then the formula i*4*width/(10*n) increments the spacing for each index, mainly by dividing the width by the number of dots (I wrote it as a much simpler but slightly longer equation before, but now I can’t undo it 😅. Besides, I ended up redoing spacing anyways).

The logic for the spacing is as follows. We want points along a line, that have a margin from the center and edges of the screen. So we take the width and subtract a margin * 2 from it. Then we divide up the remaining space by the number of points we want to draw.

Then I changed it to using polar coordinates instead. You should be familiar with polar coordinates and some circle stuff by now (you did read my blog last week right 🤨? It explains some of this), which will come in handy as some of the logic is similar to last week’s work. I position the small circles along larger circles (rings) with differing radii, and then we can move them around in a circle, similar to how we drew the line last week. So now the drawing code is

 
translate(width/2, height/2)
background(0)

fill(255)
noStroke()
circle(0, 0, 10) // Draw the center circle (filled), just for reference

noFill();
stroke(255)

let n = 7 // Number of rings and dots
let angle = TWO_PI // Angle to draw dots at
let r = 0
for (let i = 0; i < n; i++) {
  r = width/15 + i*4*width/(10*n) // Radius of each ring
  arc(0, 0, 2*r, 2*r, PI, TWO_PI); // Draw the ring
  circle(cos(angle) * r, sin(angle) * r, 10) // Draw the outer dots (outlined)
}
 

And we get:

V2

And we got a pretty close structure already!

I then played around with the spacing a bit, and ended up using a different equation altogether (same concept and method as original, but I didn’t condense it).

V2 of work

V3

Now, I changed the arcs to be nearly full circles instead (wasn’t sure yet, so you’ll see a lot of screenshots with a gap in the right side, that was intentional to help me note something). Also, now that I had some of the basics of the project down, I switched to using Object Oriented Programming, and made a class for the rings and another for the dots, as well as added a simple line to the center of the screen. I could then animate the dots across the screen by changing their angle. Now, instead of assigning them all the same angle (which would result in a straight line of dots traveling along a circle), I made each dot have a different velocity, and then incremented their angle by that velocity, giving us staggered dots!

V4
The first hints of a spiral (and changed the spacing again)
V5
Changed the spacing yet again.

The cool thing about the velocity, is that I calculated in such a way that I could control exactly when all the dots came back together again (which is just incredibly cool to see and hear).

Now, I wanted to highlight the ring whose dot has touched the line. I first just tried checking whether the dot’s y position was zero, but that didn’t really work, because a dot could easily skip past it based on it’s velocity and starting angle (and in fact, often did). This was a similar issue with using the angle instead. I then gave a bit of margin (so instead of checking whether angle == 2 Π, I checked whether the difference was less than a certain amount. This seemed to work! (ring highlighted in red, for now, and oh, I also added a trail effect, by doing background(0, 8), a trick I love using).

V6

But unfortunately, not really… It spammed a lot.

Too many collisions
Wayy too many collisions for a few dots…

I could try adjusting the tolerance/margin, but I knew that was a losing battle (as the lower it is, the fewer false triggers I’ll receive, but also the fewer actual triggers, due to missing it, and vice versa).

So then, I came up with a new method. I would calculate the dot’s position before, now, and after one iteration, and check to see when it was closest to the line. If it was closest to the line now, and was further away before, and would be further away later, then this meant we could trigger the collision. It looked something like this:

 
class Dot {

  // ...

  hasCollided() {
    let differenceBefore = abs(sin(this.angle - this.velocity));
    let differenceNow = abs(sin(this.angle));
    let differenceAhead = abs(sin(this.angle + this.velocity));
    
    // check if the dot is closest to the line
    if (differenceNow < differenceBefore && differenceNow < differenceAhead) { 
      return true;
    } else {
      return false;
    }
 

Aaand, it worked!

After that, I added colors to the rings. I wanted a solution that would dynamically scale with the number of dots, and so what I did was divided up hue (360 degrees) by the number of dots, and used that for the HSL colour (so, similar to the dot’s spacing, but for colour) (also, gosh, isn’t HSL so much better?). I added this line to the loop let ringColour = color(i * 360 / numRings, 100, 80) (and oh, changed the spacing to let radius = gap + (width/2 - 2*gap) * i/(numRings-1) 😅, which is much more understandable).

V5
Yay, rainbow colours! (though there’s still the red highlight 🤦‍♂️)

Then I changed the highlight to the ring’s colour, and tried something out. I increased the ring amount to something much higher (I think 21), and got this cool result.

V8
Oooooo

Refining it a bit more (changing the background call’s opacity), and we get an even more beautiful result! (though it’s hard to tell when the dots collide, as it’s now just a continuous line)

V9
Ooooooooooo pretty!

 

 

## Sound & Additional Thoughts

Then, I wanted to add some sound (you know, like the MAIN reason I even wanted to make this?). This was a challenge (and took a good chunk of time). Not the actual code for the playing the sound, no that was relatively simple, as I just had to create a new PolySynth (since I wanted to play MIDI notes instead of manual hard coded files, to also scale with the number of rings) with polySynth = new p5.PolySynth(), and then played a sound every time a collision happened (with the sound being based on the ring’s index). No no, the hard part was choosing what sounds to play.

I noticed that p5’s PolySynth accepted MIDI names and notes, but whenever I tried using the numbers, it didn’t really sound as it was supposed to (according to the translation tables). So I created a new rough sketch and explored in there. So basically, MIDI notes first have a character (one A, A#, B, C, C#, D, D#, E, F, F#, G, G#) (though keep it mind it usually starts from C, I don’t why p5 choose to start from A, at least from what I heard), and then a number from 0-8. I eventually created a converter that takes a number and outputs a MIDI note (with the slight help of an AI, though just because I was too tired at this time), and, it worked! Or, worked? Or, well, idk.

Ok, so it worked as intended, but it didn’t really sound nice at all, and nothing like the original video (though to be fair, he is a proper skilled artist, but still). Changing around some of the note assignment did help (I then started the notes from A4 (since I realised that you can’t even hear the first few notes, and some sound pretty terrible, so I restricted the range), and went to every 2nd or 3th note instead), but it still didn’t really sound like how I wanted to. I also tried playing with reverb, etc, but I’m probably missing a key component (yea, who could’ve guessed that a good sounding composition probably has more than just playing some notes in ascending order 😅). I could also just upload some files manually and use those (eg. from Hyperplexed’s CodePen (he also made a great video on it), which would make it sound nice, but that wouldn’t scale with the number of rings, and also feels a bit like cheating (idk why I enjoy making things harder for myself 🤷‍♂️). Well, at least it looks nice… sometimes.

Unfortunately, that about wraps up all the time I had, so we’re gonna have to end it here. As usual, there’s a lot I couldn’t add (like being to drag a slider to control the rings, and click on them to add dots, which was like, the entire reason for decoupling the dots from the rings (making separate classes, etc), so all that work was for nothing…), and even more I couldn’t write on here (especially about the sound), but that’s it for today, and I’ll catchup you next time! (right? You will read my next posts too right? please? 🥺😅)

Anyways, then, after much ado (😂), I present, my Polyrythmic spiral!

 

# Final Result

Controls:

  • Umm… nothing. Just listen to the peaceful and serene music… (just kidding, its doesn’t sound very nice yet 🙁 ).
  • You could however, adjust the number of rings and the gap between them, by opening the sketch and changing the parameters (found at the top).
  • Technically, you could also change anything in the sketch, if you get into the code (you could even turn it into a something random, like a giraffe. I don’t know why you would, but you could).

 

 

Week 2: Reflection of Casey Reas’ “Chance Operations”

Hey everyone! 👋

At Eyeo2012, Casey Reas gave a pretty interesting talk, titled”Chance Operations”, where he went somewhat deep into the history of computer graphics, and how randomness was used, sometimes for effect, othertimes as a signal or act of rebellion (against the modern, clean/perfectionist work).

It inspired me to think deeper about how I currently do and potentially could use randomness in my work (ok, fine, also since it’s the writing prompt 😅). I think I would primarily use randomness, in order to create something that can be continually enjoyed. We humans get easily bored, and so don’t really enjoy watching the exact same thing over and over again, since it becomes too predictable and boring (idk about Bob though, he binge watches the same episodes again and again…). So, by adding a certain amount of randomness, we can consistently get a unique and interesting result, which is also incredibly helpful for animation and repeated viewings/interactions.

Minecraft landscape
Minecraft, probably the most iconic poster child of random terrain generation.
(Note: Shaders and Distant Horizons were used, taken from youtu.be/4ufUqLNX9oQ)

However, this does also prompt the question about how much randomness to use. Clearly, putting randomness in charge of too many aspects can completely break down the desired effect, and often simply results in gibberish. Therefore, I think the ideal amount of randomness would be one that allows the work to be unique and “alive”, but within its bounds, making the work coherent and interesting. The exact amount of randomness used then ultimately depends on the artist and their desired result, but in almost all cases, randomness is constrained in some way (eg. instead of picking a completely random colour, one of the components of HSL (hue, saturation or lightness) could be random, while controlling the other two, allowing for random, yet pleasing and consistent colours).

Week 2: Art Using Loops – Cool Circular Magic Spell Effect

# Jump To:


# Introduction & Inspiration

Hey everyone, welcome back! 👋

For my second week’s project, I was thinking about a simple project that embodied loops. Something, which would make someone think about loops, but not too directly obvious. Just thinking about loops, an action that keeps repeating, the first 2 ideas that popped into my head (after a grid of images) were those circle drawing toys (idk what they’re called 🤷‍♂️), and L-system trees (huh? but that’s recursive! So, we’ll save that for later 😉 ). I guess we’re doing the first one. Remember these toys?

A spirograph
A spirograph, aka, “fun-children’s-drawing-circle-toy”

Ohh, a spirograph! That’s what they’re called 😅. Anyways, yes, those things. They reminded me of a loop because the person carries out the action (moving their hand in a circular motion) again and again (although surprisingly, the result is mesmerising).

I wanted to build something similar, but a bit simpler (idk why, but while building this sketch, I initially felt like I was teaching someone about loops 😅).

# Implementation

Let’s start by first drawing 1 line on a circle. More specifically, the endpoints of the line should lie on the circumference of the circle. We can use the handy unit circle and basic trigonometry to find out that we can get the x and y position of a point, by just supplying an angle and radius/length. This might not seem like a better deal, after all, we’re getting 2 (possibly more confusing) variables, for 2 (simple) variables, however, it makes the maths mucchhhh simpler.

Unit circle
Unit circle (from Wikipedia)

As you can see, the formulas are: x = cos(angle) & y = sin(angle) (but note, that angle must usually be in radians, as p5.js’s default angle mode is radians).

 
translate(width/2, height/2)

a1 = radians(0)
a2 = radians(90)
r = 300

x1 = cos(a1) * r
y1 = sin(a1) * r
x2 = cos(a2) * r
y2 = sin(a2) * r

line(x1, y1, x2, y2)
 

That gives us the following (I styled the background black, and the line red)

red line

Ok, but how can we tell this is actually on a circle, and not just some random line? Let’s draw a circle outline and check (I used noFill() and stroke(255));

 
circle(0, 0, r)
 
line on circle with outline
Yep!

Yay! We can clearly see it’s on the line, and also control the angle! We can add a dot to the center, and also control the line using our mouse.

 
fill(255);
circle(0, 0, 25);

// ...

a1 = radians(map(mouseX, 0, width, 0, 360));
a2 = radians(map(mouseY, 0, height, 0, 360));
 

Now let’s use a loop to add many more lines.

 
angle = frameCount/2000 + 1000.07 // Just a random nice looking starting point
num = 50

for (let i=0; i<num; i++) {
  x1 = cos(angle * (i-1)) * r;
  y1 = sin(angle * (i-1)) * r;
  x2 = cos(angle * i) * r;
  y2 = sin(angle * i) * r;

  strokeWeight(1);
  stroke(255, 0, 0, 255);
  line(x1, y1, x2, y2);
}
 
many lines in a circle
Cool!

Oo, it’s already a bit mesmerising! (certainly enough for me to at least sit by and watch it for several minutes 😅). You’ll notice that the shape keeps changing, and this is due to the angle changing, which we did by tagging its value to number of frames that had passed since the sketch’s start frameCount (just be sure to slow it down a lot (eg. I used 2000), since there are usually 60 frames per second, so it’ll become too crazy!). Setting a variable based on the frame count like this is an incredibly helpful trick to animate the variables over time.

 

Another helpful trick is to be able to pause the sketch, which can sometimes help you to debug or understand your sketch better. We can achieve this by calling loop() and noLoop() respectively, based on isLooping()‘s output. It’s important to keep in mind that this stops our draw() function from running, so don’t try to restart the loop from within there! Instead, a better approach is to use a key press, which can be achieved like so:

 
function keyPressed(event) {
  (event.key == "p" && isLooping()) ? noLoop() : loop();
}
 

I just used a ternary operator to make it shorter, but it corresponds to:

 
function keyPressed(event) {
  if (event.key == "p") {
    if (isLooping()) {
      noLoop();
    } else {
      loop();
    }
  }
}
 

 

Now, it’s up to you on how you wanna style it, and what features you wanna add! For example, here’s how mine now looks like:

cool circular lines
Cool, eh? Or nah?

 

The end.

 

 

 

 

 

 

 

## Drawing Context

Ok ok, fine, I’ll explain a bit, but just a BIT more. You see the cool glow effect? How did I do that? (well, I could just say the function I used, but that’s no fun, nor will you learn much).

p5.js exposes something called the rendering context, through a variable, aptly named (can you guess?)… drawingContext! (better luck next time). But what is that, and where does p5 get the value from?

The rendering context, in short, is basically what p5.js uses to actually draw the stuff on screen (part of the canvas API). For example, when write this:

 
strokeWeight(10);
noFill();
rect(x, y, w, h);
 

That might get executed as:

 
drawingContext.lineWidth = 10;
drawingContext.strokeRect(x, y, w, h);
 

Note: This is just a hypothetical example, and is probably not how p5.js actually works. For starters, drawingContext is exposed by them as you’ll read now.

And where does it get the value from? Well, it’s the “CanvasRenderingContext2D” (for the default 2D sketches), which it gets by calling .getContext('2d') on the canvas element.

 

“*Grumble grumble*, yada yada, why does this rendering wendering scherending context even matter?”

Well, firstly, rude, but secondly, good question. It’s because, while p5.js does provide easier functions for most things, it doesn’t expose everything that the rendering context has. Remember the glow effect from earlier? Well, it’s actually done through the .shadowBlur and .shadowColor properties, which aren’t exposed.

In addition to making it much easier to draw shadows/glows, there’s also a MUCH faster (hardware accelerated) blur function! (through the .filter property, which supports more than just blur.) This means we can finally use blur in our sketches, without our performance instantly tanking! (though it still has to be used with caution, as it’s still a heavy effect (similar to the shadows), so too much can still cause performance to drop.)

Also, we can easily make gradients! That’s right, linear, radial, conic, you name it, gradient! I’d recommend checking out these 3 videos to learn more:

 

For my circle, I added 3 layers of lines. The first one is red and has a stroke weight of 5, with a red glow. The next 2 don’t have any glow, and are blue (with a stroke weight of 4) and white (with a stroke weight of 2) respectively. I also changed a few things, like making it spawn and follow the user’s cursor, and the ability to change it’s size. It also reminded me of Dr. Strange’s “circle-magic-thingies” and similar, so I added another circle to lean into that.

So that’s mostly it! Anyways, without further ado, I present, the final result!

 

# Final Result

Controls:

  • Mouse click (press & hold) : Spawn and continue circular magic spell
  • Mouse move : Move magic spell
  • Up arrow (press & hold) : Grow its size
  • Down arrow (press & hold) : Shrink its size

 

# Additional Thoughts

I really like how the end result looks like, especially the colours and glow, and how it animates (hope others find it cool too). Although, I wish I could implement some of my other ideas too (and make it look more like the circular spells you see), but unfortunately that’s all I have time for right now, so we’ll have to end it here. Until next time!

goodbye neon sign
Goodbye! (from Shutterstock)
(hehe, maybe I’m liking this neon style too much 😅)