Запустил генератор на webaudio Форма не идеальная, как у настоящего прямоугольника, почему так?
/*
Здась я добавил функцию запуска по клику так как изменились правила запуска аудио в браузерах, подробности по ссылке:
https://developer.chrome.com/blog/autoplay/
Функция запускается по событию onclick='WebaudioEnable()
*/
function WebaudioEnable() {
auCtx.resume()
};
var auCtx = new AudioContext();
var osc = auCtx.createOscillator();
var gain = auCtx.createGain();
var dest = auCtx.destination;
var analyser = auCtx.createAnalyser();
analyser.fftSize = 2048;
osc.connect(gain);
gain.connect(dest);
gain.connect(analyser);
osc.frequency.value = 110;
osc.type = 'square';
gain.gain.value = 0.5;
osc.start();
var scopeCtx = document.getElementById('scope').getContext('2d');
var spectCtx = document.getElementById('spectrum').getContext('2d');
draw();
function gainInp(val) {
document.querySelector('#gainVal').value = val;
gain.gain.value = val;
}
function freqInp(val) {
document.querySelector('#freqVal').value = val;
osc.frequency.value = val;
}
function shapeInp(val) {
document.querySelector('#shapeVal').value = val;
switch (val) {
case '2':
osc.type = 'square';
break;
case '3':
osc.type = 'sawtooth';
break;
case '4':
osc.type = 'triangle';
break;
default:
osc.type = 'sine';
break;
}
}
//=============================================================
//=============================================================
//=============================================================
function Pause() {
gain.gain.value = 0;
}
function Play() {
gain.gain.value = document.querySelector('#gainVal').value;
}
//=============================================================
//=============================================================
//=============================================================
function draw() {
drawSpectrum(analyser, spectCtx);
drawScope(analyser, scopeCtx);
requestAnimationFrame(draw);
}
//=============================================================
//=============================================================
function drawSpectrum(analyser, ctx) {
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var freqData = new Uint8Array(analyser.frequencyBinCount);
var scaling = height / 256;
analyser.getByteFrequencyData(freqData);
ctx.fillStyle = 'rgba(0, 20, 0, 0.1)';
ctx.fillRect(0, 0, width, height);
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgb(0, 200, 0)';
ctx.beginPath();
for (var x = 0; x < width; x++)
ctx.lineTo(x, height - freqData[x] * scaling);
ctx.stroke();
}
//=============================================================
//=============================================================
function drawScope(analyser, ctx) {
var width = ctx.canvas.width;
var height = ctx.canvas.height;
var timeData = new Uint8Array(analyser.frequencyBinCount);
var scaling = height / 256;
var risingEdge = 0;
var edgeThreshold = 5;
analyser.getByteTimeDomainData(timeData);
ctx.fillStyle = 'rgba(0, 20, 0, 0.1)';
ctx.fillRect(0, 0, width, height);
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgb(0, 200, 0)';
ctx.beginPath();
// No buffer overrun protection
while (timeData[risingEdge++] - 128 > 0 && risingEdge <= width);
if (risingEdge >= width) risingEdge = 0;
while (timeData[risingEdge++] - 128 < edgeThreshold && risingEdge <= width);
if (risingEdge >= width) risingEdge = 0;
for (var x = risingEdge; x < timeData.length && x - risingEdge < width; x++)
ctx.lineTo(x - risingEdge, height - timeData[x] * scaling);
ctx.stroke();
}
* {
background: #000;
color: #0bb;
outline: 1px dotted #666;
}
div {
float: left;
border: 2px solid #aaa;
border-radius: 10px;
}
.b1 {
/* стиль для кнопок class="b1"*/
background: #111;
/* цвет фона */
color: white;
/* Белые букв */
font-size: 9pt;
/* Размер шрифта в пунктах */
border: 2px solid #aaa;
border-radius: 10px;
}
<body>
<!--It's remake a not of working project (on 2025-08-28) https://codepen.io/ContemporaryInsanity/pen/Mwvqpb?editors=1111 -->
<button id='start' class="b1" onclick='WebaudioEnable()'>Включить WEBAUDIO </button>
<button id='Pause' class="b1"onclick='Pause()'>Pause</button>
<button id='play'class="b1" onclick='Play()'>Play</button>
<br>
<div>
<label for='gain'>Gain</label>
<input id='gain' type='range' value='0.5' min='0.0' max='1.0' step='0.01' oninput='gainInp(value)' />
<output for='gain' id='gainVal'>0.5</output>
</div>
<div>
<label for='freq'>Freq</label>
<input id='freq' type='range' value='110' min='55' max='4400' step='10' oninput='freqInp(value)' />
<output for='freq' id='freqVal'>110</output>
</div>
<div>
<label for='shape'>Shape</label>
<input id='shape' type='range' value='2' min='1' max='4' step='1' oninput='shapeInp(value)' />
<output for='shape' id='shapeVal'>2</output>
</div>
<br>
<br>
<div>
<canvas id='scope' width=400 height=200></canvas>
<canvas id='spectrum' width=400 height=200></canvas>
</div>
</body>
Почему так происходит, откуда берется искажение в аудиоконтексте, почему он не соответствует мат модели идеального прямоугольника ?
Ответы (2 шт):
Осцилятор строит прямоугольный сигнал из набора гармоник, которые плохо приближают сигнал в точках разрыва. Это эффект Гиббса, который возникает потому что максимальная частота гармоник ограничена и не только.
Попробуйте сами.
const minS = 0.1;
const maxS = 0.75;
const makeCurve = n => {
const a = [maxS - minS];
const b = [0];
for (let i = 1; i <= n; ++i) {
const icos = x => Math.sin(2 * Math.PI * i * x) / (Math.PI * i);
const isin = x => -Math.cos(2 * Math.PI * i * x) / (Math.PI * i);
a.push(icos(maxS) - icos(minS));
b.push(isin(maxS) - isin(minS));
}
return x => {
let s = 0;
for (let i = 0; i <= n; ++i) {
s += a[i] * Math.cos(2 * Math.PI * i * x);
s += b[i] * Math.sin(2 * Math.PI * i * x);
}
return s;
};
};
const clean = context => {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
};
const makeLinearMap = (minX, maxX, minY, maxY) => {
const a = (maxY - minY) / (maxX - minX);
const b = minY - a * minX;
return x => a * x + b;
};
const makeMap = context => {
const xMap = makeLinearMap(-0.1, 1.1, 0, context.canvas.width);
const yMap = makeLinearMap(-0.5, 1.5, context.canvas.height, 0);
return p => [xMap(p[0]), yMap(p[1])];
};
const path = (context, color, points) => {
const map = makeMap(context);
context.strokeStyle = color;
context.beginPath();
context.moveTo(...map(points[0]));
for (let i = 1; i < points.length; ++i) {
context.lineTo(...map(points[i]));
}
context.stroke();
};
const paintSignal = context => {
path(context, 'gray', [
[0 , 0],
[minS, 0],
[minS, 1],
[maxS, 1],
[maxS, 0],
[1 , 0]
]);
};
const paintCurve = (context, n) => {
const map = makeMap(context);
const minX = map([0, 0])[0];
const maxX = map([1, 0])[0];
const i0 = Math.round(minX);
const i1 = Math.round(maxX);
const invMapX = makeLinearMap(minX, maxX, 0, 1);
const curve = makeCurve(n);
const points = [];
for (let i = i0; i <= i1; i += 0.1) {
const x = invMapX(i);
points.push([x, curve(x)]);
}
path(context, 'black', points);
};
const main = () => {
const harmonicsElement = document.getElementById('harmonics');
const showHarmonicsText = document.getElementById('show_harmonics').childNodes[0];
const context = document.getElementById('display').getContext('2d');
const repaint = (n) => {
clean(context);
paintSignal(context);
paintCurve(context, n);
};
const onN = () => {
const value = harmonicsElement.value;
showHarmonicsText.nodeValue = value;
repaint(parseInt(value));
};
harmonicsElement.addEventListener('input', onN);
onN();
};
main();
<label for="harmonics">Длина серии</label>
<input id="harmonics" type="range" min="0" max="200" value="10">
<span id="show_harmonics"> </span>
<br>
<canvas id="display" width="400" height="200"></canvas>
В качестве ответа... WEBAUDIO не создает прямоугольный сигнал, он его имитирует набором синусоид, а точнее суммой формул.
Привожу ссылку на ответ... Это ссылка на коментарий,а не на мой собственный вопрос ( просто движек кривой и устарел давно, к тому же не может отметить коментарии в качестве ответа) Запустил генератор на webaudio Форма не идеальная, как у настоящего прямоугольника, почему так?
Надеюсь тот ответ небыл взят автором из видения картинки спектра этого же самого сигнала (хахаха)