Audio | Part 7 — Making Our Beat Sequencer Sexy with Blinged Out Styling

Live Demo

In this blog entry, we're going to add some flair ✨ to the beat sequencing app đŸŽĩ we built in our prior post. We're going to keep our focus 👀 mainly on the aesthetics 💎 of the application this time around 🔄, while keeping the heart ❤ī¸ of the app's logic untouched 🙌. We'll simply fine-tune 🎚ī¸ the markup and corresponding styling 💅 in order to make our app have a more DAW-like structure 🏛ī¸.

Refining the Look and Feel

A little bit of styling can turn a plain and simple application into a user-friendly, visually appealing one. Let's add that extra layer of polish to make our app stand out.

The entire code is pretty extensive, so let's break it down section by section. Here is a simplified overview of the HTML structure:

<html>
  <head>...</head>
  <body>
    <main>
      <!-- Top bar elements: master volume and control buttons, bpm display, & timing display -->
      <div id="top-bar">...</div>
      <!-- Track elements: enabled/disabled LED, volume, title and loading buton, & steps -->
      <div id="tracks">...</div>
    </main>
  </body>
</html>

Imports

<head>
  ...
  <link rel="stylesheet" href="/web-audio/007-style/css/index.css">
  <script src="/assets/lib/Tone/Tone.js" defer></script>
  <script type="module" src="/web-audio/007-style/index.js"></script>
</head>

We first link to the stylesheet (index.css) that will contain our CSS rules. We next include two JavaScript files. Tone.js is stored locally on the server in order to avoid unnecessary HTTP requests to a CDN. index.js will house the logic of our app. The defer attribute ensures that the Tone.js script doesn't block the HTML parsing and waits until the page is finished parsing to load. The type=module attribute implicitly sets the defer attribute on our second <script src="..."> tag, which ensures our code is synchronously loaded after Tone.js.

The Top Bar

<div id="top-bar">
  <!-- Master volume and control buttons -->
  <div class="master-knob-volume">...</div>
  <div class="master-controls">...</div>
  
  <!-- BPM and timing displays -->
  <div class="bpm-display">...</div>
  <div class="timing-display">...</div>
</div>

The top bar is where the master controls for our app reside. We have a master volume knob, play, stop, and pause buttons, along with a display for tempo (beats per minute), and a second display to render timing information.

The buttons use SVG's for icons, which makes them infinitely scalable without losing quality. The BPM and timing displays are composed of number displays and, in the case of BPM, up and down buttons for JavaScript based interactive adjustment.

The Tracks

<div id="tracks">
  <div class="track"> <!-- Track 1 -->
    <div class="track-led-enable">...</div>
    <div class="track-knob-volume">...</div>
    <button class="track-title">Hi-Hat</button>
    <div class="steps">
      <div class="step"></div>
      <div class="step"></div>
      .
      .
      .
    </div>
  </div> <!-- /Track 1 -->

  <div class="track"> <!-- Track 2 -->
    ...
  </div> <!-- /Track 2 -->

  <!-- More tracks... -->
</div>

The #tracks section represents the different tracks of the music. Each .track <div> contains an LED enable/disable button, a volume knob, a track title (which will eventually double as a load/edit sound sample button), and steps that signify a beat sequence where each .step <div> represents the sound sample corresponding to a given track (row) played (or not played) at different indices of time.

Styling

Our styling is rather straightforward, so we won't bother spending too much time explaining it. One thing to note is that we are now using SCSS in order to utilize some features not available to us in vanilla CSS. The code in the repo includes the compiled CSS. But if you want to compile it yourself, just make sure to output the compiled CSS into the same directory as index.scss. There are a few partials in the /css/scss directory that simply import the fonts and generate a Material Design drop shadow. All of the other styling is included directly in the main index.scss file.

Our .scss partial's utilize two interesting concepts worth discussing, namely native CSS @font-face and SCSS mixins.

@font-face {
  font-family: "Roboto";
  font-weight: 300;
  src: url("/assets/fonts/Roboto/woff/roboto-light-webfont.woff2") format("woff2"), url("/assets/fonts/Roboto/woff/roboto-light-webfont.woff") format("woff");
}
...

We use the @font-face rule to specify custom fonts for our web app. We declare five variations of the "Roboto" font, each with different font weights (300, 400, 500, 700, 900). The src property defines the path to each font file, stored locally on the server to avoid unnecessary HTTP requests to external CDN's such as Google Fonts. We include different formats for maximum compatibility. We use both WOFF2 and WOFF formats since they are both currently the most web-compatible formats.

We utilize an SCSS mixin to implement the Material Design elevation specification.

@mixin shadow($level) {
  @if $level == 1 { // Material level 1
    box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
  } @else if $level == 2 { // Material level 2
    box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
  }
  .
  .
  .
}

We can now use our mixin as follows, where $n is our elevation level.

@include shadow($n);

Logic

The logic of the app is mostly unchanged from what we built in our previous blog post. However, there are several modifications to update the new UI layout.

Most of the changes in the code from our previous app are related to referencing the new DOM nodes that correspond to our updated markup. The only major change is the addition of the changeBeatState function, which has been added to help organize the state changes related to our play/pause/stop features used in the callback functions passed to the event listeners on the corresponding UI buttons.

const changeBeatState = (next) => () => {
  if (next === 'play') {
    startBeat();
    // ...enable/disable UI buttons for playing state
  }

  if (next === 'stop') {
    stopBeat();
    // ...enable/disable UI buttons for stopped state
  }

  if (next === 'pause') {
    pauseBeat();
    // ...enable/disable UI buttons for paused state
  }
}

Note that we implement changeBeatState as a higher-order function, which is defined as a function that does at least one of the following:

  • takes one or more functions as arguments
  • returns a function as its result

changeBeatState takes next: 'play' | 'stop' | 'pause' as an arguement and returns a callback function which will be passed to addEventListener with the value for next stored as a closure.

start_btn.addEventListener('click', changeBeatState('play'));
stop_btn.addEventListener('click',  changeBeatState('stop'));
pause_btn.addEventListener('click', changeBeatState('pause'));

Additional features

Our new styled app has several features that we have not wired up yet. We'll add these features (among many others) in subsequent posts.

The list of features for the already built UI that are not wired up yet are:

  1. Master volume control (knob at top left)
  2. Enable / Disable track (green button on the left of each track): This button will essentially turn the track on and off. This feature will allow the user to focus on specific tracks by isolating some tracks while ignoring others. This feature will be indispensable for making fine grained tweaks to a given track once an audio project has evolved into a complex song comprised of tens or hundreds of tracks.
  3. Volume control for each track: This will adjust the volume of the specified track.
  4. Modify the audio sample for each track (the button with the name of the audio sample): The user will click this button to open a new window where they can replace the audio sample with a different sample (including uploading a new sample) or modify the attributes of the already loaded audio sample for the specified track.

In addition to these features that we'll build out in the next several posts there are many more features planned!

Conclusion

And there you have it! 🎉 We've transformed our beat sequencer app from having a boring UI to being somewhat blinged out 💎. The makeover has brought not just changes in looks 👀 but also an improved user experience 🕹ī¸, all while keeping the heart of the app's functionality intact.

In upcoming blog posts, we'll be extending the app by adding exciting new features and functionalities! 🚀 đŸ’Ĩ

Code download