let GRADIENT_TOP, GRADIENT_BOTTOM;
let BANNER_COLOR, CARD_COLOR, TEXT_COLOR, SUBTEXT_COLOR, ACCENT_COLOR, LINE_COLOR;
artistInfo: "Known for her genre-bending sound and poetic lyricism...",
lyrics: `Hey, boy, I'ma get ya...`,
title: "Sweetest Obsession",
artistInfo: "Known for her genre-bending sound and poetic lyricism...",
lyrics: `Boy, you caught my eyes...`,
artistInfo: "A versatile artist whose gentle yet powerful voice touches hearts.",
lyrics: `자꾸만 시선이 마주치고...`,
title: "I Can't Stop Me",
artistInfo: "A bold storyteller who explores the complexities of human emotions.",
lyrics: `Why is it so hard to resist?...`,
title: "Please Please Please",
artistInfo: "A collaboration between two artists blending their unique styles.",
lyrics: `I know I have good judgement...`,
{ title: "Diamond Nights" },
{ title: "Secret Lover" },
{ title: "Moonlight Road" },
let currentTrackIndex = -1;
tracks[0].file = loadSound("Strategy1.mp3");
tracks[1].file = loadSound("Sweetest Obsession (1).mp3");
tracks[2].file = loadSound("Keeper.mp3");
tracks[3].file = loadSound("I Cant stop me.mp3");
tracks[4].file = loadSound("Please Please Please.mp3");
tracks[0].cover = loadImage("Lana Album Cover.png");
for (let i = 1; i < tracks.length; i++) {
tracks[i].cover = loadImage("Lana Album Cover.png");
createCanvas(CANVAS_W, CANVAS_H);
GRADIENT_TOP = color(60, 100, 160);
GRADIENT_BOTTOM = color(130, 80, 180);
BANNER_COLOR = color(255, 255, 255, 25);
CARD_COLOR = color(255, 255, 255, 15);
SUBTEXT_COLOR = color(200);
ACCENT_COLOR = color(255, 195, 0);
LINE_COLOR = color(255, 255, 255, 40);
fft = new p5.FFT(0.9, 64);
trackList = new TrackList(tracks);
playerOverlay = new PlayerOverlay();
setGradientDiagonal(0, 0, CANVAS_W, CANVAS_H, GRADIENT_TOP, GRADIENT_BOTTOM);
drawRotatingBackground();
} else if (state === "player") {
} else if (state === "visualizer") {
} else if (state === "artistProfile") {
} else if (state === "poll") {
function drawRotatingBackground() {
translate(width / 2, height / 2);
rotate(frameCount * 0.001);
ellipse(0, 0, width * 1.5, width * 1.5);
function mousePressed() {
let clickedIndex = trackList.getClickedTrackIndex();
if (clickedIndex !== -1) {
else if (state === "player") {
let clickedButton = playerOverlay.getClickedButton();
if (clickedButton === "playPause") {
} else if (clickedButton === "next") {
} else if (clickedButton === "prev") {
} else if (clickedButton === "close") {
} else if (clickedButton === "seek") {
playerOverlay.seekTrack();
} else if (clickedButton === "visualizer") {
} else if (clickedButton === "artist") {
else if (state === "visualizer") {
if (dist(mouseX, mouseY, backX, backY) < backSize / 2) {
else if (state === "artistProfile") {
let dBack = dist(mouseX, mouseY, backX, backY);
if (dBack < backSize / 2) {
let pollY = CANVAS_H - 260;
let pollBtnY = pollY + 70;
let pollBtnW = width - 40;
mouseX < pollBtnX + pollBtnW &&
mouseY < pollBtnY + pollBtnH
else if (state === "poll") {
let dBack = dist(mouseX, mouseY, backX, backY);
if (dBack < backSize / 2) {
for (let i = 0; i < newSongs.length; i++) {
let boxY = startY + i * (boxHeight + 30);
mouseX < width - xPadding &&
mouseY < boxY + boxHeight
let chosenSong = newSongs[i].title;
alert("You voted for '" + chosenSong + "' as the next title track!");
function mouseWheel(event) {
if (state === "player") {
playerOverlay.scrollY -= event.delta;
playerOverlay.scrollY = constrain(
-(playerOverlay.contentHeight - height),
function playTrack(index) {
if (currentTrackIndex !== -1 && tracks[currentTrackIndex].file.isPlaying()) {
tracks[currentTrackIndex].file.stop();
currentTrackIndex = index;
tracks[index].file.play();
fft.setInput(tracks[index].file);
function togglePlayPause() {
let track = tracks[currentTrackIndex];
if (track.file.isPlaying()) {
let nextIndex = (currentTrackIndex + 1) % tracks.length;
let prevIndex = currentTrackIndex - 1;
if (prevIndex < 0) prevIndex = tracks.length - 1;
function displayMiniPlayerBar() {
if (currentTrackIndex === -1) return;
rect(0, height - 70, width, 70);
let track = tracks[currentTrackIndex];
text(track.title, 20, height - 50);
text(track.artist, 20, height - 30);
let btnPrevX = width - 180;
let btnPlayX = width - 120;
let btnNextX = width - 60;
ellipse(btnPrevX, btnY, buttonSize, buttonSize);
textAlign(CENTER, CENTER);
text("«", btnPrevX, btnY + 1);
ellipse(btnPlayX, btnY, buttonSize, buttonSize);
if (track.file.isPlaying()) {
text("❚❚", btnPlayX, btnY + 1);
text("►", btnPlayX, btnY + 1);
ellipse(btnNextX, btnY, buttonSize, buttonSize);
text("»", btnNextX, btnY + 1);
function checkMiniPlayerClicks() {
if (currentTrackIndex === -1) return;
let btnPrevX = width - 180;
let btnPlayX = width - 120;
let btnNextX = width - 60;
let dPrev = dist(mouseX, mouseY, btnPrevX, btnY);
let dPlay = dist(mouseX, mouseY, btnPlayX, btnY);
let dNext = dist(mouseX, mouseY, btnNextX, btnY);
if (dPrev < buttonSize / 2) {
} else if (dPlay < buttonSize / 2) {
} else if (dNext < buttonSize / 2) {
this.displayHeroSection();
this.displaySectionTitle("Artist:", 40);
text("Lana", this.xPadding, 80);
text("Strategy - First EP album", this.xPadding, 120);
let cover = tracks[0].cover;
image(cover, width - 80, 100, 90, 90);
displaySectionTitle(title, y) {
text(title, this.xPadding, y);
for (let i = 0; i < this.tracks.length; i++) {
let y = this.yStart + i * spacing;
rect(this.xPadding, y, width - this.xPadding * 2, cardH, 10);
if (this.tracks[i].cover) {
image(this.tracks[i].cover, this.xPadding + 10, y + 10, coverSize, coverSize);
text(this.tracks[i].title, this.xPadding + coverSize + 20, y + 15);
text(`${this.tracks[i].artist} • ${this.tracks[i].plays} plays`,
this.xPadding + coverSize + 20,
for (let i = 0; i < this.tracks.length; i++) {
let y = this.yStart + i * spacing;
mouseX > this.xPadding &&
mouseX < width - this.xPadding &&
this.closeButton = { x: 60, y: 45, size: 30 };
this.visualizerButton = { x: CANVAS_W - 60, y: 45, size: 30 };
this.artistButton = { x: CANVAS_W / 2, y: 45, size: 30 };
this.coverSize = CANVAS_W - 220;
this.progressBar = { x: 30, y: 520, w: CANVAS_W - 60, h: 6 };
this.playPauseButton = { x: CANVAS_W / 2, y: 600, size: 60 };
this.nextButton = { x: CANVAS_W / 2 + 100, y: 600, size: 50 };
this.prevButton = { x: CANVAS_W / 2 - 100, y: 600, size: 50 };
this.contentHeight = CANVAS_H * 2;
let track = tracks[currentTrackIndex];
translate(0, this.scrollY);
setGradientDiagonal(0, 0, width, CANVAS_H * 2.5, GRADIENT_BOTTOM, GRADIENT_TOP);
this.displayCoverAndInfo(track);
this.displayProgressBar(track);
this.displayControls(track);
line(20, 650, width - 20, 650);
this.displayLyricsAndInfo(track, 670);
circle(this.closeButton.x, this.closeButton.y, this.closeButton.size);
textAlign(CENTER, CENTER);
text("X", this.closeButton.x, this.closeButton.y + 1);
circle(this.visualizerButton.x, this.visualizerButton.y, this.visualizerButton.size);
textAlign(CENTER, CENTER);
text("V", this.visualizerButton.x, this.visualizerButton.y + 1);
circle(this.artistButton.x, this.artistButton.y, this.artistButton.size);
textAlign(CENTER, CENTER);
text("A", this.artistButton.x, this.artistButton.y + 1);
displayCoverAndInfo(track) {
ellipse(width / 2, this.coverY + this.coverSize / 2, this.coverSize * 1.3, this.coverSize * 1.3);
image(track.cover, width / 2, this.coverY, this.coverSize, this.coverSize);
textAlign(CENTER, CENTER);
text(track.title, width / 2, this.coverY + this.coverSize / 2 + 40);
text(track.artist, width / 2, this.coverY + this.coverSize / 2 + 70);
displayProgressBar(track) {
let currentTime = track.file.currentTime();
let totalTime = track.file.duration();
rect(this.progressBar.x, this.progressBar.y, this.progressBar.w, this.progressBar.h, 3);
let w = map(currentTime, 0, totalTime, 0, this.progressBar.w);
rect(this.progressBar.x, this.progressBar.y, w, this.progressBar.h, 3);
text(formatTime(currentTime), this.progressBar.x, this.progressBar.y + 20);
textAlign(RIGHT, CENTER);
text(formatTime(totalTime), this.progressBar.x + this.progressBar.w, this.progressBar.y + 20);
circle(this.playPauseButton.x, this.playPauseButton.y, this.playPauseButton.size);
textAlign(CENTER, CENTER);
if (track.file.isPlaying()) {
text("❚❚", this.playPauseButton.x, this.playPauseButton.y + 2);
text("►", this.playPauseButton.x, this.playPauseButton.y + 2);
fill(255, 255, 255, 100);
circle(this.nextButton.x, this.nextButton.y, this.nextButton.size);
textAlign(CENTER, CENTER);
text("»", this.nextButton.x, this.nextButton.y + 1);
fill(255, 255, 255, 100);
circle(this.prevButton.x, this.prevButton.y, this.prevButton.size);
textAlign(CENTER, CENTER);
text("«", this.prevButton.x, this.prevButton.y + 1);
displayLyricsAndInfo(track, startY) {
text("About the Artist", 20, startY);
text(track.artistInfo, 20, startY + 30, width - 40, 120);
line(20, startY + 160, width - 20, startY + 160);
text("Lyrics", 20, startY + 180);
text(track.lyrics, 20, startY + 210, width - 40, 800);
let my = mouseY - this.scrollY;
let dClose = dist(mouseX, my, this.closeButton.x, this.closeButton.y);
if (dClose < this.closeButton.size / 2) return "close";
let dViz = dist(mouseX, my, this.visualizerButton.x, this.visualizerButton.y);
if (dViz < this.visualizerButton.size / 2) return "visualizer";
let dArt = dist(mouseX, my, this.artistButton.x, this.artistButton.y);
if (dArt < this.artistButton.size / 2) return "artist";
let dPP = dist(mouseX, my, this.playPauseButton.x, this.playPauseButton.y);
if (dPP < this.playPauseButton.size / 2) return "playPause";
let dNext = dist(mouseX, my, this.nextButton.x, this.nextButton.y);
if (dNext < this.nextButton.size / 2) return "next";
let dPrev = dist(mouseX, my, this.prevButton.x, this.prevButton.y);
if (dPrev < this.prevButton.size / 2) return "prev";
let bx1 = this.progressBar.x;
let bx2 = this.progressBar.x + this.progressBar.w;
let by1 = this.progressBar.y - 10;
let by2 = this.progressBar.y + 20;
if (mouseX > bx1 && mouseX < bx2 && my > by1 && my < by2) {
let track = tracks[currentTrackIndex];
let totalTime = track.file.duration();
let seekPos = map(mouseX, this.progressBar.x, this.progressBar.x + this.progressBar.w, 0, totalTime);
track.file.jump(seekPos);
function displayVisualizerTab() {
circle(backX, backY, backSize);
textAlign(CENTER, CENTER);
text("←", backX, backY + 1);
textAlign(CENTER, CENTER);
text("Visualizer Tab", width / 2, 60);
let spectrum = fft.analyze();
let barCount = spectrum.length;
let barWidth = width / barCount;
translate(0, height - 50);
for (let i = 0; i < barCount; i++) {
let barHeight = map(amp, 0, 255, 0, height - 100);
rect(i * barWidth, 0, barWidth - 2, -barHeight);
function displayArtistProfile() {
circle(backX, backY, backSize);
textAlign(CENTER, CENTER);
text("←", backX, backY + 1);
textAlign(CENTER, CENTER);
text("Artist Profile", width / 2, 60);
let track = tracks[currentTrackIndex];
text(track.artist, width / 2, 120);
"Welcome to the official artist page!\nCheck out popular releases and participate in our exclusive poll!",
text("Popular Songs", 40, 300);
for (let i = 0; i < tracks.length; i++) {
if (tracks[i].artist.includes(track.artist)) {
text("- " + tracks[i].title + " (" + tracks[i].plays + ")", 40, yPos);
let pollY = CANVAS_H - 260;
text("Fan Poll: Vote for your favorite new track!", 40, pollY);
text("Click here to help choose the next title track!", 40, pollY + 30, width - 80);
rect(20, pollY + 70, width - 40, 50, 10);
textAlign(CENTER, CENTER);
text("Vote Now", width / 2, pollY + 95);
function displayPollPage() {
circle(backX, backY, backSize);
textAlign(CENTER, CENTER);
text("←", backX, backY + 1);
textAlign(CENTER, CENTER);
text("New Album Poll", width / 2, 60);
"Help choose the next album's title track.\nClick a song below to cast your vote!",
for (let i = 0; i < newSongs.length; i++) {
let boxY = startY + i * (boxHeight + 30);
rect(xPadding, boxY, width - xPadding * 2, boxHeight, 10);
textAlign(CENTER, CENTER);
text(newSongs[i].title, width / 2, boxY + boxHeight / 2);
function formatTime(seconds) {
if (isNaN(seconds)) return "0:00";
let m = floor(seconds / 60);
let s = floor(seconds % 60);
function setGradientDiagonal(x, y, w, h, c1, c2) {
for (let i = 0; i < h; i++) {
let inter = map(i, 0, h, 0, 1);
let c = lerpColor(c1, c2, inter);
line(x, y + i, x + w, y + i);