src/nodes.js

  1. /**
  2. * #Native Audio Nodes#
  3. * Native implementations of web audio nodes.
  4. */
  5. /**
  6. * Native Script node
  7. * @function
  8. * @memberof cracked
  9. * @name cracked#script
  10. * @public
  11. * @category Node
  12. * @param {Object} [userParams] map of optional values
  13. * @param {Number} [userParams.buffersize=4096]
  14. * @param {Number} [userParams.channels=1]
  15. * @param {Function} [userParams.fn=defaultFunction]
  16. */
  17. cracked.script = function (userParams) {
  18. userParams = userParams || {};
  19. var buffersize = userParams.buffersize || 4096;
  20. var channels = userParams.channels || 1;
  21. var fn = userParams.fn || defaultFunction;
  22. var creationParams = {
  23. "method": "createScriptProcessor",
  24. "methodParams": [buffersize, channels, channels],
  25. "settings": {
  26. "onaudioprocess": fn
  27. }
  28. };
  29. createNode("script", creationParams, userParams);
  30. //default function just passes sound thru
  31. function defaultFunction(e) {
  32. var input = e.inputBuffer.getChannelData(0);
  33. var output = e.outputBuffer.getChannelData(0);
  34. for (var i = 0; i < buffersize; i++) {
  35. output[i] = input[i];
  36. }
  37. }
  38. return cracked;
  39. };
  40. /**
  41. * Native Waveshaper
  42. * @function
  43. * @memberof cracked
  44. * @category Node
  45. * @name cracked#waveshaper
  46. * @public
  47. * @param {Object} [userParams] map of optional values
  48. * @param {Number} [userParams.drive=50]
  49. */
  50. cracked.waveshaper = function (userParams) {
  51. userParams = userParams || {};
  52. var drive = __.isObj(userParams) ?
  53. cracked.ifUndef(userParams.drive, 50) :
  54. userParams;
  55. var creationParams = {
  56. "method": "createWaveShaper",
  57. "settings": {
  58. "curve": userParams.curve || makeCurve(drive),
  59. "mapping": {
  60. "distortion": {
  61. "path": "curve",
  62. "fn": (function () {
  63. return makeCurve;
  64. })()
  65. }
  66. }
  67. }
  68. };
  69. //tbd need a way to modifiy the param to makeCurve
  70. createNode("waveshaper", creationParams, userParams);
  71. return cracked;
  72. //curve generator for waveshaper
  73. function makeCurve(amount) {
  74. var k = __.isNum(amount) ? amount : 50,
  75. n_samples = 44100, //hard coded
  76. curve = new Float32Array(n_samples),
  77. deg = Math.PI / 180,
  78. x;
  79. for (var i = 0; i < n_samples; ++i) {
  80. x = i * 2 / n_samples - 1;
  81. curve[i] = (3 + k) * x * 20 * deg / (Math.PI + k * Math.abs(x));
  82. }
  83. return curve;
  84. }
  85. };
  86. /**
  87. * Native Compressor
  88. * @function
  89. * @category Node
  90. * @memberof cracked
  91. * @name cracked#compressor
  92. * @public
  93. * @param {Object} [userParams] map of optional values
  94. * @param {Number} [userParams.threshold=-24] in decibels, nominal range of -100 to 0.
  95. * @param {Number} [userParams.knee=30] in decibels, range of 0 to 40
  96. * @param {Number} [userParams.ratio=12] nominal range of 1 to 20
  97. * @param {Number} [userParams.attack=0.003] time in seconds, nominal range of 0 to 1
  98. * @param {Number} [userParams.release=0.250] time in seconds, nominal range of 0 to 1
  99. */
  100. cracked.compressor = function (userParams) {
  101. var mapping = {
  102. "threshold": "threshold.value",
  103. "knee": "knee.value",
  104. "ratio": "ratio.value",
  105. "attack": "attack.value",
  106. "release": "release.value"
  107. };
  108. var creationParams = {
  109. "method": "createDynamicsCompressor",
  110. "settings": {},
  111. "mapping": mapping
  112. };
  113. createNode("compressor", creationParams, userParams);
  114. return cracked;
  115. };
  116. /**
  117. * Native Gain
  118. * @function
  119. * @category Node
  120. * @memberof cracked
  121. * @name cracked#gain
  122. * @public
  123. * @param {Object} [userParams] map of optional values
  124. * @param {Number} [userParams.threshold=-24] in decibels, nominal range of -100 to 0.
  125. */
  126. cracked.gain = function (userParams) {
  127. var gain = __.isNum(userParams) ? userParams : 1;
  128. var params = __.isObj(userParams) ? userParams : {
  129. "gain": gain
  130. };
  131. var creationParams = {
  132. "method": "createGain",
  133. "settings": {},
  134. "mapping": {
  135. "gain": "gain.value"
  136. }
  137. };
  138. createNode("gain", creationParams, params);
  139. return cracked;
  140. };
  141. /**
  142. * Naming this with prefix native so I can use "delay" as a plugin name
  143. * max buffer size three minutes
  144. * @function
  145. * @category Node
  146. * @memberof cracked
  147. * @name cracked#native_delay
  148. * @public
  149. * @param {Object} [userParams] map of optional values
  150. * @param {Number} [userParams.delay=0] in seconds.
  151. */
  152. cracked.native_delay = function (userParams) {
  153. var creationParams = {
  154. "method": "createDelay",
  155. "methodParams": [179.0],
  156. "settings": {},
  157. "mapping": {
  158. "delay": "delayTime.value"
  159. }
  160. };
  161. createNode("delay", creationParams, userParams);
  162. return cracked;
  163. };
  164. /**
  165. * Native oscillator, used the oscillator plugins
  166. * @function
  167. * @category Node
  168. * @memberof cracked
  169. * @name cracked#osc
  170. * @public
  171. * @param {Object} [userParams] map of optional values
  172. * @param {Number} [userParams.frequency=440]
  173. * @param {Number} [userParams.detune=0]
  174. * @param {String} [userParams.type=sine]
  175. */
  176. cracked.osc = function (userParams) {
  177. var creationParams = {
  178. "method": "createOscillator",
  179. "settings": {},
  180. "mapping": {
  181. "frequency": "frequency.value",
  182. "detune": "detune.value"
  183. }
  184. };
  185. createNode("osc", creationParams, userParams);
  186. return cracked;
  187. };
  188. /**
  189. * Native biquad filter, used by filter plugins
  190. * @function
  191. * @category Node
  192. * @memberof cracked
  193. * @name cracked#biquadFilter
  194. * @public
  195. * @param {Object} [userParams] map of optional values
  196. * @param {Number} [userParams.frequency=440]
  197. * @param {Number} [userParams.q=0]
  198. * @param {String} [userParams.gain=0]
  199. * @param {String} [userParams.type=lowpass]
  200. */
  201. cracked.biquadFilter = function (userParams) {
  202. var creationParams = {
  203. "method": "createBiquadFilter",
  204. "settings": {},
  205. "mapping": {
  206. "q": "Q.value",
  207. "frequency": "frequency.value",
  208. "gain": "gain.value"
  209. }
  210. };
  211. createNode("biquadFilter", creationParams, userParams);
  212. return cracked;
  213. };
  214. /**
  215. * Native channelMerger
  216. * @function
  217. * @category Node
  218. * @memberof cracked
  219. * @name cracked#channelMerger
  220. * @public
  221. * @param {Object} [userParams] map of optional values
  222. */
  223. cracked.channelMerger = function (userParams) {
  224. if(_maxChannelCount > 2) {
  225. _context.destination.channelCount = _maxChannelCount;
  226. _context.destination.channelCountMode = "explicit";
  227. _context.destination.channelInterpretation = "discrete";
  228. }
  229. var channels = __.isNum(userParams) ? userParams : (__.isObj(userParams) && userParams.channels) ? userParams.channels : _context.destination.maxChannelCount;
  230. var creationParams = {
  231. "method": "createChannelMerger",
  232. "methodParams":[channels],
  233. "settings": {
  234. channelCount:1,
  235. channelCountMode:"explicit",
  236. channelInterpretation:"discrete"
  237. }
  238. };
  239. createNode("channelMerger", creationParams, userParams);
  240. return cracked;
  241. };
  242. /**
  243. * Native channelSplitter
  244. * @function
  245. * @category Node
  246. * @memberof cracked
  247. * @name cracked#channelSplitter
  248. * @public
  249. * @param {Object} [userParams] map of optional values
  250. */
  251. cracked.channelSplitter = function (userParams) {
  252. userParams = userParams || 2;
  253. var channels = __.isNum(userParams) ? userParams : (__.isObj(userParams) && userParams.channels) ? userParams.channels : 2;
  254. var creationParams = {
  255. "method": "createChannelSplitter",
  256. "methodParams":[channels],
  257. "settings": {}
  258. };
  259. createNode("channelSplitter", creationParams, userParams);
  260. return cracked;
  261. };
  262. /**
  263. * Native convolver, used by reverb
  264. * @function
  265. * @category Node
  266. * @memberof cracked
  267. * @name cracked#convolver
  268. * @public
  269. * @param {Object} [userParams] map of optional values
  270. * @param {String} [userParams.path] path to remote impulse
  271. * @param {Function} [userParams.fn] function to generate impulse
  272. */
  273. cracked.convolver = function (userParams) {
  274. userParams = userParams || {};
  275. var creationParams = {
  276. "method": "createConvolver",
  277. "settings": {}
  278. };
  279. var node = createNode("convolver", creationParams, userParams);
  280. loadBuffer(userParams, node);
  281. return cracked;
  282. };
  283. /**
  284. * Native stereo panner, used by panner
  285. * @function
  286. * @category Node
  287. * @memberof cracked
  288. * @name cracked#stereoPanner
  289. * @public
  290. * @param {Object} [userParams] map of optional values
  291. */
  292. cracked.stereoPanner = function (userParams) {
  293. userParams = userParams || {};
  294. var creationParams = {
  295. "mapping": {
  296. "pan": "pan.value"
  297. },
  298. "method": "createStereoPanner",
  299. "settings": {}
  300. };
  301. createNode("stereoPanner", creationParams, userParams);
  302. return cracked;
  303. };
  304. /**
  305. * Native destination, used by the dac plugin
  306. * @function
  307. * @category Node
  308. * @memberof cracked
  309. * @name cracked#destination
  310. * @public
  311. * @param {Object} [userParams] map of optional values
  312. */
  313. cracked.destination = function (userParams) {
  314. createNode("destination", {
  315. "method": "createDestination",
  316. "settings": {}
  317. }, userParams);
  318. return cracked;
  319. };
  320. /**
  321. * Native sound input node, used by the adc plugin
  322. * origin = opposite of destination
  323. * @function
  324. * @category Node
  325. * @memberof cracked
  326. * @name cracked#origin
  327. * @public
  328. * @param {Object} [userParams] map of optional values
  329. */
  330. cracked.origin = function (userParams) {
  331. var cParams = {
  332. "method": "createOrigin",
  333. "settings": {}
  334. };
  335. //mediastream creation is async so we need to jump thru some hoops...
  336. //first create a temporary, silent imposter mediastream we get swap out later
  337. var tmpNode = createNode("origin", cParams, userParams);
  338. //now create the real object asynchronously and swap it in when its ready
  339. createMediaStreamSourceNode(cParams,tmpNode);
  340. return cracked;
  341. };
  342. /**
  343. * helper function for origin method
  344. * @function
  345. * @private
  346. */
  347. function createMockMediaStream(creationParams) {
  348. //create buffer-less buffer source object as our mock mediastream
  349. creationParams.method = "createBufferSource";
  350. var tmpnode = _context[creationParams.method].apply(_context, creationParams.methodParams || []);
  351. for (var creationParam in creationParams.settings) {
  352. if (creationParams.settings.hasOwnProperty(creationParam)) {
  353. applyParam(tmpnode, creationParam, creationParams.settings[creationParam], creationParams.mapping);
  354. }
  355. }
  356. return tmpnode;
  357. }
  358. /**
  359. * helper function for origin method
  360. * @function
  361. * @private
  362. */
  363. function createMediaStreamSourceNode(params,temporaryNode) {
  364. //make the real mediastream and drop it into place.
  365. var newNode = null;
  366. navigator.getUserMedia = (navigator.getUserMedia ||
  367. navigator.webkitGetUserMedia ||
  368. navigator.mozGetUserMedia ||
  369. navigator.msGetUserMedia);
  370. if(navigator.getUserMedia) {
  371. navigator.getUserMedia(
  372. {
  373. audio:true
  374. },
  375. (function(params){
  376. var p = params;
  377. return function(stream) {
  378. p.method = "createMediaStreamSource";
  379. p.methodParams = [stream];
  380. //made an actual media stream source
  381. newNode = audioNodeFactory(p);
  382. //update the imposter mediastream w/ the real thing
  383. getNodeWithUUID(temporaryNode.getNativeNode().uuid).replaceNode(newNode);
  384. };
  385. })(params),
  386. function(error) {
  387. console.error("createMediaStreamSourceNode: getUserMedia failed.");
  388. }
  389. );
  390. } else {
  391. console.error("createMediaStreamSourceNode: getUserMedia not supported.");
  392. }
  393. }
  394. /**
  395. * Native audio source node and buffer combined.
  396. * @function
  397. * @public
  398. * @memberof cracked
  399. * @name cracked#buffer
  400. * @param {Object} [userParams] map of optional values
  401. * @param {String} [userParams.path] path to remote file
  402. * @param {Number} [userParams.speed=1] playback speed
  403. * @param {Number} [userParams.start=0] play head start value in seconds
  404. * @param {Number} [userParams.end=0] play head end value in seconds
  405. * @param {Boolean} [userParams.loop=false] loop?
  406. */
  407. cracked.buffer = function (userParams) {
  408. var creationParams = {
  409. "method": "createBufferSource",
  410. "settings": {},
  411. "mapping": {
  412. "speed": "playbackRate.value",
  413. "start": "loopStart",
  414. "end": "loopEnd",
  415. "duration":"buffer.duration"
  416. }
  417. };
  418. var buffersrc = createNode("buffer", creationParams, userParams);
  419. loadBuffer(userParams, buffersrc);
  420. return cracked;
  421. };
  422. /**
  423. * helper function for buffer & reverb
  424. * @function
  425. * @private
  426. */
  427. function loadBuffer(userParams, node) {
  428. if (userParams && userParams.path && node) {
  429. loadBufferFromFile(userParams.path, node.getNativeNode());
  430. } else if (userParams && userParams.fn && node) {
  431. loadBufferWithData(userParams.fn, node.getNativeNode());
  432. }
  433. }
  434. /**
  435. * helper function for buffer & reverb
  436. * @function
  437. * @private
  438. */
  439. function loadBufferWithData(dataFunction, buffersrc) {
  440. if (dataFunction && buffersrc) {
  441. buffersrc.buffer = dataFunction(_context);
  442. }
  443. }
  444. /**
  445. * helper function for buffer & reverb
  446. * @function
  447. * @private
  448. */
  449. function loadBufferFromFile(path_to_soundfile, buffersrc) {
  450. if (path_to_soundfile && buffersrc) {
  451. fetchSoundFile(path_to_soundfile, function (sndArray) {
  452. _context.decodeAudioData(sndArray, function (buf) {
  453. buffersrc.buffer = buf;
  454. logToConsole("sound loaded");
  455. }, function (e) {
  456. logToConsole("Couldn't load audio");
  457. });
  458. });
  459. }
  460. }
  461. /**
  462. * asynchronously fetches a file for the buffer and returns an arraybuffer
  463. * @function
  464. * @private
  465. */
  466. function fetchSoundFile(path, callback) {
  467. if (path && callback) {
  468. var request = new XMLHttpRequest();
  469. request.open("GET", path, true); // Path to Audio File
  470. request.responseType = "arraybuffer"; // Read as Binary Data
  471. request.onload = function () {
  472. if (__.isFun(callback)) {
  473. callback(request.response);
  474. }
  475. };
  476. request.send();
  477. }
  478. }