网络音频API故障/失真问题

Web Audio API glitching/distortions issues

本文关键字:失真 问题 故障 音频 API 网络      更新时间:2023-09-26

我是网络音频API的新手,做了一个简单的合成器来学习输入和输出。问题是在输入大量声音后,我的音频会失真很多。所以,如果我把大量的频率推过,它就会失真。了解API的人能否快速浏览一下我的代码,看看是否有任何重大错误/遗漏?可以在Safari、chrome和Firefox中重新创建问题。演示版在这里感谢您的帮助!!

//start new audio session.
var context = new (window.webkitAudioContext || window.AudioContext || window.mozAudioContext)

function playSound(note) {
    oscillator = context.createOscillator();
//create volume controller
var gainNode = context.createGain();
//connect signal to audio output(speakers by default)
oscillator.connect(gainNode);
gainNode.connect(context.destination);
//adjusts frequency played by 50%, 100% or 200% 
var octave = document.getElementById('octave').value;
//sets oscillator frequency
oscillator.frequency.value = frequencies[note] * octave;
//oscillator wave type
oscillator.type = document.getElementById('waveSelect').value;

//initialize gain at 0 and ramp up to full volume very quikcly (prevents audible 'pop')
gainNode.gain.value = 0
var quickFadeIn = gainNode.gain.setTargetAtTime(1, context.currentTime, 0.1);
//starts oscillator. Delayed start can be achieved by adding time(in secs) after currentTime
oscillator.start(context.currentTime + .05);
/**
 *  AUDIO EFFECTS
 */
function delayNode() {
    //create delay
    var delay = context.createDelay();
    delay.delayTime.value = .5;
    //create gain
    gainNode;
    //gainNode.gain.value = 0.8;
    quickFadeIn;
    //create feedback loop
    oscillator.connect(gainNode);
    gainNode.connect(delay);
    delay.connect(gainNode);
    delay.connect(context.destination); 
    //decrease gain
    quickFadeOut;
}
function distortionNode() {
    var distortion = context.createWaveShaper();
//distortion curve taken from MDN which they in turn took from Stack Overflow
    function makeDistortionCurve(amount) {
      var k = typeof amount === 'number' ? amount : 50,
        n_samples = 44100,
        curve = new Float32Array(n_samples),
        deg = Math.PI / 90,
        i = 0,
        x;
      for ( ; i < n_samples; ++i ) {
        x = i * 2 / n_samples - 1;
        curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) );
      }
      return curve;
    };
    distortion.curve = makeDistortionCurve(500);
    distortion.oversample = '4x';
    gainNode;
    quickFadeIn;
    oscillator.connect(gainNode);
    gainNode.connect(distortion);
    distortion.connect(context.destination);
    //decrease gain
    quickFadeOut;   
}
if (document.getElementById('toggleDelay').value == 'true'){delayNode();}   
if (document.getElementById('toggleDistortion').value == 'true'){distortionNode();}
//determines note duration
var sustain = parseFloat(document.getElementById('sustain').value);
//stops oscillator by exponentially ramping down sound over .015 seconds to avoid audible click
var quickFadeOut = gainNode.gain.setTargetAtTime(0, context.currentTime + sustain, 0.0015);
//change key color on keypress
    //append the word "note" to the object.name note to identify the correct key div
    var divId = "note" + String(note);
    var element = document.getElementById(divId);
    //change background color for durarion of note length
    var currentColor = element.style.backgroundColor;
    element.style.backgroundColor = '#3cf7ac';
    setTimeout(function () {
    if (currentColor != 'rgb(60, 247, 172)') {
        element.style.backgroundColor = currentColor
    }
 }, 1000 * sustain);

//for testing
console.log('playSound Hz:' + frequencies[note] * octave + ' octave:' + octave + ' wave:' + oscillator.type + ' duration: ' + sustain + ' time:' + context.currentTime.toFixed(2));
}
 //controls 2nd keyboard.  Same logic as playSound()
