drenl
drenl

Reputation: 1333

Passing arguments into setTimeout: one method does not work (JS)

I'm studying how to pass arguments into a setTimeout (as advised here.) In the context of a while-loop which activates an HTML5 Audio element.

This works:

window.setTimeout(playTone, time, phrasePitches[x]);

Curiously, this does not work:

window.setTimeout(function(){
    playTone(phrasePitches[x]);
}, time);

In the console, the timeouts occur as scheduled, each displaying:

TypeError: Cannot set property 'currentTime' of null

So for some reason, the second method does not want to accept the array... any idea what's going on here?

EDIT... the full code:

function playTone(tone){
        var tone = document.getElementById(tone);
        tone.currentTime = 0;
        tone.play();
    };

var pitchSet = new Array ("C3","D3","E3","F3","G3","A3","B3","C4");

fyshuffle (pitchSet);  // The Fischer-Yater shuffle function
    var i = 0;
    var phrasePitches = new Array();
    while (i < 4) { 
        phrasePitches.push(pitchSet[i]);
        i++; 
    }

var x=0;
var time = 0;
while(x<4){
//  window.setTimeout(playTone, time, phrasePitches[x]);  // This works.
    window.setTimeout(function(){
        playTone(phrasePitches[x]);
    }, time);
    time += 2000;
    x++;        
}

Upvotes: 1

Views: 60

Answers (1)

zer00ne
zer00ne

Reputation: 43880

Audiosprites

This demo uses an audiosprite⁰. I used Audacity program to concatenate small MP3 files into one larger MP3 file✲. Then I took note on each fragment's start and end time in seconds. There are other ways to generate an audiosprite if the manual way seems arduous.

✲ I found MergeMP3 after creating the audiosprite, so I haven't tested it yet, but it looks far easier to use than Audacity.

Details are commented in demo and notes corresponding to references

Demo

// Reference the <audio>
var fx = document.querySelector('#fx');

/* Reference the <fieldset> (can be any element with an
|| endtag: <tag></tag>
*/
var panel = document.querySelector('#panel');

/* Map the audiosprite's frags in an object of arrays 
|| Each array represents a frag which consists of: 
|| 'key': [start time, end time]¹
|| TODO: replace object literal with ES6 Map²
*/
var sprite = {
  'all': [0, 27],
  'click': [0, .45],
  'bell': [.65, 7.4],
  'elevator': [7.5, 9.7],
  'sonar': [10, 27]
};

// Declare default value of end time
var end = 0;

// Register the <audio> on the loadeddata event³...
fx.addEventListener('loadeddata', function(e) {

  /* Object.keys chained to .forEach()⁴ iterates through
  || 'sprite' properties (key/value pairs).
  */
  Object.keys(sprite).forEach(function(key, index) {

    /* On each iteration, generate a <button> inside the
    || <fieldset>
    || Note the use of a template literal⁵
    || TODO: Delegate⁶ click events to panel to avoid
    || inline attribute event handlers 
    */
    panel.innerHTML += `<button onclick="effect('${key}')">${key}</button>`;
  });
}, false);

// Register the <audio> on timeupdate event⁷...
fx.addEventListener('timeupdate', function(e) {

  /* timeupdate occurs when playing position changes
  || check if the currentTime⁸ property of <audio> 
  || is more than the value of 'end' 
  || ('end' is the second value of a frag: sprite[key][1])
  || If it is, pause the <audio>
  */
  if (fx.currentTime > end) {
    fx.pause();
  }
}, false);

/* This function passes the string value of 'key' 
|| in sprite object
|| Each 'key' has a value of an array containing an start
|| time and an end time. 
|| In short, 'key' is a reference to a frag.
*/
function effect(key) {

  // if the 'key' in 'sprite' object exists...
  if (sprite[key]) {

    /* The currentTime of <audio> is 
    || the first value of 'key' 
    */
    fx.currentTime = sprite[key][0];

    // Assign 'end' the second value of 'key' 
    end = sprite[key][1];

    /* Play <audio>
 	  || Note at this point, if done conventionally by
    || reloading the .src for each change to a separate
    || frag, fx.load() would need to be invoked
	  || as well, thereby incurring another HTTP request
	  */
    fx.play();
  }
}
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no">
  <style>

  </style>
</head>

<body>
  <section>
    <!-- 
An audiosprite⁰ is an audio file that is actually a
concatenation of multiple sound files 
(from now on refered to as frags) played at 
different times throughout its duration. 

The advantages are that:
- the file is downloaded once then cached
- the src doesn't change thus only one HTTP request needed

If done the conventional way with multiple frags there 
would be:
- several downloads for smaller files 
- the added HTTP request everytime the src attribute changes
  its url
-->
    <audio id='fx' src='http://vocaroo.com/media_command.php?media=s0L5VMshEG3E&command=download_mp3' controls></audio>
    <fieldset id='panel'>
      <legend>Effects</legend>
    </fieldset>
  </section>
  <!-- 
If using JS on the same page rather than externally,
place the JS here within a <script> block:
<script>
...:::JS code here:::...
</script>
-->

</body>

</html>


References

audiosprite

¹ key/value pairs

² ES6 Map ✎

³ loadeddata event

Object.keys().forEach()

template literal

Event Delegation ✎

timeupdate event

currentTime

✎ Mentioned as TODO, it has not been implemented in this demo.

Upvotes: 1

Related Questions