试图在JS创造一个单一的振荡器,一个平滑的,不点击频率的色调,迅速打开和关闭。由克里斯华莱士讨论的网络音频的时间。时间调度程序可以找到阿基拉米舍尔。这个例子根据频率产生一个正确的音调.但是,出现了一个明显的单击。已通过堆栈溢出上的公告解决了固定单击的问题。
第一个代码片段是基于代码笔版本(参见链接2),将频率更改为432 Hz。
第二个代码片段:从这些资源中改编的JS (参见链接2和3),添加倾斜,使用432 Hz频率,我可以让点击停止,但我的频率声音现在变钝了,不再有正确的432 Hz频率的声音。我怀疑我做错了什么,没有正确地使用时间或设置。我尝试过不同的时间和设置,甚至是“初始”而不是“指数”倾斜,但没有人解决了这个问题:单击“消失了”,但频率声音现在变钝了,根据选择的频率不能保留正确的声音,如果不使用倾斜,就会听到声音,留下点击。我一定做错什么了?
有办法在不减弱声音的情况下修复点击吗? 1:https://www.html5rocks.com/en/tutorials/audio/scheduling/ 2:https://codepen.io/aqilahmisuary/pen/ONEKVM 3:即使在使用exponentialRampToValueAtTime时,网络音频“单击”声音
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>
<body>
<p><button name="button" id="startBtn">Start</button>
<button name="button" id="stopBtn">Stop</button></p>
<p>Audio Context Current Time:</p>
<p><span id="clock"></span></p>
<p>nextNotetime:</p>
<p><span id="nextNote"></span></p>
<style>
span, p, button {
font-family: 'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace;
font-size: 25px;
font-weight: 1000;
line-height: 26.4px;
text-align: center;
}
span {
font-weight: 200;
}
</style>
<script>
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
var nextNotetime = audioContext.currentTime;
var clock = document.getElementById("clock");
var nextNote = document.getElementById("nextNote");
var startBtn = document.getElementById("startBtn");
var stopBtn = document.getElementById("stopBtn");
var timerID;
setInterval(function(){ clock.innerHTML = audioContext.currentTime; }, 100);
function playSound(time) {
var osc = audioContext.createOscillator();
osc.connect(audioContext.destination);
osc.frequency.value = 432;
osc.start(time);
osc.stop(time + 0.1);
};
function scheduler() {
while(nextNotetime < audioContext.currentTime + 0.1) {
nextNotetime += 0.5;
nextNote.innerHTML = nextNotetime;
playSound(nextNotetime);
}
timerID = window.setTimeout(scheduler, 50.0);
}
startBtn.addEventListener('click', function() {
scheduler();
}, false);
stopBtn.addEventListener('click', function() {
clearTimeout(timerID);
}, false);
if(audioContext.state === 'suspended'){
audioContext.resume();
};
</script>
</body>
</html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>
<body>
<p><button name="button" id="startBtn">Start</button>
<button name="button" id="stopBtn">Stop</button></p>
<p>Audio Context Current Time:</p>
<p><span id="clock"></span></p>
<p>nextNotetime:</p>
<p><span id="nextNote"></span></p>
<style>
span, p, button {
font-family: 'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace;
font-size: 25px;
font-weight: 1000;
line-height: 26.4px;
text-align: center;
}
span {
font-weight: 200;
}
</style>
<script>
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
var nextNotetime = audioContext.currentTime;
var clock = document.getElementById("clock");
var nextNote = document.getElementById("nextNote");
var startBtn = document.getElementById("startBtn");
var stopBtn = document.getElementById("stopBtn");
var timerID;
var gainNode = audioContext.createGain();
var osc;
var rampDuration = 0.3;
gainNode.connect(audioContext.destination);
setInterval(function(){ clock.innerHTML = audioContext.currentTime; }, 100);
function playSound(time) {
osc = audioContext.createOscillator();
osc.connect(gainNode);
osc.frequency.value = 432;
osc.type = "sine";
gainNode.gain.setValueAtTime(1, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(0.0001, audioContext.currentTime + rampDuration);
gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + rampDuration);
osc.start(time);
osc.stop(time + 0.01);
};
function scheduler() {
while(nextNotetime < audioContext.currentTime + 0.1) {
nextNotetime += 0.5;
nextNote.innerHTML = nextNotetime;
playSound(nextNotetime);
}
timerID = window.setTimeout(scheduler, 50.0);
}
startBtn.addEventListener('click', function() {
scheduler();
}, false);
stopBtn.addEventListener('click', function() {
clearTimeout(timerID);
}, false);
if(audioContext.state === 'suspended'){
audioContext.resume();
};
</script>
</body>
</html>
发布于 2021-04-12 16:11:27
根据这些评论,我们实际上是在处理一个需要精确计时的简短的重复循环,因此AudioBufferSourceNode是我们选择的武器:
它特别适用于播放具有特别严格的计时精度要求的音频,例如必须匹配特定节奏的声音。
不幸的是,这也意味着我们需要弄脏我们的手,编写一些DSP代码来合成这个缓冲区,但老实说,它并没有那么糟糕(特别是因为我们可以只使用浮点数,而不是原始的PCM缓冲区.)。
为了避免点击(振荡器在中间相位被切断),我们利用正弦波总是从零开始的事实,我们呈现出一个声音的单圈,所以我们所需要做的就是确保波的结束不会突然停止。我们通过稍微调整音调的长度来实现这一点,以确保最后一个可听到的样本非常接近于零。
这里的示例有几个按钮来演示不同的参数。您可以将它们连接到UI组件中。
var audioContext = new AudioContext();
var gainNode = audioContext.createGain();
gainNode.connect(audioContext.destination);
var playerNode = null; // Initialized later.
function createLoop(audioContext, toneFrequency, toneDuration, loopDuration) {
const arrayBuffer = audioContext.createBuffer(
1,
audioContext.sampleRate * loopDuration,
audioContext.sampleRate,
);
const channel = arrayBuffer.getChannelData(0); // mono, only deal with single channel
const toneDurationInSamples = Math.floor(audioContext.sampleRate * toneDuration);
const phasePerSample = (Math.PI * 2 * toneFrequency) / audioContext.sampleRate;
let audible = true;
for (let i = 0; i < arrayBuffer.length; i++) {
if (audible) {
let value = Math.sin(phasePerSample * i);
channel[i] = value;
// We might slightly overshoot the tone's requested duration
// but we need to wait for the oscillation to be near zero
// to avoid an audible click (when the signal "snaps" from an arbitrary
// value to zero).
if (i >= toneDurationInSamples && Math.abs(value) < 0.02) {
audible = false;
}
} else {
channel[i] = 0; // Silence
}
}
return arrayBuffer;
}
function go(hz, length) {
halt(); // Remove the old player node. We couldn't modify the buffer anyway.
playerNode = audioContext.createBufferSource();
playerNode.loop = true;
playerNode.connect(gainNode);
const buf = createLoop(audioContext, hz, length / 5, length);
playerNode.buffer = buf;
playerNode.start();
audioContext.resume();
}
function halt() {
if (playerNode) {
playerNode.stop();
playerNode.disconnect();
playerNode = null;
}
}
function handleVolumeChange(volume) {
gainNode.gain.setValueAtTime(volume, audioContext.currentTime);
}Since an Audio Context can't start with interaction, you'll need to hit a button...
<br />
<button onclick="go(432, 0.5)">Go at 432 hz</button>
<button onclick="go(880, 0.3)">Go faster and at 880 hz</button>
<button onclick="go(1250, 0.1)">Go really fast and high</button>
<button onclick="halt()">Stop going</button>
<br />
Volume: <input type="range" min="0" max="1" value="1" step="0.01" onInput="handleVolumeChange(event.target.valueAsNumber)">
https://stackoverflow.com/questions/67060431
复制相似问题