function playSoundb(note) {
oscillator = context.createOscillator();
var gainNode = context.createGain();
oscillator.connect(gainNode);
gainNode.connect(context.destination);

var octaveb = document.getElementById('octaveb').value;
    oscillator.frequency.value = frequencies[note] * octaveb;

oscillator.type = document.getElementById('waveSelectb').value;
gainNode.gain.value = 0
var quickFadeIn = gainNode.gain.setTargetAtTime(.75, context.currentTime, .1);
oscillator.start(context.currentTime + .05);
/**
 *  AUDIO EFFECTS
 */
function delayNode() {
    var delay = context.createDelay();
    delay.delayTime.value = .5;
    gainNode;
    quickFadeIn;
    //create feedback loop
    oscillator.connect(gainNode);
    gainNode.connect(delay);
    delay.connect(gainNode);
    delay.connect(context.destination); 
    //decrease gain
    quickFadeOut;
}
function distortionNode() {
    var distortion = context.createWaveShaper();
    function makeDistortionCurve(amount) {
      var k = typeof amount === 'number' ? amount : 50,
        n_samples = 44100,
        curve = new Float32Array(n_samples),
        deg = Math.PI / 90,
        i = 0,
        x;
      for ( ; i < n_samples; ++i ) {
        x = i * 2 / n_samples - 1;
        curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) );
      }
      return curve;
    };
    distortion.curve = makeDistortionCurve(900);
    distortion.oversample = '4x';
    gainNode;
    quickFadeIn;
    oscillator.connect(gainNode);
    gainNode.connect(distortion);
    distortion.connect(context.destination);
    quickFadeOut;
}
if (document.getElementById('toggleDelayb').value == 'true'){delayNode();}
if (document.getElementById('toggleDistortionb').value == 'true'){distortionNode();}        
var sustainb = parseFloat(document.getElementById('sustainb').value);
var quickFadeOut = gainNode.gain.setTargetAtTime(0, context.currentTime + sustainb, 0.0015);
//change key color on keypress
var divId = "note" + String(note) + "b";
var element = document.getElementById(divId);
var currentColor = element.style.backgroundColor;
element.style.backgroundColor = '#3ce4f7';
setTimeout(function () {
    if (currentColor != 'rgb(60, 228, 247)') {
        element.style.backgroundColor = currentColor
    }
 }, 1000 * sustainb);
//for testing
console.log('playSound*B* Hz:' + frequencies[note] * octave + ' octave:' + octave + ' wave:' + oscillator.type + ' duration: ' + sustain + ' time:' + context.currentTime); 

}

//reveals 2nd keyboard
function displayKeyboard2(lowersynth, uppersynth) {
    var bottom = document.getElementById(lowersynth);
    var top = document.getElementById(uppersynth);
if (bottom.style.display == 'block') {
    bottom.style.display = 'none';
    top.style.marginTop = '150px';  
}
else {
    bottom.style.display = 'block';
    top.style.marginTop = '0';
}   

}

//Frequencies in Hz of notes to be played. 
var frequencies = {
    'C_1': 130.81,
    'C#1': 139.00,
    'D_1': 146.83,
    'D#1': 156.00,
    'E_1': 164.81,
    'F_1': 174.61,
    'F#1': 185.00,
    'G_1': 196.00,
    'G#1': 208.00,
    'A_1': 220.00,
    'A#1': 233.00,
    'B_1': 246.94,
    'C_2': 261.63,
    'C#2': 277.00,
    'D_2': 293.66,
    'D#2': 311.00,
    'E_2': 329.63,
    'F_2': 349.23,
    'F#2': 370.00,
    'G_2': 392.00,
    'G#2': 415.00,
    'A_2': 440.00,
    'A#2': 466.00,
    'B_2': 493.88,
    'C_3': 523.25,
 };
 //triggers playSound() to create note
