Projekt Leaper: Porovnání verzí
Řádek 9: | Řádek 9: | ||
V první fázi byl zvuk generován přímo počítačem (bylo potřeba nastavit temperované ladění a rozmezí oktáv), v závěrečné fázi se ale podařilo systém propojit s knihovnou zvuků GarageBand pomocí protokolu MIDI – v současné době je k dispozici využití veškerých nástrojů GarageBand. | V první fázi byl zvuk generován přímo počítačem (bylo potřeba nastavit temperované ladění a rozmezí oktáv), v závěrečné fázi se ale podařilo systém propojit s knihovnou zvuků GarageBand pomocí protokolu MIDI – v současné době je k dispozici využití veškerých nástrojů GarageBand. | ||
− | == | + | == Vlastní zdrojový kód == |
+ | '''Upozornění! Vzhledem k omezení syntaxe wikisofia kopírujte prosím zdrojový kód přímo z editace zdroje stránky, nikoliv z podoby, do jaké je intepretována při zobrazení na stránce. Přislušný úsek je v souladu se syntaxí české wikipedie umístěn mezi značkou <nowiki><tt> a </tt></nowiki>.''' | ||
+ | |||
+ | <tt> | ||
+ | var lifx = require('./lifx'); | ||
+ | var util = require('util'); | ||
+ | var packet = require('./packet'); | ||
+ | var leap = require('leapjs'); | ||
+ | var midi = require('midi'); | ||
+ | |||
+ | |||
+ | var lx = lifx.init(); | ||
+ | |||
+ | lx.on('bulbstate', function(b) { | ||
+ | //console.log('Bulb state: ' + util.inspect(b)); | ||
+ | }); | ||
+ | |||
+ | lx.on('bulbonoff', function(b) { | ||
+ | //console.log('Bulb on/off: ' + util.inspect(b)); | ||
+ | }); | ||
+ | |||
+ | Number.prototype.map = function ( in_min , in_max , out_min , out_max ) { | ||
+ | return ( this - in_min ) * ( out_max - out_min ) / ( in_max - in_min ) + out_min; | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | // Set up a new output. | ||
+ | var output = new midi.output(); | ||
+ | |||
+ | // Count the available output ports. | ||
+ | var count = output.getPortCount(); | ||
+ | |||
+ | console.log("Available ports: " + count) | ||
+ | |||
+ | // Get the name of a specified output port. | ||
+ | output.getPortName(0); | ||
+ | |||
+ | // Open the first available output port. | ||
+ | output.openPort(0); | ||
+ | |||
+ | // Send a MIDI message. | ||
+ | output.sendMessage([176,22,1]); | ||
+ | |||
+ | |||
+ | var damperPedal = true; | ||
+ | |||
+ | var toneMap = { | ||
+ | 'c' : [ 60,72,84], | ||
+ | //'cis' : [277.18,554.36], | ||
+ | 'd' : [ 62,74], | ||
+ | //'dis' : [ 311.13,622.26], | ||
+ | 'e' : [ 64,76], | ||
+ | 'f' : [ 65,77], | ||
+ | //'fis' : [ 369.00,739.98], | ||
+ | 'g' : [67,79], | ||
+ | //'gis' : [ 415.00,830.60], | ||
+ | 'a' : [69,81], | ||
+ | //'ais' : [233.08,466.17], | ||
+ | 'h' : [71,83] | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | var dateDiff = function(start,end){ | ||
+ | return (end.getTime() - start.getTime())/1000; | ||
+ | } | ||
+ | |||
+ | |||
+ | var findTone = function(freq){ | ||
+ | var diff = 9999999999; | ||
+ | var tone = null; | ||
+ | for(var toneName in toneMap){ | ||
+ | //console.log("looking in " + toneName) | ||
+ | for(var i = 0; i < toneMap[toneName].length; i++){ | ||
+ | var newDiff = Math.abs(freq- toneMap[toneName][i]); | ||
+ | //console.log("diff " + newDiff) | ||
+ | if(newDiff < diff){ | ||
+ | diff = newDiff; | ||
+ | tone = {name : toneName, freq : toneMap[toneName][i]} | ||
+ | //console.log("Found " + toneName + i) | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | if(tone != null){ | ||
+ | //console.log("Found " + tone.name + " " + tone.freq + "hz") | ||
+ | }else{ | ||
+ | //console.log("No tone for " + freq) | ||
+ | } | ||
+ | |||
+ | return tone; | ||
+ | |||
+ | } | ||
+ | |||
+ | var midiMessages = { | ||
+ | "noteOn" : parseInt('10010000', 2), | ||
+ | "noteOff" : parseInt('10000000',2), | ||
+ | "mode" : parseInt('10110000',2), | ||
+ | "damper" : parseInt('01000000',2), | ||
+ | "pedalOff" : 127, | ||
+ | "pedalOn" : 0 | ||
+ | } | ||
+ | |||
+ | var findToneByMidi = function(step){ | ||
+ | var diff = 9999999999; | ||
+ | var tone = null; | ||
+ | var stepIndex = 0; | ||
+ | var i = 0; | ||
+ | for(i = 0; i< 3; i++){ | ||
+ | |||
+ | for(var toneName in toneMap){ | ||
+ | //console.log("looking in " + toneName) | ||
+ | |||
+ | if(toneMap[toneName].length > i){ | ||
+ | |||
+ | var newDiff = Math.abs(step-stepIndex); | ||
+ | //console.log("aaa" + newDiff) | ||
+ | //console.log("diff " + newDiff) | ||
+ | if(newDiff < diff){ | ||
+ | diff = newDiff; | ||
+ | tone = {name : toneName, freq : toneMap[toneName][i]} | ||
+ | //console.log("Found " + toneName + i) | ||
+ | } | ||
+ | stepIndex++; | ||
+ | } | ||
+ | |||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | if(tone != null){ | ||
+ | //console.log("Found " + tone.name + " " + tone.freq + "hz") | ||
+ | }else{ | ||
+ | //console.log("No tone for " + freq) | ||
+ | } | ||
+ | |||
+ | return tone; | ||
+ | } | ||
+ | |||
+ | var InterpolatedVal = function(){ | ||
+ | this.data = []; | ||
+ | this.windowSize = 5; | ||
+ | this.add = function(val){ | ||
+ | this.data.unshift(val); | ||
+ | //console.log("Pusging value " + val + " win size " + this.windowSize) | ||
+ | if(this.data.length > this.windowSize){ | ||
+ | this.data.pop(val) | ||
+ | } | ||
+ | //console.log("CData lenght " + this.data.length) | ||
+ | } | ||
+ | this.getValue = function(){ | ||
+ | var result = 0.0; | ||
+ | for(var i = 0; i < this.data.length; i++){ | ||
+ | result += this.data[i]; | ||
+ | } | ||
+ | //console.log("value " + result / this.data.length) | ||
+ | return result / this.data.length | ||
+ | } | ||
+ | |||
+ | }; | ||
+ | |||
+ | var Hand = function(){ | ||
+ | this.grip = new InterpolatedVal(); | ||
+ | this.grip.windowSize = 30; | ||
+ | this.rotation = new InterpolatedVal(); | ||
+ | this.rotation.windowSize = 10; | ||
+ | this.bulb = null; | ||
+ | |||
+ | this.note = new InterpolatedVal(); | ||
+ | }; | ||
+ | |||
+ | var leftHand = new Hand(); | ||
+ | var rightHand = new Hand(); | ||
+ | |||
+ | |||
+ | var baudio = require('baudio'); | ||
+ | var leftTone = 440; | ||
+ | var rightTone = 440; | ||
+ | var curLeftTone = leftTone; | ||
+ | var curRightTone = rightTone; | ||
+ | var note = null; | ||
+ | |||
+ | var n = 0; | ||
+ | // var b = baudio({rate: 704000},function (t) { | ||
+ | // //console.log(t) | ||
+ | // //console.log(leftTone) | ||
+ | // var x = Math.sin(2* (rightTone+0.01) * Math.PI * t) ;//+ Math.sin(n)); | ||
+ | // var next_x = Math.sin(2* (curLeftTone+0.02) * Math.PI * t); | ||
+ | // var a = Math.sin(2* (leftTone+0.01) * Math.PI * t) ; | ||
+ | // // if(Math.abs(x) < 0.001 && x < next_x) { | ||
+ | // // curLeftTone = leftTone | ||
+ | // // x = 0; | ||
+ | // // } | ||
+ | // // if(Math.abs(a) < 0.001) { | ||
+ | // // curRightTone = rightTone | ||
+ | // // a = 0; | ||
+ | // // } | ||
+ | // //n += Math.sin(t); | ||
+ | // return (x * rightHand.grip.getValue()) + (a * leftHand.grip.getValue()); | ||
+ | // }); | ||
+ | // b.play(); | ||
+ | |||
+ | // var baudio = require('baudio'), tune = require('tune'); | ||
+ | |||
+ | // var blackholesun = tune(String( | ||
+ | // 'A4 E4 A5 D5 A5 E4 A4 C4 E4 A5 D5 . . . ' + | ||
+ | // 'G3 D4 G4 D5 G4 D4 E3 F#3 C#4 F#4 C#5 . . . ' + | ||
+ | // 'E3 F3 C4 F4 A#5 F4 C4 F3 E3 D4 E4 B5 . . .' | ||
+ | // ).split(' ')); | ||
+ | |||
+ | // var b = baudio(function(t) { | ||
+ | // // black hole sun + some fx | ||
+ | // return blackholesun(t) + Math.sin(2 * Math.PI * t); | ||
+ | // }); | ||
+ | // b.play(); | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | leftBulb = {}; | ||
+ | leftBulb.isOn = false; | ||
+ | rightBulb = {}; | ||
+ | rightBulb.isOn = false; | ||
+ | lx.on('bulb', function(b) { | ||
+ | console.log('New bulb found: ' + b.name + " : " + b.addr.toString("hex")); | ||
+ | addr = b.addr.toString("hex"); | ||
+ | if(addr == "d073d5001d70"){ | ||
+ | console.log("Assigning left bulb"); | ||
+ | leftBulb.bulb = lx.bulbs[addr]; | ||
+ | leftHand.bulb = leftBulb | ||
+ | }else if(addr == "d073d50139d0"){ | ||
+ | console.log("Assigning right bulb"); | ||
+ | rightHand.bulb = rightBulb | ||
+ | rightBulb.bulb = lx.bulbs[addr]; | ||
+ | } | ||
+ | |||
+ | }); | ||
+ | |||
+ | lx.on('gateway', function(g) { | ||
+ | console.log('New gateway found: ' + g.ip); | ||
+ | }); | ||
+ | |||
+ | lx.on('packet', function(p) { | ||
+ | // Show informational packets | ||
+ | switch (p.packetTypeShortName) { | ||
+ | case 'powerState': | ||
+ | case 'wifiInfo': | ||
+ | case 'wifiFirmwareState': | ||
+ | case 'wifiState': | ||
+ | case 'accessPoint': | ||
+ | case 'bulbLabel': | ||
+ | case 'tags': | ||
+ | case 'tagLabels': | ||
+ | //case 'lightStatus': | ||
+ | case 'timeState': | ||
+ | case 'resetSwitchState': | ||
+ | case 'meshInfo': | ||
+ | case 'meshFirmware': | ||
+ | case 'versionState': | ||
+ | case 'infoState': | ||
+ | case 'mcuRailVoltage': | ||
+ | console.log(p.packetTypeName + " - " + p.preamble.bulbAddress.toString('hex') + " - " + util.inspect(p.payload)); | ||
+ | break; | ||
+ | default: | ||
+ | break; | ||
+ | } | ||
+ | }); | ||
+ | |||
+ | console.log("Keys:"); | ||
+ | console.log("Press 1 to turn the lights on"); | ||
+ | console.log("Press 2 to turn the lights off"); | ||
+ | console.log("Press 3 to turn the lights a dim red"); | ||
+ | console.log("Press 4 to turn the lights a dim purple"); | ||
+ | console.log("Press 5 to turn the lights a bright white"); | ||
+ | console.log("Press 6 to cycle forwards through colours"); | ||
+ | console.log("Press 7 to cycle backwards through colours"); | ||
+ | console.log("Press 8 to show debug messages including network traffic"); | ||
+ | console.log("Press 9 to hide debug messages including network traffic"); | ||
+ | console.log("Press letters a-m to request various status fields"); | ||
+ | |||
+ | var stdin = process.openStdin(); | ||
+ | process.stdin.setRawMode(true); | ||
+ | process.stdin.resume(); | ||
+ | |||
+ | var cycledColour = 0; | ||
+ | |||
+ | |||
+ | var exit = false; | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | stdin.on('data', function (key) { | ||
+ | //process.stdout.write('Got key ' + util.inspect(key) + '\n'); | ||
+ | switch (key[0]) { | ||
+ | case 0x20: | ||
+ | console.log("hit") | ||
+ | if(note != null){ | ||
+ | sendMidi(null,note.freq,127); | ||
+ | } | ||
+ | case 0x31: // 1 | ||
+ | console.log("Lights on"); | ||
+ | // lx.lightsOn('Bedroom Lamp'); // Can specify one bulb by name | ||
+ | var b = lx.bulbs['d073d5014163']; | ||
+ | lx.lightsOn(b); | ||
+ | // lx.lightsOn(); | ||
+ | break; | ||
+ | |||
+ | case 0x32: // 2 | ||
+ | console.log("Lights off"); | ||
+ | var b = lx.bulbs['d073d5014163']; | ||
+ | lx.lightsOff(b); | ||
+ | // lx.lightsOff(); | ||
+ | break; | ||
+ | |||
+ | case 0x33: // 3 | ||
+ | console.log("Dim red"); | ||
+ | // BB8 7D0 | ||
+ | lx.lightsColour(0x0000, 0xffff, 1000, 0, 0); | ||
+ | break; | ||
+ | |||
+ | case 0x34: // 4 | ||
+ | console.log("Dim purple"); | ||
+ | //lx.lightsColour(0x0000, 0xffff, 500, 0, 0); | ||
+ | lx.lightsColour(0xcc15, 0xffff, 500, 0, 0); | ||
+ | break; | ||
+ | |||
+ | case 0x35: // 5 | ||
+ | console.log("Bright white"); | ||
+ | lx.lightsColour(0x0000, 0x0000, 0x8000, 0x0af0, 0x0513); | ||
+ | break; | ||
+ | |||
+ | case 0x36: // 6 | ||
+ | cycledColour = (cycledColour+100) & 0xffff; console.log("Colour value is " + cycledColour); | ||
+ | lx.lightsColour(cycledColour, 0xffff, 0x0200, 0x0000, 0x0000); | ||
+ | break; | ||
+ | |||
+ | case 0x37: // 7 | ||
+ | cycledColour = (cycledColour-100) & 0xffff; console.log("Colour value is " + cycledColour); | ||
+ | lx.lightsColour(cycledColour, 0xffff, 0x0200, 0x0000, 0x0000); | ||
+ | break; | ||
+ | |||
+ | case 0x38: // 8 | ||
+ | console.log("Enabling debug"); | ||
+ | lifx.setDebug(true); | ||
+ | break; | ||
+ | |||
+ | case 0x39: // 9 | ||
+ | console.log("Disabling debug"); | ||
+ | lifx.setDebug(false); | ||
+ | break; | ||
+ | |||
+ | case 0x61: // a | ||
+ | console.log("Requesting voltage"); | ||
+ | var message = packet.getMcuRailVoltage(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x62: // b | ||
+ | console.log("Requesting power state"); | ||
+ | var message = packet.getPowerState(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x63: // c | ||
+ | console.log("hit") | ||
+ | if(note != null){ | ||
+ | sendMidi(midiMessages['noteOn'],note.freq,127); | ||
+ | } | ||
+ | break; | ||
+ | |||
+ | case 0x64: // d | ||
+ | console.log("Requesting wifi firmware state"); | ||
+ | var message = packet.getWifiFirmwareState(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x65: // e | ||
+ | console.log("Requesting wifi state"); | ||
+ | var message = packet.getWifiState({interface:2}); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x66: // f | ||
+ | console.log("Requesting bulb label"); | ||
+ | var message = packet.getBulbLabel(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x67: // g | ||
+ | console.log("Requesting tags"); | ||
+ | var message = packet.getTags(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x68: // h | ||
+ | console.log("Requesting tag label for tag 1"); | ||
+ | var message = packet.getTagLabels({tags:new Buffer([1,0,0,0,0,0,0,0])}); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x69: // i | ||
+ | console.log("Requesting time"); | ||
+ | var message = packet.getTime(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x6a: // j | ||
+ | console.log("Requesting info"); | ||
+ | var message = packet.getInfo(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x6b: // k | ||
+ | console.log("Requesting reset switch state"); | ||
+ | var message = packet.getResetSwitchState(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x6c: // l | ||
+ | console.log("Requesting mesh info"); | ||
+ | var message = packet.getMeshInfo(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x6d: // m | ||
+ | console.log("Requesting mesh firmware"); | ||
+ | var message = packet.getMeshFirmware(); | ||
+ | lx.sendToAll(message); | ||
+ | break; | ||
+ | |||
+ | case 0x03: // ctrl-c | ||
+ | console.log("Closing..."); | ||
+ | lx.close(); | ||
+ | // Close the port when done. | ||
+ | output.closePort(); | ||
+ | process.stdin.pause(); | ||
+ | exit = true; | ||
+ | process.exit(); | ||
+ | break; | ||
+ | |||
+ | } | ||
+ | }); | ||
+ | |||
+ | var lastMidiMessage = new Date(); | ||
+ | |||
+ | function sendMidi(c,b1,b2){ | ||
+ | var diff = dateDiff(lastMidiMessage,new Date()); | ||
+ | if(c == null){ | ||
+ | c = midiMessages['noteOn'] | ||
+ | } | ||
+ | console.log("Diff is " + diff); | ||
+ | if(diff > 0.10){ | ||
+ | lastMidiMessage = new Date(); | ||
+ | console.log("Sending c:"+c + " n:" + b1 + " v:" + b2) | ||
+ | output.sendMessage([c,b1,b2]); | ||
+ | if(c == midiMessages['noteOn']){ | ||
+ | setTimeout(function(){ | ||
+ | console.log("Sending OFF c:"+midiMessages['noteOff'] + " n:" + b1 + " v:" + b2) | ||
+ | output.sendMessage([midiMessages['noteOff'],b1,b2]); | ||
+ | }, 20); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | function vectorToString(vector, digits) { | ||
+ | if (typeof digits === "undefined") { | ||
+ | digits = 1; | ||
+ | } | ||
+ | return "(" + vector[0].toFixed(digits) + ", " | ||
+ | + vector[1].toFixed(digits) + ", " | ||
+ | + vector[2].toFixed(digits) + ")"; | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | var lightsOn = false; | ||
+ | var lastNote = null; | ||
+ | var previousFrame = null; | ||
+ | gripInterpol = new InterpolatedVal | ||
+ | toneInterpol = new InterpolatedVal | ||
+ | |||
+ | |||
+ | leap.loop(function(frame){ | ||
+ | |||
+ | if (frame.hands.length > 0) { | ||
+ | for (var i = 0; i < frame.hands.length; i++) { | ||
+ | var hand = frame.hands[i]; | ||
+ | var handString = ""; | ||
+ | handString += "<div style='width:300px; float:left; padding:5px'>"; | ||
+ | handString += "Hand ID: " + hand.id + "<br />"; | ||
+ | handString += "Type: " + hand.type + " hand" + "<br />"; | ||
+ | handString += "Direction: " + vectorToString(hand.direction, 2) + "<br />"; | ||
+ | handString += "Palm position: " + vectorToString(hand.palmPosition) + " mm<br />"; | ||
+ | handString += "Grab strength: " + hand.grabStrength + "<br />"; | ||
+ | handString += "Pinch strength: " + hand.pinchStrength + "<br />"; | ||
+ | handString += "Confidence: " + hand.confidence + "<br />"; | ||
+ | handString += "Arm direction: " + vectorToString(hand.arm.direction()) + "<br />"; | ||
+ | handString += "Arm center: " + vectorToString(hand.arm.center()) + "<br />"; | ||
+ | handString += "Arm up vector: " + vectorToString(hand.arm.basis[1]) + "<br />"; | ||
+ | //console.log(tone) | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | //console.log(handString); | ||
+ | var handModel = null; | ||
+ | if(hand.type == "right"){ | ||
+ | handModel = rightHand; | ||
+ | }else{ | ||
+ | handModel = leftHand; | ||
+ | console.log(handModel.grip.getValue()); | ||
+ | } | ||
+ | |||
+ | handModel.grip.add(hand.grabStrength); | ||
+ | handModel.note.add(hand.palmPosition[1]) | ||
+ | |||
+ | var value = handModel.note.getValue(); | ||
+ | value = Math.max(value,60); | ||
+ | value = Math.min(value,500); | ||
+ | value = value.map(60,500,0,14) | ||
+ | note = findToneByMidi(value); | ||
+ | |||
+ | if(handModel.grip.getValue() > 0.8 && damperPedal == true){ | ||
+ | console.log("gripping " + handModel.grip.getValue()) | ||
+ | |||
+ | //sendMidi(midiMessages["mode"],parseInt('00000001',2),80) | ||
+ | sendMidi(midiMessages["mode"],midiMessages["damper"],midiMessages["pedalOff"]) | ||
+ | damperPedal = false | ||
+ | } | ||
+ | else if(damperPedal == false && handModel.grip.getValue() < 0.8){ | ||
+ | damperPedal = true | ||
+ | console.log("letting go") | ||
+ | sendMidi(midiMessages["mode"],midiMessages["damper"],midiMessages["pedalOn"]) | ||
+ | //sendMidi(midiMessages["mode"],parseInt('00000001',2),0) | ||
+ | } | ||
+ | |||
+ | if(lastNote != note.freq){ | ||
+ | lastNote = note.freq; | ||
+ | console.log(note.freq) | ||
+ | } | ||
+ | |||
+ | if(note != null && hand.type == "right"){ | ||
+ | // Send a MIDI message. | ||
+ | //sendMidi(midiMessages['noteOn'],note.freq,127); | ||
+ | |||
+ | //rightTone = note.freq; | ||
+ | //console.log("assigning right tone " + rightTone) | ||
+ | }else if(note != null){ | ||
+ | //leftTone = note.freq; | ||
+ | //sendMidi(midiMessages['noteOn'],note.freq,127); | ||
+ | |||
+ | //console.log("assigning left tone " + leftTone) | ||
+ | } | ||
+ | |||
+ | if (previousFrame && previousFrame.valid) { | ||
+ | var translation = hand.translation(previousFrame); | ||
+ | //handString += "Translation: " + vectorToString(translation) + " mm<br />"; | ||
+ | |||
+ | var rotationAxis = hand.rotationAxis(previousFrame, 2); | ||
+ | var rotationAngle = hand.rotationAngle(previousFrame); | ||
+ | handModel.rotation.add(rotationAxis[0]); | ||
+ | //console.log('rotation ' + handModel.rotation.getValue()) | ||
+ | //handString += "Rotation axis: " + vectorToString(rotationAxis) + "<br />"; | ||
+ | //console.log(handString) | ||
+ | } | ||
+ | |||
+ | // Store frame for motion functions | ||
+ | previousFrame = frame; | ||
+ | |||
+ | |||
+ | |||
+ | // if(handModel.grip.getValue() >= 1 && handModel.bulb.isOn == false){ | ||
+ | // lx.lightsOn(handModel.bulb.bulb); | ||
+ | // handModel.bulb.isOn = true; | ||
+ | // }else if(handModel.grip.getValue() <= 0.2 && handModel.bulb.isOn == true){ | ||
+ | // lx.lightsOff(handModel.bulb.bulb); | ||
+ | // handModel.bulb.isOn = false; | ||
+ | // } | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | }); | ||
+ | |||
+ | </tt> | ||
+ | |||
== Popis vývoje a konečné verze vlastního produktu == | == Popis vývoje a konečné verze vlastního produktu == | ||
== Fotodokumentace == | == Fotodokumentace == |
Verze z 23. 12. 2015, 13:23
Obsah
Členové projektu
Vratislav Kalenda, Studia nových médií, e-mail: v.kalenda@gmail.com
Marta Kolárová, Studia nových médií, e-mail: martavkolarova@gmail.com
Motivace vzniku projektu a jeho význam
Původním záměrem bylo pomocí leap motion naprogramovat světelné diody - ovládané pohyby. Následně jsme se rozhodli využít leap motion i pro vytváření a modulování zvuku, tj. vytvořit pohyby ovládaný virtuální hudební nástroj v ideálním případě napojený na knihovnu samplovaných zvuků. A ještě kombinovaný s barevnými světelnými diodami, které informují o hraných tónech. Cílem byla zároveň možnost spoluhry více hráčů a vytváření harmonií.
V první fázi byl zvuk generován přímo počítačem (bylo potřeba nastavit temperované ladění a rozmezí oktáv), v závěrečné fázi se ale podařilo systém propojit s knihovnou zvuků GarageBand pomocí protokolu MIDI – v současné době je k dispozici využití veškerých nástrojů GarageBand.
Vlastní zdrojový kód
Upozornění! Vzhledem k omezení syntaxe wikisofia kopírujte prosím zdrojový kód přímo z editace zdroje stránky, nikoliv z podoby, do jaké je intepretována při zobrazení na stránce. Přislušný úsek je v souladu se syntaxí české wikipedie umístěn mezi značkou <tt> a </tt>.
var lifx = require('./lifx'); var util = require('util'); var packet = require('./packet'); var leap = require('leapjs'); var midi = require('midi');
var lx = lifx.init();
lx.on('bulbstate', function(b) { //console.log('Bulb state: ' + util.inspect(b)); });
lx.on('bulbonoff', function(b) { //console.log('Bulb on/off: ' + util.inspect(b)); });
Number.prototype.map = function ( in_min , in_max , out_min , out_max ) {
return ( this - in_min ) * ( out_max - out_min ) / ( in_max - in_min ) + out_min;
}
// Set up a new output.
var output = new midi.output();
// Count the available output ports. var count = output.getPortCount();
console.log("Available ports: " + count)
// Get the name of a specified output port. output.getPortName(0);
// Open the first available output port. output.openPort(0);
// Send a MIDI message. output.sendMessage([176,22,1]);
var damperPedal = true;
var toneMap = { 'c' : [ 60,72,84], //'cis' : [277.18,554.36], 'd' : [ 62,74], //'dis' : [ 311.13,622.26], 'e' : [ 64,76], 'f' : [ 65,77], //'fis' : [ 369.00,739.98], 'g' : [67,79], //'gis' : [ 415.00,830.60], 'a' : [69,81], //'ais' : [233.08,466.17], 'h' : [71,83] }
var dateDiff = function(start,end){
return (end.getTime() - start.getTime())/1000;
}
var findTone = function(freq){
var diff = 9999999999;
var tone = null;
for(var toneName in toneMap){
//console.log("looking in " + toneName)
for(var i = 0; i < toneMap[toneName].length; i++){
var newDiff = Math.abs(freq- toneMap[toneName][i]);
//console.log("diff " + newDiff)
if(newDiff < diff){
diff = newDiff;
tone = {name : toneName, freq : toneMap[toneName][i]}
//console.log("Found " + toneName + i)
}
}
}
if(tone != null){
//console.log("Found " + tone.name + " " + tone.freq + "hz")
}else{
//console.log("No tone for " + freq)
}
return tone;
}
var midiMessages = { "noteOn" : parseInt('10010000', 2), "noteOff" : parseInt('10000000',2), "mode" : parseInt('10110000',2), "damper" : parseInt('01000000',2), "pedalOff" : 127, "pedalOn" : 0 }
var findToneByMidi = function(step){ var diff = 9999999999; var tone = null; var stepIndex = 0; var i = 0; for(i = 0; i< 3; i++){
for(var toneName in toneMap){ //console.log("looking in " + toneName)
if(toneMap[toneName].length > i){
var newDiff = Math.abs(step-stepIndex); //console.log("aaa" + newDiff) //console.log("diff " + newDiff) if(newDiff < diff){ diff = newDiff; tone = {name : toneName, freq : toneMap[toneName][i]} //console.log("Found " + toneName + i) } stepIndex++; }
}
}
if(tone != null){ //console.log("Found " + tone.name + " " + tone.freq + "hz") }else{ //console.log("No tone for " + freq) }
return tone; }
var InterpolatedVal = function(){ this.data = []; this.windowSize = 5; this.add = function(val){ this.data.unshift(val); //console.log("Pusging value " + val + " win size " + this.windowSize) if(this.data.length > this.windowSize){ this.data.pop(val) } //console.log("CData lenght " + this.data.length) } this.getValue = function(){ var result = 0.0; for(var i = 0; i < this.data.length; i++){ result += this.data[i]; } //console.log("value " + result / this.data.length) return result / this.data.length }
};
var Hand = function(){ this.grip = new InterpolatedVal(); this.grip.windowSize = 30; this.rotation = new InterpolatedVal(); this.rotation.windowSize = 10; this.bulb = null;
this.note = new InterpolatedVal(); };
var leftHand = new Hand(); var rightHand = new Hand();
var baudio = require('baudio');
var leftTone = 440;
var rightTone = 440;
var curLeftTone = leftTone;
var curRightTone = rightTone;
var note = null;
var n = 0; // var b = baudio({rate: 704000},function (t) { // //console.log(t) // //console.log(leftTone) // var x = Math.sin(2* (rightTone+0.01) * Math.PI * t) ;//+ Math.sin(n)); // var next_x = Math.sin(2* (curLeftTone+0.02) * Math.PI * t); // var a = Math.sin(2* (leftTone+0.01) * Math.PI * t) ; // // if(Math.abs(x) < 0.001 && x < next_x) { // // curLeftTone = leftTone // // x = 0; // // } // // if(Math.abs(a) < 0.001) { // // curRightTone = rightTone // // a = 0; // // } // //n += Math.sin(t); // return (x * rightHand.grip.getValue()) + (a * leftHand.grip.getValue()); // }); // b.play();
// var baudio = require('baudio'), tune = require('tune');
// var blackholesun = tune(String( // 'A4 E4 A5 D5 A5 E4 A4 C4 E4 A5 D5 . . . ' + // 'G3 D4 G4 D5 G4 D4 E3 F#3 C#4 F#4 C#5 . . . ' + // 'E3 F3 C4 F4 A#5 F4 C4 F3 E3 D4 E4 B5 . . .' // ).split(' '));
// var b = baudio(function(t) { // // black hole sun + some fx // return blackholesun(t) + Math.sin(2 * Math.PI * t); // }); // b.play();
leftBulb = {};
leftBulb.isOn = false;
rightBulb = {};
rightBulb.isOn = false;
lx.on('bulb', function(b) {
console.log('New bulb found: ' + b.name + " : " + b.addr.toString("hex"));
addr = b.addr.toString("hex");
if(addr == "d073d5001d70"){
console.log("Assigning left bulb");
leftBulb.bulb = lx.bulbs[addr];
leftHand.bulb = leftBulb
}else if(addr == "d073d50139d0"){
console.log("Assigning right bulb");
rightHand.bulb = rightBulb
rightBulb.bulb = lx.bulbs[addr];
}
});
lx.on('gateway', function(g) { console.log('New gateway found: ' + g.ip); });
lx.on('packet', function(p) { // Show informational packets switch (p.packetTypeShortName) { case 'powerState': case 'wifiInfo': case 'wifiFirmwareState': case 'wifiState': case 'accessPoint': case 'bulbLabel': case 'tags': case 'tagLabels': //case 'lightStatus': case 'timeState': case 'resetSwitchState': case 'meshInfo': case 'meshFirmware': case 'versionState': case 'infoState': case 'mcuRailVoltage': console.log(p.packetTypeName + " - " + p.preamble.bulbAddress.toString('hex') + " - " + util.inspect(p.payload)); break; default: break; } });
console.log("Keys:"); console.log("Press 1 to turn the lights on"); console.log("Press 2 to turn the lights off"); console.log("Press 3 to turn the lights a dim red"); console.log("Press 4 to turn the lights a dim purple"); console.log("Press 5 to turn the lights a bright white"); console.log("Press 6 to cycle forwards through colours"); console.log("Press 7 to cycle backwards through colours"); console.log("Press 8 to show debug messages including network traffic"); console.log("Press 9 to hide debug messages including network traffic"); console.log("Press letters a-m to request various status fields");
var stdin = process.openStdin(); process.stdin.setRawMode(true); process.stdin.resume();
var cycledColour = 0;
var exit = false;
stdin.on('data', function (key) { //process.stdout.write('Got key ' + util.inspect(key) + '\n'); switch (key[0]) { case 0x20: console.log("hit") if(note != null){ sendMidi(null,note.freq,127); } case 0x31: // 1 console.log("Lights on"); // lx.lightsOn('Bedroom Lamp'); // Can specify one bulb by name var b = lx.bulbs['d073d5014163']; lx.lightsOn(b); // lx.lightsOn(); break;
case 0x32: // 2 console.log("Lights off"); var b = lx.bulbs['d073d5014163']; lx.lightsOff(b); // lx.lightsOff(); break;
case 0x33: // 3 console.log("Dim red"); // BB8 7D0 lx.lightsColour(0x0000, 0xffff, 1000, 0, 0); break;
case 0x34: // 4 console.log("Dim purple"); //lx.lightsColour(0x0000, 0xffff, 500, 0, 0); lx.lightsColour(0xcc15, 0xffff, 500, 0, 0); break;
case 0x35: // 5 console.log("Bright white"); lx.lightsColour(0x0000, 0x0000, 0x8000, 0x0af0, 0x0513); break;
case 0x36: // 6 cycledColour = (cycledColour+100) & 0xffff; console.log("Colour value is " + cycledColour); lx.lightsColour(cycledColour, 0xffff, 0x0200, 0x0000, 0x0000); break;
case 0x37: // 7 cycledColour = (cycledColour-100) & 0xffff; console.log("Colour value is " + cycledColour); lx.lightsColour(cycledColour, 0xffff, 0x0200, 0x0000, 0x0000); break;
case 0x38: // 8 console.log("Enabling debug"); lifx.setDebug(true); break;
case 0x39: // 9 console.log("Disabling debug"); lifx.setDebug(false); break;
case 0x61: // a console.log("Requesting voltage"); var message = packet.getMcuRailVoltage(); lx.sendToAll(message); break;
case 0x62: // b console.log("Requesting power state"); var message = packet.getPowerState(); lx.sendToAll(message); break;
case 0x63: // c console.log("hit") if(note != null){ sendMidi(midiMessages['noteOn'],note.freq,127); } break;
case 0x64: // d console.log("Requesting wifi firmware state"); var message = packet.getWifiFirmwareState(); lx.sendToAll(message); break;
case 0x65: // e console.log("Requesting wifi state"); var message = packet.getWifiState({interface:2}); lx.sendToAll(message); break;
case 0x66: // f console.log("Requesting bulb label"); var message = packet.getBulbLabel(); lx.sendToAll(message); break;
case 0x67: // g console.log("Requesting tags"); var message = packet.getTags(); lx.sendToAll(message); break;
case 0x68: // h console.log("Requesting tag label for tag 1"); var message = packet.getTagLabels({tags:new Buffer([1,0,0,0,0,0,0,0])}); lx.sendToAll(message); break;
case 0x69: // i console.log("Requesting time"); var message = packet.getTime(); lx.sendToAll(message); break;
case 0x6a: // j console.log("Requesting info"); var message = packet.getInfo(); lx.sendToAll(message); break;
case 0x6b: // k console.log("Requesting reset switch state"); var message = packet.getResetSwitchState(); lx.sendToAll(message); break;
case 0x6c: // l console.log("Requesting mesh info"); var message = packet.getMeshInfo(); lx.sendToAll(message); break;
case 0x6d: // m console.log("Requesting mesh firmware"); var message = packet.getMeshFirmware(); lx.sendToAll(message); break;
case 0x03: // ctrl-c console.log("Closing..."); lx.close(); // Close the port when done. output.closePort(); process.stdin.pause(); exit = true; process.exit(); break;
} });
var lastMidiMessage = new Date();
function sendMidi(c,b1,b2){ var diff = dateDiff(lastMidiMessage,new Date()); if(c == null){ c = midiMessages['noteOn'] } console.log("Diff is " + diff); if(diff > 0.10){ lastMidiMessage = new Date(); console.log("Sending c:"+c + " n:" + b1 + " v:" + b2) output.sendMessage([c,b1,b2]); if(c == midiMessages['noteOn']){ setTimeout(function(){ console.log("Sending OFF c:"+midiMessages['noteOff'] + " n:" + b1 + " v:" + b2) output.sendMessage([midiMessages['noteOff'],b1,b2]); }, 20); }
} }
function vectorToString(vector, digits) {
if (typeof digits === "undefined") { digits = 1; } return "(" + vector[0].toFixed(digits) + ", " + vector[1].toFixed(digits) + ", " + vector[2].toFixed(digits) + ")";
}
var lightsOn = false;
var lastNote = null;
var previousFrame = null;
gripInterpol = new InterpolatedVal
toneInterpol = new InterpolatedVal
leap.loop(function(frame){
if (frame.hands.length > 0) { for (var i = 0; i < frame.hands.length; i++) { var hand = frame.hands[i]; var handString = "";
handString += "
handString += "Hand ID: " + hand.id + "
"; handString += "Type: " + hand.type + " hand" + "
"; handString += "Direction: " + vectorToString(hand.direction, 2) + "
"; handString += "Palm position: " + vectorToString(hand.palmPosition) + " mm
"; handString += "Grab strength: " + hand.grabStrength + "
"; handString += "Pinch strength: " + hand.pinchStrength + "
"; handString += "Confidence: " + hand.confidence + "
"; handString += "Arm direction: " + vectorToString(hand.arm.direction()) + "
"; handString += "Arm center: " + vectorToString(hand.arm.center()) + "
"; handString += "Arm up vector: " + vectorToString(hand.arm.basis[1]) + "
"; //console.log(tone)
//console.log(handString); var handModel = null; if(hand.type == "right"){ handModel = rightHand; }else{ handModel = leftHand; console.log(handModel.grip.getValue()); }
handModel.grip.add(hand.grabStrength); handModel.note.add(hand.palmPosition[1]) var value = handModel.note.getValue(); value = Math.max(value,60); value = Math.min(value,500); value = value.map(60,500,0,14) note = findToneByMidi(value);
if(handModel.grip.getValue() > 0.8 && damperPedal == true){ console.log("gripping " + handModel.grip.getValue()) //sendMidi(midiMessages["mode"],parseInt('00000001',2),80) sendMidi(midiMessages["mode"],midiMessages["damper"],midiMessages["pedalOff"]) damperPedal = false } else if(damperPedal == false && handModel.grip.getValue() < 0.8){ damperPedal = true console.log("letting go") sendMidi(midiMessages["mode"],midiMessages["damper"],midiMessages["pedalOn"]) //sendMidi(midiMessages["mode"],parseInt('00000001',2),0) }
if(lastNote != note.freq){ lastNote = note.freq; console.log(note.freq) }
if(note != null && hand.type == "right"){ // Send a MIDI message. //sendMidi(midiMessages['noteOn'],note.freq,127);
//rightTone = note.freq; //console.log("assigning right tone " + rightTone) }else if(note != null){ //leftTone = note.freq; //sendMidi(midiMessages['noteOn'],note.freq,127);
//console.log("assigning left tone " + leftTone) }
if (previousFrame && previousFrame.valid) { var translation = hand.translation(previousFrame); //handString += "Translation: " + vectorToString(translation) + " mm
";
var rotationAxis = hand.rotationAxis(previousFrame, 2); var rotationAngle = hand.rotationAngle(previousFrame); handModel.rotation.add(rotationAxis[0]); //console.log('rotation ' + handModel.rotation.getValue()) //handString += "Rotation axis: " + vectorToString(rotationAxis) + "
"; //console.log(handString) }
// Store frame for motion functions previousFrame = frame;
// if(handModel.grip.getValue() >= 1 && handModel.bulb.isOn == false){ // lx.lightsOn(handModel.bulb.bulb); // handModel.bulb.isOn = true; // }else if(handModel.grip.getValue() <= 0.2 && handModel.bulb.isOn == true){ // lx.lightsOff(handModel.bulb.bulb); // handModel.bulb.isOn = false; // }
} }
});