Search code examples
javascriptarraysnode-red

How to send msg.payload Int16Array from node-red dashboard template


I am trying to send an Int16Array from a node-red dashboard template. In the template I have:

var i16Buff = new Int16Array(i16BuffSize);

//... fill with data

scope.send({payload: i16Buff});

The buffer comes through msg.payload and I can see the data in console.log as a JSON array. How do I send it from node-red dashboard template so it remains an Int16Array.


Solution

  • Because this was so painful to figure out, I thought I'd just put my entire code up as an example for others:

    <!DOCTYPE html>
    <video style="height: 0px; width: 0px;"></video>
    <md-button ng-click="startStopRec()">{{ label }}</md-button>
    
    <script>
    
        (function(scope) {
    
            // Setup cross browser compatibility
            navigator.getUserMedia  = navigator.getUserMedia ||
                    navigator.webkitGetUserMedia ||
                    navigator.mozGetUserMedia ||
                    navigator.msGetUserMedia;
    
            var video = document.querySelector('video');
    
            var startedRecording = false;
            var endRecording = false;
            var scriptNode;
            var audioCtx;
    
            // Check if supported by browser
            if (navigator.getUserMedia) {
                console.log('getUserMedia supported.');
                navigator.getUserMedia (
    
                    {
                        audio: true,
                        video: false
                    },
    
                    // Success callback
                    function(stream) {
    
                        var buffSize = 2048;
                        var buff = [];
    
                        video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
                        video.onloadedmetadata = function(e) {
                            video.muted = 'true';
                        };
    
                        audioCtx = new AudioContext();
                        var source = audioCtx.createMediaStreamSource(stream);
    
                        scriptNode = audioCtx.createScriptProcessor(buffSize, 1, 1);
    
                        scriptNode.onaudioprocess = function(APE) {
    
                            if(endRecording === true) {
    
                                // There is probably a better way to do this but it worked for me
                                scriptNode.disconnect();
                                startedRecording = false;
                                endRecording = false;
    
                                // This was key for creating appropriate buffer for msg.payload.
                                var rawBuffer = new ArrayBuffer(buff.length * buffSize * 2);
                                var rawView = new DataView(rawBuffer);
    
                                var index = 0;
                                for (var i = 0; i < buff.length; i++) {
    
                                    // Convert multi array audio buffer to flat Int16
                                    for (var j = 0; j < (buffSize); j++){
                                        rawView.setInt16(index, buff[i][j] * 0x7FFF, true);
                                        index += 2;
                                    }
                                }
                                // Send msg
                                scope.send({payload: rawBuffer, sampleRate: audioCtx.sampleRate});
                                // Clear buffer for next time
                                buff = [];
    
                            } else {
                                // Collect audio buffers into array
                                console.log('Getting data');
                                buff.push(new Float32Array(APE.inputBuffer.getChannelData(0)));
                            }
                        }
                        source.connect(scriptNode);
                    },
    
                    // Error callback
                    function(err) {
                        console.log('The following gUM error occured: ' + err);
                    }
                );
    
            } else {
               console.log('getUserMedia not supported on your browser!');
            }
    
            function writeUTFBytes(view, offset, string){ 
                var lng = string.length;
                for (var i = 0; i < lng; i++){
                    view.setUint8(offset + i, string.charCodeAt(i));
                }
            }
    
            if(scope.label === undefined) {
                scope.label = 'Record';
            }
    
            scope.startStopRec = function() {
    
                if(scope.label === 'Record') {
                    scope.label = 'Send';
                    scriptNode.connect(audioCtx.destination);
                    video.play();
                    startedRecording = true;
                } else {
                    scope.label = 'Record';
                    if(startedRecording === true) {
                        endRecording = true;
                        video.pause();
                    }
                }
            }
    
        })(scope);    
    
    </script>
    

    This template returns (msg.payload) a buffer of raw mono audio data from the microphone and (msg.sampleRate) the sample rate through msg to the next node in the line.

    NOTE: You must use HTTPS for it to work (from what I've read/experienced).