Sunday, 6 July 2014

Simple javascript oscilloscope in html page

I think versions will be OK.
Some people need just simple oscilloscope without any memory but performance only
The simplest version  will be one function to draw audio input on screen:
  http://uplight.ca/htms/Ocilloscope-Javascript.htm

Who are interested  in code right click on page go to source and in js folder find  js/UplOscilloscope1.js

I did inserted it on the bottom but without code formatting  don't think it useful.
There are 2 classes (objects) in code Oscilloscope1 controlling user interactions and OcsDisplay drawing buffer on screen.
2 library  used JQuery as interface to DOM  and createjs(easeljs) as interface to canvas they are not critical to code and can be replaced with any other or use direct access.

According documentation of API createScriptProcessor :
 bufferSize must be one of the following values: 256, 512, 1024, 2048, 4096, 8192, 16384.
This value controls how frequently the audioprocess event is dispatched and how many sample-frames need to be processed each call. Lower values for bufferSize will result in a lower (better) latency. Higher values will be necessary to avoid audio breakup and glitches. It is recommended for authors to not specify this buffer size and allow the implementation to pick a good buffer size to balance between latency and audio quality.

Main function is onAudioProcess what taking array from javascript node and draw it on screen it is triggered by ScriptProcessorNode.audioprocess.

the data is Uint8Array pulled to javascript with analyser.getByteTimeDomainData(data) function holds in array of uint where values could be form 0 to 256.
The nice surprise that .getByteTimeDomainData function forming data ready to visualization:  128 value is equal zero signal, 0 is +max 256 is -max;
all what you need go through array and draw each point on screen:
 for (var i = 1; i < n; i++)    g.lineTo(i , data[i]);


the problem what I'm facing is 
when buffer 4096 and above only 2048 values in array are valid and all others are 0:


On first image oscillogramme  with buffer size 4096 and next image 8192. How you can see after 2048 values its jumping to 0;


Javascript Oscilloscope source code:

