Build a Drum Machine - Build a Drum Machine

Tell us what’s happening:

My machine works as described, the buttons respond to mouse clicks and the sounds play on keydown events too. The display changes with each clip. Yet I’m failing tests 7 and forward, although there’s no output in the console to help me figure out what’s wrong.

My implementation seems pretty simple compared to a lot of what I see on the forum — am I missing something in the requirements?

Any guidance will be appreciated.

Your code so far

<!-- file: index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Drum Machine</title>
   <link rel="stylesheet" href="styles.css" />
</head>
<body>
    <div id="drum-machine">
        <p id="display"></p>
        <div class="group">
            <div id="pad-bank">
                <button class="drum-pad" id="Q">Q</button>
                <audio class="clip" id="Q" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-1.mp3"></audio>
                <button class="drum-pad" id="W">W</button>
                <audio class="clip" id="W" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-2.mp3"></audio>
                <button class="drum-pad" id="E">E</button>
                <audio class="clip" id="E" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-3.mp3"></audio>
                <button class="drum-pad" id="A">A</button>
                <audio class="clip" id="A" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-4_1.mp3"></audio>
                <button class="drum-pad" id="S">S</button>
                <audio class="clip" id="S" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-6.mp3"></audio>
                <button class="drum-pad" id="D">D</button>
                <audio class="clip" id="D" src="https://cdn.freecodecamp.org/curriculum/drum/Dsc_Oh.mp3"></audio>
                <button class="drum-pad" id="Z">Z</button>
                <audio class="clip" id="Z" src="https://cdn.freecodecamp.org/curriculum/drum/Kick_n_Hat.mp3"></audio>
                <button class="drum-pad" id="X">X</button>
                <audio class="clip" id="X" src="https://cdn.freecodecamp.org/curriculum/drum/RP4_KICK_1.mp3"></audio>
                <button class="drum-pad" id="C">C</button>
                <audio class="clip" id="C" src="https://cdn.freecodecamp.org/curriculum/drum/Cev_H2.mp3"></audio>
            </div>
        </div>
        <!-- <div class="group">
            <div id="content-row">
                <div class="content">Row 1</div>
                <div class="content">Row 2</div>
                <div class="content">Row 3</div>
                <div class="content">Row 4</div>
                <div class="content">Row 5</div>
            </div> -->
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

/* file: styles.css */
* {
    box-sizing: border-box;
}

body, html {
    height: 100vh;
    background: lightgray;
    display: flex;
    justify-content: center;
    align-items: center;
}

#drum-machine {
    background-color: rgb(57, 186, 228);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 90vh;
    width: 75vh;
    border-radius: 10px;
}

#display {
    width: 100%;
    padding: 10px;
    margin-top: 1px;
    font-size: 30px;
    text-align: center;
    border-radius: 5px;
}

#pad-bank {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    width: 90%;
    height: 40vh;
    margin: 0 auto;
}

.drum-pad {
    padding: 10px;
    font-size: 20px;
    background-color: #007BFF;
    color: white;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    transition: background-color 0.3s ease;
}

.drum-pad:hover {
    background-color: #0056b3;
}

#content-row {
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 50vh;
    margin-top: 20px;
}

.content {
    width: calc(100% - 40px);
    margin: 5px auto;
    background-color: #e0e0e0;
    padding: 20px;
    text-align: center;
    border-radius: 5px;
}

.group {
    display: flex;
    flex-direction: column; /* Stack content vertically */
    justify-content: center; /* Center items in the columns */
    height: 50%; /* Each group takes half the height of the drum machine */
    width: 100%; /* Full width */
    margin: 10px 0; /* Space between groups */}

/* file: script.js */
document.addEventListener('DOMContentLoaded', () => {
    const drumPads = document.querySelectorAll('.drum-pad');

    drumPads.forEach(pad => {
        pad.addEventListener('click', () => {
            const audioId = pad.id;
            const audio = pad.nextElementSibling;
            const clipName = audio.src.match(/\/([^\/]+)\.mp3$/)[1].replace(/-/g, ' ');
            
            console.log("Audio object:", audio);
            console.log('Audio ID:', audioId);
            if (audio instanceof HTMLAudioElement) {
                audio.currentTime = 0;
                audio.play();
                document.getElementById('display').textContent = `Playing: ${clipName}`;

            } else {
                console.error('Audio element not found for ID: ' + audioId);
            }
        });
    });
});

document.addEventListener('keydown', (event) => {
    const key = event.key.toUpperCase(); // Get the pressed key and convert to uppercase
    const pad = document.getElementById(key); // Find the drum pad by ID
    if (pad) {
        pad.click(); // Trigger a click event on the drum pad
    }
});

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.2 Safari/605.1.15 Ddg/26.2

