diff --git a/README.md b/README.md index a81244a..e53fb27 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,19 @@ Dancer Instance Methods // Using an audio object var a = new Audio(); a.src = 'somesong.mp3'; - dancer.load( a ); + dancer.load({ audio: a }); // Using an audio element on the page - dancer.load( document.getElementsByTagName('audio')[0] ); + dancer.load({ audio: document.getElementsByTagName('audio')[0] }); // Using a config object and you only have one encoding dancer.load({ src: 'somesong.mp3' }); // Using a config object, and you have an ogg and mp3 version dancer.load({ src: 'somesong', codecs: [ 'ogg', 'mp3' ]}); + + // Using microphone input (webkit only) + dancer.load({ microphone: true }); ``` ### Controls @@ -54,7 +57,7 @@ All controls return `this`. If provided an audio element as the source, one can * `getTime()` returns the current time. * `getProgress()` returns the downloading progress as a float from 0 to 1. * `getWaveform()` returns the waveform data array (Float32Array(1024)) -* `getSpectrum()` returns the frequency data array (Float32Array(512)). +* `getSpectrum()` returns the frequency data array (Float32Array(512)). * `getFrequency( freq [, endFreq ] )` returns the magnitude of a frequency or average over a range of frequencies. * `isLoaded()` returns a boolean value for the dancer instance's song load state. * `isPlaying()` returns a boolean value indicating whether the dancer instance's song is currently playing or not. @@ -176,7 +179,7 @@ Dependencies Extending/Plugins --- -You can extend the Dancer prototype by calling the static method `addPlugin( name, fn )`, which extends the Dancer prototype. A Dancer instance then can call the function provided in its context and subscribe to a preexisting event like `update`, or make your own. Look in the `plugins/` directory for examples. +You can extend the Dancer prototype by calling the static method `addPlugin( name, fn )`, which extends the Dancer prototype. A Dancer instance then can call the function provided in its context and subscribe to a preexisting event like `update`, or make your own. Look in the `plugins/` directory for examples. Development --- diff --git a/dancer.js b/dancer.js index 847fdba..19464db 100644 --- a/dancer.js +++ b/dancer.js @@ -20,24 +20,36 @@ var path; // Loading an Audio element - if ( source instanceof HTMLElement ) { - this.source = source; + if (source.audio) { + this.source = source.audio; if ( Dancer.isSupported() === 'flash' ) { - this.source = { src: Dancer._getMP3SrcFromAudio( source ) }; + this.source = { src: Dancer._getMP3SrcFromAudio( source.audio ) }; } + this.loadAudioAdapter(this.source); + // Loading an object with src, [codecs] - } else { - this.source = window.Audio ? new Audio() : {}; - this.source.src = Dancer._makeSupportedPath( source.src, source.codecs ); + } else if (source.src) { + this.source = window.Audio ? new Audio() : {}; + this.source.src = Dancer._makeSupportedPath( source.src, source.codecs ); + this.loadAudioAdapter(this.source); + + // Request user audio + } else if (source.microphone) { + navigator.getUserMedia({audio: true}, this._userMediaCallback, function(e) { + console.log("Unsupported.") + }); + this.source = source; } - this.audio = this.audioAdapter.load( this.source ); return this; }, - /* Controls */ + loadAudioAdapter: function(source) { + this.audio = this.audioAdapter.load( source ); + }, + /* Controls */ play : function () { this.audioAdapter.play(); return this; @@ -195,7 +207,11 @@ window.Dancer = Dancer; })(); + (function ( Dancer ) { + // Avoid browser-specific prefixes. + window.AudioContext = window.AudioContext || window.webkitAudioContext; + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia; var CODECS = { 'mp3' : 'audio/mpeg;', @@ -206,7 +222,6 @@ audioEl = document.createElement( 'audio' ); Dancer.options = {}; - Dancer.setOptions = function ( o ) { for ( var option in o ) { if ( o.hasOwnProperty( option ) ) { @@ -256,6 +271,7 @@ }; Dancer._getAdapter = function ( instance ) { + Dancer.instance = instance; switch ( Dancer.isSupported() ) { case 'webaudio': return new Dancer.adapters.webkit( instance ); @@ -277,6 +293,13 @@ return null; }; + Dancer.prototype._userMediaCallback = function(stream) { + var context = Dancer.instance.audioAdapter.context; + input = context.createMediaStreamSource(stream); + input.connect(context.destination); + Dancer.instance.loadAudioAdapter(input); + } + // Browser detection is lame, but Safari 6 has Web Audio API, // but does not support processing audio from a Media Element Source // https://gist.github.com/3265344 @@ -290,6 +313,7 @@ })( window.Dancer ); + (function ( undefined ) { var Kick = function ( dancer, o ) { o = o || {}; @@ -309,7 +333,7 @@ }; Kick.prototype = { - on : function () { + on : function () { this.isOn = true; return this; }, @@ -732,9 +756,9 @@ })(); -/* +/* * DSP.js - a comprehensive digital signal processing library for javascript - * + * * Created by Corban Brook on 2010-01-01. * Copyright 2010 Corban Brook. All rights reserved. * @@ -770,7 +794,7 @@ function FourierTransform(bufferSize, sampleRate) { imag = this.imag, bSi = 2 / this.bufferSize, sqrt = Math.sqrt, - rval, + rval, ival, mag; @@ -800,7 +824,7 @@ function FourierTransform(bufferSize, sampleRate) { */ function FFT(bufferSize, sampleRate) { FourierTransform.call(this, bufferSize, sampleRate); - + this.reverseTable = new Uint32Array(bufferSize); var limit = 1; @@ -870,7 +894,7 @@ FFT.prototype.forward = function(buffer) { //phaseShiftStepImag = Math.sin(-Math.PI/halfSize); phaseShiftStepReal = cosTable[halfSize]; phaseShiftStepImag = sinTable[halfSize]; - + currentPhaseShiftReal = 1; currentPhaseShiftImag = 0; @@ -941,7 +965,7 @@ var FlashDetect = new function(){ ]; /** * Extract the ActiveX version of the plugin. - * + * * @param {Object} The flash ActiveX object. * @type String */ @@ -954,7 +978,7 @@ var FlashDetect = new function(){ }; /** * Try and retrieve an ActiveX object having a specified name. - * + * * @param {String} name The ActiveX object name lookup. * @return One of ActiveX object or a simple object having an attribute of activeXError with a value of true. * @type Object @@ -970,8 +994,8 @@ var FlashDetect = new function(){ }; /** * Parse an ActiveX $version string into an object. - * - * @param {String} str The ActiveX Object GetVariable($version) return value. + * + * @param {String} str The ActiveX Object GetVariable($version) return value. * @return An object having raw, major, minor, revision and revisionStr attributes. * @type Object */ @@ -987,7 +1011,7 @@ var FlashDetect = new function(){ }; /** * Parse a standard enabledPlugin.description into an object. - * + * * @param {String} str The enabledPlugin.description value. * @return An object having raw, major, minor, revision and revisionStr attributes. * @type Object @@ -999,14 +1023,14 @@ var FlashDetect = new function(){ return { "raw":str, "major":parseInt(majorMinor[0], 10), - "minor":parseInt(majorMinor[1], 10), + "minor":parseInt(majorMinor[1], 10), "revisionStr":revisionStr, "revision":parseRevisionStrToInt(revisionStr) }; }; /** * Parse the plugin revision string into an integer. - * + * * @param {String} The revision in string format. * @type Number */ @@ -1015,7 +1039,7 @@ var FlashDetect = new function(){ }; /** * Is the major version greater than or equal to a specified version. - * + * * @param {Number} version The minimum required major version. * @type Boolean */ @@ -1024,7 +1048,7 @@ var FlashDetect = new function(){ }; /** * Is the minor version greater than or equal to a specified version. - * + * * @param {Number} version The minimum required minor version. * @type Boolean */ @@ -1033,7 +1057,7 @@ var FlashDetect = new function(){ }; /** * Is the revision version greater than or equal to a specified version. - * + * * @param {Number} version The minimum required revision version. * @type Boolean */ @@ -1042,7 +1066,7 @@ var FlashDetect = new function(){ }; /** * Is the version greater than or equal to a specified major, minor and revision. - * + * * @param {Number} major The minimum required major version. * @param {Number} (Optional) minor The minimum required minor version. * @param {Number} (Optional) revision The minimum required revision version. @@ -1075,7 +1099,7 @@ var FlashDetect = new function(){ var versionObj = parseStandardVersion(version); self.raw = versionObj.raw; self.major = versionObj.major; - self.minor = versionObj.minor; + self.minor = versionObj.minor; self.revisionStr = versionObj.revisionStr; self.revision = versionObj.revision; self.installed = true; @@ -1091,7 +1115,7 @@ var FlashDetect = new function(){ var versionObj = parseActiveXVersion(version); self.raw = versionObj.raw; self.major = versionObj.major; - self.minor = versionObj.minor; + self.minor = versionObj.minor; self.revision = versionObj.revision; self.revisionStr = versionObj.revisionStr; } @@ -1100,4 +1124,4 @@ var FlashDetect = new function(){ } }(); }; -FlashDetect.JS_RELEASE = "1.0.4"; \ No newline at end of file +FlashDetect.JS_RELEASE = "1.0.4"; diff --git a/examples/audio_element/js/player.js b/examples/audio_element/js/player.js index de9db96..10ac84a 100644 --- a/examples/audio_element/js/player.js +++ b/examples/audio_element/js/player.js @@ -25,7 +25,7 @@ }).on(); dancer - .load( audio ) + .load( {audio: audio} ) .waveform( waveform, { strokeStyle: '#666', strokeWidth: 2 }); Dancer.isSupported() || loaded(); diff --git a/examples/fft-microphone/css/League_Gothic-webfont.eot b/examples/fft-microphone/css/League_Gothic-webfont.eot new file mode 100755 index 0000000..08deeb7 Binary files /dev/null and b/examples/fft-microphone/css/League_Gothic-webfont.eot differ diff --git a/examples/fft-microphone/css/League_Gothic-webfont.svg b/examples/fft-microphone/css/League_Gothic-webfont.svg new file mode 100755 index 0000000..7523f75 --- /dev/null +++ b/examples/fft-microphone/css/League_Gothic-webfont.svg @@ -0,0 +1,235 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Generated in 2009 by FontLab Studio Copyright info pending + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/fft-microphone/css/League_Gothic-webfont.ttf b/examples/fft-microphone/css/League_Gothic-webfont.ttf new file mode 100755 index 0000000..efbe8b4 Binary files /dev/null and b/examples/fft-microphone/css/League_Gothic-webfont.ttf differ diff --git a/examples/fft-microphone/css/League_Gothic-webfont.woff b/examples/fft-microphone/css/League_Gothic-webfont.woff new file mode 100755 index 0000000..2e2cda9 Binary files /dev/null and b/examples/fft-microphone/css/League_Gothic-webfont.woff differ diff --git a/examples/fft-microphone/css/dancer.css b/examples/fft-microphone/css/dancer.css new file mode 100644 index 0000000..9219137 --- /dev/null +++ b/examples/fft-microphone/css/dancer.css @@ -0,0 +1,113 @@ +@font-face { + font-family: 'League Gothic'; + src: url('League_Gothic-webfont.eot'); + src: url('League_Gothic-webfont.eot?') format('embedded-opentype'), + url('League_Gothic-webfont.woff') format('woff'), + url('League_Gothic-webfont.ttf') format('opentype'); + font-weight: bold; + font-style: normal; +} +body { + background-color: #000; + margin: 0; + padding: 0; + font-family: Arial; +} +#info { + width: 100%; + position: absolute; + top: 0; + height: 52px; + background-color: #111; + color: #ccc; + font-family: 'League Gothic', arial, sans-serif; +} +#info h1 { + margin: 0 0 0 20px; + float: left; + font-size: 40px; + color: #ff0077; +} +#info h2 { + margin: 14px 0 0 5px; + float: left; + font-size: 25px; +} + +#info h3 { + margin: 19px 0 0 15px; + float: left; + font-size: 20px; +} + +#info ul { + float:right; + display-icon-type:none; +} + +#info li { + display:block; + float:left; + margin-right: 15px; +} + +#info li a { + color: #ff0077; + margin-left: 5px; +} + +#loading { + font-size: 60px; + color: #fff; + width: 500px; + margin-left: auto; + margin-right: auto; + margin-top: 100px; + background-color: #111; + padding: 10px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + opacity: 0.9; + border: 1px solid #ccc; + text-align: center; +} + +#loading a { + color: #fff; + text-shadow: 0 0 10px #fff; + text-decoration: none; +} + +#loading p{ + color: #fff; + font-size: 18px; +} + +#fft { + display: block; + margin: 0 auto; +} + +#stats { + margin: 0 0 0 10px; + float: left; +} + +#songinfo { + position: absolute; + bottom: 0; + left: 0; + background-color: #111; +} + +#songinfo span, #songinfo h3 { + color: #ccc; + font-size: 12px; + margin: 5px 30px 5px 30px; + padding: 0; +} + +#songinfo a { + color: #ff0077; +} diff --git a/examples/fft-microphone/images/particle_blue.png b/examples/fft-microphone/images/particle_blue.png new file mode 100644 index 0000000..e99476c Binary files /dev/null and b/examples/fft-microphone/images/particle_blue.png differ diff --git a/examples/fft-microphone/images/particle_green.png b/examples/fft-microphone/images/particle_green.png new file mode 100644 index 0000000..7541794 Binary files /dev/null and b/examples/fft-microphone/images/particle_green.png differ diff --git a/examples/fft-microphone/images/particle_orange.png b/examples/fft-microphone/images/particle_orange.png new file mode 100644 index 0000000..0804be8 Binary files /dev/null and b/examples/fft-microphone/images/particle_orange.png differ diff --git a/examples/fft-microphone/images/particle_pink.png b/examples/fft-microphone/images/particle_pink.png new file mode 100755 index 0000000..ce02e92 Binary files /dev/null and b/examples/fft-microphone/images/particle_pink.png differ diff --git a/examples/fft-microphone/images/particle_white.png b/examples/fft-microphone/images/particle_white.png new file mode 100644 index 0000000..f087afc Binary files /dev/null and b/examples/fft-microphone/images/particle_white.png differ diff --git a/examples/fft-microphone/images/particle_yellow.png b/examples/fft-microphone/images/particle_yellow.png new file mode 100644 index 0000000..86d67f1 Binary files /dev/null and b/examples/fft-microphone/images/particle_yellow.png differ diff --git a/examples/fft-microphone/index.html b/examples/fft-microphone/index.html new file mode 100644 index 0000000..f9c9a8a --- /dev/null +++ b/examples/fft-microphone/index.html @@ -0,0 +1,37 @@ + + + + + + + +
+

