javascript - Web 音频振荡器仅在 Firefox 中点击

标签 javascript firefox web-audio-api

我正在尝试使用网络音频振荡器创建一个简单的节拍器,这样就不需要外部音频文件了。我通过快速升高和降低振荡器的音量来创建节拍器的声音(因为您不能多次使用 start() 和 stop()),然后以设定的时间间隔重复该功能。它最终听起来像一 block 漂亮的小木 block 。

下面的代码在 Chrome、Safari 和 Opera 中工作/听起来很棒。但在 Firefox 中,当音量增加时会出现令人讨厌的间歇性“咔嗒”声。我试过更改启动/释放时间以消除咔哒声,但它们必须非常非常长才能持续消失。事实上,振荡器听起来就像一个持续的音符。

var audio = new (window.AudioContext || window.webkitAudioContext)();
var tick = audio.createOscillator();
var tickVol = audio.createGain();

tick.type = 'sine'; 
tick.frequency.value = 1000;
tickVol.gain.value = 0; //setting the volume to 0 before I connect everything
tick.connect(tickVol);
tickVol.connect(audio.destination);
tick.start(0);

var metronome = {
    start: function repeat() {
        now = audio.currentTime;

        //Make sure volume is 0 and that no events are changing it
        tickVol.gain.cancelScheduledValues(now);
        tickVol.gain.setValueAtTime(0, now);

        //Play the osc with a super fast attack and release so it sounds like a click
        tickVol.gain.linearRampToValueAtTime(1, now + .001);
        tickVol.gain.linearRampToValueAtTime(0, now + .001 + .01);

        //Repeat this function every half second
        click = setTimeout(repeat, 500);
    },
    stop: function() {
        if(typeof click !== 'undefined') {
            clearTimeout(click);
            tickVol.gain.value = 0;
        }
    }
}

$("#start").click(function(){
  metronome.start();
});

$("#stop").click(function(){
  metronome.stop();
});

Codepen

有没有办法让 FF 听起来像其他 3 种浏览器?

最佳答案

我在最新的 Opera 中遇到了完全相同的问题,发现问题出在各个声音的“十进制时间长度”上。

我写了一个莫尔斯电码翻译器,和你的一样,它只是通过 createOscillator 创建的一系列简单的短声音/蜂鸣声。

使用莫尔斯电码,您可以根据 codex 或 paris 等 5 个字母长的单词计算速度(每分钟单词数)。

为了让每分钟 20 或 30 巴黎准确地在分钟内完成,我必须使用声音时间长度,例如 0.61。在 Opera 中,这会导致“声音点击结束”。将其更改为 0.6 后,所有浏览器中的点击都消失了 - 除了 Firefox。

我已经在声音之间尝试了 freq = 0 和 gain = 0,但在 FF 中仍然在最后听到咔哒声,而且我对网络音频的了解还不够多,无法尝试其他任何方法。

另一方面,我注意到您正在使用循环和超时来到达下一个滴答声。您是否尝试过“Oscillator onended function”?我将它与一个简单的计数器增量和可变长度的空白声音/音符一起使用。想看的话就到我JS的最后吧。

**更新 - 我一直在摆弄 setValueAtTime() 和 linearRampToValueAtTime() 并且似乎已经解决了点击问题。滚动到脚本底部以查看示例。 **