Challenge Information:

Build a Drum Machine - Build a Drum Machine

Can the value of an id attribute be applied to more than one HTML element?

I revised the ids on the audio elements so that all ids are unique, and tests 7 and forward still fail. Everything works as expected when I run in my local environment.

Updated script.js:
document.addEventListener(‘DOMContentLoaded’, () => {
const drumPads = document.querySelectorAll(‘.drum-pad’);

const removeActiveClass = () => {
    drumPads.forEach(pad => pad.classList.remove('active'));
};   

    const playAudio = (pad) => {
        removeActiveClass();

        pad.classList.add('active');
        pad.offsetHeight; // Trigger a reflow to restart the animation

        const audioId = pad.id;
        const audio = pad.nextElementSibling;

        const clipName = audio.src.match(/\/([^\/]+)\.mp3$/)[1].replace(/-/g, ' ');
        
        console.log("Audio object:", audio);
        console.log('Audio ID:', audioId);

        if (audio instanceof HTMLAudioElement) {
            audio.currentTime = 0;
            audio.play();
            document.getElementById('display').textContent = `Playing: ${clipName}`;

            audio.addEventListener('ended', () => {
                console.log("Removing active class from pad:", pad.id);
                pad.classList.remove('active');
                console.log(`Current classes for pad ${pad.id}:`, pad.classList);
                }, { once: true });
            } else {
            console.error('Audio element not found for ID: ' + audioId);
        }
    }

    drumPads.forEach(pad => {
            pad.addEventListener('click', () => {
                console.log("Button clicked:", pad.id); 
                playAudio(pad); // Play audio on button click
            });
        });

    document.addEventListener('keydown', (event) => {
        console.log("Key pressed:", event.key);
        const key = event.key.toUpperCase(); // Get the pressed key and convert to uppercase
        const pad = document.getElementById(key); // Find the drum pad by ID
        if (pad) {
            event.preventDefault();
            playAudio(pad); // Trigger a click event on the drum pad
        }
    });
});

Would you post your updated HTML please?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Drum Machine</title>
   <link rel="stylesheet" href="styles.css" />
</head>
<body>
    <div id="drum-machine">
        <p id="display"></p>
        <div class="group">
            <div id="pad-bank">
                <button class="drum-pad" id="Q">Q</button>
                <audio class="clip" id="btnQ" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-1.mp3"></audio>
                <button class="drum-pad" id="W">W</button>
                <audio class="clip" id="btnW" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-2.mp3"></audio>
                <button class="drum-pad" id="E">E</button>
                <audio class="clip" id="btnE" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-3.mp3"></audio>
                <button class="drum-pad" id="A">A</button>
                <audio class="clip" id="btnA" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-4_1.mp3"></audio>
                <button class="drum-pad" id="S">S</button>
                <audio class="clip" id="btnS" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-6.mp3"></audio>
                <button class="drum-pad" id="D">D</button>
                <audio class="clip" id="btnD" src="https://cdn.freecodecamp.org/curriculum/drum/Dsc_Oh.mp3"></audio>
                <button class="drum-pad" id="Z">Z</button>
                <audio class="clip" id="btnZ" src="https://cdn.freecodecamp.org/curriculum/drum/Kick_n_Hat.mp3"></audio>
                <button class="drum-pad" id="X">X</button>
                <audio class="clip" id="btnX" src="https://cdn.freecodecamp.org/curriculum/drum/RP4_KICK_1.mp3"></audio>
                <button class="drum-pad" id="C">C</button>
                <audio class="clip" id="btnC" src="https://cdn.freecodecamp.org/curriculum/drum/Cev_H2.mp3"></audio>
            </div>
        </div>
                </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

I’ve redone some of my logic and am passing all tests except the final one: 10. When a .drum-pad is triggered, you should display a string describing the associated audio clip as the inner text of the #display element (each string must be unique).

The error message I’m getting:

[Error] n {message: "expected Set{ 'Playing: Cev H2' } to have a size of 9 but got 1", showDiff: true, actual: 1, expected: 9, stack: "@↵@↵eval code@↵eval@[native code]↵@https://www.fre…/test-runner/7.2.0/dom-test-evaluator.js:2:257630", …}
	(anonymous function) (dom-test-evaluator.js:2:256860)

is confusing me — wouldn’t a set created with one element have a size of 1? That it’s expecting a size of 9 suggests the set should maybe be a collection of the unique clip names, but we’re not asked to keep track of that anywhere.

Appreciate any feedback to figure this out.

Please post your updated code.