document.getElementById('noteC_1').addEventListener(('click' || 'touchstart'),function() { playSound('C_1');});
document.getElementById('noteC#1').addEventListener(('click' || 'touchstart'),function() { playSound('C#1');});
document.getElementById('noteD_1').addEventListener(('click' || 'touchstart'),function() { playSound('D_1');});
document.getElementById('noteD#1').addEventListener(('click' || 'touchstart'),function() { playSound('D#1');});
document.getElementById('noteE_1').addEventListener(('click' || 'touchstart'),function() { playSound('E_1');});
document.getElementById('noteF_1').addEventListener(('click' || 'touchstart'),function() { playSound('F_1');});
document.getElementById('noteF#1').addEventListener(('click' || 'touchstart'),function() { playSound('F#1');});
document.getElementById('noteG_1').addEventListener(('click' || 'touchstart'),function() { playSound('G_1');});
document.getElementById('noteG#1').addEventListener(('click' || 'touchstart'),function() { playSound('G#1');});
document.getElementById('noteA_1').addEventListener(('click' || 'touchstart'),function() { playSound('A_1');});
document.getElementById('noteA#1').addEventListener(('click' || 'touchstart'),function() { playSound('A#1');});
document.getElementById('noteB_1').addEventListener(('click' || 'touchstart'),function() { playSound('B_1');});
document.getElementById('noteC_2').addEventListener(('click' || 'touchstart'),function() { playSound('C_2');});
document.getElementById('noteC#2').addEventListener(('click' || 'touchstart'),function() { playSound('C#2');});
document.getElementById('noteD_2').addEventListener(('click' || 'touchstart'),function() { playSound('D_2');});
document.getElementById('noteD#2').addEventListener(('click' || 'touchstart'),function() { playSound('D#2');});
document.getElementById('noteE_2').addEventListener(('click' || 'touchstart'),function() { playSound('E_2');});
document.getElementById('noteF_2').addEventListener(('click' || 'touchstart'),function() { playSound('F_2');});
document.getElementById('noteF#2').addEventListener(('click' || 'touchstart'),function() { playSound('F#2');});
document.getElementById('noteG_2').addEventListener(('click' || 'touchstart'),function() { playSound('G_2');});
document.getElementById('noteG#2').addEventListener(('click' || 'touchstart'),function() { playSound('G#2');});
document.getElementById('noteA_2').addEventListener(('click' || 'touchstart'),function() { playSound('A_2');});
document.getElementById('noteA#2').addEventListener(('click' || 'touchstart'),function() { playSound('A#2');});
document.getElementById('noteB_2').addEventListener(('click' || 'touchstart'),function() { playSound('B_2');});
document.getElementById('noteC_3').addEventListener(('click' || 'touchstart'),function() { playSound('C_3');});

document.getElementById('noteC_1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('C_1');});
document.getElementById('noteC#1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('C#1');});
document.getElementById('noteD_1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('D_1');});
document.getElementById('noteD#1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('D#1');});
document.getElementById('noteE_1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('E_1');});
document.getElementById('noteF_1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('F_1');});
document.getElementById('noteF#1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('F#1');});
document.getElementById('noteG_1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('G_1');});
document.getElementById('noteG#1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('G#1');});
document.getElementById('noteA_1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('A_1');});
document.getElementById('noteA#1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('A#1');});
document.getElementById('noteB_1b').addEventListener(('click' || 'touchstart'),function() { playSoundb('B_1');});
document.getElementById('noteC_2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('C_2');});
document.getElementById('noteC#2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('C#2');});
document.getElementById('noteD_2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('D_2');});
document.getElementById('noteD#2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('D#2');});
document.getElementById('noteE_2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('E_2');});
document.getElementById('noteF_2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('F_2');});
document.getElementById('noteF#2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('F#2');});
document.getElementById('noteG_2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('G_2');});
document.getElementById('noteG#2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('G#2');});
document.getElementById('noteA_2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('A_2');});
document.getElementById('noteA#2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('A#2');});
document.getElementById('noteB_2b').addEventListener(('click' || 'touchstart'),function() { playSoundb('B_2');});
document.getElementById('noteC_3b').addEventListener(('click' || 'touchstart'),function() { playSoundb('C_3');});

没有重大遗漏-这正是数字音频中当你过载输出时发生的情况(即音频的瞬时值是<-1或>+1)-你会得到剪辑,这通常听起来很糟糕。可能最好的做法(除了保持增益值低于1)是在输出上放置一个DynamicsCompressor(即通过context.createDynamicsCoompressor()创建一个DynadynamicsCompressorNode,将其连接到context.tdestination,然后将音符连接到压缩器,而不是上下文。destination)。在这种情况下,默认值是合理的(压缩器设置是音乐决定的,但这至少有助于剪辑)。