PageRenderTime 72ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 3ms

/build/js/main_compressed.js

https://github.com/ehsan/to_the_beat
JavaScript | 4404 lines | 3105 code | 1059 blank | 240 comment | 362 complexity | 97cebfccf9ce8d09f8d1033c011bd123 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. /* Copyright 2013 Chris Wilson
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. /*
  13. This monkeypatch library is intended to be included in projects that are
  14. written to the proper AudioContext spec (instead of webkitAudioContext),
  15. and that use the new naming and proper bits of the Web Audio API (e.g.
  16. using BufferSourceNode.start() instead of BufferSourceNode.noteOn()), but may
  17. have to run on systems that only support the deprecated bits.
  18. This library should be harmless to include if the browser supports
  19. unprefixed "AudioContext", and/or if it supports the new names.
  20. The patches this library handles:
  21. if window.AudioContext is unsupported, it will be aliased to webkitAudioContext().
  22. if AudioBufferSourceNode.start() is unimplemented, it will be routed to noteOn() or
  23. noteGrainOn(), depending on parameters.
  24. The following aliases only take effect if the new names are not already in place:
  25. AudioBufferSourceNode.stop() is aliased to noteOff()
  26. AudioContext.createGain() is aliased to createGainNode()
  27. AudioContext.createDelay() is aliased to createDelayNode()
  28. AudioContext.createScriptProcessor() is aliased to createJavaScriptNode()
  29. OscillatorNode.start() is aliased to noteOn()
  30. OscillatorNode.stop() is aliased to noteOff()
  31. AudioParam.setTargetAtTime() is aliased to setTargetValueAtTime()
  32. This library does NOT patch the enumerated type changes, as it is
  33. recommended in the specification that implementations support both integer
  34. and string types for AudioPannerNode.panningModel, AudioPannerNode.distanceModel
  35. BiquadFilterNode.type and OscillatorNode.type.
  36. */
  37. (function (global, exports, perf) {
  38. 'use strict';
  39. function fixSetTarget(param) {
  40. if (!param) // if NYI, just return
  41. return;
  42. if (!param.setTargetAtTime)
  43. param.setTargetAtTime = param.setTargetValueAtTime;
  44. }
  45. if (window.hasOwnProperty('webkitAudioContext') &&
  46. !window.hasOwnProperty('AudioContext')) {
  47. window.AudioContext = webkitAudioContext;
  48. AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain;
  49. AudioContext.prototype.createGain = function() {
  50. var node = this.internal_createGain();
  51. fixSetTarget(node.gain);
  52. return node;
  53. };
  54. AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay;
  55. AudioContext.prototype.createDelay = function() {
  56. var node = this.internal_createDelay();
  57. fixSetTarget(node.delayTime);
  58. return node;
  59. };
  60. AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource;
  61. AudioContext.prototype.createBufferSource = function() {
  62. var node = this.internal_createBufferSource();
  63. if (!node.start) {
  64. node.start = function ( when, offset, duration ) {
  65. if ( offset || duration )
  66. this.noteGrainOn( when, offset, duration );
  67. else
  68. this.noteOn( when );
  69. }
  70. }
  71. if (!node.stop)
  72. node.stop = node.noteoff;
  73. fixSetTarget(node.playbackRate);
  74. return node;
  75. };
  76. AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor;
  77. AudioContext.prototype.createDynamicsCompressor = function() {
  78. var node = this.internal_createDynamicsCompressor();
  79. fixSetTarget(node.threshold);
  80. fixSetTarget(node.knee);
  81. fixSetTarget(node.ratio);
  82. fixSetTarget(node.reduction);
  83. fixSetTarget(node.attack);
  84. fixSetTarget(node.release);
  85. return node;
  86. };
  87. AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter;
  88. AudioContext.prototype.createBiquadFilter = function() {
  89. var node = this.internal_createBiquadFilter();
  90. fixSetTarget(node.frequency);
  91. fixSetTarget(node.detune);
  92. fixSetTarget(node.Q);
  93. fixSetTarget(node.gain);
  94. return node;
  95. };
  96. if (AudioContext.prototype.hasOwnProperty( 'createOscillator' )) {
  97. AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator;
  98. AudioContext.prototype.createOscillator = function() {
  99. var node = this.internal_createOscillator();
  100. if (!node.start)
  101. node.start = node.noteOn;
  102. if (!node.stop)
  103. node.stop = node.noteOff;
  104. fixSetTarget(node.frequency);
  105. fixSetTarget(node.detune);
  106. return node;
  107. };
  108. }
  109. if (!AudioContext.prototype.hasOwnProperty('createGain'))
  110. AudioContext.prototype.createGain = AudioContext.prototype.createGainNode;
  111. if (!AudioContext.prototype.hasOwnProperty('createDelay'))
  112. AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode;
  113. if (!AudioContext.prototype.hasOwnProperty('createScriptProcessor'))
  114. AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createJavaScriptNode;
  115. }
  116. }(window));
  117. /**
  118. * @author alteredq / http://alteredqualia.com/
  119. * @author mr.doob / http://mrdoob.com/
  120. */
  121. Detector = {
  122. canvas: !! window.CanvasRenderingContext2D,
  123. webgl: ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(),
  124. workers: !! window.Worker,
  125. fileapi: window.File && window.FileReader && window.FileList && window.Blob,
  126. getWebGLErrorMessage: function () {
  127. var element = document.createElement( 'div' );
  128. element.id = 'webgl-error-message';
  129. element.style.fontFamily = 'monospace';
  130. element.style.fontSize = '13px';
  131. element.style.fontWeight = 'normal';
  132. element.style.textAlign = 'center';
  133. element.style.background = '#fff';
  134. element.style.color = '#000';
  135. element.style.padding = '1.5em';
  136. element.style.width = '400px';
  137. element.style.margin = '5em auto 0';
  138. if ( ! this.webgl ) {
  139. element.innerHTML = window.WebGLRenderingContext ? [
  140. 'Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">WebGL</a>.<br />',
  141. 'Find out how to get it <a href="http://get.webgl.org/" style="color:#000">here</a>.'
  142. ].join( '\n' ) : [
  143. 'Your browser does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">WebGL</a>.<br/>',
  144. 'Find out how to get it <a href="http://get.webgl.org/" style="color:#000">here</a>.'
  145. ].join( '\n' );
  146. }
  147. return element;
  148. },
  149. addGetWebGLMessage: function ( parameters ) {
  150. var parent, id, element;
  151. parameters = parameters || {};
  152. parent = parameters.parent !== undefined ? parameters.parent : document.body;
  153. id = parameters.id !== undefined ? parameters.id : 'oldie';
  154. element = Detector.getWebGLErrorMessage();
  155. element.id = id;
  156. parent.appendChild( element );
  157. }
  158. };
  159. // AudioDetector.js / https://github.com/sole/AudioDetector.js
  160. // Based on alteredq & mrdoob's Detector.js https://github.com/mrdoob/three.js/blob/master/examples/js/Detector.js
  161. AudioDetector = {
  162. REVISION: 3,
  163. webAudioSupport: typeof(window.AudioContext) === 'function' || typeof( window.webkitAudioContext ) === 'function',
  164. oggSupport: document.createElement('audio').canPlayType('audio/ogg'),
  165. errorMessages: {
  166. 'webAudioSupport': 'Your browser does not seem to support the <a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html" style="color:#000">Web Audio API</a>.<br/>' +
  167. 'Find out how to get it <a href="http://chromium.googlecode.com/svn/trunk/samples/audio/index.html" style="color:#000">here</a>.',
  168. 'oggSupport': 'Your browser does not seem to support OGG audio.<br/>' +
  169. 'Find out how to get it <a href="https://developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements" style="color:#000">here</a>.'
  170. },
  171. getErrorMessage: function ( message ) {
  172. var element = document.createElement( 'div' );
  173. element.style.fontFamily = 'monospace';
  174. element.style.fontSize = '13px';
  175. element.style.fontWeight = 'normal';
  176. element.style.textAlign = 'center';
  177. element.style.background = '#fff';
  178. element.style.color = '#000';
  179. element.style.padding = '1.5em';
  180. element.style.width = '350px';
  181. element.style.margin = '2em auto';
  182. element.style.zIndex = 1000;
  183. element.style.position = 'relative';
  184. element.style.top = '0px';
  185. element.style.left = '0px';
  186. element.style.border = '6px solid red';
  187. element.innerHTML = message;
  188. return element;
  189. },
  190. detects: function( conditions ) {
  191. if( ! ( conditions instanceof Array ) ) {
  192. this.showErrorMessage('<span style="font-size: 200%;">Oi!</span> <strong>conditions</strong> must be an Array with conditions to be checked');
  193. return false;
  194. }
  195. for(var i = 0; i < conditions.length; i++) {
  196. var propertyName = conditions[i];
  197. if(this.errorMessages[propertyName] !== undefined) {
  198. var checkResult = this[propertyName];
  199. if(!checkResult) {
  200. this.showErrorMessage(this.errorMessages[propertyName]);
  201. return false;
  202. }
  203. }
  204. }
  205. return true;
  206. },
  207. showErrorMessage: function ( message, parameters ) {
  208. var parent, id, element;
  209. parameters = parameters || {};
  210. parent = parameters.parent !== undefined ? parameters.parent : document.body;
  211. id = parameters.id !== undefined ? parameters.id : 'audio-error-message';
  212. element = AudioDetector.getErrorMessage(message);
  213. element.id = id;
  214. parent.appendChild( element );
  215. element.style.position = 'absolute';
  216. element.style.top = '100px';
  217. element.style.left = ((window.innerWidth - element.clientWidth) / 2) + 'px';
  218. }
  219. };
  220. // sorollet.js - http://github.com/sole/sorollet.js
  221. var SOROLLET = SOROLLET || {
  222. REVISION: '2'
  223. };
  224. SOROLLET.Math = {
  225. randSeed : 1,
  226. normalize: function(value, minimum, maximum) {
  227. return (value - minimum) / (maximum - minimum);
  228. },
  229. interpolate: function(normValue, minimum, maximum) {
  230. return minimum + (maximum - minimum) * normValue;
  231. },
  232. map: function(value, in_min, in_max, out_min, out_max) {
  233. if(in_min == in_max) {
  234. return out_min;
  235. }
  236. return out_min + (out_max - out_min) * (value - in_min) / (in_max - in_min);
  237. },
  238. randf : function() {
  239. this.randSeed *= 16807;
  240. return (this.randSeed) / 0x80000000;
  241. },
  242. clip: function(value, minV, maxV) {
  243. return Math.max(Math.min(value, maxV), minV);
  244. }
  245. }
  246. 'use strict';
  247. SOROLLET.Voice = function() {
  248. this.buffer = [];
  249. this.position = 0;
  250. this.currentNote = null;
  251. this.currentVolume = 0;
  252. this.internalSamplePosition = 0;
  253. this.setSamplingRate( 44100 );
  254. this.wave1Function = this.getSineBuffer;
  255. this.wave1Octave = 5;
  256. this.wave1Volume = 0.5;
  257. this.wave1Phase = 0;
  258. this.wave2Function = this.getSquareBuffer;
  259. this.wave2Octave = 4;
  260. this.wave2Volume = 0.5;
  261. this.wave2Phase = 0;
  262. this.waveMixFunction = this.mixAdd;
  263. this.noiseAmount = 0.0;
  264. this.noiseMixFunction = this.noiseAdd;
  265. this.volumeEnvelope = new SOROLLET.ADSR(0.5, 0, 1, 1, 1);
  266. this.pitchEnvelope = new SOROLLET.ADSR(0, 0, 1, 0, 1);
  267. this.volumeEnvelope.setOutputRange( 0, 1 );
  268. this.pitchEnvelope.setOutputRange( 0, 0 );
  269. };
  270. SOROLLET.Voice.prototype = {
  271. constructor: SOROLLET.Voice,
  272. setSamplingRate : function( value ) {
  273. this.samplingRate = value;
  274. this.inverseSamplingRate = 1.0 / value;
  275. },
  276. noteToFrequency: function(note, octave) {
  277. return 440.0 * Math.pow(2, ((note - 57.0 + (octave - 4.0) * 12.0) / 12.0));
  278. },
  279. zeroBuffer: function(buffer, numSamples) {
  280. for(var i = 0; i < numSamples; i++) {
  281. buffer[i] = 0;
  282. }
  283. },
  284. getTime: function() {
  285. return this.internalSamplePosition * this.inverseSamplingRate;
  286. },
  287. getSineBuffer : function getSineBuffer(buffer, numSamples, pos, frequency, phase) {
  288. var value,
  289. cst = 2.0 * Math.PI * frequency * this.inverseSamplingRate;
  290. for(var i = 0; i < numSamples; ++i) {
  291. value = Math.sin(cst * pos + phase);
  292. buffer[i] = value;
  293. pos++;
  294. }
  295. },
  296. getTriangleBuffer: function getTriangleBuffer(buffer, numSamples, pos, frequency, phase) {
  297. var period = 1.0 / frequency,
  298. semiperiod = period * 0.5,
  299. value,
  300. ft = semiperiod * 0.5;
  301. for(var i = 0; i < numSamples; ++i) {
  302. var t = (i + pos + phase) * this.inverseSamplingRate + ft;
  303. if(t % period < semiperiod) {
  304. value = 2.0 * ((t % semiperiod) / semiperiod) - 1.0;
  305. } else {
  306. value = 1.0 - 2.0 * (t % semiperiod) / semiperiod;
  307. }
  308. buffer[i] = value;
  309. }
  310. },
  311. getSquareBuffer: function getSquareBuffer(buffer, numSamples, pos, frequency, phase) {
  312. var period = 1.0 / frequency,
  313. halfPeriod = period * 0.5,
  314. value;
  315. for(var i = 0; i < numSamples; i++) {
  316. var t = (i + pos + phase) * this.inverseSamplingRate;
  317. if(t % period < halfPeriod) {
  318. value = 1.0;
  319. } else {
  320. value = -1.0;
  321. }
  322. buffer[i] = value;
  323. }
  324. },
  325. getSawtoothBuffer: function getSawtoothBuffer(buffer, numSamples, pos, frequency, phase) {
  326. var period = 1.0 / frequency,
  327. value;
  328. for(var i = 0; i < numSamples; i++) {
  329. var t = (pos + phase) * this.inverseSamplingRate;
  330. value = 2.0 * ((t % period) * frequency) - 1.0;
  331. buffer[i] = value;
  332. pos++;
  333. }
  334. },
  335. mixAdd: function(v1, v2) {
  336. return v1 + v2;
  337. },
  338. mixSubstract: function(v1, v2) {
  339. return v1 - v2;
  340. },
  341. mixMultiply: function(v1, v2) {
  342. return v1 * v2;
  343. },
  344. mixDivide: function(v1, v2) {
  345. if(v2 == 0) {
  346. v2 = 0.0001;
  347. }
  348. return v1 / v2;
  349. },
  350. noiseAdd: function(noiseValue, waveValue, notNoiseAmount) {
  351. return noiseValue + waveValue;
  352. },
  353. noiseMix: function(noiseValue, waveValue, notNoiseAmount) {
  354. return waveValue * notNoiseAmount + noiseValue;
  355. },
  356. noiseMultiply: function(noiseValue, waveValue, notNoiseAmount) {
  357. return noiseValue * waveValue;
  358. },
  359. getNoiseBuffer: function(buffer, numSamples) {
  360. for(var i = 0; i < numSamples; i++) {
  361. buffer[i] = Math.random() * 2 - 1;
  362. }
  363. },
  364. sendNoteOn: function(note, volume) {
  365. this.position = 0;
  366. this.internalSamplePosition = 0;
  367. this.currentNote = note;
  368. this.currentVolume = volume;
  369. var t = this.getTime();
  370. this.volumeEnvelope.beginAttack(t);
  371. this.pitchEnvelope.beginAttack(t);
  372. },
  373. sendNoteOff: function() {
  374. var t = this.getTime();
  375. this.volumeEnvelope.beginRelease(t);
  376. this.pitchEnvelope.beginRelease(t);
  377. },
  378. getBuffer: function(numSamples) {
  379. var wave1Buffer = [],
  380. wave2Buffer = [],
  381. noiseBuffer = [],
  382. bufferPitch1 = [],
  383. bufferPitch2 = [],
  384. bufferAmp = [],
  385. tmpBuffer = [],
  386. currentTime = this.getTime(),
  387. // Local references
  388. buffer = this.buffer,
  389. bufferTime = currentTime,
  390. currentNote = this.currentNote,
  391. wave1Octave = this.wave1Octave,
  392. wave2Octave = this.wave2Octave,
  393. wave1Volume = this.wave1Volume,
  394. wave1Phase = this.wave1Phase,
  395. wave2Phase = this.wave2Phase,
  396. wave2Volume = this.wave2Volume,
  397. inverseSamplingRate = this.inverseSamplingRate,
  398. noiseAmount = this.noiseAmount,
  399. notNoiseAmount = 1 - noiseAmount,
  400. zeroBufferFn = this.zeroBuffer,
  401. noteToFrequencyFn = this.noteToFrequency,
  402. getNoiseBufferFn = this.getNoiseBuffer,
  403. wave1Function = this.wave1Function,
  404. wave2Function = this.wave2Function,
  405. waveMixFunction = this.waveMixFunction,
  406. clipFn = SOROLLET.Math.clip;
  407. zeroBufferFn(buffer, numSamples);
  408. if( this.volumeEnvelope.state == SOROLLET.ADSR.STATE_DONE ) {
  409. this.currentNote = null;
  410. currentNote = null;
  411. }
  412. if(this.currentNote === null) {
  413. return buffer;
  414. }
  415. zeroBufferFn(wave1Buffer, numSamples);
  416. zeroBufferFn(wave2Buffer, numSamples);
  417. zeroBufferFn(noiseBuffer, numSamples);
  418. zeroBufferFn(bufferPitch1, numSamples);
  419. zeroBufferFn(bufferPitch2, numSamples);
  420. zeroBufferFn(bufferAmp, numSamples);
  421. // Fill the amp and pitch buffers for this run
  422. for (var i = 0; i < numSamples; i++) {
  423. var pitchEnv = this.pitchEnvelope.update(bufferTime),
  424. sampleNote = currentNote + pitchEnv;
  425. bufferPitch1[i] = this.noteToFrequency(sampleNote, wave1Octave);
  426. bufferPitch2[i] = this.noteToFrequency(sampleNote, wave2Octave);
  427. bufferAmp[i] = this.volumeEnvelope.update(bufferTime);
  428. bufferTime += inverseSamplingRate;
  429. }
  430. if(this.wave1Volume > 0) {
  431. var pos = this.position;
  432. for(var i = 0; i < numSamples; i++) {
  433. var frequency = bufferPitch1[i];
  434. this.wave1Function(tmpBuffer, 1, pos, frequency, wave1Phase);
  435. wave1Buffer[i] = tmpBuffer[0];
  436. pos++;
  437. }
  438. }
  439. if(this.wave2Volume > 0) {
  440. var pos = this.position;
  441. for(var i = 0; i < numSamples; i++) {
  442. var frequency = bufferPitch2[i];
  443. this.wave2Function(tmpBuffer, 1, pos, frequency, wave2Phase);
  444. wave2Buffer[i] = tmpBuffer[0];
  445. pos++;
  446. }
  447. }
  448. if(noiseAmount > 0) {
  449. getNoiseBufferFn(noiseBuffer, numSamples);
  450. }
  451. for(var i = 0; i < numSamples; i++) {
  452. var osc1 = wave1Buffer[i] * wave1Volume,
  453. osc2 = wave2Buffer[i] * wave2Volume;
  454. buffer[i] = waveMixFunction(osc1, osc2);
  455. if(noiseAmount > 0) {
  456. var noiseValue = noiseBuffer[i] * noiseAmount;
  457. buffer[i] = this.noiseMixFunction(noiseValue, buffer[i], notNoiseAmount);
  458. }
  459. // Apply amp envelope
  460. buffer[i] *= bufferAmp[i];
  461. // Clamp
  462. buffer[i] = clipFn(buffer[i], -1, 1);
  463. }
  464. this.position += numSamples;
  465. this.internalSamplePosition += numSamples;
  466. return buffer;
  467. },
  468. functionToIndex: function(f, functionsList ) {
  469. for( var i = 0; i < functionsList.length; i++) {
  470. var o = functionsList[i];
  471. if( o.func == f ) {
  472. return i;
  473. }
  474. }
  475. return -1;
  476. },
  477. getParams: function() {
  478. return {
  479. wave1Function: this.functionToIndex( this.wave1Function, this.waveFunctions ),
  480. wave1Octave: this.wave1Octave,
  481. wave1Volume: this.wave1Volume,
  482. wave1Phase: this.wave1Phase,
  483. wave2Function: this.functionToIndex( this.wave2Function, this.waveFunctions ),
  484. wave2Octave: this.wave2Octave,
  485. wave2Volume: this.wave2Volume,
  486. wave2Phase: this.wave2Phase,
  487. waveMixFunction: this.functionToIndex( this.waveMixFunction, this.waveMixFunctions ),
  488. noiseAmount: this.noiseAmount,
  489. noiseMixFunction: this.functionToIndex( this.noiseMixFunction, this.noiseMixFunctions ),
  490. volumeEnvelope: this.volumeEnvelope.getParams(),
  491. pitchEnvelope: this.pitchEnvelope.getParams()
  492. };
  493. },
  494. setParams: function( params ) {
  495. this.wave1Function = this.waveFunctions[ params.wave1Function ].func;
  496. this.wave1Octave = params.wave1Octave;
  497. this.wave1Volume = params.wave1Volume;
  498. this.wave1Phase = params.wave1Phase;
  499. this.wave2Function = this.waveFunctions[ params.wave2Function ].func;
  500. this.wave2Octave = params.wave2Octave;
  501. this.wave2Volume = params.wave2Volume;
  502. this.wave2Phase = params.wave2Phase;
  503. this.waveMixFunction = this.waveMixFunctions[ params.waveMixFunction ].func;
  504. this.noiseAmount = params.noiseAmount;
  505. this.noiseMixFunction = this.noiseMixFunctions[ params.noiseMixFunction ].func;
  506. this.volumeEnvelope.setParams( params.volumeEnvelope );
  507. this.pitchEnvelope.setParams( params.pitchEnvelope );
  508. }
  509. };
  510. SOROLLET.Voice.prototype.waveFunctions = [
  511. { func: SOROLLET.Voice.prototype.getSineBuffer, name: 'sine' },
  512. { func: SOROLLET.Voice.prototype.getTriangleBuffer, name: 'triangle' },
  513. { func: SOROLLET.Voice.prototype.getSquareBuffer, name: 'square' },
  514. { func: SOROLLET.Voice.prototype.getSawtoothBuffer, name: 'sawtooth' }
  515. ];
  516. SOROLLET.Voice.prototype.waveMixFunctions = [
  517. { func: SOROLLET.Voice.prototype.mixAdd, name: 'add' },
  518. { func: SOROLLET.Voice.prototype.mixSubstract, name: 'substract' },
  519. { func: SOROLLET.Voice.prototype.mixMultiply, name: 'multiply' },
  520. { func: SOROLLET.Voice.prototype.mixDivide, name: 'divide' }
  521. ];
  522. SOROLLET.Voice.prototype.noiseMixFunctions = [
  523. { func: SOROLLET.Voice.prototype.noiseAdd, name: 'add' },
  524. { func: SOROLLET.Voice.prototype.noiseMix, name: 'mix' },
  525. { func: SOROLLET.Voice.prototype.noiseMultiply, name: 'multiply' }
  526. ];
  527. 'use strict';
  528. SOROLLET.ADSR = function( attackLength, decayLength, sustainLevel, releaseLength, timeScale ) {
  529. this.state = this.STATE_DONE;
  530. this.timeScale = timeScale;
  531. this.attackStartTime = 0;
  532. this.attackEndTime = 0;
  533. this.decayEndTime = 0;
  534. this.releaseStartTime = 0;
  535. this.releaseEndValue = 0;
  536. this.releaseStartValue = 0;
  537. this.__unscaledAttackLength = 0;
  538. this.attackLength = 0;
  539. this.__unscaledDecayLength = 0;
  540. this.decayLength = 0;
  541. this.sustainLevel = 0.5;
  542. this.__unscaledReleaseLength = 0;
  543. this.releaseLength = 0;
  544. this.lastValue = 0;
  545. this.setAttack( attackLength );
  546. this.setDecay( decayLength );
  547. this.setSustainLevel( sustainLevel );
  548. this.setRelease( releaseLength );
  549. this.setOutputRange( 0, 1 );
  550. }
  551. SOROLLET.ADSR.prototype = {
  552. constructor: SOROLLET.ADSR,
  553. STATE_ATTACK: 0,
  554. STATE_DECAY: 1,
  555. STATE_SUSTAIN: 2,
  556. STATE_RELEASE: 3,
  557. STATE_DONE: 4,
  558. setOutputRange : function( minimumValue, maximumValue ) {
  559. this.outputMinimumValue = minimumValue;
  560. this.outputMaximumValue = maximumValue;
  561. },
  562. setAttack: function( v ) {
  563. this.__unscaledAttackLength = v;
  564. this.attackLength = v * this.timeScale;
  565. },
  566. setDecay: function( v ) {
  567. this.__unscaledDecayLength = v;
  568. this.decayLength = v * this.timeScale;
  569. this.decayEndTime = this.attackEndTime + this.decayLength;
  570. },
  571. setSustainLevel: function( v ) {
  572. this.sustainLevel = v;
  573. },
  574. setRelease: function( v ) {
  575. this.__unscaledReleaseLength = v;
  576. this.releaseLength = v * this.timeScale;
  577. this.releaseEndTime = this.releaseStartTime + this.releaseLength;
  578. },
  579. setTimeScale: function( v ) {
  580. this.timeScale = v;
  581. this.setAttack( this.__unscaledAttackLength );
  582. this.setDecay( this.__unscaledDecayLength );
  583. this.setRelease( this.__unscaledReleaseLength );
  584. },
  585. beginAttack: function( time ) {
  586. this.state = this.STATE_ATTACK;
  587. this.attackStartTime = time;
  588. this.attackEndTime = time + this.attackLength;
  589. this.decayEndTime = this.attackEndTime + this.decayLength;
  590. },
  591. beginRelease: function( time ) {
  592. this.state = this.STATE_RELEASE;
  593. this.releaseStartValue = this.lastValue;
  594. this.releaseStartTime = time;
  595. this.releaseEndTime = time + this.releaseLength;
  596. },
  597. update: function( time ) {
  598. var scaledSustainLevel,
  599. value = 0,
  600. scaledValue = 0.5,
  601. map = SOROLLET.Math.map,
  602. interpolate = SOROLLET.Math.interpolate;
  603. scaledSustainLevel = map( this.sustainLevel, 0, 1, this.outputMinimumValue, this.outputMaximumValue );
  604. if( this.state == this.STATE_DONE ) {
  605. scaledValue = this.outputMinimumValue;
  606. } else if( this.state == this.STATE_ATTACK ) {
  607. if( time > this.attackEndTime ) {
  608. this.state = this.STATE_DECAY;
  609. scaledValue = this.outputMaximumValue;
  610. } else {
  611. scaledValue = map( time, this.attackStartTime, this.attackEndTime, this.outputMinimumValue, this.outputMaximumValue );
  612. }
  613. } else if( this.state == this.STATE_DECAY ) {
  614. if( time > this.decayEndTime ) {
  615. this.state = this.STATE_SUSTAIN;
  616. scaledValue = scaledSustainLevel;
  617. } else {
  618. scaledValue = map( time, this.attackEndTime, this.decayEndTime, this.outputMaximumValue, scaledSustainLevel );
  619. }
  620. } else if( this.state == this.STATE_SUSTAIN ) {
  621. scaledValue = scaledSustainLevel;
  622. } else if( this.state == this.STATE_RELEASE ) {
  623. if( time > this.releaseEndTime ) {
  624. this.state = this.STATE_DONE;
  625. scaledValue = this.outputMinimumValue;
  626. } else {
  627. scaledValue = map( time, this.releaseStartTime, this.releaseEndTime, this.releaseStartValue, this.outputMinimumValue );
  628. }
  629. }
  630. this.lastValue = scaledValue;
  631. return scaledValue;
  632. },
  633. getParams: function() {
  634. return {
  635. attack: this.__unscaledAttackLength,
  636. decay: this.__unscaledDecayLength,
  637. sustain: this.sustainLevel,
  638. release: this.__unscaledReleaseLength,
  639. outputMin: this.outputMinimumValue,
  640. outputMax: this.outputMaximumValue,
  641. timeScale: this.timeScale
  642. };
  643. },
  644. setParams: function( params ) {
  645. this.timeScale = params.timeScale;
  646. this.setOutputRange( params.outputMin, params.outputMax );
  647. this.setAttack( params.attack );
  648. this.setDecay( params.decay );
  649. this.setSustainLevel( params.sustain );
  650. this.setRelease( params.release );
  651. }
  652. };
  653. SOROLLET.Player = function( _samplingRate ) {
  654. 'use strict';
  655. var samplingRate = _samplingRate,
  656. inverseSamplingRate = 1.0 / samplingRate,
  657. secondsPerRow, secondsPerTick,
  658. lastPlayedTime = 0, // XXX KILL
  659. lastRowTime = 0, // XXX KILL
  660. loopStart = 0,
  661. outBuffer = [],
  662. scope = this;
  663. this.bpm = 100;
  664. this.linesPerBeat = 4;
  665. this.ticksPerLine = 12;
  666. this.currentRow = 0;
  667. this.currentOrder = 0;
  668. this.currentPattern = 0;
  669. this.repeat = true;
  670. this.finished = false;
  671. this.voices = [];
  672. this.patterns = [];
  673. this.orderList = [];
  674. this.eventsList = [];
  675. this.nextEventPosition = 0; // TODO position->index? or position ~~~ samples?
  676. this.timePosition = 0;
  677. this.position = 0;
  678. EventTarget.call( this );
  679. updateRowTiming();
  680. function updateRowTiming() {
  681. secondsPerRow = 60.0 / (scope.linesPerBeat * scope.bpm);
  682. secondsPerTick = secondsPerRow / scope.ticksPerLine;
  683. }
  684. this.play = function() {
  685. // having an updated event list is ESSENTIAL to playing!
  686. this.buildEventsList();
  687. }
  688. this.stop = function() {
  689. this.position = 0;
  690. loopStart = 0;
  691. //this.nextEventPosition = 0;
  692. this.jumpToOrder( 0, 0 );
  693. }
  694. this.jumpToOrder = function( orderIndex, row ) {
  695. // TODO if the new pattern to play has less rows than the current one,
  696. // make sure we don't play out of index
  697. changeToOrder( orderIndex );
  698. if( row === undefined ) {
  699. row = this.currentRow;
  700. }
  701. changeToRow( row );
  702. this.updateNextEventToOrderRow( orderIndex, row );
  703. var prevPosition = this.position;
  704. this.position = this.eventsList[ this.nextEventPosition ].timestampSamples + loopStart;
  705. }
  706. this.updateNextEventPosition = function() {
  707. var p = 0;
  708. for(var i = 0; i < this.eventsList.length; i++ ) {
  709. var ev = this.eventsList[i];
  710. if( ev.timestampSamples >= this.position ) {
  711. break;
  712. }
  713. p = i;
  714. }
  715. this.nextEventPosition = p;
  716. }
  717. this.updateNextEventToOrderRow = function( order, row ) {
  718. var p = 0;
  719. for(var i = 0; i < this.eventsList.length; i++) {
  720. var ev = this.eventsList[i];
  721. p = i;
  722. if( ev.TYPE_ROW_CHANGE == ev.type && ev.row == row && ev.order == order ) {
  723. break;
  724. }
  725. }
  726. this.nextEventPosition = p;
  727. }
  728. this.buildEventsList = function() {
  729. var t = 0,
  730. orderIndex = 0,
  731. patternIndex,
  732. pattern,
  733. samples = 0,
  734. samplesPerRow,
  735. trackCount = this.voices.length, // TODO it doesn't need to be a 1:1 correspondence
  736. lastTrackInstrument = new Array( trackCount ),
  737. lastTrackNote = new Array( trackCount );
  738. for (var i = 0; i < trackCount; i++) {
  739. lastTrackInstrument[i] = null;
  740. lastTrackNote[i] = null;
  741. }
  742. this.eventsList = [];
  743. samplesPerRow = (secondsPerRow * samplingRate + 0.5) >> 0; // Note: this should change if speed commands are implemented
  744. while ( orderIndex < this.orderList.length ) {
  745. var orderEv = new SOROLLET.PlayerEvent();
  746. orderEv.timestamp = t;
  747. orderEv.timestampSamples = samples;
  748. orderEv.type = orderEv.TYPE_ORDER_POSITION_CHANGE;
  749. orderEv.order = orderIndex;
  750. this.eventsList.push( orderEv );
  751. patternIndex = this.orderList[ orderIndex ];
  752. var ev = new SOROLLET.PlayerEvent();
  753. ev.timestamp = t;
  754. ev.timestampSamples = samples;
  755. ev.type = ev.TYPE_PATTERN_CHANGE;
  756. ev.pattern = patternIndex;
  757. this.eventsList.push( ev );
  758. pattern = this.patterns[ patternIndex ];
  759. for( var i = 0; i < pattern.rows.length; i++ ) {
  760. var ev = new SOROLLET.PlayerEvent();
  761. ev.timestamp = t;
  762. ev.timestampSamples = samples;
  763. ev.type = ev.TYPE_ROW_CHANGE;
  764. ev.row = i;
  765. ev.order = orderIndex;
  766. this.eventsList.push( ev );
  767. for (var j = 0; j < trackCount; j++) {
  768. var cell = pattern.getCell(i, j);
  769. // ~~ NOTE ON ~~ //
  770. if( cell.note !== null && cell.noteOff !== true /* TODO && pCell->getInstrument() != INSTRUMENT_NULL*/ ) {
  771. // TODO instrument = pCell->getInstrument();
  772. var ev = new SOROLLET.PlayerEvent();
  773. ev.timestamp = t;
  774. ev.timestampSamples = samples;
  775. ev.type = ev.TYPE_NOTE_ON;
  776. ev.note = cell.note
  777. // TODO ev.instrument = instrument;
  778. ev.instrument = j; // TODO tmp (see above)
  779. ev.volume = cell.volume;
  780. this.eventsList.push( ev );
  781. // TODO buildArpeggios(pCell, t, samples, mfSecondsPerRow, samplesPerRow, instrument, note, fVolume);
  782. // TODO Store this for later, so that if we get a new volume event we know to which instrument it applies
  783. // lastTrackInstrument[j] = instrument;
  784. // lastTrackNote[j] = note;
  785. }
  786. // ~~ NEW VOLUME ~~ //
  787. /* TODO else if (
  788. (NOTE_NULL == pCell->getNote() || NOTE_OFF == pCell->getNote()) &&
  789. INSTRUMENT_NULL == pCell->getInstrument() &&
  790. VOLUME_NULL != pCell->getVolume())
  791. {
  792. if (lastTrackInstrument[j] != INSTRUMENT_NULL)
  793. {
  794. event = new SorolletPlayerEvent();
  795. event->timestamp = t;
  796. event->timestampSamples = samples;
  797. event->type = SOROLLET_EVENT_VOLUME;
  798. event->instrument = lastTrackInstrument[j];
  799. event->volume = fVolume;
  800. addEvent(event);
  801. if (lastTrackNote[j] != NOTE_NULL)
  802. {
  803. buildArpeggios(pCell, t, samples, mfSecondsPerRow, samplesPerRow, instrument, lastTrackNote[j], fVolume);
  804. }
  805. }
  806. }*/
  807. // ~~ NOTE OFF ~~ //
  808. else if( cell.noteOff === true ) {
  809. // TODO if (lastTrackInstrument[j] != INSTRUMENT_NULL) {
  810. var ev = new SOROLLET.PlayerEvent();
  811. ev.timestamp = t;
  812. ev.timestampSamples = samples;
  813. ev.type = ev.TYPE_NOTE_OFF;
  814. // TODO ev.instrument = lastTrackInstrument[j];
  815. this.eventsList.push( ev );
  816. // }
  817. }
  818. /* TODO else if (pCell->getNote() != NOTE_OFF && lastTrackNote[j] != NOTE_NULL && lastTrackInstrument[j] != INSTRUMENT_NULL)
  819. {
  820. buildArpeggios(pCell, t, samples, mfSecondsPerRow, samplesPerRow, lastTrackInstrument[j], lastTrackNote[j], 1.0f);
  821. }*/
  822. }
  823. t += secondsPerRow;
  824. samples += samplesPerRow;
  825. }
  826. orderIndex++;
  827. }
  828. // End of the song --there can only be one of these events!!!
  829. var ev = new SOROLLET.PlayerEvent();
  830. ev.timestamp = t;
  831. ev.timestampSamples = samples;
  832. ev.type = ev.TYPE_SONG_END;
  833. this.eventsList.push( ev );
  834. }
  835. this.getBuffer = function( numSamples ) {
  836. var outBuffer = [],
  837. remainingSamples = numSamples,
  838. bufferEndSamples = this.position + numSamples,
  839. segmentStartSamples = this.position,
  840. currentEvent,
  841. currentEventStart,
  842. intervalSamples,
  843. bufferPosition = 0;
  844. for( var i = 0; i < numSamples; i++ ) {
  845. outBuffer[i] = 0;
  846. }
  847. do {
  848. if( this.finished && this.repeat ) {
  849. this.jumpToOrder( 0, 0 );
  850. this.finished = false;
  851. }
  852. if( this.nextEventPosition == this.eventsList.length ) {
  853. return outBuffer;
  854. }
  855. currentEvent = this.eventsList[ this.nextEventPosition ];
  856. currentEventStart = loopStart + currentEvent.timestampSamples;
  857. if( currentEventStart >= bufferEndSamples ) {
  858. break;
  859. }
  860. intervalSamples = currentEventStart - segmentStartSamples;
  861. // Get buffer UNTIL the event
  862. if (intervalSamples > 0) {
  863. processBuffer(outBuffer, intervalSamples, bufferPosition );
  864. remainingSamples -= intervalSamples;
  865. segmentStartSamples = currentEventStart;
  866. this.position += intervalSamples;
  867. bufferPosition += intervalSamples;
  868. }
  869. // Apply the event
  870. if( currentEvent.TYPE_ORDER_POSITION_CHANGE == currentEvent.type ) {
  871. changeToOrder( currentEvent.order );
  872. /*} else if( currentEvent.TYPE_PATTERN_CHANGE == currentEvent.type ) {
  873. //this.currentPattern = currentEvent.pattern;
  874. changeToPattern( currentEvent.pattern );
  875. */
  876. } else if( currentEvent.TYPE_ROW_CHANGE == currentEvent.type ) {
  877. changeToRow( currentEvent.row );
  878. } else if( currentEvent.TYPE_NOTE_ON == currentEvent.type) {
  879. var voice = this.voices[ currentEvent.instrument ];
  880. voice.sendNoteOn( currentEvent.note, currentEvent.volume );
  881. /*} TODO else if( currentEvent.TYPE_EVENT_VOLUME == currentEvent.type ) {
  882. var voice = this.voices[ currentEvent.instrument ];
  883. voice.sendCurrentNoteVolume( currentEvent.volume );*/
  884. } else if( currentEvent.TYPE_NOTE_OFF == currentEvent.type ) {
  885. var voice = this.voices[ currentEvent.instrument ];
  886. voice.sendNoteOff();
  887. } else if( currentEvent.TYPE_SONG_END == currentEvent.type ) {
  888. loopStart = currentEventStart;
  889. this.finished = true;
  890. changeToEnd();
  891. }
  892. this.nextEventPosition++;
  893. } while ( this.nextEventPosition < this.eventsList.length && remainingSamples > 0 );
  894. if(remainingSamples > 0) {
  895. processBuffer( outBuffer, remainingSamples, bufferPosition );
  896. }
  897. this.position += remainingSamples;
  898. this.timePosition += numSamples * inverseSamplingRate;
  899. return outBuffer;
  900. }
  901. function processBuffer( buffer, numSamples, startPosition ) {
  902. var tmpBuffer = [],
  903. endPosition = startPosition + numSamples;
  904. // Process envelopes, if applicable
  905. /* TODO SorolletPattern* pPattern = &mPatterns[miCurrentPattern];
  906. for (i = 0; i < miTrackCount; i++)
  907. {
  908. if (pPattern->trackHasEnvelopes(i) && mTracksAutomationDevices[i].isActive())
  909. {
  910. SorolletDeviceAutomation* deviceAutomation = &(mTracksAutomationDevices[i]);
  911. SorolletEnvelope** vEnvelopes = pPattern->getTrackEnvelopes(i);
  912. int numEnvelopes = pPattern->getTrackEnvelopesNumber(i);
  913. for (j = 0; j < numEnvelopes; j++)
  914. {
  915. SorolletEnvelope* pEnvelope = vEnvelopes[j];
  916. int instrumentIndex = deviceAutomation->getInstrumentIndex();
  917. int automationParameterIndex = pEnvelope->getAutomationParameterIndex();
  918. int instrumentParameterIndex = deviceAutomation->getParameterMappings()[automationParameterIndex];
  919. float value = pEnvelope->getValueAtRow(miCurrentRow);
  920. mVoices[i].setVSTParameter(instrumentParameterIndex, value);
  921. }
  922. }
  923. }*/
  924. // ~~~~
  925. for( var i = startPosition; i < endPosition; i++ ) {
  926. buffer[ i ] = 0;
  927. }
  928. for (var i = 0; i < scope.voices.length; i++) {
  929. var voice = scope.voices[i];
  930. tmpBuffer = voice.getBuffer( numSamples );
  931. for (var j = 0; j < numSamples; j++) {
  932. buffer[ j + startPosition ] += tmpBuffer[ j ];
  933. }
  934. }
  935. for (var i = startPosition; i < endPosition; i++) {
  936. buffer[i] = SOROLLET.Math.clip( buffer[i], -1.0, 1.0 );
  937. }
  938. }
  939. function changeToEnd() {
  940. scope.dispatchEvent({ type: 'songEnded' });
  941. }
  942. function changeToRow( value ) {
  943. var previousValue = scope.currentRow;
  944. scope.currentRow = value;
  945. scope.dispatchEvent({ type: 'rowChanged', row: value, previousRow: previousValue, pattern: scope.currentPattern, order: scope.currentOrder });
  946. }
  947. function changeToPattern( value ) {
  948. var previousValue = scope.currentPattern;
  949. scope.currentPattern = value;
  950. scope.dispatchEvent({ type: 'patternChanged', pattern: value, previousPattern: previousValue, order: scope.currentOrder, row: scope.currentRow });
  951. }
  952. function changeToOrder( value ) {
  953. var previousValue = scope.currentOrder;
  954. scope.currentOrder = value;
  955. scope.dispatchEvent({ type: 'orderChanged', order: value, previousOrder: previousValue, pattern: scope.currentPattern, row: scope.currentRow });
  956. changeToPattern( scope.orderList[ value ] );
  957. }
  958. this.setBPM = function( value ){
  959. this.bpm = value;
  960. updateRowTiming();
  961. this.dispatchEvent({ type: 'bpmChanged', bpm: value });
  962. }
  963. this.getSecondsPerRow = function() {
  964. return secondsPerRow;
  965. }
  966. this.getCurrentPattern = function() {
  967. return this.patterns[ this.currentPattern ];
  968. }
  969. this.addPattern = function( pattern ) {
  970. this.patterns.push( pattern );
  971. this.dispatchEvent({ type: 'change', player: this });
  972. return this.patterns.length - 1;
  973. }
  974. this.addToOrderList = function( patternIndex ) {
  975. this.orderList.push( patternIndex );
  976. this.dispatchEvent({ type: 'change', player: this });
  977. }
  978. this.addToOrderListAfter = function( patternIndex, orderListIndex ) {
  979. this.orderList.splice( orderListIndex, 0, patternIndex );
  980. this.dispatchEvent({ type: 'change', player: this });
  981. }
  982. this.removeFromOrderList = function( orderListIndex ) {
  983. this.orderList.splice( orderListIndex, 1 );
  984. this.dispatchEvent({ type: 'change', player: this });
  985. }
  986. this.setOrderValueAt = function( orderIndex, value ) {
  987. if( this.orderList.length <= orderIndex ) {
  988. console.error( 'Sorollet.Player.setOrderValueAt: trying to set value for non-existing order', orderIndex);
  989. return;
  990. } else if( this.patterns.length <= value ) {
  991. console.error( 'Sorollet.Player.setOrderValueAt: trying to set value for non-existing pattern', orderIndex);
  992. }
  993. this.orderList[ orderIndex ] = value;
  994. this.currentPattern = this.orderList[ orderIndex ];
  995. this.dispatchEvent({ type: 'change', player: this });
  996. }
  997. }
  998. SOROLLET.PlayerEvent = function() {
  999. this.timestamp = 0;
  1000. this.timestampSamples = 0;
  1001. this.type = null;
  1002. this.instrument = null;
  1003. this.volume = 0;
  1004. };
  1005. SOROLLET.PlayerEvent.prototype = {
  1006. TYPE_NULL: 0,
  1007. TYPE_NOTE_OFF: 1,
  1008. TYPE_NOTE_ON: 2,
  1009. TYPE_VOLUME: 3,
  1010. TYPE_EFFECT: 4,
  1011. TYPE_ROW_CHANGE: 5,
  1012. TYPE_PATTERN_CHANGE: 6,
  1013. TYPE_ORDER_POSITION_CHANGE: 7,
  1014. TYPE_SONG_END: 8
  1015. };
  1016. SOROLLET.Pattern = function( numTracks, length ) {
  1017. 'use strict';
  1018. this.rows = [];
  1019. for( var i = 0; i < length; i++ ) {
  1020. var row = [];
  1021. for( var j = 0; j < numTracks; j++ ) {
  1022. row.push( new SOROLLET.PatternCell() );
  1023. }
  1024. this.rows.push( row );
  1025. }
  1026. this.getCell = function( i, j ) {
  1027. return this.rows[i][j];
  1028. }
  1029. }
  1030. SOROLLET.PatternCell = function() {
  1031. this.reset();
  1032. }
  1033. SOROLLET.PatternCell.prototype = {
  1034. note: null,
  1035. noteOff: false,
  1036. volume: null,
  1037. reset: function() {
  1038. this.note = null;
  1039. this.noteOff = false;
  1040. this.volume = null;
  1041. }
  1042. };
  1043. /**
  1044. * @author mr.doob / http://mrdoob.com/
  1045. */
  1046. var EventTarget = function () {
  1047. var listeners = {};
  1048. this.addEventListener = function ( type, listener ) {
  1049. if ( listeners[ type ] == undefined ) {
  1050. listeners[ type ] = [];
  1051. }
  1052. if ( listeners[ type ].indexOf( listener ) === - 1 ) {
  1053. listeners[ type ].push( listener );
  1054. }
  1055. };
  1056. this.dispatchEvent = function ( event ) {
  1057. for ( var listener in listeners[ event.type ] ) {
  1058. listeners[ event.type ][ listener ]( event );
  1059. }
  1060. };
  1061. this.removeEventListener = function ( type, listener ) {
  1062. var index = listeners[ type ].indexOf( listener );
  1063. if ( index !== - 1 ) {
  1064. listeners[ type ].splice( index, 1 );
  1065. }
  1066. };
  1067. };
  1068. /*
  1069. JS Signals <http://millermedeiros.github.com/js-signals/>
  1070. Released under the MIT license
  1071. Author: Miller Medeiros
  1072. Version: 0.7.4 - Build: 252 (2012/02/24 10:30 PM)
  1073. */
  1074. (function(h){function g(a,b,c,d,e){this._listener=b;this._isOnce=c;this.context=d;this._signal=a;this._priority=e||0}function f(a,b){if(typeof a!=="function")throw Error("listener is a required param of {fn}() and should be a Function.".replace("{fn}",b));}var e={VERSION:"0.7.4"};g.prototype={active:!0,params:null,execute:function(a){var b;this.active&&this._listener&&(a=this.params?this.params.concat(a):a,b=this._listener.apply(this.context,a),this._isOnce&&this.detach());return b},detach:function(){return this.isBound()?
  1075. this._signal.remove(this._listener,this.context):null},isBound:function(){return!!this._signal&&!!this._listener},getListener:function(){return this._listener},_destroy:function(){delete this._signal;delete this._listener;delete this.context},isOnce:function(){return this._isOnce},toString:function(){return"[SignalBinding isOnce:"+this._isOnce+", isBound:"+this.isBound()+", active:"+this.active+"]"}};e.Signal=function(){this._bindings=[];this._prevParams=null};e.Signal.prototype={memorize:!1,_shouldPropagate:!0,
  1076. active:!0,_registerListener:function(a,b,c,d){var e=this._indexOfListener(a,c);if(e!==-1){if(a=this._bindings[e],a.isOnce()!==b)throw Error("You cannot add"+(b?"":"Once")+"() then add"+(!b?"":"Once")+"() the same listener without removing the relationship first.");}else a=new g(this,a,b,c,d),this._addBinding(a);this.memorize&&this._prevParams&&a.execute(this._prevParams);return a},_addBinding:function(a){var b=this._bindings.length;do--b;while(this._bindings[b]&&a._priority<=this._bindings[b]._priority);
  1077. this._bindings.splice(b+1,0,a)},_indexOfListener:function(a,b){for(var c=this._bindings.length,d;c--;)if(d=this._bindings[c],d._listener===a&&d.context===b)return c;return-1},has:function(a,b){return this._indexOfListener(a,b)!==-1},add:function(a,b,c){f(a,"add");return this._registerListener(a,!1,b,c)},addOnce:function(a,b,c){f(a,"addOnce");return this._registerListener(a,!0,b,c)},remove:function(a,b){f(a,"remove");var c=this._indexOfListener(a,b);c!==-1&&(this._bindings[c]._destroy(),this._bindings.splice(c,
  1078. 1));return a},removeAll:function(){for(var a=this._bindings.length;a--;)this._bindings[a]._destroy();this._bindings.length=0},getNumListeners:function(){return this._bindings.length},halt:function(){this._shouldPropagate=!1},dispatch:function(a){if(this.active){var b=Array.prototype.slice.call(arguments),c=this._bindings.length,d;if(this.memorize)this._prevParams=b;if(c){d=this._bindings.slice();this._shouldPropagate=!0;do c--;while(d[c]&&this._shouldPropagate&&d[c].execute(b)!==!1)}}},forget:function(){this._prevParams=
  1079. null},dispose:function(){this.removeAll();delete this._bindings;delete this._prevParams},toString:function(){return"[Signal active:"+this.active+" numListeners:"+this.getNumListeners()+"]"}};typeof define==="function"&&define.amd?define(e):typeof module!=="undefined"&&module.exports?module.exports=e:h.signals=e})(this);var UI = {};
  1080. UI.Element = function () {};
  1081. UI.Element.prototype = {
  1082. setClass: function ( name ) {
  1083. this.dom.className = name;
  1084. return this;
  1085. },
  1086. // styles
  1087. setStyle: function ( style, array ) {
  1088. for ( var i = 0; i < array.length; i ++ ) {
  1089. this.dom.style[ style ] = array[ i ];
  1090. }
  1091. },
  1092. setLeft: function () {
  1093. this.setStyle( 'left', arguments );
  1094. return this;
  1095. },
  1096. setTop: function () {
  1097. this.setStyle( 'top', arguments );
  1098. return this;
  1099. },
  1100. setRight: function () {
  1101. this.setStyle( 'right', arguments );
  1102. return this;
  1103. },
  1104. setBottom: function () {
  1105. this.setStyle( 'bottom', arguments );
  1106. return this;
  1107. },
  1108. setWidth: function () {
  1109. this.setStyle( 'width', arguments );
  1110. return this;
  1111. },
  1112. setHeight: function () {
  1113. this.setStyle( 'height', arguments );
  1114. return this;
  1115. },
  1116. //
  1117. setBorder: function () {
  1118. this.setStyle( 'border', arguments );
  1119. return this;
  1120. },
  1121. setBorderTop: function () {
  1122. this.setStyle( 'borderTop', arguments );
  1123. return this;
  1124. },
  1125. setBorderBottom: function () {
  1126. this.setStyle( 'borderBottom', arguments );
  1127. return this;
  1128. },
  1129. setBorderLeft: function () {
  1130. this.setStyle( 'borderLeft', arguments );
  1131. return this;
  1132. },
  1133. setBorderRight: function () {
  1134. this.setStyle( 'borderRight', arguments );
  1135. return this;
  1136. },
  1137. //
  1138. setMargin: function () {
  1139. this.setStyle( 'margin', arguments );
  1140. return this;
  1141. },
  1142. setMarginTop: function () {
  1143. this.setStyle( 'marginTop', arguments );
  1144. return this;
  1145. },
  1146. setMarginBottom: function () {
  1147. this.setStyle( 'marginBottom', arguments );
  1148. return this;
  1149. },
  1150. setMarginLeft: function () {
  1151. this.setStyle( 'marginLeft', arguments );
  1152. return this;
  1153. },
  1154. setMarginRight: function () {
  1155. this.setStyle( 'marginRight', arguments );
  1156. return this;
  1157. },
  1158. //
  1159. setPadding: function () {
  1160. this.setStyle( 'padding', arguments );
  1161. return this;
  1162. },
  1163. //
  1164. setFloat: function () {
  1165. this.setStyle( 'float', arguments );
  1166. return this;
  1167. },
  1168. //
  1169. setFontSize: function () {
  1170. this.setStyle( 'fontSize', arguments );
  1171. return this;
  1172. },
  1173. setFontWeight: function () {
  1174. this.setStyle( 'fontWeight', arguments );
  1175. return this;
  1176. },
  1177. //
  1178. setColor: function () {
  1179. this.setStyle( 'color', arguments );
  1180. return this;
  1181. },
  1182. setBackgroundColor: function () {
  1183. this.setStyle( 'backgroundColor', arguments );
  1184. return this;
  1185. },
  1186. setDisplay: function () {
  1187. this.setStyle( 'display', arguments );
  1188. return this;
  1189. },
  1190. setOverflow: function () {
  1191. this.setStyle( 'overflow', arguments );
  1192. return this;
  1193. },
  1194. //
  1195. setCursor: function () {
  1196. this.setStyle( 'cursor', arguments );
  1197. return this;
  1198. },
  1199. // content
  1200. setTextContent: function ( value ) {
  1201. this.dom.textContent = value;
  1202. return this;
  1203. },
  1204. // events
  1205. onMouseOver: function ( callback ) {
  1206. this.dom.addEventListener( 'mouseover', callback, false );
  1207. return this;
  1208. },
  1209. onMouseOut: function ( callback ) {
  1210. this.dom.addEventListener( 'mouseout', callback, false );
  1211. return this;
  1212. },
  1213. onClick: function ( callback ) {
  1214. this.dom.addEventListener( 'click', callback, false );
  1215. return this;
  1216. }
  1217. }
  1218. // Panel
  1219. UI.Panel = function ( position ) {
  1220. UI.Element.call( this );
  1221. var dom = document.createElement( 'div' );
  1222. dom.style.position = position || 'relative';
  1223. dom.style.marginBottom = '10px';
  1224. dom.style.userSelect = 'none';
  1225. dom.style.WebkitUserSelect = 'none';
  1226. dom.style.MozUserSelect = 'none';
  1227. this.dom = dom;
  1228. return this;
  1229. };
  1230. UI.Panel.prototype = Object.create( UI.Element.prototype );
  1231. UI.Panel.prototype.add = function () {
  1232. for ( var i = 0; i < arguments.length; i ++ ) {
  1233. this.dom.appendChild( arguments[ i ].dom );
  1234. }
  1235. return this;
  1236. };
  1237. // Text
  1238. UI.Text = function ( position ) {
  1239. UI.Element.call( this );
  1240. var dom = document.createElement( 'span' );
  1241. dom.style.position = position || 'relative';
  1242. dom.style.cursor = 'default';
  1243. this.dom = dom;
  1244. return this;
  1245. };
  1246. UI.Text.prototype = Object.create( UI.Element.prototype );
  1247. UI.Text.prototype.setValue = function ( value ) {
  1248. this.dom.textContent = value;
  1249. return this;
  1250. };
  1251. // Input
  1252. UI.Input = function ( position ) {
  1253. UI.Element.call( this );
  1254. var scope = this;
  1255. var dom = document.createElement( 'input' );
  1256. dom.style.position = position || 'relative';
  1257. dom.style.padding = '2px';
  1258. dom.style.marginTop = '-2px';
  1259. dom.style.marginLeft = '-2px';
  1260. dom.style.border = '1px solid #ccc';
  1261. this.dom = dom;
  1262. this.onChangeCallback = null;
  1263. this.dom.addEventListener( 'change', function ( event ) {
  1264. if ( scope.onChangeCallback ) scope.onChangeCallback();
  1265. }, false );
  1266. return this;
  1267. };
  1268. UI.Input.prototype = Object.create( UI.Element.prototype );
  1269. UI.Input.prototype.getValue = function () {
  1270. return this.dom.value;
  1271. };
  1272. UI.Input.prototype.setValue = function ( value ) {
  1273. this.dom.value = value;
  1274. return this;
  1275. };
  1276. UI.Input.prototype.onChange = function ( callback ) {
  1277. this.onChangeCallback = callback;
  1278. return this;
  1279. };
  1280. // Select
  1281. UI.Select = function ( position ) {
  1282. UI.Element.call( this );
  1283. var scope = this;
  1284. var dom = document.createElement( 'select' );
  1285. dom.style.position = position || 'relative';
  1286. dom.style.width = '64px';
  1287. dom.style.height = '16px';
  1288. dom.style.border = '0px';
  1289. dom.style.padding = '0px';
  1290. this.dom = dom;
  1291. this.onChangeCallback = null;
  1292. this.dom.addEventListener( 'change', function ( event ) {
  1293. if ( scope.onChangeCallback ) scope.onChangeCallback();
  1294. }, false );
  1295. return this;
  1296. };
  1297. UI.Select.prototype = Object.create( UI.Element.prototype );
  1298. UI.Select.prototype.setMultiple = function ( bool ) {
  1299. this.dom.multiple = bool;
  1300. return this;
  1301. };
  1302. UI.Select.prototype.setOptions = function ( options ) {
  1303. while ( this.dom.children.length > 0 ) {
  1304. this.dom.removeChild( this.dom.firstChild );
  1305. }
  1306. for ( var key in options ) {
  1307. var option = document.createElement( 'option' );
  1308. option.value = key;
  1309. option.innerHTML = options[ key ];
  1310. this.dom.appendChild( option );
  1311. }
  1312. return this;
  1313. };
  1314. UI.Select.prototype.getValue = function () {
  1315. return this.dom.value;
  1316. };
  1317. UI.Select.prototype.setValue = function ( value ) {
  1318. this.dom.value = value;
  1319. return this;
  1320. };
  1321. UI.Select.prototype.onChange = function ( callback ) {
  1322. this.onChangeCallback = callback;
  1323. return this;
  1324. };
  1325. // FancySelect
  1326. UI.FancySelect = function ( position ) {
  1327. UI.Element.call( this );
  1328. var scope = this;
  1329. var dom = document.createElement( 'div' );
  1330. dom.style.position = position || 'relative';
  1331. dom.style.background = '#fff';
  1332. dom.style.border = '1px solid #ccc';
  1333. dom.style.padding = '0';
  1334. dom.style.cursor = 'default';
  1335. dom.style.overflow = 'auto';
  1336. this.dom = dom;
  1337. this.onChangeCallback = null;
  1338. this.options = [];
  1339. this.selectedValue = null;
  1340. return this;
  1341. };
  1342. UI.FancySelect.prototype = Object.create( UI.Element.prototype );
  1343. UI.FancySelect.prototype.setOptions = function ( options ) {
  1344. var scope = this;
  1345. while ( scope.dom.children.length > 0 ) {
  1346. scope.dom.removeChild( scope.dom.firstChild );
  1347. }
  1348. scope.options = [];
  1349. var generateOptionCallback = function ( element, value ) {
  1350. return function ( event ) {
  1351. for ( var i = 0; i < scope.options.length; i ++ ) {
  1352. scope.options[ i ].style.backgroundColor = '#f0f0f0';
  1353. }
  1354. element.style.backgroundColor = '#f0f0f0';
  1355. scope.selectedValue = value;
  1356. if ( scope.onChangeCallback ) scope.onChangeCallback();
  1357. }
  1358. };
  1359. for ( var key in options ) {
  1360. var option = document.createElement( 'div' );
  1361. option.style.padding = '4px';
  1362. option.style.whiteSpace = 'nowrap';
  1363. option.innerHTML = options[ key ];
  1364. option.value = key;
  1365. scope.dom.appendChild( option );
  1366. scope.options.push( option );
  1367. option.addEventListener( 'click', generateOptionCallback( option, key ), false );
  1368. }
  1369. return scope;
  1370. };
  1371. UI.FancySelect.prototype.getValue = function () {
  1372. return this.selectedValue;
  1373. };
  1374. UI.FancySelect.prototype.setValue = function ( value ) {
  1375. // must convert raw value into string for compatibility with UI.Select
  1376. // which uses string values (initialized from options keys)
  1377. var key = value ? value.toString() : value;
  1378. for ( var i = 0; i < this.options.length; i ++ ) {
  1379. var element = this.options[ i ];
  1380. if ( element.value === key ) {
  1381. element.style.backgroundColor = '#f0f0f0';
  1382. } else {
  1383. element.style.backgroundColor = '';
  1384. }
  1385. }
  1386. this.selectedValue = value;
  1387. return this;
  1388. };
  1389. UI.FancySelect.prototype.

Large files files are truncated, but you can click here to view the full file