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

以下代码在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创建的一系列简单的声音/提示音。

使用摩尔斯电码时,您可以根据5个字母长的单词(例如手抄本或巴黎)来计算速度(每分钟的单词数)。

为了使每分钟20或30巴黎能够完全按时完成,我不得不使用声音时间长度,例如0.61。在Opera中,这导致“声音点击结束”。将其更改为0.6时,所有浏览器(Firefox除外)的点击消失。

我已经尝试过freq = 0且声音之间的增益= 0,但仍然在FF处获得了点击声,我对Web Audio的了解还不够多,无法尝试其他方法。

另一方面,我注意到您正在使用循环和超时来到达下一个刻度。您是否尝试过“振荡器附加功能”?我将其与简单的计数器增量和可变长度的空白声音/音符一起使用。如果要看一下,请转到我的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;
}

10-06 12:14