• Creating A BPM Finder Using the Spotify API

    Creating A BPM Finder Using the Spotify API

    You can find the finished bpm finder here.

    This post details the process of creating a BPM (Beats Per Minute) finder using the Spotify API and JavaScript. By inputting a song name into the search bar, the tool provides the top result and gives the estimated BPM. It’s a tool useful for musicians, producers or DJs, which saves time in finding the tempo of any song they are working with.

    Setting up API access

    Spotify provides a public REST API that gives information about their music library, such as track information and song features. By authenticating an individual Spotify account it’s also possible to manage playlists and control account playback.

    To access it, first I created an app on the Spotify developer dashboard.

    Once created, Spotify provides a ‘client ID’ and ‘client secret’ to allow access to their library. Their documentation provides a detailed step by step guide to setting it up.

    For this project, as I’m accessing publicly available data, I have chosen to use the ‘client credentials’ authentication flow as this flow allows the end user to use the tool without authenticating their Spotify account.

    Testing the API in Postman

    To check that the API is working correctly with the credentials I’ve been given, I performed some test API requests in Postman. Postman is a free API platform that allows you to make API requests. I used this tool to determine how the response data was structured.

    Choosing the end points

    The Spotify API has a number of endpoints which allow you to retrieve different information. For this project, I’ve chosen the following ones:

    Retrieve Access Token

    Endpoint: https://accounts.spotify.com/api/token

    This is used to retrieve an access token to authorize requests. The access token can be obtained by sending the client secret and client ID provided to this endpoint.

    Search for Item

    Endpoint: https://api.spotify.com/v1/search

    By sending a query string, along with the access token, to this endpoint we can search for tracks in the Spotify database. This will be used to retrieve the song ID of the top search result.

    Get Track’s Audio Features

    Endpoint: https://api.spotify.com/v1/audio-features/{id}

    By adding a Spotify song ID to the endpoint URL, we can retrieve information about the song such as danceability, loudness or tempo. This will be used to retrieve the estimated song BPM.

    Creating The Website

    To create the BPM finder, I’ve embedded the code into my WordPress site. The page is written with a combination of HTML, Javascript and CSS.

    HTML

    On a blank webpage, inside a code snippet, I created a div to insert all the elements of the finder. I used Meta’s Imagine Image Generator to create an piece for the site, based on the theme ‘bpm finder’. After a few iterations, I settled on the below image:

    The HTML structure:

    <div id="container">
      <img
        src="https://seanprailton.com/wp-content/uploads/2024/01/BPM-finder_-line-art-removebg-preview.png"
        alt=""
      />
      <h1>bpm finder</h1>
      <div class="name">By Sean Railton</div>
      <div class="spacer"></div>
      <div class="search-container" id="search-container">
        <input type="text" id="songSearch" placeholder="Enter a song name..." />
        <div class="spacer"></div>
        <button onclick="displayTopResult()" class="SEARCH">Search</button>
      </div>
    </div>

    JavaScript

    The functionality of the tool is then created with JavaScript.

    Assigning Variables

    First I’ve declared the main variables which will be used in tool.

      const container = document.getElementById("container");
      let songID;
      let accesstoken;
      let bpmResult;
      let searchResultsArray;
      let searchTerm;
      let artistName;
      let songName;
    
    

    Handling User Input

    To ensure that the search term inputted by the user is assigned to the variable ‘searchTerm’ by the time that it is used, I’ve added an event listener that updates the searchTerm variable after each time character is typed in the search box.

      const searchTermInput = document.getElementById("songSearch");
      if (searchTermInput) {
        searchTermInput.addEventListener("input", function (event) {
          searchTerm = event.target.value;
        });
      } else {
        console.error("the search input not found");
      }

    Token Retrieval

    To keep the client ID and client secret hidden, the token retrieval request is called from the backend using PHP. The access token is then returned to the front end, parsed as JSON and assigned to the variable ‘accesstoken’

    Token Retrieval (front end)

      function getToken() {
        return fetch("/Website_dependencies/Spotify BPM Finder v13 PHP.php")
          .then((response) => response.json())
          .then((data) => {
            accesstoken = data.access_token;
            return accesstoken;
          })
          .catch((error) => {
            console.error("Error fetching access token:", error);
          });
      }

    Token Retrieval (Back End)

    <?php
    
    $clientID = "YOUR_CLIENT_ID";
    $clientSecret = "YOUR_CLIENT_SECRET";
    
    $data = array(
        'grant_type' => 'client_credentials'
    );
    
    $encodedCredentials = base64_encode($clientID . ":" . $clientSecret);
    
    $ch = curl_init();
    
    curl_setopt($ch, CURLOPT_URL, "https://accounts.spotify.com/api/token");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        "Content-Type: application/x-www-form-urlencoded",
        "Authorization: Basic $encodedCredentials"
    ));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    echo $response;
    ?>
    

    Retrieving Search Results

    The below function retrieves the first search result based on the what the end user has entered. It works by first running the getToken function and using that access code to send a request to the Spotify search endpoint. The result is then parsed as JSON and the variables for artistName, songName and songID are assigned from the first object in the array of tracks that is returned.

    function getTopResult() {
        return getToken()
          .then(() => {
            return fetch(
              `https://api.spotify.com/v1/search?q=${searchTerm}&type=track&limit=5`,
              {
                method: "GET",
                headers: {
                  Authorization: `Bearer ${accesstoken}`,
                },
              }
            );
          })
          .then((response) => {
            return response.json();
          })
          .then((jsonresponse) => {
            searchResultsArray = jsonresponse.tracks.items;
            artistName = searchResultsArray[0].artists[0].name;
            songName = searchResultsArray[0].name;
            songID = searchResultsArray[0].uri.split(":").pop();
            return searchResultsArray[0];
          });
      }

    Retrieving Tempo Information

    To obtain the BPM, the below function sends a GET request with the song ID that was assigned in the previous function to the audio-features endpoint. The result is parsed as JSON and the ‘tempo’ property is retrieved, rounded and assigned to the variable bpmResult.

      function getBPM() {
        return fetch(`https://api.spotify.com/v1/audio-features/${songID}`, {
          method: "GET",
          headers: {
            Authorization: `Bearer ${accesstoken}`,
          },
        })
          .then((featuresresponse) => featuresresponse.json())
          .then((featuresjsondata) => {
            bpmResult = Math.round(featuresjsondata.tempo);
            return bpmResult;
          });
      }

    Displaying The Results

    The below function is set to run whenever the ‘enter’ key is pressed or the ‘search’ button is clicked on. It begins by clearing all the HTML in the div, calls both the functions getTopResult and getBPM and then creates new HTML displaying the results.

    The metronome is also initialized at this point (see details in the next section).

      function displayTopResult() {
        container.innerHTML = "";
        getTopResult()
          .then(() => {
            return getBPM();
          })
          .then(() => {
         container.innerHTML = `
                <p class="results">
                  <span class="song-name">${songName}</span>
                  <span class="by"> by </span>
                  <span class="artist-name">${artistName}</span><br><br>
                  <span id="bpmm" class="bpm">${bpmResult}</span>
                  <span class="by2">bpm</span>
                  <br>
                  <span id="bpmmm" class="metronome"> metronome paused<br> (press the big number to start it)</span>
                  <br>
                  <br>
                  <br>
                  <button onclick="location.reload()" class="startOver">Start Over</button>
                  </p>
        `;
                  initializeMetronome();
          });
      }

    The function is called when pressing the enter key by adding an event listener at the bottom of the code:

      searchTermInput.addEventListener("keydown", function (event) {
        if (event.key === "Enter") {
          displayTopResult();
        }
      });

    Adding a Metronome

    As an extra touch I created a metronome that plays when you touch the number. This allows you to test out the tempo and play along to it straight away. I created it using a recording of a single drum hit, which then triggers repeatedly at a certain interval based on the outcome of the search.

    It’s achieved using a number of functions that are housed within a parent function ‘initializeMetronome’ which is called at the same point that the search results are displayed.

    function initializeMetronome() {
        let intervalId;
        let isPlaying = false;
        let metronomeBuffer; // Variable to store the loaded audio buffer
        let audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const startStopButton = document.getElementById("bpmm");
    
        function startMetronome() {
          isPlaying = true;
          playMetronomeSound(); // Start the initial buffer playback instance
    
          intervalId = setInterval(playMetronomeSound, 60000 / bpmResult);
          document.getElementById("bpmmm").innerHTML =
            "metronome playing <br> (press the big number to stop it)";
        }
    
        function stopMetronome() {
          isPlaying = false;
          clearInterval(intervalId);
          document.getElementById("bpmmm").innerHTML =
            "metronome paused<br> (press the big number to start it)";
        }
    
        function loadMetronomeSound() {
          return fetch(
            "/Website_dependencies/Spotify BPM Finder v17 PHP.php?action=getMetronome"
          )
            .then((response) => {
              console.log("Response:", response);
              return response.arrayBuffer();
            })
            .then((arrayBuffer) => {
              console.log("Array Buffer:", arrayBuffer);
              return audioContext.decodeAudioData(arrayBuffer);
            })
            .then((buffer) => {
              console.log("Audio Buffer:", buffer);
              metronomeBuffer = buffer;
            })
            .catch((error) => {
              console.error("Error loading metronome sound:", error);
            });
        }
        function playMetronomeSound() {
          let source = audioContext.createBufferSource();
          source.buffer = metronomeBuffer;
          source.connect(audioContext.destination);
          source.start();
        }
    
        loadMetronomeSound();
    
        startStopButton.addEventListener("click", toggleMetronome);
    
        function toggleMetronome() {
          if (!isPlaying) {
            startMetronome();
          } else {
            stopMetronome();
          }
        }
      }

    I opted to load the sound via an API request using the fetch function and a buffer, rather than loading it straight as when playing the metronome on a mobile device issues with latency would cause the tempo to play incorrectly. Additionally the metronome sound file is loaded from the PHP file which sits on the server to avoid sharing the API key.

    Styling

    Finally, I styled the elements with CSS to make the tool visually appealing. Elements such as the search container, buttons, and result display are animated for a smoother user experience. The use of keyframes in CSS provides a pleasant fade-in effect and adds a rotating animation to the BPM display.

    You can find the CSS used here.

    Summary

    In summary, this BPM finder uses the Spotify API and JavaScript to create an interactive and visually appealing user interface. The CSS styling enhances the overall experience, and the JavaScript code efficiently handles user input, API calls, result display and creation of a metronome.

    Feel free to customize or use the code as you please in your own projects!