(function(){

/* Morse Code Generator & Translator - Kurt Grigg 2003 (Updated for sound and CSS3) */

var d = document;
d.write('<div class="Mcontainer">'
+'<div class="Mtitle">Morse Code Generator Translator</div>'
+'<textarea id="txt_in" class="Mtxtarea"></textarea>'
+'<div class="Mtxtareatitle">Input</div>'
+'<textarea id="txt_out" class="Mtxtarea" style="top: 131px;"></textarea>'
+'<div class="Mtxtareatitle" style="top: 172px;">Output</div>'
+'<div class="Mbuttonwrap">'
+'<input type="button" class="Mbuttons" id="how" value="!">'
+'<input type="button" class="Mbuttons" id="tra" value="translate">'
+'<input type="button" class="Mbuttons" id="ply" value="play">'
+'<input type="button" class="Mbuttons" id="pau" value="pause">'
+'<input type="button" class="Mbuttons" id="res" value="reset"></div>'
+'<select id="select" class="Mselect">' 
+'<option value=0.07 selected="selected">15 wpm</option>'
+'<option value=0.05>20 wpm</option>'
+'<option value=0.03>30 wpm</option>'
+'</select>'
+'<div class="sliderWrap">volume <input id="volume" type="range" min="0" max="1" step="0.01" value="0.05"/></div>'
+'<div class="Mchckboxwrap">'
+'<span style="text-align: right;">separator <input type="checkbox" id="slash" class="Mchckbox"></span>'
+'</div>'
+'<div id="about" class="Minfo">'
+'<b>Input morse</b><br>'
+'<ul><li>Enter morse into input box using full stop (period) and minus sign (hyphen)</li>'
+'<li>Morse letters must be separated by 1 space</li>'
+'<li>Morse words must be separated by 3 or more spaces</li>'
+'<li>You can use / to separate morse words. There must be at least 1 space before and after each separator used</li>'
+'</ul>'
+'<b>Input text</b><br>'
+'<ul class="Mul"><li>Enter text into input box</li>'
+'<li>Characters that cannot be translated will be ignored</li>'
+'<li>If morse and text is entered, the converter will assume morse mode</li></ul>'
+'<input type="button" value="close" id="clo" class="Mbuttons">'
+'</div><div id="mdl" class="modal"><div id="bdy"><div id="modalMsg">A MSG</div><input type="button" value="close" id="cls" class="Mbuttons"></div></div></div>');

var ftmp = d.getElementById('mdl');
var del;

d.getElementById('tra').addEventListener("click", function(){convertToAndFromMorse(txtIn.value);},false);
d.getElementById('ply').addEventListener("click", function(){CancelIfPlaying();},false);
d.getElementById('pau').addEventListener("click", function(){stp();},false);
d.getElementById('res').addEventListener("click", function(){Rst();txtIn.value = '';txtOt.value = '';},false);


d.getElementById('how').addEventListener("click", function(){msgSelect();},false);
d.getElementById('clo').addEventListener("click", function(){fadeOut();},false);

d.getElementById('cls').addEventListener("click", function(){fadeOut();},false);
d.getElementById('bdy').addEventListener("click", function(){errorSelect();},false);

var wpm = d.getElementById('select');
wpm.addEventListener("click", function(){wpMin()},false);

var inc = 0;
var playing = false; 
var txtIn = d.getElementById('txt_in');
var txtOt = d.getElementById('txt_out');
var paused = false;
var allowed = ['-','.',' '];
var aud;
var tmp = (window.AudioContext || window.webkitAudioContext)?true:false;
if (tmp) {
    aud = new (window.AudioContext || window.webkitAudioContext)();
}
var incr = 0;
var speed = parseFloat(wpm.options[wpm.selectedIndex].value);
var char = [];
var alphabet = [["A",".-"],["B","-..."],["C","-.-."],["D","-.."],["E","."],["F","..-."],["G","--."],["H","...."],["I",".."],["J",".---"],
    ["K","-.-"],["L",".-.."],["M","--"],["N","-."],["O","---"],["P",".--."],["Q","--.-"],["R",".-."],["S","..."],["T","-"],["U","..-"],
    ["V","...-"],["W",".--"],["X","-..-"],["Y","-.--"],["Z","--.."],["1",".----"],["2","..---"],["3","...--"],["4","....-"],["5","....."],
    ["6","-...."],["7","--..."],["8","---.."],["9","----."],["0","-----"],[".",".-.-.-"],[",","--..--"],["?","..--.."],["'",".----."],["!","-.-.--"],
    ["/","-..-."],[":","---..."],[";","-.-.-."],["=","-...-"],["-","-....-"],["_","..--.-"],["\"",".-..-."],["@",".--.-."],["(","-.--.-"],[" ",""]];

function errorSelect() {
    txtIn.focus();
}

function modalSwap(msg) {
    d.getElementById('modalMsg').innerHTML = msg;
}

function msgSelect() {
    ftmp = d.getElementById('about');
    fadeIn(); 
}

function fadeIn() {
    ftmp.removeEventListener("transitionend", freset);
    ftmp.style.display = "block";
    del = setTimeout(doFadeIn,100);
}

function doFadeIn() {
    clearTimeout(del);
    ftmp.style.transition = "opacity 0.5s linear";
    ftmp.style.opacity = "1";
}

function fadeOut() {
    ftmp.style.transition = "opacity 0.8s linear";
    ftmp.style.opacity = "0";
    ftmp.addEventListener("transitionend",freset , false);
}

function freset() {
    ftmp.style.display = "none";
    ftmp.style.transition = "";
    ftmp = d.getElementById('mdl');
}

function stp() {
    paused = true;
}

function wpMin() {
    speed = parseFloat(wpm.options[wpm.selectedIndex].value);
}

function Rst(){ 
    char = [];
    inc = 0;
    playing = false;
    paused = false;
}

function CancelIfPlaying(){
    if (window.AudioContext || window.webkitAudioContext) {paused = false;
        if (!playing) { 
            IsReadyToHear();
        }
        else {
            return false;
        }
    }
    else {
        modalSwap("<p>Your browser doesn't support Web Audio API</p>");
        fadeIn();
        return false;
    }
}

function IsReadyToHear(x){
    if (txtIn.value == "" || /^\s+$/.test(txtIn.value)) {
        modalSwap('<p>Nothing to play, enter morse or text first</p>');
        fadeIn();
        txtIn.value = '';
        return false;
    }
    else if (char.length < 1 && (x != "" || !/^\s+$/.test(txtIn.value)) && txtIn.value.length > 0) {
        modalSwap('<p>Click Translate button first . . .</p>');
        fadeIn();
        return false;
    }
    else{
        playMorse();
    }
}

function convertToAndFromMorse(x){
    var swap = [];
    var outPut = "";
    x = x.toUpperCase();

    /* Is input empty or all whitespace? */
    if (x == '' || /^\s+$/.test(x)) {
        modalSwap("<p>Nothing to translate, enter morse or text</p>");
        fadeIn();
        txtIn.value = '';
        return false;
    }

    /* Remove front & end whitespace */
    x = x.replace(/\s+$|^\s*/gi, ''); 
    txtIn.value = x;
    txtOt.value = "";

    var isMorse = (/(\.|\-)\.|(\.|\-)\-/i.test(x));// Good enough.

    if (!isMorse){
        for (var i = 0; i < alphabet.length; i++){
            swap[i] = [];
            for (var j = 0; j < 2; j++){
                swap[i][j] = alphabet[i][j].replace(/\-/gi, '\\-');
            }
        }
    }

    var swtch1 = (isMorse) ? allowed : swap;
    var tst = new RegExp( '[^' + swtch1.join('') + ']', 'g' ); 
    var swtch2 = (isMorse)?' ':'';
    x = x.replace( tst, swtch2);  //remove unwanted chars.
    x = x.split(swtch2); 

    if (isMorse) {
        var tidy = [];
        for (var i = 0; i < x.length; i++){
            if ((x[i] != '') || x[i+1] == '' && x[i+2] != '') {
                tidy.push(x[i]);
            }
        }
    }

    var swtch3 = (isMorse) ? tidy : x;

    for (var j = 0; j < swtch3.length; j++) {
        for (var i = 0; i < alphabet.length; i++){
            if (isMorse) {
                if (tidy[j] == alphabet[i][1]) {
                    outPut += alphabet[i][0];
                } 
            } 
            else {
                if (x[j] == alphabet[i][0]) {
                    outPut += alphabet[i][1] + ((j < x.length-1)?"  ":"");
                }
            }
        }
    }

    if (!isMorse) {
        var wordDivide = (d.getElementById('slash').checked)?"  /  ":"     ";
        outPut = outPut.replace(/\s{3,}/gi, wordDivide);
    }

    if (outPut.length < 1) {
        alert('Enter valid text or morse...');
        txtIn.value = '';
    }
    else {
        txtOt.value = outPut;
    }

    var justMorse = (!isMorse) ? outPut : tidy;

    FormatForSound(justMorse);
}

function FormatForSound(s){
    var n = [];
    var b = '';
    if (typeof s == 'object') {
        for (var i = 0; i < s.length; ++i) {
            var f = (i == s.length-1)?'':'  ';
            var t = b += (s[i] + f);
        }
    }
    var c = (typeof s == 'object')? t : s;
    c = c.replace(/\//gi, '');
    c = c.replace(/\s{1,3}/gi, '4');
    c = c.replace(/\./gi, '03');
    c = c.replace(/\-/gi, '13');  
    c = c.split('');
    for (var i = 0; i < c.length; i++) {
        n.push(c[i]);
    }
    char = n;
}

function vlm() {
    return document.getElementById('volume').value;
}

function playMorse() {

    if (paused){ 
        playing = false;
        return false;
    }

    playing = true;
    if (incr >= char.length) {
        incr = 0;
        playing = false;
        paused = false;
        return false;
    }
    
    var c = char[incr];
    var freq = 550;
    var volume = (c < 2) ? vlm() : 0 ;
    var flen = (c == 0 || c == 3) ? speed : speed * 3;

    var osc = aud.createOscillator();
    osc.type = 'sine'; 
    osc.frequency.value = freq;

    var oscGain = aud.createGain();
    oscGain.gain.value = volume;
    osc.connect(oscGain);
    oscGain.connect(aud.destination);

    var now = aud.currentTime;

    osc.start(now);

        /*
        Sharp volume fade to stop harsh clicks if wave is stopped 
        at a point other than the (natural zero crossing point) 
        */
        oscGain.gain.setValueAtTime(volume, now + (flen*0.8));
        oscGain.gain.linearRampToValueAtTime(0.0, now + (flen*0.9999));
    
    
    osc.stop(now + flen);

    osc.onended = function() {
        incr++;
        playMorse();
    }
}      
})();
body {
    text-align: center;  
}





.Mcontainer {
display: inline-block;
position: relative;
width: 382px;
height: 302px;
border: 1px solid #000;
border-radius: 6px;
text-align: center;
font: bold 11px sans-serif;
background-color: rgb(203,243,65);
box-shadow: 0px 4px 2px rgba(0,0,0,0.3);
}
.Mtitle {
-webkit-user-select: none;   
-moz-user-select: none;   
display: inline-block;
position: absolute;
width: 380px;
height: 20px;
margin: auto;
left: 0; right: 0;
font-size: 16px;
line-height: 20px;
color:  #666;
}
.Mtxtareatitle {
-webkit-user-select: none;   
-moz-user-select: none; 
display: block;
position: absolute;
top: 60px;
left: -36px;
height: 22px;
width: 106px;
font-size: 18px;
line-height: 22px;
text-align: center;
color: #555;
transform: rotate(-90deg);
}
.Mtxtarea {
display: block;
position: absolute;
top: 18px;
margin: auto;
left: 0; right: 0;
height: 98px;
width: 344px;
border: 0.5px solid #000;
border-radius: 6px;
padding-top: 6px;
padding-left: 24px;
resize: none;
background-color: #fffff0;
font: bold 10px courier;
color: #555;
text-transform: uppercase;
overflow: auto;
outline: 0;    box-shadow: inset 0px 2px 5px rgba(0,0,0,0.5);
}
.Minfo {
display: none;
position: absolute;
top: -6px; left:-6px;
padding: 6px;
height: auto;
width:  370px;
text-align: left;
border: 0.5px solid #000;
border-radius: 6px;
box-shadow: 0px 4px 2px rgba(0,0,0,0.3);
background-color: rgb(203,243,65);
font: 11px sans-serif;
color: #555;
opacity: 0;
}
.Mbuttonwrap {
display: block;
position: absolute;
top: 245px;
margin: auto;
left: 0; right: 0;
height: 26px;
width: 100%;
}
.Mbuttons {
display: inline-block;
width: 69px;
height: 22px;
border: none;
margin: 0px 3.1px 0px 3.1px;
background-color: transparent;
font: bold 11px sans-serif;
color: #555;
border-radius: 20px;
cursor: pointer;
box-shadow: 0px 2px 2px rgba(0,0,0,0.5);
outline: 0;
}
.Mbuttons:hover {
background-color:  rgb(213,253,75);
}
.Mbuttons:active {
position: relative;
top: 1px;
box-shadow: 0px 1px 2px rgba(0,0,0,0.8);
}
.Mchckboxwrap {
display: block;
position: absolute;
top: 274px;
left: 289px;
width: 87px;
height: 21px;
line-height: 22px;
border: 0.5px solid #000;
color: #555;
background: #fff;
-webkit-user-select: none;   
-moz-user-select: none;   
}
.Mselect {
display: block;
position: absolute;
top: 274px;
left: 6px;
width: 88px;
height: 22px;
border: 0.5px solid #000;
padding-left: 5%;
background: #fff;
font: bold 11px sans-serif;
color: #555;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
outline: 0;
}
::selection {
color: #fff;
background: #555;
}
.Mchckbox {
margin-top: 1px;
vertical-align: middle;
cursor: pointer;
outline: 0;
}
.modal {
display: none;
position: absolute;
margin: auto;
top: 0;right: 0;bottom: 0;left: 0;
background: rgba(0,0,0,0.5);
-webkit-user-select: none;  
-moz-user-select: none;
opacity: 0;
text-align: center;
}
.modal > div {   
display: inline-block;
position: relative;
width: 250px;
height: 70px;
margin: 10% auto;
padding: 10px;
border: 0.5px solid #000;
border-radius:6px;
background-color: rgb(203,243,65);
font: bold 11px sans-serif;
color: #555;
box-shadow: 4px 4px 2px rgba(0,0,0,0.3);
text-align: center;
}
.sliderWrap {
display: block;
position: absolute;
top: 274px;
margin:auto;padding: 0;
left: 0; right: 0;
width: 184px;
height: 21px;
border: 0.5px solid #000;
background: #fff;
font: bold 11px sans-serif;
color: #555;
line-height: 21px;
text-align: center;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
outline: 0;
}
input[type=range] {
-webkit-appearance: none;
width: 50%;
margin: 0;padding: 0;
vertical-align: middle;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: #666;
}
input[type=range]::-webkit-slider-thumb {
box-shadow: 1px 1px 0.5px rgba(0, 0, 0, 0.5);
border: none;
height: 10px;
width: 20px;
border-radius: 5px;
background: #ffffff;
cursor: pointer;
-webkit-appearance: none;
margin-top: -3px;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #666;
}
input[type=range]::-moz-range-track {
width: 100%;
height: 4px;
cursor: pointer;
background: #666;
}
input[type=range]::-moz-range-thumb {
box-shadow: 1px 1px 0.5px rgba(0, 0, 0, 0.5);
height: 10px;
width: 20px;
border: none;
border-radius: 5px;
background: #ffffff;
cursor: pointer;
}
input[type=range]::-ms-thumb {
height: 10px;
width: 20px;
border: none;
border-radius: 5px;
background: #ffffff;
box-shadow: 1px 1px 0.5px rgba(0, 0, 0, 0.5);
cursor: pointer;
}
input[type=range]::-ms-track {
width: 100%;
height: 4px;
cursor: pointer;
background: transparent;
border: 5px solid transparent;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: #666;
}
input[type=range]::-ms-fill-upper {
background: #666;
}
::-ms-tooltip {
display: none;
}
select::-ms-expand {
display: none;
}

关于javascript - Web 音频振荡器仅在 Firefox 中点击,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35000423/

相关文章:

javascript - 如何在触发 jQuery 动画之前打开链接

javascript - 图像预加载器如何工作?

html - Firefox 中的 CSS 色带断裂

javascript - Chrome - createMediaElementSource 不工作

javascript - 暂停 Web Audio API 声音播放

javascript - 如何通过方波与音调相乘来产生节拍?

JavaScript 向具有相同类名的元素添加事件

css - 表格单元格高度显示不同 IE8 vs IE7 vs Firefox

javascript - Firefox 和 IE JS 滚动问题

javascript - Onclick 在 IE 中不起作用