├── .gitignore ├── bootstrap ├── img │ ├── glyphicons-halflings.png │ └── glyphicons-halflings-white.png ├── js │ ├── bootstrap-transition.js │ ├── bootstrap-alert.js │ ├── bootstrap-button.js │ ├── bootstrap-dropdown.js │ ├── bootstrap-popover.js │ ├── bootstrap-tab.js │ ├── bootstrap-scrollspy.js │ ├── bootstrap-collapse.js │ ├── bootstrap-carousel.js │ ├── bootstrap-modal.js │ ├── bootstrap-typeahead.js │ ├── bootstrap-tooltip.js │ └── bootstrap.min.js └── css │ ├── bootstrap-responsive.min.css │ └── bootstrap-responsive.css ├── app ├── js │ ├── mathutilities.js │ ├── editorapp.js │ ├── SpectrumDisplay.js │ ├── SpectrumWorker.js │ ├── filesystemutility.js │ ├── recorder.js │ ├── filedropbox.js │ ├── fft.js │ ├── recorderWorker.js │ ├── ACSpectrum.js │ ├── ACFIRFilter.js │ ├── ACAAFilter.js │ ├── binarytoolkit.js │ ├── audioplayback.js │ ├── wavetrack.js │ ├── ACFFT.js │ └── audiosequence.js └── mainstyle.css ├── README.md └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plucked/html5-audio-editor/HEAD/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plucked/html5-audio-editor/HEAD/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /app/js/mathutilities.js: -------------------------------------------------------------------------------- 1 | var MathEx = new function MathEx() 2 | { 3 | this.lerp = function lerp(start, end, percentage) 4 | { 5 | if (start < end) 6 | { 7 | return start + (end - start) * percentage; 8 | } 9 | else 10 | { 11 | return end + (start - end) * (1.0 - percentage); 12 | } 13 | }; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | html5-audio-editor 2 | ================== 3 | 4 | A small audio editor written in html5 and javascript without usage of external plugins. 5 | 6 | 7 | You need the latest build of Google Chrome or the nightly build for Safari. Firefox is not supported yet. 8 | 9 | I'm working with Chrome Version 20.0.1132.47 beta and have to start Chrome with the parameter "--allow-file-access-from-files". You need this to start the web application from the local filesystem. 10 | 11 | If you want to see this application in action, go to http://plucked.de 12 | 13 | -------------------------------------------------------------------------------- /app/js/editorapp.js: -------------------------------------------------------------------------------- 1 | 2 | function onDocumentLoaded() 3 | { 4 | console.log("Loaded"); 5 | 6 | ACInitFFT(); 7 | 8 | initializeAudioLayerControls(); 9 | 10 | var audioLayerControl = document.querySelector("#audioLayerControl"); 11 | audioLayerControl.removeAllSequenceEditors(); 12 | var leftEditor = audioLayerControl.createSequenceEditor("Left Channel"); 13 | var rightEditor = audioLayerControl.createSequenceEditor("Right Channel"); 14 | 15 | audioLayerControl.setLinkMode(true); 16 | } -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-transition.js: -------------------------------------------------------------------------------- 1 | /* =================================================== 2 | * bootstrap-transition.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#transitions 4 | * =================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | $(function () { 24 | 25 | "use strict"; // jshint ;_; 26 | 27 | 28 | /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) 29 | * ======================================================= */ 30 | 31 | $.support.transition = (function () { 32 | 33 | var transitionEnd = (function () { 34 | 35 | var el = document.createElement('bootstrap') 36 | , transEndEventNames = { 37 | 'WebkitTransition' : 'webkitTransitionEnd' 38 | , 'MozTransition' : 'transitionend' 39 | , 'OTransition' : 'oTransitionEnd' 40 | , 'msTransition' : 'MSTransitionEnd' 41 | , 'transition' : 'transitionend' 42 | } 43 | , name 44 | 45 | for (name in transEndEventNames){ 46 | if (el.style[name] !== undefined) { 47 | return transEndEventNames[name] 48 | } 49 | } 50 | 51 | }()) 52 | 53 | return transitionEnd && { 54 | end: transitionEnd 55 | } 56 | 57 | })() 58 | 59 | }) 60 | 61 | }(window.jQuery); -------------------------------------------------------------------------------- /app/js/SpectrumDisplay.js: -------------------------------------------------------------------------------- 1 | function SpectrumDisplay(rootElement, divElement) 2 | { 3 | this.rootElement = rootElement; 4 | 5 | this.canvasRef = document.createElement("canvas"); 6 | this.canvasRef.id = "editor-spectrum"; 7 | divElement.appendChild(this.canvasRef); 8 | this.canvasRef.width = divElement.clientWidth; 9 | this.canvasRef.height = divElement.clientHeight; 10 | this.buffer = new Float32Array(this.canvasRef.width); 11 | this.min = -150;// decibel 12 | this.max = 0;// decibel 13 | this.range = this.max - this.min; 14 | this.minRange = this.canvasRef.height; 15 | 16 | this.updateBuffer = function updateBuffer(frequencyData) 17 | { 18 | this.min = -150; 19 | this.max = 0; 20 | 21 | for(var i = 0; i < this.buffer.length; ++i) 22 | { 23 | var data = frequencyData[Math.round(frequencyData.length / this.buffer.length * i)]; 24 | // clamp into range 25 | data = Math.min(this.max, Math.max(this.min, data)); 26 | this.buffer[i] = data; 27 | } 28 | }; 29 | 30 | this.paintSpectrum = function paintSpectrum() 31 | { 32 | var canvasContext = this.canvasRef.getContext('2d'); 33 | canvasContext.clearRect(0, 0, this.canvasRef.width, this.canvasRef.height); 34 | 35 | canvasContext.strokeStyle = "#369bd7"; 36 | canvasContext.beginPath(); 37 | 38 | // fit the y to display all values 39 | var yFactor = this.canvasRef.height / this.range; 40 | 41 | for(var i = 0 ; i < this.buffer.length - 1; ++i) 42 | { 43 | canvasContext.moveTo(i + 0.5, this.buffer[i] * -1.0 * yFactor); 44 | canvasContext.lineTo(i + 1.5, this.buffer[i + 1] * -1.0 * yFactor); 45 | } 46 | canvasContext.stroke(); 47 | 48 | canvasContext.fillStyle = canvasContext.strokeStyle; 49 | canvasContext.fillText(Math.round(this.max) + " db",0, 20); 50 | canvasContext.fillText(Math.round(this.min) + " db",0, this.canvasRef.height); 51 | }; 52 | } -------------------------------------------------------------------------------- /app/js/SpectrumWorker.js: -------------------------------------------------------------------------------- 1 | function SpectrumWorker() 2 | { 3 | this.toFrequencyDomain = function toFrequencyDomain(timeDomainRealIn, timeDomainImagIn, start, len, realOut, imagOut) 4 | { 5 | if (start === undefined) start = 0; 6 | if (len === undefined) len = timeDomainRealIn.length; 7 | 8 | if (IsPowerOfTwo(len) !== true) throw "The length of the timeDomain has to be power of two!"; 9 | 10 | var tempR = timeDomainRealIn.slice(start, start + len); 11 | var tempI = (timeDomainImagIn === undefined) ? undefined : timeDomainImagIn.slice(start, start + len); 12 | ACFFT(tempR.length, false, tempR, tempI, realOut, imagOut); 13 | }; 14 | 15 | this.fromFrequencyDomain = function fromFrequencyDomain(freqDomainRealIn, freqDomainImagIn, realOut, imagOut) 16 | { 17 | if (freqDomainRealIn.length !== freqDomainImagIn) throw "The real and imaginary arrays have a different size"; 18 | 19 | ACFFT(freqDomainRealIn.length, true, freqDomainRealIn, freqDomainImagIn, realOut, imagOut); 20 | }; 21 | 22 | this.toAmplitudeSpectrum = function toAmplitudeSpectrum(timeDomainData, sampleRate, start, len, windowSize, windowFuncId) 23 | { 24 | if (start === undefined) start = 0; 25 | if (len === undefined) len = timeDomainData.length; 26 | if (windowSize === undefined) windowSize = 1024; 27 | if (windowFuncId === undefined) windowFuncId = 4; 28 | if (sampleRate === undefined) sampleRate = 44100; 29 | 30 | if (timeDomainData.length < windowSize || len < windowSize) throw "Length of the timeDomainData is to small (minimum is the windowSize: " + windowSize + ")"; 31 | if (start < 0 || start >= timeDomainData.length) throw "Start is out of range"; 32 | if (start + len > timeDomainData.length) throw "Length is out of range"; 33 | 34 | var temp = timeDomainData.slice(start, start + len); 35 | var result = []; 36 | ComputeSpectrum(temp, temp.length, windowSize, sampleRate, result, false, windowFuncId); 37 | 38 | return result; 39 | }; 40 | 41 | this.toAmplitudeSpectrumFromAudioSequence = function toAmplitudeSpectrumFromAudioSequence(audioSequence, start, len, windowSize, windowFuncId) 42 | { 43 | return this.toAmplitudeSpectrum(audioSequence.data, audioSequence.sampleRate, start, len, windowSize, windowFuncId); 44 | }; 45 | 46 | 47 | } -------------------------------------------------------------------------------- /app/js/filesystemutility.js: -------------------------------------------------------------------------------- 1 | window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; 2 | 3 | 4 | var callback_requestSuccess = undefined; 5 | var callback_requestFailed = undefined; 6 | var fileSystemEntry = undefined; 7 | 8 | function getLocalStorage(sizeInBytes, requestSuccess, requestFailed) 9 | { 10 | callback_requestSuccess = requestSuccess; 11 | callback_requestFailed = requestFailed; 12 | 13 | // Webkit quota request for persistant storage 14 | window.webkitStorageInfo.requestQuota(PERSISTENT, sizeInBytes, successfulQuotaRequest,failedQuotaRequest); 15 | } 16 | 17 | function successfulQuotaRequest(grantedBytes) 18 | { 19 | window.requestFileSystem(PERSISTENT, grantedBytes, successfulFileSystemCreated, failedFileSystemCreation); 20 | } 21 | 22 | function failedQuotaRequest(errorCode) 23 | { 24 | if (callback_requestFailed !== undefined) callback_requestFailed(errorCode); 25 | } 26 | 27 | function successfulFileSystemCreated(fileSystem) 28 | { 29 | fileSystemEntry = fileSystem; 30 | if (callback_requestSuccess !== undefined) callback_requestSuccess(fileSystem); 31 | 32 | } 33 | 34 | function failedFileSystemCreation(errorCode) 35 | { 36 | if (callback_requestFailed !== undefined) callback_requestFailed(errorCode); 37 | } 38 | 39 | 40 | 41 | function readFile(filename, readSuccess, readError) 42 | { 43 | if (fileSystemEntry === undefined) debugger; 44 | 45 | fileSystemEntry.root.getFile(filename, {}, function(fileEntry) 46 | { 47 | fileEntry.file(function(file) 48 | { 49 | var reader = new FileReader(); 50 | reader.onload = function(evt) 51 | { 52 | readSuccess(evt, this); 53 | }; 54 | reader.readAsArrayBuffer(file); 55 | }); 56 | }, readError); 57 | } 58 | 59 | function writeFile(filename, writeFunction, writeError) 60 | { 61 | if (fileSystemEntry === undefined) debugger; 62 | 63 | var writeFunc = function() 64 | { 65 | var file = fileSystemEntry.root.getFile(filename, {create: true}, function(fileEntry) 66 | { 67 | var writer = fileEntry.createWriter(writeFunction); 68 | }, writeError); 69 | }; 70 | 71 | removeFile(filename, writeFunc, writeFunc); 72 | } 73 | 74 | function removeFile(filename, removeCallback, removeError) 75 | { 76 | fileSystemEntry.root.getFile(filename, {create: false}, function(fileEntry) 77 | { 78 | fileEntry.remove(removeCallback, removeError); 79 | }, removeError); 80 | } -------------------------------------------------------------------------------- /app/js/recorder.js: -------------------------------------------------------------------------------- 1 | (function(window){ 2 | 3 | var WORKER_PATH = 'recorderWorker.js'; 4 | 5 | var Recorder = function(source, cfg){ 6 | var config = cfg || {}; 7 | var bufferLen = config.bufferLen || 4096; 8 | this.context = source.context; 9 | this.node = (this.context.createScriptProcessor || 10 | this.context.createJavaScriptNode).call(this.context, 11 | bufferLen, 2, 2); 12 | var worker = new Worker(config.workerPath || WORKER_PATH); 13 | worker.postMessage({ 14 | command: 'init', 15 | config: { 16 | sampleRate: this.context.sampleRate 17 | } 18 | }); 19 | var recording = false, 20 | currCallback; 21 | 22 | this.node.onaudioprocess = function(e){ 23 | if (!recording) return; 24 | worker.postMessage({ 25 | command: 'record', 26 | buffer: [ 27 | e.inputBuffer.getChannelData(0), 28 | e.inputBuffer.getChannelData(1) 29 | ] 30 | }); 31 | } 32 | 33 | this.configure = function(cfg){ 34 | for (var prop in cfg){ 35 | if (cfg.hasOwnProperty(prop)){ 36 | config[prop] = cfg[prop]; 37 | } 38 | } 39 | } 40 | 41 | this.record = function(){ 42 | recording = true; 43 | } 44 | 45 | this.stop = function(){ 46 | recording = false; 47 | } 48 | 49 | this.clear = function(){ 50 | worker.postMessage({ command: 'clear' }); 51 | } 52 | 53 | this.getBuffer = function(cb) { 54 | currCallback = cb || config.callback; 55 | worker.postMessage({ command: 'getBuffer' }) 56 | } 57 | 58 | this.exportWAV = function(cb, type){ 59 | currCallback = cb || config.callback; 60 | type = type || config.type || 'audio/wav'; 61 | if (!currCallback) throw new Error('Callback not set'); 62 | worker.postMessage({ 63 | command: 'exportWAV', 64 | type: type 65 | }); 66 | } 67 | 68 | worker.onmessage = function(e){ 69 | var blob = e.data; 70 | currCallback(blob); 71 | } 72 | 73 | source.connect(this.node); 74 | this.node.connect(this.context.destination); //this should not be necessary 75 | }; 76 | 77 | Recorder.forceDownload = function(blob, filename){ 78 | var url = (window.URL || window.webkitURL).createObjectURL(blob); 79 | var link = window.document.createElement('a'); 80 | link.href = url; 81 | link.download = filename || 'output.wav'; 82 | var click = document.createEvent("Event"); 83 | click.initEvent("click", true, true); 84 | link.dispatchEvent(click); 85 | } 86 | 87 | window.Recorder = Recorder; 88 | 89 | })(window); 90 | -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-alert.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-alert.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#alerts 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* ALERT CLASS DEFINITION 27 | * ====================== */ 28 | 29 | var dismiss = '[data-dismiss="alert"]' 30 | , Alert = function (el) { 31 | $(el).on('click', dismiss, this.close) 32 | } 33 | 34 | Alert.prototype.close = function (e) { 35 | var $this = $(this) 36 | , selector = $this.attr('data-target') 37 | , $parent 38 | 39 | if (!selector) { 40 | selector = $this.attr('href') 41 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 42 | } 43 | 44 | $parent = $(selector) 45 | 46 | e && e.preventDefault() 47 | 48 | $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) 49 | 50 | $parent.trigger(e = $.Event('close')) 51 | 52 | if (e.isDefaultPrevented()) return 53 | 54 | $parent.removeClass('in') 55 | 56 | function removeElement() { 57 | $parent 58 | .trigger('closed') 59 | .remove() 60 | } 61 | 62 | $.support.transition && $parent.hasClass('fade') ? 63 | $parent.on($.support.transition.end, removeElement) : 64 | removeElement() 65 | } 66 | 67 | 68 | /* ALERT PLUGIN DEFINITION 69 | * ======================= */ 70 | 71 | $.fn.alert = function (option) { 72 | return this.each(function () { 73 | var $this = $(this) 74 | , data = $this.data('alert') 75 | if (!data) $this.data('alert', (data = new Alert(this))) 76 | if (typeof option == 'string') data[option].call($this) 77 | }) 78 | } 79 | 80 | $.fn.alert.Constructor = Alert 81 | 82 | 83 | /* ALERT DATA-API 84 | * ============== */ 85 | 86 | $(function () { 87 | $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) 88 | }) 89 | 90 | }(window.jQuery); -------------------------------------------------------------------------------- /app/js/filedropbox.js: -------------------------------------------------------------------------------- 1 | /* Cross Browser Support */ 2 | window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; 3 | window.BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder; 4 | window.URL = window.URL || window.webkitURL; 5 | 6 | /* The FileDropbox prepares a HTMLElement to be a drop container and loads the first dropped file into a array */ 7 | function FileDropbox() 8 | { 9 | this.result = null; 10 | this.resultArrayBuffer = null; 11 | this.onFinish = null; 12 | this.onFail = null; 13 | 14 | this.defineDropHandler = function defineDropHandler(dropContainerElement) 15 | { 16 | // init event handlers 17 | dropContainerElement.addEventListener("dragenter", this.skipEventHandler, false); 18 | dropContainerElement.addEventListener("dragexit", this.skipEventHandler, false); 19 | dropContainerElement.addEventListener("dragover", this.skipEventHandler, false); 20 | dropContainerElement.addEventListener("drop", this.dropHandler, false); 21 | dropContainerElement.masterObj = this; // need to define this controller for further events 22 | }; 23 | 24 | this.skipEventHandler = function skipEventHandler(evt) 25 | { 26 | evt.stopPropagation(); 27 | evt.preventDefault(); 28 | }; 29 | 30 | this.dropHandler = function dropHandler(evt) 31 | { 32 | evt.stopPropagation(); 33 | evt.preventDefault(); 34 | 35 | // get list of dropped files 36 | var files = evt.dataTransfer.files; 37 | // amount of dropped files 38 | var count = files.length; 39 | 40 | // One file at least neccessary to continue 41 | if (count > 0) 42 | { 43 | handleFiles(files, evt.currentTarget.masterObj); 44 | } 45 | }; 46 | 47 | function handleFiles(files, masterObj) 48 | { 49 | // handle only the first file (no multifile support) 50 | var file = files[0]; 51 | // create the reader to access the local file (note: browser have different security restrictions) 52 | var reader = new FileReader(); 53 | 54 | // init the reader event handlers 55 | reader.onload = function (evt) 56 | { 57 | var arrayBuffer = evt.target.result; 58 | 59 | masterObj.resultArrayBuffer = arrayBuffer; 60 | // write into the result array 61 | masterObj.result = new Uint8Array(arrayBuffer); 62 | 63 | // callback 64 | if (masterObj.onFinish !== null) 65 | { 66 | masterObj.onFinish(); 67 | } 68 | }; // event handle on success 69 | 70 | reader.onerror = masterObj.onFail; // event handle on failure 71 | 72 | // load the file as array buffer 73 | reader.readAsArrayBuffer(file); 74 | } 75 | 76 | 77 | }; -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-button.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-button.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#buttons 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* BUTTON PUBLIC CLASS DEFINITION 27 | * ============================== */ 28 | 29 | var Button = function (element, options) { 30 | this.$element = $(element) 31 | this.options = $.extend({}, $.fn.button.defaults, options) 32 | } 33 | 34 | Button.prototype.setState = function (state) { 35 | var d = 'disabled' 36 | , $el = this.$element 37 | , data = $el.data() 38 | , val = $el.is('input') ? 'val' : 'html' 39 | 40 | state = state + 'Text' 41 | data.resetText || $el.data('resetText', $el[val]()) 42 | 43 | $el[val](data[state] || this.options[state]) 44 | 45 | // push to event loop to allow forms to submit 46 | setTimeout(function () { 47 | state == 'loadingText' ? 48 | $el.addClass(d).attr(d, d) : 49 | $el.removeClass(d).removeAttr(d) 50 | }, 0) 51 | } 52 | 53 | Button.prototype.toggle = function () { 54 | var $parent = this.$element.parent('[data-toggle="buttons-radio"]') 55 | 56 | $parent && $parent 57 | .find('.active') 58 | .removeClass('active') 59 | 60 | this.$element.toggleClass('active') 61 | } 62 | 63 | 64 | /* BUTTON PLUGIN DEFINITION 65 | * ======================== */ 66 | 67 | $.fn.button = function (option) { 68 | return this.each(function () { 69 | var $this = $(this) 70 | , data = $this.data('button') 71 | , options = typeof option == 'object' && option 72 | if (!data) $this.data('button', (data = new Button(this, options))) 73 | if (option == 'toggle') data.toggle() 74 | else if (option) data.setState(option) 75 | }) 76 | } 77 | 78 | $.fn.button.defaults = { 79 | loadingText: 'loading...' 80 | } 81 | 82 | $.fn.button.Constructor = Button 83 | 84 | 85 | /* BUTTON DATA-API 86 | * =============== */ 87 | 88 | $(function () { 89 | $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) { 90 | var $btn = $(e.target) 91 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 92 | $btn.button('toggle') 93 | }) 94 | }) 95 | 96 | }(window.jQuery); -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdowns 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* DROPDOWN CLASS DEFINITION 27 | * ========================= */ 28 | 29 | var toggle = '[data-toggle="dropdown"]' 30 | , Dropdown = function (element) { 31 | var $el = $(element).on('click.dropdown.data-api', this.toggle) 32 | $('html').on('click.dropdown.data-api', function () { 33 | $el.parent().removeClass('open') 34 | }) 35 | } 36 | 37 | Dropdown.prototype = { 38 | 39 | constructor: Dropdown 40 | 41 | , toggle: function (e) { 42 | var $this = $(this) 43 | , $parent 44 | , selector 45 | , isActive 46 | 47 | if ($this.is('.disabled, :disabled')) return 48 | 49 | selector = $this.attr('data-target') 50 | 51 | if (!selector) { 52 | selector = $this.attr('href') 53 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 54 | } 55 | 56 | $parent = $(selector) 57 | $parent.length || ($parent = $this.parent()) 58 | 59 | isActive = $parent.hasClass('open') 60 | 61 | clearMenus() 62 | 63 | if (!isActive) $parent.toggleClass('open') 64 | 65 | return false 66 | } 67 | 68 | } 69 | 70 | function clearMenus() { 71 | $(toggle).parent().removeClass('open') 72 | } 73 | 74 | 75 | /* DROPDOWN PLUGIN DEFINITION 76 | * ========================== */ 77 | 78 | $.fn.dropdown = function (option) { 79 | return this.each(function () { 80 | var $this = $(this) 81 | , data = $this.data('dropdown') 82 | if (!data) $this.data('dropdown', (data = new Dropdown(this))) 83 | if (typeof option == 'string') data[option].call($this) 84 | }) 85 | } 86 | 87 | $.fn.dropdown.Constructor = Dropdown 88 | 89 | 90 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 91 | * =================================== */ 92 | 93 | $(function () { 94 | $('html').on('click.dropdown.data-api', clearMenus) 95 | $('body') 96 | .on('click.dropdown', '.dropdown form', function (e) { e.stopPropagation() }) 97 | .on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) 98 | }) 99 | 100 | }(window.jQuery); -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-popover.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bootstrap-popover.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#popovers 4 | * =========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * =========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* POPOVER PUBLIC CLASS DEFINITION 27 | * =============================== */ 28 | 29 | var Popover = function ( element, options ) { 30 | this.init('popover', element, options) 31 | } 32 | 33 | 34 | /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js 35 | ========================================== */ 36 | 37 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, { 38 | 39 | constructor: Popover 40 | 41 | , setContent: function () { 42 | var $tip = this.tip() 43 | , title = this.getTitle() 44 | , content = this.getContent() 45 | 46 | $tip.find('.popover-title')[this.isHTML(title) ? 'html' : 'text'](title) 47 | $tip.find('.popover-content > *')[this.isHTML(content) ? 'html' : 'text'](content) 48 | 49 | $tip.removeClass('fade top bottom left right in') 50 | } 51 | 52 | , hasContent: function () { 53 | return this.getTitle() || this.getContent() 54 | } 55 | 56 | , getContent: function () { 57 | var content 58 | , $e = this.$element 59 | , o = this.options 60 | 61 | content = $e.attr('data-content') 62 | || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) 63 | 64 | return content 65 | } 66 | 67 | , tip: function () { 68 | if (!this.$tip) { 69 | this.$tip = $(this.options.template) 70 | } 71 | return this.$tip 72 | } 73 | 74 | }) 75 | 76 | 77 | /* POPOVER PLUGIN DEFINITION 78 | * ======================= */ 79 | 80 | $.fn.popover = function (option) { 81 | return this.each(function () { 82 | var $this = $(this) 83 | , data = $this.data('popover') 84 | , options = typeof option == 'object' && option 85 | if (!data) $this.data('popover', (data = new Popover(this, options))) 86 | if (typeof option == 'string') data[option]() 87 | }) 88 | } 89 | 90 | $.fn.popover.Constructor = Popover 91 | 92 | $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, { 93 | placement: 'right' 94 | , content: '' 95 | , template: '
> 1; 106 | if (v != 0) ++r; 107 | else break; 108 | } 109 | return r; 110 | 111 | }, 112 | forward: function(input){ 113 | fft(input, this.scratch, this.factors, true); 114 | }, 115 | backward: function(input){ 116 | fft(input, this.scratch, this.factors, false); 117 | } 118 | }; 119 | 120 | return FFT; 121 | 122 | }()); 123 | -------------------------------------------------------------------------------- /app/js/recorderWorker.js: -------------------------------------------------------------------------------- 1 | var recLength = 0, 2 | recBuffersL = [], 3 | recBuffersR = [], 4 | sampleRate; 5 | 6 | this.onmessage = function(e){ 7 | switch(e.data.command){ 8 | case 'init': 9 | init(e.data.config); 10 | break; 11 | case 'record': 12 | record(e.data.buffer); 13 | break; 14 | case 'exportWAV': 15 | exportWAV(e.data.type); 16 | break; 17 | case 'getBuffer': 18 | getBuffer(); 19 | break; 20 | case 'clear': 21 | clear(); 22 | break; 23 | } 24 | }; 25 | 26 | function init(config){ 27 | sampleRate = config.sampleRate; 28 | } 29 | 30 | function record(inputBuffer){ 31 | recBuffersL.push(inputBuffer[0]); 32 | recBuffersR.push(inputBuffer[1]); 33 | recLength += inputBuffer[0].length; 34 | } 35 | 36 | function exportWAV(type){ 37 | var bufferL = mergeBuffers(recBuffersL, recLength); 38 | var bufferR = mergeBuffers(recBuffersR, recLength); 39 | var interleaved = interleave(bufferL, bufferR); 40 | var dataview = encodeWAV(interleaved); 41 | var audioBlob = new Blob([dataview], { type: type }); 42 | 43 | this.postMessage(audioBlob); 44 | } 45 | 46 | function getBuffer() { 47 | var buffers = []; 48 | buffers.push( mergeBuffers(recBuffersL, recLength) ); 49 | buffers.push( mergeBuffers(recBuffersR, recLength) ); 50 | this.postMessage(buffers); 51 | } 52 | 53 | function clear(){ 54 | recLength = 0; 55 | recBuffersL = []; 56 | recBuffersR = []; 57 | } 58 | 59 | function mergeBuffers(recBuffers, recLength){ 60 | var result = new Float32Array(recLength); 61 | var offset = 0; 62 | for (var i = 0; i < recBuffers.length; i++){ 63 | result.set(recBuffers[i], offset); 64 | offset += recBuffers[i].length; 65 | } 66 | return result; 67 | } 68 | 69 | function interleave(inputL, inputR){ 70 | var length = inputL.length + inputR.length; 71 | var result = new Float32Array(length); 72 | 73 | var index = 0, 74 | inputIndex = 0; 75 | 76 | while (index < length){ 77 | result[index++] = inputL[inputIndex]; 78 | result[index++] = inputR[inputIndex]; 79 | inputIndex++; 80 | } 81 | return result; 82 | } 83 | 84 | function floatTo16BitPCM(output, offset, input){ 85 | for (var i = 0; i < input.length; i++, offset+=2){ 86 | var s = Math.max(-1, Math.min(1, input[i])); 87 | output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); 88 | } 89 | } 90 | 91 | function writeString(view, offset, string){ 92 | for (var i = 0; i < string.length; i++){ 93 | view.setUint8(offset + i, string.charCodeAt(i)); 94 | } 95 | } 96 | 97 | function encodeWAV(samples){ 98 | var buffer = new ArrayBuffer(44 + samples.length * 2); 99 | var view = new DataView(buffer); 100 | 101 | /* RIFF identifier */ 102 | writeString(view, 0, 'RIFF'); 103 | /* file length */ 104 | view.setUint32(4, 32 + samples.length * 2, true); 105 | /* RIFF type */ 106 | writeString(view, 8, 'WAVE'); 107 | /* format chunk identifier */ 108 | writeString(view, 12, 'fmt '); 109 | /* format chunk length */ 110 | view.setUint32(16, 16, true); 111 | /* sample format (raw) */ 112 | view.setUint16(20, 1, true); 113 | /* channel count */ 114 | view.setUint16(22, 2, true); 115 | /* sample rate */ 116 | view.setUint32(24, sampleRate, true); 117 | /* byte rate (sample rate * block align) */ 118 | view.setUint32(28, sampleRate * 4, true); 119 | /* block align (channel count * bytes per sample) */ 120 | view.setUint16(32, 4, true); 121 | /* bits per sample */ 122 | view.setUint16(34, 16, true); 123 | /* data chunk identifier */ 124 | writeString(view, 36, 'data'); 125 | /* data chunk length */ 126 | view.setUint32(40, samples.length * 2, true); 127 | 128 | floatTo16BitPCM(view, 44, samples); 129 | 130 | return view; 131 | } 132 | -------------------------------------------------------------------------------- /app/js/ACSpectrum.js: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | Audacity: A Digital Audio Editor 4 | 5 | Spectrum.cpp 6 | 7 | Dominic Mazzoni 8 | 9 | *******************************************************************//*! 10 | 11 | \file Spectrum.cpp 12 | \brief Functions for computing Spectra. 13 | 14 | *//*******************************************************************/ 15 | 16 | 17 | 18 | 19 | function ComputeSpectrum(data, width, 20 | windowSize, 21 | rate, output, 22 | autocorrelation, windowFunc) 23 | { 24 | if (width < windowSize) 25 | return false; 26 | 27 | if (!data || !output) 28 | return true; 29 | 30 | var processed = new Float32Array(windowSize); 31 | 32 | var i; 33 | for (var i = 0; i < windowSize; i++) 34 | processed[i] = 0.0; 35 | var half = windowSize / 2; 36 | 37 | var inData = new Float32Array(windowSize); 38 | var out = new Float32Array(windowSize); 39 | var out2 = new Float32Array(windowSize); 40 | 41 | var start = 0; 42 | var windows = 0; 43 | while (start + windowSize <= width) { 44 | for (var i = 0; i < windowSize; i++) 45 | inData[i] = data[start + i]; 46 | 47 | WindowFunc(windowFunc, windowSize, inData); 48 | 49 | if (autocorrelation) { 50 | // Take FFT 51 | ACFFT(windowSize, false, inData, undefined, out, out2); 52 | 53 | // Compute power 54 | for (var i = 0; i < windowSize; i++) 55 | inData[i] = (out[i] * out[i]) + (out2[i] * out2[i]); 56 | 57 | // Tolonen and Karjalainen recommend taking the cube root 58 | // of the power, instead of the square root 59 | 60 | for (var i = 0; i < windowSize; i++) 61 | inData[i] = Math.pow(inData[i], 1.0 / 3.0); 62 | 63 | ACFFT(windowSize, false, inData, undefined, out, out2); 64 | } 65 | else 66 | PowerSpectrum(windowSize, inData, out); 67 | 68 | // Take real part of result 69 | for (var i = 0; i < half; i++) 70 | processed[i] += out[i]; 71 | 72 | start += half; 73 | windows++; 74 | } 75 | 76 | if (autocorrelation) { 77 | 78 | // Peak Pruning as described by Tolonen and Karjalainen, 2000 79 | /* 80 | Combine most of the calculations in a Math.Math.Math.single for loop. 81 | It should be safe, as indexes refer only to current and previous elements, 82 | that have already been clipped, etc... 83 | */ 84 | for (var i = 0; i < half; i++) { 85 | // Clip at zero, copy to temp array 86 | if (processed[i] < 0.0) 87 | processed[i] = 0.0; 88 | out[i] = processed[i]; 89 | // Subtract a time-doubled signal (linearly interp.) from the original 90 | // (clipped) signal 91 | if ((i % 2) == 0) 92 | processed[i] -= out[i / 2]; 93 | else 94 | processed[i] -= ((out[i / 2] + out[i / 2 + 1]) / 2); 95 | 96 | // Clip at zero again 97 | if (processed[i] < 0.0) 98 | processed[i] = 0.0; 99 | } 100 | 101 | // Reverse and scale 102 | for (var i = 0; i < half; i++) 103 | inData[i] = processed[i] / (windowSize / 4); 104 | for (var i = 0; i < half; i++) 105 | processed[half - 1 - i] = inData[i]; 106 | } else { 107 | // Convert to decibels 108 | // But do it safely; -Inf is nobody's friend 109 | for (var i = 0; i < half; i++){ 110 | var temp=(processed[i] / windowSize / windows); 111 | if (temp > 0.0) 112 | processed[i] = 10 * Math.log(temp) / Math.LN10; 113 | else 114 | processed[i] = 0; 115 | } 116 | } 117 | 118 | for (var i = 0; i < half; i++) 119 | output[i] = processed[i]; 120 | 121 | 122 | return true; 123 | } -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-tab.js: -------------------------------------------------------------------------------- 1 | /* ======================================================== 2 | * bootstrap-tab.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#tabs 4 | * ======================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ======================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* TAB CLASS DEFINITION 27 | * ==================== */ 28 | 29 | var Tab = function ( element ) { 30 | this.element = $(element) 31 | } 32 | 33 | Tab.prototype = { 34 | 35 | constructor: Tab 36 | 37 | , show: function () { 38 | var $this = this.element 39 | , $ul = $this.closest('ul:not(.dropdown-menu)') 40 | , selector = $this.attr('data-target') 41 | , previous 42 | , $target 43 | , e 44 | 45 | if (!selector) { 46 | selector = $this.attr('href') 47 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 48 | } 49 | 50 | if ( $this.parent('li').hasClass('active') ) return 51 | 52 | previous = $ul.find('.active a').last()[0] 53 | 54 | e = $.Event('show', { 55 | relatedTarget: previous 56 | }) 57 | 58 | $this.trigger(e) 59 | 60 | if (e.isDefaultPrevented()) return 61 | 62 | $target = $(selector) 63 | 64 | this.activate($this.parent('li'), $ul) 65 | this.activate($target, $target.parent(), function () { 66 | $this.trigger({ 67 | type: 'shown' 68 | , relatedTarget: previous 69 | }) 70 | }) 71 | } 72 | 73 | , activate: function ( element, container, callback) { 74 | var $active = container.find('> .active') 75 | , transition = callback 76 | && $.support.transition 77 | && $active.hasClass('fade') 78 | 79 | function next() { 80 | $active 81 | .removeClass('active') 82 | .find('> .dropdown-menu > .active') 83 | .removeClass('active') 84 | 85 | element.addClass('active') 86 | 87 | if (transition) { 88 | element[0].offsetWidth // reflow for transition 89 | element.addClass('in') 90 | } else { 91 | element.removeClass('fade') 92 | } 93 | 94 | if ( element.parent('.dropdown-menu') ) { 95 | element.closest('li.dropdown').addClass('active') 96 | } 97 | 98 | callback && callback() 99 | } 100 | 101 | transition ? 102 | $active.one($.support.transition.end, next) : 103 | next() 104 | 105 | $active.removeClass('in') 106 | } 107 | } 108 | 109 | 110 | /* TAB PLUGIN DEFINITION 111 | * ===================== */ 112 | 113 | $.fn.tab = function ( option ) { 114 | return this.each(function () { 115 | var $this = $(this) 116 | , data = $this.data('tab') 117 | if (!data) $this.data('tab', (data = new Tab(this))) 118 | if (typeof option == 'string') data[option]() 119 | }) 120 | } 121 | 122 | $.fn.tab.Constructor = Tab 123 | 124 | 125 | /* TAB DATA-API 126 | * ============ */ 127 | 128 | $(function () { 129 | $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { 130 | e.preventDefault() 131 | $(this).tab('show') 132 | }) 133 | }) 134 | 135 | }(window.jQuery); -------------------------------------------------------------------------------- /app/js/ACFIRFilter.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | /// 3 | /// General FIR digital filter routines with MMX optimization. 4 | /// 5 | /// Note : MMX optimized functions reside in a separate, platform-specific file, 6 | /// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp' 7 | /// 8 | /// Author : Copyright (c) Olli Parviainen 9 | /// Author e-mail : oparviai 'at' iki.fi 10 | /// SoundTouch WWW: http://www.surina.net/soundtouch 11 | /// 12 | //////////////////////////////////////////////////////////////////////////////// 13 | // 14 | // Last changed : $Date: 2006-09-18 22:29:22 $ 15 | // File revision : $Revision: 1.4 $ 16 | // 17 | // $Id: FIRFilter.cpp,v 1.4 2006-09-18 22:29:22 martynshaw Exp $ 18 | // 19 | //////////////////////////////////////////////////////////////////////////////// 20 | // 21 | // License : 22 | // 23 | // SoundTouch audio processing library 24 | // Copyright (c) Olli Parviainen 25 | // 26 | // This library is free software; you can redistribute it and/or 27 | // modify it under the terms of the GNU Lesser General Public 28 | // License as published by the Free Software Foundation; either 29 | // version 2.1 of the License, or (at your option) any later version. 30 | // 31 | // This library is distributed in the hope that it will be useful, 32 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 34 | // Lesser General Public License for more details. 35 | // 36 | // You should have received a copy of the GNU Lesser General Public 37 | // License along with this library; if not, write to the Free Software 38 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 39 | // 40 | //////////////////////////////////////////////////////////////////////////////// 41 | 42 | 43 | /***************************************************************************** 44 | * 45 | * Implementation of the class 'FIRFilter' 46 | * 47 | *****************************************************************************/ 48 | 49 | function ACFIRFilter() 50 | { 51 | this.resultDivFactor = 0; 52 | this.length = 0; 53 | this.lengthDiv8 = 0; 54 | this.filterCoeffs = undefined; 55 | this.resultDivider = 0; 56 | 57 | this.evaluateFilter = function(dest, src, numSamples) 58 | { 59 | var i, j, end; 60 | var sum; 61 | var dScaler = 1.0 / this.resultDivider; 62 | 63 | 64 | if (this.length === 0) debugger; 65 | 66 | end = numSamples - this.length; 67 | var srcPos = 0; 68 | for (var j = 0; j < end; j ++) 69 | { 70 | sum = 0; 71 | for (var i = 0; i < this.length; i += 4) 72 | { 73 | // loop is unrolled by factor of 4 here for efficiency 74 | sum += src[srcPos + i + 0] * this.filterCoeffs[i + 0] + 75 | src[srcPos + i + 1] * this.filterCoeffs[i + 1] + 76 | src[srcPos + i + 2] * this.filterCoeffs[i + 2] + 77 | src[srcPos + i + 3] * this.filterCoeffs[i + 3]; 78 | } 79 | 80 | sum *= dScaler; 81 | 82 | dest[j] = sum; 83 | srcPos++; 84 | } 85 | return end; 86 | } 87 | 88 | this.setCoefficients = function setCoefficients(coeffs, newLength, uResultDivFactor) 89 | { 90 | if (newLength === 0) debugger; 91 | if (newLength % 8) throw ("FIR filter length not divisible by 8"); 92 | 93 | this.lengthDiv8 = newLength / 8; 94 | this.length = this.lengthDiv8 * 8; 95 | if (this.length !== newLength) debugger; 96 | 97 | this.resultDivFactor = uResultDivFactor; 98 | this.resultDivider = Math.pow(2., this.resultDivFactor); 99 | 100 | this.filterCoeffs = new Float32Array(this.length); 101 | for (var i = 0; i < this.filterCoeffs.length; ++i) 102 | { 103 | this.filterCoeffs[i] = coeffs[i]; 104 | } 105 | } 106 | 107 | this.getLength = function getLength() 108 | { 109 | return this.length; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-scrollspy.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-scrollspy.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#scrollspy 4 | * ============================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* SCROLLSPY CLASS DEFINITION 27 | * ========================== */ 28 | 29 | function ScrollSpy( element, options) { 30 | var process = $.proxy(this.process, this) 31 | , $element = $(element).is('body') ? $(window) : $(element) 32 | , href 33 | this.options = $.extend({}, $.fn.scrollspy.defaults, options) 34 | this.$scrollElement = $element.on('scroll.scroll.data-api', process) 35 | this.selector = (this.options.target 36 | || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 37 | || '') + ' .nav li > a' 38 | this.$body = $('body') 39 | this.refresh() 40 | this.process() 41 | } 42 | 43 | ScrollSpy.prototype = { 44 | 45 | constructor: ScrollSpy 46 | 47 | , refresh: function () { 48 | var self = this 49 | , $targets 50 | 51 | this.offsets = $([]) 52 | this.targets = $([]) 53 | 54 | $targets = this.$body 55 | .find(this.selector) 56 | .map(function () { 57 | var $el = $(this) 58 | , href = $el.data('target') || $el.attr('href') 59 | , $href = /^#\w/.test(href) && $(href) 60 | return ( $href 61 | && href.length 62 | && [[ $href.position().top, href ]] ) || null 63 | }) 64 | .sort(function (a, b) { return a[0] - b[0] }) 65 | .each(function () { 66 | self.offsets.push(this[0]) 67 | self.targets.push(this[1]) 68 | }) 69 | } 70 | 71 | , process: function () { 72 | var scrollTop = this.$scrollElement.scrollTop() + this.options.offset 73 | , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight 74 | , maxScroll = scrollHeight - this.$scrollElement.height() 75 | , offsets = this.offsets 76 | , targets = this.targets 77 | , activeTarget = this.activeTarget 78 | , i 79 | 80 | if (scrollTop >= maxScroll) { 81 | return activeTarget != (i = targets.last()[0]) 82 | && this.activate ( i ) 83 | } 84 | 85 | for (i = offsets.length; i--;) { 86 | activeTarget != targets[i] 87 | && scrollTop >= offsets[i] 88 | && (!offsets[i + 1] || scrollTop <= offsets[i + 1]) 89 | && this.activate( targets[i] ) 90 | } 91 | } 92 | 93 | , activate: function (target) { 94 | var active 95 | , selector 96 | 97 | this.activeTarget = target 98 | 99 | $(this.selector) 100 | .parent('.active') 101 | .removeClass('active') 102 | 103 | selector = this.selector 104 | + '[data-target="' + target + '"],' 105 | + this.selector + '[href="' + target + '"]' 106 | 107 | active = $(selector) 108 | .parent('li') 109 | .addClass('active') 110 | 111 | if (active.parent('.dropdown-menu')) { 112 | active = active.closest('li.dropdown').addClass('active') 113 | } 114 | 115 | active.trigger('activate') 116 | } 117 | 118 | } 119 | 120 | 121 | /* SCROLLSPY PLUGIN DEFINITION 122 | * =========================== */ 123 | 124 | $.fn.scrollspy = function ( option ) { 125 | return this.each(function () { 126 | var $this = $(this) 127 | , data = $this.data('scrollspy') 128 | , options = typeof option == 'object' && option 129 | if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options))) 130 | if (typeof option == 'string') data[option]() 131 | }) 132 | } 133 | 134 | $.fn.scrollspy.Constructor = ScrollSpy 135 | 136 | $.fn.scrollspy.defaults = { 137 | offset: 10 138 | } 139 | 140 | 141 | /* SCROLLSPY DATA-API 142 | * ================== */ 143 | 144 | $(function () { 145 | $('[data-spy="scroll"]').each(function () { 146 | var $spy = $(this) 147 | $spy.scrollspy($spy.data()) 148 | }) 149 | }) 150 | 151 | }(window.jQuery); -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-collapse.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-collapse.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#collapse 4 | * ============================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* COLLAPSE PUBLIC CLASS DEFINITION 27 | * ================================ */ 28 | 29 | var Collapse = function (element, options) { 30 | this.$element = $(element) 31 | this.options = $.extend({}, $.fn.collapse.defaults, options) 32 | 33 | if (this.options.parent) { 34 | this.$parent = $(this.options.parent) 35 | } 36 | 37 | this.options.toggle && this.toggle() 38 | } 39 | 40 | Collapse.prototype = { 41 | 42 | constructor: Collapse 43 | 44 | , dimension: function () { 45 | var hasWidth = this.$element.hasClass('width') 46 | return hasWidth ? 'width' : 'height' 47 | } 48 | 49 | , show: function () { 50 | var dimension 51 | , scroll 52 | , actives 53 | , hasData 54 | 55 | if (this.transitioning) return 56 | 57 | dimension = this.dimension() 58 | scroll = $.camelCase(['scroll', dimension].join('-')) 59 | actives = this.$parent && this.$parent.find('> .accordion-group > .in') 60 | 61 | if (actives && actives.length) { 62 | hasData = actives.data('collapse') 63 | if (hasData && hasData.transitioning) return 64 | actives.collapse('hide') 65 | hasData || actives.data('collapse', null) 66 | } 67 | 68 | this.$element[dimension](0) 69 | this.transition('addClass', $.Event('show'), 'shown') 70 | this.$element[dimension](this.$element[0][scroll]) 71 | } 72 | 73 | , hide: function () { 74 | var dimension 75 | if (this.transitioning) return 76 | dimension = this.dimension() 77 | this.reset(this.$element[dimension]()) 78 | this.transition('removeClass', $.Event('hide'), 'hidden') 79 | this.$element[dimension](0) 80 | } 81 | 82 | , reset: function (size) { 83 | var dimension = this.dimension() 84 | 85 | this.$element 86 | .removeClass('collapse') 87 | [dimension](size || 'auto') 88 | [0].offsetWidth 89 | 90 | this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') 91 | 92 | return this 93 | } 94 | 95 | , transition: function (method, startEvent, completeEvent) { 96 | var that = this 97 | , complete = function () { 98 | if (startEvent.type == 'show') that.reset() 99 | that.transitioning = 0 100 | that.$element.trigger(completeEvent) 101 | } 102 | 103 | this.$element.trigger(startEvent) 104 | 105 | if (startEvent.isDefaultPrevented()) return 106 | 107 | this.transitioning = 1 108 | 109 | this.$element[method]('in') 110 | 111 | $.support.transition && this.$element.hasClass('collapse') ? 112 | this.$element.one($.support.transition.end, complete) : 113 | complete() 114 | } 115 | 116 | , toggle: function () { 117 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 118 | } 119 | 120 | } 121 | 122 | 123 | /* COLLAPSIBLE PLUGIN DEFINITION 124 | * ============================== */ 125 | 126 | $.fn.collapse = function (option) { 127 | return this.each(function () { 128 | var $this = $(this) 129 | , data = $this.data('collapse') 130 | , options = typeof option == 'object' && option 131 | if (!data) $this.data('collapse', (data = new Collapse(this, options))) 132 | if (typeof option == 'string') data[option]() 133 | }) 134 | } 135 | 136 | $.fn.collapse.defaults = { 137 | toggle: true 138 | } 139 | 140 | $.fn.collapse.Constructor = Collapse 141 | 142 | 143 | /* COLLAPSIBLE DATA-API 144 | * ==================== */ 145 | 146 | $(function () { 147 | $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { 148 | var $this = $(this), href 149 | , target = $this.attr('data-target') 150 | || e.preventDefault() 151 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 152 | , option = $(target).data('collapse') ? 'toggle' : $this.data() 153 | $(target).collapse(option) 154 | }) 155 | }) 156 | 157 | }(window.jQuery); -------------------------------------------------------------------------------- /app/js/ACAAFilter.js: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////////////////////////////////////////////////////// 3 | /// 4 | /// FIR low-pass (anti-alias) filter with filter coefficient design routine and 5 | /// MMX optimization. 6 | /// 7 | /// Anti-alias filter is used to prevent folding of high frequencies when 8 | /// transposing the sample rate with interpolation. 9 | /// 10 | /// Author : Copyright (c) Olli Parviainen 11 | /// Author e-mail : oparviai 'at' iki.fi 12 | /// SoundTouch WWW: http://www.surina.net/soundtouch 13 | /// 14 | //////////////////////////////////////////////////////////////////////////////// 15 | // 16 | // Last changed : $Date: 2006-09-18 22:29:22 $ 17 | // File revision : $Revision: 1.4 $ 18 | // 19 | // $Id: AAFilter.cpp,v 1.4 2006-09-18 22:29:22 martynshaw Exp $ 20 | // 21 | //////////////////////////////////////////////////////////////////////////////// 22 | // 23 | // License : 24 | // 25 | // SoundTouch audio processing library 26 | // Copyright (c) Olli Parviainen 27 | // 28 | // This library is free software; you can redistribute it and/or 29 | // modify it under the terms of the GNU Lesser General Public 30 | // License as published by the Free Software Foundation; either 31 | // version 2.1 of the License, or (at your option) any later version. 32 | // 33 | // This library is distributed in the hope that it will be useful, 34 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 35 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 36 | // Lesser General Public License for more details. 37 | // 38 | // You should have received a copy of the GNU Lesser General Public 39 | // License along with this library; if not, write to the Free Software 40 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 41 | // 42 | //////////////////////////////////////////////////////////////////////////////// 43 | 44 | function ACAAFilter(length) 45 | { 46 | if (length === undefined) length = 32; 47 | 48 | this.pFIR = new ACFIRFilter(); 49 | this.cutoffFreq = 0.9; 50 | this.length = 0; 51 | 52 | this.setCutoffFreq = function setCutoffFreq(newCutoffFreq) 53 | { 54 | this.cutoffFreq = newCutoffFreq; 55 | this.calculateCoeffs(); 56 | }; 57 | 58 | this.setLength = function setLength(newLength) 59 | { 60 | this.length = newLength; 61 | this.calculateCoeffs(); 62 | }; 63 | 64 | this.calculateCoeffs = function calculateCoeffs() 65 | { 66 | var i; 67 | var cntTemp, temp, tempCoeff,h, w; 68 | var fc2, wc; 69 | var scaleCoeff, sum; 70 | var work; 71 | var coeffs; 72 | 73 | if (this.length <= 0 || this.length % 4 != 0 || this.cutoffFreq < 0 || this.cutoffFreq > 1.5) debugger; 74 | 75 | work = new Float32Array(this.length); 76 | this.coeffs = new Float32Array(this.length); 77 | 78 | fc2 = 2.0 * this.cutoffFreq; 79 | wc = Math.PI * fc2; 80 | tempCoeff = Math.PI * 2 / length; 81 | 82 | sum = 0; 83 | for (i = 0; i < this.length; i ++) 84 | { 85 | cntTemp = i - (this.length / 2); 86 | 87 | temp = cntTemp * wc; 88 | if (temp != 0) 89 | { 90 | h = fc2 * Math.sin(temp) / temp; // sinc function 91 | } 92 | else 93 | { 94 | h = 1.0; 95 | } 96 | w = 0.54 + 0.46 * Math.cos(tempCoeff * cntTemp); // hamming window 97 | 98 | temp = w * h; 99 | work[i] = temp; 100 | 101 | // calc net sum of coefficients 102 | sum += temp; 103 | } 104 | 105 | // ensure the sum of coefficients is larger than zero 106 | /* assert(sum > 0); 107 | 108 | // ensure we've really designed a lowpass filter... 109 | assert(work[length/2] > 0); 110 | assert(work[length/2 + 1] > -1e-6); 111 | assert(work[length/2 - 1] > -1e-6); 112 | */ 113 | // Calculate a scaling coefficient in such a way that the result can be 114 | // divided by 16384 115 | scaleCoeff = 16384.0 / sum; 116 | 117 | for (var i = 0; i < this.length; i ++) 118 | { 119 | // scale & round to nearest integer 120 | temp = work[i] * scaleCoeff; 121 | temp += (temp >= 0) ? 0.5 : -0.5; 122 | // ensure no overfloods 123 | if (temp < -32768 || temp > 32767) debugger; 124 | this.coeffs[i] = temp; 125 | } 126 | 127 | // Set coefficients. Use divide factor 14 => divide result by 2^14 = 16384 128 | this.pFIR.setCoefficients(this.coeffs, this.length, 14); 129 | } 130 | 131 | this.evaluate = function evaluate(dest, src, numSamples) 132 | { 133 | return this.pFIR.evaluateFilter(dest, src, numSamples); 134 | }; 135 | 136 | this.getLength = function getLength() 137 | { 138 | return this.pFIR.getLength(); 139 | }; 140 | 141 | this.setLength(length); 142 | } 143 | 144 | -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-carousel.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-carousel.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#carousel 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* CAROUSEL CLASS DEFINITION 27 | * ========================= */ 28 | 29 | var Carousel = function (element, options) { 30 | this.$element = $(element) 31 | this.options = options 32 | this.options.slide && this.slide(this.options.slide) 33 | this.options.pause == 'hover' && this.$element 34 | .on('mouseenter', $.proxy(this.pause, this)) 35 | .on('mouseleave', $.proxy(this.cycle, this)) 36 | } 37 | 38 | Carousel.prototype = { 39 | 40 | cycle: function (e) { 41 | if (!e) this.paused = false 42 | this.options.interval 43 | && !this.paused 44 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 45 | return this 46 | } 47 | 48 | , to: function (pos) { 49 | var $active = this.$element.find('.active') 50 | , children = $active.parent().children() 51 | , activePos = children.index($active) 52 | , that = this 53 | 54 | if (pos > (children.length - 1) || pos < 0) return 55 | 56 | if (this.sliding) { 57 | return this.$element.one('slid', function () { 58 | that.to(pos) 59 | }) 60 | } 61 | 62 | if (activePos == pos) { 63 | return this.pause().cycle() 64 | } 65 | 66 | return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) 67 | } 68 | 69 | , pause: function (e) { 70 | if (!e) this.paused = true 71 | clearInterval(this.interval) 72 | this.interval = null 73 | return this 74 | } 75 | 76 | , next: function () { 77 | if (this.sliding) return 78 | return this.slide('next') 79 | } 80 | 81 | , prev: function () { 82 | if (this.sliding) return 83 | return this.slide('prev') 84 | } 85 | 86 | , slide: function (type, next) { 87 | var $active = this.$element.find('.active') 88 | , $next = next || $active[type]() 89 | , isCycling = this.interval 90 | , direction = type == 'next' ? 'left' : 'right' 91 | , fallback = type == 'next' ? 'first' : 'last' 92 | , that = this 93 | , e = $.Event('slide') 94 | 95 | this.sliding = true 96 | 97 | isCycling && this.pause() 98 | 99 | $next = $next.length ? $next : this.$element.find('.item')[fallback]() 100 | 101 | if ($next.hasClass('active')) return 102 | 103 | if ($.support.transition && this.$element.hasClass('slide')) { 104 | this.$element.trigger(e) 105 | if (e.isDefaultPrevented()) return 106 | $next.addClass(type) 107 | $next[0].offsetWidth // force reflow 108 | $active.addClass(direction) 109 | $next.addClass(direction) 110 | this.$element.one($.support.transition.end, function () { 111 | $next.removeClass([type, direction].join(' ')).addClass('active') 112 | $active.removeClass(['active', direction].join(' ')) 113 | that.sliding = false 114 | setTimeout(function () { that.$element.trigger('slid') }, 0) 115 | }) 116 | } else { 117 | this.$element.trigger(e) 118 | if (e.isDefaultPrevented()) return 119 | $active.removeClass('active') 120 | $next.addClass('active') 121 | this.sliding = false 122 | this.$element.trigger('slid') 123 | } 124 | 125 | isCycling && this.cycle() 126 | 127 | return this 128 | } 129 | 130 | } 131 | 132 | 133 | /* CAROUSEL PLUGIN DEFINITION 134 | * ========================== */ 135 | 136 | $.fn.carousel = function (option) { 137 | return this.each(function () { 138 | var $this = $(this) 139 | , data = $this.data('carousel') 140 | , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) 141 | if (!data) $this.data('carousel', (data = new Carousel(this, options))) 142 | if (typeof option == 'number') data.to(option) 143 | else if (typeof option == 'string' || (option = options.slide)) data[option]() 144 | else if (options.interval) data.cycle() 145 | }) 146 | } 147 | 148 | $.fn.carousel.defaults = { 149 | interval: 5000 150 | , pause: 'hover' 151 | } 152 | 153 | $.fn.carousel.Constructor = Carousel 154 | 155 | 156 | /* CAROUSEL DATA-API 157 | * ================= */ 158 | 159 | $(function () { 160 | $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) { 161 | var $this = $(this), href 162 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 163 | , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data()) 164 | $target.carousel(options) 165 | e.preventDefault() 166 | }) 167 | }) 168 | 169 | }(window.jQuery); -------------------------------------------------------------------------------- /app/js/binarytoolkit.js: -------------------------------------------------------------------------------- 1 | /* BinaryToolkit written by Rainer Heynke */ 2 | window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; 3 | window.BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder; 4 | window.URL = window.URL || window.webkitURL; 5 | 6 | 7 | function BinaryReader(data) 8 | { 9 | this.data = new Uint8Array(data); 10 | this.pos = 0; 11 | 12 | this.signMasks = [ 0x0, 0x80, 0x8000, 0x800000, 0x80000000 ]; 13 | this.masks = [ 0x0, 0xFF + 1, 0xFFFF + 1, 0xFFFFFF + 1, 0xFFFFFFFF + 1 ]; 14 | 15 | this.gotoString = function gotoString(value) 16 | { 17 | for(var i = this.pos; i < this.data.length; ++i) 18 | { 19 | if (value[0] == String.fromCharCode(this.data[i])) 20 | { 21 | var complete = true; 22 | for (var j = i; j < value.length + i; ++j) 23 | { 24 | if (value[j - i] != String.fromCharCode(this.data[j])) 25 | { 26 | complete = false; 27 | break; 28 | } 29 | } 30 | 31 | if (complete == true) 32 | { 33 | this.pos = i; 34 | break; 35 | } 36 | } 37 | } 38 | } 39 | 40 | this.readUInt8 = function readUInt8(bigEndian) 41 | { 42 | return this.readInteger(1, false, bigEndian); 43 | }; 44 | 45 | this.readInt8 = function readInt8(bigEndian) 46 | { 47 | return this.readInteger(1, true, bigEndian); 48 | }; 49 | 50 | this.readUInt16 = function readUInt16(bigEndian) 51 | { 52 | return this.readInteger(2, false, bigEndian); 53 | }; 54 | 55 | this.readInt16 = function readInt16(bigEndian) 56 | { 57 | return this.readInteger(2, true, bigEndian); 58 | }; 59 | 60 | this.readUInt32 = function readUInt32(bigEndian) 61 | { 62 | return this.readInteger(4, false, bigEndian); 63 | }; 64 | 65 | this.readInt32 = function readInt32(bigEndian) 66 | { 67 | return this.readInteger(4, true, bigEndian); 68 | }; 69 | 70 | this.readString = function readString(size) 71 | { 72 | var r = ""; 73 | var i = 0; 74 | 75 | for(i = 0; i < size; ++i) 76 | { 77 | r += String.fromCharCode(this.data[this.pos++]); 78 | } 79 | return r; 80 | }; 81 | 82 | /* size = size in bytes (e.g. 1 = 8 bits, ...) 83 | * signed = boolean flag to define if the value is signed 84 | * bigEndian = boolean flag to define the decoding in big endian style 85 | */ 86 | this.readInteger = function readInteger(size, signed, bigEndian) 87 | { 88 | if (this.pos + (size - 1) >= this.data.length) throw "Buffer overflow during reading."; 89 | 90 | var i = 0; 91 | var r = 0; 92 | 93 | // read the bytes 94 | for(i = 0; i < size; ++i) 95 | { 96 | if (bigEndian === true) 97 | { 98 | r = this.data[this.pos++] + (r << (i * 8)); 99 | } 100 | else 101 | { 102 | r += (this.data[this.pos++] << (i * 8)); 103 | } 104 | } 105 | 106 | // convert from unsigned to signed 107 | if (signed && r & this.signMasks[size]) 108 | { 109 | r = r - this.masks[size]; 110 | } 111 | 112 | return r; 113 | }; 114 | 115 | this.eof = function eof() 116 | { 117 | return (this.data.length >= this.pos); 118 | }; 119 | } 120 | 121 | function BinaryWriter(estimatedSize) 122 | { 123 | this.estimatedSize = estimatedSize; 124 | this.pos = 0; 125 | this.data = new Uint8Array(estimatedSize); 126 | 127 | this.masks = [ 0x0, 0xFF + 1, 0xFFFF + 1, 0xFFFFFF + 1, 0xFFFFFFFF + 1 ]; 128 | 129 | this.writeUInt8 = function writeUInt8(value, bigEndian) 130 | { 131 | return this.writeInteger(value, 1, bigEndian); 132 | }; 133 | 134 | this.writeInt8 = function writeInt8(value, bigEndian) 135 | { 136 | return this.writeInteger(value, 1, bigEndian); 137 | }; 138 | 139 | this.writeUInt16 = function writeUInt16(value, bigEndian) 140 | { 141 | return this.writeInteger(value, 2, bigEndian); 142 | }; 143 | 144 | this.writeInt16 = function writeInt16(value, bigEndian) 145 | { 146 | return this.writeInteger(value, 2, bigEndian); 147 | }; 148 | 149 | this.writeUInt32 = function writeUInt32(value, bigEndian) 150 | { 151 | return this.writeInteger(value, 4, bigEndian); 152 | }; 153 | 154 | this.writeInt32 = function writeInt32(value, bigEndian) 155 | { 156 | return this.writeInteger(value, 4, bigEndian); 157 | }; 158 | 159 | this.writeString = function writeString(value) 160 | { 161 | var i = 0; 162 | for(i = 0; i < value.length; ++i) 163 | { 164 | this.data[this.pos++] = value.charCodeAt(i); 165 | } 166 | }; 167 | 168 | /* value = the actual value which want to get stored 169 | * size = size in bytes of the value 170 | * bigEndian = flag to store the number in big endian style 171 | */ 172 | this.writeInteger = function writeInteger(value, size, bigEndian) 173 | { 174 | var r = value; 175 | var i = 0; 176 | 177 | // convert to unsigned if value is negative 178 | if (value < 0) 179 | { 180 | r += this.masks[size]; 181 | } 182 | 183 | // write the bytes 184 | for(i = 0; i < size; ++i) 185 | { 186 | if (bigEndian === true) 187 | { 188 | this.data[this.pos++] = (r >> ((size - i - 1) * 8)) & 0xFF; 189 | } 190 | else 191 | { 192 | this.data[this.pos++] = (r >> (i * 8)) & 0xFF; 193 | } 194 | } 195 | }; 196 | } -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-modal.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#modals 4 | * ========================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================= */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* MODAL CLASS DEFINITION 27 | * ====================== */ 28 | 29 | var Modal = function (content, options) { 30 | this.options = options 31 | this.$element = $(content) 32 | .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) 33 | } 34 | 35 | Modal.prototype = { 36 | 37 | constructor: Modal 38 | 39 | , toggle: function () { 40 | return this[!this.isShown ? 'show' : 'hide']() 41 | } 42 | 43 | , show: function () { 44 | var that = this 45 | , e = $.Event('show') 46 | 47 | this.$element.trigger(e) 48 | 49 | if (this.isShown || e.isDefaultPrevented()) return 50 | 51 | $('body').addClass('modal-open') 52 | 53 | this.isShown = true 54 | 55 | escape.call(this) 56 | backdrop.call(this, function () { 57 | var transition = $.support.transition && that.$element.hasClass('fade') 58 | 59 | if (!that.$element.parent().length) { 60 | that.$element.appendTo(document.body) //don't move modals dom position 61 | } 62 | 63 | that.$element 64 | .show() 65 | 66 | if (transition) { 67 | that.$element[0].offsetWidth // force reflow 68 | } 69 | 70 | that.$element.addClass('in') 71 | 72 | transition ? 73 | that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : 74 | that.$element.trigger('shown') 75 | 76 | }) 77 | } 78 | 79 | , hide: function (e) { 80 | e && e.preventDefault() 81 | 82 | var that = this 83 | 84 | e = $.Event('hide') 85 | 86 | this.$element.trigger(e) 87 | 88 | if (!this.isShown || e.isDefaultPrevented()) return 89 | 90 | this.isShown = false 91 | 92 | $('body').removeClass('modal-open') 93 | 94 | escape.call(this) 95 | 96 | this.$element.removeClass('in') 97 | 98 | $.support.transition && this.$element.hasClass('fade') ? 99 | hideWithTransition.call(this) : 100 | hideModal.call(this) 101 | } 102 | 103 | } 104 | 105 | 106 | /* MODAL PRIVATE METHODS 107 | * ===================== */ 108 | 109 | function hideWithTransition() { 110 | var that = this 111 | , timeout = setTimeout(function () { 112 | that.$element.off($.support.transition.end) 113 | hideModal.call(that) 114 | }, 500) 115 | 116 | this.$element.one($.support.transition.end, function () { 117 | clearTimeout(timeout) 118 | hideModal.call(that) 119 | }) 120 | } 121 | 122 | function hideModal(that) { 123 | this.$element 124 | .hide() 125 | .trigger('hidden') 126 | 127 | backdrop.call(this) 128 | } 129 | 130 | function backdrop(callback) { 131 | var that = this 132 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 133 | 134 | if (this.isShown && this.options.backdrop) { 135 | var doAnimate = $.support.transition && animate 136 | 137 | this.$backdrop = $('') 138 | .appendTo(document.body) 139 | 140 | if (this.options.backdrop != 'static') { 141 | this.$backdrop.click($.proxy(this.hide, this)) 142 | } 143 | 144 | if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 145 | 146 | this.$backdrop.addClass('in') 147 | 148 | doAnimate ? 149 | this.$backdrop.one($.support.transition.end, callback) : 150 | callback() 151 | 152 | } else if (!this.isShown && this.$backdrop) { 153 | this.$backdrop.removeClass('in') 154 | 155 | $.support.transition && this.$element.hasClass('fade')? 156 | this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) : 157 | removeBackdrop.call(this) 158 | 159 | } else if (callback) { 160 | callback() 161 | } 162 | } 163 | 164 | function removeBackdrop() { 165 | this.$backdrop.remove() 166 | this.$backdrop = null 167 | } 168 | 169 | function escape() { 170 | var that = this 171 | if (this.isShown && this.options.keyboard) { 172 | $(document).on('keyup.dismiss.modal', function ( e ) { 173 | e.which == 27 && that.hide() 174 | }) 175 | } else if (!this.isShown) { 176 | $(document).off('keyup.dismiss.modal') 177 | } 178 | } 179 | 180 | 181 | /* MODAL PLUGIN DEFINITION 182 | * ======================= */ 183 | 184 | $.fn.modal = function (option) { 185 | return this.each(function () { 186 | var $this = $(this) 187 | , data = $this.data('modal') 188 | , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option) 189 | if (!data) $this.data('modal', (data = new Modal(this, options))) 190 | if (typeof option == 'string') data[option]() 191 | else if (options.show) data.show() 192 | }) 193 | } 194 | 195 | $.fn.modal.defaults = { 196 | backdrop: true 197 | , keyboard: true 198 | , show: true 199 | } 200 | 201 | $.fn.modal.Constructor = Modal 202 | 203 | 204 | /* MODAL DATA-API 205 | * ============== */ 206 | 207 | $(function () { 208 | $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) { 209 | var $this = $(this), href 210 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 211 | , option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data()) 212 | 213 | e.preventDefault() 214 | $target.modal(option) 215 | }) 216 | }) 217 | 218 | }(window.jQuery); -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-typeahead.js: -------------------------------------------------------------------------------- 1 | /* ============================================================= 2 | * bootstrap-typeahead.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#typeahead 4 | * ============================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function($){ 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* TYPEAHEAD PUBLIC CLASS DEFINITION 27 | * ================================= */ 28 | 29 | var Typeahead = function (element, options) { 30 | this.$element = $(element) 31 | this.options = $.extend({}, $.fn.typeahead.defaults, options) 32 | this.matcher = this.options.matcher || this.matcher 33 | this.sorter = this.options.sorter || this.sorter 34 | this.highlighter = this.options.highlighter || this.highlighter 35 | this.updater = this.options.updater || this.updater 36 | this.$menu = $(this.options.menu).appendTo('body') 37 | this.source = this.options.source 38 | this.shown = false 39 | this.listen() 40 | } 41 | 42 | Typeahead.prototype = { 43 | 44 | constructor: Typeahead 45 | 46 | , select: function () { 47 | var val = this.$menu.find('.active').attr('data-value') 48 | this.$element 49 | .val(this.updater(val)) 50 | .change() 51 | return this.hide() 52 | } 53 | 54 | , updater: function (item) { 55 | return item 56 | } 57 | 58 | , show: function () { 59 | var pos = $.extend({}, this.$element.offset(), { 60 | height: this.$element[0].offsetHeight 61 | }) 62 | 63 | this.$menu.css({ 64 | top: pos.top + pos.height 65 | , left: pos.left 66 | }) 67 | 68 | this.$menu.show() 69 | this.shown = true 70 | return this 71 | } 72 | 73 | , hide: function () { 74 | this.$menu.hide() 75 | this.shown = false 76 | return this 77 | } 78 | 79 | , lookup: function (event) { 80 | var that = this 81 | , items 82 | , q 83 | 84 | this.query = this.$element.val() 85 | 86 | if (!this.query) { 87 | return this.shown ? this.hide() : this 88 | } 89 | 90 | items = $.grep(this.source, function (item) { 91 | return that.matcher(item) 92 | }) 93 | 94 | items = this.sorter(items) 95 | 96 | if (!items.length) { 97 | return this.shown ? this.hide() : this 98 | } 99 | 100 | return this.render(items.slice(0, this.options.items)).show() 101 | } 102 | 103 | , matcher: function (item) { 104 | return ~item.toLowerCase().indexOf(this.query.toLowerCase()) 105 | } 106 | 107 | , sorter: function (items) { 108 | var beginswith = [] 109 | , caseSensitive = [] 110 | , caseInsensitive = [] 111 | , item 112 | 113 | while (item = items.shift()) { 114 | if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item) 115 | else if (~item.indexOf(this.query)) caseSensitive.push(item) 116 | else caseInsensitive.push(item) 117 | } 118 | 119 | return beginswith.concat(caseSensitive, caseInsensitive) 120 | } 121 | 122 | , highlighter: function (item) { 123 | var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&') 124 | return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) { 125 | return '' + match + '' 126 | }) 127 | } 128 | 129 | , render: function (items) { 130 | var that = this 131 | 132 | items = $(items).map(function (i, item) { 133 | i = $(that.options.item).attr('data-value', item) 134 | i.find('a').html(that.highlighter(item)) 135 | return i[0] 136 | }) 137 | 138 | items.first().addClass('active') 139 | this.$menu.html(items) 140 | return this 141 | } 142 | 143 | , next: function (event) { 144 | var active = this.$menu.find('.active').removeClass('active') 145 | , next = active.next() 146 | 147 | if (!next.length) { 148 | next = $(this.$menu.find('li')[0]) 149 | } 150 | 151 | next.addClass('active') 152 | } 153 | 154 | , prev: function (event) { 155 | var active = this.$menu.find('.active').removeClass('active') 156 | , prev = active.prev() 157 | 158 | if (!prev.length) { 159 | prev = this.$menu.find('li').last() 160 | } 161 | 162 | prev.addClass('active') 163 | } 164 | 165 | , listen: function () { 166 | this.$element 167 | .on('blur', $.proxy(this.blur, this)) 168 | .on('keypress', $.proxy(this.keypress, this)) 169 | .on('keyup', $.proxy(this.keyup, this)) 170 | 171 | if ($.browser.webkit || $.browser.msie) { 172 | this.$element.on('keydown', $.proxy(this.keypress, this)) 173 | } 174 | 175 | this.$menu 176 | .on('click', $.proxy(this.click, this)) 177 | .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) 178 | } 179 | 180 | , keyup: function (e) { 181 | switch(e.keyCode) { 182 | case 40: // down arrow 183 | case 38: // up arrow 184 | break 185 | 186 | case 9: // tab 187 | case 13: // enter 188 | if (!this.shown) return 189 | this.select() 190 | break 191 | 192 | case 27: // escape 193 | if (!this.shown) return 194 | this.hide() 195 | break 196 | 197 | default: 198 | this.lookup() 199 | } 200 | 201 | e.stopPropagation() 202 | e.preventDefault() 203 | } 204 | 205 | , keypress: function (e) { 206 | if (!this.shown) return 207 | 208 | switch(e.keyCode) { 209 | case 9: // tab 210 | case 13: // enter 211 | case 27: // escape 212 | e.preventDefault() 213 | break 214 | 215 | case 38: // up arrow 216 | if (e.type != 'keydown') break 217 | e.preventDefault() 218 | this.prev() 219 | break 220 | 221 | case 40: // down arrow 222 | if (e.type != 'keydown') break 223 | e.preventDefault() 224 | this.next() 225 | break 226 | } 227 | 228 | e.stopPropagation() 229 | } 230 | 231 | , blur: function (e) { 232 | var that = this 233 | setTimeout(function () { that.hide() }, 150) 234 | } 235 | 236 | , click: function (e) { 237 | e.stopPropagation() 238 | e.preventDefault() 239 | this.select() 240 | } 241 | 242 | , mouseenter: function (e) { 243 | this.$menu.find('.active').removeClass('active') 244 | $(e.currentTarget).addClass('active') 245 | } 246 | 247 | } 248 | 249 | 250 | /* TYPEAHEAD PLUGIN DEFINITION 251 | * =========================== */ 252 | 253 | $.fn.typeahead = function (option) { 254 | return this.each(function () { 255 | var $this = $(this) 256 | , data = $this.data('typeahead') 257 | , options = typeof option == 'object' && option 258 | if (!data) $this.data('typeahead', (data = new Typeahead(this, options))) 259 | if (typeof option == 'string') data[option]() 260 | }) 261 | } 262 | 263 | $.fn.typeahead.defaults = { 264 | source: [] 265 | , items: 8 266 | , menu: '' 267 | , item: '' 268 | } 269 | 270 | $.fn.typeahead.Constructor = Typeahead 271 | 272 | 273 | /* TYPEAHEAD DATA-API 274 | * ================== */ 275 | 276 | $(function () { 277 | $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { 278 | var $this = $(this) 279 | if ($this.data('typeahead')) return 280 | e.preventDefault() 281 | $this.typeahead($this.data()) 282 | }) 283 | }) 284 | 285 | }(window.jQuery); -------------------------------------------------------------------------------- /bootstrap/js/bootstrap-tooltip.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bootstrap-tooltip.js v2.0.4 3 | * http://twitter.github.com/bootstrap/javascript.html#tooltips 4 | * Inspired by the original jQuery.tipsy by Jason Frame 5 | * =========================================================== 6 | * Copyright 2012 Twitter, Inc. 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ========================================================== */ 20 | 21 | 22 | !function ($) { 23 | 24 | "use strict"; // jshint ;_; 25 | 26 | 27 | /* TOOLTIP PUBLIC CLASS DEFINITION 28 | * =============================== */ 29 | 30 | var Tooltip = function (element, options) { 31 | this.init('tooltip', element, options) 32 | } 33 | 34 | Tooltip.prototype = { 35 | 36 | constructor: Tooltip 37 | 38 | , init: function (type, element, options) { 39 | var eventIn 40 | , eventOut 41 | 42 | this.type = type 43 | this.$element = $(element) 44 | this.options = this.getOptions(options) 45 | this.enabled = true 46 | 47 | if (this.options.trigger != 'manual') { 48 | eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus' 49 | eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur' 50 | this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this)) 51 | this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this)) 52 | } 53 | 54 | this.options.selector ? 55 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : 56 | this.fixTitle() 57 | } 58 | 59 | , getOptions: function (options) { 60 | options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data()) 61 | 62 | if (options.delay && typeof options.delay == 'number') { 63 | options.delay = { 64 | show: options.delay 65 | , hide: options.delay 66 | } 67 | } 68 | 69 | return options 70 | } 71 | 72 | , enter: function (e) { 73 | var self = $(e.currentTarget)[this.type](this._options).data(this.type) 74 | 75 | if (!self.options.delay || !self.options.delay.show) return self.show() 76 | 77 | clearTimeout(this.timeout) 78 | self.hoverState = 'in' 79 | this.timeout = setTimeout(function() { 80 | if (self.hoverState == 'in') self.show() 81 | }, self.options.delay.show) 82 | } 83 | 84 | , leave: function (e) { 85 | var self = $(e.currentTarget)[this.type](this._options).data(this.type) 86 | 87 | if (this.timeout) clearTimeout(this.timeout) 88 | if (!self.options.delay || !self.options.delay.hide) return self.hide() 89 | 90 | self.hoverState = 'out' 91 | this.timeout = setTimeout(function() { 92 | if (self.hoverState == 'out') self.hide() 93 | }, self.options.delay.hide) 94 | } 95 | 96 | , show: function () { 97 | var $tip 98 | , inside 99 | , pos 100 | , actualWidth 101 | , actualHeight 102 | , placement 103 | , tp 104 | 105 | if (this.hasContent() && this.enabled) { 106 | $tip = this.tip() 107 | this.setContent() 108 | 109 | if (this.options.animation) { 110 | $tip.addClass('fade') 111 | } 112 | 113 | placement = typeof this.options.placement == 'function' ? 114 | this.options.placement.call(this, $tip[0], this.$element[0]) : 115 | this.options.placement 116 | 117 | inside = /in/.test(placement) 118 | 119 | $tip 120 | .remove() 121 | .css({ top: 0, left: 0, display: 'block' }) 122 | .appendTo(inside ? this.$element : document.body) 123 | 124 | pos = this.getPosition(inside) 125 | 126 | actualWidth = $tip[0].offsetWidth 127 | actualHeight = $tip[0].offsetHeight 128 | 129 | switch (inside ? placement.split(' ')[1] : placement) { 130 | case 'bottom': 131 | tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2} 132 | break 133 | case 'top': 134 | tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2} 135 | break 136 | case 'left': 137 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth} 138 | break 139 | case 'right': 140 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width} 141 | break 142 | } 143 | 144 | $tip 145 | .css(tp) 146 | .addClass(placement) 147 | .addClass('in') 148 | } 149 | } 150 | 151 | , isHTML: function(text) { 152 | // html string detection logic adapted from jQuery 153 | return typeof text != 'string' 154 | || ( text.charAt(0) === "<" 155 | && text.charAt( text.length - 1 ) === ">" 156 | && text.length >= 3 157 | ) || /^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(text) 158 | } 159 | 160 | , setContent: function () { 161 | var $tip = this.tip() 162 | , title = this.getTitle() 163 | 164 | $tip.find('.tooltip-inner')[this.isHTML(title) ? 'html' : 'text'](title) 165 | $tip.removeClass('fade in top bottom left right') 166 | } 167 | 168 | , hide: function () { 169 | var that = this 170 | , $tip = this.tip() 171 | 172 | $tip.removeClass('in') 173 | 174 | function removeWithAnimation() { 175 | var timeout = setTimeout(function () { 176 | $tip.off($.support.transition.end).remove() 177 | }, 500) 178 | 179 | $tip.one($.support.transition.end, function () { 180 | clearTimeout(timeout) 181 | $tip.remove() 182 | }) 183 | } 184 | 185 | $.support.transition && this.$tip.hasClass('fade') ? 186 | removeWithAnimation() : 187 | $tip.remove() 188 | } 189 | 190 | , fixTitle: function () { 191 | var $e = this.$element 192 | if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') { 193 | $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title') 194 | } 195 | } 196 | 197 | , hasContent: function () { 198 | return this.getTitle() 199 | } 200 | 201 | , getPosition: function (inside) { 202 | return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), { 203 | width: this.$element[0].offsetWidth 204 | , height: this.$element[0].offsetHeight 205 | }) 206 | } 207 | 208 | , getTitle: function () { 209 | var title 210 | , $e = this.$element 211 | , o = this.options 212 | 213 | title = $e.attr('data-original-title') 214 | || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) 215 | 216 | return title 217 | } 218 | 219 | , tip: function () { 220 | return this.$tip = this.$tip || $(this.options.template) 221 | } 222 | 223 | , validate: function () { 224 | if (!this.$element[0].parentNode) { 225 | this.hide() 226 | this.$element = null 227 | this.options = null 228 | } 229 | } 230 | 231 | , enable: function () { 232 | this.enabled = true 233 | } 234 | 235 | , disable: function () { 236 | this.enabled = false 237 | } 238 | 239 | , toggleEnabled: function () { 240 | this.enabled = !this.enabled 241 | } 242 | 243 | , toggle: function () { 244 | this[this.tip().hasClass('in') ? 'hide' : 'show']() 245 | } 246 | 247 | } 248 | 249 | 250 | /* TOOLTIP PLUGIN DEFINITION 251 | * ========================= */ 252 | 253 | $.fn.tooltip = function ( option ) { 254 | return this.each(function () { 255 | var $this = $(this) 256 | , data = $this.data('tooltip') 257 | , options = typeof option == 'object' && option 258 | if (!data) $this.data('tooltip', (data = new Tooltip(this, options))) 259 | if (typeof option == 'string') data[option]() 260 | }) 261 | } 262 | 263 | $.fn.tooltip.Constructor = Tooltip 264 | 265 | $.fn.tooltip.defaults = { 266 | animation: true 267 | , placement: 'top' 268 | , selector: false 269 | , template: ' ' 270 | , trigger: 'hover' 271 | , title: '' 272 | , delay: 0 273 | } 274 | 275 | }(window.jQuery); 276 | -------------------------------------------------------------------------------- /bootstrap/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.0.4 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}@media(max-width:767px){.visible-phone{display:inherit!important}.hidden-phone{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(min-width:768px) and (max-width:979px){.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:18px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-group>label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{position:absolute;top:10px;right:10px;left:10px;width:auto;margin:0}.modal.fade.in{top:auto}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:auto;margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:20px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.762430939%;*margin-left:2.709239449638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:99.999999993%;*width:99.9468085036383%}.row-fluid .span11{width:91.436464082%;*width:91.38327259263829%}.row-fluid .span10{width:82.87292817100001%;*width:82.8197366816383%}.row-fluid .span9{width:74.30939226%;*width:74.25620077063829%}.row-fluid .span8{width:65.74585634900001%;*width:65.6926648596383%}.row-fluid .span7{width:57.182320438000005%;*width:57.129128948638304%}.row-fluid .span6{width:48.618784527%;*width:48.5655930376383%}.row-fluid .span5{width:40.055248616%;*width:40.0020571266383%}.row-fluid .span4{width:31.491712705%;*width:31.4385212156383%}.row-fluid .span3{width:22.928176794%;*width:22.874985304638297%}.row-fluid .span2{width:14.364640883%;*width:14.311449393638298%}.row-fluid .span1{width:5.801104972%;*width:5.747913482638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:714px}input.span11,textarea.span11,.uneditable-input.span11{width:652px}input.span10,textarea.span10,.uneditable-input.span10{width:590px}input.span9,textarea.span9,.uneditable-input.span9{width:528px}input.span8,textarea.span8,.uneditable-input.span8{width:466px}input.span7,textarea.span7,.uneditable-input.span7{width:404px}input.span6,textarea.span6,.uneditable-input.span6{width:342px}input.span5,textarea.span5,.uneditable-input.span5{width:280px}input.span4,textarea.span4,.uneditable-input.span4{width:218px}input.span3,textarea.span3,.uneditable-input.span3{width:156px}input.span2,textarea.span2,.uneditable-input.span2{width:94px}input.span1,textarea.span1,.uneditable-input.span1{width:32px}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:30px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.564102564%;*margin-left:2.510911074638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145300001%;*width:91.3997999636383%}.row-fluid .span10{width:82.905982906%;*width:82.8527914166383%}.row-fluid .span9{width:74.358974359%;*width:74.30578286963829%}.row-fluid .span8{width:65.81196581200001%;*width:65.7587743226383%}.row-fluid .span7{width:57.264957265%;*width:57.2117657756383%}.row-fluid .span6{width:48.717948718%;*width:48.6647572286383%}.row-fluid .span5{width:40.170940171000005%;*width:40.117748681638304%}.row-fluid .span4{width:31.623931624%;*width:31.5707401346383%}.row-fluid .span3{width:23.076923077%;*width:23.0237315876383%}.row-fluid .span2{width:14.529914530000001%;*width:14.4767230406383%}.row-fluid .span1{width:5.982905983%;*width:5.929714493638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:1160px}input.span11,textarea.span11,.uneditable-input.span11{width:1060px}input.span10,textarea.span10,.uneditable-input.span10{width:960px}input.span9,textarea.span9,.uneditable-input.span9{width:860px}input.span8,textarea.span8,.uneditable-input.span8{width:760px}input.span7,textarea.span7,.uneditable-input.span7{width:660px}input.span6,textarea.span6,.uneditable-input.span6{width:560px}input.span5,textarea.span5,.uneditable-input.span5{width:460px}input.span4,textarea.span4,.uneditable-input.span4{width:360px}input.span3,textarea.span3,.uneditable-input.span3{width:260px}input.span2,textarea.span2,.uneditable-input.span2{width:160px}input.span1,textarea.span1,.uneditable-input.span1{width:60px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:18px}.navbar-fixed-bottom{margin-top:18px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 9px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#999;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:6px 15px;font-weight:bold;color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#222}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:9px 15px;margin:9px 0;border-top:1px solid #222;border-bottom:1px solid #222;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /app/js/audioplayback.js: -------------------------------------------------------------------------------- 1 | function AudioPlayback() 2 | { 3 | /** 4 | * This is the internal update event to fill the buffer with the audio data 5 | */ 6 | this.onAudioUpdate = function onAudioUpdate(evt) 7 | { 8 | var audioPlayback = this.eventHost; 9 | var bufferSize = audioPlayback.audioBufferSize; 10 | var elapsedTime = bufferSize / audioPlayback.sampleRate; 11 | 12 | // return if playback was stopped 13 | if (audioPlayback.isPlaying === false) return; 14 | 15 | // reference to the audio data arrays and audio buffer 16 | var audioData = audioPlayback.audioDataRef; 17 | var leftBuffer = evt.outputBuffer.getChannelData(0); 18 | var rightBuffer = evt.outputBuffer.getChannelData(1); 19 | 20 | if (audioData.length == 1) // mono 21 | { 22 | audioPlayback.copyChannelDataToBuffer(leftBuffer, audioData[0], audioPlayback.currentPlayPosition, bufferSize, audioPlayback.playStart, audioPlayback.playEnd, audioPlayback.isLooped); 23 | audioPlayback.currentPlayPosition = audioPlayback.copyChannelDataToBuffer(rightBuffer, audioData[0], audioPlayback.currentPlayPosition, bufferSize, audioPlayback.playStart, audioPlayback.playEnd, audioPlayback.isLooped); 24 | } 25 | else if (audioData.length == 2) // stereo 26 | { 27 | audioPlayback.copyChannelDataToBuffer(leftBuffer, audioData[0], audioPlayback.currentPlayPosition, bufferSize, audioPlayback.playStart, audioPlayback.playEnd, audioPlayback.isLooped); 28 | audioPlayback.currentPlayPosition = audioPlayback.copyChannelDataToBuffer(rightBuffer, audioData[1], audioPlayback.currentPlayPosition, bufferSize, audioPlayback.playStart, audioPlayback.playEnd, audioPlayback.isLooped); 29 | } 30 | 31 | // the playback is done 32 | if (audioPlayback.currentPlayPosition === undefined) 33 | { 34 | // stop playing, disconnect buffer 35 | audioPlayback.stop(); 36 | } 37 | else 38 | { 39 | // Update the notification listener 40 | audioPlayback.lastPlaybackUpdate -= elapsedTime; 41 | if (audioPlayback.lastPlaybackUpdate < 0.0) 42 | { 43 | audioPlayback.lastPlaybackUpdate = audioPlayback.playbackUpdateInterval; 44 | audioPlayback.notifyUpdateListener(); 45 | } 46 | } 47 | }; 48 | 49 | /** 50 | * Copies the audio data to a channel buffer and sets the new play position. If looping is enabled, 51 | * the position is set automaticly. 52 | * @param bufferReference Reference to the channel buffer 53 | * @param dataReference Reference to the audio data 54 | * @param position Current position of the playback 55 | * @param len Length of the chunk 56 | * @param startPosition Start position for looping 57 | * @param endPosition End position for looping 58 | * @param isLooped Enable looping. 59 | */ 60 | this.copyChannelDataToBuffer = function copyChannelDataToBuffer(bufferReference, dataReference, position, len, startPosition, endPosition, isLooped) 61 | { 62 | /* In order to enable looping, we should need to split up when the end of the audio data is reached 63 | * to begin with the first position. Therefore is a split into two ranges if neccessary 64 | */ 65 | var firstSplitStart = position; 66 | var firstSplitEnd = (position + len > dataReference.length) ? 67 | dataReference.length : (position + len > endPosition) ? 68 | endPosition : (position + len); 69 | 70 | var firstSplitLen = firstSplitEnd - firstSplitStart; 71 | 72 | var secondSplitStart = (firstSplitLen < bufferReference.length) ? 73 | (isLooped) ? startPosition : 0 : undefined; 74 | 75 | var secondSplitEnd = (secondSplitStart !== undefined) ? bufferReference.length - firstSplitLen + secondSplitStart : undefined; 76 | 77 | var secondSplitOffset = bufferReference.length - (firstSplitEnd - firstSplitStart); 78 | 79 | if (secondSplitStart === undefined) 80 | { 81 | this.copyIntoBuffer(bufferReference, 0, dataReference, firstSplitStart, firstSplitEnd); 82 | return firstSplitEnd; 83 | } 84 | else 85 | { 86 | this.copyIntoBuffer(bufferReference, 0, dataReference, firstSplitStart, firstSplitEnd); 87 | 88 | if (isLooped) 89 | { 90 | this.copyIntoBuffer(bufferReference, firstSplitLen, dataReference, secondSplitStart, secondSplitEnd); 91 | 92 | return secondSplitEnd; 93 | } 94 | else 95 | { 96 | return undefined; 97 | } 98 | } 99 | }; 100 | 101 | /** 102 | * copies data from an array to the buffer with fast coping methods 103 | */ 104 | this.copyIntoBuffer = function copyIntoBuffer(bufferReference, bufferOffset, dataReference, dataOffset, end) 105 | { 106 | bufferReference.set(dataReference.slice(dataOffset, end), bufferOffset); 107 | }; 108 | 109 | 110 | this.play = function play(audioDataRef, sampleRate, isLooped, start, end) 111 | { 112 | // check if already playing or no data was given 113 | if (this.isPlaying || audioDataRef === undefined || audioDataRef.length < 1 || 114 | sampleRate === undefined || sampleRate <= 0) return; 115 | 116 | 117 | // update playback variables 118 | this.audioDataRef = audioDataRef; 119 | this.sampleRate = sampleRate; 120 | this.isLooped = (isLooped === undefined) ? false : isLooped; 121 | this.playStart = (start === undefined || start < 0 || start >= audioDataRef[0].length) ? 0 : start; 122 | this.playEnd = (end === undefined || end - this.audioBufferSize < start || end >= audioDataRef[0].length) ? audioDataRef[0].length : end; 123 | this.currentPlayPosition = this.playStart; 124 | this.isPlaying = true; 125 | 126 | // connect the node, play! 127 | this.javaScriptNode.connect(this.analyserNode); 128 | 129 | // inform updatelistener 130 | this.notifyUpdateListener(); 131 | }; 132 | 133 | /** 134 | * Stops the playback and set all references to undefined (no resume possible) 135 | */ 136 | this.stop = function stop() 137 | { 138 | // no playing audio, nothing to stop 139 | if (this.isPlaying === false) return; 140 | 141 | // diconnect the node, stop! 142 | this.javaScriptNode.disconnect(this.analyserNode); 143 | 144 | // set all playback information to default 145 | this.playStart = 0; 146 | this.playEnd = 0; 147 | this.isLooped = false; 148 | this.currentPlayPosition = 0; 149 | this.isPlaying = false; 150 | this.lastPlaybackUpdate = 0; 151 | 152 | // remove reference to the audio data 153 | this.audioDataRef = undefined; 154 | this.sampleRate = 0; 155 | 156 | // inform updatelistener 157 | this.notifyUpdateListener(); 158 | }; 159 | 160 | /** 161 | * Pause the playback of the audio 162 | */ 163 | this.pause = function pause() 164 | { 165 | // no playing audio, nothing to pause 166 | if (this.isPlaying === false) return; 167 | this.isPlaying = false; 168 | this.lastPlaybackUpdate = 0; 169 | 170 | // diconnect the node, stop! 171 | this.audioJavaScriptNode.disconnect(this.analyserNode); 172 | 173 | // inform updatelistener 174 | this.notifyUpdateListener(); 175 | }; 176 | 177 | /** 178 | * Resume the audio playback from the last position 179 | */ 180 | this.resume = function resume() 181 | { 182 | // check if already playing or no data was given 183 | if (this.isPlaying || this.audioDataRef === undefined || this.audioDataRef.length < 1) return; 184 | this.isPlaying = true; 185 | 186 | // connect the node, play! 187 | this.audioJavaScriptNode.connect(this.analyserNode); 188 | 189 | // inform updatelistener 190 | this.notifyUpdateListener(); 191 | }; 192 | 193 | /** 194 | * Add an update listener, which gets informed about changes in playback 195 | */ 196 | this.addUpdateListener = function addUpdateListener(updateCallback) 197 | { 198 | this.updateListener.push(updateCallback); 199 | }; 200 | 201 | /** 202 | * Notifies all update listener 203 | */ 204 | this.notifyUpdateListener = function notifyUpdateListener() 205 | { 206 | for(var i = 0; i < this.updateListener.length; ++i) 207 | { 208 | this.updateListener[i].audioPlaybackUpdate(); 209 | } 210 | }; 211 | 212 | // Creation of a new audio context 213 | this.audioBufferSize = 1024; 214 | this.sampleRate = 0; 215 | window.AudioContext = window.AudioContext || window.webkitAudioContext; 216 | this.audioContext = new AudioContext(); 217 | 218 | // The JavaScriptNode is used to modifiy the output buffer 219 | this.javaScriptNode = this.audioContext.createScriptProcessor(this.audioBufferSize, 1, 2); 220 | this.javaScriptNode.onaudioprocess = this.onAudioUpdate; 221 | this.javaScriptNode.eventHost = this; 222 | 223 | this.analyserNode = this.audioContext.createAnalyser(); 224 | this.analyserNode.minDecibels = -100; 225 | this.analyserNode.maxDecibels = 0; 226 | this.analyserNode.smoothingTimeConstant = 0.0; 227 | this.analyserNode.connect(this.audioContext.destination); 228 | 229 | this.audioDataRef = undefined; 230 | 231 | // Playback information 232 | this.playStart = 0; 233 | this.playEnd = 0; 234 | this.isLooped = false; 235 | this.currentPlayPosition = 0; 236 | this.isPlaying = false; 237 | 238 | // Callback information 239 | this.updateListener = []; 240 | this.playbackUpdateInterval = 0.0; // in Seconds 241 | this.lastPlaybackUpdate = 0; 242 | 243 | } -------------------------------------------------------------------------------- /app/js/wavetrack.js: -------------------------------------------------------------------------------- 1 | function WaveTrack() 2 | { 3 | this.sampleRate = 0; 4 | this.audioSequences = []; 5 | 6 | var signedBorders = [0, 0xFF - 0x80, 0xFFFF - 0x8000, 0xFFFFFFFFF - 0x80000000]; 7 | 8 | this.fromAudioSequences = function fromAudioSequences(sequences) 9 | { 10 | if (sequences.length === 0) return; 11 | 12 | var sampleRateCheck = sequences[0].sampleRate; 13 | var lengthCheck = sequences[0].data.length; 14 | 15 | for (var i = 1; i < sequences.length; ++i) 16 | { 17 | if (sequences[i].sampleRate != sampleRateCheck || 18 | sequences[i].data.length != lengthCheck) 19 | throw "The input sequences must have the same length and samplerate"; 20 | } 21 | 22 | this.sampleRate = sampleRateCheck; 23 | this.audioSequences = sequences; 24 | }; 25 | 26 | this.toBlobUrlAsync = function toBlobUrlAsync(encoding, asyncMethod, host) 27 | { 28 | 29 | 30 | var encodedWave = this.encodeWaveFile(); 31 | 32 | var blob = new Blob([encodedWave], {type: encoding}); 33 | 34 | if (asyncMethod !== undefined) 35 | { 36 | var fileReader = new FileReader(); 37 | fileReader.onloadend = function(e) 38 | { 39 | //debugger; 40 | asyncMethod(fileReader.result, host); 41 | }; 42 | fileReader.readAsDataURL(blob); 43 | } 44 | else 45 | { 46 | return window.URL.createObjectURL(blob); 47 | } 48 | }; 49 | 50 | this.decodeWaveFile = function decodeWaveFile(data) 51 | { 52 | var reader = new BinaryReader(data); 53 | 54 | /* Decoding PCM 55 | TODO: Cleanup later with reader.skip(...) function, read only information which is used later 56 | */ 57 | var waveChunkID = reader.readString(4); 58 | var waveChunkSize = reader.readUInt32(); 59 | var waveFormat = reader.readString(4); 60 | 61 | reader.gotoString("fmt "); 62 | var waveSubchunk1ID = reader.readString(4); 63 | var waveSubchunk1Size = reader.readUInt32(); 64 | var waveAudioFormat = reader.readUInt16(); 65 | var waveNumChannels = this.channels = reader.readUInt16(); 66 | var waveSampleRate = this.sampleRate = reader.readUInt32(); 67 | var waveByteRate = reader.readUInt32(); 68 | var waveBlockAlign = reader.readUInt16(); 69 | var waveBitsPerSample = reader.readUInt16(); 70 | // get the data block, sometimes there blocks like cue before 71 | reader.gotoString("data"); 72 | var waveSubchunk2ID = reader.readString(4); 73 | var waveSubchunk2Size = reader.readUInt32(); 74 | 75 | var samplesPerChannel = this.samplesPerChannel = waveSubchunk2Size / waveBlockAlign; 76 | 77 | // prepare channels 78 | var channelNames = ["Left Channel", "Right Channel"]; 79 | for (var i = 0; i < waveNumChannels; ++i) 80 | { 81 | this.audioSequences.push(new CreateNewAudioSequence(this.sampleRate)); 82 | this.audioSequences[i].name = channelNames[i]; 83 | } 84 | 85 | // fill channels 86 | var signBorderId = waveBitsPerSample / 8; 87 | var signedBorder = signedBorders[signBorderId]; 88 | 89 | this.gain = 0.0; 90 | for (var i = 0; i < samplesPerChannel; ++i) 91 | { 92 | for (var channelId = 0; channelId < waveNumChannels; ++channelId) 93 | { 94 | var value = (waveBitsPerSample == 8) ? reader.readUInt8() : 95 | (waveBitsPerSample == 16) ? reader.readInt16() : reader.readInt32(); 96 | 97 | value = Math.min(1.0, Math.max(-1.0, value)); // cut off beyond the border 98 | // Convert into a spectrum from -1.0 to 1.0 99 | // Note that 8bit values are always unsigned, therefore another converting scheme is used 100 | var floatValue = convertIntToFloat(value, waveBitsPerSample, signedBorder); 101 | 102 | this.audioSequences[channelId].data.push(floatValue); 103 | } 104 | } 105 | 106 | for (var channelId = 0; channelId < waveNumChannels; ++channelId) 107 | { 108 | this.audioSequences[channelId].gain = this.audioSequences[channelId].getGain(); 109 | } 110 | }; 111 | 112 | function convertIntToFloat(value, waveBitsPerSample, signedBorder) 113 | { 114 | return (waveBitsPerSample == 8) ? 115 | (value == 0) ? -1.0 : value / signedBorder - 1.0 116 | : 117 | (value == 0) ? 0 : value / signedBorder; 118 | } 119 | 120 | function convertFloatToInt(value, waveBitsPerSample, signedBorder) 121 | { 122 | return (waveBitsPerSample == 8) ? (value + 1.0) * signedBorder 123 | : 124 | value * signedBorder; 125 | } 126 | 127 | this.encodeWaveFile = function encodeWaveFile() 128 | { 129 | // prepare variables for encoding 130 | var waveChunkID = "RIFF"; 131 | var waveFormat = "WAVE"; 132 | var waveSubchunk1ID = "fmt "; 133 | var waveSubchunk1Size = 16; 134 | var waveAudioFormat = 1; 135 | var waveNumChannels = this.audioSequences.length; 136 | var waveSampleRate = this.sampleRate; 137 | var waveBitsPerSample = 16; // Attention! Order 138 | var waveByteRate = waveSampleRate * waveNumChannels * waveBitsPerSample / 8; 139 | var waveBlockAlign = waveNumChannels * waveBitsPerSample / 8; 140 | var waveBitsPerSample = 16; 141 | var waveSamplesPerChannel = this.audioSequences[0].data.length; 142 | var waveSubchunk2ID = "data"; 143 | var waveSubchunk2Size = waveSamplesPerChannel * waveBlockAlign; 144 | var waveChunkSize = waveSubchunk2Size + 36; // 36 are the bytes from waveFormat till waveSubchunk2Size 145 | var totalSize = waveChunkSize + 8; 146 | 147 | // actual writing 148 | var writer = new BinaryWriter(totalSize); 149 | writer.writeString(waveChunkID); 150 | writer.writeUInt32(waveChunkSize); 151 | writer.writeString(waveFormat); 152 | 153 | writer.writeString(waveSubchunk1ID); 154 | writer.writeUInt32(waveSubchunk1Size); 155 | writer.writeUInt16(waveAudioFormat); 156 | writer.writeUInt16(waveNumChannels); 157 | writer.writeUInt32(waveSampleRate); 158 | writer.writeUInt32(waveByteRate); 159 | writer.writeUInt16(waveBlockAlign); 160 | writer.writeUInt16(waveBitsPerSample); 161 | 162 | writer.writeString(waveSubchunk2ID); 163 | writer.writeUInt32(waveSubchunk2Size); 164 | 165 | var signBorderId = waveBitsPerSample / 8; 166 | var signedBorder = signedBorders[signBorderId]; 167 | 168 | for(var i = 0; i < waveSamplesPerChannel; ++i) 169 | { 170 | for (var channelId = 0; channelId < waveNumChannels; ++channelId) 171 | { 172 | writer.writeInt16(convertFloatToInt(this.audioSequences[channelId].data[i], waveBitsPerSample, signedBorder)); 173 | } 174 | } 175 | 176 | return writer.data; 177 | 178 | }; 179 | 180 | 181 | } 182 | 183 | function Complex(real, img) 184 | { 185 | this.real = real; 186 | this.img = img; 187 | 188 | this.plus = function plus(c) 189 | { 190 | return new Complex(this.real + c.real, this.img + c.img); 191 | } 192 | 193 | this.minus = function minus(c) 194 | { 195 | return new Complex(this.real - c.real, this.img - c.img); 196 | } 197 | 198 | this.times = function times(c) 199 | { 200 | return new Complex(this.real * c.real - this.img * c.img, 201 | this.real * c.img + this.img * c.real); 202 | } 203 | 204 | this.timesScalar = function timesScalar(s) 205 | { 206 | return new Complex(this.real * s,this.img * s); 207 | } 208 | 209 | this.conjugate = function conjugate() 210 | { 211 | return new Complex(this.real, -this.img); 212 | } 213 | 214 | this.print = function print() 215 | { 216 | r = this.real; 217 | //r = (r < 0.0001 && r > -0.0001) ? 0 : real; 218 | return "" + r + " " + this.img + ""; 219 | } 220 | } 221 | 222 | var FFTComplex = function FFTComplex() 223 | { 224 | this.fft = function fft(arrayOfComplex) 225 | { 226 | var len = arrayOfComplex.length; 227 | 228 | if (len === 1) return [arrayOfComplex[0]]; 229 | 230 | if (len % 2 !== 0) debugger; 231 | 232 | var even = []; 233 | for (var k = 0; k < len / 2; ++k) 234 | { 235 | even.push(arrayOfComplex[k * 2]); 236 | } 237 | var q = this.fft(even); 238 | 239 | var odd = []; 240 | for (var k = 0; k < len / 2; ++k) 241 | { 242 | odd.push(arrayOfComplex[k * 2 + 1]); 243 | } 244 | var r = this.fft(odd); 245 | 246 | var y = []; 247 | for (var k = 0; k < len / 2; ++k) 248 | { 249 | var kth = -2.0 * k * Math.PI / len; 250 | var wk = new Complex(Math.cos(kth), Math.sin(kth)); 251 | y[k] = q[k].plus(wk.times(r[k])); 252 | y[k + len / 2] = q[k].minus(wk.times(r[k])); 253 | } 254 | return y; 255 | } 256 | 257 | this.ifft = function ifft(arrayOfComplex) 258 | { 259 | var len = arrayOfComplex.length; 260 | var y = []; 261 | 262 | for (var i = 0; i < len; ++i) 263 | { 264 | y[i] = arrayOfComplex[i].conjugate(); 265 | } 266 | 267 | y = this.fft(y); 268 | 269 | for (var i = 0; i < len; ++i) 270 | { 271 | y[i] = y[i].conjugate(); 272 | } 273 | 274 | for (var i = 0; i < len; ++i) 275 | { 276 | y[i] = y[i].timesScalar(1.0 / len); 277 | } 278 | 279 | return y; 280 | } 281 | 282 | } 283 | 284 | 285 | function printComplexArray(a) 286 | { 287 | for(var i = 0; i < a.length; ++i) 288 | { 289 | console.log(a[i].print() + "\n"); 290 | } 291 | console.log("==============="); 292 | } -------------------------------------------------------------------------------- /app/mainstyle.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | margin: 0px; 4 | padding: 0px; 5 | height: 100%; 6 | background-color: rgb(200, 200, 200); 7 | } 8 | 9 | #content 10 | { 11 | box-shadow: 0px 0px 30px #000; 12 | margin: 50px; 13 | } 14 | 15 | #edit-toolbar 16 | { 17 | width: 100%; 18 | height: 50px; 19 | position: relative; 20 | margin: 0px; 21 | padding: 0px; 22 | top: 0px; 23 | left: 0px; 24 | 25 | background: #aebcbf; /* Old browsers */ 26 | background: -moz-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); /* FF3.6+ */ 27 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#aebcbf), color-stop(50%,#6e7774), color-stop(51%,#0a0e0a), color-stop(100%,#0a0809)); /* Chrome,Safari4+ */ 28 | background: -webkit-linear-gradient(top, #aebcbf 0%,#6e7774 50%,#0a0e0a 51%,#0a0809 100%); /* Chrome10+,Safari5.1+ */ 29 | background: -o-linear-gradient(top, #aebcbf 0%,#6e7774 50%,#0a0e0a 51%,#0a0809 100%); /* Opera 11.10+ */ 30 | background: -ms-linear-gradient(top, #aebcbf 0%,#6e7774 50%,#0a0e0a 51%,#0a0809 100%); /* IE10+ */ 31 | background: linear-gradient(top, #aebcbf 0%,#6e7774 50%,#0a0e0a 51%,#0a0809 100%); /* W3C */ 32 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#aebcbf', endColorstr='#0a0809',GradientType=0 ); /* IE6-9 */ 33 | } 34 | 35 | #edit-toolbar button 36 | { 37 | height: 80%; 38 | top: 10%; 39 | position: relative; 40 | border: 1px; 41 | width: auto; 42 | color: #EEE; 43 | box-shadow: inset 0px 0px 3px #000; 44 | border-radius: 5px; 45 | 46 | margin-right: 0px; 47 | margin-left: 2px; 48 | 49 | 50 | background: #cedce7; /* Old browsers */ 51 | background: -moz-linear-gradient(top, #cedce7 0%, #596a72 100%); /* FF3.6+ */ 52 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#cedce7), color-stop(100%,#596a72)); /* Chrome,Safari4+ */ 53 | background: -webkit-linear-gradient(top, #cedce7 0%,#596a72 100%); /* Chrome10+,Safari5.1+ */ 54 | background: -o-linear-gradient(top, #cedce7 0%,#596a72 100%); /* Opera 11.10+ */ 55 | background: -ms-linear-gradient(top, #cedce7 0%,#596a72 100%); /* IE10+ */ 56 | background: linear-gradient(top, #cedce7 0%,#596a72 100%); /* W3C */ 57 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cedce7', endColorstr='#596a72',GradientType=0 ); /* IE6-9 */ 58 | } 59 | 60 | #edit-toolbar botton:active 61 | { 62 | background: #cedce7; /* Old browsers */ 63 | background: -moz-linear-gradient(top, #cedce7 0%, #596a72 62%); /* FF3.6+ */ 64 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#cedce7), color-stop(62%,#596a72)); /* Chrome,Safari4+ */ 65 | background: -webkit-linear-gradient(top, #cedce7 0%,#596a72 62%); /* Chrome10+,Safari5.1+ */ 66 | background: -o-linear-gradient(top, #cedce7 0%,#596a72 62%); /* Opera 11.10+ */ 67 | background: -ms-linear-gradient(top, #cedce7 0%,#596a72 62%); /* IE10+ */ 68 | background: linear-gradient(top, #cedce7 0%,#596a72 62%); /* W3C */ 69 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cedce7', endColorstr='#596a72',GradientType=0 ); /* IE6-9 */ 70 | } 71 | 72 | #editor 73 | { 74 | background-color: #EEE; 75 | width: 100%; 76 | height: 500px; 77 | 78 | } 79 | 80 | #footer 81 | { 82 | background-color: #191919; 83 | position: relative; 84 | bottom: 0px; 85 | height: 100px; 86 | width: 100%; 87 | } 88 | 89 | #effects 90 | { 91 | background-color: #FF0000; 92 | height: 100%; 93 | width: 100%; 94 | float: left; 95 | margin-right: -300px; 96 | 97 | background: #6b6b6b; /* Old browsers */ 98 | background: -moz-linear-gradient(top, #6b6b6b 0%, #212121 100%); /* FF3.6+ */ 99 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#6b6b6b), color-stop(100%,#212121)); /* Chrome,Safari4+ */ 100 | background: -webkit-linear-gradient(top, #6b6b6b 0%,#212121 100%); /* Chrome10+,Safari5.1+ */ 101 | background: -o-linear-gradient(top, #6b6b6b 0%,#212121 100%); /* Opera 11.10+ */ 102 | background: -ms-linear-gradient(top, #6b6b6b 0%,#212121 100%); /* IE10+ */ 103 | background: linear-gradient(top, #6b6b6b 0%,#212121 100%); /* W3C */ 104 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#6b6b6b', endColorstr='#212121',GradientType=0 ); /* IE6-9 */ 105 | } 106 | 107 | #export 108 | { 109 | background-color: lime; 110 | width: 300px; 111 | height: 100px; 112 | float: right; 113 | 114 | background: #969696; /* Old browsers */ 115 | background: -moz-linear-gradient(top, #969696 0%, #444444 100%); /* FF3.6+ */ 116 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#969696), color-stop(100%,#444444)); /* Chrome,Safari4+ */ 117 | background: -webkit-linear-gradient(top, #969696 0%,#444444 100%); /* Chrome10+,Safari5.1+ */ 118 | background: -o-linear-gradient(top, #969696 0%,#444444 100%); /* Opera 11.10+ */ 119 | background: -ms-linear-gradient(top, #969696 0%,#444444 100%); /* IE10+ */ 120 | background: linear-gradient(top, #969696 0%,#444444 100%); /* W3C */ 121 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#969696', endColorstr='#444444',GradientType=0 ); /* IE6-9 */ 122 | } 123 | 124 | #editor-title 125 | { 126 | color: #222; 127 | font-size: 40px; 128 | text-align: center; 129 | } 130 | 131 | #editorbox 132 | { 133 | width: 710px; 134 | margin-left: auto; 135 | margin-right: auto; 136 | margin-top: 50px; 137 | box-shadow: 0px 0px 10px #000; 138 | } 139 | 140 | #editor-spectrum 141 | { 142 | background-color: #FFF; 143 | width: 200px; 144 | height: 200px; 145 | float: left; 146 | 147 | } 148 | 149 | #editor-sequences 150 | { 151 | background-color: #7F6; 152 | float: right; 153 | 154 | } 155 | 156 | .sequence-editor 157 | { 158 | background-color: #3F6; 159 | width: 500px; 160 | height: 100px; 161 | border: 1px solid #000; 162 | } 163 | 164 | .effect 165 | { 166 | background-color: rgba(255, 255, 255, 0.05); 167 | width: 170px; 168 | height: 80%; 169 | box-shadow: inset 0px 0px 10px #000; 170 | margin-top: 10px; 171 | margin-left: 10px; 172 | float: left; 173 | color: #EEE; 174 | font-size: 10px; 175 | text-align: center; 176 | } 177 | 178 | .effect:hover 179 | { 180 | background-color: rgba(255, 255, 255, 0.15); 181 | } 182 | 183 | .effect button 184 | { 185 | height: 30px; 186 | width: 100%; 187 | margin: auto; 188 | border: 1px; 189 | color: #EEE; 190 | box-shadow: inset 0px 0px 3px #000; 191 | border-radius: 2px; 192 | 193 | background: rgb(255,48,25); /* Old browsers */ 194 | background: -moz-linear-gradient(top, rgba(255,48,25,1) 0%, rgba(207,4,4,1) 100%); /* FF3.6+ */ 195 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,48,25,1)), color-stop(100%,rgba(207,4,4,1))); /* Chrome,Safari4+ */ 196 | background: -webkit-linear-gradient(top, rgba(255,48,25,1) 0%,rgba(207,4,4,1) 100%); /* Chrome10+,Safari5.1+ */ 197 | background: -o-linear-gradient(top, rgba(255,48,25,1) 0%,rgba(207,4,4,1) 100%); /* Opera 11.10+ */ 198 | background: -ms-linear-gradient(top, rgba(255,48,25,1) 0%,rgba(207,4,4,1) 100%); /* IE10+ */ 199 | background: linear-gradient(top, rgba(255,48,25,1) 0%,rgba(207,4,4,1) 100%); /* W3C */ 200 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff3019', endColorstr='#cf0404',GradientType=0 ); /* IE6-9 */ 201 | } 202 | 203 | .effect button:hover 204 | { 205 | background: rgb(98,125,77); /* Old browsers */ 206 | background: -moz-linear-gradient(top, rgba(98,125,77,1) 0%, rgba(31,59,8,1) 100%); /* FF3.6+ */ 207 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(98,125,77,1)), color-stop(100%,rgba(31,59,8,1))); /* Chrome,Safari4+ */ 208 | background: -webkit-linear-gradient(top, rgba(98,125,77,1) 0%,rgba(31,59,8,1) 100%); /* Chrome10+,Safari5.1+ */ 209 | background: -o-linear-gradient(top, rgba(98,125,77,1) 0%,rgba(31,59,8,1) 100%); /* Opera 11.10+ */ 210 | background: -ms-linear-gradient(top, rgba(98,125,77,1) 0%,rgba(31,59,8,1) 100%); /* IE10+ */ 211 | background: linear-gradient(top, rgba(98,125,77,1) 0%,rgba(31,59,8,1) 100%); /* W3C */ 212 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#627d4d', endColorstr='#1f3b08',GradientType=0 ); /* IE6-9 */ 213 | } 214 | 215 | 216 | .effect input 217 | { 218 | background-color: transparent; 219 | color: #EEE; 220 | border: 0px; 221 | margin: 5px auto auto auto; 222 | padding: 2px; 223 | width: 50px; 224 | } 225 | 226 | .effect p 227 | { 228 | 229 | } 230 | 231 | #exportbox 232 | { 233 | width: 80%; 234 | margin: auto; 235 | height: 80%; 236 | top: 10%; 237 | position: relative; 238 | } 239 | 240 | #exportbox button 241 | { 242 | 243 | height: 30%; 244 | width: 100%; 245 | margin: auto; 246 | border: 1px; 247 | color: #EEE; 248 | box-shadow: inset 0px 0px 3px #000; 249 | 250 | background: rgb(255,48,25); /* Old browsers */ 251 | background: -moz-linear-gradient(top, rgba(255,48,25,1) 0%, rgba(207,4,4,1) 100%); /* FF3.6+ */ 252 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,48,25,1)), color-stop(100%,rgba(207,4,4,1))); /* Chrome,Safari4+ */ 253 | background: -webkit-linear-gradient(top, rgba(255,48,25,1) 0%,rgba(207,4,4,1) 100%); /* Chrome10+,Safari5.1+ */ 254 | background: -o-linear-gradient(top, rgba(255,48,25,1) 0%,rgba(207,4,4,1) 100%); /* Opera 11.10+ */ 255 | background: -ms-linear-gradient(top, rgba(255,48,25,1) 0%,rgba(207,4,4,1) 100%); /* IE10+ */ 256 | background: linear-gradient(top, rgba(255,48,25,1) 0%,rgba(207,4,4,1) 100%); /* W3C */ 257 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff3019', endColorstr='#cf0404',GradientType=0 ); /* IE6-9 */ 258 | 259 | } 260 | 261 | #exportbox a 262 | { 263 | -webkit-box-align: center; 264 | text-align: center; 265 | display: block; 266 | width: 100%; 267 | margin: auto; 268 | border: 1px; 269 | color: #888; 270 | box-shadow: inset 0px 0px 3px #000; 271 | padding: 20px 0px 20px 0px; 272 | 273 | background: rgb(153,146,146); /* Old browsers */ 274 | background: -moz-linear-gradient(top, rgba(153,146,146,1) 0%, rgba(109,109,109,1) 40%, rgba(56,56,56,1) 100%); /* FF3.6+ */ 275 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(153,146,146,1)), color-stop(40%,rgba(109,109,109,1)), color-stop(100%,rgba(56,56,56,1))); /* Chrome,Safari4+ */ 276 | background: -webkit-linear-gradient(top, rgba(153,146,146,1) 0%,rgba(109,109,109,1) 40%,rgba(56,56,56,1) 100%); /* Chrome10+,Safari5.1+ */ 277 | background: -o-linear-gradient(top, rgba(153,146,146,1) 0%,rgba(109,109,109,1) 40%,rgba(56,56,56,1) 100%); /* Opera 11.10+ */ 278 | background: -ms-linear-gradient(top, rgba(153,146,146,1) 0%,rgba(109,109,109,1) 40%,rgba(56,56,56,1) 100%); /* IE10+ */ 279 | background: linear-gradient(top, rgba(153,146,146,1) 0%,rgba(109,109,109,1) 40%,rgba(56,56,56,1) 100%); /* W3C */ 280 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#999292', endColorstr='#383838',GradientType=0 ); /* IE6-9 */ 281 | 282 | } 283 | 284 | #exportbox a:link 285 | { 286 | color: #EEE; 287 | text-decoration: none; 288 | background: rgb(98,125,77); /* Old browsers */ 289 | background: -moz-linear-gradient(top, rgba(98,125,77,1) 0%, rgba(31,59,8,1) 100%); /* FF3.6+ */ 290 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(98,125,77,1)), color-stop(100%,rgba(31,59,8,1))); /* Chrome,Safari4+ */ 291 | background: -webkit-linear-gradient(top, rgba(98,125,77,1) 0%,rgba(31,59,8,1) 100%); /* Chrome10+,Safari5.1+ */ 292 | background: -o-linear-gradient(top, rgba(98,125,77,1) 0%,rgba(31,59,8,1) 100%); /* Opera 11.10+ */ 293 | background: -ms-linear-gradient(top, rgba(98,125,77,1) 0%,rgba(31,59,8,1) 100%); /* IE10+ */ 294 | background: linear-gradient(top, rgba(98,125,77,1) 0%,rgba(31,59,8,1) 100%); /* W3C */ 295 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#627d4d', endColorstr='#1f3b08',GradientType=0 ); /* IE6-9 */ 296 | 297 | } 298 | -------------------------------------------------------------------------------- /app/js/ACFFT.js: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | FFT.cpp 4 | 5 | Dominic Mazzoni 6 | 7 | September 2000 8 | 9 | *******************************************************************//*! 10 | 11 | \file FFT.cpp 12 | \brief Fast Fourier Transform routines. 13 | 14 | This file contains a few FFT routines, including a real-FFT 15 | routine that is almost twice as fast as a normal complex FFT, 16 | and a power spectrum routine when you know you don't care 17 | about phase information. 18 | 19 | Some of this code was based on a free implementation of an FFT 20 | by Don Cross, available on the web at: 21 | 22 | http://www.intersrv.com/~dcross/fft.html 23 | 24 | The basic algorithm for his code was based on Numerican Recipes 25 | in Fortran. I optimized his code further by reducing array 26 | accesses, caching the bit reversal table, and eliminating 27 | float-to-double conversions, and I added the routines to 28 | calculate a real FFT and a real power spectrum. 29 | 30 | *//*******************************************************************/ 31 | /* 32 | Salvo Ventura - November 2006 33 | Added more window functions: 34 | * 4: Blackman 35 | * 5: Blackman-Harris 36 | * 6: Welch 37 | * 7: Gaussian(a=2.5) 38 | * 8: Gaussian(a=3.5) 39 | * 9: Gaussian(a=4.5) 40 | */ 41 | 42 | var gFFTBitTable = undefined; 43 | var MaxFastBits = 16; 44 | 45 | 46 | 47 | function IsPowerOfTwo(x) 48 | { 49 | if (x < 2) 50 | return false; 51 | 52 | if (x & (x - 1)) /* Thanks to 'byang' for this cute trick! */ 53 | return false; 54 | 55 | return true; 56 | } 57 | 58 | function NumberOfBitsNeeded( PowerOfTwo) 59 | { 60 | var i; 61 | 62 | if (PowerOfTwo < 2) { 63 | fprintf(stderr, "Error: FFT called with size %d\n", PowerOfTwo); 64 | exit(1); 65 | } 66 | 67 | for (var i = 0;; i++) 68 | if (PowerOfTwo & (1 << i)) 69 | return i; 70 | } 71 | 72 | function ReverseBits( index, NumBits) 73 | { 74 | var i, rev; 75 | 76 | for (var i = rev = 0; i < NumBits; i++) { 77 | rev = (rev << 1) | (index & 1); 78 | index >>= 1; 79 | } 80 | 81 | return rev; 82 | } 83 | 84 | function ACInitFFT() 85 | { 86 | gFFTBitTable = []; 87 | 88 | var len = 2; 89 | for (var b = 1; b <= MaxFastBits; b++) { 90 | 91 | gFFTBitTable[b - 1] = new Int32Array(len); 92 | 93 | for (var i = 0; i < len; i++) 94 | gFFTBitTable[b - 1][i] = ReverseBits(i, b); 95 | 96 | len <<= 1; 97 | } 98 | 99 | console.log("ACFFT initiliazed"); 100 | } 101 | 102 | function DeinitFFT() 103 | { 104 | if (gFFTBitTable) { 105 | for (var b = 1; b <= MaxFastBits; b++) { 106 | gFFTBitTable[b-1] = undefined; 107 | } 108 | gFFTBitTable = undefined; 109 | } 110 | } 111 | 112 | function FastReverseBits( i, NumBits) 113 | { 114 | if (NumBits <= MaxFastBits) 115 | return gFFTBitTable[NumBits - 1][i]; 116 | else 117 | return ReverseBits(i, NumBits); 118 | } 119 | 120 | /* 121 | * Complex Fast Fourier Transform 122 | */ 123 | 124 | function ACFFT( NumSamples, 125 | InverseTransform, 126 | RealIn, ImagIn, RealOut, ImagOut) 127 | { 128 | var NumBits; /* Number of bits needed to store indices */ 129 | var i, j, k, n; 130 | var BlockSize, BlockEnd; 131 | 132 | var angle_numerator = 2.0 * Math.PI; 133 | var tr, ti; /* temp real, temp imaginary */ 134 | 135 | if (!IsPowerOfTwo(NumSamples)) { 136 | console.log(NumSamples + " is not a power of two"); 137 | return 1; 138 | } 139 | 140 | if (!gFFTBitTable) 141 | ACInitFFT(); 142 | 143 | if (!InverseTransform) 144 | angle_numerator = -angle_numerator; 145 | 146 | NumBits = NumberOfBitsNeeded(NumSamples); 147 | 148 | /* 149 | ** Do simultaneous data copy and bit-reversal ordering into outputs... 150 | */ 151 | 152 | for (var i = 0; i < NumSamples; i++) { 153 | j = FastReverseBits(i, NumBits); 154 | RealOut[j] = RealIn[i]; 155 | ImagOut[j] = (ImagIn === undefined) ? 0.0 : ImagIn[i]; 156 | } 157 | 158 | /* 159 | ** Do the FFT itself... 160 | */ 161 | 162 | BlockEnd = 1; 163 | for (BlockSize = 2; BlockSize <= NumSamples; BlockSize <<= 1) { 164 | 165 | var delta_angle = angle_numerator / BlockSize; 166 | 167 | var sm2 = Math.sin(-2 * delta_angle); 168 | var sm1 = Math.sin(-delta_angle); 169 | var cm2 = Math.cos(-2 * delta_angle); 170 | var cm1 = Math.cos(-delta_angle); 171 | var w = 2 * cm1; 172 | var ar0, ar1, ar2, ai0, ai1, ai2; 173 | 174 | for (var i = 0; i < NumSamples; i += BlockSize) { 175 | ar2 = cm2; 176 | ar1 = cm1; 177 | 178 | ai2 = sm2; 179 | ai1 = sm1; 180 | 181 | for (var j = i, n = 0; n < BlockEnd; j++, n++) { 182 | ar0 = w * ar1 - ar2; 183 | ar2 = ar1; 184 | ar1 = ar0; 185 | 186 | ai0 = w * ai1 - ai2; 187 | ai2 = ai1; 188 | ai1 = ai0; 189 | 190 | k = j + BlockEnd; 191 | tr = ar0 * RealOut[k] - ai0 * ImagOut[k]; 192 | ti = ar0 * ImagOut[k] + ai0 * RealOut[k]; 193 | 194 | RealOut[k] = RealOut[j] - tr; 195 | ImagOut[k] = ImagOut[j] - ti; 196 | 197 | RealOut[j] += tr; 198 | ImagOut[j] += ti; 199 | } 200 | } 201 | 202 | BlockEnd = BlockSize; 203 | } 204 | 205 | /* 206 | ** Need to normalize if inverse transform... 207 | */ 208 | 209 | if (InverseTransform) { 210 | var denom = NumSamples; 211 | 212 | for (var i = 0; i < NumSamples; i++) { 213 | RealOut[i] /= denom; 214 | ImagOut[i] /= denom; 215 | } 216 | } 217 | } 218 | 219 | /* 220 | * Real Fast Fourier Transform 221 | * 222 | * This function was based on the code in Numerical Recipes in C. 223 | * In Num. Rec., the inner loop is based on a Math.single 1-based array 224 | * of interleaved real and imaginary numbers. Because we have two 225 | * separate zero-based arrays, our indices are quite different. 226 | * Here is the correspondence between Num. Rec. indices and our indices: 227 | * 228 | * i1 <-> real[i] 229 | * i2 <-> imag[i] 230 | * i3 <-> real[n/2-i] 231 | * i4 <-> imag[n/2-i] 232 | */ 233 | 234 | function RealFFT( NumSamples, RealIn, RealOut, ImagOut) 235 | { 236 | 237 | var Half = NumSamples / 2; 238 | var i; 239 | 240 | var theta = Math.PI / Half; 241 | 242 | var tmpReal = new Float32Array(Half); 243 | var tmpImag = new Float32Array(Half); 244 | 245 | for (var i = 0; i < Half; i++) { 246 | tmpReal[i] = RealIn[2 * i]; 247 | tmpImag[i] = RealIn[2 * i + 1]; 248 | } 249 | 250 | ACFFT(Half, 0, tmpReal, tmpImag, RealOut, ImagOut); 251 | 252 | var wtemp = (Math.sin(0.5 * theta)); 253 | 254 | var wpr = -2.0 * wtemp * wtemp; 255 | var wpi = -1.0 * (Math.sin(theta)); 256 | var wr = 1.0 + wpr; 257 | var wi = wpi; 258 | 259 | var i3; 260 | 261 | var h1r, h1i, h2r, h2i; 262 | 263 | for (var i = 1; i < Half / 2; i++) { 264 | 265 | i3 = Half - i; 266 | 267 | h1r = 0.5 * (RealOut[i] + RealOut[i3]); 268 | h1i = 0.5 * (ImagOut[i] - ImagOut[i3]); 269 | h2r = 0.5 * (ImagOut[i] + ImagOut[i3]); 270 | h2i = -0.5 * (RealOut[i] - RealOut[i3]); 271 | 272 | RealOut[i] = h1r + wr * h2r - wi * h2i; 273 | ImagOut[i] = h1i + wr * h2i + wi * h2r; 274 | RealOut[i3] = h1r - wr * h2r + wi * h2i; 275 | ImagOut[i3] = -h1i + wr * h2i + wi * h2r; 276 | 277 | wr = (wtemp = wr) * wpr - wi * wpi + wr; 278 | wi = wi * wpr + wtemp * wpi + wi; 279 | } 280 | 281 | RealOut[0] = (h1r = RealOut[0]) + ImagOut[0]; 282 | ImagOut[0] = h1r - ImagOut[0]; 283 | } 284 | 285 | 286 | /* 287 | * PowerSpectrum 288 | * 289 | * This function computes the same as RealFFT, above, but 290 | * adds the squares of the real and imaginary part of each 291 | * coefficient, extracting the power and throwing away the 292 | * phase. 293 | * 294 | * For speed, it does not call RealFFT, but duplicates some 295 | * of its code. 296 | */ 297 | 298 | function PowerSpectrum( NumSamples, In, Out) 299 | { 300 | var Half = NumSamples / 2; 301 | var i; 302 | 303 | var theta = Math.PI / Half; 304 | 305 | var tmpReal = new Float32Array(Half); 306 | var tmpImag = new Float32Array(Half); 307 | var RealOut = new Float32Array(Half); 308 | var ImagOut = new Float32Array(Half); 309 | 310 | for (var i = 0; i < Half; i++) { 311 | tmpReal[i] = In[2 * i]; 312 | tmpImag[i] = In[2 * i + 1]; 313 | } 314 | 315 | ACFFT(Half, 0, tmpReal, tmpImag, RealOut, ImagOut); 316 | 317 | var wtemp = (Math.sin(0.5 * theta)); 318 | 319 | var wpr = -2.0 * wtemp * wtemp; 320 | var wpi = -1.0 * (Math.sin(theta)); 321 | var wr = 1.0 + wpr; 322 | var wi = wpi; 323 | 324 | var i3; 325 | 326 | var h1r, h1i, h2r, h2i, rt, it; 327 | 328 | for (var i = 1; i < Half / 2; i++) { 329 | 330 | i3 = Half - i; 331 | 332 | h1r = 0.5 * (RealOut[i] + RealOut[i3]); 333 | h1i = 0.5 * (ImagOut[i] - ImagOut[i3]); 334 | h2r = 0.5 * (ImagOut[i] + ImagOut[i3]); 335 | h2i = -0.5 * (RealOut[i] - RealOut[i3]); 336 | 337 | rt = h1r + wr * h2r - wi * h2i; 338 | it = h1i + wr * h2i + wi * h2r; 339 | 340 | Out[i] = rt * rt + it * it; 341 | 342 | rt = h1r - wr * h2r + wi * h2i; 343 | it = -h1i + wr * h2i + wi * h2r; 344 | 345 | Out[i3] = rt * rt + it * it; 346 | 347 | wr = (wtemp = wr) * wpr - wi * wpi + wr; 348 | wi = wi * wpr + wtemp * wpi + wi; 349 | } 350 | 351 | rt = (h1r = RealOut[0]) + ImagOut[0]; 352 | it = h1r - ImagOut[0]; 353 | Out[0] = rt * rt + it * it; 354 | 355 | rt = RealOut[Half / 2]; 356 | it = ImagOut[Half / 2]; 357 | Out[Half / 2] = rt * rt + it * it; 358 | } 359 | 360 | /* 361 | * Windowing Functions 362 | */ 363 | 364 | function NumWindowFuncs() 365 | { 366 | return 10; 367 | } 368 | 369 | function WindowFuncName(whichFunction) 370 | { 371 | switch (whichFunction) { 372 | default: 373 | case 0: 374 | return "Rectangular"; 375 | case 1: 376 | return "Bartlett"; 377 | case 2: 378 | return "Hamming"; 379 | case 3: 380 | return "Hanning"; 381 | case 4: 382 | return "Blackman"; 383 | case 5: 384 | return "Blackman-Harris"; 385 | case 6: 386 | return "Welch"; 387 | case 7: 388 | return "Gaussian(a=2.5)"; 389 | case 8: 390 | return "Gaussian(a=3.5)"; 391 | case 9: 392 | return "Gaussian(a=4.5)"; 393 | } 394 | } 395 | 396 | function WindowFunc( whichFunction, NumSamples, inData) 397 | { 398 | var i; 399 | var A; 400 | 401 | switch( whichFunction ) 402 | { 403 | case 1: 404 | // Bartlett (triangular) window 405 | for (var i = 0; i < NumSamples / 2; i++) { 406 | inData[i] *= (i / NumSamples / 2.0); 407 | inData[i + (NumSamples / 2)] *= 408 | (1.0 - (i / NumSamples / 2.0)); 409 | } 410 | break; 411 | case 2: 412 | // Hamming 413 | for (var i = 0; i < NumSamples; i++) 414 | inData[i] *= 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (NumSamples - 1)); 415 | break; 416 | case 3: 417 | // Hanning 418 | for (var i = 0; i < NumSamples; i++) 419 | inData[i] *= 0.50 - 0.50 * Math.cos(2 * Math.PI * i / (NumSamples - 1)); 420 | break; 421 | case 4: 422 | // Blackman 423 | for (var i = 0; i < NumSamples; i++) { 424 | inData[i] *= 0.42 - 0.5 * Math.cos (2 * Math.PI * i / (NumSamples - 1)) + 0.08 * Math.cos (4 * Math.PI * i / (NumSamples - 1)); 425 | } 426 | break; 427 | case 5: 428 | // Blackman-Harris 429 | for (var i = 0; i < NumSamples; i++) { 430 | inData[i] *= 0.35875 - 0.48829 * Math.cos(2 * Math.PI * i /(NumSamples-1)) + 0.14128 * Math.cos(4 * Math.PI * i/(NumSamples-1)) - 0.01168 * Math.cos(6 * Math.PI * i/(NumSamples-1)); 431 | } 432 | break; 433 | case 6: 434 | // Welch 435 | for (var i = 0; i < NumSamples; i++) { 436 | inData[i] *= 4*i/ NumSamples *(1-(i/NumSamples)); 437 | } 438 | break; 439 | case 7: 440 | // Gaussian (a=2.5) 441 | // Precalculate some values, and simplify the fmla to try and reduce overhead 442 | A=-2*2.5*2.5; 443 | 444 | for (var i = 0; i < NumSamples; i++) { 445 | 446 | inData[i] *= Math.exp(A*(0.25 + ((i/NumSamples)*(i/NumSamples)) - (i/NumSamples))); 447 | } 448 | break; 449 | case 8: 450 | // Gaussian (a=3.5) 451 | A=-2*3.5*3.5; 452 | for (var i = 0; i < NumSamples; i++) { 453 | // reduced 454 | inData[i] *= Math.exp(A*(0.25 + ((i/NumSamples)*(i/NumSamples)) - (i/NumSamples))); 455 | } 456 | break; 457 | case 9: 458 | // Gaussian (a=4.5) 459 | A=-2*4.5*4.5; 460 | 461 | for (var i = 0; i < NumSamples; i++) { 462 | // reduced 463 | inData[i] *= Math.exp(A*(0.25 + ((i/NumSamples)*(i/NumSamples)) - (i/NumSamples))); 464 | } 465 | break; 466 | default: 467 | 468 | } 469 | } 470 | 471 | // Indentation settings for Vim and Emacs and unique identifier for Arch, a 472 | // version control system. Please do not modify past this point. 473 | // 474 | // Local Variables: 475 | // c-basic-offset: 3 476 | // indent-tabs-mode: nil 477 | // End: 478 | // 479 | // vim: et sts=3 sw=3 480 | // arch-tag: 47691958-d393-488c-abc5-81178ea2686e 481 | 482 | -------------------------------------------------------------------------------- /app/js/audiosequence.js: -------------------------------------------------------------------------------- 1 | function AudioSequence() 2 | { 3 | /// Name of the audio sequence (used for channel identification) 4 | this.name = "unnamed"; 5 | this.sampleRate = 0; 6 | this.data = []; 7 | 8 | // gain level of the signal data (maximum value) 9 | this.gain = 0.0; 10 | 11 | /** 12 | * This function merges another sequence from with the same sampling rate 13 | * into this. 14 | * @param mergePosition optional position where the new data should be merged (default is the end of the data block) 15 | * */ 16 | this.merge = function merge(otherAudioSequence, mergePosition) 17 | { 18 | // default parameters 19 | if (mergePosition === undefined) mergePosition = this.data.length; 20 | // requirement check 21 | if (otherAudioSequence.sampleRate !== this.sampleRate) throw "Samplerate does not match."; 22 | if (mergePosition < 0 || mergePosition > this.data.length) throw "Merge position is invalid!"; 23 | 24 | // create a new data block 25 | var newData = new Float32Array(this.data.length + otherAudioSequence.data.length); 26 | 27 | // combine data 28 | newData.set(this.data.subarray(0, mergePosition)); 29 | newData.set(otherAudioSequence.data, mergePosition); 30 | newData.set(this.data.subarray(mergePosition), mergePosition + otherAudioSequence.data.length); 31 | 32 | // set new references 33 | this.data = newData; 34 | 35 | // update gain value 36 | this.gain = this.getGain(); 37 | }; 38 | 39 | /** 40 | * Cuts off a part of the data sequence 41 | * @param start beginning of the trim 42 | * @param len optional len length of the trim (default is till the end of the data block) 43 | **/ 44 | this.trim = function trim(start, len) 45 | { 46 | // default parameter 47 | if (len === undefined) len = this.data.length - start; 48 | 49 | if (start >= this.data.length || start < 0) throw "The start is invalid"; 50 | if (start + len > this.data.length || len < 0) throw "The length is invalid."; 51 | 52 | // create a new data block 53 | var newData = new Float32Array(this.data.length - len); 54 | 55 | // copy relevant fragments of data 56 | newData.set(this.data.subarray(0, start)); 57 | newData.set(this.data.subarray(start + len), start); 58 | 59 | // set new references 60 | this.data = newData; 61 | 62 | // update gain value 63 | this.gain = this.getGain(); 64 | }; 65 | 66 | /** 67 | * Create a clone of this sequence. Optionally the clone can be partial 68 | * @param start Optional beginning of the data block which will be cloned (default is 0) 69 | * @param len Optional len of the data block which will be cloned (default is till the end of the data block) 70 | */ 71 | this.clone = function clone(start, len) 72 | { 73 | // default parameter 74 | if (start === undefined) start = 0; 75 | if (len === undefined) len = this.data.length - start; 76 | 77 | // requirement check 78 | if (start < 0 || start > this.data.length) throw "Invalid start parameter."; 79 | if (len < 0 || len + start > this.data.length) throw "Invalid len parameter."; 80 | 81 | // create new instance and copy array fragment 82 | var clonedSequence = CreateNewAudioSequence(this.sampleRate); 83 | clonedSequence.data = this.data.slice(start, start+len); 84 | 85 | // Update the gain for the cloned sequence 86 | clonedSequence.gain = clonedSequence.getGain(); 87 | return clonedSequence; 88 | }; 89 | 90 | /** 91 | * Creates a sequence with a specified length of data with value 0 92 | * @param len length of the 0 sequence 93 | * @param start optional insertion point for the 0 sequence (default is the end of the data block) 94 | */ 95 | this.createZeroData = function createZeroData(len, start) 96 | { 97 | var emptyData = []; 98 | var i = len + 1; 99 | while(--i) 100 | { 101 | emptyData.push(0); 102 | } 103 | 104 | var tmpSequence = CreateNewAudioSequence(this.sampleRate, emptyData); 105 | this.merge(tmpSequence, start); 106 | 107 | // update gain value 108 | this.gain = this.getGain(); 109 | }; 110 | 111 | /** 112 | * Copies the data into a complex array 113 | * @param start optional beginning of the data point (default is 0) 114 | * @param len optional length of the data sequence (default is till the end of the data block) 115 | */ 116 | this.toComplexSequence = function toComplexSequence(start, len) 117 | { 118 | // default parameter 119 | if (start === undefined) start = 0; 120 | if (len === undefined) len = this.data.length - start; 121 | 122 | // requirement check 123 | if (start < 0 || start > this.data.length) throw "start parameter is invalid."; 124 | if (len < 0 || len + start > this.data.length) throw "end parameter is invalid."; 125 | 126 | var result = []; 127 | 128 | for (var i = start; i < start + len; ++i) 129 | { 130 | result.push(this.data[i]); 131 | result.push(0); 132 | } 133 | 134 | return result; 135 | }; 136 | 137 | /** 138 | * Overwrites the data with the given complex array data 139 | * @param complexArray the complex array which gets real value gets copied 140 | * @param start optional beginning in the data point (default is 0) 141 | * @param len optional length of the data sequence (default is till the end of the data block) 142 | */ 143 | this.fromComplexSequence = function fromComplexSequence(complexArray, start, len) 144 | { 145 | // default parameter 146 | if (start === undefined) start = 0; 147 | if (len === undefined) len = this.data.length - start; 148 | 149 | // requirement check 150 | if (complexArray.length / 2 !== len) throw "length of complex array does not match"; 151 | if (complexArray.length % 2 !== 0) throw "the length of the complex array is totally wrong"; 152 | if (start < 0 || start > this.data.length) throw "start parameter is invalid."; 153 | if (len < 0 || len + start > this.data.length) throw "end parameter is invalid."; 154 | 155 | var complexArrayIdx = 0; 156 | for (var i = start; i < start + len; ++i) 157 | { 158 | this.data[i] = complexArray[complexArrayIdx]; 159 | complexArrayIdx += 2; 160 | } 161 | 162 | // update gain value 163 | this.gain = this.getGain(); 164 | }; 165 | 166 | /** 167 | * Returns the gain (maximum amplitude) 168 | * @param start optional beginning in the data point (default is 0) 169 | * @param len optional length of the data sequence (default is till the end of the data block) 170 | */ 171 | this.getGain = function getGain(start, len) 172 | { 173 | // default parameter 174 | if (start === undefined) start = 0; 175 | if (len === undefined) len = this.data.length - start; 176 | 177 | // requirement check 178 | if (start < 0 || start > this.data.length) throw "start parameter is invalid."; 179 | if (len < 0 || len + start > this.data.length) throw "end parameter is invalid."; 180 | 181 | var result = 0.0; 182 | for(var i = start; i < start + len; ++i) 183 | { 184 | // the amplitude could be positive or negative 185 | var absValue = Math.abs(this.data[i]); 186 | result = Math.max(result, absValue); 187 | } 188 | return result; 189 | } 190 | 191 | /** 192 | * Returns the total length of this sequence in seconds 193 | **/ 194 | this.getLengthInSeconds = function getLengthInSeconds() 195 | { 196 | return this.data.length / this.sampleRate; 197 | } 198 | 199 | /** 200 | * Apply a normalize on the data block, which changes the data value to use the optimal bandwidth 201 | * @param start optional beginning in the data point (default is 0) 202 | * @param len optional length of the data sequence (default is till the end of the data block) 203 | */ 204 | this.filterNormalize = function filterNormalize(start, len) 205 | { 206 | // default parameter 207 | if (start === undefined) start = 0; 208 | if (len === undefined) len = this.data.length - start; 209 | 210 | // requirement check 211 | if (start < 0 || start > this.data.length) throw "start parameter is invalid."; 212 | if (len < 0 || len + start > this.data.length) throw "end parameter is invalid."; 213 | 214 | // do a amplitude correction of the sequence 215 | var gainLevel = this.getGain(start, len); 216 | var amplitudeCorrection = 1.0 / gainLevel; 217 | for (var i = start; i < start + len; ++i) 218 | { 219 | this.data[i] = this.data[i] * amplitudeCorrection; 220 | } 221 | 222 | // update gain value 223 | this.gain = this.getGain(); 224 | }; 225 | 226 | /** 227 | * Change the gain of the sequence. The result will give the sequence more or less amplitude 228 | * @param gainFactor the factor which will be applied to the sequence 229 | * @param start optional beginning in the data point (default is 0) 230 | * @param len optional length of the data sequence (default is till the end of the data block) 231 | */ 232 | this.filterGain = function filterGain(gainFactor, start, len) 233 | { 234 | // default parameter 235 | if (start === undefined) start = 0; 236 | if (len === undefined) len = this.data.length - start; 237 | 238 | // requirement check 239 | if (start < 0 || start > this.data.length) throw "start parameter is invalid."; 240 | if (len < 0 || len + start > this.data.length) throw "end parameter is invalid."; 241 | 242 | for (var i = start; i < start + len; ++i) 243 | { 244 | this.data[i] = this.data[i] * gainFactor; 245 | } 246 | 247 | // update gain value 248 | this.gain = this.getGain(); 249 | }; 250 | 251 | /** 252 | * Sets the data block to 0 (no amplitude = silence) 253 | * @param start optional beginning in the data point (default is 0) 254 | * @param len optional length of the data sequence (default is till the end of the data block) 255 | */ 256 | this.filterSilence = function filterSilence(start, len) 257 | { 258 | this.filterGain(0.0, start, len); 259 | } 260 | 261 | /** 262 | * This function apply a fade effect on a given sequence range. The value of fadeStartGainFactor and fadeEndGainFactor 263 | * controls if the fade is an fadein or fadeout 264 | * @param fadeEndGainFactor The multiplier at the beginning of the fade 265 | * @param fadeEndGainFactor The multiplier at the end of the fade 266 | * @param start optional beginning in the data point (default is 0) 267 | * @param len optional length of the data sequence (default is till the end of the data block) 268 | */ 269 | this.filterLinearFade = function filterLinearFade(fadeStartGainFactor, fadeEndGainFactor, start, len) 270 | { 271 | // default parameter 272 | if (start === undefined) start = 0; 273 | if (len === undefined) len = this.data.length - start; 274 | 275 | // requirement check 276 | if (start < 0 || start > this.data.length) throw "start parameter is invalid."; 277 | if (len < 0 || len + start > this.data.length) throw "end parameter is invalid."; 278 | 279 | var fadeGainMultiplier = 0.0; 280 | var fadePos = 0.0; 281 | for (var i = start; i < start + len; ++i) 282 | { 283 | fadePos = (i - start) / len; 284 | fadeGainMultiplier = MathEx.lerp(fadeStartGainFactor, fadeEndGainFactor, fadePos); 285 | 286 | this.data[i] = this.data[i] * fadeGainMultiplier; 287 | 288 | } 289 | 290 | // update gain value 291 | this.gain = this.getGain(); 292 | }; 293 | 294 | /** 295 | * Process an reverse of the data block 296 | */ 297 | this.filterReverse = function filterReverse() 298 | { 299 | this.data.reverse(); 300 | }; 301 | 302 | this.createTestTone = function createTestTone(frequency, sampleLength) 303 | { 304 | var data = []; 305 | var f = frequency / this.sampleRate; 306 | for (var i = 0; i < sampleLength; ++i) 307 | { 308 | data.push((Math.cos(2.0 * Math.PI * i * f) + 309 | Math.cos(2.0 * Math.PI * i * f * 1)) / 2); 310 | } 311 | 312 | this.data = data; 313 | }; 314 | } 315 | 316 | /** 317 | * Creates a new empty or with data filled sequence with the given sample rate 318 | * @param sampleRate final samplerate of the sequence 319 | * @param data optional initialization data of the sequence 320 | */ 321 | function CreateNewAudioSequence(sampleRate, data) 322 | { 323 | var sequence = new AudioSequence(); 324 | sequence.sampleRate = sampleRate; 325 | sequence.data = []; 326 | if (data !== undefined) 327 | { 328 | sequence.data = data.slice(0, data.length); 329 | } 330 | return sequence; 331 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |HTML5 Audio Editor 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 44 |45 |46 |65 |47 |64 |48 | 62 | 63 |
66 |67 |85 |68 |73 |Spectrum
69 |70 | 71 |72 |74 |84 |Editor
75 |76 |78 | 83 |77 |
86 |87 |104 |88 |103 |89 | 101 | 102 |105 |111 |106 |110 |107 | 108 |109 |
112 |113 |203 | 204 |114 |115 |167 |Gain
116 |117 |166 |Change the volume of the selected audio sequence with the given gain multiplicator.
118 | 119 |120 |127 | 165 |121 | 122 | 123 | 124 | 125 |126 |168 |175 |Normalize
169 | 174 |176 |183 |Silence
177 | 182 |184 |191 |Fade In
185 | 190 |192 |199 |Fade Out
193 | 198 |200 | 201 |202 |205 |209 | 210 | 212 | 223 | 224 | 225 | 227 | 239 | 240 | 242 | 259 | 260 | 261 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 318 | 319 | 320 | -------------------------------------------------------------------------------- /bootstrap/css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.0.4 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | 11 | .clearfix { 12 | *zoom: 1; 13 | } 14 | 15 | .clearfix:before, 16 | .clearfix:after { 17 | display: table; 18 | content: ""; 19 | } 20 | 21 | .clearfix:after { 22 | clear: both; 23 | } 24 | 25 | .hide-text { 26 | font: 0/0 a; 27 | color: transparent; 28 | text-shadow: none; 29 | background-color: transparent; 30 | border: 0; 31 | } 32 | 33 | .input-block-level { 34 | display: block; 35 | width: 100%; 36 | min-height: 28px; 37 | -webkit-box-sizing: border-box; 38 | -moz-box-sizing: border-box; 39 | -ms-box-sizing: border-box; 40 | box-sizing: border-box; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | visibility: hidden; 46 | } 47 | 48 | .visible-phone { 49 | display: none !important; 50 | } 51 | 52 | .visible-tablet { 53 | display: none !important; 54 | } 55 | 56 | .hidden-desktop { 57 | display: none !important; 58 | } 59 | 60 | @media (max-width: 767px) { 61 | .visible-phone { 62 | display: inherit !important; 63 | } 64 | .hidden-phone { 65 | display: none !important; 66 | } 67 | .hidden-desktop { 68 | display: inherit !important; 69 | } 70 | .visible-desktop { 71 | display: none !important; 72 | } 73 | } 74 | 75 | @media (min-width: 768px) and (max-width: 979px) { 76 | .visible-tablet { 77 | display: inherit !important; 78 | } 79 | .hidden-tablet { 80 | display: none !important; 81 | } 82 | .hidden-desktop { 83 | display: inherit !important; 84 | } 85 | .visible-desktop { 86 | display: none !important ; 87 | } 88 | } 89 | 90 | @media (max-width: 480px) { 91 | .nav-collapse { 92 | -webkit-transform: translate3d(0, 0, 0); 93 | } 94 | .page-header h1 small { 95 | display: block; 96 | line-height: 18px; 97 | } 98 | input[type="checkbox"], 99 | input[type="radio"] { 100 | border: 1px solid #ccc; 101 | } 102 | .form-horizontal .control-group > label { 103 | float: none; 104 | width: auto; 105 | padding-top: 0; 106 | text-align: left; 107 | } 108 | .form-horizontal .controls { 109 | margin-left: 0; 110 | } 111 | .form-horizontal .control-list { 112 | padding-top: 0; 113 | } 114 | .form-horizontal .form-actions { 115 | padding-right: 10px; 116 | padding-left: 10px; 117 | } 118 | .modal { 119 | position: absolute; 120 | top: 10px; 121 | right: 10px; 122 | left: 10px; 123 | width: auto; 124 | margin: 0; 125 | } 126 | .modal.fade.in { 127 | top: auto; 128 | } 129 | .modal-header .close { 130 | padding: 10px; 131 | margin: -10px; 132 | } 133 | .carousel-caption { 134 | position: static; 135 | } 136 | } 137 | 138 | @media (max-width: 767px) { 139 | body { 140 | padding-right: 20px; 141 | padding-left: 20px; 142 | } 143 | .navbar-fixed-top, 144 | .navbar-fixed-bottom { 145 | margin-right: -20px; 146 | margin-left: -20px; 147 | } 148 | .container-fluid { 149 | padding: 0; 150 | } 151 | .dl-horizontal dt { 152 | float: none; 153 | width: auto; 154 | clear: none; 155 | text-align: left; 156 | } 157 | .dl-horizontal dd { 158 | margin-left: 0; 159 | } 160 | .container { 161 | width: auto; 162 | } 163 | .row-fluid { 164 | width: 100%; 165 | } 166 | .row, 167 | .thumbnails { 168 | margin-left: 0; 169 | } 170 | [class*="span"], 171 | .row-fluid [class*="span"] { 172 | display: block; 173 | float: none; 174 | width: auto; 175 | margin-left: 0; 176 | } 177 | .input-large, 178 | .input-xlarge, 179 | .input-xxlarge, 180 | input[class*="span"], 181 | select[class*="span"], 182 | textarea[class*="span"], 183 | .uneditable-input { 184 | display: block; 185 | width: 100%; 186 | min-height: 28px; 187 | -webkit-box-sizing: border-box; 188 | -moz-box-sizing: border-box; 189 | -ms-box-sizing: border-box; 190 | box-sizing: border-box; 191 | } 192 | .input-prepend input, 193 | .input-append input, 194 | .input-prepend input[class*="span"], 195 | .input-append input[class*="span"] { 196 | display: inline-block; 197 | width: auto; 198 | } 199 | } 200 | 201 | @media (min-width: 768px) and (max-width: 979px) { 202 | .row { 203 | margin-left: -20px; 204 | *zoom: 1; 205 | } 206 | .row:before, 207 | .row:after { 208 | display: table; 209 | content: ""; 210 | } 211 | .row:after { 212 | clear: both; 213 | } 214 | [class*="span"] { 215 | float: left; 216 | margin-left: 20px; 217 | } 218 | .container, 219 | .navbar-fixed-top .container, 220 | .navbar-fixed-bottom .container { 221 | width: 724px; 222 | } 223 | .span12 { 224 | width: 724px; 225 | } 226 | .span11 { 227 | width: 662px; 228 | } 229 | .span10 { 230 | width: 600px; 231 | } 232 | .span9 { 233 | width: 538px; 234 | } 235 | .span8 { 236 | width: 476px; 237 | } 238 | .span7 { 239 | width: 414px; 240 | } 241 | .span6 { 242 | width: 352px; 243 | } 244 | .span5 { 245 | width: 290px; 246 | } 247 | .span4 { 248 | width: 228px; 249 | } 250 | .span3 { 251 | width: 166px; 252 | } 253 | .span2 { 254 | width: 104px; 255 | } 256 | .span1 { 257 | width: 42px; 258 | } 259 | .offset12 { 260 | margin-left: 764px; 261 | } 262 | .offset11 { 263 | margin-left: 702px; 264 | } 265 | .offset10 { 266 | margin-left: 640px; 267 | } 268 | .offset9 { 269 | margin-left: 578px; 270 | } 271 | .offset8 { 272 | margin-left: 516px; 273 | } 274 | .offset7 { 275 | margin-left: 454px; 276 | } 277 | .offset6 { 278 | margin-left: 392px; 279 | } 280 | .offset5 { 281 | margin-left: 330px; 282 | } 283 | .offset4 { 284 | margin-left: 268px; 285 | } 286 | .offset3 { 287 | margin-left: 206px; 288 | } 289 | .offset2 { 290 | margin-left: 144px; 291 | } 292 | .offset1 { 293 | margin-left: 82px; 294 | } 295 | .row-fluid { 296 | width: 100%; 297 | *zoom: 1; 298 | } 299 | .row-fluid:before, 300 | .row-fluid:after { 301 | display: table; 302 | content: ""; 303 | } 304 | .row-fluid:after { 305 | clear: both; 306 | } 307 | .row-fluid [class*="span"] { 308 | display: block; 309 | float: left; 310 | width: 100%; 311 | min-height: 28px; 312 | margin-left: 2.762430939%; 313 | *margin-left: 2.709239449638298%; 314 | -webkit-box-sizing: border-box; 315 | -moz-box-sizing: border-box; 316 | -ms-box-sizing: border-box; 317 | box-sizing: border-box; 318 | } 319 | .row-fluid [class*="span"]:first-child { 320 | margin-left: 0; 321 | } 322 | .row-fluid .span12 { 323 | width: 99.999999993%; 324 | *width: 99.9468085036383%; 325 | } 326 | .row-fluid .span11 { 327 | width: 91.436464082%; 328 | *width: 91.38327259263829%; 329 | } 330 | .row-fluid .span10 { 331 | width: 82.87292817100001%; 332 | *width: 82.8197366816383%; 333 | } 334 | .row-fluid .span9 { 335 | width: 74.30939226%; 336 | *width: 74.25620077063829%; 337 | } 338 | .row-fluid .span8 { 339 | width: 65.74585634900001%; 340 | *width: 65.6926648596383%; 341 | } 342 | .row-fluid .span7 { 343 | width: 57.182320438000005%; 344 | *width: 57.129128948638304%; 345 | } 346 | .row-fluid .span6 { 347 | width: 48.618784527%; 348 | *width: 48.5655930376383%; 349 | } 350 | .row-fluid .span5 { 351 | width: 40.055248616%; 352 | *width: 40.0020571266383%; 353 | } 354 | .row-fluid .span4 { 355 | width: 31.491712705%; 356 | *width: 31.4385212156383%; 357 | } 358 | .row-fluid .span3 { 359 | width: 22.928176794%; 360 | *width: 22.874985304638297%; 361 | } 362 | .row-fluid .span2 { 363 | width: 14.364640883%; 364 | *width: 14.311449393638298%; 365 | } 366 | .row-fluid .span1 { 367 | width: 5.801104972%; 368 | *width: 5.747913482638298%; 369 | } 370 | input, 371 | textarea, 372 | .uneditable-input { 373 | margin-left: 0; 374 | } 375 | input.span12, 376 | textarea.span12, 377 | .uneditable-input.span12 { 378 | width: 714px; 379 | } 380 | input.span11, 381 | textarea.span11, 382 | .uneditable-input.span11 { 383 | width: 652px; 384 | } 385 | input.span10, 386 | textarea.span10, 387 | .uneditable-input.span10 { 388 | width: 590px; 389 | } 390 | input.span9, 391 | textarea.span9, 392 | .uneditable-input.span9 { 393 | width: 528px; 394 | } 395 | input.span8, 396 | textarea.span8, 397 | .uneditable-input.span8 { 398 | width: 466px; 399 | } 400 | input.span7, 401 | textarea.span7, 402 | .uneditable-input.span7 { 403 | width: 404px; 404 | } 405 | input.span6, 406 | textarea.span6, 407 | .uneditable-input.span6 { 408 | width: 342px; 409 | } 410 | input.span5, 411 | textarea.span5, 412 | .uneditable-input.span5 { 413 | width: 280px; 414 | } 415 | input.span4, 416 | textarea.span4, 417 | .uneditable-input.span4 { 418 | width: 218px; 419 | } 420 | input.span3, 421 | textarea.span3, 422 | .uneditable-input.span3 { 423 | width: 156px; 424 | } 425 | input.span2, 426 | textarea.span2, 427 | .uneditable-input.span2 { 428 | width: 94px; 429 | } 430 | input.span1, 431 | textarea.span1, 432 | .uneditable-input.span1 { 433 | width: 32px; 434 | } 435 | } 436 | 437 | @media (min-width: 1200px) { 438 | .row { 439 | margin-left: -30px; 440 | *zoom: 1; 441 | } 442 | .row:before, 443 | .row:after { 444 | display: table; 445 | content: ""; 446 | } 447 | .row:after { 448 | clear: both; 449 | } 450 | [class*="span"] { 451 | float: left; 452 | margin-left: 30px; 453 | } 454 | .container, 455 | .navbar-fixed-top .container, 456 | .navbar-fixed-bottom .container { 457 | width: 1170px; 458 | } 459 | .span12 { 460 | width: 1170px; 461 | } 462 | .span11 { 463 | width: 1070px; 464 | } 465 | .span10 { 466 | width: 970px; 467 | } 468 | .span9 { 469 | width: 870px; 470 | } 471 | .span8 { 472 | width: 770px; 473 | } 474 | .span7 { 475 | width: 670px; 476 | } 477 | .span6 { 478 | width: 570px; 479 | } 480 | .span5 { 481 | width: 470px; 482 | } 483 | .span4 { 484 | width: 370px; 485 | } 486 | .span3 { 487 | width: 270px; 488 | } 489 | .span2 { 490 | width: 170px; 491 | } 492 | .span1 { 493 | width: 70px; 494 | } 495 | .offset12 { 496 | margin-left: 1230px; 497 | } 498 | .offset11 { 499 | margin-left: 1130px; 500 | } 501 | .offset10 { 502 | margin-left: 1030px; 503 | } 504 | .offset9 { 505 | margin-left: 930px; 506 | } 507 | .offset8 { 508 | margin-left: 830px; 509 | } 510 | .offset7 { 511 | margin-left: 730px; 512 | } 513 | .offset6 { 514 | margin-left: 630px; 515 | } 516 | .offset5 { 517 | margin-left: 530px; 518 | } 519 | .offset4 { 520 | margin-left: 430px; 521 | } 522 | .offset3 { 523 | margin-left: 330px; 524 | } 525 | .offset2 { 526 | margin-left: 230px; 527 | } 528 | .offset1 { 529 | margin-left: 130px; 530 | } 531 | .row-fluid { 532 | width: 100%; 533 | *zoom: 1; 534 | } 535 | .row-fluid:before, 536 | .row-fluid:after { 537 | display: table; 538 | content: ""; 539 | } 540 | .row-fluid:after { 541 | clear: both; 542 | } 543 | .row-fluid [class*="span"] { 544 | display: block; 545 | float: left; 546 | width: 100%; 547 | min-height: 28px; 548 | margin-left: 2.564102564%; 549 | *margin-left: 2.510911074638298%; 550 | -webkit-box-sizing: border-box; 551 | -moz-box-sizing: border-box; 552 | -ms-box-sizing: border-box; 553 | box-sizing: border-box; 554 | } 555 | .row-fluid [class*="span"]:first-child { 556 | margin-left: 0; 557 | } 558 | .row-fluid .span12 { 559 | width: 100%; 560 | *width: 99.94680851063829%; 561 | } 562 | .row-fluid .span11 { 563 | width: 91.45299145300001%; 564 | *width: 91.3997999636383%; 565 | } 566 | .row-fluid .span10 { 567 | width: 82.905982906%; 568 | *width: 82.8527914166383%; 569 | } 570 | .row-fluid .span9 { 571 | width: 74.358974359%; 572 | *width: 74.30578286963829%; 573 | } 574 | .row-fluid .span8 { 575 | width: 65.81196581200001%; 576 | *width: 65.7587743226383%; 577 | } 578 | .row-fluid .span7 { 579 | width: 57.264957265%; 580 | *width: 57.2117657756383%; 581 | } 582 | .row-fluid .span6 { 583 | width: 48.717948718%; 584 | *width: 48.6647572286383%; 585 | } 586 | .row-fluid .span5 { 587 | width: 40.170940171000005%; 588 | *width: 40.117748681638304%; 589 | } 590 | .row-fluid .span4 { 591 | width: 31.623931624%; 592 | *width: 31.5707401346383%; 593 | } 594 | .row-fluid .span3 { 595 | width: 23.076923077%; 596 | *width: 23.0237315876383%; 597 | } 598 | .row-fluid .span2 { 599 | width: 14.529914530000001%; 600 | *width: 14.4767230406383%; 601 | } 602 | .row-fluid .span1 { 603 | width: 5.982905983%; 604 | *width: 5.929714493638298%; 605 | } 606 | input, 607 | textarea, 608 | .uneditable-input { 609 | margin-left: 0; 610 | } 611 | input.span12, 612 | textarea.span12, 613 | .uneditable-input.span12 { 614 | width: 1160px; 615 | } 616 | input.span11, 617 | textarea.span11, 618 | .uneditable-input.span11 { 619 | width: 1060px; 620 | } 621 | input.span10, 622 | textarea.span10, 623 | .uneditable-input.span10 { 624 | width: 960px; 625 | } 626 | input.span9, 627 | textarea.span9, 628 | .uneditable-input.span9 { 629 | width: 860px; 630 | } 631 | input.span8, 632 | textarea.span8, 633 | .uneditable-input.span8 { 634 | width: 760px; 635 | } 636 | input.span7, 637 | textarea.span7, 638 | .uneditable-input.span7 { 639 | width: 660px; 640 | } 641 | input.span6, 642 | textarea.span6, 643 | .uneditable-input.span6 { 644 | width: 560px; 645 | } 646 | input.span5, 647 | textarea.span5, 648 | .uneditable-input.span5 { 649 | width: 460px; 650 | } 651 | input.span4, 652 | textarea.span4, 653 | .uneditable-input.span4 { 654 | width: 360px; 655 | } 656 | input.span3, 657 | textarea.span3, 658 | .uneditable-input.span3 { 659 | width: 260px; 660 | } 661 | input.span2, 662 | textarea.span2, 663 | .uneditable-input.span2 { 664 | width: 160px; 665 | } 666 | input.span1, 667 | textarea.span1, 668 | .uneditable-input.span1 { 669 | width: 60px; 670 | } 671 | .thumbnails { 672 | margin-left: -30px; 673 | } 674 | .thumbnails > li { 675 | margin-left: 30px; 676 | } 677 | .row-fluid .thumbnails { 678 | margin-left: 0; 679 | } 680 | } 681 | 682 | @media (max-width: 979px) { 683 | body { 684 | padding-top: 0; 685 | } 686 | .navbar-fixed-top, 687 | .navbar-fixed-bottom { 688 | position: static; 689 | } 690 | .navbar-fixed-top { 691 | margin-bottom: 18px; 692 | } 693 | .navbar-fixed-bottom { 694 | margin-top: 18px; 695 | } 696 | .navbar-fixed-top .navbar-inner, 697 | .navbar-fixed-bottom .navbar-inner { 698 | padding: 5px; 699 | } 700 | .navbar .container { 701 | width: auto; 702 | padding: 0; 703 | } 704 | .navbar .brand { 705 | padding-right: 10px; 706 | padding-left: 10px; 707 | margin: 0 0 0 -5px; 708 | } 709 | .nav-collapse { 710 | clear: both; 711 | } 712 | .nav-collapse .nav { 713 | float: none; 714 | margin: 0 0 9px; 715 | } 716 | .nav-collapse .nav > li { 717 | float: none; 718 | } 719 | .nav-collapse .nav > li > a { 720 | margin-bottom: 2px; 721 | } 722 | .nav-collapse .nav > .divider-vertical { 723 | display: none; 724 | } 725 | .nav-collapse .nav .nav-header { 726 | color: #999999; 727 | text-shadow: none; 728 | } 729 | .nav-collapse .nav > li > a, 730 | .nav-collapse .dropdown-menu a { 731 | padding: 6px 15px; 732 | font-weight: bold; 733 | color: #999999; 734 | -webkit-border-radius: 3px; 735 | -moz-border-radius: 3px; 736 | border-radius: 3px; 737 | } 738 | .nav-collapse .btn { 739 | padding: 4px 10px 4px; 740 | font-weight: normal; 741 | -webkit-border-radius: 4px; 742 | -moz-border-radius: 4px; 743 | border-radius: 4px; 744 | } 745 | .nav-collapse .dropdown-menu li + li a { 746 | margin-bottom: 2px; 747 | } 748 | .nav-collapse .nav > li > a:hover, 749 | .nav-collapse .dropdown-menu a:hover { 750 | background-color: #222222; 751 | } 752 | .nav-collapse.in .btn-group { 753 | padding: 0; 754 | margin-top: 5px; 755 | } 756 | .nav-collapse .dropdown-menu { 757 | position: static; 758 | top: auto; 759 | left: auto; 760 | display: block; 761 | float: none; 762 | max-width: none; 763 | padding: 0; 764 | margin: 0 15px; 765 | background-color: transparent; 766 | border: none; 767 | -webkit-border-radius: 0; 768 | -moz-border-radius: 0; 769 | border-radius: 0; 770 | -webkit-box-shadow: none; 771 | -moz-box-shadow: none; 772 | box-shadow: none; 773 | } 774 | .nav-collapse .dropdown-menu:before, 775 | .nav-collapse .dropdown-menu:after { 776 | display: none; 777 | } 778 | .nav-collapse .dropdown-menu .divider { 779 | display: none; 780 | } 781 | .nav-collapse .navbar-form, 782 | .nav-collapse .navbar-search { 783 | float: none; 784 | padding: 9px 15px; 785 | margin: 9px 0; 786 | border-top: 1px solid #222222; 787 | border-bottom: 1px solid #222222; 788 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 789 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 790 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 791 | } 792 | .navbar .nav-collapse .nav.pull-right { 793 | float: none; 794 | margin-left: 0; 795 | } 796 | .nav-collapse, 797 | .nav-collapse.collapse { 798 | height: 0; 799 | overflow: hidden; 800 | } 801 | .navbar .btn-navbar { 802 | display: block; 803 | } 804 | .navbar-static .navbar-inner { 805 | padding-right: 10px; 806 | padding-left: 10px; 807 | } 808 | } 809 | 810 | @media (min-width: 980px) { 811 | .nav-collapse.collapse { 812 | height: auto !important; 813 | overflow: visible !important; 814 | } 815 | } 816 | -------------------------------------------------------------------------------- /bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(a){a(function(){"use strict",a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=c,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(a){return a||(this.paused=!0),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j=a.Event("slide");this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c);e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():f.interval&&e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning)return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning)return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c.type=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e,f,g;if(c.is(".disabled, :disabled"))return;return f=c.attr("data-target"),f||(f=c.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,"")),e=a(f),e.length||(e=c.parent()),g=e.hasClass("open"),d(),g||e.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown",".dropdown form",function(a){a.stopPropagation()}).on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,a.proxy(f,this)):f.call(this)):b&&b()}function f(){this.$backdrop.remove(),this.$backdrop=null}function g(){var b=this;this.isShown&&this.options.keyboard?a(document).on("keyup.dismiss.modal",function(a){a.which==27&&b.hide()}):this.isShown||a(document).off("keyup.dismiss.modal")}"use strict";var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this))};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;a("body").addClass("modal-open"),this.isShown=!0,g.call(this),e.call(this,function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in"),c?b.$element.one(a.support.transition.end,function(){b.$element.trigger("shown")}):b.$element.trigger("shown")})},hide:function(b){b&&b.preventDefault();var e=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,a("body").removeClass("modal-open"),g.call(this),this.$element.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?c.call(this):d.call(this)}},a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a(function(){a("body").on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({},e.data(),c.data());b.preventDefault(),e.modal(f)})})}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,this.options.trigger!="manual"&&(e=this.options.trigger=="hover"?"mouseenter":"focus",f=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(e,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f,this.options.selector,a.proxy(this.leave,this))),this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,b,this.$element.data()),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);if(!c.options.delay||!c.options.delay.show)return c.show();clearTimeout(this.timeout),c.hoverState="in",this.timeout=setTimeout(function(){c.hoverState=="in"&&c.show()},c.options.delay.show)},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!c.options.delay||!c.options.delay.hide)return c.hide();c.hoverState="out",this.timeout=setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide)},show:function(){var a,b,c,d,e,f,g;if(this.hasContent()&&this.enabled){a=this.tip(),this.setContent(),this.options.animation&&a.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,b=/in/.test(f),a.remove().css({top:0,left:0,display:"block"}).appendTo(b?this.$element:document.body),c=this.getPosition(b),d=a[0].offsetWidth,e=a[0].offsetHeight;switch(b?f.split(" ")[1]:f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}a.css(g).addClass(f).addClass("in")}},isHTML:function(a){return typeof a!="string"||a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3||/^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(a)},setContent:function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.isHTML(b)?"html":"text"](b),a.removeClass("fade in top bottom left right")},hide:function(){function d(){var b=setTimeout(function(){c.off(a.support.transition.end).remove()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.remove()})}var b=this,c=this.tip();c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d():c.remove()},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(b){return a.extend({},b?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()}},a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'206 | 207 |208 |',trigger:"hover",title:"",delay:0}}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.isHTML(b)?"html":"text"](b),a.find(".popover-content > *")[this.isHTML(c)?"html":"text"](c),a.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-content")||(typeof c.content=="function"?c.content.call(b[0]):c.content),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip}}),a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",content:"",template:''})}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body"),this.refresh(),this.process()}"use strict",b.prototype={constructor:b,refresh:function(){var b=this,c;this.offsets=a([]),this.targets=a([]),c=this.$body.find(this.selector).map(function(){var b=a(this),c=b.data("target")||b.attr("href"),d=/^#\w/.test(c)&&a(c);return d&&c.length&&[[d.position().top,c]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,c=b-this.$scrollElement.height(),d=this.offsets,e=this.targets,f=this.activeTarget,g;if(a>=c)return f!=(g=e.last()[0])&&this.activate(g);for(g=d.length;g--;)f!=e[g]&&a>=d[g]&&(!d[g+1]||a<=d[g+1])&&this.activate(e[g])},activate:function(b){var c,d;this.activeTarget=b,a(this.selector).parent(".active").removeClass("active"),d=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',c=a(d).parent("li").addClass("active"),c.parent(".dropdown-menu")&&(c=c.closest("li.dropdown").addClass("active")),c.trigger("activate")}},a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a(function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f,g;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active a").last()[0],g=a.Event("show",{relatedTarget:e}),b.trigger(g);if(g.isDefaultPrevented())return;f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}},a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a(function(){a("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=a(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(a)).change(),this.hide()},updater:function(a){return a},show:function(){var b=a.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:b.top+b.height,left:b.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c=this,d,e;return this.query=this.$element.val(),this.query?(d=a.grep(this.source,function(a){return c.matcher(a)}),d=this.sorter(d),d.length?this.render(d.slice(0,this.options.items)).show():this.shown?this.hide():this):this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){var b=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return a.replace(new RegExp("("+b+")","ig"),function(a,b){return""+b+""})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),(a.browser.webkit||a.browser.msie)&&this.$element.on("keydown",a.proxy(this.keypress,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this))},keyup:function(a){switch(a.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},keypress:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:if(a.type!="keydown")break;a.preventDefault(),this.prev();break;case 40:if(a.type!="keydown")break;a.preventDefault(),this.next()}a.stopPropagation()},blur:function(a){var b=this;setTimeout(function(){b.hide()},150)},click:function(a){a.stopPropagation(),a.preventDefault(),this.select()},mouseenter:function(b){this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")}},a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'',item:''},a.fn.typeahead.Constructor=b,a(function(){a("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;b.preventDefault(),c.typeahead(c.data())})})}(window.jQuery); --------------------------------------------------------------------------------