DrumMachine one audio problem

Hey guys, please help me to understand.

I’m building in React Drummachine. App.js just rendering “DrumMachine” component, which is JS file.
In DurmMachine here is the code

import React from 'react'

const data =[
  { id: 'snare', letter: 'Q', src: 'https://www.myinstants.com/media/sounds/snare.mp3' },
  { id: 'bass 1', letter: 'W', src: 'https://www.myinstants.com/media/sounds/bass-drum.mp3' },
  { id: 'bongo', letter: 'E', src: 'http://tipiwiki.free.fr/snd/Percussion(4e)2.wav' },
  { id: 'tom tom', letter: 'A', src: 'http://www.denhaku.com/r_box/sr16/sr16tom/loelectm.wav' },
  { id: 'bass 3', letter: 'S', src: 'http://billor.chsh.chc.edu.tw/sound/bass4.wav' },
  { id: 'shotgun', letter: 'D', src: 'http://david.guerrero.free.fr/Effects/ShotgunReload.wav' },
  { id: 'high hat', letter: 'Z', src: 'http://www.denhaku.com/r_box/tr707/closed.wav' },
  { id: 'drum hit', letter: 'X', src: 'http://www.masterbits.de/sc_docu/sounds1/1TM00013.MP3' },
  { id: 'laser', letter: 'C', src: 'http://www.sa-matra.net/sounds/starcontrol/Umgah-Backzip.wav'  },
]

class DrumChange extends React.Component{
   componentDidMount(){
     console.log(this.audio)
   }

    handleClick =() => {
        console.log('playing')
        this.audio.play()
        this.audio.currenTime=0;
        
    }

  render() {
    
    return (
      <container id='drum-machine'>
        <container id='display'>
          Showing string of playable audio
        </container>
       <div>
         {data.map(d => <button className='drum-pad' 
         id={d.id} 
         key={d.id} 
         letter={d.letter}
         onClick={this.handleClick}>
           <audio className='clip' 
           id={d.letter} 
           src={d.src} 
           ref={ref => this.audio = ref} />
         <p>{d.letter}</p> </button> )}
       </div>

      </container>
    )
  }
}

export default DrumChange;

Problem is that HandleClick function plays one last audio source from the data array.
I see that audio elements mounted with different links of the sources.

How to fix this or why it happens? I just don’t understand

sorry what do you mean by one last audio source? do you have a link to the project?

Thanks for the reply. I pushed to the git; here is the link: https://github.com/TyroniUA/DrumMachine

Will try to explain in detail:

on that screen shot with mouse coursor I highligted the console.log output of ‘this.audio’ It holds the one last audio element from data array, which was mapped with {data.map(d=> {<button…/>})} in render(){return ()} of the Drumchange.JS

I’m not sure appending audio object and p inside a button is the way to go in the first place.

Nevertheless, what this in your handleClick refers to?

basically in element I make a reference to it with ref= {ref=> this.audio = ref} and in handleClick = ()=>{
this.audio.play()}

Want to clarify: all buttons have their own unique elements and audio is not exception. I’ve checked with console.log: each button has audio element with unique audio src.

So my guess that somethings with handleClick and setting a refference with ref ={ref => this.audio = ref} but I do not get an idea how to fix it

I don’t think refs work like that. You are just getting a ref to the last element inserted into the DOM, not a unique one for each element. If you log this.audio in the click handler you can see you always just get the last element no matter what button you clicked. Not sure if refs is the right approach here to identify the target of the click.

If you remove the <p> inside the button you can get to the audio element pretty easily by just passing the event to the click handler and then getting the event target and firstChild (e.target.firstChild).

Here is a codesandbox with your code from Github using the event target.

Thank you very much for your insights! Next question then, if we can’t make a reference to that audio, how then handle key pressed with audio played? I can track key pressed with ComponentDidMount, but got stuck with implementation in my case.

I’ve seen others, but currently having lack of understanding and knowledge to implement in my solution, which is different.

Can you show what you have tried. I didn’t see any new commits in your repo.

I think most attach an event listener to the window or document and then in the event handler figure out what key was pressed (e.key). You can then get the audio element by id using the value from e.key and call play() on it.

Pushed changes. Basically there are few lines of codes.
What my thoughts are, please correct me if I’m wrong.

How I wrote the code:

  • I create buttons with map function but basically store them nowhere.
  • With that I have issues in my understanding to access this audio element.

Basically in normal C# or JS i would store object and then accessed it with the objectName.value and played the audio.

But I guess I’m thinking more like C# in Unity project, which is totally wrong approach here.

import React from 'react'

