鼓机<音频>滞后

时间:2020-08-09 05:12:44

标签: javascript html

我正在组合一个鼓机/音序器,虽然音序器的主要功能运行良好,但是我在每个鼓单元中嵌入的音频在首次播放音序器时确实存在明显的滞后。似乎在第一个节拍之后会自行纠正,并且一切正常,直到将其他声音添加到模式中为止。

我有三行表格单元格,每一行代表不同的鼓声。当用户使用所有可用声音构建鼓模式时,循环最终似乎不同步,有些声音播放的时间比其他声音晚了几分之一秒,但后来进行了纠正。我主要担心的是样本的回放不一致。

我已将标签嵌入到预加载属性设置为auto的元素中。

<table class="pad">
        <tbody>
        <tr>
            <td class="kick sounds">
                <audio preload="auto"  src="https://raw.githubusercontent.com/wesbos/JavaScript30/master/01%20-%20JavaScript%20Drum%20Kit/sounds/kick.wav"></audio>
            </td>
        </tr>
      </tbody>
</table>

因此,每行有8个表格单元,每个单元具有与上述相同的格式(嵌入了元素)。诚然,我可以想象到我的结构方式效率很低,并且使用网络音频API会更好,但是我还没有学习过API。我可以在JS中做些什么,使这些音频样本的播放更快?

编辑:这是音序器代码。它循环遍历每个元素,并检查是否选择了一个单元格。如果是这样,则播放该元素的音频文件。如果不是,则跳至下一列。

class Sequencer {
    playButton = btn;
    clearButton = clear;
    sounds = Array.from(sounds);
    kicks = Array.from(kicks);
    hihats = Array.from(hihats);
    snares = Array.from(snares);
    currentBeatIndexKick = 0;
    currentBeatIndexHiHat = 0;
    currentBeatIndexSnare = 0;
    isPlaying = false;

    constructor(msPerBeat) {
        this.msPerBeat = msPerBeat;
        this.playButton.addEventListener('click', () => this.toggleStartStop())
        this.clearButton.addEventListener('click', () => this.clear())
        this.sounds.forEach(sound => {
            sound.addEventListener('click', e => {
                if (((e.target.classList.contains('kick')) || (e.target.classList.contains('hihat')) || (e.target.classList.contains('snare'))) && !e.target.classList.contains('selected')) {
                    e.target.classList.add('selected');
                } else {
                    e.target.classList.remove('selected');
                }
            })
        })
    }
    
    toggleStartStop() {
        if (this.isPlaying) {
            this.stop();
        } else {
            this.start();
        }
    }

    clear() {
        this.kicks.forEach(kick => {
            if (kick.classList.contains('selected')) {
                kick.classList.remove('selected');
            }
        });
        hihats.forEach(hihat => {
            if (hihat.classList.contains('selected')) {
                hihat.classList.remove('selected');
            }
        });
        snares.forEach(snare => {
            if (snare.classList.contains('selected')) {
                snare.classList.remove('selected');
            }
        });
        this.stop();
        console.clear();
    }

    stop() {
        this.isPlaying = false;
        this.currentBeatIndexKick = 0;
        this.currentBeatIndexHiHat = 0;
        this.currentBeatIndexSnare = 0;
        this.playButton.innerText = 'Play'; 
    }

    start() {
        this.isPlaying = true;
        this.playCurrentNoteAndSetTimeoutKick() // kicks
        this.playCurrentNoteAndSetTimeoutHiHats() // hihats
        this.playCurrentNoteAndSetTimeoutSnares() // snares
        this.playButton.innerText = 'Stop';
    }

    playCurrentNoteAndSetTimeoutKick() {
        if (this.isPlaying && this.kicks[this.currentBeatIndexKick].classList.contains('selected')) {
            this.kicks[this.currentBeatIndexKick].childNodes[1].play();
            setTimeout(() => {
                this.toNextBeatKicks();
                this.playCurrentNoteAndSetTimeoutKick(); 
            }, this.msPerBeat)
        }
        if (this.isPlaying && !this.kicks[this.currentBeatIndexKick].classList.contains('selected'))
            setTimeout(() => {
                this.toNextBeatKicks();
                this.playCurrentNoteAndSetTimeoutKick();
            }, this.msPerBeat)
    }

    playCurrentNoteAndSetTimeoutHiHats() {
        if (this.isPlaying && this.hihats[this.currentBeatIndexHiHat].classList.contains('selected')) {
            this.hihats[this.currentBeatIndexHiHat].childNodes[1].play();
            setTimeout(() => {
                this.toNextBeatHiHats();
                this.playCurrentNoteAndSetTimeoutHiHats();
            }, this.msPerBeat)
        }
        if (this.isPlaying && !this.hihats[this.currentBeatIndexHiHat].classList.contains('selected'))
            setTimeout(() => {
                this.toNextBeatHiHats();
                this.playCurrentNoteAndSetTimeoutHiHats();
            }, this.msPerBeat)
    }

    playCurrentNoteAndSetTimeoutSnares() {
        if (this.isPlaying && this.snares[this.currentBeatIndexSnare].classList.contains('selected')) {
            this.snares[this.currentBeatIndexSnare].childNodes[1].play();
            setTimeout(() => {
                this.toNextBeatSnares();
                this.playCurrentNoteAndSetTimeoutSnares(); 
            }, this.msPerBeat)
        }
        if (this.isPlaying && !this.snares[this.currentBeatIndexSnare].classList.contains('selected'))
            setTimeout(() => {
                this.toNextBeatSnares();
                this.playCurrentNoteAndSetTimeoutSnares(); 
            }, this.msPerBeat)
    }

    toNextBeatKicks() {
        this.currentBeatIndexKick = ++this.currentBeatIndexKick % this.kicks.length; 
    }

    toNextBeatHiHats() {
        this.currentBeatIndexHiHat = ++this.currentBeatIndexHiHat % this.hihats.length; 
    }

    toNextBeatSnares() {
        this.currentBeatIndexSnare = ++this.currentBeatIndexSnare % this.snares.length; 
    }
    
}

const sequencer = new Sequencer(213)

1 个答案:

答案 0 :(得分:0)

我认为您可能需要做的是以编程方式播放音频文件。在不给我们更多代码访问权限的情况下,很难理解音序器的工作方式,我采用的方法是在用户选择音序后以编程方式播放音频文件。一旦在音序器上选择了步骤,我就会分配一个变量来播放。

var audio = new Audio('audio_file.mp3');
audio.play();

audio.play将在序列上调用。不确定是否有帮助,但这是我认为您所指的api的一部分。