/* * Haptics.js - http://hapticsjs.org/ * Copyright (c) Shantanu Bala 2014 * Direct questions to shantanu@sbala.org * Haptics.js can be freely distributed under the MIT License. */ "use strict"; (function (global) { var Haptics = {}, enabled, currentRecording, navigatorVibrate, log, navigator; navigator = global.navigator; // a console.log wrapper for debugging log = function () { // store logs to an array for reference log.history = log.history || []; log.history.push(arguments); if (global.console) { global.console.log(Array.prototype.slice.call(arguments)); } }; // used for timeouts that 'accomplish nothing' in a pattern function emptyFunc() { log("Executed emptyFunc, which does nothing."); } // check for navigator variables from different vendors navigatorVibrate = navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate; enabled = !!navigatorVibrate; // calls to navigatorVibrate always bound to global navigator object function vibrate() { if (enabled) { // vibrate will not work unless bound to navigator global navigatorVibrate.apply(navigator, arguments); return true; } // log instead of actually vibrating device if disabled log(arguments); return false; } // execute two functions timed using the provided durations function executeSequence(durations, currentFunc, nextFunc) { var d = durations.shift(); nextFunc = nextFunc || currentFunc; currentFunc(d); if (durations.length === 0) { return true; // finished executing sequence } // handle remaining durations return global.setTimeout(function () { // swap order of next and currentFunc return executeSequence(durations, nextFunc, currentFunc); }, d); } // create a pattern function from a duration sequence function createSequenceFunc(durations) { var sum = 0, i = 0, len; for (i = 0, len = durations.length; i < len; i += 1) { sum += durations[i]; } return function (duration) { var d = duration / sum, newVibration = [], j, len2; for (j = 0, len2 = durations.length; j < len2; j += 1) { newVibration.push(durations[j] * d); } Haptics.vibrate(newVibration); }; } // create a single pattern function from a sequence of functions function concatenatePatternFuncs() { var funcs = arguments, len = arguments.length; return function (duration) { var i = 0, d = duration / len; function executeCurrentFunc() { funcs[i](d); } for (i = 0; i < len; i += 1) { global.setTimeout(executeCurrentFunc, d); } }; } // a way to quickly create/compose new tactile animations function patternFactory() { var len, j, newPattern, funcs = arguments; // each argument is a pattern being combined len = funcs.length; for (j = 0; j < len; j += 1) { if (typeof funcs[j] !== "function") { funcs[j] = createSequenceFunc(funcs[j]); } } newPattern = concatenatePatternFuncs(funcs); return function (args) { if (typeof args === "number") { newPattern(args); } else { executeSequence(args, newPattern, emptyFunc); } }; } // create a sequencing pattern function function createPattern(func) { if (arguments.length > 1) { func = patternFactory.apply(this, arguments); } else if (func && typeof func !== "function" && func.length) { func = createSequenceFunc(func); } else if (func && typeof func !== "function") { return null; } function newSequence(args) { if (typeof args === "number") { func(args); } else { executeSequence(args, func, emptyFunc); } } return newSequence; } // handle click/touch event function onRecord(e) { e.preventDefault(); currentRecording.push(new Date()); } // begin recording a sequence of taps/clicks function record() { currentRecording = []; global.addEventListener("touchstart", onRecord, false); global.addEventListener("touchend", onRecord, false); global.addEventListener("mousedown", onRecord, false); global.addEventListener("mouseup", onRecord, false); } // complete a recording of a sequence of taps/clicks function finish() { log(currentRecording); global.removeEventListener("touchstart", onRecord); global.removeEventListener("touchend", onRecord); global.removeEventListener("mousedown", onRecord); global.removeEventListener("mouseup", onRecord); if (currentRecording.length % 2 !== 0) { currentRecording.push(new Date()); } var vibrationPattern = [], i, j, len; for (i = 0, len = currentRecording.length; i < len; i += 2) { j = i + 1; if (j >= len) { break; } vibrationPattern.push(currentRecording[j] - currentRecording[i]); } return vibrationPattern; } // EFFECTS: Fade In function vibrateFadeIn(duration) { var pulses = [], d, i; if (duration < 100) { pulses = duration; } else { d = duration / 100; for (i = 1; i <= 10; i += 1) { pulses.push(i * d); if (i < 10) { pulses.push((10 - i) * d); } } } vibrate(pulses); } // EFFECTS: Fade Out function vibrateFadeOut(duration) { var pulses = [], d, i; if (duration < 100) { pulses = duration; } else { d = duration / 100; for (i = 1; i <= 10; i += 1) { pulses.push(i * d); if (i < 10) { pulses.push((10 - i) * d); } } pulses.reverse(); } vibrate(pulses); } // EFFECTS: notification function vibrateNotification(duration) { var pause, dot, dash; pause = duration / 27; dot = 2 * pause; dash = 3 * pause; vibrate([dot, pause, dot, pause, dot, pause * 2, dash, pause, dash, pause * 2, dot, pause, dot, pause, dot]); } // EFFECTS: heartbeat function vibrateHeartbeat(duration) { var pause, dot, dash; dot = duration / 60; pause = dot * 2; dash = dot * 24; vibrate([dot, pause, dash, pause * 2, dash, pause * 2, dot]); } // EFFECTS: clunk function vibrateClunk(duration) { var pause, dot, dash; dot = duration * 4 / 22; pause = dot * 2; dash = dot / 2 * 5; vibrate([dot, pause, dash]); } // EFFECTS: PWM function vibratePWM(duration, on, off) { var pattern = [on]; duration -= on; while (duration > 0) { duration -= off; duration -= on; pattern.push(off); pattern.push(on); } vibrate(pattern); } function pwm(args, on, off) { var newVibratePWM; if (typeof args === "number") { vibratePWM(args, on, off); } else { newVibratePWM = function (d) { vibratePWM(d, on, off); }; executeSequence(args, newVibratePWM, emptyFunc); } } // a way to quickly create new PWM intensity functions function createPatternPWM(on, off) { return function (args) { pwm(args, on, off); }; } // expose local functions to global API Haptics.enabled = enabled; Haptics.record = record; Haptics.finish = finish; Haptics.fadeIn = createPattern(vibrateFadeIn); Haptics.fadeOut = createPattern(vibrateFadeOut); Haptics.notification = createPattern(vibrateNotification); Haptics.heartbeat = createPattern(vibrateHeartbeat); Haptics.clunk = createPattern(vibrateClunk); Haptics.pwm = pwm; Haptics.createPatternPWM = createPatternPWM; Haptics.createPattern = createPattern; Haptics.vibrate = vibrate; // set global object global.Haptics = Haptics; }(this));