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;
// }
} }
});