Have you logged clipName to make sure it’s what you expect? Are you supposed to prepend 'Playing: ’ to the clipName?

Yes, clipName is what I expect, and no, I removed the prepend and added back the .mp3, so display.InnerText is now the full name of the audio clip. I get the same error – can you say more about the error? Here’s my current code, and thank you for your time :grinning_face::

document.addEventListener('DOMContentLoaded', () => {
    const drumPads = document.querySelectorAll('.drum-pad');

    const removeActiveClass = () => {
        drumPads.forEach(pad => pad.classList.remove('active'));
    };

    const playAudio = (pad) => {
        const audio = pad.querySelector('audio');
        if (!audio) {
            console.error('Audio element not found for pad:', pad);
            return; 
        }

        audio.currentTime = 0;
        audio.play().then(() => {
            removeActiveClass();
            pad.classList.add('active');

        const clipNameMatch = audio.src.match(/([^\/]+)\.mp3$/);
        const clipName = clipNameMatch ? clipNameMatch[0] : "Unknown Clip";
        console.log(clipName);
        const displayElement = document.getElementById('display');
        displayElement.innerText = clipName;

            // debugging
            console.log(`Currently playing: ${clipName}`);
        }).catch((error) => {
            console.error("Audio playback failed:", error);
        });

        audio.addEventListener('ended', () => {
            pad.classList.remove('active');
        }, { once: true });
    };

    drumPads.forEach(pad => {
        pad.addEventListener('click', () => {
        console.log("Pad clicked:", pad.textContent.trim());
        playAudio(pad); 
        });
    });

    document.addEventListener('keydown', (event) => {
        const key = event.key.toUpperCase();
        const pad = Array.from(drumPads).find(pad => pad.textContent.trim() === key);

        console.log("Key pressed:", key);
        if (pad) {
            event.preventDefault();
            playAudio(pad);
        } else {
            console.warn(`No pad found for key: ${key}`);
        }
    });
});

Have you tried without the “.mp3”?

I changed the conditional for clipName to match clipNameMatch[1], which returns the part of the name before .mp3, and still fail on the last test with the same error message:

expected Set{ 'Cev_H2' } to have a size of 9 but got 1

Can you please explain to me what that test is looking at, and why the size of Set{ ‘Cev_H2’ } is not 1?

  1. Each .drum-pad should have an audio element which has a class of clip, a src attribute that points to an audio clip, and an id corresponding to the inner text of its parent .drum-pad element (e.g. id=“Q”, id=“W”, id=“E” etc.).
  2. When you click on a .drum-pad element, the audio clip contained in its child audio element should be triggered.
  3. When you press one of the keys Q, W, E, A, S, D, Z, X, C on your keyboard, the corresponding audio element should play the corresponding sound.
  4. When a .drum-pad is triggered, you should display a string describing the associated audio clip as the inner text of the #display element (each string must be unique).

Your logs are telling you that when a pad is clicked, it can’t find the audio element for that pad.

Looking at the first failing test, do your audio elements have an id that matches the inner text of its parent .drum_pad element?

Here are some debugging steps you can follow. Focus on one test at a time:

  1. Are there any errors or messages in the console?
  2. What is the requirement of the failing test?
  3. Check the related User Story and ensure it’s followed precisely.
  4. What line of code implements this?
  5. What is the result of the code and does it match the requirement? (Write the value of a variable to the console at that point in the code if needed.)

If this does not help you solve the problem, please reply with answers to these questions.

I’m not getting the “audio element not found” debugging line in my console.

Each audio element has an id that matches the inner text of its parent .drum_pad element. Test 7 addressing this passes.

  1. In the console are the debugging statements I’ve put in, and one error:
n {message: "expected Set{ 'Cev_H2' } to have a size of 9 but got 1", showDiff: true, actual: 1, expected: 9, stack: "@↵@↵eval code@↵eval@[native code]↵@https://www.fre…/test-runner/7.2.0/dom-test-evaluator.js:2:257630", …}

Can I ask again for some detail on what this test is checking?

  1. The failing test says:

When a .drum-pad is triggered, you should display a string describing the associated audio clip as the inner text of the display element (each string must be unique).

