Measure the waveforms of Web Audio API’s OscillatorNode with phyphox

I measured the sound of Web Audio API’s OscillatorNode with phyphox (Physical Phone Experiments).

I set four different waveform parameters for the OscillatorNode: sine, square, sawtooth and triangle. All frequencies were set to 440 Hz.

Click the buttons below to play the sound. Pay attention to the volume (especially the square wave).
Click the buttons again to turn off the sound.

1. sine wave(440Hz)

2. square wave(440Hz)

3. sawtooth wave(440Hz)

4. triangle wave(440Hz)

I measured the audio spectra of the above four audio waves with phyphox. The data was obtained in the same way as in this blog. 2,048 samples were used for FFT.

Audio waves were output from Fujitsu LIFEBOOK UH77/C3. An iPhone SE (1st generation) was used for measurements. When audio waves were output from the laptop speaker, waveforms other than the sine wave were different from the expected waveforms. I used external speakers and re-measured audio waves.

1. Sine wave (440 Hz): Laptop speakers

The waveform of the sound pressure data is a sine curve.

The audio spectrum takes a sharp peak at 445.3125 Hz (close to 440 Hz).

Note:
When the number of FFT samples was set to 32,768, large peak values were taken at 439.453125Hz and 440.91796875Hz which are closer to 440Hz. Since it takes time to display a web page when the number of samples is set to 32,768, I decided to plot the results with 2,048 samples this time.

2. Square wave (440 Hz): Laptop speakers

The waveform of the sound pressure data is shown in the graph below.

The audio spectrum has a small peak at 445.3125 Hz (close to 440 Hz), but the peak frequency is 1,312.5 Hz.

3. Sawtooth wave (440 Hz): Laptop speaker

The waveform of the sound pressure data is shown in the graph below.

The audio spectrum is shown in the graph below. Although not a peak frequency, the lowest frequency peak is 445.3125 Hz which is close to 440 Hz.

4. Triangular wave (440 Hz): Laptop speaker

The waveform of the sound pressure data is shown in the graph below.

The audio spectrum is shown in the graph below. Although not a peak frequency, the lowest frequency peak is 445.3125 Hz which is close to 440 Hz.

Measurement results using an external speaker as the sound source

5. Sine wave (440 Hz): External speaker

The waveform of the sound pressure data is a sine curve.

The audio spectrum takes a sharp peak at 445.3125 Hz (close to 440 Hz).

6. Square wave (440 Hz): External speaker

The waveform of the sound pressure data is shown in the graph below. Although the waveform is different from square waves, it is closer to square waves than the square wave (440 Hz) in 2. where the sound was output from the laptop speaker.

The waveform varied with volume and the positional relationship between the smartphone and the speaker. When one speaker was placed face down and measurements were taken just behind the other speaker, relatively stable waveforms could be measured.

The audio spectrum takes a sharp peak at 445.3125 Hz (close to 440 Hz).

7. Sawtooth wave (440 Hz): External speaker

The waveform of the sound pressure data is shown in the graph below. Although the waveform is different from sawtooth waves, it is closer to sawtooth waves than the sawtooth wave (440 Hz) in 3. where the sound was output from the laptop speaker.

The waveform changed depending on the volume and the positional relationship between the smartphone and the speaker. Placing one speaker face down and measuring just behind the other speaker resulted in a relatively stable waveform.

The audio spectrum takes a sharp peak at 445.3125 Hz (close to 440 Hz).

8. Triangular wave (440 Hz): external speaker

The waveform of the sound pressure data is shown in the graph below. The waveform of the triangular wave is not clearly shown, but it is closer to triangular waves than the triangular wave (440 Hz) in 4. where sound was output from the notebook computer speakers.

The audio spectrum takes a sharp peak at 445.3125 Hz (close to 440 Hz).

9. The following JavaScript was written using Web Audio API’s OscillatorNode to produce sound when the button is clicked.

<script type="text/javascript">

// create web audio api context
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

const oscillatorMap = new Map();
const isPlayingMap = new Map();

// sine, 440Hz
CreateAudioOscillator('audio (sine, 440Hz)', 'start-stop-sine', 'sine', 440);

// square, 440Hz
CreateAudioOscillator('audio (square, 440Hz)', 'start-stop-square', 'square', 440);

