HTML5 X Y Oscilloscope
这是Github上的一个开源的代码,在网页里面实现示波器波形和频谱的显示
标签
示波器
测试
开源
硬禾发布
更新2021-04-20
1494

这实在Github分享的一个完全基于HTML5写成的X Y示波器软件,只有一个页面,文件也只有十几KB的大小,不仅支持双通道波形的显示,而且可以做频谱分析。信号源就用电脑或手机的麦克风。

FmRY2kFcJdg34Jvpa08py0U5rPJ1Fi8ginBpa_oHkss8i9-dwg7PBZ2x

此项目的Github链接地址

演示页面地址

 

它可以接受来自“麦克风”或“立体声混音”来源的实时输入。

它有三种模式:

  • X Y,在此模式下左输入通道影响X轴,右输入通道影响Y轴,需要立体输入才能正确地绘制图像,否则它只绘制一条对角线,因为左和右是相同的,当收到警告时,你需要允许浏览器访问你的麦克风。
  • 波形
  • 频谱

 

在左通道播放正弦波,在右通道播放余弦波会画一个圆。

在Windows 10和微软Edge浏览器上工作得最好。

目前我无法让Chrome正确地分割左右通道,firefox似乎有一些我无法重写的AGC。您的设置可能没有这些问题。

 

在windows上操作方式:

  • 在windows启动按钮附近的Cortana输入中打开“隐私”设置。
  • 允许Microsoft Edge访问麦克风
  • 在windows启动按钮附近的Cortana输入框中打开“声音”设置。
  • 管理“录音”设备选择所需的音频输入,首选立体声输入。
  • 刷新Edge浏览器的示波器页面,按下“开始”按钮,允许网站访问您的麦克风。
  • 使一些噪音

您可以观看这个视频,以看到相同的模式,绘制在X Y视图,正如在视频中绘制的。图像使用可听见的声波在应用程序窗口之间传输。甚至可以在有空气隙的设备上工作。

要在windows中获得立体声输入,最好使用“立体声混音”选项设置为录音设备声音选项中的默认设备,并确保它的水平设置为100以获得最佳效果。

如果“立体声混合”选项不可见,右键单击设备区域,勾选“显示禁用设备”和“显示断开连接的设备”。

如果“立体声混音”仍然不可见,您可能需要从RealTek网站更新RealTek高清音频驱动程序。

如果所有设置都正确,那么从桌面播放合成器音乐将在显示中产生图案。

代码如下:

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=980;initial-scale=1.0">
    <meta http-equiv="cache-control" content="max-age=0" />
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="expires" content="0" />
    <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
    <meta http-equiv="pragma" content="no-cache" />
    <title>X Y Oscilloscope by seanwasere ytbe</title>

    <style>
        BODY {
            margin: 0px;
            padding: 0px;
            font-family: Arial, Helvetica, sans-serif;
            background-color: black;
            color: black;
            font-size: 17px;
            overflow: hidden;
        }

        #scope {
            position: absolute;
            top: 0px;
            left: 0px;
            width: 1024px;
            height: 1024px;
        }

        .startButton {
            width: 100%;
            height: 50px;
            background-color: orangered;
            border: 1px solid;
            color: black;
            font-size: 32px;
            cursor: pointer;
        }

        .modal {
            display: block;
            position: fixed;
            z-index: 1;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            overflow: auto;
            background-color: rgb(0, 0, 0);
            background-color: rgba(0, 0, 0, 0.6);
        }

        .modal-content {
            background-color: #dddddd;
            margin: 12% auto;
            padding: 20px;
            border: 1px solid #888;
            width: 50%;
        }

        .modal-footer {
            text-align: center;
        }

        #modeOptions {
            position: absolute;
            top: 50px;
            left: 10px;
            color: white;
        }

        #LeftRightChannels {
            position: absolute;
            top: 90px;
            left: 10px;
            color: white;
            display: none;
        }

        #XYFlipOptions {
            position: absolute;
            top: 90px;
            left: 10px;
            color: white;
            display: block;
        }

        .bordered {
            padding: 4px;
            margin-right: 20px;
            border: 1px solid gray;
        }

        #header {
            position: absolute;
            top: 10px;
            left: 10px;
            color: greenyellow;
        }

        #header a {
            color: hotpink;
        }
    </style>
</head>

