xxxxxxxxxx
252
let pendulums = [];
let swingSpeed = 1;
let currentNote = 60; // MIDI note for middle C
const THE_BASE_NOTE = 60;
// Constants for easy tweaking
const RECORDING_DURATION = 10000; // Duration in milliseconds
// Variables for recording
let soundRecorder, soundFile;
let octave = 0;
let paused = false;
let synth;
let addPendulumOnPeak = false;
const speedMultiplier = 0.03;
let addOnPressCheckbox;
let mainCanvas;
let backgroundCanvas;
function mousePressed() {
userStartAudio()
}
class Pendulum {
constructor(length, gravity, note) {
this.length = length;
this.angle = PI / 4;
this.aVelocity = 0.0;
this.aAcceleration = 0.0;
this.gravity = gravity;
this.note = note;
this.swingingTowardsCenter = false;
this.origin = createVector(width / 2, 0);
this.position = createVector();
this.calculatePosition();
this.oscillator = new p5.Oscillator("sine");
this.oscillator.freq(midiToFreq(note));
this.oscillator.start();
this.oscillator.amp(0);
this.env = new p5.Envelope();
this.env.setADSR(0.05, 0.4, 0.1, 0.5); // Smoother ADSR settings
this.env.setRange(0.3, 0); // Lower maximum amplitude
// Optional: Apply a low-pass filter
this.filter1 = new p5.LowPass();
this.filter1.freq(2100); // Adjust frequency as needed
this.filter2 = new p5.HighPass();
this.filter2.freq(250);
this.oscillator.disconnect();
this.oscillator.connect(this.filter1);
this.oscillator.connect(this.filter2);
}
playSound() {
// Trigger the envelope to play the sound
this.env.play(this.oscillator);
}
calculatePosition() {
this.position.set(
this.origin.x + this.length * sin(this.angle),
this.origin.y + this.length * cos(this.angle)
);
}
justPassedPeak() {
// Check for a change in the sign of the angular velocity
let peakPassed = this.aVelocity * this.previousAVelocity <= 0;
this.previousAVelocity = this.aVelocity;
return peakPassed;
}
update(silent=false) {
let force = -this.gravity * sin(this.angle);
this.aAcceleration = force / this.length;
this.aVelocity += this.aAcceleration;
this.angle += this.aVelocity;
this.aVelocity *= 1; // damping
this.calculatePosition();
// Check if pendulum is swinging towards the center
let currentlySwingingTowardsCenter = this.aVelocity < 0;
// Play the note at the change of direction
if (this.swingingTowardsCenter !== currentlySwingingTowardsCenter) {
if (!silent) this.playSound();
this.swingingTowardsCenter = currentlySwingingTowardsCenter;
}
}
display() {
stroke(0);
strokeWeight(2);
line(this.origin.x, this.origin.y, this.position.x, this.position.y);
fill(127);
ellipse(this.position.x, this.position.y, 20, 20);
backgroundCanvas.stroke(lerpColor(color('yellow'), color('red'), this.position.x/width/2 + this.position.y/height/2))
backgroundCanvas.noFill()
backgroundCanvas.ellipse(this.position.x, this.position.y, 20, 20);
}
}
function initializeChromaticScale(baseNote, octaves) {
let numberOfNotes = octaves * 12; // 12 notes per octave
for (let i = 0; i < numberOfNotes; i++) {
let note = baseNote + (pow(i, 2)) % int(baseNote/2);
let length = 100 + i * 30; // Adjust length for each pendulum
let gravity = calcGravity(swingSpeed, length); // You might want to adjust calcGravity
var pendulum = new Pendulum(length, gravity, note)
for (var j = 0; j < 10 * (numberOfNotes-i) ; j++){
pendulum.update(true)
}
pendulums.push(pendulum);
}
}
function stopRecording() {
soundRecorder.stop(); // Stop the recording
print('recording stopped@!')
}
function setup() {
let canvasSize = min(windowWidth, windowHeight);
mainCanvas = createCanvas(canvasSize, canvasSize);
mainCanvas.parent('sketch-holder');
backgroundCanvas = createGraphics(canvasSize, canvasSize);
synth = new p5.PolySynth();
// Adjust length and spacing for smaller screens
let baseLength = canvasSize / 10;
let lengthIncrement = canvasSize / 30;
initializeChromaticScale(THE_BASE_NOTE, 2, baseLength, lengthIncrement); // Adjusted function call
addOnPressCheckbox = createCheckbox("Add pendulums on press").checked(true);
soundRecorder = new p5.SoundRecorder();
soundFile = new p5.SoundFile();
}
function draw() {
if (!paused) {
background(255);
backgroundCanvas.background(255,255,255,3)
image(backgroundCanvas,0,0,width,height)
pendulums.forEach((pendulum) => {
pendulum.update();
pendulum.display();
});
if (!addOnPressCheckbox.checked()) {
if (addPendulumOnPeak && pendulums.length > 0) {
if (pendulums[0].justPassedPeak()) {
let length = 100 + pendulums.length * 50;
let gravity = calcGravity(swingSpeed, length);
pendulums.push(new Pendulum(length, gravity, currentNote));
playNote(currentNote);
addPendulumOnPeak = false;
}
}
}
}
}
function keyPressed() {
userStartAudio();
if (key === " ") {
paused = !paused;
} else if (key === "[" ) {
soundRecorder.record(soundFile);
print('recording started')
setTimeout(stopRecording, RECORDING_DURATION); // Stop recording after a set duration
}else if (key === "]") {
saveSound(soundFile, 'mySound.mp3'); // Save the recorded sound
print('saved!')
}
else if (key === "P" || key === "p") {
if (!addOnPressCheckbox.checked()) {
if (pendulums.length == 0) {
let length = 100 + pendulums.length * 50;
let gravity = calcGravity(swingSpeed, length);
pendulums.push(new Pendulum(length, gravity, currentNote));
} else {
addPendulumOnPeak = true;
}
} else {
let length = 100 + pendulums.length * 30;
let gravity = calcGravity(swingSpeed, length);
pendulums.push(new Pendulum(length, gravity, currentNote));
}
} else if (key === "Q" || key === "q") {
pendulums.forEach((p) => {
p.angle = PI / 4 ;
p.aVelocity = 0;
});
} else if (key === "O" || key === "o") {
pendulums.pop()
}else if ("12345678".includes(key)) {
swingSpeed = Math.pow(2, parseInt(key) - 1);
} else if ("awsedftgyhujk".includes(key)) {
currentNote = 60 + "awsedftgyhujk".indexOf(key) + octave * 12;
} else if ("zxcv".includes(key)) {
octave = "zxcv".indexOf(key);
}
}
function calcGravity(swingSpeed, length) {
// Decrease the speedMultiplier to make pendulums slower
swingSpeed *= speedMultiplier; // Adjust this value to control speed
return (
(4 * Math.PI * Math.PI * length) /
Math.pow((1 / swingSpeed) * 2 * Math.PI * sqrt(100 / 9.8), 2)
);
}
function playNote(note, duration = 0.1) {
let freq = midiToFreq(note);
setTimeout(() => {
let env = new p5.Envelope();
env.setADSR(0.01, 0.2, 0.2, 0.1); // Attack, Decay, Sustain, Release times
env.setRange(0.5, 0); // Attack level, Release level
synth.noteAttack(freq, 0.5); // Start the note
env.play(synth); // Apply envelope to synth
synth.noteRelease(freq); // Release the note after duration
}, duration * 1000);
}