spectrogram

master
Ondřej Hruška 8 years ago
parent 42f869de61
commit 55ed773298
  1. 2
      esp_meas.pro.user
  2. 6
      html/js/all.js
  3. 2
      html/pages/about.tpl
  4. 2
      html/pages/fft.html
  5. 2
      html/pages/status.tpl
  6. 2
      html/pages/wfm.html
  7. 2
      html/pages/wifi.tpl
  8. 2
      html_src/_start.php
  9. 1
      html_src/gulpfile.js
  10. 11
      html_src/js-src/app.js
  11. 157
      html_src/js-src/lib/chibi.js
  12. 165
      html_src/js-src/page_spectrogram.js
  13. 23
      html_src/js-src/page_waveform.js
  14. 4
      html_src/js-src/utils.js
  15. 6
      html_src/js/all.js
  16. 2
      html_src/js/all.js.map
  17. 29
      html_src/page_spectrogram.php

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 3.6.1, 2016-04-01T14:34:14. --> <!-- Written by QtCreator 3.6.1, 2016-04-01T15:01:35. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>

File diff suppressed because one or more lines are too long

@ -19,7 +19,7 @@
<div id="outer"> <div id="outer">
<nav id="menu"> <nav id="menu">
<div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div> <div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div>
<a href="/status">Home</a><a href="/wifi">WiFi config</a><a href="/waveform">Waveform</a><a href="/fft">FFT</a><a href="/about" class="selected">About</a></nav> <a href="/status">Home</a><a href="/wifi">WiFi config</a><a href="/waveform">Waveform</a><a href="/fft">FFT</a><a href="/spectrogram">Spectrogram</a><a href="/about" class="selected">About</a></nav>
<div id="content"> <div id="content">
<img src="/img/loader.gif" alt="Loading…" id="loader"> <img src="/img/loader.gif" alt="Loading…" id="loader">

@ -19,7 +19,7 @@
<div id="outer"> <div id="outer">
<nav id="menu"> <nav id="menu">
<div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div> <div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div>
<a href="/status">Home</a><a href="/wifi">WiFi config</a><a href="/waveform">Waveform</a><a href="/fft" class="selected">FFT</a><a href="/about">About</a></nav> <a href="/status">Home</a><a href="/wifi">WiFi config</a><a href="/waveform">Waveform</a><a href="/fft" class="selected">FFT</a><a href="/spectrogram">Spectrogram</a><a href="/about">About</a></nav>
<div id="content"> <div id="content">
<img src="/img/loader.gif" alt="Loading…" id="loader"> <img src="/img/loader.gif" alt="Loading…" id="loader">

@ -19,7 +19,7 @@
<div id="outer"> <div id="outer">
<nav id="menu"> <nav id="menu">
<div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div> <div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div>
<a href="/status" class="selected">Home</a><a href="/wifi">WiFi config</a><a href="/waveform">Waveform</a><a href="/fft">FFT</a><a href="/about">About</a></nav> <a href="/status" class="selected">Home</a><a href="/wifi">WiFi config</a><a href="/waveform">Waveform</a><a href="/fft">FFT</a><a href="/spectrogram">Spectrogram</a><a href="/about">About</a></nav>
<div id="content"> <div id="content">
<img src="/img/loader.gif" alt="Loading…" id="loader"> <img src="/img/loader.gif" alt="Loading…" id="loader">

@ -19,7 +19,7 @@
<div id="outer"> <div id="outer">
<nav id="menu"> <nav id="menu">
<div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div> <div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div>
<a href="/status">Home</a><a href="/wifi">WiFi config</a><a href="/waveform" class="selected">Waveform</a><a href="/fft">FFT</a><a href="/about">About</a></nav> <a href="/status">Home</a><a href="/wifi">WiFi config</a><a href="/waveform" class="selected">Waveform</a><a href="/fft">FFT</a><a href="/spectrogram">Spectrogram</a><a href="/about">About</a></nav>
<div id="content"> <div id="content">
<img src="/img/loader.gif" alt="Loading…" id="loader"> <img src="/img/loader.gif" alt="Loading…" id="loader">

