Skip to content

Dancing Cairo circles (with music!)

Made for a TilingTuesday post on Mastodon.

Click somewhere on the page to start the music.

White circles positioned at the centers of a cairo tiling take turns randomly swapping with a neighboring circle.

let synth

function setup() {
  createCanvas(400, 400);
  
  synth = new p5.PolySynth()
  synth.setADSR(0.05, 0.05, 0.5, 0.2)

  windowResized()
  describe('Against a gray background, white circles positioned on the vertices of a snub square tiling take turns randomly swapping with one of their 5 neighboring circles, with one circle going in front and the other one passing behind it. On occasion, some circles are left without a "partner" and stand still for a turn. Every swap, a note plays in sequence.')
}
function windowResized() {
  // resizeCanvas(windowWidth, windowHeight)
}
function mousePressed() {
  userStartAudio();
}

let l = 0.8
let t = l
let c = 0
let swaps = {}
let fronts = {}
let notes = ['A4', 'D4', 'E4', 'F#4', 'C#4', 'G4']
let notes2 = ['D3', 'F#3', 'C#3', 'A3', 'G3', 'E3']

function draw() {
  background(100);
  t += deltaTime / 1000  // 1 / 30
  if (t > l) {
    c ++
    t %= l
    // ABACABA sequence
    let note = notes.find((_, i) => c & (1 << i)) || notes[notes.length - 1]
    synth.play(note, 0.5, l - t + l / 2, 0.3)
    if ((c - 1) & 16) {
      let note2 = notes2.find((_, i) => (c) & (1 << i)) || notes[notes.length - 1]
      synth.play(note2, 0.5, l - t, 0.3)
    }
    swaps = {}
  }
  let f = 0.8
  let b = (1 - cos(t / l * PI)) / 2
  let br = sqrt(b * (1 - b))
  let r = 35
  let d = 100
  let d1 = 18
  let d2 = 35
  function node(x, y, front) {
    // magic starts here
    let cx = floor(x/d) * d + d/2
    let cy = floor(y/d) * d + d/2
    let k1 = (cx - x) == d1 ? 1 : (cx - x) == -d1 ? -1 : 0
    let k2 = (cy - y) == -d1 ? 1 : (cy - y) == d1 ? -1 : 0
    let neighbors = [
      [cx + (cy - y), cy - (cx - x)],
      [cx - (cy - y), cy + (cx - x)],
      [cx - k1 * d + (cy - y), cy + k2 * d - (cx - x)],
      [cx - k2 * d - (cy - y), cy - k1 * d + (cx - x)],
      [cx - k2 * d + (cx - x), cy - k1 * d + (cy - y)]
    ]
    // magic ends here
    if (!swaps[[x, y]]) {
      let filtered = neighbors.filter(x => !swaps[x])
      if (filtered.length) {
        // pick a neighbor to swap with
        // swaps[[x, y]] = filtered.find((_, i) => c & (1 << i)) || filtered[filtered.length - 1]
        swaps[[x, y]] = random(filtered)
        // swaps[[x, y]] = filtered[c % filtered.length]
        swaps[swaps[[x, y]]] = [x, y]
        fronts[[x, y]] = !fronts[[x, y]]
        fronts[swaps[[x, y]]] = !fronts[[x, y]]
      } else {
        swaps[[x, y]] = [x, y]
        fronts[[x, y]] = random([true, false])
      }
    }
    // render animation
    if (fronts[[x, y]] == front) {
      let sx = lerp(x, swaps[[x, y]][0], b)
      let sy = lerp(y, swaps[[x, y]][1], b)
      let sr = r * pow(f, fronts[[x, y]] ? br : -br)
      strokeWeight(1)
      stroke(100)
      fill(lerp(245, 255, 0.5 - (fronts[[x, y]] ? br : -br)))
      circle(sx, sy + 5 * br * (front ? -1 : 1), sr)
    }
  }
  // let seed = random(1, 1000000)
  // let seed2 = random(1, 1000000)
  for (let front of [true, false]) {
    for (let x = -d; x < width + d; x += d) {
      for (let y = -d; y < height + d; y += d) {
        // randomSeed(x + y % 2 ? seed : seed2)
        node(x + d/2 - d1, y + d/2 - d2, front)
        node(x + d/2 + d1, y + d/2 + d2, front)
        node(x + d/2 + d2, y + d/2 - d1, front)
        node(x + d/2 - d2, y + d/2 + d1, front)
      }
    }
  }
}

(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/tMCeEmqO2)

Experiments tagged p5 (61/85)

Experiments tagged music (2/2) →|

Experiments tagged tiling (6/6) →|

Experiments on this site (61/85)