DEV Community

Masui Masanori
Masui Masanori

Posted on

1

[TypeScript][Chart.js] Play my own voice 3

Intro

This time, I will try "WaveShaperNode" to use distortion.

Environments

  • Node.js ver.16.8.0
  • TypeScript ver.4.3.5
  • Webpack ver.5.42.0
  • ts-loader ver.9.2.3
  • webpack-cli ver.4.7.2
  • chart.js ver.3.5.1

Use WaveShaperNode

The distortion is made by clipping the wave.
Alt Text

To intentionally cause clipping, I can use "WaveShaperNode".

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Audio sample</title>
        <meta charset="utf-8">
    </head>
    <body>
        <div>
            <canvas id="curve_view" style="width: 30vw; height: 30vh;"></canvas>
        </div>
        <script src="./js/main.page.js"></script>
        <script>Page.init();</script>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

main.page.ts

let audioContext: AudioContext;

export async function init(): Promise<void> {
    const medias = await navigator.mediaDevices.getUserMedia({
        video: false,
        audio: true,
    });
    audioContext = new AudioContext();
    const audioSourceNode = audioContext.createMediaStreamSource(medias);

    const distortion = audioContext.createWaveShaper();
    distortion.curve = makeDistortionCurve(50);
    distortion.oversample = '4x';

    audioSourceNode
        .connect(distortion)
        .connect(audioContext.destination);
}
function makeDistortionCurve(amount: number): Float32Array {
    const sample = 44100;
    const curve = new Float32Array(sample);
    const deg = Math.PI / 180;

    const viewCanvas = document.getElementById('curve_view') as HTMLCanvasElement;
    for (let i = 0; i < sample; ++i ) {
        const x = i * 2 / sample - 1;
        curve[i] = ( 3 + amount ) * x * 20 * deg / ( Math.PI + amount * Math.abs(x));
    }
    return curve;
}
Enter fullscreen mode Exit fullscreen mode

Curve

WaveShaperNode has two properties.
They are "curve" and "oversample".

"oversample" is for anti-aliasing for drawing the curve.

So I can think the most important property for distortion is "curve".

Like the code sample above, the curve is expressed by Float32Array.
Because I want to display the curve visually, I use Chart.js.

Draw the curve

I add Chart.js and some code like last time.

main.page.ts

import { Chart, ChartConfiguration, ChartTypeRegistry } from "chart.js";

let audioContext: AudioContext;

export async function init(): Promise<void> {
    const medias = await navigator.mediaDevices.getUserMedia({
        video: false,
        audio: true,
    });

    audioContext = new AudioContext();
    const audioSourceNode = audioContext.createMediaStreamSource(medias);

    const distortion = audioContext.createWaveShaper();

    distortion.curve = makeDistortionCurve(50);
    distortion.oversample = '4x';

    audioSourceNode
        .connect(distortion)
        .connect(audioContext.destination);
}
function makeDistortionCurve(amount: number): Float32Array {
    const sample = 44100;
    const curve = new Float32Array(sample);
    const deg = Math.PI / 180;

    for (let i = 0; i < sample; ++i ) {
        const x = i * 2 / sample - 1;
        curve[i] = ( 3 + amount ) * x * 20 * deg / ( Math.PI + amount * Math.abs(x));
    } 
    drawSample(curve);
    return curve;
}
function drawSample(values: Float32Array) {
    const viewCanvas = document.getElementById('curve_view') as HTMLCanvasElement;

    const labels: string[] = [];
    for(const v of values) {
        labels.push(v.toString());
    }
      const data = {
        labels: labels,
        datasets: [{
          label: 'curve',
          backgroundColor: 'rgb(255, 99, 132)',
          borderColor: 'rgb(255, 99, 132)',
          data: [...values],
        }]
      };
      const config: ChartConfiguration<keyof ChartTypeRegistry> = {
        type: 'line',
        data: data,
        options: {}
      };
    new Chart(canvas, config);
}
Enter fullscreen mode Exit fullscreen mode

But I got an error.

ncaught (in promise) Error: "line" is not a registered controller.
Enter fullscreen mode Exit fullscreen mode

After all, I added "registerables".

main.page.ts

import { Chart, ChartConfiguration, ChartTypeRegistry, registerables } from "chart.js";

let audioContext: AudioContext;

export async function init(): Promise<void> {
    Chart.register(...registerables);
...
}
Enter fullscreen mode Exit fullscreen mode

Curve shape

The result of drawing the curve like below.
Alt Text

I change the curve values.

amount

If I change the "amount" value of the argument of "makeDistortionCurve" more larger, the curve will be close to a right angle and the distortion will be harder.

amount = 400

Alt Text

If I set "0", I can hardly feel the distortion.

amount = 0

Alt Text

Range

Their range is from -1 to 1.
If I change the range more wider, the sound will be more larger.

main.page.ts

...
function makeDistortionCurve(amount: number): Float32Array {
    const sample = 44100;
    const curve = new Float32Array(sample);
    const deg = Math.PI / 180;

    const viewCanvas = document.getElementById('curve_view') as HTMLCanvasElement;
    for (let i = 0; i < sample; ++i ) {
        const x = i * 2 / sample - 1;
        curve[i] = (Math.PI + amount) * x / (Math.PI + amount * Math.abs(x));
    } 
    drawSample(viewCanvas, curve);
    return curve;
}
...
Enter fullscreen mode Exit fullscreen mode

Alt Text

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay