From bcbcfe3a8fcbd80a8bd96c2552de909a6ada0b18 Mon Sep 17 00:00:00 2001 From: Dan Calacci Date: Fri, 22 Jul 2016 14:17:45 -0400 Subject: [PATCH 01/10] alerts aren't super annoying anymore --- demo/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/demo/index.html b/demo/index.html index 0154b27..764f98e 100644 --- a/demo/index.html +++ b/demo/index.html @@ -23,7 +23,9 @@ } #info { - font-size: 22px; + font-size: 22px; + overflow: scroll; + max-height: 300px; } #wrapper { From 1aeff0b48adf6f1a23cd9fc7ada57bc8c01ca854 Mon Sep 17 00:00:00 2001 From: Dan Calacci Date: Fri, 22 Jul 2016 14:36:17 -0400 Subject: [PATCH 02/10] updated sibilant version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46c26f1..9df5425 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,6 @@ "microevent": "^1.0.0" }, "devDependencies": { - "alerts": "^0.1.3" + "alerts": "^0.1.4" } } From 2360024958e4a95b1500720add05e8e52faf1e76 Mon Sep 17 00:00:00 2001 From: Dan Calacci Date: Fri, 22 Jul 2016 14:36:55 -0400 Subject: [PATCH 03/10] OOPS --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9df5425..953e672 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sibilant-webaudio", - "version": "0.1.1", + "version": "0.1.2", "description": "detect speaking events from webRTC audio", "main": "sibilant.js", "scripts": { @@ -18,6 +18,6 @@ "microevent": "^1.0.0" }, "devDependencies": { - "alerts": "^0.1.4" + "alerts": "^0.1.3" } } From 6d01fc9746b50c69acabddc49c3da649f3703858 Mon Sep 17 00:00:00 2001 From: Dan Calacci Date: Mon, 6 Mar 2017 17:09:05 -0500 Subject: [PATCH 04/10] also sends out volume data --- package.json | 2 +- sibilant.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 953e672..b5a1618 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sibilant-webaudio", - "version": "0.1.2", + "version": "0.1.3", "description": "detect speaking events from webRTC audio", "main": "sibilant.js", "scripts": { diff --git a/sibilant.js b/sibilant.js index 8303f46..a7ba70d 100644 --- a/sibilant.js +++ b/sibilant.js @@ -24,6 +24,7 @@ function speakingDetectionNode (audioContext, analyser, threshold, emitter) { var speakingTimes = [] var quietHistory = [] // only contains continuous 'quiet' times var currentVolume = -Infinity + var volumes = [] var hasStoppedSpeaking = function () { return (_.max(quietHistory) - _.min(quietHistory) > 500) @@ -34,6 +35,7 @@ function speakingDetectionNode (audioContext, analyser, threshold, emitter) { analyser.getFloatFrequencyData(fftBins) var maxVolume = _.max(_.filter(fftBins, function (v) { return v < 0 })) currentVolume = maxVolume + volumes.push(currentVolume) emitter.trigger('volumeChange', currentVolume) // speaking, add the date to the stack, clear quiet record if (currentVolume > threshold) { @@ -42,7 +44,8 @@ function speakingDetectionNode (audioContext, analyser, threshold, emitter) { quietHistory = [] } else if (speakingTimes.length > 0) { if (hasStoppedSpeaking()) { - emitter.trigger('stoppedSpeaking', {'start': _.min(speakingTimes), 'end': _.max(speakingTimes)}) + emitter.trigger('stoppedSpeaking', {'start': _.min(speakingTimes), 'end': _.max(speakingTimes), 'volumes': volumes}) + volumes = [] speakingTimes = [] } else { quietHistory.push(new Date()) From dae97082078d73eb0fb984966491b386a5ff2400 Mon Sep 17 00:00:00 2001 From: Dan Calacci Date: Mon, 6 Mar 2017 19:52:18 -0500 Subject: [PATCH 05/10] volumes in correct format... --- sibilant.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sibilant.js b/sibilant.js index a7ba70d..5c517e6 100644 --- a/sibilant.js +++ b/sibilant.js @@ -35,7 +35,8 @@ function speakingDetectionNode (audioContext, analyser, threshold, emitter) { analyser.getFloatFrequencyData(fftBins) var maxVolume = _.max(_.filter(fftBins, function (v) { return v < 0 })) currentVolume = maxVolume - volumes.push(currentVolume) + volumes.push({timestamp: Date.now(), + vol: currentVolume}) emitter.trigger('volumeChange', currentVolume) // speaking, add the date to the stack, clear quiet record if (currentVolume > threshold) { From 9ac805df03d5a8e164de1b87c958e504ab4a43af Mon Sep 17 00:00:00 2001 From: Dan Calacci Date: Mon, 6 Mar 2017 19:52:46 -0500 Subject: [PATCH 06/10] updated version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b5a1618..a4643c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sibilant-webaudio", - "version": "0.1.3", + "version": "0.1.4", "description": "detect speaking events from webRTC audio", "main": "sibilant.js", "scripts": { From 2bf481616b90260e171c8a2def7da23b5c2899c4 Mon Sep 17 00:00:00 2001 From: Dan Calacci Date: Thu, 13 Sep 2018 15:57:30 -0400 Subject: [PATCH 07/10] slightly better band pass --- demo/demo.bundle.js | 34196 ++++++++++++++++++++++-------------------- demo/demo.js | 83 +- demo/index.html | 3 +- package.json | 4 +- sibilant.js | 182 +- 5 files changed, 17895 insertions(+), 16573 deletions(-) diff --git a/demo/demo.bundle.js b/demo/demo.bundle.js index 45a2bb7..6bbb7cb 100644 --- a/demo/demo.bundle.js +++ b/demo/demo.bundle.js @@ -1,38 +1,63 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o') - document.querySelector('#vid video').style.border = '10px solid #555' - }) - } -}) +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i { + console.log('Playback resumed successfully'); + }); + }); + speakingEvents.bind('speaking', function () { + document.querySelector('#vid video').style.border = '10px solid #27ae60'; + console.log('speaking!'); + }); + + speakingEvents.bind('stoppedSpeaking', function (data) { + console.log('speaking event recorded!', data.start); + var start = new Date(data.start); + var end = new Date(data.end); + var duration = end - start; + $('#info').prepend('You spoke for ' + duration + ' ms!
'); + document.querySelector('#vid video').style.border = '10px solid #555'; + }); + }, + + // errorCallback + function(err) { + if(err === 'PERMISSION_DENIED') { + alert('Sibilant couldnt get your user media. give it access to audio and try again!', err); + console.log("error", err); + // Explain why you need permission and how to update the permission setting + } + } +); -},{"alerts":2,"attachmediastream":3,"getusermedia":4,"jquery":5,"sibilant-webaudio":9}],2:[function(require,module,exports){ +},{"../":18,"alerts":2,"attachmediastream":3,"getusermedia":4,"jquery":5}],2:[function(require,module,exports){ var initialized; var container = document.createElement('div'); @@ -164,7 +189,7 @@ module.exports = function (stream, el, options) { return element; }; -},{"webrtc-adapter":10}],4:[function(require,module,exports){ +},{"webrtc-adapter":7}],4:[function(require,module,exports){ // getUserMedia helper by @HenrikJoreteg used for navigator.getUserMedia shim var adapter = require('webrtc-adapter'); @@ -241,20 +266,19 @@ module.exports = function (constraints, cb) { }); }; -},{"webrtc-adapter":10}],5:[function(require,module,exports){ -/*eslint-disable no-unused-vars*/ +},{"webrtc-adapter":7}],5:[function(require,module,exports){ /*! - * jQuery JavaScript Library v3.1.0 + * jQuery JavaScript Library v3.3.1 * https://jquery.com/ * * Includes Sizzle.js * https://sizzlejs.com/ * - * Copyright jQuery Foundation and other contributors + * Copyright JS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2016-07-07T21:44Z + * Date: 2018-01-20T17:24Z */ ( function( global, factory ) { @@ -316,24 +340,65 @@ var ObjectFunctionString = fnToString.call( Object ); var support = {}; +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + + + var preservedScriptAttributes = { + type: true, + src: true, + noModule: true + }; - function DOMEval( code, doc ) { + function DOMEval( code, doc, node ) { doc = doc || document; - var script = doc.createElement( "script" ); + var i, + script = doc.createElement( "script" ); script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + if ( node[ i ] ) { + script[ i ] = node[ i ]; + } + } + } doc.head.appendChild( script ).parentNode.removeChild( script ); } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} /* global Symbol */ -// Defining this global in .eslintrc would create a danger of using the global +// Defining this global in .eslintrc.json would create a danger of using the global // unguarded in another place, it seems safer to define global only for this module var - version = "3.1.0", + version = "3.3.1", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -345,16 +410,7 @@ var // Support: Android <=4.0 only // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; jQuery.fn = jQuery.prototype = { @@ -373,13 +429,14 @@ jQuery.fn = jQuery.prototype = { // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { - return num != null ? - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } - // Return all the elements in a clean array - slice.call( this ); + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; }, // Take an array of elements and push it onto the stack @@ -453,7 +510,7 @@ jQuery.extend = jQuery.fn.extend = function() { } // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } @@ -480,11 +537,11 @@ jQuery.extend = jQuery.fn.extend = function() { // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = jQuery.isArray( copy ) ) ) ) { + ( copyIsArray = Array.isArray( copy ) ) ) ) { if ( copyIsArray ) { copyIsArray = false; - clone = src && jQuery.isArray( src ) ? src : []; + clone = src && Array.isArray( src ) ? src : []; } else { clone = src && jQuery.isPlainObject( src ) ? src : {}; @@ -519,30 +576,6 @@ jQuery.extend( { noop: function() {}, - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isArray: Array.isArray, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); - }, - isPlainObject: function( obj ) { var proto, Ctor; @@ -576,33 +609,11 @@ jQuery.extend( { return true; }, - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - // Evaluates a script in a global context globalEval: function( code ) { DOMEval( code ); }, - // Convert dashed to camelCase; used by the css and data modules - // Support: IE <=9 - 11, Edge 12 - 13 - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - each: function( obj, callback ) { var length, i = 0; @@ -723,37 +734,6 @@ jQuery.extend( { // A global GUID counter for objects guid: 1, - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - // jQuery.support is not used in Core but other projects attach their // properties to it so it needs to exist. support: support @@ -776,9 +756,9 @@ function isArrayLike( obj ) { // hasOwn isn't used here due to false negatives // regarding Nodelist length in IE var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); + type = toType( obj ); - if ( type === "function" || jQuery.isWindow( obj ) ) { + if ( isFunction( obj ) || isWindow( obj ) ) { return false; } @@ -787,14 +767,14 @@ function isArrayLike( obj ) { } var Sizzle = /*! - * Sizzle CSS Selector Engine v2.3.0 + * Sizzle CSS Selector Engine v2.3.3 * https://sizzlejs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: 2016-01-04 + * Date: 2016-08-08 */ (function( window ) { @@ -940,7 +920,7 @@ var i, // CSS string/identifier serialization // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g, + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, fcssescape = function( ch, asCodePoint ) { if ( asCodePoint ) { @@ -967,7 +947,7 @@ var i, disabledAncestor = addCombinator( function( elem ) { - return elem.disabled === true; + return elem.disabled === true && ("form" in elem || "label" in elem); }, { dir: "parentNode", next: "legend" } ); @@ -1253,26 +1233,54 @@ function createButtonPseudo( type ) { * @param {Boolean} disabled true for :disabled; false for :enabled */ function createDisabledPseudo( disabled ) { - // Known :disabled false positives: - // IE: *[disabled]:not(button, input, select, textarea, optgroup, option, menuitem, fieldset) - // not IE: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable return function( elem ) { - // Check form elements and option elements for explicit disabling - return "label" in elem && elem.disabled === disabled || - "form" in elem && elem.disabled === disabled || + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } - // Check non-disabled form elements for fieldset[disabled] ancestors - "form" in elem && elem.disabled === false && ( - // Support: IE6-11+ - // Ancestry is covered for us - elem.isDisabled === disabled || + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || - // Otherwise, assume any non-