image.png

Inspired by this picture, we decided to create a melody and make its zigzag line move behind the mountain image.

Create the melody

We generated an algorithmic melody using a C Major Pentatonic scale with five notes: C (60), D (62), E (64), G (67), and A (69), creating a harmonious and relaxing melody, and playing it in a loop with a p5.js MonoSynth.

let synth;
let melodyNotes;
let noteDurations;
let currentNote = 0;
let scale = [60, 62, 64, 67, 69]; // C Major Pentatonic scale
let bpm = 180;
let noteInterval;
let startTime;
let noiseOffset = 0;

function setup() {
createCanvas(400, 400);
synth = new p5.MonoSynth();

// Seed noise and random
noiseSeed(0);
randomSeed(0);

// Generate a melody and rhythm algorithmically
melodyNotes = generateMelody();
noteDurations = generateDurations(melodyNotes.length);

// Calculate time between notes
noteInterval = (60 / bpm) * 1000;

startTime = millis();
}

function draw() {
background(200, 150, 250);

// Play notes based on the algorithm
if (millis() - startTime >= noteInterval) {
playNote();
startTime = millis();
}
}

function generateMelody() {
let melody = [];
for (let i = 0; i < 32; i++) { // 32 notes for 60 seconds
let noiseValue = noise(noiseOffset);
let noteIndex = floor(map(noiseValue, 0, 1, 0, scale.length));
melody.push(scale[noteIndex]);
noiseOffset += 0.1;
}
return melody;
}

function generateDurations(length) {
let durations = [];
for (let i = 0; i < length; i++) {
durations.push(random([0.25, 0.5, 1]) * (60 / bpm)); // quarter, half, whole notes
}
return durations;
}

function playNote() {
if (currentNote < melodyNotes.length) {
let note = midiToFreq(melodyNotes[currentNote]);
let duration = noteDurations[currentNote];
synth.play(note, 0.5, 0, duration);
currentNote++;
} else {
currentNote = 0; // Loop the melody
}
}

Create the zigzag line

We created a zigzag line that moves in sync with the MIDI notes of the melody. When the melody plays, the line moves along with the MIDI notes. When the melody stops, the line remains flat.

let zigzagLinePoints = [];
let maxPoints = 50;
let amplitude = 30;
let yOffset = -50;
let isPlaying = true;

// Initialize the zigzag line points
for (let i = 0; i < maxPoints; i++) {
  zigzagLinePoints.push({
    x: i * (width / maxPoints),
    y: height / 2 + yOffset,
  });
}
}

function draw() {
  background(0);

  // Draw the zigzag line
  noFill();
  stroke(255);
  strokeWeight(4);
  beginShape();
  for (let point of zigzagLinePoints) {
    vertex(point.x, point.y);
  }
  endShape();

  // Play notes and update zigzag line
  if (isPlaying && millis() - startTime >= noteInterval) {
    playNote(); // Sync sound and zigzag line update
    startTime = millis();
  } else if (!isPlaying) {
    // Keep the zigzag line flat when paused
    maintainZigzagLine();
  }
}

function updateZigzagLine() {
  // Remove the leftmost point
  zigzagLinePoints.shift();

  // Calculate the y-coordinate of the new point based on the current MIDI note
  let midiValue = melodyNotes[currentNote % melodyNotes.length]; // Current MIDI note
  let newY =
    map(
      midiValue,
      min(scale),
      max(scale),
      height / 2 - amplitude * 2,
      height / 2 + amplitude * 2
    ) + yOffset;
  // Add a new point to the rightmost end of the line
  let newX = width;
  zigzagLinePoints.push({ x: newX, y: newY });

  // Move all points to the left to maintain the dynamic effect
  for (let point of zigzagLinePoints) {
    point.x -= width / maxPoints;
  }
}

// Maintain a flat line when paused
function maintainZigzagLine() {
  zigzagLinePoints.shift();

  let newX = width;
  let newY = height / 2 + yOffset;

  zigzagLinePoints.push({ x: newX, y: newY });

  // Move all points to the left to maintain the dynamic effect
  for (let point of zigzagLinePoints) {
    point.x -= width / maxPoints;
  }
}

function mousePressed() {
  isPlaying = !isPlaying;
}

Add the mountain images

We want the line to move behind the mountain, so we use Photoshop to cut out the mountain from the background.

ama-dablam2-most-beautiful-mountains-in-the-world.png

0.png

Then we imported both images and drew the cut-out image on top of the zigzag line.

let img;
let topImage;

function preload() {
  img = loadImage("mountain2.png");
  topImage = loadImage("mountain.png");
}

function draw() {
  background(0);
  tint(200);
  image(img, 0, 0, width, height);

  // Draw the zigzag line
  noFill();
  stroke(255);
  strokeWeight(4);
  beginShape();
  for (let point of zigzagLinePoints) {
    vertex(point.x, point.y);
  }
  endShape()

  // Draw the top image
  image(topImage, 0, 0, width, height);
}

Add the text

In the end, we added some text to indicate the actions “Pause” and “Continue.”

function draw() {
  // Draw the text
  fill(255);
  noStroke();
  textAlign(CENTER, CENTER);
  textSize(16);
  let message;
  if (isPlaying) {
    message = "Click to Pause";
  } else {
    message = "Click to Continue";
  }
  text(message, width / 2, height / 2 + 170);
}

https://editor.p5js.org/HanBao/full/i4NnpdUIy