Drum machine - all pads play same sound

Tell us what’s happening:
The drum machine is working but all the buttons play the same sound.
see project on codepen: https://codepen.io/Logan_code/full/poNwBMX
Your code so far

const arr = [
  {
    keyCode: 81,
    key:"Q" ,
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Heater-1.mp3'
  },
  {
    keyCode: 87,
    key:"W" ,
    sound:'https://s3.amazonaws.co/freecodecamp/drums/Heater-2.mp3'
  },
  {
    keyCode: 69,
    key:"E" ,
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Heater-3.mp3'
  },
  {
    keyCode: 65,
    key:"A" ,
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Heater-6.mp3'
  },
  {
    keyCode: 83,
    key: "S",
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Dsc_Oh.mp3'
  },
  {
    keyCode: 68,
    key: "D",
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Kick_n_Hat.mp3'
  },
  {
    keyCode: 90,
    key:"Z" ,
    sound:'https://s3.amazonaws.com/freecodecamp/drums/RP4_KICK_1.mp3'
  },
  {
    keyCode: 88,
    key: "X",
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Cev_H2.mp3'
  },
  {
    keyCode: 67,
    key: "C",
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Brk_Snr.mp3'
  }
]

class App extends React.Component{
  constructor(props){
    super(props);
    this.audio = React.createRef();
    this.playSound= this.playSound.bind(this);
  }
  
  playSound(){
    this.audio.current.play();
  }
  
  render(){
    return (
    <div id="display">
      {arr.map((arr,i) =>(
        <div className="drum-pad" text={arr.key} key={i} id={arr.key} onClick={this.playSound}>{arr.key}
          <audio className="clip" src={arr.sound} id={arr.key} ref={this.audio}/>
          </div>
        ))}
    </div>
    );
  }
};

ReactDOM.render(<App />, document.getElementById("drum-machine"));

Your browser information:

User Agent is: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0.

Challenge: Build a Drum Machine

Link to the challenge:

On my phone so I can’t try it myself but it looks like you need arr[i].sound and arr[i].key inside your map.

it says "arr[i] is undefined "

displaying the pads work perfectly with the syntax i have, its just that the audio is not playing the sounds assigned to them .

figured it out! the src was being overwritten because of the map

1 Like

Oh nice! I just sat at my computer and was going to give this a proper look but you figured it out!

1 Like

Tell us what’s happening:
Hi all, I’m a little confused on how the keyboard events work. I’ve created the event, and called it through OnKeyPress but it still isnt working. its like its not even there, can anyone help?
see on codepen: https://codepen.io/Logan_code/pen/poNwBMX

Your code so far

const arr = [
  {
    keyCode: 81,
    key:"Q" ,
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Heater-1.mp3'
  },
  {
    keyCode: 87,
    key:"W" ,
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Chord_2.mp3'
  },
  {
    keyCode: 69,
    key:"E" ,
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Heater-3.mp3'
  },
  {
    keyCode: 65,
    key:"A" ,
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Heater-6.mp3'
  },
  {
    keyCode: 83,
    key: "S",
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Dsc_Oh.mp3'
  },
  {
    keyCode: 68,
    key: "D",
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Kick_n_Hat.mp3'
  },
  {
    keyCode: 90,
    key:"Z" ,
    sound:'https://s3.amazonaws.com/freecodecamp/drums/RP4_KICK_1.mp3'
  },
  {
    keyCode: 88,
    key: "X",
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Chord_1.mp3'
  },
  {
    keyCode: 67,
    key: "C",
    sound:'https://s3.amazonaws.com/freecodecamp/drums/Brk_Snr.mp3'
  }
];

class App extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      play:false,
    };
    this.playSound= this.playSound.bind(this);
    this.keyPress = this.keyPress.bind(this);
  }
  
  playSound(e){
    this.setState({
      play:!this.state.play,
    });

    const audio = e.target.querySelector('audio');

    audio.play();
    audio.currentTime = 0;
  }
 
   keyPress(e) {
    if (e.keyCode === this.props.keyCode) {
      this.playSound;
    }
  }
  
  componentDidMount(){
    document.addEventListener("PlaySound", this.playSound);
    document.addEventListener('keydown', this.keyPress);
  }
  componentWillMount(){
    document.removeEventListener("PlaySound", this.playSound);
    document.removeEventListener('keydown', this.keyPress);
  }
  
  render(){
    return (
    <div id="display">
      {arr.map((arr,i) =>(
        <div className="drum-pad" text={arr.key} key={i} id={arr.key} onClick={this.playSound} onKeyDown={this.keyPress}>{arr.key}
          <audio className="clip" key={i} src={arr.sound} id={arr.key} ref={this.audio}/>
         </div>
        ))}
    </div>
    );
  }
};