// sawtooth, 440Hz
CreateAudioOscillator('audio (sawtooth, 440Hz)', 'start-stop-sawtooth', 'sawtooth', 440);

// triangle, 440Hz
CreateAudioOscillator('audio (triangle, 440Hz)', 'start-stop-triangle', 'triangle', 440);

function CreateAudioOscillator(buttonText, buttonID, oscillatorType, frequency) {

    const button = document.getElementById(buttonID);
    button.textContent = buttonText;

    button.addEventListener("click", () => {

        let oscillator = oscillatorMap.get(buttonID);
        let isPlaying = isPlayingMap.get(buttonID);

        if (isPlaying === undefined) {
            isPlaying = false;
        }

        if (isPlaying === false) {
            oscillator = audioCtx.createOscillator();
            oscillator.type = oscillatorType;
            oscillator.frequency.setValueAtTime(frequency, audioCtx.currentTime);
            oscillator.connect(audioCtx.destination);
            oscillator.start();
            isPlaying = true;

            button.textContent = buttonText + " - playing";
        } else {
            oscillator.stop();
            isPlaying = false;

            button.textContent = buttonText;
        }

        oscillatorMap.set(buttonID, oscillator);
        isPlayingMap.set(buttonID, isPlaying);

    });
}
</script>

10. The data recorded by phyphox was uploaded to Google Sheets using the same procedure as in this blog and plotted on this web page using the JavaScript below.

<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">

google.charts.load('current', {packages:['corechart']});
google.charts.setOnLoadCallback(Spreadsheet);