@ -19,7 +19,7 @@
<div id="outer"> <div id="outer">
<nav id="menu"> <nav id="menu">
<div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div> <div id="brand" onclick="$('#menu').toggleClass('expanded')">Current Analyser</div>
<a href="/status">Home</a><a href="/wifi" class="selected">WiFi config</a><a href="/waveform">Waveform</a><a href="/fft">FFT</a><a href="/about">About</a></nav> <a href="/status">Home</a><a href="/wifi" class="selected">WiFi config</a><a href="/waveform">Waveform</a><a href="/fft">FFT</a><a href="/spectrogram">Spectrogram</a><a href="/about">About</a></nav>
<div id="content"> <div id="content">
<img src="/img/loader.gif" alt="Loading…" id="loader"> <img src="/img/loader.gif" alt="Loading…" id="loader">

@ -8,7 +8,7 @@
'wifi' => [ $prod ? '/wifi' : '/page_wifi.php', 'WiFi config' ], 'wifi' => [ $prod ? '/wifi' : '/page_wifi.php', 'WiFi config' ],
'waveform' => [ $prod ? '/waveform' : '/page_waveform.php', 'Waveform' ], 'waveform' => [ $prod ? '/waveform' : '/page_waveform.php', 'Waveform' ],
'fft' => [ $prod ? '/fft' : '/page_fft.php', 'FFT' ], 'fft' => [ $prod ? '/fft' : '/page_fft.php', 'FFT' ],
// 'spectrogram' => [ '/spectrogram', 'Spectrogram' ], 'spectrogram' => [ $prod ? '/spectrogram' : '/page_spectrogram.php', 'Spectrogram' ],
// 'transient' => [ '/transient', 'Power-on transient' ], // 'transient' => [ '/transient', 'Power-on transient' ],
'about' => [ $prod ? '/about' : '/page_about.php', 'About' ], 'about' => [ $prod ? '/about' : '/page_about.php', 'About' ],
]; ];

@ -31,6 +31,7 @@ elixir(function (mix) {
'js-src/app.js', 'js-src/app.js',
'js-src/page_wifi.js', 'js-src/page_wifi.js',
'js-src/page_waveform.js', 'js-src/page_waveform.js',
'js-src/page_spectrogram.js',
'js-src/page_status.js', 'js-src/page_status.js',
], 'js/all.js'); ], 'js/all.js');
}); });

@ -20,6 +20,17 @@ $().ready(function () {
}); });
}, 1000); }, 1000);
$('input[type=number]').on('mousewheel', function(e) {
var val = +$(this).val();
var step = +($(this).attr('step') || 1);
if(e.wheelDelta > 0) {
val += step;
} else {
val -= step;
}
$(this).val(val);
});
modal.init(); modal.init();
notify.init(); notify.init();
}); });

