From 8fd0256a43c6f9b4d4623e372d83fbb712ed8787 Mon Sep 17 00:00:00 2001 From: stelian56 Date: Fri, 20 Feb 2015 17:08:02 -0500 Subject: [PATCH] Euler angles-based grid --- bmconfig.json | 11 +- bmserver.py => bodymusic.py | 21 +- css/bm.css | 4 - css/bodymusic.css | 75 +++ html/bm.html | 26 - html/bodymusic.html | 11 + html/play.html | 63 --- js/bm.js | 77 --- js/bodymusic.js | 600 ++++++++++++++++++++ log/2015-02-14.1.log | 1042 ----------------------------------- log/2015-02-20.91.log | 837 ++++++++++++++++++++++++++++ 11 files changed, 1538 insertions(+), 1229 deletions(-) rename bmserver.py => bodymusic.py (96%) delete mode 100644 css/bm.css create mode 100644 css/bodymusic.css delete mode 100644 html/bm.html create mode 100644 html/bodymusic.html delete mode 100644 html/play.html delete mode 100644 js/bm.js create mode 100644 js/bodymusic.js delete mode 100644 log/2015-02-14.1.log create mode 100644 log/2015-02-20.91.log diff --git a/bmconfig.json b/bmconfig.json index 01cea8e..c26a5fc 100644 --- a/bmconfig.json +++ b/bmconfig.json @@ -4,14 +4,9 @@ "maxUpdateRate": 100, "readings": [ { - "name": "quat", - "command": "getTaredOrientationAsQuaternion", - "components": ["q0", "q1", "q2", "q3"] - }, - { - "name": "acc", - "command": "getCorrectedAccelerometerVector", - "components": ["x", "y", "z"] + "name": "angles", + "command": "getTaredOrientationAsEulerAngles", + "components": ["pitch", "yaw", "roll"] } ] }, diff --git a/bmserver.py b/bodymusic.py similarity index 96% rename from bmserver.py rename to bodymusic.py index 30ec432..e117940 100644 --- a/bmserver.py +++ b/bodymusic.py @@ -16,7 +16,6 @@ config_file_name = "bmconfig.json" log_dir = "log" -log_file_regex = re.compile(".+\.(.+)\.log") class BMHttpHandler(BaseHTTPServer.BaseHTTPRequestHandler): def __init__(self, on_start, on_stop, on_playback, *args): @@ -165,15 +164,19 @@ def __init__(self, config, update_rate, on_sensor_data, stop_event): self.sensors = None def create_log_file(self): - file_names = glob.glob("%s/*.log" % log_dir) + today = datetime.date.today() + file_names = glob.glob("%s/%s.*.log" % (log_dir, today)) file_count = len(file_names) - suffix = "1" + suffix = 1 if file_count > 0: - file_names.sort() - last_file_name = file_names.pop() - match = log_file_regex.match(last_file_name) - suffix = int(match.group(1)) + 1 - file_path = "%s/%s.%s.log" % (log_dir, datetime.date.today(), suffix) + regex = re.compile(".+\.(.+)\.log") + suffixes = [] + for file_name in file_names: + match = regex.match(file_name) + suffixes.append(int(match.group(1))) + suffixes.sort() + suffix = suffixes.pop() + 1 + file_path = "%s/%s.%s.log" % (log_dir, today, suffix) dir_name = os.path.dirname(file_path) if not os.path.exists(dir_name): os.makedirs(dir_name) @@ -183,7 +186,6 @@ def init(self): self.log_file = self.create_log_file() if not self.log_file: return False - print "Started logging to %s" % self.log_file.name device_list = api.getComPorts(filter = api.TSS_FIND_DNG|api.TSS_FIND_WL) for device_port in device_list: com_port, device_name, device_type = device_port @@ -231,6 +233,7 @@ def init(self): eval(expression) sensor.startStreaming() print "Started sensors at update rate %dHz" % self.update_rate + print "Started logging to %s" % self.log_file.name return True def stop(self): diff --git a/css/bm.css b/css/bm.css deleted file mode 100644 index 5222bf8..0000000 --- a/css/bm.css +++ /dev/null @@ -1,4 +0,0 @@ -.update_rate { - width: 60px; -} - \ No newline at end of file diff --git a/css/bodymusic.css b/css/bodymusic.css new file mode 100644 index 0000000..be97902 --- /dev/null +++ b/css/bodymusic.css @@ -0,0 +1,75 @@ +body { +margin: 10px; +} +.bodymusic_ribbon { +width: 100%; +margin-top: 10px; +margin-bottom: 10px; +} +.bodymusic_legendicon { +display: inline-block; +width: 15px; +height: 15px; +margin-left: 15px; +} +.bodymusic_legendlabel { +margin-left: 3px; +} +.bodymusic_staff { +position: relative; +width: 100%; +border: 1px solid black; +} +.bodymusic_plot { +position: relative; +width: 100%; +margin: -1px 0 0 0; +border: 1px solid black; +z-index: 1; +} +.bodymusic_veil { +position: absolute; +top: 0; +left: 0; +right: 0; +height: 100%; +margin: 0; +background-color: black; +opacity: 0.3; +} +.bodymusic_tooltip { +position: absolute; +display: none; +padding: 5px; +background-color: #ffffcc; +border: 1px solid black; +z-index: 2; +} +svg { +width: 100%; +height: 100%; +} +line { +display: none; +} +circle { +display: none; +} +ellipse { +display: none; +} +text { +font-size: 0.9em; +} +.bodymusic_updaterate { + margin-left: 5px; + margin-right: 10px; + width: 60px; +} +.bodymusic_logfile { + margin-left: 10px; + margin-right: 10px; +} +.bodymusic_button { + margin-right: 10px; +} \ No newline at end of file diff --git a/html/bm.html b/html/bm.html deleted file mode 100644 index 45a3e9f..0000000 --- a/html/bm.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - -Body Music - - - - -Update rate (Hz): - - - - - - - - - - - diff --git a/html/bodymusic.html b/html/bodymusic.html new file mode 100644 index 0000000..9945d23 --- /dev/null +++ b/html/bodymusic.html @@ -0,0 +1,11 @@ + + + + + +Body Music + + + + + diff --git a/html/play.html b/html/play.html deleted file mode 100644 index 6b4b292..0000000 --- a/html/play.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - -

- - - - - diff --git a/js/bm.js b/js/bm.js deleted file mode 100644 index 9a977f4..0000000 --- a/js/bm.js +++ /dev/null @@ -1,77 +0,0 @@ -var bm = (function() { - - var websocketPort = 8081; - var websocket; - - var startWebSocket = function() { - if (!websocket || websocket.readyState != WebSocket.OPEN) { - websocket = new WebSocket("ws://localhost:" + websocketPort); - websocket.onmessage = function(event) { - var sensorData = JSON.parse(event.data); - console.log(JSON.stringify(sensorData, null, 0)); - }; - } - }; - - var stop = function() { - var request = new XMLHttpRequest(); - request.open("GET", "/?command=stop", true); - request.send(); - }; - - var start = function() { - startWebSocket(); - var updateRateString = $("#updateRate").val(); - var updateRate = parseInt(updateRateString); - var query = "/?command=start"; - if (updateRate) { - query += "&updateRate=" + updateRate; - } - var request = new XMLHttpRequest(); - request.open("GET", query, true); - request.send(); - }; - - var playback = function() { - startWebSocket(); - var logFile = $("#logFiles").val(); - if (logFile) { - var query = "/?command=playback"; - query += "&logFile=" + logFile; - } - var request = new XMLHttpRequest(); - request.open("GET", query, true); - request.send(); - }; - - var parseInputs = function() { - $("#stop").click(stop); - $("#start").click(start); - $("#playback").click(playback); - }; - - var getLogFiles = function() { - var request = new XMLHttpRequest(); - request.onload = function() { - $.each(request.responseText.split(","), function() { - $("#logFiles").append($("").text(this)); - }); - }; - request.open("GET", "/?command=getLogFiles", true); - request.send(); - } - - var init = function() { - getLogFiles(); - parseInputs(); - startWebSocket(); - }; - - return { - init: init - }; -})(); - -$(document).ready(function() { - bm.init(); -}); diff --git a/js/bodymusic.js b/js/bodymusic.js new file mode 100644 index 0000000..639c796 --- /dev/null +++ b/js/bodymusic.js @@ -0,0 +1,600 @@ +var bmprocessor = (function() { + + var angleAssignment = { yaw: "group", roll: "row", pitch: "column" }; + var angleRanges = { yaw: [-Math.PI, Math.PI], roll: [-Math.PI, Math.PI], + pitch: [-0.4*Math.PI, 0.4*Math.PI] }; + var nodeCounts = { group: 8, row: 8, column: 8 }; + var overlap = 0.1; + var currentNode; + + var grid = [ + "MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY", + "MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY", + "MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY", + "MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY", + "MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY", + "MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY", + "MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY", + "MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY","MNQRTUXY" + ]; + + var update = function(data) { + var node = {}; + var groupIndex, rowIndex, columnIndex; + var nodeChanged = !currentNode && true; + $.each(data.angles, function(key, value) { + var assignment = angleAssignment[key]; + var nodeCount = nodeCounts[assignment]; + if (nodeCount > 1) { + var angleRange = angleRanges[key]; + var angleMin = angleRange[0]; + var angleMax = angleRange[1]; + if (value < angleMin || value > angleMax) { + nodeChanged = false; + return false; + } + var gap = (angleMax - angleMin)/(nodeCount - 1); + var nodeIndex = Math.round((value - angleMin)/gap); + node[assignment] = nodeIndex; + if (!nodeChanged) { + var currentNodeIndex = currentNode[assignment]; + if (Math.abs(value - (angleMin + currentNodeIndex*gap)) > 0.5*gap*(1 + 2*overlap)) { + nodeChanged = true; + } + } + } + }); + if (nodeChanged) { + var pitch = grid[node.row*nodeCounts.row + node.group][node.column]; + bmplayer.update(pitch); + currentNode = node; +// console.log(JSON.stringify(node, null) + " " + pitch); + } + }; + + var stop = function() { + currentNode = null; + } + + return { + update: update, + stop: stop + }; +})(); + +var bmplayer = (function() { + + var pitches = { + "A": {name: "C3"}, + "B": {name: "C#3"}, + "C": {name: "D3"}, + "D": {name: "D#3"}, + "E": {name: "E3"}, + "F": {name: "F3"}, + "G": {name: "F#3"}, + "H": {name: "G3"}, + "I": {name: "G#3"}, + "J": {name: "A3"}, + "K": {name: "A#3"}, + "L": {name: "B3"}, + "M": {name: "C4"}, + "N": {name: "C#4"}, + "O": {name: "D4"}, + "P": {name: "D#4"}, + "Q": {name: "E4"}, + "R": {name: "F4"}, + "S": {name: "F#4"}, + "T": {name: "G4"}, + "U": {name: "G#4"}, + "V": {name: "A4", frequency: 440}, + "W": {name: "A#4"}, + "X": {name: "B4"}, + "Y": {name: "C5"} + }; + var refPitch = "V"; + $.each(pitches, function(key) { + var offset = key.charCodeAt(0) - refPitch.charCodeAt(0); + if (offset != 0) { + this.frequency = pitches[refPitch].frequency*Math.pow(2, offset/12); + } + }); + + var contextClass = window.AudioContext || window.webkitAudioContext; + var context = new contextClass(); + var attack = 0.001 + var release = 0.5; + var spacing = 0.25; + var checkInterval = 0.025; + var lookahead = 0.05; + var lastNote; + var nextPitch; + var timer; + + var update = function(pitch) { + nextPitch = pitch; + } + + var scheduleNote = function(pitch, time) { + if (lastNote) { + lastNote.stop(time); + } + var note = context.createOscillator(); + var frequency = pitches[pitch].frequency; + note.frequency.setValueAtTime(frequency, time); + var envelope = context.createGain(); + envelope.gain.setValueAtTime(0, time); + envelope.gain.setTargetAtTime(1, time, attack); + envelope.gain.setTargetAtTime(0, time + attack, release); + note.connect(envelope); + envelope.connect(context.destination); + note.start(time); + lastNote = note; +// console.log(nextPitch); + }; + + var start = function() { + timer = setInterval(function() { + if (nextPitch) { + var currentTime = context.currentTime; + var tick = Math.floor((currentTime + lookahead)/spacing); + var time = tick*spacing; + if (Math.floor(currentTime < time)) { + scheduleNote(nextPitch, time); + nextPitch = null; + } + } + }, checkInterval*1000); + }; + + var stop = function() { + if (timer) { + clearInterval(timer); + } + }; + + return { + start: start, + update: update, + stop: stop + }; +})(); + +var defaultUpdateRate = 20; + +var bmplotter = (function() { + + var plotProps = [ + { + title: "Pitch", + sensorId: 0, + maxValue: 0.5*Math.PI, + key: "angles", + series: [ + {key: "pitch", name: "pitch", color: "magenta"} + ], + scale: 1, + unit: "" + }, + { + title: "Yaw", + sensorId: 0, + maxValue: Math.PI, + key: "angles", + series: [ + {key: "yaw", name: "yaw", color: "green"} + ], + scale: 1, + unit: "" + }, + { + title: "Roll", + sensorId: 0, + maxValue: Math.PI, + key: "angles", + series: [ + {key: "roll", name: "roll", color: "blue"} + ], + scale: 1, + unit: "" + } + ]; + var plotUpdateInterval = 1; + var plotStep = 2; + + var plotHeight = 300 / plotProps.length; + var plotMargin = 10; + var lineWidth = 2; + var markerRadius = 4; + var bodyRect; + var plots = []; + var clock = new (function() { + this.tick = 0; + })(); + + var createRibbon = function() { + bodyRect = document.body.getBoundingClientRect(); + var ribbon = $("
", { + class: "bodymusic_ribbon" + }).appendTo("body"); + $.each(plotProps, function() { + $.each(this.series, function() { + var iconDiv = $("
", { + class: "bodymusic_legendicon" + }).appendTo(ribbon); + iconSvg = $(document.createElementNS("http://www.w3.org/2000/svg", "svg")) + .appendTo(iconDiv); + $(document.createElementNS("http://www.w3.org/2000/svg", "line")).attr({ + x1: 0, + x2: 20, + y1: 10, + y2: 10, + stroke: this.color, + "stroke-width": lineWidth*2 + }).css("display", "inline").appendTo(iconSvg); + $("", { + class: "bodymusic_legendlabel" + }).appendTo(ribbon).html(this.name); + }); + }); + }; + + var createPlots = function() { + var strokeIndex; + $.each(plotProps, function() { + var thisPlotProps = this; + var $plotElement; + var plot; + var $svg; + var veil; + var series = []; + var plotWidth; + + var createCanvas = function() { + $plotElement = $("
", { + class: "bodymusic_plot" + }).appendTo("body"); + plotWidth = $plotElement.width(); + $plotElement.height(plotHeight); + $svg = $(document.createElementNS("http://www.w3.org/2000/svg", "svg")) + .appendTo($plotElement); + }; + + var createVeil = function() { + veil = document.createElement("div"); + $(veil).attr({ + class: "bodymusic_veil" + }).appendTo($plotElement); + }; + + var createAxis = function() { + var axis = document.createElementNS("http://www.w3.org/2000/svg", "line"); + $(axis).attr({ + x1: 0, + x2: plotWidth, + y1: plotHeight/2, + y2: plotHeight/2, + stroke: "gray", + "stroke-dasharray": "5,3" + }).appendTo($svg); + axis.style.display = "inline"; + }; + + var createLabels = function() { + var zeroLabel = document.createElementNS("http://www.w3.org/2000/svg", "text"); + $(maxLabel).attr({ + x: 5, + y: plotHeight/2 - 5 + }).appendTo($svg); + var maxLabel = document.createElementNS("http://www.w3.org/2000/svg", "text"); + $(maxLabel).attr({ + x: 5, + y: 15 + }).appendTo($svg); + maxLabel.textContent = (thisPlotProps.maxValue * thisPlotProps.scale).toFixed(0) + + " " + thisPlotProps.unit; + var minLabel = document.createElementNS("http://www.w3.org/2000/svg", "text"); + $(minLabel).attr({ + x: 5, + y: plotHeight - 5 + }).appendTo($svg); + minLabel.textContent = (-thisPlotProps.maxValue * thisPlotProps.scale).toFixed(0) + + " " + thisPlotProps.unit; + var plotLabel = document.createElementNS("http://www.w3.org/2000/svg", "text"); + $(plotLabel).attr({ + x: 50, + y: 15 + }).appendTo($svg); + plotLabel.textContent = thisPlotProps.title; + }; + + var createPlot = function() { + var yBase = plotHeight/2; + plot = { + key: thisPlotProps.key, + id: thisPlotProps.sensorId, + yBase: yBase, + yScale: yBase/thisPlotProps.maxValue, + veil: veil, + series: series, + currentStrokeIndex: -1, + history: [] + }; + plot.strokeCount = Math.floor((plotWidth - 2*plotMargin)/plotStep); + plots.push(plot); + }; + + var createSlider = function() { + var slider = document.createElementNS("http://www.w3.org/2000/svg", "line"); + $(slider).attr({ + x1: 0, + x2: 0, + y1: 0, + y2: plotHeight, + stroke: "gray" + }).appendTo($svg); + var tooltip = document.createElement("div"); + $(tooltip).attr({ + class: "bodymusic_tooltip" + }).appendTo("body"); + slider.tooltip = tooltip; + plot.slider = slider; + $plotElement.mouseover(function() { + slider.style.display = "inline"; + $plotElement.css("cursor", "none"); + }); + $plotElement.mouseout(function() { + slider.style.display = "none"; + slider.tooltip.style.display = "none"; + }); + $plotElement.mousemove(function(event) { + var eventX = event.clientX - bodyRect.left; + var strokeIndex = Math.floor((eventX - plotMargin) / plotStep); + var history = plot.history; + if (strokeIndex > -1 && strokeIndex < history.length) { + $(slider).attr({ + x1: eventX, + x2: eventX + }); + var tooltipText = ""; + var data = history[strokeIndex]; + var plotKey = plot.key; + tooltipText = "Time: " + data.time.toFixed(2) + " sec"; + $.each(plot.series, function() { + var value = data[plotKey][this.key] * thisPlotProps.scale; + tooltipText += "
" + this.name + ": " + + value.toFixed(5) + " " + thisPlotProps.unit; + }); + tooltip.style.display = "inline-block"; + tooltip.style.left = eventX - 30 + "px"; + tooltip.style.top = event.clientY + 10 + "px"; + } + else { + tooltip.style.display = "none"; + } + tooltip.innerHTML = tooltipText; + }); + }; + + + var createSeries = function() { + $.each(thisPlotProps.series, function() { + var color = this.color; + var strokes = []; + var marker = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + var thisSeries = { + key: this.key, + name: this.name, + strokes: strokes, + marker: marker + }; + series.push(thisSeries); + for (strokeIndex = 0; strokeIndex < plot.strokeCount; strokeIndex++) { + var stroke = document.createElementNS("http://www.w3.org/2000/svg", "line"); + $(stroke).attr({ + x1: plotMargin + strokeIndex*plotStep, + x2: plotMargin + (strokeIndex + 1)*plotStep, + y1: plotHeight/2, + y2: plotHeight/2, + stroke: color, + "stroke-width": lineWidth, + "stroke-linecap": "round" + }).appendTo($svg); + strokes.push(stroke); + } + $(marker).attr({ + cx: 0, + cy: 0, + r: markerRadius, + stroke: color, + fill: color + }).appendTo($svg); + }); + }; + + createCanvas(); + createVeil(); + createAxis(); + createLabels(); + createPlot(); + createSlider(); + createSeries(); + }); + }; + + var updatePlots = function(data) { + $.each(plots, function() { + var plot = this; + if (plot.sensorId = data.id) { + if (plot.currentStrokeIndex < plot.strokeCount - 1) { + plot.currentStrokeIndex++; + } + else { + plot.currentStrokeIndex = 0; + } + var x = plotMargin + (plot.currentStrokeIndex + 1)*plotStep; + var thisPlotProps = plotProps[plot.key]; + var ys = data[plot.key]; + if (ys) { + $.each(plot.series, function() { + var series = this; + var y = ys[series.key]; + var strokes = series.strokes; + var marker = series.marker; + var yScaled = plot.yBase - y*plot.yScale; + var stroke = strokes[plot.currentStrokeIndex]; + stroke.setAttribute("y2", yScaled); + marker.setAttribute("cx", x); + marker.setAttribute("cy", yScaled); + if (plot.currentStrokeIndex > 0) { + var prevY = strokes[plot.currentStrokeIndex - 1].getAttribute("y2"); + stroke.setAttribute("y1", prevY); + stroke.style.display = "inline"; + marker.style.display = "inline"; + } + }); + } + var history = plot.history; + if (plot.currentStrokeIndex < history.length) { + history[plot.currentStrokeIndex] = data; + } + else { + history.push(data); + } + plot.veil.style.left = x + "px"; + } + }); + }; + + var update = function(data) { + if (clock.tick++ % plotUpdateInterval == 0) { + updatePlots(data); + } + }; + + var init = function() { + createRibbon(); + createPlots(); + }; + + return { + init: init, + update: update + }; +})(); + +var bmconsole = (function() { + + var updateRateInput; + var logFileInput; + var websocketPort = 8081; + var websocket; + + var startWebSocket = function() { + if (!websocket || websocket.readyState != WebSocket.OPEN) { + websocket = new WebSocket("ws://localhost:" + websocketPort); + websocket.onmessage = function(event) { + var data = JSON.parse(event.data); + bmprocessor.update(data); + bmplotter.update(data); + }; + } + }; + + var stop = function() { + var request = new XMLHttpRequest(); + request.open("GET", "/?command=stop", true); + request.send(); + bmplayer.stop(); + bmprocessor.stop(); + }; + + var start = function() { + bmplayer.start(); + startWebSocket(); + var updateRateString = updateRateInput.val(); + var updateRate = parseInt(updateRateString); + var query = "/?command=start"; + if (updateRate) { + query += "&updateRate=" + updateRate; + } + var request = new XMLHttpRequest(); + request.open("GET", query, true); + request.send(); + }; + + var playback = function() { + startWebSocket(); + var logFile = logFileInput.val(); + if (logFile) { + var query = "/?command=playback"; + query += "&logFile=" + logFile; + } + var request = new XMLHttpRequest(); + request.open("GET", query, true); + request.send(); + }; + + var getLogFiles = function() { + var request = new XMLHttpRequest(); + request.onload = function() { + $.each(request.responseText.split(","), function() { + logFileInput.append($("").text(this)); + }); + }; + request.open("GET", "/?command=getLogFiles", true); + request.send(); + }; + + var init = function() { + var stopButton = $(" ").attr({ + type: "button", + class: "bodymusic_button", + value: "Stop" + }).appendTo($("body")); + stopButton.click(function() { + stop(); + }); + $("Update rate (Hz):").appendTo($("body")); + updateRateInput = $("").attr({ + type: "text", + class: "bodymusic_updaterate", + value: defaultUpdateRate, + list: "updateRates" + }).appendTo($("body")); + var dataList = $("").attr({ + id:"updateRates" + }).appendTo($("body")); + $.each([1, 2, 4, 10, 20, 50, 100], function() { + $('