Hamburger Icon
  • Labs icon Lab
  • Core Tech
Labs

Guided: Build a Drum Machine with JavaScript

In this code lab, learners will build an interactive web-based drum machine that allows users to create and modify rhythmic patterns for various drum elements like kick, snare, and hihat. Key learning topics covered include DOM Manipulation, event handling, data structures, looping & iteration, timed actions, and string manipulation.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 14m
Published
Clock icon Sep 25, 2023

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Overview

    In this lab, you'll develop an interactive drum machine for the web. This tool will let users design patterns for drum elements such as kick, snare, and hihat. You will use 'x' to indicate an active beat and '-' for a rest. Users can toggle between these beats with a simple click. The patterns loop at a set BPM, and the drum sounds come from HTML audio samples.

    How to Use the Drum Machine

    You can find the working drum machine in the Web Browser tab on the right. As you add functionality, you'll gain the ability to start and stop the beat playback.

    Note:

    • After adding new functionality, refresh the Web Browser tab to see the updates.
    • If you create or modify a beat pattern in the UI, stop the playback first, then start it again to hear the changes.

    Let's get started!


  2. Challenge

    Creating Track Elements

    Overview

    The createTrackElement function is designed to create a DOM element that visualizes a drum track's label, such as "Kick:", "Snare:" or "Hihat".

    Function Signature: createTrackElement(track):

    • Input:
      • track: A string that symbolizes the name of a drum track, like 'kick', 'snare', or 'hihat'.
    • Output: A DOM <div> element that portrays the drum track's label.

    Construct the Element.

    Create a <div> element and assign it to a variable named trackEl.

    👣 Steps
    1. Use the document.createElement method.
    2. Specify the type of the element, in this case, div.
    3. Assign this new element to the trackEl variable.
    🔍 Hint

    To create a new HTML <div> element in JavaScript, use the document.createElement method. Specify div as the type of element you want to create. After creation, assign it to a variable named trackEl.

    🔑 Solution
    const trackEl = document.createElement('div');
    

    Assigning Class Names.

    Set the className of trackEl to track.

    👣 Steps
    1. Access and modify the className property of trackEl.
    🔍 Hint

    You can set the desired class for trackEl by updating its className property.

    🔑 Solution
    trackEl.className = 'track';
    

    Inserting the Track Name.

    Update trackEl's innerHTML with a string that includes a <span> element. The text inside the span should be the track's name, with the first letter in uppercase.

    👣 Steps
    1. Modify the innerHTML of trackEl.
    2. Use a string with embedded JavaScript expressions through template literals.
    3. Adjust the track string to display its first character in uppercase.
    🔍 Hint

    Consider how you can insert content into an HTML element. The goal is to display the first letter of the track variable in uppercase, followed by the remaining string. Template literals (using backticks) can help you embed dynamic content within the HTML structure.

    🔑 Solution
    trackEl.innerHTML = `<span>${track[0].toUpperCase() + track.slice(1)}</span>`;
    

    Return the Created Element.

    Return the trackEl variable.

    👣 Steps
    1. Use the return keyword before the variable you want to return.
    🔍 Hint

    To return a result from a function, use the return keyword.

    🔑 Solution
    return trackEl;
    

  3. Challenge

    Creating Step Elements

    Overview

    The createStepElement function is designed to create a visual representation for each beat of a drum track. Each beat can have a toggle event to activate or deactivate it.

    Function Signature: createStepElement(track, pattern, index):

    • Inputs:
      • track: A string representing the drum track's name.
      • pattern: A string indicating the drum track's pattern (e.g., "x-------x-------").
      • index: An integer pointing to a specific beat within the pattern.
    • Output: A DOM <div> element representing a specific beat within a drum track's pattern.

    Create a New <div> Element.

    Create a div element and assign it to a variable named stepEl.

    👣 Steps
    1. Call the document.createElement method.
    2. Specify the element type, in this case, div.
    3. Assign the created element to the stepEl variable.
    🔍 Hint

    To create a new HTML <div> element, use the document.createElement method, specifying div as the element type. After creation, assign it to a variable called stepEl.

    🔑 Solution
    const stepEl = document.createElement('div');
    

    Assign Class Names to the Element.

    Set the className of stepEl to step. If the beat at the current index in the pattern is 'x', also add the active class.

    👣 Steps
    1. Modify the className property of the element to assign class names.
    2. Use a ternary operator to conditionally add the active class based on the current index of the pattern.
    🔍 Hint

    You can modify the className property of an element to assign classes. To conditionally add the active class based on the current index of the pattern, use a ternary operator to check if the beat is 'x'. If true, add the active class; otherwise, simply set it to step.

    🔑 Solution
    stepEl.className = `step ${pattern[index] === 'x' ? 'active' : ''}`;
    

    Add an Onclick Event Listener.

    Attach an onclick event listener to stepEl. This listener should toggle the active class and update the pattern for the track in trackData.

    👣 Steps
    1. Assign a function to the onclick property of the stepEl to add the event listener.
    2. Inside the function, use the classList.toggle method to toggle the active class.
    3. Also, make sure the pattern in trackData is updated using the appropriate method.
    🔍 Hint

    To attach an onclick event listener to an element, assign a function to its onclick property. Within this function, you can use the classList.toggle method to toggle the active class on stepEl. Also, remember to update the trackData pattern using the relevant method.

    🔑 Solution
    stepEl.onclick = () => {
        stepEl.classList.toggle('active');
        trackData[track] = updatePattern(trackData[track], index, stepEl.classList.contains('active'));
    };
    

    Return the Created Element.

    Return the stepEl variable.

    👣 Steps
    1. Use the return keyword before the variable you intend to return.
    🔍 Hint

    Use the return keyword to return a result from a function.

    🔑 Solution
    return stepEl;
    

  4. Challenge

    Creating Track Visuals

    Overview

    Description: The createTrackVisuals function is central to rendering the drum machine's visual representation. It processes each track and its corresponding pattern, then generates the necessary visual elements to represent the beats in each track.

    Function Signature: createTrackVisuals():

    • Inputs:
      • None.
    • Output:
      • No direct output, but it modifies the DOM by adding the necessary visual elements to represent the tracks and beats.

    Building the createTrackVisuals function.

    Iterate over all tracks and their patterns, create visual elements for them, and add them to the main tracks container in the DOM.

    👣 Steps
    1. Access the Main Tracks Container:

      • Retrieve the main tracks container element (tracksEl) using the document.getElementById method and the ID 'tracks'.
    2. Iterate Over Track Data:

      • Loop through each track and its associated pattern in trackData using Object.entries.
    3. Generate Track Element:

      • For each track, create a visual element (trackEl) using the createTrackElement function with the track as its argument.
    4. Process Each Beat in the Pattern:

      • Convert the pattern string into an array and loop through each beat.
    5. Create Beat Element:

      • For each beat, generate a visual element (stepEl) using the createStepElement function. This function should accept the track, pattern, and beat index as its arguments.
    6. Attach Beat to Track:

      • Append the created beat element (stepEl) to the track element (trackEl).
    7. Add Track to Main Container:

      • Once all beats are processed and added to the track, append the entire track element (trackEl) to the main tracks container (tracksEl).
    🔍 Hints
    • The createTrackVisuals function revolves around the central idea of translating the data (tracks and patterns) into visual elements in the DOM.
    • Use the Object.entries method to access both the track name and its pattern.
    • The Array.from method can transform the pattern string into an array, which can be looped through for beat creation.
    • Ensure each beat element is appended to its parent track element, and then the track element gets appended to the main container.
    🔑 Solution
    function createTrackVisuals() {
        // 1. Access the main tracks container.
        const tracksEl = document.getElementById('tracks');
    
        // 2. Iterate over track data.
        for (let [track, pattern] of Object.entries(trackData)) {
    
            // 3. Generate track element.
            const trackEl = createTrackElement(track);
    
            // 4. Process each beat in the pattern.
            Array.from(pattern).forEach((beat, i) => {
    
                // 5. Create beat element.
                const stepEl = createStepElement(track, pattern, i);
    
                // 6. Attach beat to track.
                trackEl.appendChild(stepEl);
            });
    
            // 7. Add track to main container.
            tracksEl.appendChild(trackEl);
        }
    }
    

    The createTrackVisuals function sets up the visual layout of the drum machine, drawing from the trackData to generate both track and beat elements in the DOM. These visual components facilitate user interaction with the drum machine.


  5. Challenge

    Updating Track Patterns

    Overview

    The updatePattern function is pivotal to the drum machine's functionality. It allows users to change a specific beat in a drum track's pattern, ensuring that the UI's visual cues match the underlying pattern data.

    Function Signature: updatePattern(pattern, index, active):

    • Inputs:
      • pattern: A string detailing the current drum beat pattern, such as "x--x--x--".
      • index: An integer, highlighting which beat's status needs modification within the pattern.
      • active: A boolean denoting the beat's new status. If true, the beat is active ('x'); otherwise, it's inactive ('-').
    • Output:
      • Returns a string portraying the revised drum beat pattern.

    Creating the updatePattern function.

    Modify the pattern string to reflect the status of a specific beat, then return this updated pattern. You'll be furnished with the current pattern, the beat's index to change, and its desired status (active or not).

    👣 Steps
    1. Initiate Function Definition:

      • Start by creating a function named updatePattern, taking in pattern, index, and active as parameters.
    2. Segment the Pattern Before the Targeted Index:

      • Employ the string's slice method on pattern to capture characters leading up to the given index.
    3. Create the Beat's Character:

      • Use the ternary operation to decide the character based on the active parameter:
        • 'x' is chosen if active is true (indicating an active beat).
        • '-' is selected otherwise (indicating an inactive beat).
    4. Segment the Pattern Post the Targeted Index:

      • Once more, utilize the slice method on pattern to gather characters post the stated index.
    5. Merge All Segments:

      • String together the initial segment, the beat character, and the latter segment to produce the updated pattern.
    6. Return the Updated Pattern:

      • Use the return keyword to send the newly formed string back from the function.
    7. Close the Function:

      • Finish the function using the suitable closing curly brace.
    🔍 Hints

    Bear in mind, the pattern string signifies beats with either 'x' for the active or '-' for the inactive ones. Given the beat's index and its active status, your mission is to modify that particular beat without affecting the rest.

    Here's a plan:

    1. Use the slice method to derive the part of the pattern before the beat you're altering.
    2. Get the new beat's value.
    3. Extract the section of the pattern following the beat under modification.
    4. Merge these segments to produce the updated pattern.
    🔑 Solution
    function updatePattern(pattern, index, active) {
        return `${pattern.slice(0, index)}${active ? 'x' : '-'}${pattern.slice(index + 1)}`;
    }
    

    Using the slice method, this function preserves all components of the pattern, save for the beat at the designated index. This specific beat is adjusted as per the active parameter, with 'x' symbolizing an active beat and '-' a passive one.


  6. Challenge

    Starting the Drum Machine

    Overview

    The startDrumMachine function manages the rhythmic playback of drum samples based on user-defined drum patterns and BPM (Beats Per Minute).

    Function Steps:

    1. Stop any ongoing drum playback.
    2. Convert BPM to milliseconds.
    3. Define the drum tracks.
    4. Start the rhythmic playback loop.

    Stopping Previous Drum Playback

    Stop any currently running drum sequences to prevent overlapping.

    stopDrumMachine();
    

    Convert BPM to Milliseconds

    Convert the provided BPM value to its equivalent in milliseconds to determine the beat duration.

    👣 Steps
    1. Retrieve BPM Value:

      • Get the DOM element with the ID bpmInput.
      • Extract its value.
      • Convert this value to a float using parseFloat.
      • Store the result in a constant called bpm.
    2. Calculate Beat Duration:

      • Divide 60,000 (milliseconds in a minute) by bpm to get the duration of each beat.
      • Store this in a constant named step_time.
    🔍 Hints

    Remember that BPM stands for beats per minute. For a BPM of 60, there's one beat every second, which is 1000 milliseconds.

    🔑 Solution
    const bpm = parseFloat(document.getElementById('bpmInput').value);
    const step_time = 60000 / bpm;
    

    Define Drum Tracks

    Set up the tracks array. For each drum type ('kick', 'snare', 'hihat'), get its audio using document.getElementById and associate it with its rhythm from trackData.

    👣 Steps
    1. Set Up Drum Tracks:
      • Associate each drum type with its audio and pattern.
    🔍 Hints

    Consider using arrays and objects to link each drum type with its audio and pattern for easier playback.

    🔑 Solution
    const tracks = [
        { sample: document.getElementById('kick'), steps: trackData.kick },
        { sample: document.getElementById('snare'), steps: trackData.snare },
        { sample: document.getElementById('hihat'), steps: trackData.hihat }
    ];
    

    Start Playback Loop

    Create a loop that goes through drum patterns, triggering the relevant samples when an active beat (represented by 'x') is found.

    👣 Steps
    1. Initialize Counter:

      • Start a counter called step at 0.
    2. Use setInterval:

      • Use setInterval to run a block of code repeatedly at the step_time interval.
      • Within this function:
        • Loop through each track in the tracks array.
        • Check if the current step of the track has an active beat ('x').
        • If true, reset the sample's playback position and play the sample.
        • After checking all tracks, increment step.
        • Loop step back to the start when it reaches the end of the pattern.
    🔍 Hints

    The drum machine should cycle through each position of the drum pattern. Check if each drum should play at the current step. If a drum has an active beat ('x'), reset its playback position and play it. Loop back to the start when reaching the end of the pattern.

    🔑 Solution
    let step = 0;
    drumInterval = setInterval(() => {
        tracks.forEach(track => {
            if (track.steps[step] === 'x') {
                track.sample.currentTime = 0;
                track.sample.play();
            }
        });
        step = (step + 1) % tracks[0].steps.length;
    }, step_time);
    

  7. Challenge

    Stopping the Drum Machine

    Overview

    stopDrumMachine() Function

    • Inputs: None.
    • Output: None (Stops ongoing drum playback).

    Define the stopDrumMachine function.

    Make sure any ongoing drum sequences are stopped, allowing the drum machine to be cleanly restarted or stopped.

    👣 Steps
    1. Function Definition:

      • Declare the function stopDrumMachine.
    2. Active Interval Check:

      • Determine if drumInterval is assigned (which means the drum machine is active).
    3. Stop Playback:

      • If the drum machine is active, use clearInterval to end the playback loop. Provide drumInterval to clearInterval.
    4. Clear Interval Reference:

      • Reset drumInterval to null to avoid any remaining references and show the drum machine has stopped.
    5. End Function:

      • Close the function using the corresponding curly brace.
    🔍 Hint

    To stop an interval in JavaScript, keep a reference to that interval. Use clearInterval with that reference to stop it. Resetting the reference prevents overlaps when restarting the drum machine.

    🔑 Solution
    function stopDrumMachine() {
        if (drumInterval) {
            clearInterval(drumInterval);
            drumInterval = null;
        }
    }
    

Danny Sullivan is a former special education teacher and professional baseball player that moved into software development in 2014. He’s experienced with Ruby, Python and JavaScript ecosystems, but enjoys Ruby most for its user friendliness and rapid prototyping capabilities.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.