When we where told to look into new kinds of technology, there was so many options to pick. Originally I would of wanted to look into AR however due to the industry software needed and the lack of a up to date device to run it I was unable to.
I had an idea of wanting to see how I could gather data of a found song, and start using said data to create art unique to each specific input made. To do this I would have to do a lot of complicated coding that I would have no idea how to do, so to do this I had no choice than to turn to AI.
To start I had to find a way to gather a songs information. An "easy" way to do this would be to use Spotify's developer API, or an external plugin. However, since I had no clue what to do with any of this I had to ask the AI, in which case it constantly outputted the same answers or gave me a link to a non descript GITHUB page with little to no detail on how to download the addon.
This would mean I could not gather the data I needed to create what I wanted, HOWEVER I was still able to attempt to make my own way of gathering this data, with a prototype that would sometimes work and sometimes not (we'll get to why later). Throughout much trial and error, the AI eventually gave me the code for a system that would:
- Ask the user for a song and band
- Travel to an external website with this information and try to find it
- Take the BPM and use it to create a pulsing circle thats speed was the BPM, and max width was set by the length of the song
The problem. The external site I was using did have a search bar, but it would come up with a list of different songs with the same name and the code wouldn't be able to figure out which to use. So, looking at the different pages for songs I eventually figured out the way their URLs worked. Choosing to display websitename/band-name/song-name all with these dashes for spaces, therefore I could make the code fill in the song and band inside the URL, it would just mean the user would have to use a dash not a space, since trying to turn spaces into dashes didn't work out.
Once again, HOWEVER. The site has some anomalies with this URL pattern, as some songs have a random string of numbers at the end of their URL with no way to figure out which ones would OR what the string would be. This just had to mean for the basic prototype that some songs would not work.
The code for the basic prototype can be
found below. Running it requires installation
of "Programming" and "Jsoup's Java Plugin"
float circleSize = 50; // Initial size of the circle
float maxSize = 150; // Maximum size of the circle (in cm, will be adjusted based on song length)
float minSize = 0; // Minimum size of the circle (fully vanishing)
boolean growing = true; // Flag to track if the circle is growing or shrinking
float growthSpeed = 1; // Default speed, adjusted by BPM
float bpm = 120; // Example BPM, replace this with actual BPM (e.g., from a song)
float timePerBeat; // Time per beat in seconds
String artist = "MODERN-BASEBALL"; // Example artist, replace with actual data
String song = "TEARS-OVER-BEERS"; // Example song, replace with actual data
float songLengthInSeconds = 0; // Total length of the song in seconds
float textOffset = 40; // Distance between the maximum size of the circle and the text
// Variables for the vertical progress bar
float progressBarX;
float progressBarWidth = 20; // Set a fixed width for the progress bar
float progressBarHeight;
void setup() {
size(1000, 1000); // Set the size of the window
noStroke(); // Remove the outline of the circle
// Prompt the user to enter the artist and song
artist = prompt("Enter the artist name (no capitals, replace spaces with hyphens):");
song = prompt("Enter the song name (no capitals, replace spaces with hyphens):");
if (artist == null || song == null || artist.isEmpty() || song.isEmpty()) {
println("Artist or song name missing. Exiting...");
exit(); // Stop the sketch if input is invalid
}
// Ensure no uppercase letters and replace spaces with hyphens
artist = artist.toLowerCase().replace(" ", "-");
song = song.toLowerCase().replace(" ", "-");
// Construct the URL in the format: https://songbpm.com/@{artist}/{song}
String url = "https://songbpm.com/@" + urlencode(artist) + "/" + urlencode(song);
// Display the song details in a formatted way
displaySongInfo(artist, song, url);
}
void displaySongInfo(String artist, String song, String url) {
// Display the artist and song in a more readable format
println("\n=== Song Information ===");
println("Artist: " + artist.replace("-", " ").toUpperCase()); // Remove hyphens and uppercase
println("Song: " + song.replace("-", " ").toUpperCase()); // Remove hyphens and uppercase
// Scrape song details from the constructed URL
getSongDetails(url);
}
void getSongDetails(String url) {
try {
// Connect to the song's details page with simulated human headers
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
.referrer("https://www.google.com")
.header("Accept-Language", "en-US,en;q=0.9")
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.header("Connection", "keep-alive")
.get();
// Scrape the "Song Metrics" section containing Key, Length, and BPM
Elements songMetrics = doc.select("dd.mt-1.text-3xl.font-semibold.text-card-foreground"); // This should contain "D 4:43 135" type values
if (!songMetrics.isEmpty()) {
// Extract the full string, e.g. "D 4:43 135"
String songDetails = songMetrics.text();
// Split the string into its components
String[] details = songDetails.split(" ");
if (details.length == 3) {
// Assuming the format is: Key (first), Song Length (middle), BPM (last)
String key = details[0]; // "D"
String songLength = details[1]; // "4:43"
String bpmString = details[2]; // "135"
// Convert BPM to integer
bpm = Integer.parseInt(bpmString);
// Replace 'b' with ♭ and '#' with ♯ in the key
key = key.replace("b", "♭").replace("#", "♯");
// Print the extracted details
println("\n=== Song Metrics ===");
println("Key: " + key);
println("Song Length: " + songLength);
println("BPM: " + bpm);
// Calculate the time per beat and adjust growth speed
timePerBeat = 60.0 / bpm;
growthSpeed = (maxSize - minSize) / (timePerBeat * 30); // Adjust based on 30 fps
// Convert song length (e.g., "4:43") to seconds
songLengthInSeconds = convertToSeconds(songLength);
// Set max size based on song length in seconds
maxSize = songLengthInSeconds; // One second = one centimeter, so maxSize in cm is song length in seconds
println("Song length in seconds: " + songLengthInSeconds);
} else {
println("\nSong details are in an unexpected format.");
}
} else {
println("\nSong details not found.");
}
} catch (Exception e) {
println("Error fetching song details: " + e.getMessage());
}
}
// Helper function to convert song length (e.g., "4:43") to total seconds
float convertToSeconds(String songLength) {
String[] timeParts = songLength.split(":");
int minutes = Integer.parseInt(timeParts[0]);
int seconds = Integer.parseInt(timeParts[1]);
return (minutes * 60) + seconds;
}
// Helper function to URL encode input
String urlencode(String input) {
try {
return java.net.URLEncoder.encode(input, "UTF-8");
} catch (Exception e) {
println("Error encoding URL: " + e.getMessage());
return "";
}
}
// Helper function to display a popup dialog for user input
String prompt(String message) {
return javax.swing.JOptionPane.showInputDialog(null, message);
}
void draw() {
background(255); // Set background to white
// Calculate the position of the text above the circle's maximum size
float textY = height / 2 - maxSize / 2 - textOffset;
// Draw the pulsing circle
fill(255, 0, 0, 150); // Set color to semi-transparent red
ellipse(width / 2, height / 2 + 50, circleSize, circleSize); // Draw the circle at the center
// Update circle size for pulsing effect
if (growing) {
circleSize += growthSpeed; // Increase size based on the calculated growth speed
if (circleSize >= maxSize) { // Max size reached
growing = false; // Switch to shrinking
}
} else {
circleSize -= growthSpeed; // Decrease size
if (circleSize <= minSize) { // Min size reached (vanishing)
growing = true; // Switch to growing
}
}
// Display the artist and song name above the circle at the stationary position
textSize(24);
fill(0); // Set color to black
textAlign(CENTER, CENTER);
text(artist.replace("-", " ").toUpperCase() + " - " + song.replace("-", " ").toUpperCase(), width / 2, textY);
// --- Vertical progress bar ---
progressBarX = width - progressBarWidth - 50; // Set the X position of the progress bar (50px distance from the right edge)
progressBarHeight = maxSize; // Set the height of the progress bar based on song length
// Draw the progress bar background
fill(200); // Light gray color for the background
rect(progressBarX, height / 2 - progressBarHeight / 2, progressBarWidth, progressBarHeight);
// Calculate the filled height of the progress bar (based on elapsed time)
float progressBarFilledHeight = map(millis() / 1000.0, 0, songLengthInSeconds, 0, progressBarHeight);
// Draw the filled part of the progress bar
fill(0, 255, 0); // Green color for the filled part
rect(progressBarX, height / 2 - progressBarHeight / 2 + progressBarHeight - progressBarFilledHeight, progressBarWidth, progressBarFilledHeight);
}