The event listener on each drum pad triggers playAudio(), which creates a unique string based on the name of the relevant audio clip and displays it as the inner text of the display element.

  1. The related User Story uses the exact same wording, so I think I’m following it precisely.

  2. The code that implements the display of the unique string is:

 const clipNameMatch = audio.src.match(/([^\/]+)\.mp3$/);
        const clipName = clipNameMatch ? clipNameMatch[1] : "Unknown Clip";
        console.log(clipName);
        const displayElement = document.getElementById('display');
        displayElement.innerText = clipName;
  1. Immediately after the code shown in #4, I debug with this code:
            // debugging
            console.log(`Currently playing: ${clipName}`);
        }).catch((error) => {

And the console output from this is:

[Log] Cev_H2 (about:srcdoc, line 158)
[Log] Currently playing: Cev_H2 (about:srcdoc, line 163)

The first line is from the console log in #4, the second from the code in #5.

Thank you for your patience in working through this with me.

n {message: "expected Set{ 'Cev_H2' } to have a size of 9 but got 1", showDiff: true, actual: 1, expected: 9, stack: "@↵@↵eval code@↵eval@[native code]↵@https://www.fre…/test-runner/7.2.0/dom-test-evaluator.js:2:257630", …}

I am not seeing this error in the console from the code you provided. Did you change your code again? And you really should be focusing on the first failed test. Your HTML in incorrect, assuming what you posted has not changed.

I reviewed my current code to make sure I had provided it, and other than using clipNameMatch[1] instead of clipNameMatch[0] (thus removing .mp3 from clipName), it matches what I provided in the post beginning with:

I just ran it with clipNameMatch[0] instead of clipNameMatch[1] and the only difference in the log input is the presence of .mp3 in clipName. It fails only the same test. The User Story doesn’t specify the content of the unique string describing the associated audio element, so it seems to me either version would be ok.

Since you’re seeing the error message about not finding the audio element, which I don’t see, and you’re not seeing the one about the Set{} being the wrong size, which I do see, may I respectfully ask if you’re using the version of my code from that post?

The first (only) failed test is #10 quoted in my last response. Which HTML is incorrect?

Please post all of your current code…everything.

Script:

document.addEventListener('DOMContentLoaded', () => {
    const drumPads = document.querySelectorAll('.drum-pad');

    const removeActiveClass = () => {
        drumPads.forEach(pad => pad.classList.remove('active'));
    };

    const playAudio = (pad) => {
        const audio = pad.querySelector('audio');
        if (!audio) {
            console.error('Audio element not found for pad:', pad);
            return; 
        }

        audio.currentTime = 0;
        audio.play().then(() => {
            removeActiveClass();
            pad.classList.add('active');

        const clipNameMatch = audio.src.match(/([^\/]+)\.mp3$/);
        const clipName = clipNameMatch ? clipNameMatch[0] : "Unknown Clip";
        console.log(clipName);
        const displayElement = document.getElementById('display');
        displayElement.innerText = clipName;

            // debugging
            console.log(`Currently playing: ${clipName}`);
        }).catch((error) => {
            console.error("Audio playback failed:", error);
        });

        audio.addEventListener('ended', () => {
            pad.classList.remove('active');
        }, { once: true });
    };

    drumPads.forEach(pad => {
        pad.addEventListener('click', () => {
        console.log("Pad clicked:", pad.textContent.trim());
        playAudio(pad); 
        });
    });

    document.addEventListener('keydown', (event) => {
        const key = event.key.toUpperCase();
        const pad = Array.from(drumPads).find(pad => pad.textContent.trim() === key);

        console.log("Key pressed:", key);
        if (pad) {
            event.preventDefault();
            playAudio(pad);
        } else {
            console.warn(`No pad found for key: ${key}`);
        }
    });
});

HTML (you said everything, I wasn’t sure if you meant this too — not posting .css since I didn’t touch it):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Drum Machine</title>
   <link rel="stylesheet" href="styles.css" />
</head>
<body>
    <div id="drum-machine">
        <p id="display"></p>
        <div class="group">
            <div id="pad-bank">
                <button class="drum-pad">Q
                <audio class="clip" id="Q" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-1.mp3"></audio></button>
                <button class="drum-pad">W
                <audio class="clip" id="W" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-2.mp3"></audio></button>
                <button class="drum-pad">E
                <audio class="clip" id="E" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-3.mp3"></audio></button>
                <button class="drum-pad">A
                <audio class="clip" id="A" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-4_1.mp3"></audio></button>
                <button class="drum-pad">S
                <audio class="clip" id="S" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-6.mp3"></audio></button>
                <button class="drum-pad">D
                <audio class="clip" id="D" src="https://cdn.freecodecamp.org/curriculum/drum/Dsc_Oh.mp3"></audio></button>
                <button class="drum-pad">Z
                <audio class="clip" id="Z" src="https://cdn.freecodecamp.org/curriculum/drum/Kick_n_Hat.mp3"></audio></button>
                <button class="drum-pad">X
                <audio class="clip" id="X" src="https://cdn.freecodecamp.org/curriculum/drum/RP4_KICK_1.mp3"></audio></button>
                <button class="drum-pad">C
                <audio class="clip" id="C" src="https://cdn.freecodecamp.org/curriculum/drum/Cev_H2.mp3"></audio></button>
            </div>
        </div>
                </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

Thanks.

You have completely changed your HTML from the last time you posted it in this thread!

I apologize, I remembered that I mentioned a change, but neglected to post it. Hopefully this helps us get to end-of-game!

I accept your apology for wasting my time. Going forward, be sure to post all of your updated code when you need help.

Take a look at the chart in the instructions. Drum name is most likely the clip name that should be displayed.

Since you won’t always be able to get that from the audio clip src value, you’ll need to think of another way to make that value available.

I updated the HTML and the script — added name attribute to the audio elements and use that to set clipName. Still getting the same error about the set size .

Updated files:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>Drum Machine</title>
   <link rel="stylesheet" href="styles.css" />
</head>
<body>
    <div id="drum-machine">
        <p id="display"></p>
        <div class="group">
            <div id="pad-bank">
                <button class="drum-pad">Q
                <audio name="Heater1" class="clip" id="Q" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-1.mp3"></audio></button>
                <button class="drum-pad">W
                <audio name="Heater 2" class="clip" id="W" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-2.mp3"></audio></button>
                <button class="drum-pad">E
                <audio name="Heater 3" class="clip" id="E" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-3.mp3"></audio></button>
                <button class="drum-pad">A
                <audio name="Heater 4" class="clip" id="A" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-4_1.mp3"></audio></button>
                <button class="drum-pad">S
                <audio name="Clap" class="clip" id="S" src="https://cdn.freecodecamp.org/curriculum/drum/Heater-6.mp3"></audio></button>
                <button class="drum-pad">D
                <audio name="Open-HH" class="clip" id="D" src="https://cdn.freecodecamp.org/curriculum/drum/Dsc_Oh.mp3"></audio></button>
                <button class="drum-pad">Z
                <audio name="Kick-n'-Hat" class="clip" id="Z" src="https://cdn.freecodecamp.org/curriculum/drum/Kick_n_Hat.mp3"></audio></button>
                <button class="drum-pad">X
                <audio name="Kick" class="clip" id="X" src="https://cdn.freecodecamp.org/curriculum/drum/RP4_KICK_1.mp3"></audio></button>
                <button class="drum-pad">C
                <audio name="Closed-HH" class="clip" id="C" src="https://cdn.freecodecamp.org/curriculum/drum/Cev_H2.mp3"></audio></button>
            </div>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

script.js:

document.addEventListener('DOMContentLoaded', () => {
    const drumPads = document.querySelectorAll('.drum-pad');

    const removeActiveClass = () => {
        drumPads.forEach(pad => pad.classList.remove('active'));
    };

    const playAudio = (pad) => {
        const audio = pad.querySelector('audio');
        if (!audio) {
            console.error('Audio element not found for pad:', pad);
            return; 
        }

        audio.currentTime = 0;
        audio.play().then(() => {
            removeActiveClass();
            pad.classList.add('active');

        const clipName = audio.getAttribute('name'); /*src.match(/([^\/]+)\.mp3$/);
        const clipName = clipNameMatch ? clipNameMatch[0] : "Unknown Clip";*/
        console.log(clipName);
        const displayElement = document.getElementById('display');
        displayElement.innerText = clipName;

            // debugging
            console.log(`Currently playing: ${clipName}`);
        }).catch((error) => {
            console.error("Audio playback failed:", error);
        });

        audio.addEventListener('ended', () => {
            pad.classList.remove('active');
        }, { once: true });
    };

    drumPads.forEach(pad => {
        pad.addEventListener('click', () => {
        console.log("Pad clicked:", pad.textContent.trim());
        playAudio(pad); 
        });
    });

    document.addEventListener('keydown', (event) => {
        const key = event.key.toUpperCase();
        const pad = Array.from(drumPads).find(pad => pad.textContent.trim() === key);

        console.log("Key pressed:", key);
        if (pad) {
            event.preventDefault();
            playAudio(pad);
        } else {
            console.warn(`No pad found for key: ${key}`);
        }
    });
});

Error message:

[Error] n {message: "expected Set{ 'Closed-HH' } to have a size of 9 but got 1", showDiff: true, actual: 1, expected: 9, stack: "@↵@↵eval code@↵eval@[native code]↵@https://www.fre…/test-runner/7.2.0/dom-test-evaluator.js:2:257630", …}
	(anonymous function) (dom-test-evaluator.js:2:256860)

9 is indeed the length of the single element of the set, but isn’t the size of the set 1?