ReactDOM.render(<App />, document.getElementById("drum-machine"));

Your browser information:

User Agent is: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0.

Challenge: Build a Drum Machine

Link to the challenge:

Unfortunately in order to have a keypress works like that the item needs to have focus (that’s a browser requirement), so you either have to wait for the user to click (or tab) into it or you programmatically give it to it with React.

However there’s a preferred solution (imho): use React lifecycles to add/remove a document listener and control it from there, instead of at div element.

Here’s a demo for you, I have used hooks for brevity, but the same logic can be expressed using classes methods.

Hope it helps :sparkles:

so i changed my code keypress(), when a key is pressed i get the needed keyCode. but when i call the playSound function nothing happens. is there a problem with the way i call it?

class App extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      play:false,
    };
    this.playSound= this.playSound.bind(this);
    this.keyPress = this.keyPress.bind(this);
  }
  
  playSound = (e)=>{
    this.setState({
      play:!this.state.play,
    });

    const audio = e.target.querySelector('audio');

    audio.play();
    audio.currentTime = 0;
  }
 
  keyPress(e) {
    const keyArr = [81,87,69,65,83,68,90,88,67];
    for (let i = 0;i<=keyArr.length;i++){
      if(e.keyCode === keyArr[i]){
        console.log(e.keyCode);
        this.playSound;
      }
    }
    
  }
  
  componentDidMount(){
    document.addEventListener('keydown', this.keyPress);
  }
  componentWillMount(){
    document.removeEventListener('keydown', this.keyPress);
  }
  
  render(){
    return (
    <div id="display">
      {arr.map((arr,i) =>(
        <div className="drum-pad" text={arr.key} key={i} id={arr.key} onClick={this.playSound}>{arr.key}
          <audio className="clip" key={i} src={arr.sound} id={arr.key} ref={this.audio}/>
         </div>
        ))}
    </div>
    );
  }
};

Tell us what’s happening:
I finally got keypress to work, it can tell if the user is pressing the correct keys but they all play the audio (the one assigned to Q). is there a way to make them play their needed audios without making a long if else statement?

Your code so far

class App extends React.Component{
  constructor(props){
    super(props);
    this.state = {
      play:false,
    };
    this.playSound= this.playSound.bind(this);
    this.keyPress = this.keyPress.bind(this);
  }
  
  playSound = (e)=>{
    this.setState({
      play:!this.state.play,
    });

    const audio = e.target.querySelector('audio');

    audio.play();
    audio.currentTime = 0;
  }
 
  keyPress(e) {
    const keyArr = [81,87,69,65,83,68,90,88,67];
    for (let i = 0;i<=keyArr.length;i++){
      if(e.keyCode === keyArr[i]){
        console.log(e.keyCode);
        
        this.setState({
          play:!this.state.play,
        });
        
        const audio = e.target.querySelector('audio');

        audio.play();
        audio.currentTime = 0;
      }
    }
    
  }
  
  componentDidMount(){
    document.addEventListener('keydown', this.keyPress);
  }
  componentWillMount(){
    document.removeEventListener('keydown', this.keyPress);
  }
  
  render(){
    return (
    <div id="display">
      {arr.map((arr,i) =>(
        <div className="drum-pad" text={arr.key} key={i} id={arr.key} onClick={this.playSound}>{arr.key}
          <audio className="clip" key={i} src={arr.sound} id={arr.key} ref={this.audio}/>
         </div>
        ))}
    </div>
    );
  }
};

Your browser information:

User Agent is: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0.

Challenge: Build a Drum Machine

Link to the challenge:

That’s happening because you are still trying to rely on event.taget to determine which audio to play.
In the case of a keypress however the target is the document.

An easy solution would be adding the “key” as data attribute to each element, so you can select the DOM node correctly.

However this does not feel very “react” as you are relying a lot on DOM and DOM interaction.

Have you considered using a fully managed JS solution with the Audio api?
Hope it helps :slight_smile:


p.s. componentWillMount is an error, did you mean componentWillUnmount?

Hi Marmiz, you have helped me out so much already. Using audio() works but it creates a new audio element every time, i was wondering if there is a way to trigger the drum pad <div> element which then calls the playSound. Is this possible and if so is there anything i should look into to accomplish this? thank you for your help.

I mean, you can definitely refactor that as you don’t really need to create a new audio on every click.

But regardless, if you want to interact with the div directly you can do so in React by using a ref.

Hope this helps :sparkles:

thank you :slightly_smiling_face: