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:
switchTime variable decreases by 3ms each cycle, gradually accelerating the pattern until resetting to 400ms—adding tension and variation.randomSeed(77) ensures the same "random" sequence on every run.// 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
}
}