var upl;
(function (upl) {
    var c = createjs;
    var Oscilloscope1 = (function () {
        // private oscAnalyser:OscRecorder
        function Oscilloscope1(prefix) {
            var _this = this;
            this.prefix = prefix;
            this.bufferSize = 1024;
            var view = $('#' + prefix + '-oscilloscope');
            this.stage = new c.Stage(document.getElementById(prefix + '-screen1'));
            this.select = view.find('[data-id=input_select]:first').on('change', null, function (evt) {
                return _this.onInputChanged(evt);
            });
            var canvas = $('#' + prefix + '-screen1');
            this.lineColor = canvas.data('color') || '#000000';
            //this.width = canvas.width();
            var audioContext = window.AudioContext || window.webkitAudioContext;
            this.audioContext = new audioContext();
            navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia);
            if (navigator.getUserMedia)
                setTimeout(function () {
                    return _this.getInputsList();
                }, 200);
            else
                alert('browser doest support getUserMedia');
            this.speed = document.getElementById(prefix + '-speed');
            this.hrange = view.find('[data-id=h-range]').on('change', null, function (evt) {
                return _this.onRangeChanged(evt);
            }).on('input', null, function (evt) {
                return _this.onRangeInput(evt);
            });
            ;
            this.h_indicator = view.find('[data-id=h_indicator]');
            this.onRangeInput(null);
            this.onRangeChanged(null);
        }
        Oscilloscope1.prototype.onRangeChanged = function (evt) {
            var val = Number(this.hrange.val());
            if (isNaN(val))
                return;
            val = Math.pow(2, val);
            this.bufferSize = val;
            if (this.display)
                this.display.makeBuffer(val);
        };
        Oscilloscope1.prototype.onRangeInput = function (evt) {
            var val = Number(this.hrange.val());
            if (isNaN(val))
                return;
            val = Math.pow(2, val);
            this.h_indicator.text(val);
        };
        Oscilloscope1.prototype.getInputsList = function () {
            var _this = this;
            MediaStreamTrack.getSources(function (info) {
                return _this.gotSources(info);
            });
        };
        Oscilloscope1.prototype.gotSources = function (souces) {
            //   console.log(souces);
            var str = '';
            for (var i = 0, n = souces.length; i < n; i++)
                if (souces[i].kind == 'audio')
                    str += '<option value="' + souces[i].id + '">' + (souces[i].label || 'Input ' + i) + '</option>';
            this.select.html(str);
            this.getUserMedia({ audio: true, video: false });
        };
        Oscilloscope1.prototype.getUserMedia = function (constraints) {
            var _this = this;
            navigator.getUserMedia(constraints, function (stream) {
                return _this.onConnect(stream);
            }, function (e) {
                alert('Error getting audio');
                console.log(e);
            });
        };
        Oscilloscope1.prototype.onInputChanged = function (evt) {
            var select = evt.currentTarget;
            var constraints = {
                audio: {
                    optional: [{ sourceId: select.value }]
                },
                video: false
            };
            console.log('getting source ' + select.value);
            this.getUserMedia(constraints);
        };
        Oscilloscope1.prototype.onConnect = function (stream) {
            //  this.mediaStriam = stream;
            if (this.display)
                this.display.destroy();
            this.stage.removeAllChildren();
            this.display = new OscDisplay(this.audioContext, stream, this.stage, this.speed, this.lineColor);
            this.display.makeBuffer(this.bufferSize).connect();
        };
        return Oscilloscope1;
    })();
    upl.Oscilloscope1 = Oscilloscope1;
////////////////////////////-----------------------------------------------////////////////////////////////////////////////////
    var OscDisplay = (function () {
        function OscDisplay(context, stream, stage, speed, color) {
            this.count = 0;
            this.startn = 0;
            this.speed = speed;
            this.stage = stage;
            this.color = color;
            this.context = context;
            this.stream = stream;
            var sh = new c.Shape();
            this.screen = sh;
            stage.addChild(sh);
            this.graphics = sh.graphics;
            this.create();
        }
        OscDisplay.prototype.create = function () {
            this.streamSource = this.context.createMediaStreamSource(this.stream);
            this.analyser = this.context.createAnalyser();
        };
        OscDisplay.prototype.destroy = function () {
            this.streamSource = null;
            this.jsProcessor.onaudioprocess = null;
            this.jsProcessor = null;
            this.analyser = null;
            this.isConnected = false;
        };
        OscDisplay.prototype.disconnect = function () {
            this.analyser.disconnect();
            this.streamSource.disconnect();
            this.jsProcessor.disconnect();
            this.jsProcessor.onaudioprocess = null;
            this.isConnected = false;
            return this;
        };
        OscDisplay.prototype.connect = function () {
            var _this = this;
            console.log('Connectiong with data length: ' + this.data.length);
            this.isConnected = true;
            this.jsProcessor.connect(this.analyser);
            this.streamSource.connect(this.analyser);
            this.startn = Date.now();
            this.count = 0;
            this.jsProcessor.onaudioprocess = function (evt) {
                return _this.onAudioProcess(evt);
            };
            return this;
        };
        OscDisplay.prototype.makeBuffer = function (buffer) {
            var connected = this.isConnected;
            if (connected)
                this.disconnect();
            this.data = new Uint8Array(buffer);
            this.jsProcessor = this.context.createScriptProcessor(buffer, 1, 1);
            if (connected)
                this.connect();
            return this;
        };
        OscDisplay.prototype.onAudioProcess = function (evt) {
            var data = this.data;
            this.analyser.getByteTimeDomainData(data);
            var g = this.graphics;
            g.clear();
            g.setStrokeStyle(1);
            g.beginStroke(this.color);
            g.moveTo(0, data[0]);
            var n = data.length;
            var dev = 1024 / n;
            for (var i = 1; i < n; i++)
                g.lineTo(i * dev, data[i]);
            this.stage.update();
            if (++this.count == 9) {
                var end = Date.now();
                this.count = 0;
                this.speed.textContent = ((end - this.startn) / 10).toString();
                this.startn = end;
            }
        };
        return OscDisplay;
    })();
    upl.OscDisplay = OscDisplay;
})(upl || (upl = {}));

No comments:

Post a Comment