xxxxxxxxxx
478
let freqVal = 0; // Frequency value received from Arduino
let isPlaying = false; // Checks if a sound is currently playing
let osc; // Oscillator for sound generation
let fft; // FFT object for audio visualization
let particles = []; // Array for particle visualization
let img, noteImg, backImg; // Images for back button and music note
let notes = ["E", "F", "G", "A", "B", "C", "D", "E2", "F2"]; // Note sequence
// Dimensions for the music note image
let newWidth = 120; // Note image width
let newHeight = 240; // Note image height
let spacing = 40; // Staff line spacing
let noteHeights = {}; // Map notes to y-positions
let noteWidths = {}; // Map notes to x-positions
let pressedButtons = {}; // Tracks pressed button states
// Game state variables
let currentMode = "start"; // Tracks the current mode: "start", "game", "tutorial", or "freeplay"
let currentNote = ""; // Tracks current note being played
let previousNote = ""; // Previous note to avoid repetition
let points = 0; // Points for the game
let timeLeft = 60; // 1-minute timer
let gameOver = false; // To check if the game is over
let timer; // Timer interval
let currentNoteIndex = 0; // Tracks the current tutorial note index
let tutorialComplete = false; // Tracks if the tutorial is complete
let isVisualizing = false; // Controls visualization
function preload() {
img = loadImage("music_note.png");
backImg = loadImage("back.png");
noteImg = loadImage("music_note.png");
}
function updateNotePositions() {
noteHeights = {
"E": height / 2 + 80,
"F": height / 2 + 60,
"G": height / 2 + 40,
"A": height / 2 + 20,
"B": height / 2,
"C": height / 2 - 20,
"D": height / 2 - 40,
"E2": height / 2 - 60,
"F2": height / 2 - 80,
};
noteWidths = {
"E": width / 2 - 100,
"F": width / 2 - 80,
"G": width / 2 - 60,
"A": width / 2 - 40,
"B": width / 2,
"C": width / 2 + 40,
"D": width / 2 + 60,
"E2": width / 2 + 80,
"F2": width / 2 + 100,
};
}
function setup() {
createCanvas(windowWidth, windowHeight);
rectMode(CENTER);
angleMode(DEGREES);
textSize(18);
fft = new p5.FFT(); // Initialize FFT for visualization
// to change the music note imge from black to white
[img, noteImg].forEach((image) => {
image.resize(newWidth, newHeight);
image.loadPixels();
for (let i = 0; i < image.pixels.length; i += 4) {
image.pixels[i] = 255 - image.pixels[i];
image.pixels[i + 1] = 255 - image.pixels[i + 1];
image.pixels[i + 2] = 255 - image.pixels[i + 2];
}
image.updatePixels();
});
// Update note positions
updateNotePositions();
// Initialize oscillator for sound as a sine wave
osc = new p5.Oscillator();
osc.setType("sine");
osc.start();
osc.amp(0);
// Start serial communication
setUpSerial();
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
updateNotePositions(); // Recalculate note positions depending of the screen size
}
function draw() {
background(0);
if (!serialActive) {
fill(255);
text("Press Space Bar to select Serial Port, Select USB", 20, 30);
return;
}
// Draw back button on all screens except start screen
if (currentMode !== "start") {
image(backImg, 20, 20, 50, 50);
}
switch (currentMode) {
case "start":
drawStartScreen();
break;
case "game":
drawGame();
break;
case "tutorial":
drawTutorial();
break;
case "freeplay":
drawFreeplay();
break;
}
}
function drawStartScreen() {
fill(255);
textAlign(CENTER, CENTER);
textSize(60);
noStroke();
text("Melody Masters", width / 2, height / 6);
textSize(30);
text("The Ultimate Note Challenge", width / 2, height / 4 + 10);
text("START", width / 2, height / 2);
textSize(20);
text("Tutorial", width / 2 - 150, height / 2 + 100);
text("Free Play", width / 2 + 150, height / 2 + 100);
stroke(255);
noFill();
strokeWeight(2);
rect(width / 2, height / 2, 250, 70, 50);
rect(width / 2 - 150, height / 2 + 100, 200, 50, 50);
rect(width / 2 + 150, height / 2 + 100, 200, 50, 50);
}
function drawGame() {
if (gameOver) {
fill(255);
textSize(24);
textAlign(CENTER, CENTER);
text("Game Over!", width / 2, height / 2 - 40);
text(`Points: ${points}`, width / 2, height / 2);
fill(100);
noStroke();
rect(width / 2, height / 2 + 60, 150, 50, 10);
fill(255);
textSize(18);
text("Restart", width / 2, height / 2 + 60);
} else {
drawStaff();
drawCurrentNote();
drawVisualization();
drawNoteImage();
fill(255);
noStroke();
textSize(24);
text(`Points: ${points}`, 150, 30);
text(`Time Left: ${timeLeft}s`, width - 150, 30);
}
}
function drawCurrentNote() {
if (currentNote) {
let yPosition = noteHeights[currentNote];
noFill();
strokeWeight(5);
ellipse(width / 2, yPosition, 45, 35);
}
}
function drawTutorial() {
drawStaff();
drawNoteImage();
if (tutorialComplete) {
noStroke();
fill(255);
textAlign(CENTER, CENTER);
textSize(24);
text("Tutorial Complete!", width / 2, height / 2 - 40);
fill(100);
rect(width / 2, height / 2 + 10, 150, 50, 10);
fill(255);
textSize(18);
text("Restart", width / 2, height / 2 + 10);
} else {
let currentNote = notes[currentNoteIndex];
let yPosition = noteHeights[currentNote];
let xPosition = noteWidths[currentNote];
stroke(255);
noFill();
strokeWeight(5);
ellipse(xPosition, yPosition, 45, 35);
fill(255);
textSize(24);
noStroke();
textAlign(CENTER, CENTER);
text(`Press the ${currentNote} note`, width / 2, height / 4);
}
}
function drawFreeplay() {
drawStaff();
drawPressedNotes();
drawVisualization();
drawNoteImage();
}
function drawStaff() {
stroke(255);
strokeWeight(1);
for (let i = -2; i <= 2; i++) {
line(0, height / 2 + i * spacing, width, height / 2 + i * spacing);
}
}
function drawPressedNotes() {
noFill();
strokeWeight(5);
stroke(255);
for (let note in pressedButtons) {
if (pressedButtons[note]) {
let yPosition = noteHeights[note];
ellipse(width / 2, yPosition, 45, 35);
}
}
}
function drawNoteImage() {
image(noteImg, width / 5 - newWidth, height / 2 - newHeight / 2);
}
function drawVisualization() {
push();
translate(width / 2, height / 2);
stroke(255);
noFill();
let wave = fft.waveform();
for (let t = -1; t <= 1; t += 2) {
beginShape();
for (let i = 0; i < 180; i += 0.5) {
let index = floor(map(i, 0, 180, 0, wave.length - 1));
let r = map(wave[index], -1, 1, 150, 300);
let x = r * sin(i) * t;
let y = r * cos(i);
vertex(x, y);
}
endShape();
}
let p = new Particle();
particles.push(p);
for (let i = particles.length - 1; i >= 0; i--) {
if (!particles[i].edges()) {
particles[i].update();
particles[i].show();
} else {
particles.splice(i, 1);
}
}
pop();
}
class Particle {
constructor() {
this.pos = p5.Vector.random2D().mult(225);
this.vel = createVector(0, 0);
this.acc = this.pos.copy().mult(random(0.0001, 0.0001));
this.w = random(3, 5);
}
update() {
this.vel.add(this.acc);
this.pos.add(this.vel);
}
edges() {
return (
this.pos.x < -width / 2 ||
this.pos.x > width / 2 ||
this.pos.y < -height / 2 ||
this.pos.y > height / 2
);
}
show() {
noStroke();
fill(255);
ellipse(this.pos.x, this.pos.y, this.w);
}
}
function mousePressed() {
if (currentMode === "start") {
if (mouseX > width / 2 - 125 && mouseX < width / 2 + 125 && mouseY > height / 2 - 35 && mouseY < height / 2 + 35) {
currentMode = "game";
startGame();
} else if (
mouseX > width / 2 - 250 &&
mouseX < width / 2 - 50 &&
mouseY > height / 2 + 75 &&
mouseY < height / 2 + 125
) {
currentMode = "tutorial";
} else if (
mouseX > width / 2 + 50 &&
mouseX < width / 2 + 250 &&
mouseY > height / 2 + 75 &&
mouseY < height / 2 + 125
) {
currentMode = "freeplay";
}
} else if (currentMode !== "start" && mouseX > 20 && mouseX < 70 && mouseY > 20 && mouseY < 70) {
currentMode = "start";
clearInterval(timer);
} else if (currentMode === "game" && gameOver) {
restartGame();
} else if (currentMode === "tutorial" && tutorialComplete) {
restartTutorial();
}
}
function startGame() {
points = 0;
timeLeft = 60;
gameOver = false;
timer = setInterval(() => {
if (timeLeft > 0) {
timeLeft--;
} else {
clearInterval(timer);
gameOver = true;
}
}, 1000);
pickNextNote();
}
function restartGame() {
clearInterval(timer);
startGame();
}
function restartTutorial() {
currentNoteIndex = 0;
tutorialComplete = false;
}
function pickNextNote() {
let availableNotes = notes.filter((note) => note !== previousNote);
currentNote = random(availableNotes);
previousNote = currentNote;
}
function keyPressed() {
if (key == " ") {
// Setup the serial communication when the space bar is pressed
setUpSerial();
}
if (key === 'f' || key === 'F') {
let fs = fullscreen();
fullscreen(!fs);
}
}
function readSerial(data) {
if (data != null) {
let fromArduino = split(trim(data), ",");
if (fromArduino.length == 1) {
let buttonIndex = int(fromArduino[0]);
if (buttonIndex >= 0 && buttonIndex < notes.length) {
let receivedNote = notes[buttonIndex];
if (currentMode === "game") {
checkGameNote(receivedNote, buttonIndex);
} else if (currentMode === "tutorial") {
checkTutorialNote(receivedNote);
} else if (currentMode === "freeplay") {
if (!pressedButtons[receivedNote]) {
pressedButtons[receivedNote] = true;
playSound(receivedNote);
}
}
} else if (buttonIndex === -1) {
if (currentMode === "freeplay") {
stopSound();
}
stopAllNotes();
}
}
}
}
function checkGameNote(receivedNote, buttonIndex) {
if (!pressedButtons[buttonIndex]) {
pressedButtons[buttonIndex] = true;
if (receivedNote === currentNote) {
points += 10;
playSound(currentNote);
pickNextNote();
} else {
points -= 5;
}
setTimeout(() => {
pressedButtons[buttonIndex] = false;
}, 500);
}
}
function checkTutorialNote(receivedNote) {
if (currentNoteIndex < notes.length) {
let currentNote = notes[currentNoteIndex];
if (receivedNote === currentNote) {
playSound(currentNote);
currentNoteIndex++;
if (currentNoteIndex >= notes.length) {
tutorialComplete = true;
}
}
}
}
function playSound(note) {
let frequencies = {
"E": 329.63,
"F": 349.23,
"G": 392.0,
"A": 440.0,
"B": 493.88,
"C": 523.25,
"D": 587.33,
"E2": 659.25,
"F2": 698.46,
};
let freq = frequencies[note];
osc.freq(freq);
osc.amp(0.5, 0.1);
isPlaying = true;
// Only set a timeout to stop the sound in non-freeplay modes
if (currentMode !== "freeplay") {
setTimeout(() => {
stopSound();
}, 500);
}
}
function stopSound() {
if (isPlaying) {
osc.amp(0, 0.1);
isPlaying = false;
}
}
function stopAllNotes() {
for (let note in pressedButtons) {
pressedButtons[note] = false;
}
stopSound();
}