diff --git a/package.json b/package.json index 590e92625..3c738719a 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "interactive-shader-format": "github:vcync/interactive-shader-format-js#feat/ImageBitmap", "lfo-for-modv": "0.0.1", "lodash.get": "^4.4.2", + "mathjs": "^3.20.2", "meyda": "^5.0.1", "mkdirp": "^0.5.1", "npm": "6.14.6", diff --git a/src/application/worker/store/modules/expressions.js b/src/application/worker/store/modules/expressions.js new file mode 100644 index 000000000..90099345e --- /dev/null +++ b/src/application/worker/store/modules/expressions.js @@ -0,0 +1,128 @@ +import math from "mathjs"; +import uuidv4 from "uuid/v4"; + +const state = { + assignments: {} +}; + +// getters +const getters = { + getByInputId: state => inputId => { + const assignmentValues = Object.values(state.assignments); + + return assignmentValues.find(assignment => assignment.inputId === inputId); + } +}; + +function compileExpression(expression) { + const scope = { value: 0, time: 0 }; + + let newFunction; + try { + const node = math.parse(expression, scope); + + newFunction = node.compile(); + newFunction.eval(scope); + } catch (e) { + throw e; + } + + return newFunction; +} + +// actions +const actions = { + create({ commit }, { expression = "value", id, inputId }) { + if (!inputId) { + throw new Error("Input ID required"); + } + + if (expression.trim() === "value") { + return null; + } + + const expressionId = id || uuidv4(); + + const func = compileExpression(expression); + + if (!func) { + throw new Error("Unable to compile Expression"); + } + + const assignment = { + id: expressionId, + inputId, + func, + expression + }; + + commit("ADD_EXPRESSION", { assignment }); + + return expressionId; + }, + + update({ commit }, { id, expression = "value" }) { + if (!id) { + throw new Error("Expression ID required"); + } + + const existingExpression = state.assignments[id]; + + if (!existingExpression) { + throw new Error(`Existing expression with ID ${id} not found`); + } + + if (expression.trim() === "value") { + commit("REMOVE_EXPRESSION", { id }); + return null; + } + + const func = compileExpression(expression); + + if (!func) { + throw new Error("Unable to compile Expression"); + } + + existingExpression.func = func; + existingExpression.expression = expression; + + commit("ADD_EXPRESSION", { assignment: existingExpression }); + return existingExpression.id; + }, + + remove({ commit }, args) { + commit("REMOVE_EXPRESSION", args); + }, + + createPresetData() { + return state; + }, + + async loadPresetData({ dispatch }, data) { + const assignments = Object.values(data.assignments); + for (let i = 0, len = assignments.length; i < len; i++) { + const assignment = assignments[i]; + + await dispatch("create", assignment); + } + } +}; + +// mutations +const mutations = { + ADD_EXPRESSION(state, { assignment }) { + state.assignments[assignment.id] = assignment; + }, + + REMOVE_EXPRESSION(state, { id }) { + delete state.assignments[id]; + } +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations +}; diff --git a/src/application/worker/store/modules/modules.js b/src/application/worker/store/modules/modules.js index 7aec22a2e..b6acccc1c 100644 --- a/src/application/worker/store/modules/modules.js +++ b/src/application/worker/store/modules/modules.js @@ -333,6 +333,7 @@ const actions = { { moduleId, prop, data, group, groupName, writeToSwap } ) { const moduleName = state.active[moduleId].$moduleName; + const inputId = state.active[moduleId].$props[prop].id; const propData = state.registered[moduleName].props[prop]; const currentValue = state.active[moduleId][prop]; const { type } = propData; @@ -347,18 +348,18 @@ const actions = { let dataOut = data; - // store.getters['plugins/enabledPlugins'] - // .filter(plugin => 'processValue' in plugin.plugin) - // .forEach(plugin => { - // const newValue = plugin.plugin.processValue({ - // currentValue: data, - // controlVariable: prop, - // delta: modV.delta, - // moduleName: name - // }) - - // if (typeof newValue !== 'undefined') dataOut = newValue - // }) + const expressionAssignment = store.getters["expressions/getByInputId"]( + inputId + ); + + if (expressionAssignment) { + const scope = { + value: dataOut, + time: Date.now() + }; + + dataOut = expressionAssignment.func.eval(scope); + } if (store.state.dataTypes[type] && store.state.dataTypes[type].create) { dataOut = await store.state.dataTypes[type].create(dataOut); diff --git a/src/components/InputConfig.vue b/src/components/InputConfig.vue index ed3eafb21..c443540ef 100644 --- a/src/components/InputConfig.vue +++ b/src/components/InputConfig.vue @@ -65,6 +65,18 @@ + + + + + +
@@ -77,6 +89,7 @@ import AudioFeatures from "./InputLinkComponents/AudioFeatures"; import MIDI from "./InputLinkComponents/MIDI"; import Tween from "./InputLinkComponents/Tween"; +import Expression from "./InputLinkComponents/Expression"; import CollapsibleRow from "./CollapsibleRow"; export default { @@ -84,6 +97,7 @@ export default { AudioFeatures, MIDI, Tween, + Expression, CollapsibleRow }, @@ -175,7 +189,6 @@ grid.borders > c:not(:last-child):not(:first-child) { diff --git a/yarn.lock b/yarn.lock index c8eb78214..b1e49e1d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3079,6 +3079,11 @@ complex.js@2.0.11: resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.11.tgz#09a873fbf15ffd8c18c9c2201ccef425c32b8bf1" integrity sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw== +complex.js@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.4.tgz#d8e7cfb9652d1e853e723386421c1a0ca7a48373" + integrity sha512-Syl95HpxUTS0QjwNxencZsKukgh1zdS9uXeXX2Us0pHaqBR6kiZZi0AkZ9VpZFwHJyVIUVzI4EumjWdXP3fy6w== + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -3689,6 +3694,11 @@ decimal.js@10.2.0: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== +decimal.js@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-9.0.1.tgz#1cc8b228177da7ab6498c1cc06eb130a290e6e1e" + integrity sha512-2h0iKbJwnImBk4TGk7CG1xadoA0g3LDPlQhQzbZ221zvG0p2YVUedbKIPsOZXKZGx6YmZMJKYOalpCMxSdDqTQ== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -4403,7 +4413,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-latex@1.2.0: +escape-latex@1.2.0, escape-latex@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== @@ -5208,6 +5218,11 @@ fraction.js@4.0.12: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.12.tgz#0526d47c65a5fb4854df78bc77f7bec708d7b8c3" integrity sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA== +fraction.js@4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.4.tgz#04e567110718adf7b52974a10434ab4c67a5183e" + integrity sha512-aK/oGatyYLTtXRHjfEsytX5fieeR5H4s8sLorzcT12taFS+dbMZejnvm9gRa8mZAPwci24ucjq9epDyaq5u8Iw== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -7636,6 +7651,20 @@ mathjs-expression-parser@^1.0.2: dependencies: mathjs "^5.2.3" +mathjs@^3.20.2: + version "3.20.2" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-3.20.2.tgz#3218aebde7de8cb5627c8fe3a1a216bf399ba91d" + integrity sha512-3f6/+uf1cUtIz1rYFz775wekl/UEDSQ3mU6xdxW7qzpvvhc2v28i3UtLsGTRB+u8OqDWoSX6Dz8gehaGFs6tCA== + dependencies: + complex.js "2.0.4" + decimal.js "9.0.1" + escape-latex "^1.0.0" + fraction.js "4.0.4" + javascript-natural-sort "0.7.1" + seed-random "2.2.0" + tiny-emitter "2.0.2" + typed-function "0.10.7" + mathjs@^5.2.3: version "5.10.3" resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-5.10.3.tgz#e998885f932ea8886db8b40f7f5b199f89b427f1" @@ -11687,6 +11716,11 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-emitter@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" + integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== + tiny-emitter@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" @@ -11875,6 +11909,11 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typed-function@0.10.7: + version "0.10.7" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-0.10.7.tgz#f702af7d77a64b61abf86799ff2d74266ebc4477" + integrity sha512-3mlZ5AwRMbLvUKkc8a1TI4RUJUS2H27pmD5q0lHRObgsoWzhDAX01yg82kwSP1FUw922/4Y9ZliIEh0qJZcz+g== + typed-function@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-1.1.0.tgz#ea149706e0fb42aca1791c053a6d94ccd6c4fdcb"