xxxxxxxxxx
215
// Magic Constants
const N = 16
const WINDOW_SIZE = 669;
const CIRCLE_RADIUS = WINDOW_SIZE / 2 - 50;
const BALL_RADIUS = 15;
let depr1 = [62, 65, 57, 82, 43, 67, 50, 70]; // old way
let depr2 = ["C5", "G5", "B5", "E6", "B6", "E7", "D7", "B7"]; //
let Cmaj9_E = ["E", "C", "E", "G", "B", "D"];
let Bbmaj9_D = ["D", "A#", "D", "F", "A", "C"];
let Abmaj9_C = ["C", "G#", "C", "D#", "G", "A#"];
let Gbmaj9_Bb = ["A#", "F#", "A#", "C#", "E", "G#"];
function notesInMultipleOctaves(notes, lowerOctave, higherOctave) {
let result = [];
for (let octave = lowerOctave; octave <= higherOctave; octave++) {
result = result.concat(notes.map((note) => note + octave));
}
return result;
}
G_7 = ["C7","G#6","G6","D#6","C6","G#5","G5","D#5","C5","G#4","G4","D#4","C4","G#3","G3","D#3","C3","G#2","G2","D#2","C2","G#",
"G",
"D#",
"C",
];
const FREQUENCIES = G_7 //notesInMultipleOctaves(Gbmaj9_Bb, 3, 7);
const BOUNCES_TILL_SWAP_DECAY = 10;
const ATTACK_TIME = 0.01;
const DECAY_TIME = 0.5;
const SUSTAIN_LEVEL = 0.1;
const RELEASE_TIME = 1;
let balls = [];
let reverb;
var t = 0;
var dt = 0.05;
function setup() {
createCanvas(WINDOW_SIZE, WINDOW_SIZE);
angleMode(DEGREES);
reverb = new p5.Reverb();
initializeInputElements();
updateNotesAndOctaves();
}
function initializeInputElements() {
const lowOctaveSelect = select("#lowOctave");
const highOctaveSelect = select("#highOctave");
for (let i = 3; i <= 9; i++) {
lowOctaveSelect.option(i);
highOctaveSelect.option(i);
}
lowOctaveSelect.selected(4);
highOctaveSelect.selected(7);
}
function draw() {
background(0, 0, 0, 10);
t += dt;
translate(WINDOW_SIZE / 2, WINDOW_SIZE / 2);
noFill();
for (let ball of balls) {
ball.update();
ball.display();
}
noFill();
stroke("white");
circle(0, 0, CIRCLE_RADIUS * 2 + BALL_RADIUS * 2);
}
function mouseClicked(){
userStartAudio()
}
function keyPressed(){
userStartAudio()
}
function updateNotesAndOctaves() {
const input = select("#noteInput").value();
const notes = input.split(",").map((note) => note.trim());
const lowOctave = int(select("#lowOctave").value());
const highOctave = int(select("#highOctave").value());
const frequencies = notesInMultipleOctaves(notes, lowOctave, highOctave);
teardownBalls();
setupBalls(frequencies);
}
function notesInMultipleOctaves(notes, lowerOctave, higherOctave) {
let result = [];
for (let octave = lowerOctave; octave <= higherOctave; octave++) {
result = result.concat(notes.map((note) => note + octave));
}
return result;
}
function setupBalls(frequencies) {
let prev = null;
for (let i = 0; i < N; i++) {
let angle = (180 / N) * i;
let note_string = frequencies[i % frequencies.length];
let frequency = noteToFreq(note_string);
const ball = new Ball(angle, frequency, 1 - (i + 1) / 100);
if (prev) {
ball.prev = prev;
prev.next = ball;
}
prev = ball;
balls.push(ball);
}
balls[0].prev = balls[balls.length - 1];
balls[balls.length - 1].next = balls[0];
}
function teardownBalls() {
for (let ball of balls) {
ball.oscillator.stop();
}
balls = [];
}
class Ball {
constructor(angle, frequency, decay) {
this.angle = angle;
this.frequency = frequency;
this.position = createVector(0, 0);
this.bounces = 0;
this.decay = decay;
this.speed = createVector(-cos(this.angle), -sin(this.angle));
this.speed.mult(2);
this.prev = null;
this.next = null;
this.oscillator = new p5.Oscillator("sine");
this.oscillator.freq(this.frequency);
// Adjust envelope for a chime-like sound
this.envelope = new p5.Envelope();
this.envelope.setADSR(0.001, 0.2, 0.1, 0.05); // Fast attack, short decay, low sustain, and a bit of release
this.envelope.setRange(1, 0); // Loud attack, silent release
this.oscillator.amp(this.envelope);
this.oscillator.start();
// Reverb to give it a spacious sound
this.reverb = new p5.Reverb();
this.oscillator.disconnect(); // Disconnect from master output
this.reverb.process(this.oscillator, 3, 2); // 3-second reverb time, 2-second decay
}
update() {
let distanceFromCenter = this.position.mag(); // Get the magnitude of the position vector (distance from center)
// Check if the ball has reached or exceeded the edge of the circle
if (distanceFromCenter > CIRCLE_RADIUS) {
// Calculate overshoot
let overshoot = distanceFromCenter - CIRCLE_RADIUS;
// Reverse direction
this.speed.mult(-1);
this.bounces++;
if (this.bounces % BOUNCES_TILL_SWAP_DECAY == 0) {
if (0.99 < this.decay && this.decay < 1.01) {
} else {
if (this.decay < 1) {
this.decay = 1 + (1 - this.decay);
} else {
this.decay = 1 - (this.decay - 1);
}
}
}
// Apply decay to the speed
this.speed.mult(this.decay);
// Move the ball back inside the circle by the overshoot amount to ensure it doesn't spawn on the other side
this.position.setMag(CIRCLE_RADIUS - overshoot);
// Play the envelope
this.envelope.play();
}
// Move the ball
this.position.add(this.speed);
}
display() {
stroke(this.position.x, this.position.y, 200);
circle(this.position.x, this.position.y, 2 * BALL_RADIUS);
stroke("rgba(255,255,255,0.4)");
line(
this.position.x,
this.position.y,
this.prev.position.x,
this.prev.position.y
);
push();
rotate(this.angle);
stroke(222, 100, 222, 1);
line(-CIRCLE_RADIUS, 0, CIRCLE_RADIUS, 0);
pop();
}
}