From f5ba3c9fb3b69d9f10b56a2cd92a90beca5e302b Mon Sep 17 00:00:00 2001 From: Lajos Meszaros Date: Thu, 7 Feb 2019 17:15:39 +0100 Subject: [PATCH] feat(DelayNode): Implemented DelayNode and created a demo with it --- poc/common/virtual-webaudio.js | 16 +++- poc/index.html | 1 + poc/monosynth-with-delay/index.html | 22 +++++ poc/monosynth-with-delay/script.js | 136 ++++++++++++++++++++++++++++ src/VirtualAudioContext.js | 8 +- src/VirtualDelayNode.js | 22 +++++ src/helpers.js | 5 + 7 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 poc/monosynth-with-delay/index.html create mode 100644 poc/monosynth-with-delay/script.js create mode 100644 src/VirtualDelayNode.js diff --git a/poc/common/virtual-webaudio.js b/poc/common/virtual-webaudio.js index fc2d1bf..86734da 100644 --- a/poc/common/virtual-webaudio.js +++ b/poc/common/virtual-webaudio.js @@ -4067,7 +4067,7 @@ eval("__webpack_require__.r(__webpack_exports__);\nfunction _classCallCheck(inst /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./constants */ \"./src/constants.js\");\n/* harmony import */ var _UniqueIdGenerator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./UniqueIdGenerator */ \"./src/UniqueIdGenerator.js\");\n/* harmony import */ var _Events__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Events */ \"./src/Events.js\");\n/* harmony import */ var _VirtualOscillatorNode__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./VirtualOscillatorNode */ \"./src/VirtualOscillatorNode.js\");\n/* harmony import */ var _VirtualGainNode__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./VirtualGainNode */ \"./src/VirtualGainNode.js\");\n/* harmony import */ var _VirtualPeriodicWave__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./VirtualPeriodicWave */ \"./src/VirtualPeriodicWave.js\");\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n\n\n\n\n\n\n\nvar VirtualAudioContext =\n/*#__PURE__*/\nfunction () {\n function VirtualAudioContext() {\n _classCallCheck(this, VirtualAudioContext);\n\n this._ = {\n uniqueIdGenerator: new _UniqueIdGenerator__WEBPACK_IMPORTED_MODULE_1__[\"default\"](0),\n events: new _Events__WEBPACK_IMPORTED_MODULE_2__[\"default\"](),\n initialTime: Date.now(),\n unitOfTimeInSeconds: 0.02\n };\n }\n\n _createClass(VirtualAudioContext, [{\n key: \"createGain\",\n\n /*\r\n get listener\r\n get sampleRate\r\n get state\r\n createAnalyser()\r\n createBiquadFilter()\r\n createBuffer()\r\n createBufferSource()\r\n createChannelMerger()\r\n createChannelSplitter()\r\n createConstantSource()\r\n createConvolver()\r\n createDelay()\r\n createDynamicsCompressor()\r\n */\n value: function createGain() {\n var id = this._.uniqueIdGenerator.generate();\n\n return new _VirtualGainNode__WEBPACK_IMPORTED_MODULE_4__[\"default\"](id, this);\n }\n /*\r\n createIIRFilter()\r\n */\n\n }, {\n key: \"createOscillator\",\n value: function createOscillator() {\n var id = this._.uniqueIdGenerator.generate();\n\n return new _VirtualOscillatorNode__WEBPACK_IMPORTED_MODULE_3__[\"default\"](id, this);\n }\n /*\r\n createPanner()\r\n */\n\n }, {\n key: \"createPeriodicWave\",\n value: function createPeriodicWave(real, imag\n /*, constraints */\n ) {\n var id = this._.uniqueIdGenerator.generate();\n\n return new _VirtualPeriodicWave__WEBPACK_IMPORTED_MODULE_5__[\"default\"](id, this, real, imag);\n }\n /*\r\n createScriptProcessor()\r\n createStereoPanner()\r\n createWaveShaper()\r\n decodeAudioData()\r\n resume()\r\n */\n\n }, {\n key: \"currentTime\",\n get: function get() {\n var _this$_ = this._,\n initialTime = _this$_.initialTime,\n unitOfTimeInSeconds = _this$_.unitOfTimeInSeconds;\n var realTimeInSeconds = (Date.now() - initialTime) / 1000;\n var throttle = unitOfTimeInSeconds * 1000;\n return Math.round(realTimeInSeconds * throttle) / throttle;\n }\n }, {\n key: \"destination\",\n get: function get() {\n return _constants__WEBPACK_IMPORTED_MODULE_0__[\"CTX_DESTINATION\"];\n }\n }]);\n\n return VirtualAudioContext;\n}();\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (VirtualAudioContext);\n\n//# sourceURL=webpack://virtualWebaudio/./src/VirtualAudioContext.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./constants */ \"./src/constants.js\");\n/* harmony import */ var _UniqueIdGenerator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./UniqueIdGenerator */ \"./src/UniqueIdGenerator.js\");\n/* harmony import */ var _Events__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Events */ \"./src/Events.js\");\n/* harmony import */ var _VirtualOscillatorNode__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./VirtualOscillatorNode */ \"./src/VirtualOscillatorNode.js\");\n/* harmony import */ var _VirtualGainNode__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./VirtualGainNode */ \"./src/VirtualGainNode.js\");\n/* harmony import */ var _VirtualPeriodicWave__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./VirtualPeriodicWave */ \"./src/VirtualPeriodicWave.js\");\n/* harmony import */ var _VirtualDelayNode__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./VirtualDelayNode */ \"./src/VirtualDelayNode.js\");\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n\n\n\n\n\n\n\n\nvar VirtualAudioContext =\n/*#__PURE__*/\nfunction () {\n function VirtualAudioContext() {\n _classCallCheck(this, VirtualAudioContext);\n\n this._ = {\n uniqueIdGenerator: new _UniqueIdGenerator__WEBPACK_IMPORTED_MODULE_1__[\"default\"](0),\n events: new _Events__WEBPACK_IMPORTED_MODULE_2__[\"default\"](),\n initialTime: Date.now(),\n unitOfTimeInSeconds: 0.02\n };\n }\n\n _createClass(VirtualAudioContext, [{\n key: \"createDelay\",\n\n /*\r\n get listener\r\n get sampleRate\r\n get state\r\n createAnalyser()\r\n createBiquadFilter()\r\n createBuffer()\r\n createBufferSource()\r\n createChannelMerger()\r\n createChannelSplitter()\r\n createConstantSource()\r\n createConvolver()\r\n */\n value: function createDelay() {\n var maxDelayTime = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;\n\n var id = this._.uniqueIdGenerator.generate();\n\n return new _VirtualDelayNode__WEBPACK_IMPORTED_MODULE_6__[\"default\"](id, this, maxDelayTime);\n }\n /*\r\n createDynamicsCompressor()\r\n */\n\n }, {\n key: \"createGain\",\n value: function createGain() {\n var id = this._.uniqueIdGenerator.generate();\n\n return new _VirtualGainNode__WEBPACK_IMPORTED_MODULE_4__[\"default\"](id, this);\n }\n /*\r\n createIIRFilter()\r\n */\n\n }, {\n key: \"createOscillator\",\n value: function createOscillator() {\n var id = this._.uniqueIdGenerator.generate();\n\n return new _VirtualOscillatorNode__WEBPACK_IMPORTED_MODULE_3__[\"default\"](id, this);\n }\n /*\r\n createPanner()\r\n */\n\n }, {\n key: \"createPeriodicWave\",\n value: function createPeriodicWave(real, imag\n /*, constraints */\n ) {\n var id = this._.uniqueIdGenerator.generate();\n\n return new _VirtualPeriodicWave__WEBPACK_IMPORTED_MODULE_5__[\"default\"](id, this, real, imag);\n }\n /*\r\n createScriptProcessor()\r\n createStereoPanner()\r\n createWaveShaper()\r\n decodeAudioData()\r\n resume()\r\n */\n\n }, {\n key: \"currentTime\",\n get: function get() {\n var _this$_ = this._,\n initialTime = _this$_.initialTime,\n unitOfTimeInSeconds = _this$_.unitOfTimeInSeconds;\n var realTimeInSeconds = (Date.now() - initialTime) / 1000;\n var throttle = unitOfTimeInSeconds * 1000;\n return Math.round(realTimeInSeconds * throttle) / throttle;\n }\n }, {\n key: \"destination\",\n get: function get() {\n return _constants__WEBPACK_IMPORTED_MODULE_0__[\"CTX_DESTINATION\"];\n }\n }]);\n\n return VirtualAudioContext;\n}();\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (VirtualAudioContext);\n\n//# sourceURL=webpack://virtualWebaudio/./src/VirtualAudioContext.js?"); /***/ }), @@ -4095,6 +4095,18 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _con /***/ }), +/***/ "./src/VirtualDelayNode.js": +/*!*********************************!*\ + !*** ./src/VirtualDelayNode.js ***! + \*********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _VirtualAudioNode__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./VirtualAudioNode */ \"./src/VirtualAudioNode.js\");\n/* harmony import */ var _VirtualAudioParam__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./VirtualAudioParam */ \"./src/VirtualAudioParam.js\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./constants */ \"./src/constants.js\");\nfunction _typeof(obj) { if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\n\n\n\n\nvar VirtualDelayNode =\n/*#__PURE__*/\nfunction (_VirtualAudioNode) {\n _inherits(VirtualDelayNode, _VirtualAudioNode);\n\n function VirtualDelayNode(id, ctx) {\n var _this;\n\n _classCallCheck(this, VirtualDelayNode);\n\n _this = _possibleConstructorReturn(this, _getPrototypeOf(VirtualDelayNode).call(this, id, ctx)); // TODO: add validation\n // * maxDelayTime is an integer between 0 and 180, default 1\n // * delayTime is an integer between 0 and maxDelayTime, default 0\n\n for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n\n var maxDelayTime = args[0];\n _this._.maxDelayTime = maxDelayTime;\n _this.delayTime = new _VirtualAudioParam__WEBPACK_IMPORTED_MODULE_1__[\"default\"](id, ctx, 'delayTime', 0);\n\n ctx._.events.add(_constants__WEBPACK_IMPORTED_MODULE_2__[\"EVENTS\"].CREATE, 'delay', id, ctx.currentTime, args);\n\n return _this;\n }\n\n return VirtualDelayNode;\n}(_VirtualAudioNode__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (VirtualDelayNode);\n\n//# sourceURL=webpack://virtualWebaudio/./src/VirtualDelayNode.js?"); + +/***/ }), + /***/ "./src/VirtualGainNode.js": /*!********************************!*\ !*** ./src/VirtualGainNode.js ***! @@ -4151,7 +4163,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"invertEvent\", function() { return invertEvent; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getNodeById\", function() { return getNodeById; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setNodeById\", function() { return setNodeById; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"removeNodeById\", function() { return removeNodeById; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"markTimeArg\", function() { return markTimeArg; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"parseTimeArg\", function() { return parseTimeArg; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"applyEventToContext\", function() { return applyEventToContext; });\n/* harmony import */ var ramda__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ramda */ \"./node_modules/ramda/es/index.js\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./constants */ \"./src/constants.js\");\n/* harmony import */ var _VirtualPeriodicWave__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./VirtualPeriodicWave */ \"./src/VirtualPeriodicWave.js\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\nvar invertEvent = Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"cond\"])([[Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CREATE), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].REMOVE)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].UPDATE), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].NOP)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CONNECT), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].DISCONNECT)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].DISCONNECT), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CONNECT)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].SET), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].NOP)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CALL), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"cond\"])([[Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"equals\"])('start'), ramda__WEBPACK_IMPORTED_MODULE_0__[\"last\"], Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"prop\"])('param')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"evolve\"])({\n param: Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"update\"])(-1, 'stop')\n})], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"equals\"])('stop'), ramda__WEBPACK_IMPORTED_MODULE_0__[\"last\"], Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"prop\"])('param')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"evolve\"])({\n param: Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"update\"])(-1, 'start')\n})], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"contains\"])(ramda__WEBPACK_IMPORTED_MODULE_0__[\"__\"], ['setPeriodicWave', 'cancelScheduledValues', 'cancelAndHoldAtTime']), ramda__WEBPACK_IMPORTED_MODULE_0__[\"last\"], Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"prop\"])('param')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].NOP)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"contains\"])(ramda__WEBPACK_IMPORTED_MODULE_0__[\"__\"], ['setValueAtTime', 'linearRampToValueAtTime', 'exponentialRampToValueAtTime', 'setTargetAtTime', 'setValueCurveAtTime']), ramda__WEBPACK_IMPORTED_MODULE_0__[\"last\"], Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"prop\"])('param')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"evolve\"])({\n param: Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"update\"])(-1, 'cancelAndHoldAtTime'),\n args: function args() {\n return [markTimeArg(-1)];\n }\n})], [ramda__WEBPACK_IMPORTED_MODULE_0__[\"T\"], function (_ref) {\n var param = _ref.param;\n console.error(\"inverting: unknown command \".concat(param));\n}]])], [ramda__WEBPACK_IMPORTED_MODULE_0__[\"T\"], function (_ref2) {\n var eventName = _ref2.eventName;\n console.error(\"inverting: unknown event \".concat(eventName));\n}]]);\n\nvar getNodeById = function getNodeById(id, ctx) {\n return ctx._nodes[id];\n};\n\nvar setNodeById = function setNodeById(id, node, ctx) {\n if (!ctx._nodes) {\n ctx._nodes = {};\n }\n\n ctx._nodes[id] = node;\n};\n\nvar removeNodeById = function removeNodeById(id, ctx) {\n delete ctx._nodes[id];\n};\n\nvar TIME_MARK = '{{currentTime}}';\n\nvar markTimeArg = function markTimeArg(arg) {\n return TIME_MARK + ' + ' + arg;\n};\n\nvar parseTimeArg = Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"curry\"])(function (time, arg) {\n return Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"when\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"both\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"is\"])(String), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"startsWith\"])(TIME_MARK + ' + ')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"when\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"lt\"])(ramda__WEBPACK_IMPORTED_MODULE_0__[\"__\"], 0), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"always\"])(0)), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"add\"])(time), parseFloat, Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"replace\"])(TIME_MARK + ' + ', '')))(arg);\n});\nvar applyEventToContext = Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"curry\"])(function (_ref3, ctx) {\n var targetId = _ref3.targetId,\n eventName = _ref3.eventName,\n param = _ref3.param,\n time = _ref3.time,\n args = _ref3.args;\n\n // TODO: how to deal with time?\n switch (eventName) {\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CREATE:\n switch (param) {\n case 'oscillator':\n {\n var node = ctx.createOscillator();\n setNodeById(targetId, node, ctx);\n }\n break;\n\n case 'gain':\n {\n var _node = ctx.createGain();\n\n setNodeById(targetId, _node, ctx);\n }\n break;\n\n case 'periodicWave':\n {\n var _node2 = Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"apply\"])(ctx.createPeriodicWave.bind(ctx), args);\n\n setNodeById(targetId, _node2, ctx);\n }\n break;\n\n default:\n {\n console.error('unknown node type', param);\n }\n }\n\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].UPDATE:\n {\n var _node3 = getNodeById(targetId, ctx);\n\n _node3[param].value = args[0];\n }\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].SET:\n {\n var _node4 = getNodeById(targetId, ctx);\n\n _node4[param] = args[0];\n }\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CONNECT:\n {\n var _node5 = getNodeById(targetId, ctx);\n\n switch (param.length) {\n case 1:\n {\n var target = param[0] === _constants__WEBPACK_IMPORTED_MODULE_1__[\"CTX_DESTINATION\"] ? ctx.destination : getNodeById(param[0], ctx);\n\n _node5.connect(target);\n }\n break;\n\n case 2:\n {\n var _target = getNodeById(param[0], ctx);\n\n var property = _target[param[1]];\n\n _node5.connect(property);\n }\n }\n }\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CALL:\n {\n var _node6 = getNodeById(targetId, ctx);\n\n switch (Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"length\"])(param)) {\n case 1:\n {\n var _param = _slicedToArray(param, 1),\n command = _param[0];\n\n switch (command) {\n case 'start':\n case 'stop':\n Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"apply\"])(_node6[command].bind(_node6), args);\n break;\n\n case 'setPeriodicWave':\n if (args[0] instanceof _VirtualPeriodicWave__WEBPACK_IMPORTED_MODULE_2__[\"default\"]) {\n var nodeId = args[0]._.id; // args[0] is a VirtualPeriodicWave, but we need the real one\n\n var argNode = getNodeById(nodeId, ctx);\n\n _node6[command](argNode);\n } else {\n _node6[command](args[0]);\n }\n\n break;\n\n default:\n {\n console.error('unknown command', command);\n }\n }\n }\n break;\n\n case 2:\n {\n var _param2 = _slicedToArray(param, 2),\n paramName = _param2[0],\n _command = _param2[1];\n\n switch (_command) {\n case 'setValueAtTime':\n case 'linearRampToValueAtTime':\n case 'exponentialRampToValueAtTime':\n case 'setTargetAtTime':\n case 'setValueCurveAtTime':\n case 'cancelScheduledValues':\n case 'cancelAndHoldAtTime':\n Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"apply\"])(_node6[paramName][_command].bind(_node6[paramName]), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"map\"])(parseTimeArg(time), args));\n break;\n\n default:\n {\n console.error('unknown command', _command);\n }\n }\n }\n break;\n }\n }\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].REMOVE:\n removeNodeById(targetId, ctx);\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].DISCONNECT:\n {\n var _node7 = getNodeById(targetId, ctx);\n\n switch (param.length) {\n case 0:\n _node7.disconnect();\n\n break;\n\n case 1:\n {\n var _target2 = param[0] === _constants__WEBPACK_IMPORTED_MODULE_1__[\"CTX_DESTINATION\"] ? ctx.destination : getNodeById(param[0], ctx);\n\n _node7.disconnect(_target2);\n }\n break;\n\n case 2:\n {\n var _target3 = getNodeById(param[0], ctx);\n\n var _property = _target3[param[1]];\n\n _node7.disconnect(_property);\n }\n }\n }\n break;\n\n default:\n {\n console.error('unknown event', eventName);\n }\n }\n});\n\n\n//# sourceURL=webpack://virtualWebaudio/./src/helpers.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"invertEvent\", function() { return invertEvent; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getNodeById\", function() { return getNodeById; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setNodeById\", function() { return setNodeById; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"removeNodeById\", function() { return removeNodeById; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"markTimeArg\", function() { return markTimeArg; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"parseTimeArg\", function() { return parseTimeArg; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"applyEventToContext\", function() { return applyEventToContext; });\n/* harmony import */ var ramda__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ramda */ \"./node_modules/ramda/es/index.js\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./constants */ \"./src/constants.js\");\n/* harmony import */ var _VirtualPeriodicWave__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./VirtualPeriodicWave */ \"./src/VirtualPeriodicWave.js\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\nvar invertEvent = Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"cond\"])([[Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CREATE), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].REMOVE)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].UPDATE), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].NOP)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CONNECT), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].DISCONNECT)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].DISCONNECT), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CONNECT)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].SET), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].NOP)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"propEq\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CALL), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"cond\"])([[Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"equals\"])('start'), ramda__WEBPACK_IMPORTED_MODULE_0__[\"last\"], Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"prop\"])('param')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"evolve\"])({\n param: Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"update\"])(-1, 'stop')\n})], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"equals\"])('stop'), ramda__WEBPACK_IMPORTED_MODULE_0__[\"last\"], Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"prop\"])('param')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"evolve\"])({\n param: Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"update\"])(-1, 'start')\n})], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"contains\"])(ramda__WEBPACK_IMPORTED_MODULE_0__[\"__\"], ['setPeriodicWave', 'cancelScheduledValues', 'cancelAndHoldAtTime']), ramda__WEBPACK_IMPORTED_MODULE_0__[\"last\"], Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"prop\"])('param')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"assoc\"])('eventName', _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].NOP)], [Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"contains\"])(ramda__WEBPACK_IMPORTED_MODULE_0__[\"__\"], ['setValueAtTime', 'linearRampToValueAtTime', 'exponentialRampToValueAtTime', 'setTargetAtTime', 'setValueCurveAtTime']), ramda__WEBPACK_IMPORTED_MODULE_0__[\"last\"], Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"prop\"])('param')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"evolve\"])({\n param: Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"update\"])(-1, 'cancelAndHoldAtTime'),\n args: function args() {\n return [markTimeArg(-1)];\n }\n})], [ramda__WEBPACK_IMPORTED_MODULE_0__[\"T\"], function (_ref) {\n var param = _ref.param;\n console.error(\"inverting: unknown command \".concat(param));\n}]])], [ramda__WEBPACK_IMPORTED_MODULE_0__[\"T\"], function (_ref2) {\n var eventName = _ref2.eventName;\n console.error(\"inverting: unknown event \".concat(eventName));\n}]]);\n\nvar getNodeById = function getNodeById(id, ctx) {\n return ctx._nodes[id];\n};\n\nvar setNodeById = function setNodeById(id, node, ctx) {\n if (!ctx._nodes) {\n ctx._nodes = {};\n }\n\n ctx._nodes[id] = node;\n};\n\nvar removeNodeById = function removeNodeById(id, ctx) {\n delete ctx._nodes[id];\n};\n\nvar TIME_MARK = '{{currentTime}}';\n\nvar markTimeArg = function markTimeArg(arg) {\n return TIME_MARK + ' + ' + arg;\n};\n\nvar parseTimeArg = Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"curry\"])(function (time, arg) {\n return Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"when\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"both\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"is\"])(String), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"startsWith\"])(TIME_MARK + ' + ')), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"compose\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"when\"])(Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"lt\"])(ramda__WEBPACK_IMPORTED_MODULE_0__[\"__\"], 0), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"always\"])(0)), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"add\"])(time), parseFloat, Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"replace\"])(TIME_MARK + ' + ', '')))(arg);\n});\nvar applyEventToContext = Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"curry\"])(function (_ref3, ctx) {\n var targetId = _ref3.targetId,\n eventName = _ref3.eventName,\n param = _ref3.param,\n time = _ref3.time,\n args = _ref3.args;\n\n // TODO: how to deal with time?\n switch (eventName) {\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CREATE:\n switch (param) {\n case 'oscillator':\n {\n var node = ctx.createOscillator();\n setNodeById(targetId, node, ctx);\n }\n break;\n\n case 'gain':\n {\n var _node = ctx.createGain();\n\n setNodeById(targetId, _node, ctx);\n }\n break;\n\n case 'periodicWave':\n {\n var _node2 = Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"apply\"])(ctx.createPeriodicWave.bind(ctx), args);\n\n setNodeById(targetId, _node2, ctx);\n }\n break;\n\n case 'delay':\n {\n var _node3 = Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"apply\"])(ctx.createDelay.bind(ctx), args);\n\n setNodeById(targetId, _node3, ctx);\n }\n break;\n\n default:\n {\n console.error('unknown node type', param);\n }\n }\n\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].UPDATE:\n {\n var _node4 = getNodeById(targetId, ctx);\n\n _node4[param].value = args[0];\n }\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].SET:\n {\n var _node5 = getNodeById(targetId, ctx);\n\n _node5[param] = args[0];\n }\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CONNECT:\n {\n var _node6 = getNodeById(targetId, ctx);\n\n switch (param.length) {\n case 1:\n {\n var target = param[0] === _constants__WEBPACK_IMPORTED_MODULE_1__[\"CTX_DESTINATION\"] ? ctx.destination : getNodeById(param[0], ctx);\n\n _node6.connect(target);\n }\n break;\n\n case 2:\n {\n var _target = getNodeById(param[0], ctx);\n\n var property = _target[param[1]];\n\n _node6.connect(property);\n }\n }\n }\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].CALL:\n {\n var _node7 = getNodeById(targetId, ctx);\n\n switch (Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"length\"])(param)) {\n case 1:\n {\n var _param = _slicedToArray(param, 1),\n command = _param[0];\n\n switch (command) {\n case 'start':\n case 'stop':\n Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"apply\"])(_node7[command].bind(_node7), args);\n break;\n\n case 'setPeriodicWave':\n if (args[0] instanceof _VirtualPeriodicWave__WEBPACK_IMPORTED_MODULE_2__[\"default\"]) {\n var nodeId = args[0]._.id; // args[0] is a VirtualPeriodicWave, but we need the real one\n\n var argNode = getNodeById(nodeId, ctx);\n\n _node7[command](argNode);\n } else {\n _node7[command](args[0]);\n }\n\n break;\n\n default:\n {\n console.error('unknown command', command);\n }\n }\n }\n break;\n\n case 2:\n {\n var _param2 = _slicedToArray(param, 2),\n paramName = _param2[0],\n _command = _param2[1];\n\n switch (_command) {\n case 'setValueAtTime':\n case 'linearRampToValueAtTime':\n case 'exponentialRampToValueAtTime':\n case 'setTargetAtTime':\n case 'setValueCurveAtTime':\n case 'cancelScheduledValues':\n case 'cancelAndHoldAtTime':\n Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"apply\"])(_node7[paramName][_command].bind(_node7[paramName]), Object(ramda__WEBPACK_IMPORTED_MODULE_0__[\"map\"])(parseTimeArg(time), args));\n break;\n\n default:\n {\n console.error('unknown command', _command);\n }\n }\n }\n break;\n }\n }\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].REMOVE:\n removeNodeById(targetId, ctx);\n break;\n\n case _constants__WEBPACK_IMPORTED_MODULE_1__[\"EVENTS\"].DISCONNECT:\n {\n var _node8 = getNodeById(targetId, ctx);\n\n switch (param.length) {\n case 0:\n _node8.disconnect();\n\n break;\n\n case 1:\n {\n var _target2 = param[0] === _constants__WEBPACK_IMPORTED_MODULE_1__[\"CTX_DESTINATION\"] ? ctx.destination : getNodeById(param[0], ctx);\n\n _node8.disconnect(_target2);\n }\n break;\n\n case 2:\n {\n var _target3 = getNodeById(param[0], ctx);\n\n var _property = _target3[param[1]];\n\n _node8.disconnect(_property);\n }\n }\n }\n break;\n\n default:\n {\n console.error('unknown event', eventName);\n }\n }\n});\n\n\n//# sourceURL=webpack://virtualWebaudio/./src/helpers.js?"); /***/ }), diff --git a/poc/index.html b/poc/index.html index 9c49178..9680717 100644 --- a/poc/index.html +++ b/poc/index.html @@ -16,6 +16,7 @@

