Запустил генератор на 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 шт):

Автор решения: Stanislav Volodarskiy

Осцилятор строит прямоугольный сигнал из набора гармоник, которые плохо приближают сигнал в точках разрыва. Это эффект Гиббса, который возникает потому что максимальная частота гармоник ограничена и не только.

Попробуйте сами.

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>

→ Ссылка
Автор решения: izadapter

В качестве ответа... WEBAUDIO не создает прямоугольный сигнал, он его имитирует набором синусоид, а точнее суммой формул.

Привожу ссылку на ответ... Это ссылка на коментарий,а не на мой собственный вопрос ( просто движек кривой и устарел давно, к тому же не может отметить коментарии в качестве ответа) Запустил генератор на webaudio Форма не идеальная, как у настоящего прямоугольника, почему так?

введите сюда описание изображения

Надеюсь тот ответ небыл взят автором из видения картинки спектра этого же самого сигнала (хахаха)

→ Ссылка