xxxxxxxxxx
371
//Night Sky Music Maker
//Created by Mirette Dahab
//December ..., 2024
//Created as a final project for The Code of Music class
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//TO DO LIST
//map the notes vertically
//add key presses for the chords
//define updateStarNote() function
//make more stars by using more octaves
//add randomization of scale in the random function
//add visualization elements for chords and drums
//Add a nice font for instructions
//add clear button
let stars = [];
let cursorX = 0;
let synth, chordSynth;
let snarePlayer, hihatPlayer, kickPlayer;
let isSnareOn = false, isHihatOn = false, isKickOn = false;
let snareButton, hihatButton, kickButton, tempoUpButton, tempoDownButton, randButton, decScaleButton, incScaleButton;
let snareEventId, hihatEventId, kickEventId; //event IDs for each sound
// MIDI root notes for all major scales
let rootNotes = [48, 55, 62, 69, 76, 83, 90, 97, 53, 58, 63, 68]; // C3 to Ab3
let majorIntervals = [0, 2, 4, 5, 7, 9, 11]; // Major scale intervals
let currentScaleIndex = 0;
let currentScale = [];
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();
chordSynth = new Tone.PolySynth().toDestination();
// Initialize scale notes dynamically
updateCurrentScale();
// Create stars
for (let i = 0; i < 7; i++) {
stars.push(
new Star(
random(10, width - 10),
random(10, height - 100),
currentScale[i % currentScale.length]
)
);
}
Tone.Transport.bpm.value = 120;
randButton = createButton('Random');
randButton.position(20, height + 10);
randButton.mousePressed(randomizeAll);
snareButton = createButton('Snare');
snareButton.position(160, height + 10);
snareButton.mousePressed(toggleSnare);
hihatButton = createButton('Hi-hat');
hihatButton.position(225, height + 10);
hihatButton.mousePressed(toggleHihat);
kickButton = createButton('Kick');
kickButton.position(290, height + 10);
kickButton.mousePressed(toggleKick);
//tempo buttons
tempoUpButton = createButton('+');
tempoUpButton.position(410, height + 10);
tempoUpButton.mousePressed(increaseTempo);
tempoDownButton = createButton('-');
tempoDownButton.position(450, height + 10);
tempoDownButton.mousePressed(decreaseTempo);
decScaleButton = createButton('<');
decScaleButton.position(510, height + 10);
decScaleButton.mousePressed(decreaseScale);
incScaleButton = createButton('>');
incScaleButton.position(550, height + 10);
incScaleButton.mousePressed(increaseScale);
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();
}
}
//INSTRUCTIONS
fill(90);
noStroke();
ellipse(15, height-15, 15);
fill(242, 233, 245);
textSize(12);
text("i", 14, height-10);
if (mouseX > 7.5 && mouseX < 22.5 && mouseY > height-22.5 && mouseY < height-7.5) {
fill(209, 229, 250);
strokeWeight(3);
stroke(10, 21, 36);
rect(25, 15, 550, 350, 20);
fill(0);
noStroke();
textSize(28);
text("Instructions", 220, 58);
textSize(16);
text("- Click on a star to turn it on.", 50, 100);
text("- Use the buttons to toggle on/off the drums.", 50, 140);
text("- Randomize the melody using the Random button.", 50, 180);
text("- Control Tempo using + and - buttons", 50, 220);
text("- Use keys a, d, g, j to play chords", 50, 260);
text("- Use the arrow buttons to change between the scales", 50, 300);
}
}
//turn the stars on/off
function mousePressed() {
for (let star of stars) {
if (star.isHovered(mouseX, mouseY)) {
star.toggle();
console.log(star.note);
}
}
//update connections after each toggle
updateAllConnections();
}
//Star class
class Star {
constructor(x, y, note) {
this.x = x;
this.y = y;
this.note = note;
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.note, "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();
}
}
}
function updateCurrentScale() {
let rootNote = rootNotes[currentScaleIndex];
currentScale = majorIntervals.map(interval => {
return Tone.Frequency(rootNote + interval, "midi").toNote();
});
}
function updateStarNotes() {
//this function is used to update the notes of the stars when the scale is changed
let i = 0;
for(let star of stars){
star.note = currentScale[i++ % currentScale.length];
}
}
// 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;
}
//define functions to switch between scales
function increaseScale() {
currentScaleIndex = (currentScaleIndex + 1) % rootNotes.length;
updateCurrentScale();
updateStarNotes();
console.log(currentScale[0]);
console.log(currentScaleIndex);
}
function decreaseScale() {
currentScaleIndex = (currentScaleIndex - 1 + rootNotes.length) % rootNotes.length;
updateCurrentScale();
updateStarNotes();
console.log(currentScale[0]);
console.log(currentScaleIndex);
}
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);
}
function keyPressed(){
if(keyCode == 65){
let currentChord = majorChords[currentScaleIndex];
chordSynth.triggerAttackRelease(currentChord, "16n");
}
if(keyCode == 68){
let currentChord = minorChords[currentScaleIndex];
chordSynth.triggerAttackRelease(currentChord, "16n");
}
if(keyCode == 71){
let currentChord = diminishedChords[currentScaleIndex];
chordSynth.triggerAttackRelease(currentChord, "16n");
}
if(keyCode == 74){
let currentChord = augmentedChords[currentScaleIndex];
chordSynth.triggerAttackRelease(currentChord, "16n");
}
}