Building a Reusable Music Component in Astro using ABCjs

Creating an Astro Music Component with ABCjs

I recently built a reusable Astro component that integrates ABCjs, allowing me to easily include music notation in any post or page on my Astro website.

With ABCjs, a versatile JavaScript library, you can easily render beautiful sheet music directly in the browser using ABC notation—a specialized, plain-text format for music. Not only can you visually render notation, but you can also integrate audio playback with synthesized audio.

Creating the AbcjsPlayer.astro

To create the reusable Music Component, I started with a new Astro component called AbcjsPlayer.astro. Here’s a detailed breakdown of the key steps involved:

Step 0: Define the Component Props

To create the Astro component with ABCjs, you’ll first need to install the required dependencies:

npm install abcjs

Then, in your Astro component file (AbcjsPlayer.astro), import the necessary modules and define the component props:

// AbcjsPlayer.astro
import { renderAbc, synth } from 'abcjs';
import 'abcjs/abcjs-audio.css'; // Import the required ABCjs audio CSS
 
const { notation, showControls = true, responsive = true } = Astro.props;
Make sure to install the ABCjs library using npm install abcjs before proceeding. You won’t be able to render music or use audio features without it!

Step 1: Define the Component Props

The first step to creating this component is to define the properties it will take. These include the notation, an optional showControls flag for playback buttons, and responsive behavior.

// AbcjsPlayer.astro
interface Props {
  notation: string;
  showControls?: boolean;
  responsive?: boolean;
}

const { 
  notation,
  showControls = true,
  responsive = true
} = Astro.props;
Both showControls and responsive props are optional. By default, the controls are enabled (showControls = true), and the component is responsive (responsive = true). You can omit these props if you're satisfied with the defaults.

Step 2: Build the HTML Structure for Interactive Music Rendering

For rendering music seamlessly, we need two separate elements inside the abcjs-container:

  1. #paper to render the sheet music notation.
  2. #audio to handle music controls like play and pause.

This structure keeps the visual and interactive elements separate, making them easier to manage and style:

<div class="abcjs-container" data-notation={notation}>
  <div id="paper"></div>
  {showControls && <div id="audio"></div>}
</div>

The data-notation attribute contains the ABC notation format that you want to render. Both paper and audio will be dynamically updated later by the script.

Step 3: Adding ABCjs Rendering Logic

Next, I imported ABCjs and ensured that it runs only on the client-side (window !== ‘undefined’). I used ABCjs’s API to render sheet music and synthesize audio playback.

<script>
  if (typeof window !== 'undefined') {
    import('abcjs').then((abcjs) => {
      function initializeAbcjs() {
        const containers = document.querySelectorAll('.abcjs-container');
        
        containers.forEach((container, index) => {
          const notation = container.getAttribute('data-notation');
          if (!notation) return;

          const paperId = `paper-${index}`;
          const audioId = `audio-${index}`;
          
          const paperDiv = container.querySelector('#paper');
          const audioDiv = container.querySelector('#audio');
          
          if (paperDiv) paperDiv.id = paperId;
          if (audioDiv) audioDiv.id = audioId;

          // Rendering Music Notation using ABCjs
          const visualObj = abcjs.default.renderAbc(paperId, notation, {
            responsive: "resize",
            add_classes: true,
            paddingleft: 0,
            paddingright: 0,
            paddingbottom: 10,
            padddingtop: 10,
          });

          // Handling Audio Playback if Controls are Enabled
          if (audioDiv) {
            const synthControl = new abcjs.default.synth.SynthController();
            synthControl.load(`#${audioId}`, null, {
              displayLoop: true,
              displayRestart: true,
              displayPlay: true,
              displayProgress: true,
              displayWarp: true,
            });

            const createSynth = new abcjs.default.synth.CreateSynth();
            createSynth
              .init({ visualObj: visualObj[0] })
              .then(() => {
                synthControl.setTune(visualObj[0], false);
              })
              .catch((error) => {
                console.warn("Audio synthesis initialization failed:", error);
              });
          }
        });
      }

      initializeAbcjs();
      document.addEventListener('astro:page-load', initializeAbcjs);
    });
  }
</script>
Note: The window !== 'undefined' check ensures that ABCjs runs only on the client-side. Astro runs on both server and client, so make sure you wrap any browser-dependent code with this check to prevent errors.

Step 4: Adding Custom Styles

Finally, I added some basic styling to ensure the music notation and audio controls look good on the page and are responsive. You can customize this further to fit your design guidelines.

<style>
  .abcjs-container {
    width: 100%;
    max-width: 800px;
    margin: 0 auto;
  }

  #paper {
    width: 100%;
    min-height: 200px;
  }

  #audio {
    width: 100%;
    margin-top: 20px;
  }

  :global(.abcjs-inline-audio) {
    max-width: 100%;
  }

  :global(.abcjs-btn) {
    margin-right: 0.5rem;
  }
</style>

Step 5: Integrate ABCjsPlayer in an Astro Page

Finally, here’s how you can implement the AbcjsPlayer component you’ve created inside any Astro page or markdown file. Just pass in your desired ABC notation and optional props.

Tip: Since this component is reusable, you can easily include different sheet music across any page or post in your Astro project just by passing in different ABC notation strings.
//Music Post.mdx
---
layout: ../../layouts/post.astro
title: Step 5: Usage Example in an Astro Post or Page
description: Step 5: Usage Example in an Astro Post or Page 
dateFormatted: 30.10.2024
---
 
import AbcjsPlayer from './components/AbcjsPlayer.astro';

<AbcjsPlayer
notation={`X:1
T: Cooley's
M: 4/4
L: 1/8
K: Emin
|:D2|"Em"EBBA B2 EB|~B2 AB dBAG|"D"FDAD BDAD|FDAD dAFD|
"Em"EBBA B2 EB|B2 AB defg|"D"afe^c dBAF|"Em"DEFD E2:|
|:gf|"Em"eB B2 efge|eB B2 gedB|"D"A2 FA DAFA|A2 FA defg|
"Em"eB B2 eBgB|eB B2 defg|"D"afe^c dBAF|"Em"DEFD E2:|
`}
/>

Next Steps

Now that you’ve built a simple but powerful music component, consider expanding its features. You could support additional file formats, add more customizable controls, or enable export to different music notation formats. Drop a comment below or share your improved version with the community!

If you’re new to Astro, make sure to check out the official Astro Documentation. Happy coding!

Disclaimer: This post is for personal use, but I hope it can also help others. I'm sharing my thoughts and experiences here.
If you have any insights or feedback, please reach out!
Note: Some content on this site may have been formatted using AI.

© 2024 Pavlin

Instagram GitHub