@ -63,7 +63,9 @@
// Convert to camel case // Convert to camel case
function cssCamel(property) { function cssCamel(property) {
return property.replace(/-\w/g, function (result) {return result.charAt(1).toUpperCase(); }); return property.replace(/-\w/g, function (result) {
return result.charAt(1).toUpperCase();
});
} }
// Get computed style // Get computed style
@ -82,7 +84,8 @@
function setCss(elm, property, value) { function setCss(elm, property, value) {
try { try {
elm.style[cssCamel(property)] = value; elm.style[cssCamel(property)] = value;
} catch (e) {} } catch (e) {
}
} }
// Show CSS // Show CSS
@ -117,38 +120,38 @@
if (!subelm.disabled) { if (!subelm.disabled) {
switch (subelm.type) { switch (subelm.type) {
// Ignore buttons, unsupported XHR 1 form fields // Ignore buttons, unsupported XHR 1 form fields
case 'button': case 'button':
case 'image': case 'image':
case 'file': case 'file':
case 'submit': case 'submit':
case 'reset': case 'reset':
break; break;
case 'select-one':
if (subelm.length > 0) {
querystring += '&' + queryPair(subelm.name, subelm.value);
}
break;
case 'select-multiple': case 'select-one':
for (j = 0; j < subelm.length; j += 1) { if (subelm.length > 0) {
if (subelm[j].selected) { querystring += '&' + queryPair(subelm.name, subelm.value);
querystring += '&' + queryPair(subelm.name, subelm[j].value);
} }
} break;
break;
case 'checkbox': case 'select-multiple':
case 'radio': for (j = 0; j < subelm.length; j += 1) {
if (subelm.checked) { if (subelm[j].selected) {
querystring += '&' + queryPair(subelm.name, subelm.value); querystring += '&' + queryPair(subelm.name, subelm[j].value);
} }
break; }
break;
// Everything else including shinny new HTML5 input types case 'checkbox':
default: case 'radio':
querystring += '&' + queryPair(subelm.name, subelm.value); if (subelm.checked) {
querystring += '&' + queryPair(subelm.name, subelm.value);
}
break;
// Everything else including shinny new HTML5 input types
default:
querystring += '&' + queryPair(subelm.name, subelm.value);
} }
} }
} }
@ -205,7 +208,9 @@
} else if (position === 'prepend') { } else if (position === 'prepend') {
elm.insertBefore(tmpnode, elm.firstChild); elm.insertBefore(tmpnode, elm.firstChild);
} }
} catch (e) {break; } } catch (e) {
break;
}
} }
}, nodes); }, nodes);
} }
@ -347,7 +352,7 @@
// Toggle node display // Toggle node display
cb.toggle = function (state) { cb.toggle = function (state) {
if (typeof state != 'undefined') { // ADDED if (typeof state != 'undefined') { // ADDED
if(state) if (state)
cb.show(); cb.show();
else else
cb.hide(); cb.hide();
@ -370,7 +375,8 @@
// Catch error in unlikely case elm has been removed // Catch error in unlikely case elm has been removed
try { try {
elm.parentNode.removeChild(elm); elm.parentNode.removeChild(elm);
} catch (e) {} } catch (e) {
}
}, nodes); }, nodes);
return chibi(); return chibi();
}; };
@ -397,7 +403,7 @@
cb.getClass = function () { cb.getClass = function () {
if (nodes[0] && nodes[0].className.length > 0) { if (nodes[0] && nodes[0].className.length > 0) {
// Weak IE trim support // Weak IE trim support
return nodes[0].className.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '').replace(/\s+/,' '); return nodes[0].className.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '').replace(/\s+/, ' ');
} }
}; };
// Set (replaces) classes // Set (replaces) classes
@ -501,7 +507,7 @@
// Get/Set HTML data property // Get/Set HTML data property
cb.data = function (key, value) { cb.data = function (key, value) {
if (key) { if (key) {
return cb.attr('data-'+key, value); return cb.attr('data-' + key, value);
} }
}; };
// Get/Set form element values // Get/Set form element values
@ -510,26 +516,26 @@
if (value || value === '') { if (value || value === '') {
nodeLoop(function (elm) { nodeLoop(function (elm) {
switch (elm.nodeName) { switch (elm.nodeName) {
case 'SELECT': case 'SELECT':
if (typeof value === 'string' || typeof value === 'number') { if (typeof value === 'string' || typeof value === 'number') {
value = [value]; value = [value];
} }
for (i = 0; i < elm.length; i += 1) { for (i = 0; i < elm.length; i += 1) {
// Multiple select // Multiple select
for (j = 0; j < value.length; j += 1) { for (j = 0; j < value.length; j += 1) {
elm[i].selected = ''; elm[i].selected = '';
if (elm[i].value === value[j]) { if (elm[i].value === value[j]) {
elm[i].selected = 'selected'; elm[i].selected = 'selected';
break; break;
}
} }
} }
} break;
break; case 'INPUT':
case 'INPUT': case 'TEXTAREA':
case 'TEXTAREA': case 'BUTTON':
case 'BUTTON': elm.value = value;
elm.value = value; break;
break;
} }
}, nodes); }, nodes);
@ -537,18 +543,18 @@
} }
if (nodes[0]) { if (nodes[0]) {
switch (nodes[0].nodeName) { switch (nodes[0].nodeName) {
case 'SELECT': case 'SELECT':
values = []; values = [];
for (i = 0; i < nodes[0].length; i += 1) { for (i = 0; i < nodes[0].length; i += 1) {
if (nodes[0][i].selected) { if (nodes[0][i].selected) {
values.push(nodes[0][i].value); values.push(nodes[0][i].value);
}
} }
} return (values.length > 1) ? values : values[0];
return (values.length > 1) ? values : values[0]; case 'INPUT':
case 'INPUT': case 'TEXTAREA':
case 'TEXTAREA': case 'BUTTON':
case 'BUTTON': return nodes[0].value;
return nodes[0].value;
} }
} }
}; };
@ -576,7 +582,9 @@
elm.addEventListener(event, fn, false); elm.addEventListener(event, fn, false);
} else if (d.attachEvent) { } else if (d.attachEvent) {
// <= IE 8 loses scope so need to apply, we add this to object so we can detach later (can't detach anonymous functions) // <= IE 8 loses scope so need to apply, we add this to object so we can detach later (can't detach anonymous functions)
elm[event + fn] = function () { return fn.apply(elm, arguments); }; elm[event + fn] = function () {
return fn.apply(elm, arguments);
};
elm.attachEvent('on' + event, elm[event + fn]); elm.attachEvent('on' + event, elm[event + fn]);
} }
}, nodes); }, nodes);
@ -599,11 +607,14 @@
return cb; return cb;
}; };
// Basic XHR 1, no file support. Shakes fist at IE // Basic XHR 1, no file support. Shakes fist at IE
cb.ajax = function (url, method, callback, options) { cb.ajax = function (url, method, callback, options) { // if options is a number, it's timeout in ms
var xhr, var xhr,
query = serializeData(nodes), query = serializeData(nodes),
type = (method) ? method.toUpperCase() : 'GET', type = (method) ? method.toUpperCase() : 'GET',
timestamp = '_ts=' + (+new Date()); timestamp = '_ts=' + (+new Date()),
abortTmeo;
if (_.isNumber(options)) options = {timeout: options};
var opts = Chartist.extend({}, { var opts = Chartist.extend({}, {
nocache: true, nocache: true,
@ -611,7 +622,7 @@
loader: true, loader: true,
}, options); }, options);
console.log('ajax to = '+opts.timeout); console.log('ajax to = ' + opts.timeout);
if (query && (type === 'GET')) { if (query && (type === 'GET')) {
url += (url.indexOf('?') === -1) ? '?' + query : '&' + query; url += (url.indexOf('?') === -1) ? '?' + query : '&' + query;
@ -633,6 +644,12 @@
xhr.timeout = opts.timeout; xhr.timeout = opts.timeout;
abortTmeo = setTimeout(function () {
errorMsg("XHR timed out.");
xhr.abort();
if (opts.loader) $('#loader').removeClass('show');
}, opts.timeout + 10); // a bit later, but still.;
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { if (xhr.readyState === 4) {
@ -642,6 +659,8 @@
if (callback && xhr.status != 0) { // xhr.status 0 means "aborted" if (callback && xhr.status != 0) { // xhr.status 0 means "aborted"
callback(xhr.responseText, xhr.status); callback(xhr.responseText, xhr.status);
} }
clearTimeout(abortTmeo);
} }
}; };
@ -654,7 +673,7 @@
xhr.send(query); xhr.send(query);
} }
return cb; return xhr;
}; };
// Alias to cb.ajax(url, 'get', callback) // Alias to cb.ajax(url, 'get', callback)
cb.get = function (url, callback, opts) { cb.get = function (url, callback, opts) {

@ -0,0 +1,165 @@
var page_spectrogram = (function () {
var sg = {};
var ctx;
// drawing area
var plot = {
x:0,
y:0,
w:860,
h:512,
dx: 1, // bin
dy: 1
};
var interval = 1000;
var running = false;
var readTimeout; // timer
var readoutPending;
var readXhr;
var sampCount = 1024;
var binCount = sampCount/2;
var colormap = {
r: [
{x: 0, b: 0},
{x: .7, b: 0},
{x: 1, b: 1},
],
g: [
{x: 0, b: 0},
{x: .3, b: 0},
{x: .7, b: 1},
{x: 1, b: 1},
],
b: [
{x: 0, b: 0},
{x: .02, b: .3},
{x: .3, b: 1},
{x: 1, b: 1},
]
};
function cmResolv(db, tab) {
var startX,endX,startC,endC;
db /=6;
if (db > 1) db = 1;
if (db < 0) db = 0;
for (var i = 0; i < tab.length; i++) {
var p = tab[i];
if (db >= p.x) {
startX = p.x;
startC = p.b;
}
if (db <= p.x) {
endX = p.x;
endC = p.b;
}
}
return Math.round((startC + (endC - startC)*((db - startX)/(endX - startX)))*255);
}
function val2color(x) {
var xx = x;//20 * Math.log(x);
var r = cmResolv(xx, colormap.r);
var g = cmResolv(xx, colormap.g);
var b = cmResolv(xx, colormap.b);
return 'rgb('+r+','+g+','+b+')';
}
function shiftSg() {
var imageData = ctx.getImageData(plot.x+plot.dx, plot.y, plot.w-plot.dx, plot.h);
ctx.putImageData(imageData, plot.x, plot.y);
}
function drawSg(col) {
shiftSg();
for (var i = 0; i < binCount; i++) {
// resolve color from the value
var y = binCount - i;
var clr;
if (i > col.length) {
clr = '#000';
} else {
clr = val2color(col[i]);
}
ctx.fillStyle = clr;
ctx.fillRect(plot.x+plot.w-plot.dx, plot.y+y*plot.dy, plot.dx, plot.dy);
}
}
function onRxData(resp, status) {
readoutPending = false;
if (status == 200) {
try {
var j = JSON.parse(resp);
if (j.success) {
// display
drawSg(j.samples);
} else {
errorMsg("Sampling failed.");
}
} catch(e) {
errorMsg(e);
}
} else {
errorMsg("Request failed.");
}
if (running)
readTimeout = setTimeout(requestData, interval); // TODO should actually compute time remaining, this adds interval to the request time.
}
function requestData() {
if (readoutPending) {
errorMsg("Request already pending - aborting.");
readXhr.abort();
}
readoutPending = true;
var fs = $('#freq').val();
var url = _root+'/measure/fft?n='+sampCount+'&fs='+fs;
readXhr = $().get(url, onRxData, estimateLoadTime(fs,sampCount));
return true;
}
sg.init = function () {
var canvas = $('#sg')[0];
ctx = canvas.getContext('2d');
ctx.fillStyle = '#000';
ctx.fillRect(plot.x, plot.y, plot.w, plot.h);
$('#go-btn').on('click', function() {
interval = +$('#interval').val(); // ms
running = !running;
if (running) {
requestData();
} else {
clearTimeout(readTimeout);
}
$('#go-btn')
.toggleClass('btn-green')
.toggleClass('btn-red')
.html(running ? 'Stop' : 'Start');
});
};
return sg;
})();

@ -168,15 +168,8 @@ var page_waveform = (function () {
var n = $('#count').val(); var n = $('#count').val();
var fs = $('#freq').val(); var fs = $('#freq').val();
var url = _root+'/measure/{fmt}?n={n}&fs={fs}'.format({ var url = _root+'/measure/'+dataFormat+'?n='+n+'&fs='+fs;
fmt: dataFormat, // fft or raw $().get(url, onRxData, estimateLoadTime(fs,n));
n: n,
fs: fs
});
$().get(url, onRxData, {
timeout: (1000/fs)*n+1500
});
return true; return true;
} }
@ -224,18 +217,6 @@ var page_waveform = (function () {
return false; return false;
}); });
// --- scroll the input box ---
$('input[type=number]').on('mousewheel', function(e) {
var val = +$(this).val();
var step = +($(this).attr('step') || 1);
if(e.wheelDelta > 0) {
val += step;
} else {
val -= step;
}
$(this).val(val);
});
// auto-reload button // auto-reload button
$('#ar-btn').on('click', toggleAutoReload); $('#ar-btn').on('click', toggleAutoReload);
}; };

@ -7,6 +7,10 @@ function numfmt(x, places) {
return Math.round(x*pow) / pow; return Math.round(x*pow) / pow;
} }
function estimateLoadTime(fs, n) {
return (1000/fs)*n+1500;
}
/** /**
* Perform a substitution in the given string. * Perform a substitution in the given string.
* *

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,29 @@
<?php $page = 'spectrogram'; include "_start.php"; ?>
<h1>Spectrogram</h1>
<div class="Box center" id="samp-ctrl">
<div>
<label for="freq">Rate <span class="mq-tablet-max" style="font-weight:normal;">(Hz)</span></label>
<input id="freq" type="number" value="4096">
<span class="mq-normal-min">Hz</span>
</div>
<div>
<label for="interval">Interval <span class="mq-tablet-max" style="font-weight:normal;">(ms)</span></label>
<input id="interval" type="number" value="100" step=100 min=0>
<span class="mq-normal-min">ms</span>
</div>
<div>
<a id="go-btn" class="button btn-green">Start</a>
</div>
</div>
<div class="Box center">
<canvas id="sg" width=860 height=512></canvas>
</div>
<script>
$().ready(page_spectrogram.init());
</script>
<?php include "_end.php"; ?>
Loading…
Cancel
Save