如何使用Javascript按顺序运行可变数量的定时任务?
我正在为免费代码营中的经典电子记忆游戏Simon进行娱乐活动。我使用Web Audio API生成声音,我需要正确计时。
我需要start
一个声音,等待420毫秒,stop
声音,然后在开始下一个声音之前等待500毫秒。
当我只需要播放单个声音时,我可以使用setTimeout
使其正常工作,但我无法弄清楚如何让它等待播放后续声音。
这是显示问题的代码的子集。它可以正常使用1个声音,但是当它不止一个时,它会同时播放所有声音(除了第一个声音?):
var audioContext = new AudioContext();
var oscillator = null;
function playSound(frequency) {
oscillator = audioContext.createOscillator();
oscillator.type = 'square';
oscillator.connect(audioContext.destination);
oscillator.frequency.value = frequency;
oscillator.start();
}
function stopSound() {
oscillator.stop();
oscillator.disconnect(audioContext.destination);
oscillator = null;
}
var frequencies = [329.628, 220, 277.183, 164.814];
var turn = 1;
var soundCounter = 0;
var duration = 0;
if (turn <= 5) {
duration = 420;
} else if (turn <= 13) {
duration = 320;
} else {
duration = 220;
}
for (var i = 0; i < turn; i++) {
var freqId = Math.floor(Math.random() * frequencies.length);
var frequency = frequencies[freqId];
playSound(frequency);
setTimeout(function() { stopSound(); }, duration);
}
&#13;
谢谢。
如果没有@RyanZim,@ trincot和@ lee-daniel-crocker提供的优秀答案,我无法完成这项工作。这些答案都不能直接用于copy&amp;粘贴,这是一件好事,因为我应该学习一些东西:D我从所有答案中学习并使用了一些东西。因此,需要有一种方法可以为这种情况选择多个正确的答案。
好奇? http://codepen.io/VAggrippino/pen/WoBQXR
代码在某些地方很邋,,但它确实有效,我现在宁愿继续前进而不是完美。
答案 0 :(得分:2)
您需要使用递归函数。这是一个有效的例子:
var audioContext = new AudioContext();
var oscillator = null;
function playSound(frequency) {
oscillator = audioContext.createOscillator();
oscillator.type = 'square';
oscillator.connect(audioContext.destination);
oscillator.frequency.value = frequency;
oscillator.start();
}
function stopSound() {
oscillator.stop();
oscillator.disconnect(audioContext.destination);
oscillator = null;
}
var frequencies = [329.628, 220, 277.183, 164.814];
var turn = 5;
var soundCounter = 0;
var duration = 0;
if (turn <= 5) {
duration = 420;
} else if (turn <= 13) {
duration = 320;
} else {
duration = 220;
}
playSeq();
function playSeq(i) {
if (!i) i = 0;
var freqId = Math.floor(Math.random() * frequencies.length);
var frequency = frequencies[freqId];
playSound(frequency);
setTimeout(function() {
stopSound();
i++;
if (i < turn) playSeq(i);
}, duration);
}
for
循环在这里不起作用,因为循环将继续到下一次迭代,而不等待setTimeout
回调被执行。一个简单的例子:
for (var i = 0; i < 3; i++) {
// Schedules the code to run in the future:
setTimeout(function () { console.log('Hi'); }, 2);
// Continues to the next line:
console.log(i);
// Continues to the next iteration of the loop...
}
通过使用递归函数,js引擎将等待setTimeout
代码完成以继续下一次迭代。
无论何时使用setTimeout
或setInterval
,如果要等待超时完成,就不能依赖JS循环,必须使用递归函数。
答案 1 :(得分:1)
您需要异步循环,但您还需要存储生成的音符,以便您可以为游戏重复这些音符。
我还建议使用一个承诺,所以你知道回放何时结束。
这是一个工作片段(首先关闭你的音量):
var audioContext = new AudioContext();
var oscillator = null;
function playSound(frequency) {
oscillator = audioContext.createOscillator();
oscillator.type = 'square';
oscillator.connect(audioContext.destination);
oscillator.frequency.value = frequency;
oscillator.start();
}
function stopSound() {
oscillator.stop();
oscillator.disconnect(audioContext.destination);
oscillator = null;
}
var frequencies = [329.628, 220, 277.183, 164.814];
// You need to store the tones that were already generated, so
// the sequence remains the same once generated:
var sequence = [];
function playTurn(turn) {
return new Promise(function (resolve) {
// First complete the sequence:
while (sequence.length < turn) {
sequence.push(Math.floor(Math.random() * frequencies.length));
}
var duration = turn <= 5 ? 420
: turn <= 13 ? 320
: 220;
(function loop(i) {
if (i >= sequence.length) return resolve();
playSound(frequencies[sequence[i]]);
setTimeout(stopSound, duration);
setTimeout(loop.bind(null,i+1), duration*2.2);
})(0);
});
}
// Generate and play 4 notes:
playTurn(4).then(function() {
alert('done');
});
答案 2 :(得分:1)
以下是我可能会这样做的(在ES6中,您可能需要翻译/加载承诺库等):
// Using playSound(), stopSound() from question
let furElise = [
[ 329.63, 200 ],
[ 311.13, 200 ],
[ 329.63, 200 ],
[ 311.13, 200 ],
[ 329.63, 200 ],
[ 246.94, 200 ],
[ 293.66, 200 ],
[ 261.63, 200 ],
[ 220.00, 400 ]
];
function playNote(pitch, duration) {
return new Promise((resolve, reject) => {
playSound(pitch);
setTimeout(() => {
stopSound();
resolve(true);
}, duration);
});
}
function playTune(notes) {
if (notes.length > 0) {
playNote(notes[0][0], notes[0][1]).then(() => {
playTune(notes.slice(1));
});
}
}
playTune(furElise);
答案 3 :(得分:0)
(代表问题作者发布的解决方案,将其移至答案空间)。
我已经完成了!
如果没有@ RyanZim,@ trincot和@ lee-daniel-crocker提供的出色答案,我将无法完成。没有一个答案可直接用于复制和粘贴,这是一件好事,因为我应该学习的是:D我从所有答案中学习并使用了一些答案。
代码如下:
/* jshint esversion: 6 */
var Game = function() {
var audioContext = new window.AudioContext();
var oscillator = null;
var gainNode = null;
var winner = false;
var that = this;
this.power = false;
this.started = false;
this.playerTurn = false;
this.strictMode = false;
this.sequence = [];
this.playerSequence = [];
this.turn = 0;
var winningSong = [];
winningSong.push($('.button.red').get(0));
winningSong.push($('.button.yellow').get(0));
winningSong.push($('.button.blue').get(0));
winningSong.push($('.button.green').get(0));
winningSong.push($('.button.green').get(0));
winningSong.push($('.button.green').get(0));
winningSong.push($('.button.red').get(0));
winningSong.push($('.button.yellow').get(0));
var playSound = function(frequency) {
if (!that.power || !that.started || oscillator !== null) return false;
gainNode = audioContext.createGain();
oscillator = audioContext.createOscillator();
oscillator.type = 'square';
oscillator.frequency.value = frequency;
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
gainNode.gain.value = 0.1;
oscillator.start();
};
var stopSound = function() {
if (!oscillator || !gainNode) return false;
oscillator.stop();
gainNode.disconnect(audioContext.destination);
oscillator = null;
gainNode = null;
};
var randomButton = function() {
var $buttons = $('.button');
var buttonNumber = Math.floor(Math.random() * $buttons.length);
return $buttons[buttonNumber];
};
var playTone = function(frequency, duration) {
return new Promise((resolve, reject) => {
playSound(frequency);
setTimeout(() => {
stopSound();
resolve(true);
}, duration);
});
};
var playSequence = function(buttons) {
if (buttons.length === 0) {
that.playerTurn = true;
if (winner) {
that.start();
}
return that.playerTurn;
}
// Assign the duration of the tone based on how many
// turns we've already played.
// ref: http://www.waitingforfriday.com/?p=586#Sound_frequencies_and_timing
var duration = that.sequence.length <= 5 ? 420
: that.sequence.length <= 13 ? 320
: 220;
if (winner) {
duration = 100;
}
var button = buttons[0];
if (!winner) {
if (buttons.length === that.sequence.length) console.log('+++++');
console.log((that.sequence.length - buttons.length) + ':' + $(button).data('color'));
if (buttons.length === 1) console.log('-----');
}
that.activateButton(button);
var frequency = $(button).data('frequency');
playTone(frequency, duration).then(() => {
that.deactivateButton(button);
playSequence(buttons.slice(1));
});
};
var takeTurn = function() {
if ( that.turn === 20 ) {
winner = true;
playSequence(winningSong);
return true;
}
that.turn++;
// Add a new button to the sequence.
that.sequence.push(randomButton());
// If necessary, prepend the turn number with a zero
// before updating the display.
var displayString = that.turn < 10 ? '0' + that.turn : '' + that.turn;
$('.display').html(displayString);
playSequence(that.sequence);
};
this.activateButton = function(button) {
// If the game hasn't been turned on or started, don't do
// anything.
if (!that.power || !that.started) return false;
// Create a jQuery object from the DOM element reference.
var $button = $(button);
// Light up the active button.
$button.addClass('active');
};
this.deactivateButton = function(e) {
var $button = $(e.target || e);
stopSound();
$button.removeClass('active');
if (that.playerTurn && that.playerSequence.length === that.sequence.length) {
that.playerTurn = false;
that.playerSequence = [];
setTimeout(takeTurn, 500);
}
};
this.pressButton = function(e) {
if (!that.power || !that.started || !that.playerTurn) return false;
// Add the pressed button to the player's button sequence.
that.playerSequence.push(e.target);
that.activateButton(e.target);
playSound($(e.target).data('frequency'));
// Check if the player's button matches the computer's
// button at the same position in the sequence.
var playerButton = e.target;
var computerButton = that.sequence[that.playerSequence.length - 1];
// If the player's button doesn't match, play the
// failure sound and end the game.
// ref: http://www.waitingforfriday.com/?p=586#Sound_frequencies_and_timing
if (playerButton !== computerButton) {
that.playerTurn = false;
that.playerSequence = [];
setTimeout(function() {
that.deactivateButton(e.target);
stopSound();
playSound(42);
if (that.strictMode) {
that.started = false;
setTimeout(stopSound, 1500);
} else {
setTimeout(function() {
stopSound();
playSequence(that.sequence);
}, 500);
}
}, 200);
}
};
this.start = function() {
if (!that.power) return false;
winner = false;
that.started = true;
that.turn = 0;
$('.display').html('00');
that.sequence = [];
that.playerSequence = [];
takeTurn();
};
this.toggleStrict = function() {
if (!that.power) return false;
that.strictMode = !that.strictMode;
$('.strict').toggleClass('on');
}
};
$(function() {
// Assign the tone frequency based on the button's color.
$('div.button.green').data('frequency', '329.628');
$('div.button.red').data('frequency', '220');
$('div.button.yellow').data('frequency', '277.183');
$('div.button.blue').data('frequency', '164.814');
/*
// From http://www.waitingforfriday.com/?p=586#Sound_frequencies_and_timing
$buttons.filter('.green').data('frequency', '415');
$buttons.filter('.red').data('frequency', '310');
$buttons.filter('.yellow').data('frequency', '252');
$buttons.filter('.blue').data('frequency', '209');
// From Wikipedia: "Simon (game)"
$buttons.filter('.green').data('frequency', '659.255');
$buttons.filter('.red').data('frequency', '440');
$buttons.filter('.yellow').data('frequency', '277.18');
$buttons.filter('.blue').data('frequency', '329.628');
// From the Free Code Camp forums:
https://forum.freecodecamp.com/t/better-audiocontext-frequencies-for-simon/69483/2?u=vaggrippino
*/
var $buttons = $('div.button');
var game = new Game();
$buttons.on('mousedown', game.pressButton);
$buttons.on('mouseup mouseleave', game.deactivateButton);
$('.power .switch').on('click', function() {
game.power = !game.power;
$('.power .switch').toggleClass('on');
$('.display').html(game.power ? '--' : '');
});
$('.start').on('click', game.start);
$('.strict').on('click', game.toggleStrict);
});
http://codepen.io/VAggrippino/pen/WoBQXR
该代码在某些地方草率,但它可以工作,我宁愿继续而不是立即完善它。