dancer.js

+

JavaScript Audio Library

+

Microphone input / FFT

+ +
+
Loading . . .
+ +
+ Now playing... +

Zircon - Devil's Spirit

+
+ + + + + + + + + + + + + + + diff --git a/examples/fft-microphone/js/player.js b/examples/fft-microphone/js/player.js new file mode 100644 index 0000000..7290352 --- /dev/null +++ b/examples/fft-microphone/js/player.js @@ -0,0 +1,55 @@ +(function() { + var + fft = document.getElementById( 'fft' ), + ctx = fft.getContext( '2d' ), + dancer, kick; + + /* + * Dancer.js magic + */ + Dancer.setOptions({ + flashSWF : '../../lib/soundmanager2.swf', + flashJS : '../../lib/soundmanager2.js' + }); + + dancer = new Dancer(); + kick = dancer.createKick({ + onKick: function () { + ctx.fillStyle = '#ff0077'; + }, + offKick: function () { + ctx.fillStyle = '#666'; + } + }).on(); + + dancer + .fft( fft, { fillStyle: '#666' }) + .load({microphone: true}); + + Dancer.isSupported() || loaded(); + !dancer.isLoaded() ? dancer.bind( 'loaded', loaded ) : loaded(); + + /* + * Loading + */ + + function loaded () { + var + loading = document.getElementById( 'loading' ), + supported = Dancer.isSupported(), + p; + + if ( !supported ) { + p = document.createElement('P'); + p.appendChild( document.createTextNode( 'Your browser does not currently support either Web Audio API or Audio Data API. The audio may play, but the visualizers will not move to the music; check out the latest Chrome or Firefox browsers!' ) ); + loading.appendChild( p ); + } else { + loading.parentNode.removeChild(loading); + } + + } + + // For debugging + window.dancer = dancer; + +})(); diff --git a/src/adapterWebkit.js b/src/adapterWebkit.js index 2981988..92ee7d5 100644 --- a/src/adapterWebkit.js +++ b/src/adapterWebkit.js @@ -6,9 +6,7 @@ var adapter = function ( dancer ) { this.dancer = dancer; this.audio = new Audio(); - this.context = window.AudioContext ? - new window.AudioContext() : - new window.webkitAudioContext(); + this.context = new window.AudioContext(); }; adapter.prototype = { @@ -37,17 +35,28 @@ connectContext.call( _this ); } - this.audio.addEventListener( 'progress', function ( e ) { - if ( e.currentTarget.duration ) { - _this.progress = e.currentTarget.seekable.end( 0 ) / e.currentTarget.duration; - } - }); + + if (!this.isStreaming()) { + this.audio.addEventListener( 'progress', function ( e ) { + if ( e.currentTarget.duration ) { + _this.progress = e.currentTarget.seekable.end( 0 ) / e.currentTarget.duration; + } + }); + } else { + this.play(); + } return this.audio; }, + isStreaming: function() { + return !(this.audio instanceof Audio || this.audio instanceof HTMLElement); + }, + play : function () { - this.audio.play(); + if (!this.isStreaming()) { + this.audio.play(); + } this.isPlaying = true; }, @@ -107,7 +116,12 @@ }; function connectContext () { - this.source = this.context.createMediaElementSource( this.audio ); + if (this.isStreaming()) { + this.source = this.audio ; + } else { + this.source = this.context.createMediaElementSource( this.audio ); + } + this.source.connect( this.proc ); this.source.connect( this.gain ); this.gain.connect( this.context.destination ); diff --git a/src/dancer.js b/src/dancer.js index f10f42e..8012701 100644 --- a/src/dancer.js +++ b/src/dancer.js @@ -16,24 +16,36 @@ var path; // Loading an Audio element - if ( source instanceof HTMLElement ) { - this.source = source; + if (source.audio) { + this.source = source.audio; if ( Dancer.isSupported() === 'flash' ) { - this.source = { src: Dancer._getMP3SrcFromAudio( source ) }; + this.source = { src: Dancer._getMP3SrcFromAudio( source.audio ) }; } + this.loadAudioAdapter(this.source); + // Loading an object with src, [codecs] - } else { - this.source = window.Audio ? new Audio() : {}; - this.source.src = Dancer._makeSupportedPath( source.src, source.codecs ); + } else if (source.src) { + this.source = window.Audio ? new Audio() : {}; + this.source.src = Dancer._makeSupportedPath( source.src, source.codecs ); + this.loadAudioAdapter(this.source); + + // Request user audio + } else if (source.microphone) { + navigator.getUserMedia({audio: true}, this._userMediaCallback, function(e) { + console.log("Unsupported."); + }); + this.source = source; } - this.audio = this.audioAdapter.load( this.source ); return this; }, - /* Controls */ + loadAudioAdapter: function(source) { + this.audio = this.audioAdapter.load( source ); + }, + /* Controls */ play : function () { this.audioAdapter.play(); return this; diff --git a/src/support.js b/src/support.js index 6f5aa3a..74df2d4 100644 --- a/src/support.js +++ b/src/support.js @@ -1,4 +1,7 @@ (function ( Dancer ) { + // Avoid browser-specific prefixes. + window.AudioContext = window.AudioContext || window.webkitAudioContext; + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia; var CODECS = { 'mp3' : 'audio/mpeg;', @@ -9,7 +12,6 @@ audioEl = document.createElement( 'audio' ); Dancer.options = {}; - Dancer.setOptions = function ( o ) { for ( var option in o ) { if ( o.hasOwnProperty( option ) ) { @@ -59,6 +61,7 @@ }; Dancer._getAdapter = function ( instance ) { + Dancer.instance = instance; switch ( Dancer.isSupported() ) { case 'webaudio': return new Dancer.adapters.webkit( instance ); @@ -80,6 +83,13 @@ return null; }; + Dancer.prototype._userMediaCallback = function(stream) { + var context = Dancer.instance.audioAdapter.context; + input = context.createMediaStreamSource(stream); + input.connect(context.destination); + Dancer.instance.loadAudioAdapter(input); + }; + // Browser detection is lame, but Safari 6 has Web Audio API, // but does not support processing audio from a Media Element Source // https://gist.github.com/3265344