From d781a8aa3d01b6ebfea52409568eb38bd0445e7f Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Sat, 28 Nov 2020 23:51:41 +0000 Subject: [PATCH 1/5] feat(expression): adds expressions Adds the expression editor from modV 2 fix #453 fix #415 --- package.json | 1 + .../worker/store/modules/expressions.js | 116 ++++++++++++++++++ .../worker/store/modules/modules.js | 24 ++-- src/components/InputConfig.vue | 14 +++ .../InputLinkComponents/Expression.vue | 84 +++++++++++++ src/components/inputs/Textarea.vue | 1 + yarn.lock | 41 ++++++- 7 files changed, 268 insertions(+), 13 deletions(-) create mode 100644 src/application/worker/store/modules/expressions.js create mode 100644 src/components/InputLinkComponents/Expression.vue diff --git a/package.json b/package.json index 4c0b0c0c7..83cd92d56 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..d9665b50f --- /dev/null +++ b/src/application/worker/store/modules/expressions.js @@ -0,0 +1,116 @@ +import math from "mathjs"; +import uuidv4 from "uuid/v4"; + +const state = { + assignments: {}, + delta: 0 +}; + +// 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, delta: 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", inputId }) { + if (!inputId) { + throw new Error("Input ID required"); + } + + if (expression.trim() === "value") { + return null; + } + + const expressionId = 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); + } +}; + +// 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 77b1a3341..6ac72c7ae 100644 --- a/src/application/worker/store/modules/modules.js +++ b/src/application/worker/store/modules/modules.js @@ -301,6 +301,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; @@ -315,18 +316,17 @@ 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 + }; + + 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 9193e7f4f..84fac1da0 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 }, diff --git a/src/components/InputLinkComponents/Expression.vue b/src/components/InputLinkComponents/Expression.vue new file mode 100644 index 000000000..c8d960110 --- /dev/null +++ b/src/components/InputLinkComponents/Expression.vue @@ -0,0 +1,84 @@ +