function Spreadsheet() {

    // Note PC speaker
    const sine_sheetdata = 'https://docs.google.com/spreadsheets/d/1WnHJhNyvOwXlX-nqhNwRbjFlY53pPYtQcVu-Zjo6fbo/edit?usp=sharing';
    const square_sheetdata = 'https://docs.google.com/spreadsheets/d/12ciF19pUXNmgcMzS2X0zgr3nAaLUNepECbu2Y3wERHE/edit?usp=sharing';
    const sawtooth_sheetdata = 'https://docs.google.com/spreadsheets/d/1hZFBLbzkOPBK50O7EJwi9jE7RNfevDO3N43iXX0FIos/edit?usp=sharing';
    const triangle_sheetdata = 'https://docs.google.com/spreadsheets/d/1XwCTs6-N8zr_FfnLiHUxs1XagUTmrg_vxT_6348Goj4/edit?usp=sharing';


    const query_sine_raw_data = new google.visualization.Query(sine_sheetdata + '&gid=11974291&range=A1:B501');
    drawRawDataWithSpecifiedId(query_sine_raw_data, 'audio_raw_data_sine_440Hz');

    const query_sine = new google.visualization.Query(sine_sheetdata);
    drawFFTSpectrumWithSpecifiedId(query_sine, 'audio_spectrum_sine_440Hz_logx');


    const query_square_raw_data = new google.visualization.Query(square_sheetdata + '&gid=297086323&range=A1:B501');
    drawRawDataWithSpecifiedId(query_square_raw_data, 'audio_raw_data_square_440Hz');

    const query_square = new google.visualization.Query(square_sheetdata);
    drawFFTSpectrumWithSpecifiedId(query_square, 'audio_spectrum_square_440Hz_logx');


    const query_sawtooth_raw_data = new google.visualization.Query(sawtooth_sheetdata + '&gid=1334885699&range=A1:B501');
    drawRawDataWithSpecifiedId(query_sawtooth_raw_data, 'audio_raw_data_sawtooth_440Hz');

    const query_sawtooth = new google.visualization.Query(sawtooth_sheetdata);
    drawFFTSpectrumWithSpecifiedId(query_sawtooth, 'audio_spectrum_sawtooth_440Hz_logx');


    const query_triangle_raw_data = new google.visualization.Query(triangle_sheetdata + '&gid=841364434&range=A1:B501');
    drawRawDataWithSpecifiedId(query_triangle_raw_data, 'audio_raw_data_triangle_440Hz');

    const query_triangle = new google.visualization.Query(triangle_sheetdata);
    drawFFTSpectrumWithSpecifiedId(query_triangle, 'audio_spectrum_triangle_440Hz_logx');


    // ----------------------------------------------------------------------------------
    // with external speaker
    const sine_with_speaker_sheetdata = 'https://docs.google.com/spreadsheets/d/1ue2hClO51djXHrDVlIyBimSGTRTOXWPSVNhcqESw_zk/edit?usp=sharing';
    const square_with_speaker_sheetdata = 'https://docs.google.com/spreadsheets/d/1Z0fe3hLOH0u-bA4epkQgMfAig6yCQCImZ20_tJ_zxuQ/edit?usp=sharing';
    const sawtooth_with_speaker_sheetdata = 'https://docs.google.com/spreadsheets/d/1L1U2zRE-Q7ZA9EUc3PzFgCM81e3YdO965v35fOhEtsA/edit?usp=sharing';
    const triangle_with_speaker_sheetdata = 'https://docs.google.com/spreadsheets/d/1MLv5dNoEKK_y8m90ac3cxi0LQ6IC6Y39aK98QlyWE1o/edit?usp=sharing';


    const query_sine_with_speaker_raw_data = new google.visualization.Query(sine_with_speaker_sheetdata + '&gid=573933332&range=A1:B501');
    drawRawDataWithSpecifiedId(query_sine_with_speaker_raw_data, 'audio_raw_data_sine_440Hz_with_speaker');

    const query_sine_with_speaker = new google.visualization.Query(sine_with_speaker_sheetdata);
    drawFFTSpectrumWithSpecifiedId(query_sine_with_speaker, 'audio_spectrum_sine_440Hz_logx_with_speaker');


    const query_square_with_speaker_raw_data = new google.visualization.Query(square_with_speaker_sheetdata + '&gid=1774282809&range=A1:B501');
    drawRawDataWithSpecifiedId(query_square_with_speaker_raw_data, 'audio_raw_data_square_440Hz_with_speaker');

    const query_square_with_speaker = new google.visualization.Query(square_with_speaker_sheetdata);
    drawFFTSpectrumWithSpecifiedId(query_square_with_speaker, 'audio_spectrum_square_440Hz_logx_with_speaker');


    const query_sawtooth_with_speaker_raw_data = new google.visualization.Query(sawtooth_with_speaker_sheetdata + '&gid=1119483708&range=A1:B501');
    drawRawDataWithSpecifiedId(query_sawtooth_with_speaker_raw_data, 'audio_raw_data_sawtooth_440Hz_with_speaker');

    const query_sawtooth_with_speaker = new google.visualization.Query(sawtooth_with_speaker_sheetdata);
    drawFFTSpectrumWithSpecifiedId(query_sawtooth_with_speaker, 'audio_spectrum_sawtooth_440Hz_logx_with_speaker');


    const query_triangle_with_speaker_raw_data = new google.visualization.Query(triangle_with_speaker_sheetdata + '&gid=116127029&range=A1:B501');
    drawRawDataWithSpecifiedId(query_triangle_with_speaker_raw_data, 'audio_raw_data_triangle_440Hz_with_speaker');

    const query_triangle_with_speaker = new google.visualization.Query(triangle_with_speaker_sheetdata);
    drawFFTSpectrumWithSpecifiedId(query_triangle_with_speaker, 'audio_spectrum_triangle_440Hz_logx_with_speaker');
}


function drawFFTSpectrumWithSpecifiedId(query, graph_id_logx) {

    query.send( function(response) {
        const data = response.getDataTable();

        // audio spectrum : horizontal axis is logarithmic scale
        const options_logx = {title: 'phyphox audio spectrum : horizontal axis is logarithmic scale',
                              hAxis: {title: 'Frequency [Hz]', scaleType: 'log'},
                              vAxis: {title: 'FFT Magnitude'}};
        const chart_logx = new google.visualization.LineChart(document.getElementById(graph_id_logx));
        chart_logx.draw(data, options_logx);
    });
}


function drawRawDataWithSpecifiedId(query, graph_id) {

    query.send( function(response) {
        const data = response.getDataTable();

        // Peak History
        const options = {title: 'phyphox audio - raw data',
                         hAxis: {title: 'Time [s]'},
                         vAxis: {title: 'Sound Pressure'}};
        const chart = new google.visualization.LineChart(document.getElementById(graph_id));
        chart.draw(data, options);
    });
}
</script>

Leave a Reply