const data =[
  { id: 'snare', letter: 'Q', src: 'https://www.myinstants.com/media/sounds/snare.mp3', ref: React.createRef(),
},
  { id: 'bass 1', letter: 'W', src: 'https://www.myinstants.com/media/sounds/bass-drum.mp3', ref: React.createRef() },
  { id: 'bongo', letter: 'E', src: 'http://tipiwiki.free.fr/snd/Percussion(4e)2.wav', ref: React.createRef()},
  { id: 'tom tom', letter: 'A', src: 'http://www.denhaku.com/r_box/sr16/sr16tom/loelectm.wav', ref: React.createRef() },
  { id: 'bass 3', letter: 'S', src: 'http://billor.chsh.chc.edu.tw/sound/bass4.wav', ref: React.createRef() },
  { id: 'shotgun', letter: 'D', src: 'http://david.guerrero.free.fr/Effects/ShotgunReload.wav', ref: React.createRef() },
  { id: 'high hat', letter: 'Z', src: 'http://www.denhaku.com/r_box/tr707/closed.wav', ref: React.createRef() },
  { id: 'drum hit', letter: 'X', src: 'http://www.masterbits.de/sc_docu/sounds1/1TM00013.MP3', ref: React.createRef() },
  { id: 'laser', letter: 'C', src: 'http://www.sa-matra.net/sounds/starcontrol/Umgah-Backzip.wav', ref: React.createRef()  },
]

class DrumChange extends React.Component{
   componentDidMount(){
     console.log(this.audio)
     document.addEventListener('keydown', this.handlePress)
   }
componentWillUnmount(){
  document.removeEventListener('keydown', this.handleKeyDown)
}
    handlePress = event => {
      
      if(event.keyCode === 83){
        console.log(event.keyCode)
      }
    }


    handleClick =(e) => {
        console.log('playing')
        const target = e.target.firstChild;
        target.play();
        target.currentTime =0;
        
    }

  render() {
    
    console.log(this.arr);
    
    return (
      <container id='drum-machine' >
        
        <container id='display'>
          Showing string of playable audio
        </container>
       <div >
         {data.map((d, index) => <button className='drum-pad' 
         id={d.id} 
         key={d.id} 
         letter={d.letter}
         onClick={e => this.handleClick(e)}
         >
           <audio className='clip' 
           id={d.letter} 
           src={d.src} 
           ref={d.ref} />
         {d.letter} </button> )}
       </div>

      </container>
    )
  }
}

export default DrumChange;

Try using event.key instead of event.keyCode. Capture the event and log it out to the console. Look at it and think about how you would use it with document.getElementById to get the audio element using the event.key.

I know that it will show the keys themselves. Problem is I really don’t ‘click’ how to connect it. Usually I would do just for loop (probably it’s beginner’s tactic)

Link for the document.getElementById is this https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById

So if I understand correctly, I will make it:
let sound = document.getElementById(event.key)
sound.play()

Yep, but you need to take the case of the letter into account (case of the element id vs case of event.key)

Sorry, can you explain in more detail, because I guess I didn’t understand it clearly…

If you uppercase the event key it will match the id on the audio element. Also, remember to check the element is “truthy” (i.e. not null) before calling play on it.

You pretty much have the code now, so I don’t think this is much of a spoiler, but you can just choose not to look if you don’t want to.

Thank you.
Next question goes to string. I do not get why I can’t create some property like ‘name={d.id}’ in audio element when do data.map and then retrieve this audio.name as a string. When I do for example:

let title = audio.id - and do console.log it - the result is the letter.
But if I declare < audio… audName={d.id} /> and in function:
let title = audio.audName - result is undefined.

Can you explain why it happen? I cannot declare my variables in the object? How then I should retrieve the string of the song ID and transfer it to the text?

UPD: I pushed code to the git. I making string with a click, simply creating new function:

handleDisplay = display => this.setState({display})

and in

  handleClick = (e) => {
    console.log('playing')
    const target = e.target.firstChild;
    target.play();
    target.currentTime = 0;
    this.handleDisplay(e.target.id)

  }

But in keypressed event we retrieve and audio element. So basically my question:

  • can we retrieve button element of the pressed key?
  • If no => can we create simply a property in audio element with ID value which we can retrieve in the handlePress function

If you want the id of the parent element of the audio element (i.e. the button) inside the keydown handler you can use audio.parentElement.id

Sir you save my nerves! Thank you very much. And sorry for probably stupid questions. Just for advice: to get familiar with all function/methods do you recommend MDN or some other resources? For react currenty I’m reading react.js - official documentation, but may be you have other opinion.

There are no stupid questions when learning (well there might be but I like the saying nevertheless).

You can’t go wrong with MDN and the official React docs are very good. You can always look for more articles and tutorials, just don’t go too crazy or you will end up reading/viewing more than writing.

1 Like