<body>
    <canvas id='scope' width='1024' height='1024'></canvas>
    <a href="https://github.com/Sean-Bradley/Oscilloscope">
        <img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"
            alt="Fork me on GitHub">
    </a>
    <span id="header">
        Tutorial at
        <a href="https://www.youtube.com/watch?v=JrOP-RJ5p1I" target="_blank">Pictures from Sound using an Oscilloscope and Fruityloops FL Studio</a>
    </span>
    <span id="modeOptions">Mode:&nbsp;&nbsp;
        <label class="bordered">
            <input type="radio" name="rdoDisplayMode" id="0" onclick="setXYMode(0);" checked/>X Y</label>
        <label class="bordered">
            <input type="radio" name="rdoDisplayMode" id="1" onclick="setXYMode(1);" />Waveform</label>
        <label class="bordered">
            <input type="radio" name="rdoDisplayMode" id="1" onclick="setXYMode(2);" />Spectrum</label>
    </span>
    <span id="LeftRightChannels">
        <label class="bordered">
            <input type="checkbox" onchange="toggleLeft();" checked />Left Channel
        </label>
        <label class="bordered">
            <input type="checkbox" onchange="toggleRight();" checked />Right Channel
        </label>
    </span>
    <span id="XYFlipOptions">
        <label class="bordered">
            <input type="checkbox" onchange="changeflipX();" checked />Flip X
        </label>
        <label class="bordered">
            <input type="checkbox" onchange="changeflipY();" checked />Flip Y
        </label>
    </span>
    <div id="myModal" class="modal">
        <div class="modal-content">
            <div class="modal-header">
                <h2>X Y Oscilloscope by seanwasere ytbe</h2>
            </div>
            <div class="modal-body">
                <p>The X Y Oscilloscope uses Stereo Input.</p>
                <p>Left Input channel affects X axis and right input channel affects Y axis.</p>
                <p>Playing sine wave in left channel and cosine wave in right channel draws a circle.</p>

                <button class="startButton" onclick="connectAudioAPI(); ">START</button>

                <p>Works Best with Windows 10 and Edge browser (it's true).
                    <ul>
                        <li>Open 'Privacy' Settings from the Cortana input.</li>
                        <li>Allow Microsoft Edge to access Microphone</li>
                        <li>Open 'Sound' settings from the Cortana input box.</li>
                        <li>Manage 'Recording Devices' to select desired audio input, stero input preferred.</li>
                        <li>Refresh Oscilloscope Page in Edge Browser, Press 'START' button and allow website to access your
                            microphone.
                        </li>
                        <li>Make some noise</li>
                    </ul>
                </p>

                <p>
                    Tutorial of the Oscillioscope setup and drawing shapes with sound
                    <br/>
                    <a href="https://www.youtube.com/watch?v=JrOP-RJ5p1I" target="_blank">
                        <img src="https://img.youtube.com/vi/JrOP-RJ5p1I/0.jpg">
                    </a>
                </p>

                <p>Source code at
                    <a href="https://github.com/Sean-Bradley/Oscilloscope">https://github.com/Sean-Bradley/Oscilloscope</a>
                </p>

                <p>Written by
                    <a href="https://youtube.com/seanwasere " target="_blank">SeanWasEre Youtube</a>
                </p>
            </div>
        </div>
    </div>
    <br/>

    <script lang="javascript ">

        var AudioContext = window.AudioContext || window.webkitAudioContext || false;

        var XYMode = 0;
        var connected = false;
        var context;

        var currentStream;
        var mediaSource, mediaBuffer, remoteDestination;
        var analyser1;
        var analyser2;
        var splitter;
        var javascriptNode;

        function setXYMode(b) {
            if (b === 0) {
                XYMode = 0;
                document.getElementById("XYFlipOptions").style.display = "block";
                document.getElementById("LeftRightChannels").style.display = "none";
                analyser1.fftSize = 4096;
                analyser2.fftSize = 4096;
            } else if (b === 1) {
                XYMode = 1;
                document.getElementById("XYFlipOptions").style.display = "none";
                document.getElementById("LeftRightChannels").style.display = "block";
                analyser1.fftSize = 2048;
                analyser2.fftSize = 2048;
            } else {
                XYMode = 2;
                document.getElementById("XYFlipOptions").style.display = "none";
                document.getElementById("LeftRightChannels").style.display = "block";
                analyser1.fftSize = 2048;
                analyser2.fftSize = 2048;
            }
        }

        var leftChannel = true;
        var rightChannel = true;
        function toggleLeft() {
            leftChannel = !leftChannel;
        }
        function toggleRight() {
            rightChannel = !rightChannel;
        }
        function connectAudioAPI() {
            try {
                context = new AudioContext();

                analyser1 = context.createAnalyser();
                analyser1.fftSize = 4096;

                analyser2 = context.createAnalyser();
                analyser2.fftSize = 4096;

                splitter = context.createChannelSplitter();

                javascriptNode = context.createScriptProcessor(2048, 1, 1);


                connectInput();
            } catch (e) {
                alert(e);
            }
        }

        function connectInput() {
            navigator.mediaDevices.getUserMedia({ audio: true })
                .then(function (mediaStream) {
                    gotStream(mediaStream);
                }).catch(function (e) {
                    alert("In connectInput : " + e);
                });

        }

        function gotStream(stream) {
            try {

                mediaSource = context.createMediaStreamSource(stream);

                mediaSource.connect(splitter);

                splitter.connect(analyser1, 0, 0);
                splitter.connect(analyser2, 1, 0);

                document.getElementById("myModal").style.display = "none";
                resizeCanvas();

                javascriptNode.connect(context.destination);
                javascriptNode.onaudioprocess = function () {
                    drawScope();
                }

            } catch (e) {
                alert("In gotStream : " + e);
            }
        }


        var ctx = document.getElementById('scope').getContext('2d');
        var width = 1024;
        var height = 1024;
        var canvasData = ctx.getImageData(0, 0, width, height);
        var canvasBlankData = ctx.getImageData(0, 0, width, height);

        window.addEventListener('resize', resizeCanvas, false);
        function resizeCanvas() {
            document.getElementById('scope').style.width = window.innerWidth + "px";
            document.getElementById('scope').style.height = window.innerHeight + "px";
        }

        var flipX = false;
        var flipY = false;
        var scaleX = 4;
        var scaleY = 4;

        function drawScope() {

            var timeData1 = new Uint8Array(analyser1.frequencyBinCount);
            var timeData2 = new Uint8Array(analyser2.frequencyBinCount);

            if (XYMode === 0) {
                analyser1.getByteTimeDomainData(timeData1);
                analyser2.getByteTimeDomainData(timeData2);

                ctx.fillStyle = 'rgba(0, 0, 0, .1)';
                ctx.fillRect(0, 0, width, height);

                ctx.beginPath();
                ctx.lineWidth = 1;
                ctx.strokeStyle = 'rgb(0, 128, 255)';
                ctx.beginPath();

                if (flipX && flipY) {
                    for (var x = 0; x < timeData1.length; x++) {
                        ctx.lineTo(width - timeData1[x] * scaleX, timeData2[x] * scaleY);
                    }
                } else if (flipX && !flipY) {
                    for (var x = 0; x < timeData1.length; x++) {
                        ctx.lineTo(width - timeData1[x] * scaleX, height - timeData2[x] * scaleY);
                    }
                } else if (!flipX && flipY) {
                    for (var x = 0; x < timeData1.length; x++) {
                        ctx.lineTo(timeData1[x] * scaleX, timeData2[x] * scaleY);
                    }
                } else {
                    for (var x = 0; x < timeData1.length; x++) {
                        ctx.lineTo(timeData1[x] * scaleX, height - timeData2[x] * scaleY);
                    }
                }
                ctx.stroke();
            } else {
                if (XYMode === 1) {
                    analyser1.getByteTimeDomainData(timeData1);
                    analyser2.getByteTimeDomainData(timeData2);

                    ctx.fillStyle = "rgb(0, 0, 0) ";
                    ctx.fillRect(0, 0, 1024, 1024);
                    ctx.strokeStyle = 'rgb(255, 128, 0)';
                    ctx.lineWidth = 3;
                    if (leftChannel && rightChannel) {
                        ctx.beginPath();
                        for (var x = 0; x < 1024; x++) {
                            ctx.lineTo(x, 512 - (timeData1[x] * 2));
                        }
                        ctx.stroke();
                        ctx.beginPath();
                        for (var x = 0; x < 1024; x++) {
                            ctx.lineTo(x, 1024 - (timeData2[x] * 2));
                        }
                        ctx.stroke();
                    } else if (leftChannel && !rightChannel) {
                        ctx.beginPath();
                        for (var x = 0; x < 1024; x++) {
                            ctx.lineTo(x, 1024 - (timeData1[x] * 4));
                        }
                        ctx.stroke();
                    } else if (!leftChannel && rightChannel) {
                        ctx.beginPath();
                        for (var x = 0; x < 1024; x++) {
                            ctx.lineTo(x, 1024 - (timeData2[x] * 4));
                        }
                        ctx.stroke();
                    }
                } else {
                    analyser1.getByteFrequencyData(timeData1);
                    analyser2.getByteFrequencyData(timeData2);

                    ctx.fillStyle = "rgb(0, 0, 0) ";
                    ctx.fillRect(0, 0, 1024, 1024);
                    ctx.strokeStyle = 'rgb(128, 0, 256)';
                    ctx.lineWidth = 6;
                    if (leftChannel && rightChannel) {
                        ctx.beginPath();
                        for (var x = 0; x < 512; x++) {
                            ctx.lineTo(x * 2, 500 - (timeData1[x] * 2));
                        }
                        ctx.stroke();
                        ctx.beginPath();
                        for (var x = 0; x < 512; x++) {
                            ctx.lineTo(x * 2, 1012 - (timeData2[x] * 2));
                        }
                        ctx.stroke();
                    } else if (leftChannel && !rightChannel) {
                        ctx.beginPath();
                        for (var x = 0; x < 512; x++) {
                            ctx.lineTo(x * 2, 1012 - (timeData1[x] * 4));
                        }
                        ctx.stroke();
                    } else if (!leftChannel && rightChannel) {
                        ctx.beginPath();
                        for (var x = 0; x < 512; x++) {
                            ctx.lineTo(x * 2, 1012 - (timeData2[x] * 4));
                        }
                        ctx.stroke();
                    }
                }
            }
        }

        function changeflipX(e) {
            flipX = !flipX;
        }
        function changeflipY(e) {
            flipY = !flipY;
        }

    </script>
</body>

</html>
团队介绍
Sean-Bradley
团队成员
Sean-Bradley
来自Github上的一位分享者,他的Youtube:https://www.youtube.com/user/seanwasere, Github页面:https://github.com/Sean-Bradley
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号