xxxxxxxxxx
263
//Night Sky Melody
//Created by Mirette Dahab
//October 21, 2024
//Created as part of a Melody study for The Code of Music class
let stars = [];
let cursorX = 0;
let synth;
let snarePlayer, hihatPlayer, kickPlayer;
let isSnareOn = false, isHihatOn = false, isKickOn = false;
let snareButton, hihatButton, kickButton, tempoUpButton, tempoDownButton, randButton;
let snareEventId, hihatEventId, kickEventId; //event IDs for each sound
//!!!!!!!!!!!!!!!!!!!!!!!!!
// To do:
// Make more space between the stars
//!!!!!!!!!!!!!!!!!!!!!!!!
function preload(){
//preload the drum sounds
snarePlayer = new Tone.Player("sounds/snare.mp3").toDestination();
hihatPlayer = new Tone.Player("sounds/hh.mp3").toDestination();
kickPlayer = new Tone.Player("sounds/kick.mp3").toDestination();
img = loadImage("nightsky.jpeg");
}
function setup() {
createCanvas(600, 430);
synth = new Tone.Synth().toDestination();
//create the stars
for (let i = 0; i < random(10, 20); i++) {
stars.push(new Star(random(10, width - 10), random(10, height - 100)));
}
Tone.Transport.bpm.value = 120;
snareButton = createButton('Snare');
snareButton.position(215, height + 10);
snareButton.mousePressed(toggleSnare);
hihatButton = createButton('Hi-hat');
hihatButton.position(280, height + 10);
hihatButton.mousePressed(toggleHihat);
kickButton = createButton('Kick');
kickButton.position(345, height + 10);
kickButton.mousePressed(toggleKick);
//tempo buttons
tempoUpButton = createButton('+');
tempoUpButton.position(510, height + 10);
tempoUpButton.mousePressed(increaseTempo);
tempoDownButton = createButton('-');
tempoDownButton.position(550, height + 10);
tempoDownButton.mousePressed(decreaseTempo);
randButton = createButton('Random');
randButton.position(30, height + 10);
randButton.mousePressed(randomizeAll);
Tone.Transport.start();
}
function draw() {
background(8, 11, 66);
image(img,0,0);
fill(0);
noStroke();
ellipse(width/2+50, height-30, 30 );
//display the stars
for (let star of stars) {
star.display();
star.drawConnections(); // draw connections to nearest "on" star
}
//cursor
stroke(183, 210, 235);
strokeWeight(2);
line(cursorX, 0, cursorX, height);
//motion from left to right
cursorX += Tone.Transport.bpm.value / 40; // Adjust speed with tempo
if (cursorX > width) cursorX = 0;
//if the cursor passes over an "on" star, trigger sound
for (let star of stars) {
if (star.isHovered(cursorX, star.y) && star.isOn) {
star.trigger();
}
}
}
//turn the stars on/off
function mousePressed() {
for (let star of stars) {
if (star.isHovered(mouseX, mouseY)) {
star.toggle();
}
}
//update connections after each toggle
updateAllConnections();
}
//Star class
class Star {
constructor(x, y) {
this.x = x;
this.y = y;
//Map y-axis to frequency range so that higher notes are on the top of the screen
this.freq = map(this.y, height-100, 0, 220, 660);
this.size = 4;
this.Onsize = 7;
this.isOn = false;
this.connectedStar = null; //reference to nearest "on" star
}
display() {
//brighter color and bigger size for the stars that are "on"
noStroke();
fill(this.isOn ? color(209,240,250) : color(186,213,222));
ellipse(this.x, this.y, this.isOn ? this.Onsize : this.size);
}
toggle() {
//toggle the state
this.isOn = !this.isOn;
//if toggled "off", remove the connection
if (!this.isOn) {
this.connectedStar = null;
}
}
trigger() {
// if star is on and the cursor passes over, play sound
synth.triggerAttackRelease(this.freq, "16n");
}
isHovered(x, y) {
// check if cursor or mouse is over the star
return dist(this.x, this.y, x, y) < this.size;
}
findNearestOnStar() {
let nearestDist = Infinity;
let nearestStar = null;
//find the narest "on" star
for (let otherStar of stars) {
if (otherStar !== this && otherStar.isOn) {
let d = dist(this.x, this.y, otherStar.x, otherStar.y);
if (d < nearestDist) {
nearestDist = d;
nearestStar = otherStar;
}
}
}
// set the nearest "on" star as the connected star
this.connectedStar = nearestStar;
}
drawConnections() {
if (this.isOn && this.connectedStar) {
// draw a line connecting this star to the nearest "on" star to create the constellations
stroke(186,213,222);
strokeWeight(1);
line(this.x, this.y, this.connectedStar.x, this.connectedStar.y);
}
}
}
function updateAllConnections() {
for (let star of stars) {
if (star.isOn) {
star.findNearestOnStar();
}
}
}
// Toggle snare
function toggleSnare() {
if (!isSnareOn) {
snareEventId = Tone.Transport.scheduleRepeat((time) => {
snarePlayer.start(time);
}, '1m');
snareButton.style('background-color', 'lightgray');
} else {
Tone.Transport.clear(snareEventId); //clear only the snare event
snareButton.style('background-color', '');
}
isSnareOn = !isSnareOn;
}
// Toggle hi-hat
function toggleHihat() {
if (!isHihatOn) {
hihatEventId = Tone.Transport.scheduleRepeat((time) => {
hihatPlayer.start(time);
}, '1m');
hihatButton.style('background-color', 'lightgray');
} else {
Tone.Transport.clear(hihatEventId); //clear hi-hat event
hihatButton.style('background-color', '');
}
isHihatOn = !isHihatOn;
}
// Toggle kick part
function toggleKick() {
if (!isKickOn) {
kickEventId = Tone.Transport.scheduleRepeat((time) => {
kickPlayer.start(time);
}, '1m');
kickButton.style('background-color', 'lightgray');
} else {
Tone.Transport.clear(kickEventId); // clear only the kick event.
kickButton.style('background-color', '');
}
isKickOn = !isKickOn;
}
//define functions to increase and decrease tempo
function increaseTempo() {
Tone.Transport.bpm.value += 10;
}
function decreaseTempo() {
Tone.Transport.bpm.value -= 10;
}
function randomizeAll() {
//randomize the star states
for (let star of stars) {
if (random() > 0.5) {
star.isOn = true;
} else {
star.isOn = false;
}
}
updateAllConnections(); //recalculate connections after toggling stars
//randomize the Drum sounds.
if (random() > 0.5) toggleSnare();
if (random() > 0.5) toggleHihat();
if (random() > 0.5) toggleKick();
//random the tempo between 60 and 180 bpm
Tone.Transport.bpm.value = random(60, 180);
}