virtual-webaudio demos

  • envelope monosynth
  • offline audio context
  • scheduled sequencing
  • +
  • delayed monosynth
  • diff --git a/poc/monosynth-with-delay/index.html b/poc/monosynth-with-delay/index.html new file mode 100644 index 0000000..02254f1 --- /dev/null +++ b/poc/monosynth-with-delay/index.html @@ -0,0 +1,22 @@ + + + + + Monosynth with delay + + + +

    A simple, 1 octave, monophonic synthesizer, that you can play with the keyboard with added 5 stage delay effect

    + Press the following keys to play the monosynth: +
    +   W E   T Y U
    +  A S D F G H J K
    +  
    + + + + + + \ No newline at end of file diff --git a/poc/monosynth-with-delay/script.js b/poc/monosynth-with-delay/script.js new file mode 100644 index 0000000..376e1b9 --- /dev/null +++ b/poc/monosynth-with-delay/script.js @@ -0,0 +1,136 @@ +/* global virtualWebaudio, AudioContext */ + +const { VirtualAudioContext, patch, render, diff } = virtualWebaudio + +const sound = (frequency, volume, delayInSeconds) => { + const ctx = new VirtualAudioContext() + + const osc = ctx.createOscillator() + const gain = ctx.createGain() + + osc.frequency.linearRampToValueAtTime(frequency, ctx.currentTime + 0.01) + osc.type = 'triangle' + + gain.gain.linearRampToValueAtTime(volume, ctx.currentTime + delayInSeconds) + + const delays = [ + { delay: ctx.createDelay(2), delayGain: ctx.createGain() }, + { delay: ctx.createDelay(2), delayGain: ctx.createGain() }, + { delay: ctx.createDelay(2), delayGain: ctx.createGain() }, + { delay: ctx.createDelay(2), delayGain: ctx.createGain() }, + { delay: ctx.createDelay(2), delayGain: ctx.createGain() } + ] + delays.forEach(({ delay, delayGain }, index) => { + delayGain.gain.value = 0.9 - index / 5 + delay.delayTime.value = 0.3 * index + + gain.connect(delay) + delay.connect(delayGain) + delayGain.connect(ctx.destination) + }) + + osc.connect(gain) + gain.connect(ctx.destination) + + osc.start() + + return ctx +} + +const frequencies = [ + 261.6, + 277.1, + 293.6, + 311.1, + 329.6, + 349.2, + 369.9, + 391.9, + 415.3, + 440, + 466.1, + 493.8, + 523.2 +] +const keyMap = { + A: 0, + W: 1, + S: 2, + E: 3, + D: 4, + F: 5, + T: 6, + G: 7, + Z: 8, + Y: 8, + H: 9, + U: 10, + J: 11, + K: 12 +} + +let ctx +let old = null + +const change = (virtualCtx, ctx) => { + if (old === null) { + render(virtualCtx, ctx) + } else { + patch(diff(old, virtualCtx), ctx) + } + + old = virtualCtx +} + +let lastPressedNoteIndex = null +let isNotePlaying = false +const keyPressed = { + A: false, + W: false, + S: false, + E: false, + D: false, + F: false, + T: false, + G: false, + Z: false, + Y: false, + H: false, + U: false, + J: false, + K: false +} + +document.body.addEventListener('keydown', e => { + if (!ctx) { + ctx = new AudioContext() + } + const pressedChar = String.fromCharCode(e.keyCode) + if (!keyPressed[pressedChar]) { + keyPressed[pressedChar] = true + + if (keyMap.hasOwnProperty(pressedChar)) { + if (lastPressedNoteIndex !== keyMap[pressedChar] || !isNotePlaying) { + lastPressedNoteIndex = keyMap[pressedChar] + isNotePlaying = true + + change(sound(frequencies[lastPressedNoteIndex], 1, 1), ctx) + } + } + } +}) + +document.body.addEventListener('keyup', e => { + const pressedChar = String.fromCharCode(e.keyCode) + if (keyPressed[pressedChar]) { + keyPressed[pressedChar] = false + + if (keyMap.hasOwnProperty(pressedChar)) { + if (lastPressedNoteIndex === keyMap[pressedChar] && isNotePlaying) { + lastPressedNoteIndex = keyMap[pressedChar] + isNotePlaying = false + change(sound(frequencies[lastPressedNoteIndex], 0, 1), ctx) + } + } + } +}) diff --git a/src/VirtualAudioContext.js b/src/VirtualAudioContext.js index 0c1bf1f..6453866 100644 --- a/src/VirtualAudioContext.js +++ b/src/VirtualAudioContext.js @@ -5,6 +5,7 @@ import Events from './Events' import VirtualOscillatorNode from './VirtualOscillatorNode' import VirtualGainNode from './VirtualGainNode' import VirtualPeriodicWave from './VirtualPeriodicWave' +import VirtualDelayNode from './VirtualDelayNode' class VirtualAudioContext { constructor () { @@ -37,7 +38,12 @@ class VirtualAudioContext { createChannelSplitter() createConstantSource() createConvolver() - createDelay() + */ + createDelay (maxDelayTime = 1) { + const id = this._.uniqueIdGenerator.generate() + return new VirtualDelayNode(id, this, maxDelayTime) + } + /* createDynamicsCompressor() */ createGain () { diff --git a/src/VirtualDelayNode.js b/src/VirtualDelayNode.js new file mode 100644 index 0000000..b057279 --- /dev/null +++ b/src/VirtualDelayNode.js @@ -0,0 +1,22 @@ +import VirtualAudioNode from './VirtualAudioNode' +import VirtualAudioParam from './VirtualAudioParam' + +import { EVENTS } from './constants' + +class VirtualDelayNode extends VirtualAudioNode { + constructor (id, ctx, ...args) { + super(id, ctx) + + // TODO: add validation + // * maxDelayTime is an integer between 0 and 180, default 1 + // * delayTime is an integer between 0 and maxDelayTime, default 0 + + const [ maxDelayTime ] = args + this._.maxDelayTime = maxDelayTime + this.delayTime = new VirtualAudioParam(id, ctx, 'delayTime', 0) + + ctx._.events.add(EVENTS.CREATE, 'delay', id, ctx.currentTime, args) + } +} + +export default VirtualDelayNode diff --git a/src/helpers.js b/src/helpers.js index 8d6bd7e..8295ce2 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -110,6 +110,11 @@ const applyEventToContext = curry(({ targetId, eventName, param, time, args }, c setNodeById(targetId, node, ctx) } break + case 'delay': { + const node = apply(ctx.createDelay.bind(ctx), args) + setNodeById(targetId, node, ctx) + } + break default: { console.error('unknown node type', param) }