https://editor.p5js.org/giav2030/sketches/_v5jAQQu2

For this algorithmic sound composition project, my partner Ivan and I started by exploring two distinct approaches.

Ivan's Initial Concept: An Interactive Instrument

Ivan built an interactive instrument triggered by keyboard input. His code used two oscillators and a custom scale defined by pitch ratios. The core algorithm used cos() and sin() functions, perturbed by a random element in the key press handler, to navigate through the scale and generate both melodic and bass frequencies. While engaging, it required user interaction to produce sound.

// Key part of Ivan's initial code: Interactive instrument
function keyPressed() {
  t += random(4) > 0.5 ? 3 : 2;
  let r = (cos(t) + 1) * 4;
  let f = ratios[floor(r)] * BASE;
  // ... (bass calculation similar)
  melOsc.freq(f);
  bassOsc.freq(bassf);
}

My Initial Concept: An Automatic 60s-Style Melody

I initially misinterpreted the brief, aiming for a "60s-style" piece inspired by the iconic singer Teresa Teng, rather than a 60-second one. My code automatically played notes from a similar custom scale at fixed intervals (switchTime), using random() to select the sequence. This created a gentle, slightly unpredictable melody with a nostalgic, retro character.

// Key part of my initial code: Automatic melody
function draw() {
  if (currentTime - lastSwitch > switchTime) {
    let whichNote = floor(random(notes.length));
    let frequency = baseNote * notes[whichNote];
    osc.freq(frequency);
    lastSwitch = currentTime;
  }
}

However, Ivan rightly pointed out a crucial requirement I had missed: the composition must be repeatable each time it's played. My use of random() without a fixed seed meant the melody would be different every time.

Synthesis and Final Composition

We merged the strengths of both prototypes. The final code integrates:

// Final integrated solution  
function draw() {  
  if (switchTime < 10) switchTime = 400; // Reset acceleration  
  let curTime = millis();  
  if (curTime - lastSwitch > switchTime) {  
    t += random(4) > 0.5 ? 3 : 2; // Ivan’s probabilistic melody logic  
    t2 += 400;  
    // Calculate frequencies using trigonometric scaling  
    let r = (cos(t) + 1) * 4;  
    let f = ratios[floor(r)] * BASE;  
    let r2 = (sin(t2) + 1) * 4;  
    let bassf = ratios[floor(r2)] * bassBASE;  
    // Update oscillators  
    melOsc.freq(f);  
    bassOsc.freq(bassf);  
    lastSwitch = curTime;  
    switchTime -= 3; // Accelerate  
  }  
}