From ad6ca34c75bf512613bdb0524a3040b1f8124e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Wed, 2 Oct 2019 10:53:17 +0200 Subject: [PATCH 001/131] Store refactored. --- src/js/wh/state/selectors.js | 2 +- src/js/wh/state/store.js | 100 +++++++++++++++-------------------- 2 files changed, 43 insertions(+), 59 deletions(-) diff --git a/src/js/wh/state/selectors.js b/src/js/wh/state/selectors.js index e9d1b037..7912853d 100644 --- a/src/js/wh/state/selectors.js +++ b/src/js/wh/state/selectors.js @@ -1,6 +1,6 @@ const themeColors = {}; -export function memoize(state, action = {}, actions) { +export default function memoize(state, action = {}, actions) { switch (action.type) { case actions.CREATE_PROJECT: diff --git a/src/js/wh/state/store.js b/src/js/wh/state/store.js index c9214a18..ae9303e6 100644 --- a/src/js/wh/state/store.js +++ b/src/js/wh/state/store.js @@ -1,63 +1,47 @@ -import { memoize } from './selectors.js'; -import { setConfig } from '../core/config.js'; +import actions from './actions.js'; +import memoize from './selectors.js'; +import reduce from './reducers.js'; -export default function createStore(specs = {}, my = {}) { - const STATE_CHANGE = 'STATE_CHANGE'; +export const STATE_CHANGE = 'STATE_CHANGE'; - let that = {}, - actions = specs.actions, - reducers = specs.reducers, - currentState, +let currentState = null; - init = () => { - currentState = reducers.reduce(); - }, - - dispatch = (action) => { - // thunk or not - if (typeof action === 'function') { - const resultActionObject = action(dispatch, getState, getActions); - if (resultActionObject) { - dispatch(resultActionObject); - } - } else { - currentState = reducers.reduce(currentState, action, actions); - memoize(currentState, action, actions); - document.dispatchEvent(new CustomEvent(STATE_CHANGE, { detail: { - state: currentState, - action: action, - actions: actions - }})); - } - }, - - getActions = () => { - return actions; - }, - - getState = () => { - return currentState; - }, - - persist = () => { - const name = 'persist'; - window.addEventListener('beforeunload', e => { - localStorage.setItem(name, JSON.stringify(currentState)); - }); - let data = localStorage.getItem(name); - if (data) { - dispatch(getActions().setProject(JSON.parse(data))); - } - }; - - that = specs.that || {}; +export function dispatch(action) { + // thunk or not + if (typeof action === 'function') { + const resultActionObject = action(dispatch, getState, getActions); + if (resultActionObject) { + dispatch(resultActionObject); + } + } else { + const state = reduce(currentState, action, actions); + memoize(state, action, actions); + currentState = state; + document.dispatchEvent(new CustomEvent(STATE_CHANGE, { detail: { + state, action, actions, + }})); + } +} + +export function getActions() { + return actions; +} + +export function getState() { + return currentState; +} + +export function persist() { + const name = 'persist'; + window.addEventListener('beforeunload', e => { + localStorage.setItem(name, JSON.stringify(currentState)); + }); + let data = localStorage.getItem(name); + if (data && data !== 'undefined') { + currentState = JSON.parse(data); + } else { - init(); - - that.STATE_CHANGE = STATE_CHANGE; - that.dispatch = dispatch; - that.getActions = getActions; - that.getState = getState; - that.persist = persist; - return that; + // start with the initial state + dispatch({ type: null, }); + } } From 62d946ca025bcbb00cde797ed20de59fd7234ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Wed, 2 Oct 2019 11:00:24 +0200 Subject: [PATCH 002/131] Selectors module memoize refactored. --- src/js/wh/processors/epg/object3d.js | 6 +-- .../wh/processors/epg/object3dController.js | 8 ++-- src/js/wh/processors/euclidfx/object3d.js | 6 +-- .../processors/euclidfx/object3dController.js | 6 +-- src/js/wh/processors/output/object3d.js | 6 +-- .../processors/output/object3dController.js | 4 +- src/js/wh/state/selectors.js | 37 +++++++++++++------ src/js/wh/webgl/canvas3d.js | 6 +-- src/js/wh/webgl/connections3d.js | 10 ++--- src/js/wh/webgl/object3dControllerBase.js | 4 +- 10 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/js/wh/processors/epg/object3d.js b/src/js/wh/processors/epg/object3d.js index 4a0cb993..b64a0e66 100644 --- a/src/js/wh/processors/epg/object3d.js +++ b/src/js/wh/processors/epg/object3d.js @@ -5,7 +5,7 @@ import { createShape, drawConnectors, } from '../../webgl/draw3dHelper.js'; -import { getThemeColors } from '../../state/selectors.js'; +import { getTheme } from '../../state/selectors.js'; const { Group, @@ -54,7 +54,7 @@ export function createObject3d(id, inputs, outputs) { * @return {object} Group of drag plane. */ createWheel = function() { - const defaultColor = getThemeColors().colorHigh; + const defaultColor = getTheme().colorHigh; const hitarea = createCircleFilled(3, defaultColor); hitarea.name = 'hitarea'; @@ -112,7 +112,7 @@ export function createObject3d(id, inputs, outputs) { wheel.add(label); // add inputs and outputs - drawConnectors(wheel, inputs, outputs, getThemeColors().colorLow); + drawConnectors(wheel, inputs, outputs, getTheme().colorLow); return wheel; }; diff --git a/src/js/wh/processors/epg/object3dController.js b/src/js/wh/processors/epg/object3dController.js index 960610ba..eae2c551 100644 --- a/src/js/wh/processors/epg/object3dController.js +++ b/src/js/wh/processors/epg/object3dController.js @@ -4,7 +4,7 @@ import { ShapeGeometry, Vector2, } from '../../../lib/three.module.js'; -import { getThemeColors } from '../../state/selectors.js'; +import { getTheme } from '../../state/selectors.js'; import createObject3dControllerBase from '../../webgl/object3dControllerBase.js'; import { getEuclidPattern, rotateEuclidPattern } from './euclid.js'; import { PPQN } from '../../core/config.js'; @@ -44,7 +44,7 @@ export function createObject3dController(specs, my) { document.addEventListener(my.store.STATE_CHANGE, handleStateChanges); - defaultColor = getThemeColors().colorHigh; + defaultColor = getTheme().colorHigh; const params = specs.processorData.params.byId; my.updateLabel(params.name.value); @@ -104,7 +104,7 @@ export function createObject3dController(specs, my) { * Set theme colors on the 3D pattern. */ updateTheme = function() { - const { colorLow, colorHigh } = getThemeColors(); + const { colorLow, colorHigh } = getTheme(); setThemeColorRecursively(my.object3d, colorLow, colorHigh); }, @@ -216,7 +216,7 @@ export function createObject3dController(specs, my) { } const line = polygon3d.getObjectByName('polygonLine'); - redrawShape(line, points, getThemeColors().colorLow); + redrawShape(line, points, getTheme().colorLow); }, /** diff --git a/src/js/wh/processors/euclidfx/object3d.js b/src/js/wh/processors/euclidfx/object3d.js index ada07e75..3d719aa5 100644 --- a/src/js/wh/processors/euclidfx/object3d.js +++ b/src/js/wh/processors/euclidfx/object3d.js @@ -9,7 +9,7 @@ import { createShape, drawConnectors, } from '../../webgl/draw3dHelper.js'; -import { getThemeColors } from '../../state/selectors.js'; +import { getTheme } from '../../state/selectors.js'; export function createObject3d(id, inputs, outputs) { let defaultColor, @@ -19,7 +19,7 @@ export function createObject3d(id, inputs, outputs) { * Initialization. */ init = function() { - defaultColor = getThemeColors().colorHigh; + defaultColor = getTheme().colorHigh; lineMaterial = new LineBasicMaterial({ color: defaultColor, }); @@ -73,7 +73,7 @@ export function createObject3d(id, inputs, outputs) { root.add(label); // add inputs and outputs - drawConnectors(root, inputs, outputs, getThemeColors().colorLow); + drawConnectors(root, inputs, outputs, getTheme().colorLow); return root; }; diff --git a/src/js/wh/processors/euclidfx/object3dController.js b/src/js/wh/processors/euclidfx/object3dController.js index d2f37ab0..ce4f5e0f 100644 --- a/src/js/wh/processors/euclidfx/object3dController.js +++ b/src/js/wh/processors/euclidfx/object3dController.js @@ -2,7 +2,7 @@ import { EllipseCurve, Vector2, } from '../../../lib/three.module.js'; -import { getThemeColors } from '../../state/selectors.js'; +import { getTheme } from '../../state/selectors.js'; import createObject3dControllerBase from '../../webgl/object3dControllerBase.js'; import { getEuclidPattern, rotateEuclidPattern } from './euclid.js'; import { PPQN } from '../../core/config.js'; @@ -46,7 +46,7 @@ export function createObject3dController(specs, my) { document.addEventListener(my.store.STATE_CHANGE, handleStateChanges); - defaultColor = getThemeColors().colorHigh; + defaultColor = getTheme().colorHigh; const params = specs.processorData.params.byId; my.updateLabel(params.name.value); @@ -163,7 +163,7 @@ export function createObject3dController(specs, my) { * Set theme colors on the 3D pattern. */ updateTheme = function() { - const { colorLow, colorHigh } = getThemeColors(); + const { colorLow, colorHigh } = getTheme(); setThemeColorRecursively(my.object3d, colorLow, colorHigh); }, diff --git a/src/js/wh/processors/output/object3d.js b/src/js/wh/processors/output/object3d.js index c789dba8..218d787c 100644 --- a/src/js/wh/processors/output/object3d.js +++ b/src/js/wh/processors/output/object3d.js @@ -9,7 +9,7 @@ import { drawConnectors, createShape, } from '../../webgl/draw3dHelper.js'; -import { getThemeColors } from '../../state/selectors.js'; +import { getTheme } from '../../state/selectors.js'; export function createObject3d(id, inputs, outputs) { @@ -18,7 +18,7 @@ export function createObject3d(id, inputs, outputs) { radius = 3, init = function() { - defaultColor = getThemeColors().colorHigh; + defaultColor = getTheme().colorHigh; lineMaterial = new LineBasicMaterial({ color: defaultColor, }); @@ -62,7 +62,7 @@ export function createObject3d(id, inputs, outputs) { group.add(label); // add inputs and outputs - drawConnectors(group, inputs, outputs, getThemeColors().colorLow); + drawConnectors(group, inputs, outputs, getTheme().colorLow); return group; }; diff --git a/src/js/wh/processors/output/object3dController.js b/src/js/wh/processors/output/object3dController.js index ecca95ff..a4b7116d 100644 --- a/src/js/wh/processors/output/object3dController.js +++ b/src/js/wh/processors/output/object3dController.js @@ -1,5 +1,5 @@ -import { getThemeColors } from '../../state/selectors.js'; +import { getTheme } from '../../state/selectors.js'; import createObject3dControllerBase from '../../webgl/object3dControllerBase.js'; export function createObject3dController(specs, my) { @@ -56,7 +56,7 @@ export function createObject3dController(specs, my) { * Set theme colors on the 3D pattern. */ updateTheme = function() { - const { colorLow, colorHigh } = getThemeColors(); + const { colorLow, colorHigh } = getTheme(); setThemeColorRecursively(my.object3d, colorLow, colorHigh); }, diff --git a/src/js/wh/state/selectors.js b/src/js/wh/state/selectors.js index 7912853d..77526432 100644 --- a/src/js/wh/state/selectors.js +++ b/src/js/wh/state/selectors.js @@ -1,25 +1,40 @@ -const themeColors = {}; +let theme = {}; +/** + * Provide the theme. + */ +export function getTheme() { + return theme; +} + +/** + * Store values that can be calculated based on combined state values. + * @param {Object} state + * @param {Object} action + * @param {Object} actions + */ export default function memoize(state, action = {}, actions) { switch (action.type) { case actions.CREATE_PROJECT: case actions.RESCAN_TYPES: case actions.SET_THEME: - document.querySelector('#app').dataset.theme = state.theme; - const themeStyles = window.getComputedStyle(document.querySelector('[data-theme]')); - themeColors.colorBackground = themeStyles.getPropertyValue('--bg-color').trim(); - themeColors.colorHigh = themeStyles.getPropertyValue('--webgl-high-color').trim(); - themeColors.colorMid = themeStyles.getPropertyValue('--webgl-mid-color').trim(); - themeColors.colorLow = themeStyles.getPropertyValue('--webgl-low-color').trim(); + setTheme(state); break; } } /** - * Memoised selector to access processors by id as object key. - * Recreates the memoised data each time a processor is created or deleted. + * Recreate the theme. + * @param {Object} state */ -export function getThemeColors() { - return themeColors; +function setTheme(state) { + document.querySelector('#app').dataset.theme = state.theme; + const themeStyles = window.getComputedStyle(document.querySelector('[data-theme]')); + theme = Object.freeze({ + colorBackground: themeStyles.getPropertyValue('--bg-color').trim(), + colorHigh: themeStyles.getPropertyValue('--webgl-high-color').trim(), + colorMid: themeStyles.getPropertyValue('--webgl-mid-color').trim(), + colorLow: themeStyles.getPropertyValue('--webgl-low-color').trim(), + }); } diff --git a/src/js/wh/webgl/canvas3d.js b/src/js/wh/webgl/canvas3d.js index 38ba6ccc..37c8b1f1 100644 --- a/src/js/wh/webgl/canvas3d.js +++ b/src/js/wh/webgl/canvas3d.js @@ -1,7 +1,7 @@ import addWindowResize from '../view/windowresize.js'; import addConnections3d from './connections3d.js'; import { setLineMaterialResolution } from './draw3dHelper.js'; -import { getThemeColors } from '../state/selectors.js'; +import { getTheme } from '../state/selectors.js'; import { eventType } from '../core/util.js'; const { @@ -327,7 +327,7 @@ export default function createCanvas3d(specs, my) { initWorld = function() { renderer = new WebGLRenderer({antialias: true}); - renderer.setClearColor(new Color( getThemeColors().colorBackground || '#cccccc' )); + renderer.setClearColor(new Color( getTheme().colorBackground || '#cccccc' )); rootEl = document.querySelector('#canvas-container'); rootEl.appendChild(renderer.domElement); @@ -352,7 +352,7 @@ export default function createCanvas3d(specs, my) { }, setThemeOnWorld = function() { - renderer.setClearColor(new Color( getThemeColors().colorBackground )); + renderer.setClearColor(new Color( getTheme().colorBackground )); }, /** diff --git a/src/js/wh/webgl/connections3d.js b/src/js/wh/webgl/connections3d.js index f7f1b6c7..16e08498 100644 --- a/src/js/wh/webgl/connections3d.js +++ b/src/js/wh/webgl/connections3d.js @@ -1,4 +1,4 @@ -import { getThemeColors } from '../state/selectors.js'; +import { getTheme } from '../state/selectors.js'; import { createCircleFilled, createCircleOutline, @@ -29,7 +29,7 @@ export default function addConnections3d(specs, my) { dragHandleRadius = 1.5, init = function() { - currentCableDragHandle = createCircleOutline(dragHandleRadius, getThemeColors().colorHigh); + currentCableDragHandle = createCircleOutline(dragHandleRadius, getTheme().colorHigh); currentCableDragHandle.name = 'dragHandle'; document.addEventListener(store.STATE_CHANGE, (e) => { @@ -153,7 +153,7 @@ export default function addConnections3d(specs, my) { * @return {Object} Cable object3d. */ createCable = function(connectionId) { - const { colorLow } = getThemeColors(); + const { colorLow } = getTheme(); const cable = createShape(); cable.name = connectionId; @@ -233,7 +233,7 @@ export default function addConnections3d(specs, my) { ); const points = curve.getPoints(50); - redrawShape(cable, points, getThemeColors().colorLow); + redrawShape(cable, points, getTheme().colorLow); const deleteBtn = cable.getObjectByName('delete'); if (deleteBtn) { @@ -264,7 +264,7 @@ export default function addConnections3d(specs, my) { */ updateTheme = function() { if (cablesGroup) { - const { colorLow, colorHigh } = getThemeColors(); + const { colorLow, colorHigh } = getTheme(); setThemeColorRecursively(cablesGroup, colorLow, colorHigh); } }, diff --git a/src/js/wh/webgl/object3dControllerBase.js b/src/js/wh/webgl/object3dControllerBase.js index 3d0a587a..fd01bb2f 100644 --- a/src/js/wh/webgl/object3dControllerBase.js +++ b/src/js/wh/webgl/object3dControllerBase.js @@ -1,5 +1,5 @@ import setText3d from './text3d.js'; -import { getThemeColors } from '../state/selectors.js'; +import { getTheme } from '../state/selectors.js'; /** * Base object for all processor WebGL object controllers. @@ -15,7 +15,7 @@ export default function createObject3dControllerBase(specs, my) { * Update the pattern's name. */ updateLabel = function(labelString) { - setText3d(my.label3d, labelString.toUpperCase(), getThemeColors().colorHigh); + setText3d(my.label3d, labelString.toUpperCase(), getTheme().colorHigh); }, /** From c541ea5611d5354675090467105d01b2b76f88ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Wed, 2 Oct 2019 11:07:11 +0200 Subject: [PATCH 003/131] =?UTF-8?q?Removed=20directory=20Wouter=20Hisschem?= =?UTF-8?q?=C3=B6ller/=20from=20Javascript=20pat.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/{wh => }/core/config.js | 0 src/js/{wh => }/core/convert_xml.js | 0 src/js/{wh => }/core/transport.js | 0 src/js/{wh => }/core/util.js | 0 src/js/main.js | 196 +++++++++--------- src/js/{wh => }/midi/connectorin.js | 0 src/js/{wh => }/midi/connectorout.js | 0 src/js/{wh => }/midi/midi.js | 0 src/js/{wh => }/midi/network.js | 0 src/js/{wh => }/midi/network_ordering.js | 0 src/js/{wh => }/midi/processorbase.js | 0 src/js/{wh => }/processors/epg/config.json | 0 src/js/{wh => }/processors/epg/euclid.js | 0 src/js/{wh => }/processors/epg/object3d.js | 0 .../processors/epg/object3dController.js | 0 src/js/{wh => }/processors/epg/processor.js | 0 src/js/{wh => }/processors/epg/settings.html | 0 .../{wh => }/processors/euclidfx/config.json | 0 src/js/{wh => }/processors/euclidfx/euclid.js | 0 .../{wh => }/processors/euclidfx/object3d.js | 0 .../processors/euclidfx/object3dController.js | 0 .../{wh => }/processors/euclidfx/processor.js | 0 .../processors/euclidfx/settings.html | 0 src/js/{wh => }/processors/output/config.json | 0 src/js/{wh => }/processors/output/object3d.js | 0 .../processors/output/object3dController.js | 0 .../{wh => }/processors/output/processor.js | 0 .../{wh => }/processors/output/settings.html | 0 src/js/{wh => }/state/actions.js | 0 src/js/{wh => }/state/reducers.js | 2 +- src/js/{wh => }/state/selectors.js | 0 src/js/{wh => }/state/store.js | 0 src/js/{wh => }/view/app.js | 6 +- src/js/{wh => }/view/dialog.js | 0 src/js/{wh => }/view/library.js | 0 src/js/{wh => }/view/midi_base.js | 0 src/js/{wh => }/view/midi_input.js | 0 src/js/{wh => }/view/midi_output.js | 0 src/js/{wh => }/view/preferences.js | 0 src/js/{wh => }/view/remote.js | 0 src/js/{wh => }/view/remote_group.js | 0 src/js/{wh => }/view/remote_item.js | 0 src/js/{wh => }/view/setting/base.js | 0 src/js/{wh => }/view/setting/boolean.js | 0 src/js/{wh => }/view/setting/integer.js | 0 src/js/{wh => }/view/setting/itemized.js | 0 src/js/{wh => }/view/setting/remote.js | 0 src/js/{wh => }/view/setting/string.js | 0 src/js/{wh => }/view/settings.js | 0 src/js/{wh => }/view/windowresize.js | 0 src/js/{wh => }/webgl/canvas3d.js | 0 src/js/{wh => }/webgl/connections3d.js | 0 src/js/{wh => }/webgl/draw3dHelper.js | 0 .../{wh => }/webgl/object3dControllerBase.js | 0 src/js/{wh => }/webgl/text3d.js | 0 src/js/{wh => }/webgl/text3dFontData.js | 0 56 files changed, 105 insertions(+), 99 deletions(-) rename src/js/{wh => }/core/config.js (100%) rename src/js/{wh => }/core/convert_xml.js (100%) rename src/js/{wh => }/core/transport.js (100%) rename src/js/{wh => }/core/util.js (100%) rename src/js/{wh => }/midi/connectorin.js (100%) rename src/js/{wh => }/midi/connectorout.js (100%) rename src/js/{wh => }/midi/midi.js (100%) rename src/js/{wh => }/midi/network.js (100%) rename src/js/{wh => }/midi/network_ordering.js (100%) rename src/js/{wh => }/midi/processorbase.js (100%) rename src/js/{wh => }/processors/epg/config.json (100%) rename src/js/{wh => }/processors/epg/euclid.js (100%) rename src/js/{wh => }/processors/epg/object3d.js (100%) rename src/js/{wh => }/processors/epg/object3dController.js (100%) rename src/js/{wh => }/processors/epg/processor.js (100%) rename src/js/{wh => }/processors/epg/settings.html (100%) rename src/js/{wh => }/processors/euclidfx/config.json (100%) rename src/js/{wh => }/processors/euclidfx/euclid.js (100%) rename src/js/{wh => }/processors/euclidfx/object3d.js (100%) rename src/js/{wh => }/processors/euclidfx/object3dController.js (100%) rename src/js/{wh => }/processors/euclidfx/processor.js (100%) rename src/js/{wh => }/processors/euclidfx/settings.html (100%) rename src/js/{wh => }/processors/output/config.json (100%) rename src/js/{wh => }/processors/output/object3d.js (100%) rename src/js/{wh => }/processors/output/object3dController.js (100%) rename src/js/{wh => }/processors/output/processor.js (100%) rename src/js/{wh => }/processors/output/settings.html (100%) rename src/js/{wh => }/state/actions.js (100%) rename src/js/{wh => }/state/reducers.js (99%) rename src/js/{wh => }/state/selectors.js (100%) rename src/js/{wh => }/state/store.js (100%) rename src/js/{wh => }/view/app.js (99%) rename src/js/{wh => }/view/dialog.js (100%) rename src/js/{wh => }/view/library.js (100%) rename src/js/{wh => }/view/midi_base.js (100%) rename src/js/{wh => }/view/midi_input.js (100%) rename src/js/{wh => }/view/midi_output.js (100%) rename src/js/{wh => }/view/preferences.js (100%) rename src/js/{wh => }/view/remote.js (100%) rename src/js/{wh => }/view/remote_group.js (100%) rename src/js/{wh => }/view/remote_item.js (100%) rename src/js/{wh => }/view/setting/base.js (100%) rename src/js/{wh => }/view/setting/boolean.js (100%) rename src/js/{wh => }/view/setting/integer.js (100%) rename src/js/{wh => }/view/setting/itemized.js (100%) rename src/js/{wh => }/view/setting/remote.js (100%) rename src/js/{wh => }/view/setting/string.js (100%) rename src/js/{wh => }/view/settings.js (100%) rename src/js/{wh => }/view/windowresize.js (100%) rename src/js/{wh => }/webgl/canvas3d.js (100%) rename src/js/{wh => }/webgl/connections3d.js (100%) rename src/js/{wh => }/webgl/draw3dHelper.js (100%) rename src/js/{wh => }/webgl/object3dControllerBase.js (100%) rename src/js/{wh => }/webgl/text3d.js (100%) rename src/js/{wh => }/webgl/text3dFontData.js (100%) diff --git a/src/js/wh/core/config.js b/src/js/core/config.js similarity index 100% rename from src/js/wh/core/config.js rename to src/js/core/config.js diff --git a/src/js/wh/core/convert_xml.js b/src/js/core/convert_xml.js similarity index 100% rename from src/js/wh/core/convert_xml.js rename to src/js/core/convert_xml.js diff --git a/src/js/wh/core/transport.js b/src/js/core/transport.js similarity index 100% rename from src/js/wh/core/transport.js rename to src/js/core/transport.js diff --git a/src/js/wh/core/util.js b/src/js/core/util.js similarity index 100% rename from src/js/wh/core/util.js rename to src/js/core/util.js diff --git a/src/js/main.js b/src/js/main.js index d560359a..28c0a21a 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,112 +1,118 @@ /** - Euclidean Pattern Generator - Copyright (C) 2017 - 2019 Wouter Hisschemoller + Music Pattern Generator + Copyright (C) 2017 - 2019 Wouter Hisschemoller - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ -import createActions from './wh/state/actions.js'; -import createReducers from './wh/state/reducers.js'; -import createStore from './wh/state/store.js'; +import createActions from './state/actions.js'; +import createReducers from './state/reducers.js'; +import { dispatch, getActions, getState, persist } from './state/store.js'; -import createAppView from './wh/view/app.js'; -// import createCanvasView from './wh/view/canvas.js'; -import createCanvas3d from './wh/webgl/canvas3d.js'; -import createDialog from './wh/view/dialog.js'; -import createLibraryView from './wh/view/library.js'; -import createMIDI from './wh/midi/midi.js'; -import createMIDINetwork from './wh/midi/network.js'; -import createPreferencesView from './wh/view/preferences.js'; -import createRemoteView from './wh/view/remote.js'; -import createTransport from './wh/core/transport.js'; +import createAppView from './view/app.js'; +// import createCanvasView from './view/canvas.js'; +import createCanvas3d from './webgl/canvas3d.js'; +import createDialog from './view/dialog.js'; +import createLibraryView from './view/library.js'; +import createMIDI from './midi/midi.js'; +import createMIDINetwork from './midi/network.js'; +import createPreferencesView from './view/preferences.js'; +import createRemoteView from './view/remote.js'; +import createTransport from './core/transport.js'; -import { showDialog } from './wh/view/dialog.js'; +import { showDialog } from './view/dialog.js'; + +async function main() { + persist(); +} + +main(); /** * Application startup. */ -function init() { - // Create all objects that will be the modules of the app. - var appView = {}, - canvasView = {}, - dialog = {}, - libraryView = {}, - midi = {}, - midiNetwork = {}, - preferencesView = {}, - remoteView = {}, - transport = {}; +// function init() { +// // Create all objects that will be the modules of the app. +// var appView = {}, +// canvasView = {}, +// dialog = {}, +// libraryView = {}, +// midi = {}, +// midiNetwork = {}, +// preferencesView = {}, +// remoteView = {}, +// transport = {}; - const store = createStore({ - actions: createActions(), - reducers: createReducers() - }); +// const store = createStore({ +// actions: createActions(), +// reducers: createReducers() +// }); - // Add functionality to the modules and inject dependencies. - createAppView({ - that: appView, - store - }); - createCanvas3d({ - that: canvasView, - store - }); - createDialog({ - that: dialog, - }); - createLibraryView({ - that: libraryView, - store - }); - createMIDI({ - that: midi, - store - }); - createMIDINetwork({ - that: midiNetwork, - store - }); - createPreferencesView({ - that: preferencesView, - store - }); - createRemoteView({ - that: remoteView, - store - }); - createTransport({ - that: transport, - store, - canvasView, - midiNetwork - }); +// // Add functionality to the modules and inject dependencies. +// createAppView({ +// that: appView, +// store +// }); +// createCanvas3d({ +// that: canvasView, +// store +// }); +// createDialog({ +// that: dialog, +// }); +// createLibraryView({ +// that: libraryView, +// store +// }); +// createMIDI({ +// that: midi, +// store +// }); +// createMIDINetwork({ +// that: midiNetwork, +// store +// }); +// createPreferencesView({ +// that: preferencesView, +// store +// }); +// createRemoteView({ +// that: remoteView, +// store +// }); +// createTransport({ +// that: transport, +// store, +// canvasView, +// midiNetwork +// }); - // scan installed processors - store.dispatch(store.getActions().rescanTypes()); +// // scan installed processors +// store.dispatch(store.getActions().rescanTypes()); - // initialise - midi.connect() - .then(() => { - store.persist(); - transport.run(); - }) - .catch(errorMsg => { - showDialog('No MIDI available', `The app was unable to find any MIDI ports. This is usually because the browser doesn't support Web MIDI. Check current browser support at Can I Use.

Error message:
${errorMsg}`); - }); -} +// // initialise +// midi.connect() +// .then(() => { +// store.persist(); +// transport.run(); +// }) +// .catch(errorMsg => { +// showDialog('No MIDI available', `The app was unable to find any MIDI ports. This is usually because the browser doesn't support Web MIDI. Check current browser support at Can I Use.

Error message:
${errorMsg}`); +// }); +// } -document.addEventListener('DOMContentLoaded', function() { - console.log('DOMContentLoaded'); - init(); -}); \ No newline at end of file +// document.addEventListener('DOMContentLoaded', function() { +// console.log('DOMContentLoaded'); +// init(); +// }); diff --git a/src/js/wh/midi/connectorin.js b/src/js/midi/connectorin.js similarity index 100% rename from src/js/wh/midi/connectorin.js rename to src/js/midi/connectorin.js diff --git a/src/js/wh/midi/connectorout.js b/src/js/midi/connectorout.js similarity index 100% rename from src/js/wh/midi/connectorout.js rename to src/js/midi/connectorout.js diff --git a/src/js/wh/midi/midi.js b/src/js/midi/midi.js similarity index 100% rename from src/js/wh/midi/midi.js rename to src/js/midi/midi.js diff --git a/src/js/wh/midi/network.js b/src/js/midi/network.js similarity index 100% rename from src/js/wh/midi/network.js rename to src/js/midi/network.js diff --git a/src/js/wh/midi/network_ordering.js b/src/js/midi/network_ordering.js similarity index 100% rename from src/js/wh/midi/network_ordering.js rename to src/js/midi/network_ordering.js diff --git a/src/js/wh/midi/processorbase.js b/src/js/midi/processorbase.js similarity index 100% rename from src/js/wh/midi/processorbase.js rename to src/js/midi/processorbase.js diff --git a/src/js/wh/processors/epg/config.json b/src/js/processors/epg/config.json similarity index 100% rename from src/js/wh/processors/epg/config.json rename to src/js/processors/epg/config.json diff --git a/src/js/wh/processors/epg/euclid.js b/src/js/processors/epg/euclid.js similarity index 100% rename from src/js/wh/processors/epg/euclid.js rename to src/js/processors/epg/euclid.js diff --git a/src/js/wh/processors/epg/object3d.js b/src/js/processors/epg/object3d.js similarity index 100% rename from src/js/wh/processors/epg/object3d.js rename to src/js/processors/epg/object3d.js diff --git a/src/js/wh/processors/epg/object3dController.js b/src/js/processors/epg/object3dController.js similarity index 100% rename from src/js/wh/processors/epg/object3dController.js rename to src/js/processors/epg/object3dController.js diff --git a/src/js/wh/processors/epg/processor.js b/src/js/processors/epg/processor.js similarity index 100% rename from src/js/wh/processors/epg/processor.js rename to src/js/processors/epg/processor.js diff --git a/src/js/wh/processors/epg/settings.html b/src/js/processors/epg/settings.html similarity index 100% rename from src/js/wh/processors/epg/settings.html rename to src/js/processors/epg/settings.html diff --git a/src/js/wh/processors/euclidfx/config.json b/src/js/processors/euclidfx/config.json similarity index 100% rename from src/js/wh/processors/euclidfx/config.json rename to src/js/processors/euclidfx/config.json diff --git a/src/js/wh/processors/euclidfx/euclid.js b/src/js/processors/euclidfx/euclid.js similarity index 100% rename from src/js/wh/processors/euclidfx/euclid.js rename to src/js/processors/euclidfx/euclid.js diff --git a/src/js/wh/processors/euclidfx/object3d.js b/src/js/processors/euclidfx/object3d.js similarity index 100% rename from src/js/wh/processors/euclidfx/object3d.js rename to src/js/processors/euclidfx/object3d.js diff --git a/src/js/wh/processors/euclidfx/object3dController.js b/src/js/processors/euclidfx/object3dController.js similarity index 100% rename from src/js/wh/processors/euclidfx/object3dController.js rename to src/js/processors/euclidfx/object3dController.js diff --git a/src/js/wh/processors/euclidfx/processor.js b/src/js/processors/euclidfx/processor.js similarity index 100% rename from src/js/wh/processors/euclidfx/processor.js rename to src/js/processors/euclidfx/processor.js diff --git a/src/js/wh/processors/euclidfx/settings.html b/src/js/processors/euclidfx/settings.html similarity index 100% rename from src/js/wh/processors/euclidfx/settings.html rename to src/js/processors/euclidfx/settings.html diff --git a/src/js/wh/processors/output/config.json b/src/js/processors/output/config.json similarity index 100% rename from src/js/wh/processors/output/config.json rename to src/js/processors/output/config.json diff --git a/src/js/wh/processors/output/object3d.js b/src/js/processors/output/object3d.js similarity index 100% rename from src/js/wh/processors/output/object3d.js rename to src/js/processors/output/object3d.js diff --git a/src/js/wh/processors/output/object3dController.js b/src/js/processors/output/object3dController.js similarity index 100% rename from src/js/wh/processors/output/object3dController.js rename to src/js/processors/output/object3dController.js diff --git a/src/js/wh/processors/output/processor.js b/src/js/processors/output/processor.js similarity index 100% rename from src/js/wh/processors/output/processor.js rename to src/js/processors/output/processor.js diff --git a/src/js/wh/processors/output/settings.html b/src/js/processors/output/settings.html similarity index 100% rename from src/js/wh/processors/output/settings.html rename to src/js/processors/output/settings.html diff --git a/src/js/wh/state/actions.js b/src/js/state/actions.js similarity index 100% rename from src/js/wh/state/actions.js rename to src/js/state/actions.js diff --git a/src/js/wh/state/reducers.js b/src/js/state/reducers.js similarity index 99% rename from src/js/wh/state/reducers.js rename to src/js/state/reducers.js index b929e1ea..b8e74df0 100644 --- a/src/js/wh/state/reducers.js +++ b/src/js/state/reducers.js @@ -35,7 +35,7 @@ export default function createReducers() { }, bpm: 120, selectedID: null, - theme: 'light', // 'light|dark' + theme: 'dev', // 'light|dark' transport: 'stop', // 'play|pause|stop' connectModeActive: false, learnModeActive: false, diff --git a/src/js/wh/state/selectors.js b/src/js/state/selectors.js similarity index 100% rename from src/js/wh/state/selectors.js rename to src/js/state/selectors.js diff --git a/src/js/wh/state/store.js b/src/js/state/store.js similarity index 100% rename from src/js/wh/state/store.js rename to src/js/state/store.js diff --git a/src/js/wh/view/app.js b/src/js/view/app.js similarity index 99% rename from src/js/wh/view/app.js rename to src/js/view/app.js index 4c6c8f22..d48063c9 100644 --- a/src/js/wh/view/app.js +++ b/src/js/view/app.js @@ -122,9 +122,9 @@ export default function createAppView(specs, my) { // don't perform shortcuts while typing in a text input. if (!(e.target.tagName.toLowerCase() == 'input' && e.target.getAttribute('type') == 'text')) { switch (e.keyCode) { - case 82: - case 83: - case 84: + case 82: // r + case 83: // s + case 84: // t // clear all data on key combination 'rst' (reset) resetKeyCombo.push(e.keyCode); if (resetKeyCombo.indexOf(82) > -1 && resetKeyCombo.indexOf(83) > -1 && resetKeyCombo.indexOf(84) > -1) { diff --git a/src/js/wh/view/dialog.js b/src/js/view/dialog.js similarity index 100% rename from src/js/wh/view/dialog.js rename to src/js/view/dialog.js diff --git a/src/js/wh/view/library.js b/src/js/view/library.js similarity index 100% rename from src/js/wh/view/library.js rename to src/js/view/library.js diff --git a/src/js/wh/view/midi_base.js b/src/js/view/midi_base.js similarity index 100% rename from src/js/wh/view/midi_base.js rename to src/js/view/midi_base.js diff --git a/src/js/wh/view/midi_input.js b/src/js/view/midi_input.js similarity index 100% rename from src/js/wh/view/midi_input.js rename to src/js/view/midi_input.js diff --git a/src/js/wh/view/midi_output.js b/src/js/view/midi_output.js similarity index 100% rename from src/js/wh/view/midi_output.js rename to src/js/view/midi_output.js diff --git a/src/js/wh/view/preferences.js b/src/js/view/preferences.js similarity index 100% rename from src/js/wh/view/preferences.js rename to src/js/view/preferences.js diff --git a/src/js/wh/view/remote.js b/src/js/view/remote.js similarity index 100% rename from src/js/wh/view/remote.js rename to src/js/view/remote.js diff --git a/src/js/wh/view/remote_group.js b/src/js/view/remote_group.js similarity index 100% rename from src/js/wh/view/remote_group.js rename to src/js/view/remote_group.js diff --git a/src/js/wh/view/remote_item.js b/src/js/view/remote_item.js similarity index 100% rename from src/js/wh/view/remote_item.js rename to src/js/view/remote_item.js diff --git a/src/js/wh/view/setting/base.js b/src/js/view/setting/base.js similarity index 100% rename from src/js/wh/view/setting/base.js rename to src/js/view/setting/base.js diff --git a/src/js/wh/view/setting/boolean.js b/src/js/view/setting/boolean.js similarity index 100% rename from src/js/wh/view/setting/boolean.js rename to src/js/view/setting/boolean.js diff --git a/src/js/wh/view/setting/integer.js b/src/js/view/setting/integer.js similarity index 100% rename from src/js/wh/view/setting/integer.js rename to src/js/view/setting/integer.js diff --git a/src/js/wh/view/setting/itemized.js b/src/js/view/setting/itemized.js similarity index 100% rename from src/js/wh/view/setting/itemized.js rename to src/js/view/setting/itemized.js diff --git a/src/js/wh/view/setting/remote.js b/src/js/view/setting/remote.js similarity index 100% rename from src/js/wh/view/setting/remote.js rename to src/js/view/setting/remote.js diff --git a/src/js/wh/view/setting/string.js b/src/js/view/setting/string.js similarity index 100% rename from src/js/wh/view/setting/string.js rename to src/js/view/setting/string.js diff --git a/src/js/wh/view/settings.js b/src/js/view/settings.js similarity index 100% rename from src/js/wh/view/settings.js rename to src/js/view/settings.js diff --git a/src/js/wh/view/windowresize.js b/src/js/view/windowresize.js similarity index 100% rename from src/js/wh/view/windowresize.js rename to src/js/view/windowresize.js diff --git a/src/js/wh/webgl/canvas3d.js b/src/js/webgl/canvas3d.js similarity index 100% rename from src/js/wh/webgl/canvas3d.js rename to src/js/webgl/canvas3d.js diff --git a/src/js/wh/webgl/connections3d.js b/src/js/webgl/connections3d.js similarity index 100% rename from src/js/wh/webgl/connections3d.js rename to src/js/webgl/connections3d.js diff --git a/src/js/wh/webgl/draw3dHelper.js b/src/js/webgl/draw3dHelper.js similarity index 100% rename from src/js/wh/webgl/draw3dHelper.js rename to src/js/webgl/draw3dHelper.js diff --git a/src/js/wh/webgl/object3dControllerBase.js b/src/js/webgl/object3dControllerBase.js similarity index 100% rename from src/js/wh/webgl/object3dControllerBase.js rename to src/js/webgl/object3dControllerBase.js diff --git a/src/js/wh/webgl/text3d.js b/src/js/webgl/text3d.js similarity index 100% rename from src/js/wh/webgl/text3d.js rename to src/js/webgl/text3d.js diff --git a/src/js/wh/webgl/text3dFontData.js b/src/js/webgl/text3dFontData.js similarity index 100% rename from src/js/wh/webgl/text3dFontData.js rename to src/js/webgl/text3dFontData.js From 09e33c056def437451770471731cd9131c2197ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Wed, 2 Oct 2019 11:11:14 +0200 Subject: [PATCH 004/131] MIDI access refactored. --- src/js/main.js | 4 +++- src/js/midi/midi.js | 25 ++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 28c0a21a..780b6ad9 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -25,7 +25,7 @@ import createAppView from './view/app.js'; import createCanvas3d from './webgl/canvas3d.js'; import createDialog from './view/dialog.js'; import createLibraryView from './view/library.js'; -import createMIDI from './midi/midi.js'; +import { accessMidi } from './midi/midi.js'; import createMIDINetwork from './midi/network.js'; import createPreferencesView from './view/preferences.js'; import createRemoteView from './view/remote.js'; @@ -34,6 +34,8 @@ import createTransport from './core/transport.js'; import { showDialog } from './view/dialog.js'; async function main() { + await accessMidi(); + persist(); } diff --git a/src/js/midi/midi.js b/src/js/midi/midi.js index d4c96c42..fe0dc4ec 100644 --- a/src/js/midi/midi.js +++ b/src/js/midi/midi.js @@ -1,4 +1,27 @@ -let midiAccess; + +let midiAccess = null; + +export function accessMidi() { + return new Promise((resolve, reject) => { + if (navigator.requestMIDIAccess) { + navigator.requestMIDIAccess({ sysex: false }) + .then( + access => { + console.log('MIDI enabled.'); + midiAccess = access; + resolve(); + }, + () => { + reject(`MIDI access failed`); + } + ); + } else { + reject(`No MIDI access in this browser`); + } + }); +} + + export default function createMIDI(specs) { var that, From 0b31e0152978e3f110a44c3bc2b80a7e700ee2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Wed, 2 Oct 2019 11:49:29 +0200 Subject: [PATCH 005/131] Processor preloading added. --- src/js/core/processor-loader.js | 79 +++++++++++++++++++ src/js/main.js | 5 +- src/js/processors/epg/object3dController.js | 4 +- src/js/processors/epg/processor.js | 2 +- src/js/processors/epg/{euclid.js => utils.js} | 0 src/js/processors/euclidfx/object3d.js | 2 +- .../processors/euclidfx/object3dController.js | 4 +- src/js/processors/euclidfx/processor.js | 2 +- .../euclidfx/{euclid.js => utils.js} | 0 src/js/processors/output/object3d.js | 2 +- src/js/processors/output/utils.js | 0 src/json/processors.json | 15 ++++ 12 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 src/js/core/processor-loader.js rename src/js/processors/epg/{euclid.js => utils.js} (100%) rename src/js/processors/euclidfx/{euclid.js => utils.js} (100%) create mode 100644 src/js/processors/output/utils.js create mode 100644 src/json/processors.json diff --git a/src/js/core/processor-loader.js b/src/js/core/processor-loader.js new file mode 100644 index 00000000..860f8676 --- /dev/null +++ b/src/js/core/processor-loader.js @@ -0,0 +1,79 @@ +/** + * Load a JSON file with the list of all processors, + * then preload all the processor's files + * and store the results in the processors object. + */ + +/** + * Processors contains each processor's configuration, + * settings panel HTML and module. + */ +const processors = {}; + +/** + * Data contains the parsed JSON. + */ +let data = null; + +/** + * Load the performers JSON file and each performer's files. + */ +export function preloadProcessors() { + return new Promise((resolve, reject) => { + fetch(`json/processors.json`) + .then(response => response.json()) + .then(json => { + data = json; + return Promise.all(json.ids.map(id => { + return Promise.all(json.files.map((file, index) => { + switch (index) { + case 0: + case 1: + return fetch(`js/processors/${id}/${file}`); + default: + return import(`../processors/${id}/${file}`); + } + })); + })) + .then(allResults => { + return Promise.all(allResults.map(results => { + return Promise.all(results.map((result, index) => { + switch (index) { + case 0: + return result.json(); + case 1: + return result.text(); + default: + return result; + } + })) + })) + }) + .then(allResults => { + allResults.forEach((results, index) => { + processors[json.ids[index]] = { + config: results[0], + settings: results[1], + module3d: results[2], + module3dController: results[3], + moduleProcessor: results[4], + moduleUtils: results[5], + }; + }); + console.log('Processor data preloaded.', processors); + resolve(); + }); + }); + }); +} + +export function getProcessorTypes() { + return data.ids; +} + +export function getProcessorData(name, type) { + if (performers[name] && performers[name][type]) { + return performers[name][type]; + } +} + diff --git a/src/js/main.js b/src/js/main.js index 780b6ad9..a62b227f 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -21,12 +21,12 @@ import createReducers from './state/reducers.js'; import { dispatch, getActions, getState, persist } from './state/store.js'; import createAppView from './view/app.js'; -// import createCanvasView from './view/canvas.js'; import createCanvas3d from './webgl/canvas3d.js'; import createDialog from './view/dialog.js'; import createLibraryView from './view/library.js'; import { accessMidi } from './midi/midi.js'; import createMIDINetwork from './midi/network.js'; +import { preloadProcessors } from './core/processor-loader.js'; import createPreferencesView from './view/preferences.js'; import createRemoteView from './view/remote.js'; import createTransport from './core/transport.js'; @@ -35,7 +35,8 @@ import { showDialog } from './view/dialog.js'; async function main() { await accessMidi(); - + await preloadProcessors(); + persist(); } diff --git a/src/js/processors/epg/object3dController.js b/src/js/processors/epg/object3dController.js index eae2c551..197a141a 100644 --- a/src/js/processors/epg/object3dController.js +++ b/src/js/processors/epg/object3dController.js @@ -3,10 +3,10 @@ import { Shape, ShapeGeometry, Vector2, -} from '../../../lib/three.module.js'; +} from '../../lib/three.module.js'; import { getTheme } from '../../state/selectors.js'; import createObject3dControllerBase from '../../webgl/object3dControllerBase.js'; -import { getEuclidPattern, rotateEuclidPattern } from './euclid.js'; +import { getEuclidPattern, rotateEuclidPattern } from './utils.js'; import { PPQN } from '../../core/config.js'; import { createCircleOutline, diff --git a/src/js/processors/epg/processor.js b/src/js/processors/epg/processor.js index 2f647cd3..90857285 100644 --- a/src/js/processors/epg/processor.js +++ b/src/js/processors/epg/processor.js @@ -1,6 +1,6 @@ import createMIDIProcessorBase from '../../midi/processorbase.js'; import { PPQN } from '../../core/config.js'; -import { getEuclidPattern, rotateEuclidPattern } from './euclid.js'; +import { getEuclidPattern, rotateEuclidPattern } from './utils.js'; export function createProcessor(specs, my) { let that, diff --git a/src/js/processors/epg/euclid.js b/src/js/processors/epg/utils.js similarity index 100% rename from src/js/processors/epg/euclid.js rename to src/js/processors/epg/utils.js diff --git a/src/js/processors/euclidfx/object3d.js b/src/js/processors/euclidfx/object3d.js index 3d719aa5..7537607e 100644 --- a/src/js/processors/euclidfx/object3d.js +++ b/src/js/processors/euclidfx/object3d.js @@ -1,7 +1,7 @@ import { Group, LineBasicMaterial, -} from '../../../lib/three.module.js'; +} from '../../lib/three.module.js'; import { createCircleFilled, createCircleOutline, diff --git a/src/js/processors/euclidfx/object3dController.js b/src/js/processors/euclidfx/object3dController.js index ce4f5e0f..fb293007 100644 --- a/src/js/processors/euclidfx/object3dController.js +++ b/src/js/processors/euclidfx/object3dController.js @@ -1,10 +1,10 @@ import { EllipseCurve, Vector2, -} from '../../../lib/three.module.js'; +} from '../../lib/three.module.js'; import { getTheme } from '../../state/selectors.js'; import createObject3dControllerBase from '../../webgl/object3dControllerBase.js'; -import { getEuclidPattern, rotateEuclidPattern } from './euclid.js'; +import { getEuclidPattern, rotateEuclidPattern } from './utils.js'; import { PPQN } from '../../core/config.js'; import { redrawShape } from '../../webgl/draw3dHelper.js'; diff --git a/src/js/processors/euclidfx/processor.js b/src/js/processors/euclidfx/processor.js index e792e82e..4943a414 100644 --- a/src/js/processors/euclidfx/processor.js +++ b/src/js/processors/euclidfx/processor.js @@ -1,6 +1,6 @@ import createMIDIProcessorBase from '../../midi/processorbase.js'; import { PPQN } from '../../core/config.js'; -import { getEuclidPattern, rotateEuclidPattern } from './euclid.js'; +import { getEuclidPattern, rotateEuclidPattern } from './utils.js'; export function createProcessor(specs, my) { let that, diff --git a/src/js/processors/euclidfx/euclid.js b/src/js/processors/euclidfx/utils.js similarity index 100% rename from src/js/processors/euclidfx/euclid.js rename to src/js/processors/euclidfx/utils.js diff --git a/src/js/processors/output/object3d.js b/src/js/processors/output/object3d.js index 218d787c..e9cfea9c 100644 --- a/src/js/processors/output/object3d.js +++ b/src/js/processors/output/object3d.js @@ -2,7 +2,7 @@ import { Group, LineBasicMaterial, Vector2, -} from '../../../lib/three.module.js'; +} from '../../lib/three.module.js'; import { createCircleFilled, createCircleOutline, diff --git a/src/js/processors/output/utils.js b/src/js/processors/output/utils.js new file mode 100644 index 00000000..e69de29b diff --git a/src/json/processors.json b/src/json/processors.json new file mode 100644 index 00000000..0e88183c --- /dev/null +++ b/src/json/processors.json @@ -0,0 +1,15 @@ +{ + "ids": [ + "epg", + "euclidfx", + "output" + ], + "files": [ + "config.json", + "settings.html", + "object3d.js", + "object3dController.js", + "processor.js", + "utils.js" + ] +} From c8ea870969952b4927f3074ae0a109f75df452af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 3 Oct 2019 17:03:24 +0200 Subject: [PATCH 006/131] Controls module added. Still empty. --- src/js/main.js | 6 +++--- src/js/view/controls.js | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 src/js/view/controls.js diff --git a/src/js/main.js b/src/js/main.js index a62b227f..ca0b72a4 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -16,8 +16,6 @@ along with this program. If not, see . */ -import createActions from './state/actions.js'; -import createReducers from './state/reducers.js'; import { dispatch, getActions, getState, persist } from './state/store.js'; import createAppView from './view/app.js'; @@ -26,17 +24,19 @@ import createDialog from './view/dialog.js'; import createLibraryView from './view/library.js'; import { accessMidi } from './midi/midi.js'; import createMIDINetwork from './midi/network.js'; +import { setup as setupControls } from './view/controls.js'; import { preloadProcessors } from './core/processor-loader.js'; import createPreferencesView from './view/preferences.js'; import createRemoteView from './view/remote.js'; import createTransport from './core/transport.js'; - import { showDialog } from './view/dialog.js'; async function main() { await accessMidi(); await preloadProcessors(); + setupControls(); + persist(); } diff --git a/src/js/view/controls.js b/src/js/view/controls.js new file mode 100644 index 00000000..535929db --- /dev/null +++ b/src/js/view/controls.js @@ -0,0 +1,5 @@ + + +export function setup() { + +} From 77778dd268a978dc94502637a98e7f19fea2c572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Wed, 9 Oct 2019 16:08:11 +0200 Subject: [PATCH 007/131] Controls module complete. --- src/js/view/controls.js | 128 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/src/js/view/controls.js b/src/js/view/controls.js index 535929db..fb820956 100644 --- a/src/js/view/controls.js +++ b/src/js/view/controls.js @@ -1,5 +1,133 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; +const resetKeyCombo = []; +const newEl = document.querySelector('#file-new'); +const importEl= document.querySelector('#file-import'); +const exportEl = document.querySelector('#file-export'); +const playEl = document.getElementById('play-check'); +const bpmEl = document.getElementById('bpm-number'); +const remoteEl = document.getElementById('learn-check'); +const libraryEl = document.getElementById('library-check'); +const prefsEl = document.getElementById('prefs-check'); +const editEl = document.getElementById('edit-check'); +const connectionsEl = document.getElementById('connections-check'); +const helpEl = document.getElementById('help-check'); export function setup() { + addEventListeners(); +} + +function addEventListeners() { + const actions = getActions(); + + document.addEventListener(STATE_CHANGE, handleStateChanges); + + newEl.addEventListener('click', e => { + dispatch(actions.newProject()); + }); + importEl.addEventListener('change', e => { + dispatch(actions.importProject(e.target.files[0])); + }); + exportEl.addEventListener('click', e => { + dispatch(actions.exportProject()); + }); + playEl.addEventListener('change', e => { + dispatch(actions.setTransport('toggle')); + }); + bpmEl.addEventListener('change', e => { + dispatch(actions.setTempo(bpmEl.value)); + }); + remoteEl.addEventListener('change', e => { + dispatch(actions.toggleMIDILearnMode()); + }); + libraryEl.addEventListener('change', e => { + dispatch(actions.togglePanel('library')); + }); + prefsEl.addEventListener('change', e => { + dispatch(actions.togglePanel('preferences')); + }); + editEl.addEventListener('change', e => { + dispatch(actions.togglePanel('settings')); + }); + connectionsEl.addEventListener('change', e => { + dispatch(actions.toggleConnectMode()); + }); + helpEl.addEventListener('change', e => { + dispatch(actions.togglePanel('help')); + }); + + document.addEventListener('keyup', e => { + + // don't perform shortcuts while typing in a text input. + if (!(e.target.tagName.toLowerCase() == 'input' && e.target.getAttribute('type') == 'text')) { + switch (e.keyCode) { + case 32: // space + dispatch(actions.setTransport('toggle')); + break; + + case 83: // s + console.log('state', store.getState()); + break; + } + } + resetKeyCombo.length = 0; + }); + + document.addEventListener('keydown', e => { + + // don't perform shortcuts while typing in a text input. + if (!(e.target.tagName.toLowerCase() == 'input' && e.target.getAttribute('type') == 'text')) { + switch (e.keyCode) { + case 82: // r + case 83: // s + case 84: // t + // clear all data on key combination 'rst' (reset) + resetKeyCombo.push(e.keyCode); + if (resetKeyCombo.indexOf(82) > -1 && resetKeyCombo.indexOf(83) > -1 && resetKeyCombo.indexOf(84) > -1) { + localStorage.clear(); + dispatch(actions.newProject()); + } + break; + } + } + }); +} + +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.CREATE_PROJECT: + case actions.DELETE_PROCESSOR: + case actions.SELECT_PROCESSOR: + case actions.TOGGLE_MIDI_LEARN_MODE: + case actions.TOGGLE_PANEL: + updateInputs(state); + bpmEl.value = state.bpm; + break; + + case actions.SET_TEMPO: + bpmEl.value = state.bpm; + break; + + case actions.SET_TRANSPORT: + playEl.checked = state.transport === 'play'; + break; + } +} +/** + * Update controls state. + * @param {Object} state App state. + */ +function updateInputs(state) { + helpEl.checked = state.showHelpPanel; + prefsEl.checked = state.showPreferencesPanel; + remoteEl.checked = state.learnModeActive; + editEl.checked = state.showSettingsPanel; + libraryEl.checked = state.showLibraryPanel; + connectionsEl.checked = state.connectModeActive; } From fe5224703e8dbc602232fd29e7934c2aa2a171ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Wed, 9 Oct 2019 17:27:15 +0200 Subject: [PATCH 008/131] App module refacored to Controls and Panels. --- src/js/main.js | 4 +- src/js/state/actions.js | 139 ++++--- src/js/state/reducers.js | 811 ++++++++++++++++++++------------------- src/js/view/controls.js | 8 +- src/js/view/panels.js | 220 +++++++++++ 5 files changed, 703 insertions(+), 479 deletions(-) create mode 100644 src/js/view/panels.js diff --git a/src/js/main.js b/src/js/main.js index ca0b72a4..3079eb6a 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { dispatch, getActions, getState, persist } from './state/store.js'; +import { dispatch, getActions, getState, persist, } from './state/store.js'; import createAppView from './view/app.js'; import createCanvas3d from './webgl/canvas3d.js'; @@ -25,6 +25,7 @@ import createLibraryView from './view/library.js'; import { accessMidi } from './midi/midi.js'; import createMIDINetwork from './midi/network.js'; import { setup as setupControls } from './view/controls.js'; +import { setup as setupPanels } from './view/panels.js'; import { preloadProcessors } from './core/processor-loader.js'; import createPreferencesView from './view/preferences.js'; import createRemoteView from './view/remote.js'; @@ -36,6 +37,7 @@ async function main() { await preloadProcessors(); setupControls(); + setupPanels(); persist(); } diff --git a/src/js/state/actions.js b/src/js/state/actions.js index 8c17bf5a..8ee104df 100644 --- a/src/js/state/actions.js +++ b/src/js/state/actions.js @@ -4,76 +4,74 @@ import { getConfig, setConfig, processorTypes } from '../core/config.js'; import { getAllMIDIPorts } from '../midi/midi.js'; import { showDialog } from '../view/dialog.js'; -export default function createActions(specs = {}, my = {}) { - const RESCAN_TYPES = 'RESCAN_TYPES', - CREATE_PROJECT = 'CREATE_PROJECT', - SET_THEME = 'SET_THEME', - CREATE_PROCESSOR = 'CREATE_PROCESSOR', - ADD_PROCESSOR = 'ADD_PROCESSOR', - DELETE_PROCESSOR = 'DELETE_PROCESSOR', - SELECT_PROCESSOR = 'SELECT_PROCESSOR', - DRAG_SELECTED_PROCESSOR = 'DRAG_SELECTED_PROCESSOR', - DRAG_ALL_PROCESSORS = 'DRAG_ALL_PROCESSORS', - CHANGE_PARAMETER = 'CHANGE_PARAMETER', - RECREATE_PARAMETER = 'RECREATE_PARAMETER', - SET_TEMPO = 'SET_TEMPO', - CREATE_MIDI_PORT = 'CREATE_MIDI_PORT', - UPDATE_MIDI_PORT = 'UPDATE_MIDI_PORT', - TOGGLE_MIDI_PREFERENCE = 'TOGGLE_MIDI_PREFERENCE', - TOGGLE_MIDI_LEARN_MODE = 'TOGGLE_MIDI_LEARN_MODE', - TOGGLE_MIDI_LEARN_TARGET = 'TOGGLE_MIDI_LEARN_TARGET', - SET_TRANSPORT = 'SET_TRANSPORT', - RECEIVE_MIDI_CC = 'RECEIVE_MIDI_CC', - ASSIGN_EXTERNAL_CONTROL = 'ASSIGN_EXTERNAL_CONTROL', - UNASSIGN_EXTERNAL_CONTROL = 'UNASSIGN_EXTERNAL_CONTROL', - TOGGLE_PANEL = 'TOGGLE_PANEL', - TOGGLE_CONNECT_MODE = 'TOGGLE_CONNECT_MODE', - CONNECT_PROCESSORS = 'CONNECT_PROCESSORS', - DISCONNECT_PROCESSORS = 'DISCONNECT_PROCESSORS', - SET_CAMERA_POSITION = 'SET_CAMERA_POSITION', - LIBRARY_DROP = 'LIBRARY_DROP'; - - return { - - importProject: (file) => { - return (dispatch, getState, getActions) => { - - file.text() - .then(text => { - let isJSON = true, - isXML = false; - try { - const data = JSON.parse(text); - if (data) { - dispatch(getActions().setProject(data)); - } - } catch(errorMessage) { - isJSON = false; - } - if (!isJSON) { - - // try if it's a legacy xml file - const legacyData = convertLegacyFile(text); - if (legacyData) { - dispatch(getActions().setProject(legacyData)); - isXML = true; - } - } - if (!isJSON && !isXML) { - showDialog( - 'Import failed', - `The file to import wasn't recognised as a valid type for this application.`, - 'Ok'); - } - }) - .catch(() =>{ - showDialog( - 'Import failed', - `The file could not be opened.`, - 'Ok'); - }); +const RESCAN_TYPES = 'RESCAN_TYPES', + CREATE_PROJECT = 'CREATE_PROJECT', + SET_THEME = 'SET_THEME', + CREATE_PROCESSOR = 'CREATE_PROCESSOR', + ADD_PROCESSOR = 'ADD_PROCESSOR', + DELETE_PROCESSOR = 'DELETE_PROCESSOR', + SELECT_PROCESSOR = 'SELECT_PROCESSOR', + DRAG_SELECTED_PROCESSOR = 'DRAG_SELECTED_PROCESSOR', + DRAG_ALL_PROCESSORS = 'DRAG_ALL_PROCESSORS', + CHANGE_PARAMETER = 'CHANGE_PARAMETER', + RECREATE_PARAMETER = 'RECREATE_PARAMETER', + SET_TEMPO = 'SET_TEMPO', + CREATE_MIDI_PORT = 'CREATE_MIDI_PORT', + UPDATE_MIDI_PORT = 'UPDATE_MIDI_PORT', + TOGGLE_MIDI_PREFERENCE = 'TOGGLE_MIDI_PREFERENCE', + TOGGLE_MIDI_LEARN_MODE = 'TOGGLE_MIDI_LEARN_MODE', + TOGGLE_MIDI_LEARN_TARGET = 'TOGGLE_MIDI_LEARN_TARGET', + SET_TRANSPORT = 'SET_TRANSPORT', + RECEIVE_MIDI_CC = 'RECEIVE_MIDI_CC', + ASSIGN_EXTERNAL_CONTROL = 'ASSIGN_EXTERNAL_CONTROL', + UNASSIGN_EXTERNAL_CONTROL = 'UNASSIGN_EXTERNAL_CONTROL', + TOGGLE_PANEL = 'TOGGLE_PANEL', + TOGGLE_CONNECT_MODE = 'TOGGLE_CONNECT_MODE', + CONNECT_PROCESSORS = 'CONNECT_PROCESSORS', + DISCONNECT_PROCESSORS = 'DISCONNECT_PROCESSORS', + SET_CAMERA_POSITION = 'SET_CAMERA_POSITION', + LIBRARY_DROP = 'LIBRARY_DROP'; + +// actions +export default { + + importProject: file => { + return (dispatch, getState, getActions) => { + file.text() + .then(text => { + let isJSON = true, isXML = false; + try { + const data = JSON.parse(text); + if (data) { + dispatch(getActions().setProject(data)); } - }, + } catch(errorMessage) { + isJSON = false; + } + if (!isJSON) { + + // try if it's a legacy xml file + const legacyData = convertLegacyFile(text); + if (legacyData) { + dispatch(getActions().setProject(legacyData)); + isXML = true; + } + } + if (!isJSON && !isXML) { + showDialog( + 'Import failed', + `The file to import wasn't recognised as a valid type for this application.`, + 'Ok'); + } + }) + .catch(() =>{ + showDialog( + 'Import failed', + `The file could not be opened.`, + 'Ok'); + }); + } + }, exportProject: () => { return (dispatch, getState, getActions) => { @@ -387,8 +385,7 @@ export default function createActions(specs = {}, my = {}) { libraryDrop: (processorType, x, y) => { return { type: LIBRARY_DROP, processorType, x, y, }; }, - }; -} + } /** * Convert a MIDI control value to a parameter value, depending on the parameter type. diff --git a/src/js/state/reducers.js b/src/js/state/reducers.js index b8e74df0..2e68371a 100644 --- a/src/js/state/reducers.js +++ b/src/js/state/reducers.js @@ -1,422 +1,425 @@ import orderProcessors from '../midi/network_ordering.js'; -export default function createReducers() { +const initialState = { + assignments: { + allIds: [], + byId: {}, + }, + bpm: 120, + camera: { + x: 0, + y: 0, + z: 0, + }, + connections: { + allIds: [], + byId: {}, + }, + connectModeActive: false, + learnModeActive: false, + learnTargetParameterKey: null, + learnTargetProcessorID: null, + libraryDropPosition: { + type: null, + x: 0, + y: 0, + }, + ports: { + allIds: [], + byId: {}, + }, + processors: { + allIds: [], + byId: {}, + }, + selectedID: null, + showHelpPanel: false, + showLibraryPanel: true, + showPreferencesPanel: false, + showSettingsPanel: false, + theme: 'dev', // 'light|dark' + transport: 'stop', // 'play|pause|stop' + types: { + allIds: [], + byId: {}, + }, + version: '2.1.0-beta.2', +}; - const initialState = { - processors: { - byId: {}, - allIds: [] - }, - connections: { - byId: {}, - allIds: [] - }, - ports: { - byId: {}, - allIds: [] - }, - types: { - byId: {}, - allIds: [] - }, - assignments: { - byId: {}, - allIds: [] - }, - camera: { - x: 0, - y: 0, - z: 0, - }, - libraryDropPosition: { - type: null, - x: 0, - y: 0, - }, - bpm: 120, - selectedID: null, - theme: 'dev', // 'light|dark' - transport: 'stop', // 'play|pause|stop' - connectModeActive: false, - learnModeActive: false, - learnTargetProcessorID: null, - learnTargetParameterKey: null, - showHelpPanel: false, - showLibraryPanel: true, - showPreferencesPanel: false, - showSettingsPanel: false, - version: '2.1.0-beta.2', - }, - - reduce = function(state = initialState, action = {}, actions = {}) { - let newState; - switch(action.type) { +/** + * + * @param {Object} state + * @param {Object} action + * @param {String} action.type + */ +export default function reduce(state = initialState, action, actions = {}) { + let newState; + switch(action.type) { - case actions.CREATE_PROJECT: - return { - ...initialState, - ...(action.data || {}), - transport: initialState.transport }; + case actions.CREATE_PROJECT: + return { + ...initialState, + ...(action.data || {}), + transport: initialState.transport, + }; - case actions.SET_THEME: - return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' }; + case actions.SET_THEME: + return { + ...state, + theme: state.theme === 'light' ? 'dark' : 'light', + }; - case actions.ADD_PROCESSOR: - newState = { - ...state, - showSettingsPanel: true, - processors: { - byId: { - ...state.processors.byId, - [action.data.id]: action.data - }, - allIds: [ ...state.processors.allIds ] - } }; + case actions.ADD_PROCESSOR: + newState = { + ...state, + showSettingsPanel: true, + processors: { + byId: { + ...state.processors.byId, + [action.data.id]: action.data + }, + allIds: [ ...state.processors.allIds ] + } }; - // array index depends on processor type - let numInputProcessors = newState.processors.allIds.filter(id => { newState.processors.byId[id].type === 'input' }).length; - switch (action.data.type) { - case 'input': - newState.processors.allIds.unshift(action.data.id); - numInputProcessors++; - break; - case 'output': - newState.processors.allIds.push(action.data.id); - break; - default: - newState.processors.allIds.splice(numInputProcessors, 0, action.data.id); - } - - return newState; - - case actions.DELETE_PROCESSOR: - const index = state.processors.allIds.indexOf(action.id); - - // delete the processor - newState = { - ...state, - processors: { - byId: { ...state.processors.byId }, - allIds: state.processors.allIds.filter(id => id !== action.id) - } }; - delete newState.processors.byId[action.id]; - - // delete all connections to and from the deleted processor - newState.connections = { - byId: { ...state.connections.byId }, - allIds: [ ...state.connections.allIds ] - } - for (let i = newState.connections.allIds.length -1, n = 0; i >= n; i--) { - const connectionID = newState.connections.allIds[i]; - const connection = newState.connections.byId[connectionID]; - if (connection.sourceProcessorID === action.id || connection.destinationProcessorID === action.id) { - newState.connections.allIds.splice(i, 1); - delete newState.connections.byId[connectionID]; - } - } + // array index depends on processor type + let numInputProcessors = newState.processors.allIds.filter(id => { newState.processors.byId[id].type === 'input' }).length; + switch (action.data.type) { + case 'input': + newState.processors.allIds.unshift(action.data.id); + numInputProcessors++; + break; + case 'output': + newState.processors.allIds.push(action.data.id); + break; + default: + newState.processors.allIds.splice(numInputProcessors, 0, action.data.id); + } + + return newState; + + case actions.DELETE_PROCESSOR: + const index = state.processors.allIds.indexOf(action.id); + + // delete the processor + newState = { + ...state, + processors: { + byId: { ...state.processors.byId }, + allIds: state.processors.allIds.filter(id => id !== action.id) + } }; + delete newState.processors.byId[action.id]; + + // delete all connections to and from the deleted processor + newState.connections = { + byId: { ...state.connections.byId }, + allIds: [ ...state.connections.allIds ] + } + for (let i = newState.connections.allIds.length -1, n = 0; i >= n; i--) { + const connectionID = newState.connections.allIds[i]; + const connection = newState.connections.byId[connectionID]; + if (connection.sourceProcessorID === action.id || connection.destinationProcessorID === action.id) { + newState.connections.allIds.splice(i, 1); + delete newState.connections.byId[connectionID]; + } + } - // select the next processor, if any, or a previous one - let newIndex; - if (newState.selectedID === action.id && newState.processors.allIds.length) { - if (newState.processors.allIds[index]) { - newIndex = index; - } else if (index > 0) { - newIndex = index - 1; - } else { - newIndex = 0; - } - newState.selectedID = newState.processors.allIds[newIndex]; - } - - // reorder the processors - orderProcessors(newState); + // select the next processor, if any, or a previous one + let newIndex; + if (newState.selectedID === action.id && newState.processors.allIds.length) { + if (newState.processors.allIds[index]) { + newIndex = index; + } else if (index > 0) { + newIndex = index - 1; + } else { + newIndex = 0; + } + newState.selectedID = newState.processors.allIds[newIndex]; + } + + // reorder the processors + orderProcessors(newState); - return newState; - - case actions.SELECT_PROCESSOR: - return { ...state, selectedID: action.id }; - - case actions.DRAG_SELECTED_PROCESSOR: - return { - ...state, - processors: { - allIds: [ ...state.processors.allIds ], - byId: Object.values(state.processors.byId).reduce((accumulator, processor) => { - if (processor.id === state.selectedID) { - accumulator[processor.id] = { ...processor, positionX: action.x, positionY: action.y, positionZ: action.z }; - } else { - accumulator[processor.id] = { ...processor }; - } - return accumulator; - }, {}) - } }; + return newState; + + case actions.SELECT_PROCESSOR: + return { ...state, selectedID: action.id }; + + case actions.DRAG_SELECTED_PROCESSOR: + return { + ...state, + processors: { + allIds: [ ...state.processors.allIds ], + byId: Object.values(state.processors.byId).reduce((accumulator, processor) => { + if (processor.id === state.selectedID) { + accumulator[processor.id] = { ...processor, positionX: action.x, positionY: action.y, positionZ: action.z }; + } else { + accumulator[processor.id] = { ...processor }; + } + return accumulator; + }, {}) + } }; - case actions.DRAG_ALL_PROCESSORS: - return { - ...state, - processors: { - allIds: [ ...state.processors.allIds ], - byId: Object.values(state.processors.byId).reduce((accumulator, processor) => { - accumulator[processor.id] = { - ...processor, - positionX: processor.positionX + action.x, - positionY: processor.positionY + action.y }; - return accumulator; - }, {}) - } }; - - case actions.CHANGE_PARAMETER: - newState = { - ...state, - processors: { - byId: { ...state.processors.byId }, - allIds: [ ...state.processors.allIds ] - } }; - const param = newState.processors.byId[action.processorID].params.byId[action.paramKey]; - switch (param.type) { - case 'integer': - param.value = Math.max(param.min, Math.min(action.paramValue, param.max)); - break; - case 'boolean': - param.value = !!action.paramValue; - break; - case 'itemized': - param.value = action.paramValue; - break; - case 'string': - param.value = action.paramValue; - break; - } - return newState; - - case actions.RECREATE_PARAMETER: - // clone state - newState = { - ...state, - processors: { - byId: { ...state.processors.byId }, - allIds: [ ...state.processors.allIds ] - } }; - - // clone parameter, overwrite with new settings. - newState.processors.byId[action.processorID].params.byId[action.paramKey] = { - ...newState.processors.byId[action.processorID].params.byId[action.paramKey], - ...action.paramObj - }; - - return newState; - - case actions.SET_TEMPO: - return { ...state, bpm: action.value }; + case actions.DRAG_ALL_PROCESSORS: + return { + ...state, + processors: { + allIds: [ ...state.processors.allIds ], + byId: Object.values(state.processors.byId).reduce((accumulator, processor) => { + accumulator[processor.id] = { + ...processor, + positionX: processor.positionX + action.x, + positionY: processor.positionY + action.y }; + return accumulator; + }, {}) + } }; + + case actions.CHANGE_PARAMETER: + newState = { + ...state, + processors: { + byId: { ...state.processors.byId }, + allIds: [ ...state.processors.allIds ] + } }; + const param = newState.processors.byId[action.processorID].params.byId[action.paramKey]; + switch (param.type) { + case 'integer': + param.value = Math.max(param.min, Math.min(action.paramValue, param.max)); + break; + case 'boolean': + param.value = !!action.paramValue; + break; + case 'itemized': + param.value = action.paramValue; + break; + case 'string': + param.value = action.paramValue; + break; + } + return newState; + + case actions.RECREATE_PARAMETER: + // clone state + newState = { + ...state, + processors: { + byId: { ...state.processors.byId }, + allIds: [ ...state.processors.allIds ] + } }; + + // clone parameter, overwrite with new settings. + newState.processors.byId[action.processorID].params.byId[action.paramKey] = { + ...newState.processors.byId[action.processorID].params.byId[action.paramKey], + ...action.paramObj + }; + + return newState; + + case actions.SET_TEMPO: + return { ...state, bpm: action.value }; - case actions.CREATE_MIDI_PORT: - return { - ...state, - ports: { - allIds: [ ...state.ports.allIds, action.portID ], - byId: { - ...state.ports.byId, - [action.portID]: action.data - } - } - }; + case actions.CREATE_MIDI_PORT: + return { + ...state, + ports: { + allIds: [ ...state.ports.allIds, action.portID ], + byId: { + ...state.ports.byId, + [action.portID]: action.data + } + } + }; - case actions.UPDATE_MIDI_PORT: - return { - ...state, - ports: { - allIds: [ ...state.ports.allIds ], - byId: Object.values(state.ports.byId).reduce((returnObject, port) => { - if (port.id === action.portID) { - returnObject[port.id] = { ...port, ...action.data }; - } else { - returnObject[port.id] = { ...port }; - } - return returnObject; - }, {}) - } - }; - - case actions.TOGGLE_MIDI_PREFERENCE: - return { - ...state, - ports: { - allIds: [ ...state.ports.allIds ], - byId: Object.values(state.ports.allIds).reduce((accumulator, portID) => { - if (portID === action.id) { - accumulator[portID] = { - ...state.ports.byId[portID], - [action.preferenceName]: typeof action.isEnabled === 'boolean' ? isEnabled : !state.ports.byId[action.id][action.preferenceName] - }; - } else { - accumulator[portID] = { ...state.ports.byId[portID] }; - } - return accumulator; - }, {}) - } - }; - - case actions.TOGGLE_MIDI_LEARN_MODE: - return { ...state, learnModeActive: !state.learnModeActive }; - - case actions.TOGGLE_MIDI_LEARN_TARGET: - return { - ...state, - learnTargetProcessorID: action.processorID, - learnTargetParameterKey: action.parameterKey - }; - - case actions.SET_TRANSPORT: - let value = action.command; - if (action.command === 'toggle') { - value = state.transport === 'play' ? 'pause' : 'play'; - } - return Object.assign({}, state, { - transport: value - }); + case actions.UPDATE_MIDI_PORT: + return { + ...state, + ports: { + allIds: [ ...state.ports.allIds ], + byId: Object.values(state.ports.byId).reduce((returnObject, port) => { + if (port.id === action.portID) { + returnObject[port.id] = { ...port, ...action.data }; + } else { + returnObject[port.id] = { ...port }; + } + return returnObject; + }, {}) + } + }; + + case actions.TOGGLE_MIDI_PREFERENCE: + return { + ...state, + ports: { + allIds: [ ...state.ports.allIds ], + byId: Object.values(state.ports.allIds).reduce((accumulator, portID) => { + if (portID === action.id) { + accumulator[portID] = { + ...state.ports.byId[portID], + [action.preferenceName]: typeof action.isEnabled === 'boolean' ? isEnabled : !state.ports.byId[action.id][action.preferenceName] + }; + } else { + accumulator[portID] = { ...state.ports.byId[portID] }; + } + return accumulator; + }, {}) + } + }; + + case actions.TOGGLE_MIDI_LEARN_MODE: + return { ...state, learnModeActive: !state.learnModeActive }; + + case actions.TOGGLE_MIDI_LEARN_TARGET: + return { + ...state, + learnTargetProcessorID: action.processorID, + learnTargetParameterKey: action.parameterKey + }; + + case actions.SET_TRANSPORT: + let value = action.command; + if (action.command === 'toggle') { + value = state.transport === 'play' ? 'pause' : 'play'; + } + return Object.assign({}, state, { + transport: value + }); - case actions.ASSIGN_EXTERNAL_CONTROL: - return { - ...state, - assignments: { - allIds: [...state.assignments.allIds, action.assignID], - byId: { - ...state.assignments.byId, - [action.assignID]: { - remoteChannel: action.remoteChannel, - remoteCC: action.remoteCC, - processorID: action.processorID, - paramKey: action.paramKey - } - } - } - }; - - case actions.UNASSIGN_EXTERNAL_CONTROL: - return { - ...state, - assignments: { - allIds: state.assignments.allIds.reduce((accumulator, assignID) => { - const assignment = state.assignments.byId[assignID]; - if (assignment.processorID !== action.processorID || assignment.paramKey !== action.paramKey) { - accumulator.push(assignID); - } - return accumulator; - }, []), - byId: state.assignments.allIds.reduce((accumulator, assignID) => { - const assignment = state.assignments.byId[assignID]; - if (assignment.processorID !== action.processorID || assignment.paramKey !== action.paramKey) { - accumulator[assignID] = {...assignment}; - } - return accumulator; - }, {}) - } - }; - - case actions.TOGGLE_PANEL: - return { - ...state, - showHelpPanel: action.panelName === 'help' ? !state.showHelpPanel : state.showHelpPanel, - showPreferencesPanel: action.panelName === 'preferences' ? !state.showPreferencesPanel : state.showPreferencesPanel, - showSettingsPanel: action.panelName === 'settings' ? !state.showSettingsPanel : state.showSettingsPanel, - showLibraryPanel: action.panelName === 'library' ? !state.showLibraryPanel : state.showLibraryPanel - }; - - case actions.TOGGLE_CONNECT_MODE: - return { - ...state, - connectModeActive: !state.connectModeActive - }; - - case actions.CONNECT_PROCESSORS: + case actions.ASSIGN_EXTERNAL_CONTROL: + return { + ...state, + assignments: { + allIds: [...state.assignments.allIds, action.assignID], + byId: { + ...state.assignments.byId, + [action.assignID]: { + remoteChannel: action.remoteChannel, + remoteCC: action.remoteCC, + processorID: action.processorID, + paramKey: action.paramKey + } + } + } + }; + + case actions.UNASSIGN_EXTERNAL_CONTROL: + return { + ...state, + assignments: { + allIds: state.assignments.allIds.reduce((accumulator, assignID) => { + const assignment = state.assignments.byId[assignID]; + if (assignment.processorID !== action.processorID || assignment.paramKey !== action.paramKey) { + accumulator.push(assignID); + } + return accumulator; + }, []), + byId: state.assignments.allIds.reduce((accumulator, assignID) => { + const assignment = state.assignments.byId[assignID]; + if (assignment.processorID !== action.processorID || assignment.paramKey !== action.paramKey) { + accumulator[assignID] = {...assignment}; + } + return accumulator; + }, {}) + } + }; + + case actions.TOGGLE_PANEL: + return { + ...state, + showHelpPanel: action.panelName === 'help' ? !state.showHelpPanel : state.showHelpPanel, + showPreferencesPanel: action.panelName === 'preferences' ? !state.showPreferencesPanel : state.showPreferencesPanel, + showSettingsPanel: action.panelName === 'settings' ? !state.showSettingsPanel : state.showSettingsPanel, + showLibraryPanel: action.panelName === 'library' ? !state.showLibraryPanel : state.showLibraryPanel + }; + + case actions.TOGGLE_CONNECT_MODE: + return { + ...state, + connectModeActive: !state.connectModeActive + }; + + case actions.CONNECT_PROCESSORS: - // abort if the connection already exists - for (let i = 0, n = state.connections.allIds.length; i < n; i++) { - const connection = state.connections.byId[state.connections.allIds[i]]; - if (connection.sourceProcessorID === action.payload.sourceProcessorID && - connection.sourceConnectorID === action.payload.sourceConnectorID && - connection.destinationProcessorID === action.payload.destinationProcessorID && - connection.destinationConnectorID === action.payload.destinationConnectorID) { - return state; - } - } + // abort if the connection already exists + for (let i = 0, n = state.connections.allIds.length; i < n; i++) { + const connection = state.connections.byId[state.connections.allIds[i]]; + if (connection.sourceProcessorID === action.payload.sourceProcessorID && + connection.sourceConnectorID === action.payload.sourceConnectorID && + connection.destinationProcessorID === action.payload.destinationProcessorID && + connection.destinationConnectorID === action.payload.destinationConnectorID) { + return state; + } + } - // add new connection - newState = { - ...state, - connections: { - byId: { ...state.connections.byId, [action.id]: action.payload }, - allIds: [ ...state.connections.allIds, action.id ] - }, - processors: { - byId: { ...state.processors.byId }, - allIds: [ ...state.processors.allIds ] - } - }; + // add new connection + newState = { + ...state, + connections: { + byId: { ...state.connections.byId, [action.id]: action.payload }, + allIds: [ ...state.connections.allIds, action.id ] + }, + processors: { + byId: { ...state.processors.byId }, + allIds: [ ...state.processors.allIds ] + } + }; - // reorder the processors - orderProcessors(newState); - return newState; - - case actions.DISCONNECT_PROCESSORS: - newState = { - ...state, - connections: { - allIds: state.connections.allIds.reduce((accumulator, connectionID) => { - if (connectionID !== action.id) { - accumulator.push(connectionID) - } - return accumulator; - }, []), - byId: Object.values(state.connections.allIds).reduce((accumulator, connectionID) => { - if (connectionID !== action.id) { - accumulator[connectionID] = { ...state.connections.byId[connectionID] }; - } - return accumulator; - }, {}) - } - }; - - // reorder the processors - orderProcessors(newState); - return newState; + // reorder the processors + orderProcessors(newState); + return newState; + + case actions.DISCONNECT_PROCESSORS: + newState = { + ...state, + connections: { + allIds: state.connections.allIds.reduce((accumulator, connectionID) => { + if (connectionID !== action.id) { + accumulator.push(connectionID) + } + return accumulator; + }, []), + byId: Object.values(state.connections.allIds).reduce((accumulator, connectionID) => { + if (connectionID !== action.id) { + accumulator[connectionID] = { ...state.connections.byId[connectionID] }; + } + return accumulator; + }, {}) + } + }; + + // reorder the processors + orderProcessors(newState); + return newState; - case actions.RESCAN_TYPES: - return { - ...state, - types: { - allIds: Object.keys(action.types), - byId: action.types - } - }; - - case actions.SET_CAMERA_POSITION: - const { x, y, z, isRelative } = action; - return { - ...state, - camera: { - x: isRelative ? state.camera.x + x : x, - y: isRelative ? state.camera.y + y : y, - z: isRelative ? state.camera.z + z : z, - } - }; - - case actions.LIBRARY_DROP: - return { - ...state, - libraryDropPosition: { - type: action.processorType, - x: action.x, - y: action.y, - } - }; + case actions.RESCAN_TYPES: + return { + ...state, + types: { + allIds: Object.keys(action.types), + byId: action.types + } + }; + + case actions.SET_CAMERA_POSITION: + const { x, y, z, isRelative } = action; + return { + ...state, + camera: { + x: isRelative ? state.camera.x + x : x, + y: isRelative ? state.camera.y + y : y, + z: isRelative ? state.camera.z + z : z, + } + }; + + case actions.LIBRARY_DROP: + return { + ...state, + libraryDropPosition: { + type: action.processorType, + x: action.x, + y: action.y, + } + }; - default: - return state; - } - }; - - return { - reduce: reduce - } -} + default: + return state ? state : initialState; + } +}; diff --git a/src/js/view/controls.js b/src/js/view/controls.js index fb820956..26418de3 100644 --- a/src/js/view/controls.js +++ b/src/js/view/controls.js @@ -1,4 +1,4 @@ -import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; +import { dispatch, getActions, getState, STATE_CHANGE, } from '../state/store.js'; const resetKeyCombo = []; const newEl = document.querySelector('#file-new'); @@ -66,7 +66,7 @@ function addEventListeners() { break; case 83: // s - console.log('state', store.getState()); + console.log('state', getState()); break; } } @@ -98,7 +98,9 @@ function addEventListeners() { * @param {Object} e */ function handleStateChanges(e) { - const { state, action, actions, } = e.detail; + const { state, action, } = e.detail; + const actions = getActions(); + switch (action.type) { case actions.CREATE_PROJECT: case actions.DELETE_PROCESSOR: diff --git a/src/js/view/panels.js b/src/js/view/panels.js new file mode 100644 index 00000000..808d5d37 --- /dev/null +++ b/src/js/view/panels.js @@ -0,0 +1,220 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; +import createSettingsPanel from './settings.js'; +import addWindowResizeCallback from '../view/windowresize.js'; +import { getProcessorData } from '../core/processor-loader.js'; + +const settingsViews = []; +const panelsEl = document.querySelector('.panels'); +const libraryEl = document.querySelector('.library'); +const helpEl = document.querySelector('.help'); +const prefsEl = document.querySelector('.prefs'); +const editEl = document.querySelector('.edit'); +const editContentEl = document.querySelector('.edit .panel__content'); +const remoteEl = document.querySelector('.remote'); +let panelHeaderHeight; + +export function setup() { + addEventListeners(); + + // get panel header height from CSS. + const style = getComputedStyle(document.body); + panelHeaderHeight = parseInt(style.getPropertyValue('--header-height'), 10); + + addWindowResizeCallback(renderLayout); + renderLayout(); +} + +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); +} + +/** + * Create settings controls view for a processor. + * @param {Object} processor MIDI processor to control with the settings. + */ +function createSettingsViews(state) { + const { processors, selectedID, } = state; + processors.allIds.forEach((id, i) => { + const processorData = processors.byId[id]; + let exists = settingsViews.some(view => view.getID() === id); + if (!exists) { + const settingsHTML = getProcessorData(processorData.type, 'settings'); + settingsViews.splice(i, 0, createSettingsPanel({ + data: processorData, + store, + parentEl: editContentEl, + template: settingsHTML, + isSelected: selectedID === processorData.id + })); + } + }); +} + +/** + * Delete settings controls view for a processor. + * @param {String} id MIDI processor ID. + */ +function deleteSettingsView(id) { + settingsViews = settingsViews.reduce((accumulator, view) => { + if (view.getID() === id) { + view.terminate(); + return accumulator; + } + return [...accumulator, view]; + }, []); +} + +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + + case actions.ADD_PROCESSOR: + createSettingsViews(state); + renderLayout(); + break; + + case actions.CREATE_PROJECT: + setProject(state); + showPanels(state); + break; + + case actions.DELETE_PROCESSOR: + deleteSettingsView(action.id); + showPanels(state); + selectSettingsView(state.selectedID); + renderLayout(); + break; + + case actions.SELECT_PROCESSOR: + selectSettingsView(action.id); + + // fallthrough intentional + case actions.TOGGLE_MIDI_LEARN_MODE: + case actions.TOGGLE_PANEL: + showPanels(state); + break; + } +} + +/** + * Render the panels layout. + * @param {Boolean} leftColumn Render the left panel column. + * @param {Boolean} rightColumn Render the right panel column. + */ +function renderLayout(leftColumn = true, rightColumn = true) { + if (leftColumn) { + renderColumnLayout(prefsEl, remoteEl, false); + } + if (rightColumn) { + renderColumnLayout(helpEl, editEl, true); + } +} + +/** + * Render a column of the panels layout. + * @param {Object} topEl Bottom panel in the column. + * @param {Object} topEl Top panel in the column. + * @param {Boolean} isRightColumn True if the right column is being rendered. + */ +function renderColumnLayout(topEl, btmEl, isRightColumn) { + const totalHeight = panelsEl.clientHeight, + columnWidth = document.querySelector('.panels__right').clientWidth, + topWidth = topEl.clientWidth, + btmWidth = btmEl.clientWidth, + isTopVisible = topEl.dataset.show == 'true', + isBtmVisible = btmEl.dataset.show == 'true', + topViewportEl = topEl.querySelector('.panel__viewport'), + btmViewportEl = btmEl.querySelector('.panel__viewport'); + + let topHeight, btmHeight, topContentHeight, btmContentHeight; + + // reset heights before measuring them + topViewportEl.style.height = 'auto'; + btmViewportEl.style.height = 'auto'; + + topHeight = topEl.clientHeight, + btmHeight = btmEl.clientHeight, + topContentHeight = topEl.querySelector('.panel__content').clientHeight, + btmContentHeight = btmEl.querySelector('.panel__content').clientHeight; + + if (isRightColumn && (topWidth + btmWidth < columnWidth)) { + if (topContentHeight + panelHeaderHeight > totalHeight) { + topViewportEl.style.height = totalHeight - panelHeaderHeight + 'px'; + } else { + topViewportEl.style.height = 'auto'; + } + if (btmContentHeight + panelHeaderHeight > totalHeight) { + btmViewportEl.style.height = totalHeight - panelHeaderHeight + 'px'; + } else { + btmViewportEl.style.height = 'auto'; + } + } else { + if (isTopVisible && isBtmVisible) { + let combinedHeight = topContentHeight + btmContentHeight + (panelHeaderHeight * 2); + if (combinedHeight > totalHeight) { + if (topContentHeight + panelHeaderHeight < totalHeight / 2) { + topViewportEl.style.height = prefsEl.topContentHeight + 'px'; + btmViewportEl.style.height = (totalHeight - topContentHeight - (panelHeaderHeight * 2)) + 'px'; + } else if (btmContentHeight + panelHeaderHeight < totalHeight / 2) { + topViewportEl.style.height = (totalHeight - btmContentHeight - (panelHeaderHeight * 2)) + 'px'; + btmViewportEl.style.height = remoteEl.topContentHeight + 'px'; + } else { + topViewportEl.style.height = ((totalHeight / 2) - panelHeaderHeight) + 'px'; + btmViewportEl.style.height = ((totalHeight / 2) - panelHeaderHeight) + 'px'; + } + } else { + topViewportEl.style.height = 'auto'; + btmViewportEl.style.height = 'auto'; + } + } else if (isTopVisible) { + if (topContentHeight + panelHeaderHeight > totalHeight) { + topViewportEl.style.height = totalHeight - panelHeaderHeight + 'px'; + } else { + topViewportEl.style.height = 'auto'; + } + } else if (isBtmVisible) { + if (btmContentHeight + panelHeaderHeight > totalHeight) { + btmViewportEl.style.height = totalHeight - panelHeaderHeight + 'px'; + } else { + btmViewportEl.style.height = 'auto'; + } + } + } +} + +/** + * Show the settings controls view for a processor. + * @param {String} id MIDI processor ID. + */ +function selectSettingsView(id) { + settingsViews.forEach(view => view.select(id)); +} + +/** + * Set up a new project, create th esetting views. + * @param {Object} state App state object. + */ +function setProject(state) { + let i = settingsViews.length; + while (--i >= 0) { + deleteSettingsView(settingsViews[i].getID()); + } + createSettingsViews(state); +} + +/** + * Set panels visibility. + * @param {Object} state App state. + */ +function showPanels(state) { + helpEl.dataset.show = state.showHelpPanel; + prefsEl.dataset.show = state.showPreferencesPanel; + remoteEl.dataset.show = state.learnModeActive; + editEl.dataset.show = state.showSettingsPanel; + libraryEl.dataset.show = state.showLibraryPanel; + renderLayout(); +} From 7422fc36dd9395a81d9ffc5eb9f059a6998cc3a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 10 Oct 2019 16:04:53 +0200 Subject: [PATCH 009/131] Canvas3d and connections3d modules refactored. --- src/js/core/processor-loader.js | 4 +- src/js/main.js | 5 +- src/js/webgl/canvas3d.js | 1379 +++++++++++++++++++++---------- src/js/webgl/connections3d.js | 805 +++++++++++------- 4 files changed, 1466 insertions(+), 727 deletions(-) diff --git a/src/js/core/processor-loader.js b/src/js/core/processor-loader.js index 860f8676..5bbd9ddb 100644 --- a/src/js/core/processor-loader.js +++ b/src/js/core/processor-loader.js @@ -72,8 +72,8 @@ export function getProcessorTypes() { } export function getProcessorData(name, type) { - if (performers[name] && performers[name][type]) { - return performers[name][type]; + if (processors[name] && processors[name][type]) { + return processors[name][type]; } } diff --git a/src/js/main.js b/src/js/main.js index 3079eb6a..6f2d78a7 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -19,11 +19,12 @@ import { dispatch, getActions, getState, persist, } from './state/store.js'; import createAppView from './view/app.js'; -import createCanvas3d from './webgl/canvas3d.js'; import createDialog from './view/dialog.js'; import createLibraryView from './view/library.js'; import { accessMidi } from './midi/midi.js'; import createMIDINetwork from './midi/network.js'; +import { setup as setupCanvas3d } from './webgl/canvas3d.js'; +import { setup as setupConnections3d } from './webgl/connections3d.js'; import { setup as setupControls } from './view/controls.js'; import { setup as setupPanels } from './view/panels.js'; import { preloadProcessors } from './core/processor-loader.js'; @@ -38,6 +39,8 @@ async function main() { setupControls(); setupPanels(); + setupCanvas3d(); + setupConnections3d(); persist(); } diff --git a/src/js/webgl/canvas3d.js b/src/js/webgl/canvas3d.js index 37c8b1f1..eeec6ae1 100644 --- a/src/js/webgl/canvas3d.js +++ b/src/js/webgl/canvas3d.js @@ -1,8 +1,9 @@ -import addWindowResize from '../view/windowresize.js'; -import addConnections3d from './connections3d.js'; +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; +import addWindowResizeCallback from '../view/windowresize.js'; +import { createConnection, dragEndConnection, dragMoveConnection, dragStartConnection, getCablesGroup, } from './connections3d.js'; import { setLineMaterialResolution } from './draw3dHelper.js'; import { getTheme } from '../state/selectors.js'; -import { eventType } from '../core/util.js'; +import { getProcessorData } from '../core/processor-loader.js' const { Color, @@ -15,477 +16,959 @@ const { WebGLRenderer } = THREE; -export default function createCanvas3d(specs, my) { - let that, - store = specs.store, - rootEl, - canvasRect, - renderer, - camera, - plane, - mousePoint = new Vector2(), - mousePointPrevious = new Vector2(), - intersection = new Vector3(), - raycaster = new Raycaster(), - dragObject, - dragObjectType, - dragOffset = new Vector3(), - allObjects = [], - controllers = [], - doubleClickCounter = 0, - doubleClickDelay = 300, - doubleClickTimer, - - init = function() { - - document.addEventListener(store.STATE_CHANGE, (e) => { - switch (e.detail.action.type) { - - case e.detail.actions.SELECT_PROCESSOR: - selectProcessorView(e.detail.state); - break; +const + doubleClickDelay = 300, + dragOffset = new Vector3(), + intersection = new Vector3(), + mousePoint = new Vector2(), + raycaster = new Raycaster(); + +let + allObjects = [], + camera, + canvasRect, + controllers = [], + doubleClickCounter = 0, + doubleClickTimer, + dragObject, + dragObjectType, + isConnectMode = false, + mousePointPrevious = new Vector2(), + plane, + renderer, + rootEl, + scene; + +/** + * Provide cables3d with the scene so it can add and remove cables. + */ +export function getScene() { + return scene; +} - case e.detail.actions.ADD_PROCESSOR: - createProcessorViews(e.detail.state); - break; - - case e.detail.actions.DELETE_PROCESSOR: - deleteProcessorView(e.detail.action.id); - selectProcessorView(e.detail.state); - break; - - case e.detail.actions.CREATE_PROJECT: - setThemeOnWorld(); - updateCamera(e.detail.state); - clearProcessorViews(); - createProcessorViews(e.detail.state); - onWindowResize(); - break; - - case e.detail.actions.RESCAN_TYPES: - case e.detail.actions.SET_THEME: - setThemeOnWorld(); - break; - - case e.detail.actions.SET_CAMERA_POSITION: - updateCamera(e.detail.state); - break; - - case e.detail.actions.LIBRARY_DROP: - onDrop(e.detail.state); - break; - } - }); - - my.addWindowResizeCallback(onWindowResize); - initWorld(); - initDOMEvents(); - onWindowResize(); - draw(); - }, - - /** - * Initialise DOM events for click, drag etcetera. - */ - initDOMEvents = function() { - renderer.domElement.addEventListener('touchend', onClick); - renderer.domElement.addEventListener('click', onClick); - renderer.domElement.addEventListener('touchstart', onTouchStart); - renderer.domElement.addEventListener('mousedown', onTouchStart); - renderer.domElement.addEventListener('touchmove', dragMove); - renderer.domElement.addEventListener('mousemove', dragMove); - renderer.domElement.addEventListener('touchend', dragEnd); - renderer.domElement.addEventListener('mouseup', dragEnd); - - // prevent system doubleclick to interfere with the custom doubleclick - renderer.domElement.addEventListener('dblclick', function(e) {e.preventDefault();}); - }, - - /** - * Window resize event handler. - */ - onWindowResize = function() { - canvasRect = renderer.domElement.getBoundingClientRect(); - renderer.setSize(window.innerWidth, window.innerHeight - canvasRect.top); - camera.aspect = window.innerWidth / (window.innerHeight - canvasRect.top); - camera.updateProjectionMatrix(); - canvasRect = renderer.domElement.getBoundingClientRect(); - - // move camera further back when viewport height increases so objects stay the same size - let scale = 0.15; - let fieldOfView = camera.fov * (Math.PI / 180); // convert fov to radians - let targetZ = canvasRect.height / (2 * Math.tan(fieldOfView / 2)); - - setLineMaterialResolution(); - - store.dispatch(store.getActions().setCameraPosition(camera.position.x, camera.position.y, targetZ * scale)); - }, - - /** - * Drop of object dragged from library. - * Create a new processor. - */ - onDrop = function(state) { - const { type, x, y, } = state.libraryDropPosition; - updateMouseRay({ clientX: x, clientY: y, }); - if (raycaster.ray.intersectPlane(plane, intersection)) { - store.dispatch(store.getActions().createProcessor({ - type, - positionX: intersection.x, - positionY: intersection.y, - positionZ: intersection.z, - })); - }; - }, - - /** - * Separate click and doubleclick. - * @see http://stackoverflow.com/questions/6330431/jquery-bind-double-click-and-single-click-separately - */ - onClick = function(e) { - // separate click from doubleclick - doubleClickCounter ++; - if (doubleClickCounter == 1) { - doubleClickTimer = setTimeout(function() { - doubleClickCounter = 0; - // implement single click behaviour here - handleClick(e); - }, doubleClickDelay); - } else { - clearTimeout(doubleClickTimer); - doubleClickCounter = 0; - // implement double click behaviour here - } - }, - - /** - * Select the object under the mouse. - * Start dragging the object. - */ - onTouchStart = function(e) { - // update picking ray - updateMouseRay(e); - mousePointPrevious = { ...mousePoint }; - - // get intersected object3ds - const intersects = raycaster.intersectObjects(allObjects, true); - let outerObject = null; - dragObjectType = 'background'; - if (intersects.length) { - - // test for processors - let intersect = intersects.find(intersect => intersect.object.name === 'hitarea'); - if (intersect) { - // get topmost parent of closest object - outerObject = getOuterParentObject(intersect.object); - // select the touched processor - store.dispatch(store.getActions().selectProcessor(outerObject.userData.id)); - dragObjectType = 'processor'; - } +export function setup() { + addWindowResizeCallback(onWindowResize); + createWorld(); + addEventListeners(); + onWindowResize(); + draw(); +} - // test for output connectors - intersect = intersects.find(intersect => intersect.object.name === 'output'); - if (intersect && my.isConnectMode) { - // get outer parent of closest object - outerObject = getOuterParentObject(intersect.object); - my.dragStartConnection( - outerObject.userData.id, - intersect.object.userData.id, - outerObject.clone().position.add(intersect.object.position)); - dragObjectType = 'connection'; - } - } +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); - if (dragObjectType === 'background') { - outerObject = camera; - } + renderer.domElement.addEventListener('touchend', onClick); + renderer.domElement.addEventListener('click', onClick); + renderer.domElement.addEventListener('touchstart', onTouchStart); + renderer.domElement.addEventListener('mousedown', onTouchStart); + renderer.domElement.addEventListener('touchmove', dragMove); + renderer.domElement.addEventListener('mousemove', dragMove); + renderer.domElement.addEventListener('touchend', dragEnd); + renderer.domElement.addEventListener('mouseup', dragEnd); - dragStart(outerObject, mousePoint); - }, - - /** - * Initialise object dragging. - * @param {object} object3d The Object3D to be dragged. - */ - dragStart = function(object3d, mousePoint) { - dragObject = object3d; - // update the picking ray with the camera and mouse position - raycaster.setFromCamera(mousePoint, camera); - // if ray intersects plane, store point in vector 'intersection' - if (raycaster.ray.intersectPlane(plane, intersection)) { - switch (dragObjectType) { + // prevent system doubleclick to interfere with the custom doubleclick + renderer.domElement.addEventListener('dblclick', function(e) {e.preventDefault();}); +} - case 'processor': - // offset is the intersection point minus object position, - // so distance from object to mouse - dragOffset.copy(intersection).sub(dragObject.position); - break; - - case 'connection': - break; +/** + * Remove all processor objects from the scene + * and delete all their controllers. + */ +function clearProcessorViews() { + + // remove all processor 3D objects + allObjects = allObjects.reduce((accumulator, object3D) => { + scene.remove(object3D); + return accumulator; + }, []); + + // remove all controllers + controllers = controllers.reduce((accumulator, controller) => { + controller.terminate(); + return accumulator; + }, []); +} + +/** + * Create canvas 2D object if it exists for the type. + * @param {Array} data Array of current processors' state. + */ +function createProcessorViews(state) { + const isConnectMode = state.connectModeActive; + for (let id of state.processors.allIds) { + const processorData = state.processors.byId[id]; + const { inputs, outputs, positionX, positionY, positionZ, type } = processorData; + const isExists = allObjects.find(obj3d => obj3d.userData.id === id); + if (!isExists) { + + // create the processor 3d object + const object3dModule = getProcessorData('object3d', type); + const object3d = object3dModule.createObject3d(id, inputs, outputs); + object3d.position.set(positionX, positionY, positionZ); + allObjects.push(object3d); + scene.add(object3d); + + // create controller for the object + const controllerModule = getProcessorData('object3dController', type); + const controller = controllerModule.createObject3dController({ object3d, processorData, store, isConnectMode, }); + controller.updateSelectCircle(store.getState().selectedID); + controllers.push(controller); + } + }; +} - case 'background': - dragOffset.copy(intersection).sub(dragObject.position); - break; - } - rootEl.style.cursor = 'move'; - } - }, +/** + * Set up the 3D world. + */ +function createWorld() { + renderer = new WebGLRenderer({antialias: true}); + renderer.setClearColor(new Color( getTheme().colorBackground || '#cccccc' )); + + rootEl = document.querySelector('#canvas-container'); + rootEl.appendChild(renderer.domElement); + + scene = new Scene(); + + camera = new PerspectiveCamera(45, 1, 1, 500); + scene.add(camera); + + plane = new Plane(); + plane.name = 'plane'; + plane.setFromNormalAndCoplanarPoint( + camera.getWorldDirection(plane.normal), + new Vector3(0,0,0)); +} + +/** + * Delete canvas 2D object when the processor is deleted. + * @param {Object} processor MIDI processor for which the 3D object will be a view. + */ +function deleteProcessorView(id) { + + // remove 3D object from allObjects + allObjects = allObjects.reduce((accumulator, object3D) => { + if (object3D.userData.id === id) { + + // remove 3D object from scene + scene.remove(object3D); + return accumulator; + } + return [...accumulator, object3D]; + }, []); + + // remove controller + controllers = controllers.reduce((accumulator, controller) => { + if (controller.getID() === id) { + controller.terminate(); + return accumulator; + } + return [...accumulator, controller]; + }, []); +} - /** - * Drag a 3D object. - * @param {Object} e Event. - */ - dragMove = function(e) { - e.preventDefault(); - - // update picking ray. - updateMouseRay(e); - switch (dragObjectType) { - case 'processor': - if (raycaster.ray.intersectPlane(plane, intersection)) { - // set position of dragObject to the mouse intersection minus the offset - const position = intersection.sub(dragOffset); - store.dispatch(store.getActions().dragSelectedProcessor(intersection.x, intersection.y, position.z)); - } - break; - - case 'background': - const x = (mousePointPrevious.x - mousePoint.x) * 50; - const y = (mousePointPrevious.y - mousePoint.y) * 50; - store.dispatch(store.getActions().setCameraPosition(x, y, 0, true)); - break; - - case 'connection': - if (raycaster.ray.intersectPlane(plane, intersection)) { - my.dragMoveConnection(intersection); - } - break; - - // when not dragging - default: - var intersects = raycaster.intersectObjects(allObjects, true); - if (intersects.length > 0) { - const intersectHitarea = intersects.find(intersect => intersect.object.name === 'hitarea'); - if (intersectHitarea) { - rootEl.style.cursor = 'pointer'; - } else { - rootEl.style.cursor = 'auto'; - } - } +/** + * Dragging 3D object ended. + * @param {Object} e Event. + */ +function dragEnd(e) { + e.preventDefault(); + updateMouseRay(e); + + switch (dragObjectType) { + case 'connection': + dragEndConnection(); + + // test for input connectors + const intersects = raycaster.intersectObjects(allObjects, true); + const intersect = intersects.find(intersect => intersect.object.name === 'input'); + if (intersect && isConnectMode) { + const outerObject = getOuterParentObject(intersect.object); + createConnection( + outerObject.userData.id, + intersect.object.userData.id); } - mousePointPrevious = { ...mousePoint }; - }, + break; + } + dragObject = null; + dragObjectType = null; + rootEl.style.cursor = 'auto'; +} - /** - * Dragging 3D object ended. - * @param {Object} e Event. - */ - dragEnd = function(e) { - e.preventDefault(); - updateMouseRay(e); - - switch (dragObjectType) { - case 'connection': - my.dragEndConnection(); - - // test for input connectors - const intersects = raycaster.intersectObjects(allObjects, true); - const intersect = intersects.find(intersect => intersect.object.name === 'input'); - if (intersect && my.isConnectMode) { - const outerObject = getOuterParentObject(intersect.object); - my.createConnection( - outerObject.userData.id, - intersect.object.userData.id); - } - break; +/** + * Drag a 3D object. + * @param {Object} e Event. + */ +function dragMove(e) { + e.preventDefault(); + + // update picking ray. + updateMouseRay(e); + switch (dragObjectType) { + case 'processor': + if (raycaster.ray.intersectPlane(plane, intersection)) { + // set position of dragObject to the mouse intersection minus the offset + const position = intersection.sub(dragOffset); + dispatch(getActions().dragSelectedProcessor(intersection.x, intersection.y, position.z)); + } + break; + + case 'background': + const x = (mousePointPrevious.x - mousePoint.x) * 50; + const y = (mousePointPrevious.y - mousePoint.y) * 50; + dispatch(getActions().setCameraPosition(x, y, 0, true)); + break; + + case 'connection': + if (raycaster.ray.intersectPlane(plane, intersection)) { + dragMoveConnection(intersection); } - dragObject = null; - dragObjectType = null; - rootEl.style.cursor = 'auto'; - }, - - /** - * Handle single mouse click. - */ - handleClick = function(e) { - if (my.cablesGroup) { - updateMouseRay(e); - - // look for click on connection cable delete button - const cableIntersects = raycaster.intersectObjects(my.cablesGroup.children, true); - const deleteIntersect = cableIntersects.find(intersect => intersect.object.name === 'delete'); - if (deleteIntersect) { - store.dispatch(store.getActions().disconnectProcessors(deleteIntersect.object.userData.connectionId)); + break; + + // when not dragging + default: + const intersects = raycaster.intersectObjects(allObjects, true); + if (intersects.length > 0) { + const intersectHitarea = intersects.find(intersect => intersect.object.name === 'hitarea'); + if (intersectHitarea) { + rootEl.style.cursor = 'pointer'; + } else { + rootEl.style.cursor = 'auto'; } } - }, + } + mousePointPrevious = { ...mousePoint }; +} + +/** + * Initialise object dragging. + * @param {object} object3d The Object3D to be dragged. + * @param {object} mousePoint Mouse location. + */ +function dragStart(object3d, mousePoint) { + dragObject = object3d; + // update the picking ray with the camera and mouse position + raycaster.setFromCamera(mousePoint, camera); + // if ray intersects plane, store point in vector 'intersection' + if (raycaster.ray.intersectPlane(plane, intersection)) { + switch (dragObjectType) { + + case 'processor': + // offset is the intersection point minus object position, + // so distance from object to mouse + dragOffset.copy(intersection).sub(dragObject.position); + break; + + case 'connection': + break; + + case 'background': + dragOffset.copy(intersection).sub(dragObject.position); + break; + } + rootEl.style.cursor = 'move'; + } +} + +/** + * Update any tween animations that are going on and redraw the canvases if needed. + * @param {Number} position Transport playback position in ticks. + * @param {Array} processorEvents Array of processor generated events to displayin the view. + */ +function draw(position, processorEvents) { + controllers.forEach(controller => controller.draw(position, processorEvents)); + renderer.render(scene, camera); +} + +/** + * Recursive function to get top level object of a group. + * @param {object} object3d An Three.js Object3D. + */ +function getOuterParentObject(object3d) { + if (object3d.object && object3d.object.parent && object3d.object.parent.type !== 'Scene') { + return getOuterParentObject(object3d.object.parent); + } else if (object3d.parent && object3d.parent.type !== 'Scene') { + return getOuterParentObject(object3d.parent); + } + if (object3d.object) { + return object3d.object; + } + return object3d; +} + +/** + * Handle single mouse click. + */ +function handleClick(e) { + const cablesGroup = getCablesGroup(); + if (cablesGroup) { + updateMouseRay(e); + + // look for click on connection cable delete button + const cableIntersects = raycaster.intersectObjects(cablesGroup.children, true); + const deleteIntersect = cableIntersects.find(intersect => intersect.object.name === 'delete'); + if (deleteIntersect) { + dispatch(getActions().disconnectProcessors(deleteIntersect.object.userData.connectionId)); + } + } +} + +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + + case actions.SELECT_PROCESSOR: + selectProcessorView(state); + break; + + case actions.ADD_PROCESSOR: + createProcessorViews(state); + break; + + case actions.DELETE_PROCESSOR: + deleteProcessorView(e.detail.action.id); + selectProcessorView(state); + break; + + case actions.CREATE_PROJECT: + setThemeOnWorld(); + updateCamera(state); + clearProcessorViews(); + createProcessorViews(state); + onWindowResize(); + break; + + case actions.RESCAN_TYPES: + case actions.SET_THEME: + setThemeOnWorld(); + break; + + case actions.SET_CAMERA_POSITION: + updateCamera(state); + break; + + case actions.LIBRARY_DROP: + onDrop(state); + break; + } +} - /** - * Set up the 3D world. - */ - initWorld = function() { +/** + * Separate click and doubleclick. + * @see http://stackoverflow.com/questions/6330431/jquery-bind-double-click-and-single-click-separately + */ +function onClick(e) { + doubleClickCounter++; + if (doubleClickCounter == 1) { + doubleClickTimer = setTimeout(function() { + doubleClickCounter = 0; + // implement single click behaviour here + handleClick(e); + }, doubleClickDelay); + } else { + clearTimeout(doubleClickTimer); + doubleClickCounter = 0; + // implement double click behaviour here + } +} + +/** + * Drop of object dragged from library. + * Create a new processor. + */ +function onDrop(state) { + const { type, x, y, } = state.libraryDropPosition; + updateMouseRay({ clientX: x, clientY: y, }); + if (raycaster.ray.intersectPlane(plane, intersection)) { + dispatch(getActions().createProcessor({ + type, + positionX: intersection.x, + positionY: intersection.y, + positionZ: intersection.z, + })); + }; +} + +/** + * Select the object under the mouse. + * Start dragging the object. + */ +function onTouchStart(e) { + + // update picking ray + updateMouseRay(e); + mousePointPrevious = { ...mousePoint }; + + // get intersected object3ds + const intersects = raycaster.intersectObjects(allObjects, true); + let outerObject = null; + dragObjectType = 'background'; + if (intersects.length) { + + // test for processors + let intersect = intersects.find(intersect => intersect.object.name === 'hitarea'); + if (intersect) { + // get topmost parent of closest object + outerObject = getOuterParentObject(intersect.object); + // select the touched processor + dispatch(getActions().selectProcessor(outerObject.userData.id)); + dragObjectType = 'processor'; + } + + // test for output connectors + intersect = intersects.find(intersect => intersect.object.name === 'output'); + if (intersect && isConnectMode) { + + // get outer parent of closest object + outerObject = getOuterParentObject(intersect.object); + dragStartConnection( + outerObject.userData.id, + intersect.object.userData.id, + outerObject.clone().position.add(intersect.object.position)); + dragObjectType = 'connection'; + } + } + + if (dragObjectType === 'background') { + outerObject = camera; + } + + dragStart(outerObject, mousePoint); +} - renderer = new WebGLRenderer({antialias: true}); - renderer.setClearColor(new Color( getTheme().colorBackground || '#cccccc' )); +/** + * Window resize event handler. + */ +function onWindowResize() { + canvasRect = renderer.domElement.getBoundingClientRect(); + renderer.setSize(window.innerWidth, window.innerHeight - canvasRect.top); + camera.aspect = window.innerWidth / (window.innerHeight - canvasRect.top); + camera.updateProjectionMatrix(); + canvasRect = renderer.domElement.getBoundingClientRect(); - rootEl = document.querySelector('#canvas-container'); - rootEl.appendChild(renderer.domElement); + // move camera further back when viewport height increases so objects stay the same size + const scale = 0.15; + const fieldOfView = camera.fov * (Math.PI / 180); // convert fov to radians + const targetZ = canvasRect.height / (2 * Math.tan(fieldOfView / 2)); - my.scene = new Scene(); + setLineMaterialResolution(); - camera = new PerspectiveCamera(45, 1, 1, 500); - my.scene.add(camera); + dispatch(getActions().setCameraPosition(camera.position.x, camera.position.y, targetZ * scale)); +} - plane = new Plane(); - plane.name = 'plane'; - plane.setFromNormalAndCoplanarPoint( - camera.getWorldDirection(plane.normal), - new Vector3(0,0,0)); - }, +/** + * Show the selected state of the processors. + */ +function selectProcessorView(state) { + const { selectedId } = state; + controllers.forEach(controller => controller.updateSelectCircle(selectedId)); +} - /** - * Update the camera position to what's stored in the state. - */ - updateCamera = function(state) { - camera.position.set(state.camera.x, state.camera.y, state.camera.z); - }, +/** + * Set the canvas background colour. + */ +function setThemeOnWorld() { + renderer.setClearColor(new Color(getTheme().colorBackground)); +} - setThemeOnWorld = function() { - renderer.setClearColor(new Color( getTheme().colorBackground )); - }, +/** + * Update the camera position to what's stored in the state. + */ +function updateCamera(state) { + camera.position.set(state.camera.x, state.camera.y, state.camera.z); +} + +/** + * Set a raycaster's ray to point from the camera to the mouse postion. + * @param {event} mouseEvent Event rom which to get the mouse coordinates. + */ +function updateMouseRay(e) { + const x = isNaN(e.clientX) ? e.changedTouches[0].clientX : e.clientX; + const y = isNaN(e.clientY) ? e.changedTouches[0].clientY : e.clientY; + + // update mouse vector with mouse coordinated translated to viewport + mousePoint.x = ((x - canvasRect.left) / canvasRect.width ) * 2 - 1; + mousePoint.y = - ((y - canvasRect.top) / canvasRect.height ) * 2 + 1; + + // update the picking ray with the camera and mouse position + raycaster.setFromCamera(mousePoint, camera); +} + + +// export default function createCanvas3d(specs, my) { +// let that, +// store = specs.store, +// rootEl, +// canvasRect, +// renderer, +// camera, +// plane, +// mousePoint = new Vector2(), +// mousePointPrevious = new Vector2(), +// intersection = new Vector3(), +// raycaster = new Raycaster(), +// dragObject, +// dragObjectType, +// dragOffset = new Vector3(), +// allObjects = [], +// controllers = [], +// doubleClickCounter = 0, +// doubleClickDelay = 300, +// doubleClickTimer, + +// init = function() { + +// document.addEventListener(store.STATE_CHANGE, (e) => { +// switch (e.detail.action.type) { + +// case actions.SELECT_PROCESSOR: +// selectProcessorView(state); +// break; + +// case actions.ADD_PROCESSOR: +// createProcessorViews(state); +// break; + +// case actions.DELETE_PROCESSOR: +// deleteProcessorView(e.detail.action.id); +// selectProcessorView(state); +// break; + +// case actions.CREATE_PROJECT: +// setThemeOnWorld(); +// updateCamera(state); +// clearProcessorViews(); +// createProcessorViews(state); +// onWindowResize(); +// break; + +// case actions.RESCAN_TYPES: +// case actions.SET_THEME: +// setThemeOnWorld(); +// break; + +// case actions.SET_CAMERA_POSITION: +// updateCamera(state); +// break; + +// case actions.LIBRARY_DROP: +// onDrop(state); +// break; +// } +// }); + +// my.addWindowResizeCallback(onWindowResize); +// initWorld(); +// initDOMEvents(); +// onWindowResize(); +// draw(); +// }, + +// /** +// * Initialise DOM events for click, drag etcetera. +// */ +// initDOMEvents = function() { +// renderer.domElement.addEventListener('touchend', onClick); +// renderer.domElement.addEventListener('click', onClick); +// renderer.domElement.addEventListener('touchstart', onTouchStart); +// renderer.domElement.addEventListener('mousedown', onTouchStart); +// renderer.domElement.addEventListener('touchmove', dragMove); +// renderer.domElement.addEventListener('mousemove', dragMove); +// renderer.domElement.addEventListener('touchend', dragEnd); +// renderer.domElement.addEventListener('mouseup', dragEnd); + +// // prevent system doubleclick to interfere with the custom doubleclick +// renderer.domElement.addEventListener('dblclick', function(e) {e.preventDefault();}); +// }, + +// /** +// * Window resize event handler. +// */ +// onWindowResize = function() { +// canvasRect = renderer.domElement.getBoundingClientRect(); +// renderer.setSize(window.innerWidth, window.innerHeight - canvasRect.top); +// camera.aspect = window.innerWidth / (window.innerHeight - canvasRect.top); +// camera.updateProjectionMatrix(); +// canvasRect = renderer.domElement.getBoundingClientRect(); + +// // move camera further back when viewport height increases so objects stay the same size +// let scale = 0.15; +// let fieldOfView = camera.fov * (Math.PI / 180); // convert fov to radians +// let targetZ = canvasRect.height / (2 * Math.tan(fieldOfView / 2)); + +// setLineMaterialResolution(); + +// store.dispatch(store.getActions().setCameraPosition(camera.position.x, camera.position.y, targetZ * scale)); +// }, + +// /** +// * Drop of object dragged from library. +// * Create a new processor. +// */ +// onDrop = function(state) { +// const { type, x, y, } = state.libraryDropPosition; +// updateMouseRay({ clientX: x, clientY: y, }); +// if (raycaster.ray.intersectPlane(plane, intersection)) { +// store.dispatch(store.getActions().createProcessor({ +// type, +// positionX: intersection.x, +// positionY: intersection.y, +// positionZ: intersection.z, +// })); +// }; +// }, + +// /** +// * Separate click and doubleclick. +// * @see http://stackoverflow.com/questions/6330431/jquery-bind-double-click-and-single-click-separately +// */ +// onClick = function(e) { +// // separate click from doubleclick +// doubleClickCounter ++; +// if (doubleClickCounter == 1) { +// doubleClickTimer = setTimeout(function() { +// doubleClickCounter = 0; +// // implement single click behaviour here +// handleClick(e); +// }, doubleClickDelay); +// } else { +// clearTimeout(doubleClickTimer); +// doubleClickCounter = 0; +// // implement double click behaviour here +// } +// }, + +// /** +// * Select the object under the mouse. +// * Start dragging the object. +// */ +// onTouchStart = function(e) { +// // update picking ray +// updateMouseRay(e); +// mousePointPrevious = { ...mousePoint }; + +// // get intersected object3ds +// const intersects = raycaster.intersectObjects(allObjects, true); +// let outerObject = null; +// dragObjectType = 'background'; +// if (intersects.length) { + +// // test for processors +// let intersect = intersects.find(intersect => intersect.object.name === 'hitarea'); +// if (intersect) { +// // get topmost parent of closest object +// outerObject = getOuterParentObject(intersect.object); +// // select the touched processor +// store.dispatch(store.getActions().selectProcessor(outerObject.userData.id)); +// dragObjectType = 'processor'; +// } + +// // test for output connectors +// intersect = intersects.find(intersect => intersect.object.name === 'output'); +// if (intersect && my.isConnectMode) { +// // get outer parent of closest object +// outerObject = getOuterParentObject(intersect.object); +// my.dragStartConnection( +// outerObject.userData.id, +// intersect.object.userData.id, +// outerObject.clone().position.add(intersect.object.position)); +// dragObjectType = 'connection'; +// } +// } + +// if (dragObjectType === 'background') { +// outerObject = camera; +// } + +// dragStart(outerObject, mousePoint); +// }, + +// /** +// * Initialise object dragging. +// * @param {object} object3d The Object3D to be dragged. +// */ +// dragStart = function(object3d, mousePoint) { +// dragObject = object3d; +// // update the picking ray with the camera and mouse position +// raycaster.setFromCamera(mousePoint, camera); +// // if ray intersects plane, store point in vector 'intersection' +// if (raycaster.ray.intersectPlane(plane, intersection)) { +// switch (dragObjectType) { + +// case 'processor': +// // offset is the intersection point minus object position, +// // so distance from object to mouse +// dragOffset.copy(intersection).sub(dragObject.position); +// break; + +// case 'connection': +// break; + +// case 'background': +// dragOffset.copy(intersection).sub(dragObject.position); +// break; +// } +// rootEl.style.cursor = 'move'; +// } +// }, + +// /** +// * Drag a 3D object. +// * @param {Object} e Event. +// */ +// dragMove = function(e) { +// e.preventDefault(); + +// // update picking ray. +// updateMouseRay(e); +// switch (dragObjectType) { +// case 'processor': +// if (raycaster.ray.intersectPlane(plane, intersection)) { +// // set position of dragObject to the mouse intersection minus the offset +// const position = intersection.sub(dragOffset); +// store.dispatch(store.getActions().dragSelectedProcessor(intersection.x, intersection.y, position.z)); +// } +// break; + +// case 'background': +// const x = (mousePointPrevious.x - mousePoint.x) * 50; +// const y = (mousePointPrevious.y - mousePoint.y) * 50; +// store.dispatch(store.getActions().setCameraPosition(x, y, 0, true)); +// break; + +// case 'connection': +// if (raycaster.ray.intersectPlane(plane, intersection)) { +// my.dragMoveConnection(intersection); +// } +// break; + +// // when not dragging +// default: +// var intersects = raycaster.intersectObjects(allObjects, true); +// if (intersects.length > 0) { +// const intersectHitarea = intersects.find(intersect => intersect.object.name === 'hitarea'); +// if (intersectHitarea) { +// rootEl.style.cursor = 'pointer'; +// } else { +// rootEl.style.cursor = 'auto'; +// } +// } +// } +// mousePointPrevious = { ...mousePoint }; +// }, - /** - * Set a raycaster's ray to point from the camera to the mouse postion. - * @param {event} mouseEvent Event rom which to get the mouse coordinates. - */ - updateMouseRay = function(e) { - const x = isNaN(e.clientX) ? e.changedTouches[0].clientX : e.clientX; - const y = isNaN(e.clientY) ? e.changedTouches[0].clientY : e.clientY; +// /** +// * Dragging 3D object ended. +// * @param {Object} e Event. +// */ +// dragEnd = function(e) { +// e.preventDefault(); +// updateMouseRay(e); + +// switch (dragObjectType) { +// case 'connection': +// my.dragEndConnection(); + +// // test for input connectors +// const intersects = raycaster.intersectObjects(allObjects, true); +// const intersect = intersects.find(intersect => intersect.object.name === 'input'); +// if (intersect && my.isConnectMode) { +// const outerObject = getOuterParentObject(intersect.object); +// my.createConnection( +// outerObject.userData.id, +// intersect.object.userData.id); +// } +// break; +// } +// dragObject = null; +// dragObjectType = null; +// rootEl.style.cursor = 'auto'; +// }, + +// /** +// * Handle single mouse click. +// */ +// handleClick = function(e) { +// if (my.cablesGroup) { +// updateMouseRay(e); + +// // look for click on connection cable delete button +// const cableIntersects = raycaster.intersectObjects(my.cablesGroup.children, true); +// const deleteIntersect = cableIntersects.find(intersect => intersect.object.name === 'delete'); +// if (deleteIntersect) { +// store.dispatch(store.getActions().disconnectProcessors(deleteIntersect.object.userData.connectionId)); +// } +// } +// }, + +// /** +// * Set up the 3D world. +// */ +// initWorld = function() { + +// renderer = new WebGLRenderer({antialias: true}); +// renderer.setClearColor(new Color( getTheme().colorBackground || '#cccccc' )); + +// rootEl = document.querySelector('#canvas-container'); +// rootEl.appendChild(renderer.domElement); + +// my.scene = new Scene(); + +// camera = new PerspectiveCamera(45, 1, 1, 500); +// my.scene.add(camera); + +// plane = new Plane(); +// plane.name = 'plane'; +// plane.setFromNormalAndCoplanarPoint( +// camera.getWorldDirection(plane.normal), +// new Vector3(0,0,0)); +// }, + +// /** +// * Update the camera position to what's stored in the state. +// */ +// updateCamera = function(state) { +// camera.position.set(state.camera.x, state.camera.y, state.camera.z); +// }, + +// setThemeOnWorld = function() { +// renderer.setClearColor(new Color( getTheme().colorBackground )); +// }, + +// /** +// * Set a raycaster's ray to point from the camera to the mouse postion. +// * @param {event} mouseEvent Event rom which to get the mouse coordinates. +// */ +// updateMouseRay = function(e) { +// const x = isNaN(e.clientX) ? e.changedTouches[0].clientX : e.clientX; +// const y = isNaN(e.clientY) ? e.changedTouches[0].clientY : e.clientY; - // update mouse vector with mouse coordinated translated to viewport - mousePoint.x = ((x - canvasRect.left) / canvasRect.width ) * 2 - 1; - mousePoint.y = - ((y - canvasRect.top) / canvasRect.height ) * 2 + 1; +// // update mouse vector with mouse coordinated translated to viewport +// mousePoint.x = ((x - canvasRect.left) / canvasRect.width ) * 2 - 1; +// mousePoint.y = - ((y - canvasRect.top) / canvasRect.height ) * 2 + 1; - // update the picking ray with the camera and mouse position - raycaster.setFromCamera(mousePoint, camera); - }, +// // update the picking ray with the camera and mouse position +// raycaster.setFromCamera(mousePoint, camera); +// }, - /** - * Recursive function to get top level object of a group. - * @param {object} object3d An Three.js Object3D. - */ - getOuterParentObject = function(object3d) { - if (object3d.object && object3d.object.parent && object3d.object.parent.type !== 'Scene') { - return getOuterParentObject(object3d.object.parent); - } else if (object3d.parent && object3d.parent.type !== 'Scene') { - return getOuterParentObject(object3d.parent); - } - if (object3d.object) { - return object3d.object; - } - return object3d; - }, +// /** +// * Recursive function to get top level object of a group. +// * @param {object} object3d An Three.js Object3D. +// */ +// getOuterParentObject = function(object3d) { +// if (object3d.object && object3d.object.parent && object3d.object.parent.type !== 'Scene') { +// return getOuterParentObject(object3d.object.parent); +// } else if (object3d.parent && object3d.parent.type !== 'Scene') { +// return getOuterParentObject(object3d.parent); +// } +// if (object3d.object) { +// return object3d.object; +// } +// return object3d; +// }, - /** - * Create canvas 2D object if it exists for the type. - * @param {Array} data Array of current processors' state. - */ - createProcessorViews = async (state) => { - const isConnectMode = state.connectModeActive; - // state.processors.allIds.forEach((id, i) => { - for (let id of state.processors.allIds) { - const processorData = state.processors.byId[id]; - const { inputs, outputs, positionX, positionY, positionZ, type } = processorData; - const isExists = allObjects.find(obj3d => obj3d.userData.id === id); - if (!isExists) { - - // create the processor 3d object - const object3dModule = await import(`../processors/${type}/object3d.js`); - const object3d = object3dModule.createObject3d(id, inputs, outputs); - object3d.position.set(positionX, positionY, positionZ); - allObjects.push(object3d); - my.scene.add(object3d); - - // create controller for the object - const controllerModule = await import(`../processors/${type}/object3dController.js`); - const controller = controllerModule.createObject3dController({ object3d, processorData, store, isConnectMode, }); - controller.updateSelectCircle(store.getState().selectedID); - controllers.push(controller); - } - }; - }, - - /** - * Show the selected state of the processors. - */ - selectProcessorView = function(state) { - controllers.forEach(controller => { - controller.updateSelectCircle(state.selectedID); - }); - }, - - /** - * Remove all processor objects from the scene - * and delete all their controllers. - */ - clearProcessorViews = function() { - // remove all processor 3D objects - allObjects = allObjects.reduce((accumulator, object3D) => { - my.scene.remove(object3D); - return accumulator; - }, []); - - // remove all controllers - controllers = controllers.reduce((accumulator, controller) => { - controller.terminate(); - return accumulator; - }, []); - }, +// /** +// * Create canvas 2D object if it exists for the type. +// * @param {Array} data Array of current processors' state. +// */ +// createProcessorViews = async (state) => { +// const isConnectMode = state.connectModeActive; +// // state.processors.allIds.forEach((id, i) => { +// for (let id of state.processors.allIds) { +// const processorData = state.processors.byId[id]; +// const { inputs, outputs, positionX, positionY, positionZ, type } = processorData; +// const isExists = allObjects.find(obj3d => obj3d.userData.id === id); +// if (!isExists) { + +// // create the processor 3d object +// const object3dModule = await import(`../processors/${type}/object3d.js`); +// const object3d = object3dModule.createObject3d(id, inputs, outputs); +// object3d.position.set(positionX, positionY, positionZ); +// allObjects.push(object3d); +// my.scene.add(object3d); + +// // create controller for the object +// const controllerModule = await import(`../processors/${type}/object3dController.js`); +// const controller = controllerModule.createObject3dController({ object3d, processorData, store, isConnectMode, }); +// controller.updateSelectCircle(store.getState().selectedID); +// controllers.push(controller); +// } +// }; +// }, + +// /** +// * Show the selected state of the processors. +// */ +// selectProcessorView = function(state) { +// controllers.forEach(controller => { +// controller.updateSelectCircle(state.selectedID); +// }); +// }, + +// /** +// * Remove all processor objects from the scene +// * and delete all their controllers. +// */ +// clearProcessorViews = function() { +// // remove all processor 3D objects +// allObjects = allObjects.reduce((accumulator, object3D) => { +// my.scene.remove(object3D); +// return accumulator; +// }, []); + +// // remove all controllers +// controllers = controllers.reduce((accumulator, controller) => { +// controller.terminate(); +// return accumulator; +// }, []); +// }, - /** - * Delete canvas 2D object when the processor is deleted. - * @param {Object} processor MIDI processor for which the 3D object will be a view. - */ - deleteProcessorView = function(id) { - // remove 3D object from allObjects - allObjects = allObjects.reduce((accumulator, object3D) => { - if (object3D.userData.id === id) { - // remove 3D object from scene - my.scene.remove(object3D); - return accumulator; - } - return [...accumulator, object3D]; - }, []); - - // remove controller - controllers = controllers.reduce((accumulator, controller) => { - if (controller.getID() === id) { - controller.terminate(); - return accumulator; - } - return [...accumulator, controller]; - }, []); - }, - - /** - * Update any tween animations that are going on and redraw the canvases if needed. - * @param {Number} position Transport playback position in ticks. - * @param {Array} processorEvents Array of processor generated events to displayin the view. - */ - draw = function(position, processorEvents) { - controllers.forEach(controller => controller.draw(position, processorEvents)); - renderer.render(my.scene, camera); - }; - - my = my || {}; - my.scene = null; +// /** +// * Delete canvas 2D object when the processor is deleted. +// * @param {Object} processor MIDI processor for which the 3D object will be a view. +// */ +// deleteProcessorView = function(id) { +// // remove 3D object from allObjects +// allObjects = allObjects.reduce((accumulator, object3D) => { +// if (object3D.userData.id === id) { +// // remove 3D object from scene +// my.scene.remove(object3D); +// return accumulator; +// } +// return [...accumulator, object3D]; +// }, []); + +// // remove controller +// controllers = controllers.reduce((accumulator, controller) => { +// if (controller.getID() === id) { +// controller.terminate(); +// return accumulator; +// } +// return [...accumulator, controller]; +// }, []); +// }, + +// /** +// * Update any tween animations that are going on and redraw the canvases if needed. +// * @param {Number} position Transport playback position in ticks. +// * @param {Array} processorEvents Array of processor generated events to displayin the view. +// */ +// draw = function(position, processorEvents) { +// controllers.forEach(controller => controller.draw(position, processorEvents)); +// renderer.render(my.scene, camera); +// }; + +// my = my || {}; +// my.scene = null; - that = addWindowResize(specs, my); - that = addConnections3d(specs, my); +// that = addWindowResize(specs, my); +// that = addConnections3d(specs, my); - init(); +// init(); - that.draw = draw; - return that; -} +// that.draw = draw; +// return that; +// } diff --git a/src/js/webgl/connections3d.js b/src/js/webgl/connections3d.js index 16e08498..b9236bb7 100644 --- a/src/js/webgl/connections3d.js +++ b/src/js/webgl/connections3d.js @@ -1,300 +1,553 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; import { getTheme } from '../state/selectors.js'; -import { - createCircleFilled, - createCircleOutline, - createShape, - redrawShape, -} from './draw3dHelper.js'; +import { createCircleFilled, createCircleOutline, createShape, } from './draw3dHelper.js'; +import { getScene } from './canvas3d.js'; const { - CubicBezierCurve, Group, Vector2, } = THREE; -export default function addConnections3d(specs, my) { - let that, - store = specs.store, - state = { - sourceProcessorID: null, - sourceConnectorID: null, - sourceConnectorPosition: null, - }, - lineMaterial, - currentCable, - currentCableDragHandle, - cablesGroup, - deleteButtonRadius = 2.0, - deleteCrossRadius = 0.8, - dragHandleRadius = 1.5, +const state = { + sourceProcessorID: null, + sourceConnectorID: null, + sourceConnectorPosition: null, + }, + deleteButtonRadius = 2.0, + deleteCrossRadius = 0.8, + dragHandleRadius = 1.5; + +let lineMaterial, + currentCable, + currentCableDragHandle, + cablesGroup; + +/** + * Create connection between two processors. + * @param {String} destinationProcessorID Processor ID. + * @param {String} destinationConnectorID Connector ID. + */ +export function createConnection(destinationProcessorID, destinationConnectorID) { + dispatch(getActions().connectProcessors({ + sourceProcessorID: state.sourceProcessorID, + sourceConnectorID: state.sourceConnectorID, + destinationProcessorID: destinationProcessorID, + destinationConnectorID: destinationConnectorID, + })); + state.sourceProcessorID = null; + state.sourceConnectorID = null; +} + +/** + * Drag connection cable ended. + */ +export function dragEndConnection() { + currentCable.geometry.dispose(); + cablesGroup.remove(currentCable); + cablesGroup.remove(currentCableDragHandle); +} + +/** + * Drag a connection cable. + * @param {Vector3} position3d + */ +export function dragMoveConnection(position3d) { + drawCable( + currentCable.name, + new Vector2(state.sourceConnectorPosition.x, state.sourceConnectorPosition.y), + new Vector2(position3d.x, position3d.y)); + currentCableDragHandle.position.copy(position3d); +} + +/** + * Start dragging a connection cable. + * @param {String} sourceProcessorID + * @param {String} sourceConnectorID + * @param {Vector3} sourceConnectorPosition + */ +export function dragStartConnection(sourceProcessorID, sourceConnectorID, sourceConnectorPosition) { + state = { ...state, sourceProcessorID, sourceConnectorID, sourceConnectorPosition, }; + currentCable = createShape(); + currentCable.name = 'currentCable'; + cablesGroup.add(currentCable); + + currentCableDragHandle.position.copy(sourceConnectorPosition); + cablesGroup.add(currentCableDragHandle); +} + +/** + * Provide Canvas3D with the cablesGroup for mouseclick intersection. + */ +export function getCablesGroup() { + return cablesGroup; +} + +export function setup() { + currentCableDragHandle = createCircleOutline(dragHandleRadius, getTheme().colorHigh); + currentCableDragHandle.name = 'dragHandle'; + + addEventListeners(); +} + +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); +} + +/** + * Draw all cables acctording to the state. + * @param {String} connectionId Connection ID. + * @return {Object} Cable object3d. + */ +function createCable(connectionId, isConnectMode) { + const { colorLow } = getTheme(); + + const cable = createShape(); + cable.name = connectionId; + cablesGroup.add(cable); + + const deleteBtn = createCircleFilled(deleteButtonRadius, colorLow, 0); + deleteBtn.name = 'delete'; + deleteBtn.userData.connectionId = connectionId; + deleteBtn.visible = isConnectMode; + cable.add(deleteBtn); + + const deleteBtnBorder = createCircleOutline(deleteButtonRadius, colorLow); + deleteBtnBorder.name = 'deleteBorder'; + deleteBtn.add(deleteBtnBorder); + + const points1 = [ + new Vector2(-deleteCrossRadius, -deleteCrossRadius), + new Vector2(deleteCrossRadius, deleteCrossRadius), + ]; + const line1 = createShape(points1, colorLow); + line1.name = 'deleteCross1'; + deleteBtn.add(line1); + + const points2 = [ + new Vector2(-deleteCrossRadius, deleteCrossRadius), + new Vector2(deleteCrossRadius, -deleteCrossRadius), + ]; + const line2 = createShape(points2, colorLow); + line2.name = 'deleteCross2'; + deleteBtn.add(line2); + + return cable; +} + +/** + * Draw all cables acctording to the state. + * @param {Object} state Application state. + */ +function drawCables(state) { + state.connections.allIds.forEach(connectionID => { + const connection = state.connections.byId[connectionID]; + const sourceProcessor = state.processors.byId[connection.sourceProcessorID]; + const destinationProcessor = state.processors.byId[connection.destinationProcessorID]; + + if (sourceProcessor && destinationProcessor) { + const sourceConnector = sourceProcessor.outputs.byId[connection.sourceConnectorID]; + const destinationConnector = destinationProcessor.inputs.byId[connection.destinationConnectorID]; + + drawCable( + connectionID, + new Vector2( + sourceProcessor.positionX + sourceConnector.x, + sourceProcessor.positionY + sourceConnector.y,), + new Vector2( + destinationProcessor.positionX + destinationConnector.x, + destinationProcessor.positionY + destinationConnector.y,)); + } + }); +} + +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.TOGGLE_CONNECT_MODE: + toggleConnectMode(state.connectModeActive); + break; + + case actions.DELETE_PROCESSOR: + case actions.CONNECT_PROCESSORS: + case actions.DISCONNECT_PROCESSORS: + updateCables(state); + drawCables(state); + break; + + case actions.DRAG_SELECTED_PROCESSOR: + case actions.DRAG_ALL_PROCESSORS: + drawCables(state); + break; + + case actions.CREATE_PROJECT: + updateTheme(); + updateCables(state); + drawCables(state); + toggleConnectMode(state.connectModeActive); + break; + + case actions.SET_THEME: + updateTheme(); + toggleConnectMode(state.connectModeActive); + break; + } +} + +/** + * Enter or leave application connect mode. + * @param {Boolean} isEnabled True to enable connect mode. + */ +function toggleConnectMode(isEnabled) { + + // toggle cable delete buttons + cablesGroup.children.forEach(cable => { + const deleteBtn = cable.getObjectByName('delete'); + deleteBtn.visible = isEnabled; + }); +} + +/** + * Create and delete cables acctording to the state. + * @param {Object} state Application state. + */ +function updateCables(state) { + if (!cablesGroup) { + cablesGroup = new Group(); + getScene().add(cablesGroup); + } + + // delete all removed cables + let count = cablesGroup.children.length; + while (--count >= 0) { + const cable = cablesGroup.children[count]; + if (state.connections.allIds.indexOf(cable.name) === -1) { + cablesGroup.remove(cable); + } + } + + // create all new cables + state.connections.allIds.forEach(connectionId => { + if (!cablesGroup.getObjectByName(connectionId)) { + createCable(connectionId, state.isConnectMode); + } + }); +} + +/** + * Update theme colors. + */ +function updateTheme() { + if (cablesGroup) { + const { colorLow, colorHigh } = getTheme(); + setThemeColorRecursively(cablesGroup, colorLow, colorHigh); + } +} + +/** + * Loop through all the object3d's children to set the color. + * @param {Object3d} object3d An Object3d of which to change the color. + * @param {String} colorLow Hex color string of the low contrast color. + * @param {String} colorHigh Hex color string of the high contrast color. + */ +function setThemeColorRecursively(object3d, colorLow, colorHigh) { + if (object3d.material && object3d.material.color) { + object3d.material.color.set(colorLow); + } + object3d.children.forEach(childObject3d => { + setThemeColorRecursively(childObject3d, colorLow, colorHigh); + }); +} + + + + +// export default function addConnections3d(specs, my) { +// let that, +// store = specs.store, +// state = { +// sourceProcessorID: null, +// sourceConnectorID: null, +// sourceConnectorPosition: null, +// }, +// lineMaterial, +// currentCable, +// currentCableDragHandle, +// cablesGroup, +// deleteButtonRadius = 2.0, +// deleteCrossRadius = 0.8, +// dragHandleRadius = 1.5, - init = function() { - currentCableDragHandle = createCircleOutline(dragHandleRadius, getTheme().colorHigh); - currentCableDragHandle.name = 'dragHandle'; +// init = function() { +// currentCableDragHandle = createCircleOutline(dragHandleRadius, getTheme().colorHigh); +// currentCableDragHandle.name = 'dragHandle'; - document.addEventListener(store.STATE_CHANGE, (e) => { - switch (e.detail.action.type) { +// document.addEventListener(store.STATE_CHANGE, (e) => { +// switch (action.type) { - case e.detail.actions.TOGGLE_CONNECT_MODE: - toggleConnectMode(e.detail.state.connectModeActive); - break; +// case actions.TOGGLE_CONNECT_MODE: +// toggleConnectMode(state.connectModeActive); +// break; - case e.detail.actions.DELETE_PROCESSOR: - case e.detail.actions.CONNECT_PROCESSORS: - case e.detail.actions.DISCONNECT_PROCESSORS: - updateCables(e.detail.state); - drawCables(e.detail.state); - break; +// case actions.DELETE_PROCESSOR: +// case actions.CONNECT_PROCESSORS: +// case actions.DISCONNECT_PROCESSORS: +// updateCables(state); +// drawCables(state); +// break; - case e.detail.actions.DRAG_SELECTED_PROCESSOR: - case e.detail.actions.DRAG_ALL_PROCESSORS: - drawCables(e.detail.state); - break; +// case actions.DRAG_SELECTED_PROCESSOR: +// case actions.DRAG_ALL_PROCESSORS: +// drawCables(state); +// break; - case e.detail.actions.CREATE_PROJECT: - updateTheme(); - updateCables(e.detail.state); - drawCables(e.detail.state); - toggleConnectMode(e.detail.state.connectModeActive); - break; - - case e.detail.actions.SET_THEME: - updateTheme(); - toggleConnectMode(e.detail.state.connectModeActive); - break; - } - }); - }, +// case actions.CREATE_PROJECT: +// updateTheme(); +// updateCables(state); +// drawCables(state); +// toggleConnectMode(state.connectModeActive); +// break; + +// case actions.SET_THEME: +// updateTheme(); +// toggleConnectMode(state.connectModeActive); +// break; +// } +// }); +// }, - /** - * Start dragging a connection cable. - * @param {String} sourceProcessorID - * @param {String} sourceConnectorID - * @param {Vector3} sourceConnectorPosition - */ - dragStartConnection = function(sourceProcessorID, sourceConnectorID, sourceConnectorPosition) { - state = { ...state, sourceProcessorID, sourceConnectorID, sourceConnectorPosition, }; - currentCable = createShape(); - currentCable.name = 'currentCable'; - cablesGroup.add(currentCable); - - currentCableDragHandle.position.copy(sourceConnectorPosition); - cablesGroup.add(currentCableDragHandle); - }, +// /** +// * Start dragging a connection cable. +// * @param {String} sourceProcessorID +// * @param {String} sourceConnectorID +// * @param {Vector3} sourceConnectorPosition +// */ +// dragStartConnection = function(sourceProcessorID, sourceConnectorID, sourceConnectorPosition) { +// state = { ...state, sourceProcessorID, sourceConnectorID, sourceConnectorPosition, }; +// currentCable = createShape(); +// currentCable.name = 'currentCable'; +// cablesGroup.add(currentCable); + +// currentCableDragHandle.position.copy(sourceConnectorPosition); +// cablesGroup.add(currentCableDragHandle); +// }, - /** - * Drag a connection cable. - * @param {Vector3} position3d - */ - dragMoveConnection = function(position3d) { - drawCable( - currentCable.name, - new Vector2(state.sourceConnectorPosition.x, state.sourceConnectorPosition.y), - new Vector2(position3d.x, position3d.y)); - currentCableDragHandle.position.copy(position3d); - }, +// /** +// * Drag a connection cable. +// * @param {Vector3} position3d +// */ +// dragMoveConnection = function(position3d) { +// drawCable( +// currentCable.name, +// new Vector2(state.sourceConnectorPosition.x, state.sourceConnectorPosition.y), +// new Vector2(position3d.x, position3d.y)); +// currentCableDragHandle.position.copy(position3d); +// }, - /** - * Drag connection cable ended. - */ - dragEndConnection = function() { - currentCable.geometry.dispose(); - cablesGroup.remove(currentCable); - cablesGroup.remove(currentCableDragHandle); - }, - - /** - * Create connection between two processors. - * @param {String} destinationProcessorID Processor ID. - * @param {String} destinationConnectorID Connector ID. - */ - createConnection = function(destinationProcessorID, destinationConnectorID) { - store.dispatch(store.getActions().connectProcessors({ - sourceProcessorID: state.sourceProcessorID, - sourceConnectorID: state.sourceConnectorID, - destinationProcessorID: destinationProcessorID, - destinationConnectorID: destinationConnectorID, - })); - state.sourceProcessorID = null; - state.sourceConnectorID = null; - }, - - /** - * Create and delete cables acctording to the state. - * @param {Object} state Application state. - */ - updateCables = function(state) { - if (!cablesGroup) { - cablesGroup = new Group(); - my.cablesGroup = cablesGroup; - my.scene.add(cablesGroup); - } - - // delete all removed cables - let count = cablesGroup.children.length; - while (--count >= 0) { - const cable = cablesGroup.children[count]; - if (state.connections.allIds.indexOf(cable.name) === -1) { - cablesGroup.remove(cable); - } - } - - // create all new cables - state.connections.allIds.forEach(connectionId => { - if (!cablesGroup.getObjectByName(connectionId)) { - createCable(connectionId); - } - }); - }, - - /** - * Draw all cables acctording to the state. - * @param {String} connectionId Connection ID. - * @return {Object} Cable object3d. - */ - createCable = function(connectionId) { - const { colorLow } = getTheme(); - - const cable = createShape(); - cable.name = connectionId; - cablesGroup.add(cable); - - const deleteBtn = createCircleFilled(deleteButtonRadius, colorLow, 0); - deleteBtn.name = 'delete'; - deleteBtn.userData.connectionId = connectionId; - deleteBtn.visible = my.isConnectMode; - cable.add(deleteBtn); - - const deleteBtnBorder = createCircleOutline(deleteButtonRadius, colorLow); - deleteBtnBorder.name = 'deleteBorder'; - deleteBtn.add(deleteBtnBorder); - - const points1 = [ - new Vector2(-deleteCrossRadius, -deleteCrossRadius), - new Vector2(deleteCrossRadius, deleteCrossRadius), - ]; - const line1 = createShape(points1, colorLow); - line1.name = 'deleteCross1'; - deleteBtn.add(line1); - - const points2 = [ - new Vector2(-deleteCrossRadius, deleteCrossRadius), - new Vector2(deleteCrossRadius, -deleteCrossRadius), - ]; - const line2 = createShape(points2, colorLow); - line2.name = 'deleteCross2'; - deleteBtn.add(line2); - - return cable; - }, - - /** - * Draw all cables acctording to the state. - * @param {Object} state Application state. - */ - drawCables = function(state) { - state.connections.allIds.forEach(connectionID => { - const connection = state.connections.byId[connectionID]; - const sourceProcessor = state.processors.byId[connection.sourceProcessorID]; - const destinationProcessor = state.processors.byId[connection.destinationProcessorID]; - - if (sourceProcessor && destinationProcessor) { - const sourceConnector = sourceProcessor.outputs.byId[connection.sourceConnectorID]; - const destinationConnector = destinationProcessor.inputs.byId[connection.destinationConnectorID]; +// /** +// * Drag connection cable ended. +// */ +// dragEndConnection = function() { +// currentCable.geometry.dispose(); +// cablesGroup.remove(currentCable); +// cablesGroup.remove(currentCableDragHandle); +// }, + +// /** +// * Create connection between two processors. +// * @param {String} destinationProcessorID Processor ID. +// * @param {String} destinationConnectorID Connector ID. +// */ +// createConnection = function(destinationProcessorID, destinationConnectorID) { +// store.dispatch(store.getActions().connectProcessors({ +// sourceProcessorID: state.sourceProcessorID, +// sourceConnectorID: state.sourceConnectorID, +// destinationProcessorID: destinationProcessorID, +// destinationConnectorID: destinationConnectorID, +// })); +// state.sourceProcessorID = null; +// state.sourceConnectorID = null; +// }, + +// /** +// * Create and delete cables acctording to the state. +// * @param {Object} state Application state. +// */ +// updateCables = function(state) { +// if (!cablesGroup) { +// cablesGroup = new Group(); +// my.cablesGroup = cablesGroup; +// my.scene.add(cablesGroup); +// } + +// // delete all removed cables +// let count = cablesGroup.children.length; +// while (--count >= 0) { +// const cable = cablesGroup.children[count]; +// if (state.connections.allIds.indexOf(cable.name) === -1) { +// cablesGroup.remove(cable); +// } +// } + +// // create all new cables +// state.connections.allIds.forEach(connectionId => { +// if (!cablesGroup.getObjectByName(connectionId)) { +// createCable(connectionId); +// } +// }); +// }, + +// /** +// * Draw all cables acctording to the state. +// * @param {String} connectionId Connection ID. +// * @return {Object} Cable object3d. +// */ +// createCable = function(connectionId) { +// const { colorLow } = getTheme(); + +// const cable = createShape(); +// cable.name = connectionId; +// cablesGroup.add(cable); + +// const deleteBtn = createCircleFilled(deleteButtonRadius, colorLow, 0); +// deleteBtn.name = 'delete'; +// deleteBtn.userData.connectionId = connectionId; +// deleteBtn.visible = my.isConnectMode; +// cable.add(deleteBtn); + +// const deleteBtnBorder = createCircleOutline(deleteButtonRadius, colorLow); +// deleteBtnBorder.name = 'deleteBorder'; +// deleteBtn.add(deleteBtnBorder); + +// const points1 = [ +// new Vector2(-deleteCrossRadius, -deleteCrossRadius), +// new Vector2(deleteCrossRadius, deleteCrossRadius), +// ]; +// const line1 = createShape(points1, colorLow); +// line1.name = 'deleteCross1'; +// deleteBtn.add(line1); + +// const points2 = [ +// new Vector2(-deleteCrossRadius, deleteCrossRadius), +// new Vector2(deleteCrossRadius, -deleteCrossRadius), +// ]; +// const line2 = createShape(points2, colorLow); +// line2.name = 'deleteCross2'; +// deleteBtn.add(line2); + +// return cable; +// }, + +// /** +// * Draw all cables acctording to the state. +// * @param {Object} state Application state. +// */ +// drawCables = function(state) { +// state.connections.allIds.forEach(connectionID => { +// const connection = state.connections.byId[connectionID]; +// const sourceProcessor = state.processors.byId[connection.sourceProcessorID]; +// const destinationProcessor = state.processors.byId[connection.destinationProcessorID]; + +// if (sourceProcessor && destinationProcessor) { +// const sourceConnector = sourceProcessor.outputs.byId[connection.sourceConnectorID]; +// const destinationConnector = destinationProcessor.inputs.byId[connection.destinationConnectorID]; - const cable = cablesGroup.getObjectByName(connectionID); - drawCable( - connectionID, - new Vector2( - sourceProcessor.positionX + sourceConnector.x, - sourceProcessor.positionY + sourceConnector.y,), - new Vector2( - destinationProcessor.positionX + destinationConnector.x, - destinationProcessor.positionY + destinationConnector.y,)); - } - }); - }, - - /** - * Enter or leave application connect mode. - * @param {Vector3} sourcePosition Cable start position. - * @param {Vector3} destinationPosition Cable end position. - */ - drawCable = function(connectionID, sourcePosition, destinationPosition) { - const cable = cablesGroup.getObjectByName(connectionID); - if (cable) { - const distance = sourcePosition.distanceTo(destinationPosition); - const curveStrength = Math.min(distance / 2, 30); - const curve = new CubicBezierCurve( - sourcePosition.clone(), - sourcePosition.clone().sub(new Vector2(0, curveStrength)), - destinationPosition.clone().add(new Vector2(0, curveStrength)), - destinationPosition.clone() - ); - const points = curve.getPoints(50); +// const cable = cablesGroup.getObjectByName(connectionID); +// drawCable( +// connectionID, +// new Vector2( +// sourceProcessor.positionX + sourceConnector.x, +// sourceProcessor.positionY + sourceConnector.y,), +// new Vector2( +// destinationProcessor.positionX + destinationConnector.x, +// destinationProcessor.positionY + destinationConnector.y,)); +// } +// }); +// }, + +// /** +// * Enter or leave application connect mode. +// * @param {Vector3} sourcePosition Cable start position. +// * @param {Vector3} destinationPosition Cable end position. +// */ +// drawCable = function(connectionID, sourcePosition, destinationPosition) { +// const cable = cablesGroup.getObjectByName(connectionID); +// if (cable) { +// const distance = sourcePosition.distanceTo(destinationPosition); +// const curveStrength = Math.min(distance / 2, 30); +// const curve = new CubicBezierCurve( +// sourcePosition.clone(), +// sourcePosition.clone().sub(new Vector2(0, curveStrength)), +// destinationPosition.clone().add(new Vector2(0, curveStrength)), +// destinationPosition.clone() +// ); +// const points = curve.getPoints(50); - redrawShape(cable, points, getTheme().colorLow); +// redrawShape(cable, points, getTheme().colorLow); - const deleteBtn = cable.getObjectByName('delete'); - if (deleteBtn) { +// const deleteBtn = cable.getObjectByName('delete'); +// if (deleteBtn) { - // get mid point on cable - const position = points[Math.floor(points.length / 2)]; - deleteBtn.position.set(position.x, position.y, 0); - } - } - }, - - /** - * Enter or leave application connect mode. - * @param {Boolean} isEnabled True to enable connect mode. - */ - toggleConnectMode = function(isEnabled) { - my.isConnectMode = isEnabled; - - // toggle cable delete buttons - cablesGroup.children.forEach(cable => { - const deleteBtn = cable.getObjectByName('delete'); - deleteBtn.visible = my.isConnectMode; - }); - }, - - /** - * Update theme colors. - */ - updateTheme = function() { - if (cablesGroup) { - const { colorLow, colorHigh } = getTheme(); - setThemeColorRecursively(cablesGroup, colorLow, colorHigh); - } - }, - - /** - * Loop through all the object3d's children to set the color. - * @param {Object3d} object3d An Object3d of which to change the color. - * @param {String} colorLow Hex color string of the low contrast color. - * @param {String} colorHigh Hex color string of the high contrast color. - */ - setThemeColorRecursively = function(object3d, colorLow, colorHigh) { - if (object3d.material && object3d.material.color) { - object3d.material.color.set(colorLow); - } - object3d.children.forEach(childObject3d => { - setThemeColorRecursively(childObject3d, colorLow, colorHigh); - }); - }; +// // get mid point on cable +// const position = points[Math.floor(points.length / 2)]; +// deleteBtn.position.set(position.x, position.y, 0); +// } +// } +// }, + +// /** +// * Enter or leave application connect mode. +// * @param {Boolean} isEnabled True to enable connect mode. +// */ +// toggleConnectMode = function(isEnabled) { +// my.isConnectMode = isEnabled; + +// // toggle cable delete buttons +// cablesGroup.children.forEach(cable => { +// const deleteBtn = cable.getObjectByName('delete'); +// deleteBtn.visible = my.isConnectMode; +// }); +// }, + +// /** +// * Update theme colors. +// */ +// updateTheme = function() { +// if (cablesGroup) { +// const { colorLow, colorHigh } = getTheme(); +// setThemeColorRecursively(cablesGroup, colorLow, colorHigh); +// } +// }, + +// /** +// * Loop through all the object3d's children to set the color. +// * @param {Object3d} object3d An Object3d of which to change the color. +// * @param {String} colorLow Hex color string of the low contrast color. +// * @param {String} colorHigh Hex color string of the high contrast color. +// */ +// setThemeColorRecursively = function(object3d, colorLow, colorHigh) { +// if (object3d.material && object3d.material.color) { +// object3d.material.color.set(colorLow); +// } +// object3d.children.forEach(childObject3d => { +// setThemeColorRecursively(childObject3d, colorLow, colorHigh); +// }); +// }; - my = my || {}; - my.isConnectMode = false, - my.cablesGroup = cablesGroup; - my.dragStartConnection = dragStartConnection; - my.dragMoveConnection = dragMoveConnection; - my.dragEndConnection = dragEndConnection; - my.createConnection = createConnection; +// my = my || {}; +// my.isConnectMode = false, +// my.cablesGroup = cablesGroup; +// my.dragStartConnection = dragStartConnection; +// my.dragMoveConnection = dragMoveConnection; +// my.dragEndConnection = dragEndConnection; +// my.createConnection = createConnection; - that = specs.that || {}; +// that = specs.that || {}; - init(); +// init(); - return that; -} +// return that; +// } From 3b8ae48a5f248e75793b6df1bff22fb2f1835fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 10 Oct 2019 16:05:09 +0200 Subject: [PATCH 010/131] Library module refactored. --- src/js/main.js | 3 +- src/js/view/library.js | 149 ++++++++++++++++++++--------------------- 2 files changed, 73 insertions(+), 79 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 6f2d78a7..fea6e8b4 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -20,12 +20,12 @@ import { dispatch, getActions, getState, persist, } from './state/store.js'; import createAppView from './view/app.js'; import createDialog from './view/dialog.js'; -import createLibraryView from './view/library.js'; import { accessMidi } from './midi/midi.js'; import createMIDINetwork from './midi/network.js'; import { setup as setupCanvas3d } from './webgl/canvas3d.js'; import { setup as setupConnections3d } from './webgl/connections3d.js'; import { setup as setupControls } from './view/controls.js'; +import { setup as setupLibrary } from './view/library.js'; import { setup as setupPanels } from './view/panels.js'; import { preloadProcessors } from './core/processor-loader.js'; import createPreferencesView from './view/preferences.js'; @@ -41,6 +41,7 @@ async function main() { setupPanels(); setupCanvas3d(); setupConnections3d(); + setupLibrary(); persist(); } diff --git a/src/js/view/library.js b/src/js/view/library.js index 8d899a53..cc1eff37 100644 --- a/src/js/view/library.js +++ b/src/js/view/library.js @@ -1,89 +1,82 @@ -/** - * Library for all processor types. - */ -export default function createLibraryView(specs, my) { - var that, - store = specs.store, - listEl = document.querySelector('.library__list'), - dragType = null, - dragEl = null, +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; +import { getProcessorData, getProcessorTypes, } from '../core/processor-loader.js'; - init = function() { - document.addEventListener(store.STATE_CHANGE, (e) => { - switch (e.detail.action.type) { - case e.detail.actions.RESCAN_TYPES: - populateLibrary(e.detail.state.types); - break; - } - }); - }, - - /** - * Populate the library with all available processor types. - * Processor types are not shown in the libray - * if they have the flag excludedFromLibrary = true - * in their config.json file. - */ - populateLibrary = function(typesTable) { - const template = document.querySelector('#template-library-item'); +const listEl = document.querySelector('.library__list'); - typesTable.allIds.forEach(id => { - const type = typesTable.byId[id]; - const clone = template.content.cloneNode(true); - const el = clone.firstElementChild; - listEl.appendChild(el); +let dragEl, dragType; - el.querySelector('.library__item-label').innerHTML = type.name; - el.dataset.type = id; - el.addEventListener('touchstart', onTouchStart); - el.addEventListener('mousedown', onTouchStart); - }); +export function setup() { + populateLibrary(); - const draggerTemplate = document.querySelector('#template-library-dragger'); - dragEl = draggerTemplate.content.cloneNode(true).firstElementChild; - }, + const draggerTemplate = document.querySelector('#template-library-dragger'); + dragEl = draggerTemplate.content.cloneNode(true).firstElementChild; +} - onTouchStart = e => { - e.preventDefault(); - const el = e.currentTarget; - dragType = el.dataset.type; - document.addEventListener('touchmove', onTouchMove); - document.addEventListener('mousemove', onTouchMove); - document.addEventListener('touchend', onTouchEnd); - document.addEventListener('mouseup', onTouchEnd); +function onTouchEnd(e) { + e.preventDefault(); + document.removeEventListener('touchmove', onTouchMove); + document.removeEventListener('mousemove', onTouchMove); + document.removeEventListener('touchend', onTouchEnd); + document.removeEventListener('mouseup', onTouchEnd); + document.body.removeChild(dragEl); + const x = e.type === 'mouseup' ? e.clientX : e.changedTouches[0].clientX; + const y = e.type === 'mouseup' ? e.clientY : e.changedTouches[0].clientY; + dispatch(getActions().libraryDrop(dragType, x, y)); + dragType = null; +} - dragEl.querySelector('.library__dragger-label').textContent = el.textContent; - document.body.appendChild(dragEl); - setDragElPosition(e); - }, +function onTouchMove(e) { + setDragElPosition(e); +} + +function onTouchStart(e) { + e.preventDefault(); + const el = e.currentTarget; + dragType = el.dataset.type; + document.addEventListener('touchmove', onTouchMove); + document.addEventListener('mousemove', onTouchMove); + document.addEventListener('touchend', onTouchEnd); + document.addEventListener('mouseup', onTouchEnd); - onTouchMove = e => { - setDragElPosition(e); - }, + dragEl.querySelector('.library__dragger-label').textContent = el.textContent; + document.body.appendChild(dragEl); + setDragElPosition(e); +} - onTouchEnd = e => { - e.preventDefault(); - document.removeEventListener('touchmove', onTouchMove); - document.removeEventListener('mousemove', onTouchMove); - document.removeEventListener('touchend', onTouchEnd); - document.removeEventListener('mouseup', onTouchEnd); - document.body.removeChild(dragEl); - const el = e.type === 'mouseup' ? e.currentTarget : e.changedTouches[0].target.parentNode; - const x = e.type === 'mouseup' ? e.clientX : e.changedTouches[0].clientX; - const y = e.type === 'mouseup' ? e.clientY : e.changedTouches[0].clientY; - store.dispatch(store.getActions().libraryDrop(dragType, x, y)); - dragType = null; - }, +setDragElPosition = e => { + const x = e.type.indexOf('mouse') !== -1 ? e.clientX : e.touches[0].clientX; + const y = e.type.indexOf('mouse') !== -1 ? e.clientY : e.touches[0].clientY; + dragEl.setAttribute('style', `left: ${x - (dragEl.offsetWidth * 0.5)}px; top: ${y - (dragEl.offsetHeight * 0.9)}px;`); +} - setDragElPosition = e => { - const x = e.type.indexOf('mouse') !== -1 ? e.clientX : e.touches[0].clientX; - const y = e.type.indexOf('mouse') !== -1 ? e.clientY : e.touches[0].clientY; - dragEl.setAttribute('style', `left: ${x - (dragEl.offsetWidth * 0.5)}px; top: ${y - (dragEl.offsetHeight * 0.9)}px;`); - }; - - that = specs.that || {}; +/** + * Populate the library with all available processor types. + * Processor types are not shown in the libray + * if they have the flag excludedFromLibrary = true + * in their config.json file. + */ +function populateLibrary() { + const template = document.querySelector('#template-library-item'); + + getProcessorTypes().forEach(type => { + const clone = template.content.cloneNode(true); + const el = clone.firstElementChild; + listEl.appendChild(el); + + const config = getProcessorData(type, 'config'); - init(); - - return that; + el.querySelector('.library__item-label').innerHTML = config.name; + el.addEventListener('touchstart', onTouchStart); + el.addEventListener('mousedown', onTouchStart); + }); +} + +/** + * set the dragged element position. + * @param {Object} e + */ +function setDragElPosition(e) { + const x = e.type.indexOf('mouse') !== -1 ? e.clientX : e.touches[0].clientX; + const y = e.type.indexOf('mouse') !== -1 ? e.clientY : e.touches[0].clientY; + dragEl.setAttribute('style', `left: ${x - (dragEl.offsetWidth * 0.5)}px; top: ${y - (dragEl.offsetHeight * 0.9)}px;`); } From 760f456ac2d08cd5ab9dfae0ccaa789ff2969f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 10 Oct 2019 17:24:02 +0200 Subject: [PATCH 011/131] Settings panel refactored. --- src/js/view/setting/base.js | 138 ++++++++++++------------ src/js/view/setting/boolean.js | 96 ++++++++--------- src/js/view/setting/integer.js | 85 +++++++-------- src/js/view/setting/itemized.js | 142 ++++++++++++------------ src/js/view/setting/remote.js | 185 ++++++++++++++++---------------- src/js/view/setting/string.js | 74 ++++++------- src/js/view/settings.js | 45 ++++---- 7 files changed, 387 insertions(+), 378 deletions(-) diff --git a/src/js/view/setting/base.js b/src/js/view/setting/base.js index 2315d26e..90320cc7 100644 --- a/src/js/view/setting/base.js +++ b/src/js/view/setting/base.js @@ -1,3 +1,4 @@ +import { STATE_CHANGE, } from '../../state/store.js'; import createRemoteSettingView from './remote.js'; /** @@ -5,73 +6,78 @@ import createRemoteSettingView from './remote.js'; * which has a slider and a number field. */ export default function createBaseSettingView(specs, my) { - var that, - - initialise = function() { - // find template, add clone to settings panel - let template = document.querySelector('#template-setting-' + my.data.type); - let clone = template.content.cloneNode(true); - my.el = clone.firstElementChild; - specs.parentEl.appendChild(my.el); - - // show label - my.el.querySelector('.setting__label').innerHTML = my.data.label; + let that, + + initialise = function() { + + // find template, add clone to settings panel + let template = document.querySelector('#template-setting-' + my.data.type); + let clone = template.content.cloneNode(true); + my.el = clone.firstElementChild; + specs.parentEl.appendChild(my.el); + + // show label + my.el.querySelector('.setting__label').innerHTML = my.data.label; - if (my.data.isMidiControllable) { - my.changeRemoteState(specs.store.getState()); - } + if (my.data.isMidiControllable) { + my.changeRemoteState(); + } - document.addEventListener(my.store.STATE_CHANGE, handleStateChanges); - }, - - terminate = function() { - document.removeEventListener(my.store.STATE_CHANGE, handleStateChanges); - }, - - handleStateChanges = function(e) { - switch (e.detail.action.type) { - case e.detail.actions.CHANGE_PARAMETER: - if (e.detail.action.processorID === my.processorID && - e.detail.action.paramKey === my.key) { - my.setValue(e.detail.state.processors.byId[my.processorID].params.byId[my.key].value); - } - break; - - case e.detail.actions.RECREATE_PARAMETER: - if (e.detail.action.processorID === my.processorID && - e.detail.action.paramKey === my.key) { - my.data = e.detail.state.processors.byId[my.processorID].params.byId[my.key]; - my.initData(); - my.setValue(e.detail.state.processors.byId[my.processorID].params.byId[my.key].value); - } - break; - - case e.detail.actions.TOGGLE_MIDI_LEARN_MODE: - case e.detail.actions.TOGGLE_MIDI_LEARN_TARGET: - case e.detail.actions.SELECT_PROCESSOR: - case e.detail.actions.DELETE_PROCESSOR: - case e.detail.actions.ASSIGN_EXTERNAL_CONTROL: - case e.detail.actions.UNASSIGN_EXTERNAL_CONTROL: - if (my.data.isMidiControllable) { - my.changeRemoteState(e.detail.state); - } - break; - } - }; - - my = my || {}; - my.store = specs.store; - my.key = specs.key; - my.data = specs.data; - my.processorID = specs.processorID; - my.el; - - that = that || {}; - if (my.data.isMidiControllable) { - that = createRemoteSettingView(specs, my); - } - - initialise(); + document.addEventListener(STATE_CHANGE, handleStateChanges); + }, + + terminate = function() { + document.removeEventListener(STATE_CHANGE, handleStateChanges); + }, - return that; + /** + * Handle state changes. + * @param {Object} e + */ + handleStateChanges = function(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.CHANGE_PARAMETER: + if (action.processorID === my.processorID && + action.paramKey === my.key) { + my.setValue(state.processors.byId[my.processorID].params.byId[my.key].value); + } + break; + + case actions.RECREATE_PARAMETER: + if (action.processorID === my.processorID && + action.paramKey === my.key) { + my.data = state.processors.byId[my.processorID].params.byId[my.key]; + my.initData(); + my.setValue(state.processors.byId[my.processorID].params.byId[my.key].value); + } + break; + + case actions.TOGGLE_MIDI_LEARN_MODE: + case actions.TOGGLE_MIDI_LEARN_TARGET: + case actions.SELECT_PROCESSOR: + case actions.DELETE_PROCESSOR: + case actions.ASSIGN_EXTERNAL_CONTROL: + case actions.UNASSIGN_EXTERNAL_CONTROL: + if (my.data.isMidiControllable) { + my.changeRemoteState(state); + } + break; + } + }; + + my = my || {}; + my.key = specs.key; + my.data = specs.data; + my.processorID = specs.processorID; + my.el; + + that = that || {}; + if (my.data.isMidiControllable) { + that = createRemoteSettingView(specs, my); + } + + initialise(); + + return that; } diff --git a/src/js/view/setting/boolean.js b/src/js/view/setting/boolean.js index eed51499..6fc2ecb9 100644 --- a/src/js/view/setting/boolean.js +++ b/src/js/view/setting/boolean.js @@ -1,3 +1,4 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import createBaseSettingView from './base.js'; /** @@ -5,53 +6,52 @@ import createBaseSettingView from './base.js'; * which has a checkbox input. */ export default function createBooleanSettingView(specs, my) { - var that, - checkEl, - - init = function() { - let id = getTemporaryInputAndLabelId(); - - checkEl = my.el.querySelector('.setting__check'); - checkEl.value = my.data.default; - checkEl.setAttribute('id', id); - checkEl.addEventListener('change', onChange); - - let labelEl = my.el.querySelector('.toggle__label'); - labelEl.setAttribute('for', id); - - initData(); - setValue(my.data.value); - }, + var that, + checkEl, + + init = function() { + let id = getTemporaryInputAndLabelId(); + + checkEl = my.el.querySelector('.setting__check'); + checkEl.value = my.data.default; + checkEl.setAttribute('id', id); + checkEl.addEventListener('change', onChange); + + let labelEl = my.el.querySelector('.toggle__label'); + labelEl.setAttribute('for', id); + + initData(); + setValue(my.data.value); + }, - initData = function() { - - }, - - /** - * A quick ID to tie label to input elements. - * @return {Number} Unique ID. - */ - getTemporaryInputAndLabelId = function() { - return 'id' + Math.random() + performance.now(); - }, - - onChange = function(e) { - my.store.dispatch(my.store.getActions().changeParameter( - my.processorID, - my.key, - e.target.checked)); - }, - - setValue = function(value) { - checkEl.checked = value; - }; - - my = my || {}; - my.setValue = setValue; - - that = createBaseSettingView(specs, my); - - init(); - - return that; + initData = function() { + }, + + /** + * A quick ID to tie label to input elements. + * @return {Number} Unique ID. + */ + getTemporaryInputAndLabelId = function() { + return 'id' + Math.random() + performance.now(); + }, + + onChange = function(e) { + dispatch(getActions().changeParameter( + my.processorID, + my.key, + e.target.checked)); + }, + + setValue = function(value) { + checkEl.checked = value; + }; + + my = my || {}; + my.setValue = setValue; + + that = createBaseSettingView(specs, my); + + init(); + + return that; } diff --git a/src/js/view/setting/integer.js b/src/js/view/setting/integer.js index 0b4052ee..0baba94c 100644 --- a/src/js/view/setting/integer.js +++ b/src/js/view/setting/integer.js @@ -1,3 +1,4 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import createBaseSettingView from './base.js'; /** @@ -5,49 +6,49 @@ import createBaseSettingView from './base.js'; * which has a slider and a number field. */ export default function createIntegerSettingView(specs, my) { - var that, - rangeEl, - numberEl, - - init = function() { - rangeEl = my.el.getElementsByClassName('setting__range')[0]; - rangeEl.addEventListener('input', onChange); - rangeEl.addEventListener('change', onChange); - - numberEl = my.el.getElementsByClassName('setting__number')[0]; - numberEl.addEventListener('change', onChange); + let that, + rangeEl, + numberEl, + + init = function() { + rangeEl = my.el.getElementsByClassName('setting__range')[0]; + rangeEl.addEventListener('input', onChange); + rangeEl.addEventListener('change', onChange); + + numberEl = my.el.getElementsByClassName('setting__number')[0]; + numberEl.addEventListener('change', onChange); - initData(); - setValue(my.data.value); - }, + initData(); + setValue(my.data.value); + }, - initData = function() { - rangeEl.setAttribute('min', my.data.min); - rangeEl.setAttribute('max', my.data.max); + initData = function() { + rangeEl.setAttribute('min', my.data.min); + rangeEl.setAttribute('max', my.data.max); - numberEl.setAttribute('min', my.data.min); - numberEl.setAttribute('max', my.data.max); - }, - - onChange = function(e) { - my.store.dispatch(my.store.getActions().changeParameter( - my.processorID, - my.key, - parseInt(e.target.value, 10))); - }, - - setValue = function(value) { - rangeEl.value = value; - numberEl.value = value; - }; - - my = my || {}; - my.initData = initData; - my.setValue = setValue; - - that = createBaseSettingView(specs, my); - - init(); - - return that; + numberEl.setAttribute('min', my.data.min); + numberEl.setAttribute('max', my.data.max); + }, + + onChange = function(e) { + dispatch(getActions().changeParameter( + my.processorID, + my.key, + parseInt(e.target.value, 10))); + }, + + setValue = function(value) { + rangeEl.value = value; + numberEl.value = value; + }; + + my = my || {}; + my.initData = initData; + my.setValue = setValue; + + that = createBaseSettingView(specs, my); + + init(); + + return that; } diff --git a/src/js/view/setting/itemized.js b/src/js/view/setting/itemized.js index 1aa933fa..0236c883 100644 --- a/src/js/view/setting/itemized.js +++ b/src/js/view/setting/itemized.js @@ -1,3 +1,4 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import createBaseSettingView from './base.js'; /** @@ -5,77 +6,78 @@ import createBaseSettingView from './base.js'; * which has a radio buttons for item selection. */ export default function createItemizedSettingView(specs, my) { - var that, - valueEl, - radioInputs = [], - numInputs, - - init = function() { - valueEl = my.el.querySelector('.setting__value'); + var that, + valueEl, + radioInputs = [], + numInputs, + + init = function() { + valueEl = my.el.querySelector('.setting__value'); - initData(); - setValue(my.data.value); - }, + initData(); + setValue(my.data.value); + }, - initData = function() { - // remove previous radio buttons, if any - while (valueEl.firstChild) { - valueEl.firstChild.removeEventListener('change', onChange); - valueEl.removeChild(valueEl.firstChild); - } - - // add the radio buttons - let radioTemplate = document.querySelector('#template-setting-itemized-item'), - model = my.data.model; - numInputs = model.length; - for (var i = 0; i < numInputs; i++) { - let id = getTemporaryInputAndLabelId(); - - // add a new cloned radio element - let radioInputEl = radioTemplate.content.children[0].cloneNode(true); - valueEl.appendChild(radioInputEl); - radioInputEl.setAttribute('name', specs.key); - radioInputEl.setAttribute('id', id); - radioInputEl.value = model[i].value; - radioInputEl.addEventListener('change', onChange); - radioInputs.push(radioInputEl); - - // add a new cloned label element - let radioLabelEl = radioTemplate.content.children[1].cloneNode(true); - valueEl.appendChild(radioLabelEl); - radioLabelEl.setAttribute('for', id); - radioLabelEl.innerHTML = model[i].label; - } - }, - - /** - * A quick ID to tie label to input elements. - * @return {Number} Unique ID. - */ - getTemporaryInputAndLabelId = function() { - return 'id' + Math.random() + performance.now(); - }, - - onChange = function(e) { - my.store.dispatch(my.store.getActions().changeParameter( - my.processorID, - my.key, - e.target.value)); - }, + initData = function() { - setValue = function(value) { - radioInputs.forEach(radioInput => { - radioInput.checked = (radioInput.value == value); - }); - }; - - my = my || {}; - my.initData = initData; - my.setValue = setValue; - - that = createBaseSettingView(specs, my); - - init(); - - return that; + // remove previous radio buttons, if any + while (valueEl.firstChild) { + valueEl.firstChild.removeEventListener('change', onChange); + valueEl.removeChild(valueEl.firstChild); + } + + // add the radio buttons + let radioTemplate = document.querySelector('#template-setting-itemized-item'), + model = my.data.model; + numInputs = model.length; + for (var i = 0; i < numInputs; i++) { + let id = getTemporaryInputAndLabelId(); + + // add a new cloned radio element + let radioInputEl = radioTemplate.content.children[0].cloneNode(true); + valueEl.appendChild(radioInputEl); + radioInputEl.setAttribute('name', specs.key); + radioInputEl.setAttribute('id', id); + radioInputEl.value = model[i].value; + radioInputEl.addEventListener('change', onChange); + radioInputs.push(radioInputEl); + + // add a new cloned label element + let radioLabelEl = radioTemplate.content.children[1].cloneNode(true); + valueEl.appendChild(radioLabelEl); + radioLabelEl.setAttribute('for', id); + radioLabelEl.innerHTML = model[i].label; + } + }, + + /** + * A quick ID to tie label to input elements. + * @return {Number} Unique ID. + */ + getTemporaryInputAndLabelId = function() { + return 'id' + Math.random() + performance.now(); + }, + + onChange = function(e) { + dispatch(getActions().changeParameter( + my.processorID, + my.key, + e.target.value)); + }, + + setValue = function(value) { + radioInputs.forEach(radioInput => { + radioInput.checked = (radioInput.value == value); + }); + }; + + my = my || {}; + my.initData = initData; + my.setValue = setValue; + + that = createBaseSettingView(specs, my); + + init(); + + return that; } diff --git a/src/js/view/setting/remote.js b/src/js/view/setting/remote.js index 242d8c48..9b55c9bf 100644 --- a/src/js/view/setting/remote.js +++ b/src/js/view/setting/remote.js @@ -1,98 +1,101 @@ +import { dispatch, getActions, getState, STATE_CHANGE, } from '../../state/store.js'; + /** * Processor setting overlay for assinging MIDI control to the parameter. */ export default function createRemoteSettingView(specs, my) { - var that, - learnClickLayer, - - init = function() { - if (my.data.isMidiControllable) { - let template = document.querySelector('#template-setting-learnmode'); - let clone = template.content.cloneNode(true); - learnClickLayer = clone.firstElementChild; - } - }, - - /** - * State of the parameter in the assignment process changed, - * the element will show this visually. - * @param {String} state New state of the parameter. - */ - changeRemoteState = function(state) { - if (my.data.isMidiControllable) { - if (state.learnModeActive) { - showRemoteState('enter'); + let that, + learnClickLayer, + + init = function() { + if (my.data.isMidiControllable) { + const template = document.querySelector('#template-setting-learnmode'); + const clone = template.content.cloneNode(true); + learnClickLayer = clone.firstElementChild; + } + }, + + /** + * State of the parameter in the assignment process changed, + * the element will show this visually. + * @param {String} state New state of the parameter. + */ + changeRemoteState = function() { + const { assignments, learnModeActive, learnTargetParameterKey, learnTargetProcessorID, } = getState(); + if (my.data.isMidiControllable) { + if (learnModeActive) { + showRemoteState('enter'); - // search for assignment - let assignment; - state.assignments.allIds.forEach(assignID => { - const assign = state.assignments.byId[assignID]; - if (assign.processorID === my.processorID && assign.paramKey === my.key) { - assignment = assign; - } - }); + // search for assignment + let assignment; + assignments.allIds.forEach(assignID => { + const assign = assignments.byId[assignID]; + if (assign.processorID === my.processorID && assign.paramKey === my.key) { + assignment = assign; + } + }); - if (assignment) { - showRemoteState('assigned'); - } else { - showRemoteState('unassigned'); - } - if (state.learnTargetProcessorID === my.processorID && state.learnTargetParameterKey === my.key) { - showRemoteState('selected'); - } else { - showRemoteState('deselected'); - } - } else { - showRemoteState('exit'); - } - } - }, - - /** - * State of the parameter in the assignment process changed, - * the element will show this visually. - * @param {String} status New state of the parameter. - */ - showRemoteState = function(status) { - switch (status) { - case 'enter': - my.el.appendChild(learnClickLayer); - learnClickLayer.addEventListener('click', onLearnLayerClick); - break; - case 'exit': - if (my.el.contains(learnClickLayer)) { - my.el.removeChild(learnClickLayer); - learnClickLayer.removeEventListener('click', onLearnLayerClick); - } - break; - case 'selected': - learnClickLayer.dataset.selected = true; - break; - case 'deselected': - learnClickLayer.dataset.selected = false; - break; - case 'assigned': - learnClickLayer.dataset.assigned = true; - break; - case 'unassigned': - learnClickLayer.dataset.assigned = false; - break; - default: - console.log('Unknown remote state: ', state); - break; - } - }, - - onLearnLayerClick = function(e) { - my.store.dispatch(my.store.getActions().toggleMIDILearnTarget(my.processorID, my.key)); - }; - - my = my || {}; - my.changeRemoteState = changeRemoteState; - - that = that || {}; - - init(); - - return that; + if (assignment) { + showRemoteState('assigned'); + } else { + showRemoteState('unassigned'); + } + if (learnTargetProcessorID === my.processorID && learnTargetParameterKey === my.key) { + showRemoteState('selected'); + } else { + showRemoteState('deselected'); + } + } else { + showRemoteState('exit'); + } + } + }, + + /** + * State of the parameter in the assignment process changed, + * the element will show this visually. + * @param {String} status New state of the parameter. + */ + showRemoteState = function(status) { + switch (status) { + case 'enter': + my.el.appendChild(learnClickLayer); + learnClickLayer.addEventListener('click', onLearnLayerClick); + break; + case 'exit': + if (my.el.contains(learnClickLayer)) { + my.el.removeChild(learnClickLayer); + learnClickLayer.removeEventListener('click', onLearnLayerClick); + } + break; + case 'selected': + learnClickLayer.dataset.selected = true; + break; + case 'deselected': + learnClickLayer.dataset.selected = false; + break; + case 'assigned': + learnClickLayer.dataset.assigned = true; + break; + case 'unassigned': + learnClickLayer.dataset.assigned = false; + break; + default: + console.log('Unknown remote state: ', state); + break; + } + }, + + onLearnLayerClick = function(e) { + dispatch(getActions().toggleMIDILearnTarget(my.processorID, my.key)); + }; + + my = my || {}; + my.changeRemoteState = changeRemoteState; + + that = that || {}; + + init(); + + return that; } diff --git a/src/js/view/setting/string.js b/src/js/view/setting/string.js index 69564757..d2f13882 100644 --- a/src/js/view/setting/string.js +++ b/src/js/view/setting/string.js @@ -1,46 +1,46 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import createBaseSettingView from './base.js'; /** * Processor setting view for a Boolean type parameter, * which has a checkbox input. */ - export default function createStringSettingView(specs, my) { - var that, - textEl, - - init = function() { - textEl = my.el.getElementsByClassName('setting__text')[0]; - textEl.addEventListener('input', onChange); - - initData(); - setValue(my.data.value); - }, + let that, + textEl, + + init = function() { + textEl = my.el.getElementsByClassName('setting__text')[0]; + textEl.addEventListener('input', onChange); + + initData(); + setValue(my.data.value); + }, - initData = function() {}, - - onChange = function(e) { - e.preventDefault(); - my.store.dispatch(my.store.getActions().changeParameter( - my.processorID, - my.key, - e.target.value)); - }, - - setValue = function(value) { - // only update if the text input doesn't have focus, - // else value gets refreshed and cursor jumps to end - if (textEl != document.activeElement) { - textEl.value = value; - } - }; - - my = my || {}; - my.setValue = setValue; - - that = createBaseSettingView(specs, my); - - init(); - - return that; + initData = function() {}, + + onChange = function(e) { + e.preventDefault(); + dispatch(getActions().changeParameter( + my.processorID, + my.key, + e.target.value)); + }, + + setValue = function(value) { + // only update if the text input doesn't have focus, + // else value gets refreshed and cursor jumps to end + if (textEl != document.activeElement) { + textEl.value = value; + } + }; + + my = my || {}; + my.setValue = setValue; + + that = createBaseSettingView(specs, my); + + init(); + + return that; } diff --git a/src/js/view/settings.js b/src/js/view/settings.js index 784332cc..1d50f64f 100644 --- a/src/js/view/settings.js +++ b/src/js/view/settings.js @@ -7,31 +7,29 @@ import createStringSettingView from './setting/string.js'; * Processor settings view. */ export default function createSettingsPanel(specs, my) { - let that, - store = specs.store, - data = specs.data, - parentEl = specs.parentEl, - settingViews = [], - el, + const { data, parentEl, } = specs; + const { id, params, } = data; + + let el, initialize = function() { + let settingView; + // const htmlString = require(`html-loader!../processors/${data.type}/settings.html`); el = document.createElement('div'); el.innerHTML = specs.template; // loop through all processor parameters and add setting view if required - data.params.allIds.forEach(id => { + params.allIds.forEach(paramId => { // only create setting if there's a container el for it in the settings panel - var settingContainerEl = el.querySelector('.' + id); + var settingContainerEl = el.querySelector('.' + paramId); if (settingContainerEl) { - let paramData = data.params.byId[id], - settingView, + let paramData = params.byId[paramId], settingViewSpecs = { - store: store, - key: id, + key: paramId, data: paramData, parentEl: settingContainerEl, - processorID: data.id + processorID: id }; // create the setting view based on the parameter type @@ -56,7 +54,7 @@ export default function createSettingsPanel(specs, my) { if (el && el.querySelector('.settings__delete')) { el.querySelector('.settings__delete').addEventListener('click', function(e) { e.preventDefault(); - store.dispatch(store.getActions().deleteProcessor(data.id)); + store.dispatch(store.getActions().deleteProcessor(id)); }); } @@ -88,20 +86,19 @@ export default function createSettingsPanel(specs, my) { * Show or hide settings depending on ID. * @param {String} id ID of the selected processor. */ - select = function(id) { - show(id === data.id); + select = function(_id) { + show(_id === id); }, getID = function() { - return data.id; + return id; }; - - that = data.that || {}; - + initialize(); - that.terminate = terminate; - that.select = select; - that.getID = getID; - return that; + return Object.freeze({ + getID, + select, + terminate, + }); } From 15560bdb3fff9015c4407668b5a2b1e465b6ccea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 10 Oct 2019 17:24:34 +0200 Subject: [PATCH 012/131] Bug fixes and refactoring. --- src/js/core/processor-loader.js | 14 +++++--- src/js/processors/epg/object3d.js | 2 +- src/js/processors/epg/object3dController.js | 12 +++---- src/js/processors/euclidfx/object3d.js | 18 +++++----- .../processors/euclidfx/object3dController.js | 11 ++---- src/js/processors/output/object3d.js | 21 +++++------ .../processors/output/object3dController.js | 9 ++--- src/js/state/actions.js | 35 ++++++++----------- src/js/view/library.js | 1 + src/js/view/panels.js | 1 - src/js/webgl/canvas3d.js | 16 +++++---- src/js/webgl/object3dControllerBase.js | 5 ++- 12 files changed, 65 insertions(+), 80 deletions(-) diff --git a/src/js/core/processor-loader.js b/src/js/core/processor-loader.js index 5bbd9ddb..61c31b5f 100644 --- a/src/js/core/processor-loader.js +++ b/src/js/core/processor-loader.js @@ -54,10 +54,10 @@ export function preloadProcessors() { processors[json.ids[index]] = { config: results[0], settings: results[1], - module3d: results[2], - module3dController: results[3], - moduleProcessor: results[4], - moduleUtils: results[5], + object3d: results[2], + object3dController: results[3], + processor: results[4], + utils: results[5], }; }); console.log('Processor data preloaded.', processors); @@ -71,9 +71,13 @@ export function getProcessorTypes() { return data.ids; } +/** + * Get preloaded processor data. + * @param {String} name Processor type. + * @param {String} type Data type. + */ export function getProcessorData(name, type) { if (processors[name] && processors[name][type]) { return processors[name][type]; } } - diff --git a/src/js/processors/epg/object3d.js b/src/js/processors/epg/object3d.js index 72f4cd07..61e812b5 100644 --- a/src/js/processors/epg/object3d.js +++ b/src/js/processors/epg/object3d.js @@ -54,7 +54,7 @@ export function createObject3d(id, inputs, outputs) { * @return {object} Group of drag plane. */ createWheel = function() { - const { colorLow, colorHigh, } = getThemeColors(); + const { colorLow, colorHigh, } = getTheme(); const hitarea = createCircleFilled(3, colorHigh); hitarea.name = 'hitarea'; diff --git a/src/js/processors/epg/object3dController.js b/src/js/processors/epg/object3dController.js index 60adfd0f..d233b914 100644 --- a/src/js/processors/epg/object3dController.js +++ b/src/js/processors/epg/object3dController.js @@ -1,5 +1,5 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import { - BufferGeometry, Shape, ShapeGeometry, Vector2, @@ -42,7 +42,7 @@ export function createObject3dController(specs, my) { select3d = my.object3d.getObjectByName('select'), zeroMarker3d = my.object3d.getObjectByName('zeroMarker'), - document.addEventListener(my.store.STATE_CHANGE, handleStateChanges); + document.addEventListener(STATE_CHANGE, handleStateChanges); defaultColor = getTheme().colorHigh; @@ -54,7 +54,7 @@ export function createObject3dController(specs, my) { }, terminate = function() { - document.removeEventListener(my.store.STATE_CHANGE, handleStateChanges); + document.removeEventListener(STATE_CHANGE, handleStateChanges); }, handleStateChanges = function(e) { @@ -350,9 +350,9 @@ export function createObject3dController(specs, my) { // retain necklace dot state in object dotAnimations[stepIndex] = { - dot, - scale: 1, - isActive: false, + dot, + scale: 1, + isActive: false, } // delay start of animation diff --git a/src/js/processors/euclidfx/object3d.js b/src/js/processors/euclidfx/object3d.js index 7537607e..cec2404a 100644 --- a/src/js/processors/euclidfx/object3d.js +++ b/src/js/processors/euclidfx/object3d.js @@ -19,10 +19,6 @@ export function createObject3d(id, inputs, outputs) { * Initialization. */ init = function() { - defaultColor = getTheme().colorHigh; - lineMaterial = new LineBasicMaterial({ - color: defaultColor, - }); }, /** @@ -30,18 +26,20 @@ export function createObject3d(id, inputs, outputs) { * @return {object} Group object3D of drag plane. */ create = function() { - const hitarea = createCircleFilled(3, defaultColor); + const { colorLow, colorHigh, } = getTheme(); + + const hitarea = createCircleFilled(3, colorHigh); hitarea.name = 'hitarea'; hitarea.material.opacity = 0.0; - const centreCircle = createCircleOutline(3, defaultColor); + const centreCircle = createCircleOutline(3, colorHigh); centreCircle.name = 'centreCircle'; - const selectCircle = createCircleOutline(2, defaultColor); + const selectCircle = createCircleOutline(2, colorHigh); selectCircle.name = 'select'; selectCircle.visible = false; - const centreDot = createCircleOutlineFilled(1.5, defaultColor); + const centreDot = createCircleOutlineFilled(1.5, colorHigh); centreDot.name = 'centreDot'; centreDot.visible = false; @@ -51,7 +49,7 @@ export function createObject3d(id, inputs, outputs) { const necklace = createShape(); necklace.name = 'necklace'; - const zeroMarker = createCircleOutline(0.5, defaultColor); + const zeroMarker = createCircleOutline(0.5, colorHigh); zeroMarker.name = 'zeroMarker'; zeroMarker.translateY(2.5); necklace.add(zeroMarker); @@ -73,7 +71,7 @@ export function createObject3d(id, inputs, outputs) { root.add(label); // add inputs and outputs - drawConnectors(root, inputs, outputs, getTheme().colorLow); + drawConnectors(root, inputs, outputs, colorLow); return root; }; diff --git a/src/js/processors/euclidfx/object3dController.js b/src/js/processors/euclidfx/object3dController.js index fb293007..4c353c6f 100644 --- a/src/js/processors/euclidfx/object3dController.js +++ b/src/js/processors/euclidfx/object3dController.js @@ -1,3 +1,4 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import { EllipseCurve, Vector2, @@ -12,29 +13,23 @@ const TWO_PI = Math.PI * 2; export function createObject3dController(specs, my) { let that, - centreCircle3d, centreDot3d, select3d, pointer3d, necklace3d, defaultColor, - lineMaterial, duration, pointerRotation, - pointerRotationPrevious = 0, status = true, euclid, steps, rotation, centerRadius = 3, centerScale = 0, - selectRadius = 2, innerRadius = 4, outerRadius = 6, - dotRadius = 1, locatorRadius = 8, zeroMarkerRadius = 0.5, - zeroMarkerY = outerRadius + zeroMarkerRadius + 1, doublePI = Math.PI * 2, initialize = function() { @@ -44,7 +39,7 @@ export function createObject3dController(specs, my) { pointer3d = my.object3d.getObjectByName('pointer'), necklace3d = my.object3d.getObjectByName('necklace'), - document.addEventListener(my.store.STATE_CHANGE, handleStateChanges); + document.addEventListener(STATE_CHANGE, handleStateChanges); defaultColor = getTheme().colorHigh; @@ -58,7 +53,7 @@ export function createObject3dController(specs, my) { }, terminate = function() { - document.removeEventListener(my.store.STATE_CHANGE, handleStateChanges); + document.removeEventListener(STATE_CHANGE, handleStateChanges); }, handleStateChanges = function(e) { diff --git a/src/js/processors/output/object3d.js b/src/js/processors/output/object3d.js index e9cfea9c..8d157724 100644 --- a/src/js/processors/output/object3d.js +++ b/src/js/processors/output/object3d.js @@ -1,6 +1,5 @@ import { Group, - LineBasicMaterial, Vector2, } from '../../lib/three.module.js'; import { @@ -13,19 +12,15 @@ import { getTheme } from '../../state/selectors.js'; export function createObject3d(id, inputs, outputs) { - let defaultColor, - lineMaterial, - radius = 3, + let radius = 3, init = function() { - defaultColor = getTheme().colorHigh; - lineMaterial = new LineBasicMaterial({ - color: defaultColor, - }); }, createGraphic = function() { - const hitarea = createCircleFilled(3, defaultColor); + const { colorLow, colorHigh, } = getTheme(); + + const hitarea = createCircleFilled(3, colorHigh); hitarea.name = 'hitarea'; hitarea.material.opacity = 0.0; @@ -34,10 +29,10 @@ export function createObject3d(id, inputs, outputs) { label.scale.set(0.1, 0.1, 1); label.translateY(-7); - const centreCircle = createCircleOutline(radius, defaultColor); + const centreCircle = createCircleOutline(radius, colorHigh); centreCircle.name = 'centreCircle'; - const selectCircle = createCircleOutline(2, defaultColor); + const selectCircle = createCircleOutline(2, colorHigh); selectCircle.name = 'select'; selectCircle.visible = false; @@ -50,7 +45,7 @@ export function createObject3d(id, inputs, outputs) { new Vector2(0, -radius * 1.8), new Vector2(radius, -radius), ]; - const graphic = createShape(points, defaultColor); + const graphic = createShape(points, colorHigh); const group = new Group(); group.name = 'output'; @@ -62,7 +57,7 @@ export function createObject3d(id, inputs, outputs) { group.add(label); // add inputs and outputs - drawConnectors(group, inputs, outputs, getTheme().colorLow); + drawConnectors(group, inputs, outputs, colorLow); return group; }; diff --git a/src/js/processors/output/object3dController.js b/src/js/processors/output/object3dController.js index a4b7116d..41147b52 100644 --- a/src/js/processors/output/object3dController.js +++ b/src/js/processors/output/object3dController.js @@ -1,17 +1,16 @@ - +import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import { getTheme } from '../../state/selectors.js'; import createObject3dControllerBase from '../../webgl/object3dControllerBase.js'; export function createObject3dController(specs, my) { let that, - centreCircle3d, select3d, initialize = function() { centreCircle3d = my.object3d.getObjectByName('centreCircle'), select3d = my.object3d.getObjectByName('select'), - document.addEventListener(my.store.STATE_CHANGE, handleStateChanges); + document.addEventListener(STATE_CHANGE, handleStateChanges); const params = specs.processorData.params.byId; my.updateLabel(params.name.value); @@ -19,12 +18,11 @@ export function createObject3dController(specs, my) { }, terminate = function() { - document.removeEventListener(my.store.STATE_CHANGE, handleStateChanges); + document.removeEventListener(STATE_CHANGE, handleStateChanges); }, handleStateChanges = function(e) { const { action, actions, state, } = e.detail; - const processor = state.processors.byId[my.id]; switch (action.type) { case actions.CHANGE_PARAMETER: @@ -83,7 +81,6 @@ export function createObject3dController(specs, my) { }, draw = function(position, processorEvents) { - }; my = my || {}; diff --git a/src/js/state/actions.js b/src/js/state/actions.js index 8ee104df..f047d1a5 100644 --- a/src/js/state/actions.js +++ b/src/js/state/actions.js @@ -3,6 +3,7 @@ import { createUUID } from '../core/util.js'; import { getConfig, setConfig, processorTypes } from '../core/config.js'; import { getAllMIDIPorts } from '../midi/midi.js'; import { showDialog } from '../view/dialog.js'; +import { getProcessorData, } from '../core/processor-loader.js'; const RESCAN_TYPES = 'RESCAN_TYPES', CREATE_PROJECT = 'CREATE_PROJECT', @@ -178,26 +179,20 @@ export default { return { type: SET_THEME, themeName }; }, - CREATE_PROCESSOR, - createProcessor: (data) => { - return (dispatch, getState, getActions) => { - fetch(`js/wh/processors/${data.type}/config.json`) - .then( - response => response.json(), - error => console.log('An error occurred.', error) - ) - .then(json => { - const id = data.id || `${data.type}_${createUUID()}`; - const fullData = {...json, ...data}; - fullData.id = id; - fullData.positionX = data.positionX; - fullData.positionY = data.positionY; - fullData.params.byId.name.value = data.name || getProcessorDefaultName(getState().processors); - dispatch(getActions().addProcessor(fullData)); - dispatch(getActions().selectProcessor(id)); - }); - } - }, + CREATE_PROCESSOR, + createProcessor: data => { + return (dispatch, getState, getActions) => { + const configJson = getProcessorData(data.type, 'config'); + const id = data.id || `${data.type}_${createUUID()}`; + const fullData = {...configJson, ...data}; + fullData.id = id; + fullData.positionX = data.positionX; + fullData.positionY = data.positionY; + fullData.params.byId.name.value = data.name || getProcessorDefaultName(getState().processors); + dispatch(getActions().addProcessor(fullData)); + dispatch(getActions().selectProcessor(id)); + } + }, ADD_PROCESSOR, addProcessor: (data) => { diff --git a/src/js/view/library.js b/src/js/view/library.js index cc1eff37..3a88a836 100644 --- a/src/js/view/library.js +++ b/src/js/view/library.js @@ -66,6 +66,7 @@ function populateLibrary() { const config = getProcessorData(type, 'config'); el.querySelector('.library__item-label').innerHTML = config.name; + el.dataset.type = type; el.addEventListener('touchstart', onTouchStart); el.addEventListener('mousedown', onTouchStart); }); diff --git a/src/js/view/panels.js b/src/js/view/panels.js index 808d5d37..50a6927c 100644 --- a/src/js/view/panels.js +++ b/src/js/view/panels.js @@ -41,7 +41,6 @@ function createSettingsViews(state) { const settingsHTML = getProcessorData(processorData.type, 'settings'); settingsViews.splice(i, 0, createSettingsPanel({ data: processorData, - store, parentEl: editContentEl, template: settingsHTML, isSelected: selectedID === processorData.id diff --git a/src/js/webgl/canvas3d.js b/src/js/webgl/canvas3d.js index eeec6ae1..245b6089 100644 --- a/src/js/webgl/canvas3d.js +++ b/src/js/webgl/canvas3d.js @@ -94,24 +94,26 @@ function clearProcessorViews() { * @param {Array} data Array of current processors' state. */ function createProcessorViews(state) { - const isConnectMode = state.connectModeActive; - for (let id of state.processors.allIds) { - const processorData = state.processors.byId[id]; + const { connectModeActive, processors, selectedID, } = state; + const isConnectMode = connectModeActive; + for (let id of processors.allIds) { + const processorData = processors.byId[id]; const { inputs, outputs, positionX, positionY, positionZ, type } = processorData; const isExists = allObjects.find(obj3d => obj3d.userData.id === id); if (!isExists) { // create the processor 3d object - const object3dModule = getProcessorData('object3d', type); + console.log(type, 'object3d'); + const object3dModule = getProcessorData(type, 'object3d'); const object3d = object3dModule.createObject3d(id, inputs, outputs); object3d.position.set(positionX, positionY, positionZ); allObjects.push(object3d); scene.add(object3d); // create controller for the object - const controllerModule = getProcessorData('object3dController', type); - const controller = controllerModule.createObject3dController({ object3d, processorData, store, isConnectMode, }); - controller.updateSelectCircle(store.getState().selectedID); + const controllerModule = getProcessorData(type, 'object3dController'); + const controller = controllerModule.createObject3dController({ object3d, processorData, isConnectMode, }); + controller.updateSelectCircle(selectedID); controllers.push(controller); } }; diff --git a/src/js/webgl/object3dControllerBase.js b/src/js/webgl/object3dControllerBase.js index fd01bb2f..31790c72 100644 --- a/src/js/webgl/object3dControllerBase.js +++ b/src/js/webgl/object3dControllerBase.js @@ -40,11 +40,10 @@ export default function createObject3dControllerBase(specs, my) { return my.id; }; - my.store = specs.store, my.id = specs.object3d.userData.id; my.object3d = specs.object3d; - my.hitarea3d = my.object3d.getObjectByName('hitarea'), - my.label3d = my.object3d.getObjectByName('label'), + my.hitarea3d = my.object3d.getObjectByName('hitarea'); + my.label3d = my.object3d.getObjectByName('label'); my.updateLabel = updateLabel; my.updatePosition = updatePosition; my.updateConnectMode = updateConnectMode; From 0b51d1bb26bb0a65ca6946e2b8d5eae4d968731f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 10 Oct 2019 17:55:15 +0200 Subject: [PATCH 013/131] Fixes. --- src/js/view/settings.js | 3 ++- src/js/webgl/canvas3d.js | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/js/view/settings.js b/src/js/view/settings.js index 1d50f64f..e4ed95b3 100644 --- a/src/js/view/settings.js +++ b/src/js/view/settings.js @@ -1,3 +1,4 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; import createBooleanSettingView from './setting/boolean.js'; import createIntegerSettingView from './setting/integer.js'; import createItemizedSettingView from './setting/itemized.js'; @@ -54,7 +55,7 @@ export default function createSettingsPanel(specs, my) { if (el && el.querySelector('.settings__delete')) { el.querySelector('.settings__delete').addEventListener('click', function(e) { e.preventDefault(); - store.dispatch(store.getActions().deleteProcessor(id)); + dispatch(getActions().deleteProcessor(id)); }); } diff --git a/src/js/webgl/canvas3d.js b/src/js/webgl/canvas3d.js index 245b6089..092f0468 100644 --- a/src/js/webgl/canvas3d.js +++ b/src/js/webgl/canvas3d.js @@ -39,6 +39,16 @@ let rootEl, scene; +/** + * Update any tween animations that are going on and redraw the canvases if needed. + * @param {Number} position Transport playback position in ticks. + * @param {Array} processorEvents Array of processor generated events to displayin the view. + */ +export function draw(position, processorEvents) { + controllers.forEach(controller => controller.draw(position, processorEvents)); + renderer.render(scene, camera); +} + /** * Provide cables3d with the scene so it can add and remove cables. */ @@ -103,7 +113,6 @@ function createProcessorViews(state) { if (!isExists) { // create the processor 3d object - console.log(type, 'object3d'); const object3dModule = getProcessorData(type, 'object3d'); const object3d = object3dModule.createObject3d(id, inputs, outputs); object3d.position.set(positionX, positionY, positionZ); @@ -270,16 +279,6 @@ function dragStart(object3d, mousePoint) { rootEl.style.cursor = 'move'; } } - -/** - * Update any tween animations that are going on and redraw the canvases if needed. - * @param {Number} position Transport playback position in ticks. - * @param {Array} processorEvents Array of processor generated events to displayin the view. - */ -function draw(position, processorEvents) { - controllers.forEach(controller => controller.draw(position, processorEvents)); - renderer.render(scene, camera); -} /** * Recursive function to get top level object of a group. From 3ab2bfe24d2af45aa095d8fcd4fc2cfe2ab7f274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 10 Oct 2019 17:56:32 +0200 Subject: [PATCH 014/131] Network module refactor, WIP. --- src/js/main.js | 4 +- src/js/midi/network.js | 156 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 3 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index fea6e8b4..9d5269e0 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -18,8 +18,8 @@ import { dispatch, getActions, getState, persist, } from './state/store.js'; -import createAppView from './view/app.js'; -import createDialog from './view/dialog.js'; +// import createAppView from './view/app.js'; +// import createDialog from './view/dialog.js'; import { accessMidi } from './midi/midi.js'; import createMIDINetwork from './midi/network.js'; import { setup as setupCanvas3d } from './webgl/canvas3d.js'; diff --git a/src/js/midi/network.js b/src/js/midi/network.js index 64a337e1..1e5ba05c 100644 --- a/src/js/midi/network.js +++ b/src/js/midi/network.js @@ -1,9 +1,163 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; +import { getProcessorData } from '../core/processor-loader.js'; + +export function setup() { + addEventListeners(); +} + +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); +} + +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.CREATE_PROJECT: + disconnectProcessors(state.connections); + deleteProcessors(state.processors); + createProcessors(state); + break; + + case actions.ADD_PROCESSOR: + createProcessors(state); + break; + + case actions.DELETE_PROCESSOR: + disconnectProcessors(state.connections); + deleteProcessors(state.processors); + reorderProcessors(state.processors); + break; + + case actions.CONNECT_PROCESSORS: + connectProcessors(state.connections); + reorderProcessors(state.processors); + break; + + case actions.DISCONNECT_PROCESSORS: + disconnectProcessors(state.connections); + reorderProcessors(state.processors); + break; + } +} + +/** + * Create a new processor in the network. + * @param {Object} state State processors table. + */ +function createProcessors(state) { + let loaded = 0; + for (let id of state.processors.allIds) { + const processorData = state.processors.byId[id]; + const isExists = processors.find(processor => processor.getID() === id); + if (!isExists) { + const module = getProcessorData(processorData.type, 'processor'); + const processor = module.createProcessor({ + that: {}, + data: processorData, + store, + }); + processors.push(processor); + numProcessors = processors.length; + + loaded += 1; + if (loaded === state.processors.allIds.length) { + connectProcessors(state.connections); + reorderProcessors(state.processors); + } + } + }; +} + +/** + * Go through all connection data and create the connections + * that don't yet exist. + */ +function connectProcessors(connections) { + connections.allIds.forEach(connectionID => { + const connection = connections.byId[connectionID]; + processors.forEach(sourceProcessor => { + if (sourceProcessor.getID() === connection.sourceProcessorID) { + let exists = false; + sourceProcessor.getDestinations().forEach(destinationProcessor => { + if (destinationProcessor.getID() === connection.destinationProcessorID) { + exists = true; + } + }); + if (!exists) { + processors.forEach(destinationProcessor => { + if (destinationProcessor.getID() === connection.destinationProcessorID) { + sourceProcessor.connect(destinationProcessor); + } + }); + } + } + }); + }); +} + +/** + * Go through all processor outputs and check if + * they still exist in the state. If not, disconnect them. + * + * TODO: allow for processors with multiple inputs or outputs. + */ +function disconnectProcessors(connections) { + processors.forEach(sourceProcessor => { + if (sourceProcessor.getDestinations instanceof Function) { + const destinationProcessors = sourceProcessor.getDestinations(); + destinationProcessors.forEach(destinationProcessor => { + let exists = false; + connections.allIds.forEach(connectionID => { + const connection = connections.byId[connectionID]; + if (connection.sourceProcessorID === sourceProcessor.getID() && + connection.destinationProcessorID === destinationProcessor.getID()) { + exists = true; + } + }); + if (!exists) { + sourceProcessor.disconnect(destinationProcessor); + } + }); + } + }); +}, + +/** + * Delete a processor. + * @param {Object} state State processors table. + */ +function deleteProcessors(processorsState) { + for (let i = processors.length - 1, n = 0; i >= n; i--) { + + // search for the processor in the state + let exists = false; + processorsState.allIds.forEach(processorID => { + if (processorID === processors[i].getID()) { + exists = true; + } + }); + + // remove processor if it doesn't exist in the state + if (!exists) { + const processor = processors[i]; + if (processor.terminate instanceof Function) { + processor.terminate(); + } + processors.splice(i, 1); + } + } + numProcessors = processors.length; +} + /** * Manages the graph of midi processors. */ export default function createMIDINetwork(specs, my) { var that, - store = specs.store, processors = [], numProcessors = 0, From 3c76862d6d4e7be1d1e52d159635c4aee0384f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Mon, 14 Oct 2019 15:14:45 +0200 Subject: [PATCH 015/131] Network module refactored. --- src/js/midi/network.js | 409 +++++++++++------------------------------ 1 file changed, 104 insertions(+), 305 deletions(-) diff --git a/src/js/midi/network.js b/src/js/midi/network.js index 1e5ba05c..c22f10b0 100644 --- a/src/js/midi/network.js +++ b/src/js/midi/network.js @@ -1,8 +1,25 @@ import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; import { getProcessorData } from '../core/processor-loader.js'; +let processors = []; + +/** + * Let all processors process their data. + * @param {Number} start Start time in ticks of timespan to process. + * @param {Number} end End time in ticks of timespan to process. + * @param {Number} nowToScanStart Duration from now until start time in ticks. + * @param {Number} ticksToMsMultiplier Ticks to ms. conversion multiplier. + * @param {Number} offset Position of transport playhead in ticks. + * @param {Object} processorEvents Object to collect processor generated events to displayin the view. + */ +export function process(start, end, nowToScanStart, ticksToMsMultiplier, offset, processorEvents) { + processors.forEach(processor => { + processor.process(start, end, nowToScanStart, ticksToMsMultiplier, offset, processorEvents); + }); +} + export function setup() { - addEventListeners(); + addEventListeners(); } function addEventListeners() { @@ -10,347 +27,129 @@ function addEventListeners() { } /** - * Handle state changes. - * @param {Object} e + * Go through all connection data and connect the processors + * that are,'t connected yet. + * @param {Object} state App state. */ -function handleStateChanges(e) { - const { state, action, actions, } = e.detail; - switch (action.type) { - case actions.CREATE_PROJECT: - disconnectProcessors(state.connections); - deleteProcessors(state.processors); - createProcessors(state); - break; - - case actions.ADD_PROCESSOR: - createProcessors(state); - break; - - case actions.DELETE_PROCESSOR: - disconnectProcessors(state.connections); - deleteProcessors(state.processors); - reorderProcessors(state.processors); - break; - - case actions.CONNECT_PROCESSORS: - connectProcessors(state.connections); - reorderProcessors(state.processors); - break; - - case actions.DISCONNECT_PROCESSORS: - disconnectProcessors(state.connections); - reorderProcessors(state.processors); - break; - } +function connectProcessors(state) { + state.connections.allIds.forEach(connectionID => { + const connection = state.connections.byId[connectionID]; + const sourceProcessor = processors.find(processor => processor.getID() === connection.sourceProcessorID); + const destinationProcessor = sourceProcessor.getDestinations().find(processor => processor.getID() === connection.destinationProcessorID); + if (!destinationProcessor) { + sourceProcessor.connect(destinationProcessor); + } + }); } /** * Create a new processor in the network. - * @param {Object} state State processors table. + * @param {Object} state App state. */ function createProcessors(state) { - let loaded = 0; for (let id of state.processors.allIds) { const processorData = state.processors.byId[id]; const isExists = processors.find(processor => processor.getID() === id); if (!isExists) { const module = getProcessorData(processorData.type, 'processor'); - const processor = module.createProcessor({ - that: {}, - data: processorData, - store, - }); + const processor = module.createProcessor(processorData); processors.push(processor); - numProcessors = processors.length; - - loaded += 1; - if (loaded === state.processors.allIds.length) { - connectProcessors(state.connections); - reorderProcessors(state.processors); - } } }; + connectProcessors(state); + reorderProcessors(state); } /** - * Go through all connection data and create the connections - * that don't yet exist. - */ -function connectProcessors(connections) { - connections.allIds.forEach(connectionID => { - const connection = connections.byId[connectionID]; - processors.forEach(sourceProcessor => { - if (sourceProcessor.getID() === connection.sourceProcessorID) { - let exists = false; - sourceProcessor.getDestinations().forEach(destinationProcessor => { - if (destinationProcessor.getID() === connection.destinationProcessorID) { - exists = true; - } - }); - if (!exists) { - processors.forEach(destinationProcessor => { - if (destinationProcessor.getID() === connection.destinationProcessorID) { - sourceProcessor.connect(destinationProcessor); - } - }); - } - } - }); - }); -} - -/** - * Go through all processor outputs and check if - * they still exist in the state. If not, disconnect them. - * - * TODO: allow for processors with multiple inputs or outputs. - */ -function disconnectProcessors(connections) { - processors.forEach(sourceProcessor => { - if (sourceProcessor.getDestinations instanceof Function) { - const destinationProcessors = sourceProcessor.getDestinations(); - destinationProcessors.forEach(destinationProcessor => { - let exists = false; - connections.allIds.forEach(connectionID => { - const connection = connections.byId[connectionID]; - if (connection.sourceProcessorID === sourceProcessor.getID() && - connection.destinationProcessorID === destinationProcessor.getID()) { - exists = true; - } - }); - if (!exists) { - sourceProcessor.disconnect(destinationProcessor); - } - }); - } - }); -}, - -/** - * Delete a processor. - * @param {Object} state State processors table. + * Delete processors. + * @param {Object} state App state. */ -function deleteProcessors(processorsState) { +function deleteProcessors(state) { for (let i = processors.length - 1, n = 0; i >= n; i--) { - + const processor = processors[i]; + // search for the processor in the state - let exists = false; - processorsState.allIds.forEach(processorID => { - if (processorID === processors[i].getID()) { - exists = true; - } - }); + const processorData = state.processors.allIds.find(processorID => processorID === processor.getID()); // remove processor if it doesn't exist in the state - if (!exists) { - const processor = processors[i]; + if (!processorData) { if (processor.terminate instanceof Function) { processor.terminate(); } processors.splice(i, 1); } } - numProcessors = processors.length; } /** - * Manages the graph of midi processors. + * Go through all processor outputs and check if + * they still exist in the state. If not, disconnect them. + * + * TODO: allow for processors with multiple inputs or outputs. + * @param {Object} state App state. */ -export default function createMIDINetwork(specs, my) { - var that, - processors = [], - numProcessors = 0, - - init = function() { - document.addEventListener(store.STATE_CHANGE, (e) => { - const { action, actions, state } = e.detail; - switch (action.type) { - case actions.CREATE_PROJECT: - disconnectProcessors(state.connections); - deleteProcessors(state.processors); - createProcessors(state); - break; - - case actions.ADD_PROCESSOR: - createProcessors(state); - break; - - case actions.DELETE_PROCESSOR: - disconnectProcessors(state.connections); - deleteProcessors(state.processors); - reorderProcessors(state.processors); - break; - - case actions.CONNECT_PROCESSORS: - connectProcessors(state.connections); - reorderProcessors(state.processors); - break; - - case actions.DISCONNECT_PROCESSORS: - disconnectProcessors(state.connections); - reorderProcessors(state.processors); - break; - } - }); - - document.addEventListener('keyup', function(e) { - switch (e.keyCode) { - case 83: // s - console.log(' ++++ '); - processors.forEach(processor => { - console.log('network processor', processor.getID()); - }); - break; - } - }); - }, - - /** - * Create a new processor in the network. - * @param {Object} state State processors table. - */ - createProcessors = async state => { - let loaded = 0; - for (let id of state.processors.allIds) { - const processorData = state.processors.byId[id]; - const isExists = processors.find(processor => processor.getID() === id); - if (!isExists) { - const module = await import(`../processors/${processorData.type}/processor.js`); - const processor = module.createProcessor({ - that: {}, - data: processorData, - store, - }); - processors.push(processor); - numProcessors = processors.length; - - loaded += 1; - if (loaded === state.processors.allIds.length) { - connectProcessors(state.connections); - reorderProcessors(state.processors); - } - } - }; - }, - - /** - * Delete a processor. - * @param {Object} state State processors table. - */ - deleteProcessors = function(processorsState) { - for (let i = processors.length - 1, n = 0; i >= n; i--) { - // search for the processor in the state - let exists = false; - processorsState.allIds.forEach(processorID => { - if (processorID === processors[i].getID()) { - exists = true; - } - }); - - // remove processor if it doesn't exist in the state - if (!exists) { - const processor = processors[i]; - if (processor.terminate instanceof Function) { - processor.terminate(); - } - processors.splice(i, 1); - } - } - numProcessors = processors.length; - }, - - /** - * Go through all connection data and create the connections - * that don't yet exist. - */ - connectProcessors = function(connections) { - connections.allIds.forEach(connectionID => { - const connection = connections.byId[connectionID]; - processors.forEach(sourceProcessor => { - if (sourceProcessor.getID() === connection.sourceProcessorID) { - let exists = false; - sourceProcessor.getDestinations().forEach(destinationProcessor => { - if (destinationProcessor.getID() === connection.destinationProcessorID) { - exists = true; - } - }); - if (!exists) { - processors.forEach(destinationProcessor => { - if (destinationProcessor.getID() === connection.destinationProcessorID) { - sourceProcessor.connect(destinationProcessor); - } - }); - } - } - }); - }); - }, - - /** - * Go through all processor outputs and check if - * they still exist in the state. If not, disconnect them. - * - * TODO: allow for processors with multiple inputs or outputs. - */ - disconnectProcessors = function(connections) { - processors.forEach(sourceProcessor => { - if (sourceProcessor.getDestinations instanceof Function) { - const destinationProcessors = sourceProcessor.getDestinations(); - destinationProcessors.forEach(destinationProcessor => { - let exists = false; - connections.allIds.forEach(connectionID => { - const connection = connections.byId[connectionID]; - if (connection.sourceProcessorID === sourceProcessor.getID() && - connection.destinationProcessorID === destinationProcessor.getID()) { - exists = true; - } - }); - if (!exists) { - sourceProcessor.disconnect(destinationProcessor); - } - }); - } - }); - }, - - /** - * Reorder the processors according to their order in the state. - * @param {Object} State processor table. - */ - reorderProcessors = function(processorsState) { - const orderedProcessors = []; - processorsState.allIds.forEach(processorID => { - processors.forEach(processor => { - if (processor.getID() === processorID) { - orderedProcessors.push(processor); - } - }); - }); - processors = orderedProcessors; - }, +function disconnectProcessors(state) { + processors.forEach(sourceProcessor => { + if (sourceProcessor.getDestinations instanceof Function) { + const destinationProcessors = sourceProcessor.getDestinations(); + destinationProcessors.forEach(destinationProcessor => { + let exists = false; + state.connections.allIds.forEach(connectionID => { + const connection = state.connections.byId[connectionID]; + if (connection.sourceProcessorID === sourceProcessor.getID() && + connection.destinationProcessorID === destinationProcessor.getID()) { + exists = true; + } + }); + if (!exists) { + sourceProcessor.disconnect(destinationProcessor); + } + }); + } + }); +} - /** - * Let all processors process their data. - * @param {Number} start Start time in ticks of timespan to process. - * @param {Number} end End time in ticks of timespan to process. - * @param {Number} nowToScanStart Duration from now until start time in ticks. - * @param {Number} ticksToMsMultiplier Ticks to ms. conversion multiplier. - * @param {Number} offset Position of transport playhead in ticks. - * @param {Object} processorEvents Object to collect processor generated events to displayin the view. - */ - process = function(start, end, nowToScanStart, ticksToMsMultiplier, offset, processorEvents) { - for (var i = 0; i < numProcessors; i++) { - processors[i].process(start, end, nowToScanStart, ticksToMsMultiplier, offset, processorEvents); - } - }; +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.CREATE_PROJECT: + disconnectProcessors(state); + deleteProcessors(state); + createProcessors(state); + break; - my = my || {}; + case actions.ADD_PROCESSOR: + createProcessors(state); + break; - that = specs.that; + case actions.DELETE_PROCESSOR: + disconnectProcessors(state); + deleteProcessors(state); + reorderProcessors(state); + break; - init(); + case actions.CONNECT_PROCESSORS: + connectProcessors(state); + reorderProcessors(state); + break; - that.process = process; + case actions.DISCONNECT_PROCESSORS: + disconnectProcessors(state); + reorderProcessors(state); + break; + } +} - return that; +/** + * Reorder the processors according to their order in the state. + * @param {Object} state, App state. + */ +function reorderProcessors(state) { + processors = state.processors.allIds.map(processorId => { + return processors.find(processor => processor.getID() === processorId); + }); } From 8ae244fe5ae25176cf7eb2bb7aaca12f23b675be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Mon, 14 Oct 2019 15:15:17 +0200 Subject: [PATCH 016/131] Euclidean processor refactored. --- src/js/processors/epg/processor.js | 389 +++++++++++++++-------------- 1 file changed, 197 insertions(+), 192 deletions(-) diff --git a/src/js/processors/epg/processor.js b/src/js/processors/epg/processor.js index fc99a4b0..229c24a6 100644 --- a/src/js/processors/epg/processor.js +++ b/src/js/processors/epg/processor.js @@ -1,206 +1,211 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import createMIDIProcessorBase from '../../midi/processorbase.js'; import { PPQN } from '../../core/config.js'; import { getEuclidPattern, rotateEuclidPattern } from './utils.js'; -export function createProcessor(specs, my) { - let that, - store = specs.store, - position = 0, - duration = 0, - noteDuration, - params = {}, - euclidPattern = [], - pulsesOnly = []; +export function createProcessor(data, my = {}) { + let that, + position = 0, + duration = 0, + noteDuration, + params = {}, + euclidPattern = [], + pulsesOnly = []; - const initialize = function() { - document.addEventListener(store.STATE_CHANGE, handleStateChanges); - updateAllParams(specs.data.params.byId); - updatePattern(true); - }, + const initialize = data => { + document.addEventListener(STATE_CHANGE, handleStateChanges); + updateAllParams(data.params.byId); + updatePattern(true); + }, - terminate = function() { - document.removeEventListener(store.STATE_CHANGE, handleStateChanges); - }, + terminate = () => { + document.removeEventListener(STATE_CHANGE, handleStateChanges); + }, - handleStateChanges = function(e) { - switch (e.detail.action.type) { - case e.detail.actions.CHANGE_PARAMETER: - if (e.detail.action.processorID === my.id) { - updateAllParams(e.detail.state.processors.byId[my.id].params.byId); - switch (e.detail.action.paramKey) { - case 'steps': - updatePulsesAndRotation(); - updatePattern(true); - break; - case 'pulses': - case 'rotation': - updatePattern(true); - break; - case 'is_triplets': - case 'rate': - case 'note_length': - updatePattern(); - break; - case 'is_mute': - break; - } - } - break; - } - }, - - /** - * Process events to happen in a time slice. - * timeline start now scanStart scanEnd - * |----------------------|-----------|------------| - * |-----------| - * nowToScanStart - * @param {Number} scanStart Timespan start in ticks from timeline start. - * @param {Number} scanEnd Timespan end in ticks from timeline start. - * @param {Number} nowToScanStart Timespan from current timeline position to scanStart, in ticks. - * @param {Number} ticksToMsMultiplier Duration of one tick in milliseconds. - * @param {Number} offset Time from doc start to timeline start in ticks. - * @param {Array} processorEvents Array to collect processor generated events to displaying the view. - */ - process = function(scanStart, scanEnd, nowToScanStart, ticksToMsMultiplier, offset, processorEvents) { + /** + * Handle state changes. + * @param {Object} e + */ + handleStateChanges = e => { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.CHANGE_PARAMETER: + if (action.processorID === my.id) { + updateAllParams(state.processors.byId[my.id].params.byId); + switch (action.paramKey) { + case 'steps': + updatePulsesAndRotation(); + updatePattern(true); + break; + case 'pulses': + case 'rotation': + updatePattern(true); + break; + case 'is_triplets': + case 'rate': + case 'note_length': + updatePattern(); + break; + case 'is_mute': + break; + } + } + break; + } + }, + + /** + * Process events to happen in a time slice. + * timeline start now scanStart scanEnd + * |----------------------|-----------|------------| + * |-----------| + * nowToScanStart + * @param {Number} scanStart Timespan start in ticks from timeline start. + * @param {Number} scanEnd Timespan end in ticks from timeline start. + * @param {Number} nowToScanStart Timespan from current timeline position to scanStart, in ticks. + * @param {Number} ticksToMsMultiplier Duration of one tick in milliseconds. + * @param {Number} offset Time from doc start to timeline start in ticks. + * @param {Array} processorEvents Array to collect processor generated events to displaying the view. + */ + process = (scanStart, scanEnd, nowToScanStart, ticksToMsMultiplier, offset, processorEvents) => { - // clear the output event stack - my.clearOutputData(); - - // abort if the processor is muted - if (params.is_mute) { - return; - } - - // if the pattern loops during this timespan. - var localScanStart = scanStart % duration, - localScanEnd = scanEnd % duration, - localScanStart2 = false, - localScanEnd2; - if (localScanStart > localScanEnd) { - localScanStart2 = 0, - localScanEnd2 = localScanEnd; - localScanEnd = duration; - } - - // check if notes occur during the current timespan - var n = pulsesOnly.length; - for (let i = 0; i < n; i++) { - var pulseStartTime = pulsesOnly[i].startTime, - scanStartToNoteStart = pulseStartTime - localScanStart, - isOn = (localScanStart <= pulseStartTime) && (pulseStartTime < localScanEnd); - - // if pattern looped back to the start - if (localScanStart2 !== false && isOn === false) { - scanStartToNoteStart = pulseStartTime - localScanStart + duration; - isOn = isOn || (localScanStart2 <= pulseStartTime) && (pulseStartTime < localScanEnd2); - } - - // if a note should play - if (isOn) { - var channel = params.channel_out, - pitch = params.pitch_out, - velocity = params.velocity_out, - pulseStartTimestamp = scanStart + scanStartToNoteStart; - - // send the Note On message - // subtract 1 from duration to avoid overlaps - my.setOutputData({ - timestampTicks: pulseStartTimestamp, - durationTicks: noteDuration - 1, - channel: channel, - type: 'note', - pitch: pitch, - velocity: velocity - }); - - // add events to processorEvents for the canvas to show them - if (!processorEvents[my.id]) { - processorEvents[my.id] = []; - } - - const delayFromNowToNoteStart = (nowToScanStart + scanStartToNoteStart) * ticksToMsMultiplier; - processorEvents[my.id].push({ - stepIndex: pulsesOnly[i].stepIndex, - delayFromNowToNoteStart: delayFromNowToNoteStart, - delayFromNowToNoteEnd: delayFromNowToNoteStart + (noteDuration * ticksToMsMultiplier) - }); - } - } - - if (localScanStart2 !== false) { - localScanStart = localScanStart2; - } - }, + // clear the output event stack + my.clearOutputData(); + + // abort if the processor is muted + if (params.is_mute) { + return; + } + + // if the pattern loops during this timespan. + let localScanStart = scanStart % duration, + localScanEnd = scanEnd % duration, + localScanStart2 = false, + localScanEnd2; + if (localScanStart > localScanEnd) { + localScanStart2 = 0, + localScanEnd2 = localScanEnd; + localScanEnd = duration; + } + + // check if notes occur during the current timespan + pulsesOnly.forEach(pulse => { + const { startTime, stepIndex, } = pulse; + let scanStartToNoteStart = startTime - localScanStart; + let isOn = (localScanStart <= startTime) && (startTime < localScanEnd); + + // if pattern looped back to the start + if (localScanStart2 !== false && isOn === false) { + scanStartToNoteStart = startTime - localScanStart + duration; + isOn = isOn || (localScanStart2 <= startTime) && (startTime < localScanEnd2); + } + + // if a note should play + if (isOn) { + const { channel_out, pitch_out, velocity_out, } = params; + const pulseStartTimestamp = scanStart + scanStartToNoteStart; + + // send the Note On message + // subtract 1 from duration to avoid overlaps + my.setOutputData({ + timestampTicks: pulseStartTimestamp, + durationTicks: noteDuration - 1, + channel: channel_out, + type: 'note', + pitch: pitch_out, + velocity: velocity_out + }); + + // add events to processorEvents for the canvas to show them + if (!processorEvents[my.id]) { + processorEvents[my.id] = []; + } + + const delayFromNowToNoteStart = (nowToScanStart + scanStartToNoteStart) * ticksToMsMultiplier; + processorEvents[my.id].push({ + stepIndex, + delayFromNowToNoteStart: delayFromNowToNoteStart, + delayFromNowToNoteEnd: delayFromNowToNoteStart + (noteDuration * ticksToMsMultiplier) + }); + } + }); + + if (localScanStart2 !== false) { + localScanStart = localScanStart2; + } + }, - updateAllParams = function(parameters) { - params.steps = parameters.steps.value; - params.pulses = parameters.pulses.value; - params.rotation = parameters.rotation.value; - params.isTriplets = parameters.is_triplets.value; - params.rate = parameters.rate.value; - params.note_length = parameters.note_length.value; - params.is_mute = parameters.is_mute.value; - params.channel_out = parameters.channel_out.value; - params.pitch_out = parameters.pitch_out.value; - params.velocity_out = parameters.velocity_out.value; - }, + /** + * Store parameter values locally for quick access by the process function. + * @param {Object} parameters Processor's paramer data in state. + */ + updateAllParams = parameters => { + params.steps = parameters.steps.value; + params.pulses = parameters.pulses.value; + params.rotation = parameters.rotation.value; + params.isTriplets = parameters.is_triplets.value; + params.rate = parameters.rate.value; + params.note_length = parameters.note_length.value; + params.is_mute = parameters.is_mute.value; + params.channel_out = parameters.channel_out.value; + params.pitch_out = parameters.pitch_out.value; + params.velocity_out = parameters.velocity_out.value; + }, - /** - * After a change of the steps parameter update the pulses and rotation parameters. - */ - updatePulsesAndRotation = function() { - store.dispatch(store.getActions().recreateParameter(my.id, 'pulses', { - max: params.steps, - value: Math.min(params.pulses, params.steps), - })); - store.dispatch(store.getActions().recreateParameter(my.id, 'rotation', { - max: params.steps - 1, - value: Math.min(params.rotation, params.steps - 1), - })); - - store.dispatch(store.getActions().changeParameter(my.id, 'pulses', params.pulses)); - store.dispatch(store.getActions().changeParameter(my.id, 'rotation', params.rotation)); - }, - - /** - * Update all pattern properties. - * @param {Boolean} isEuclidChange Steps, pulses or rotation change. - */ - updatePattern = function(isEuclidChange) { - // euclidean pattern properties, changes in steps, pulses, rotation - if (isEuclidChange) { - euclidPattern = getEuclidPattern(params.steps, params.pulses); - euclidPattern = rotateEuclidPattern(euclidPattern, params.rotation); - } - - // playback properties, changes in isTriplets, rate, noteLength - var rate = params.is_triplets ? params.rate * (2 / 3) : params.rate, - stepDuration = rate * PPQN; - noteDuration = params.note_length * PPQN; - duration = params.steps * stepDuration; - position = position % duration; - - // create array of note start times in ticks - pulsesOnly.length = 0; - var n = euclidPattern.length; - for (var i = 0; i < n; i++) { - if (euclidPattern[i]) { - pulsesOnly.push({ - startTime: i * stepDuration, - stepIndex: i - }); - } - } - }; + /** + * After a change of the steps parameter update the pulses and rotation parameters. + */ + updatePulsesAndRotation = () => { + dispatch(getActions().recreateParameter(my.id, 'pulses', { + max: params.steps, + value: Math.min(params.pulses, params.steps), + })); + dispatch(getActions().recreateParameter(my.id, 'rotation', { + max: params.steps - 1, + value: Math.min(params.rotation, params.steps - 1), + })); + + dispatch(getActions().changeParameter(my.id, 'pulses', params.pulses)); + dispatch(getActions().changeParameter(my.id, 'rotation', params.rotation)); + }, + + /** + * Update all pattern properties. + * @param {Boolean} isEuclidChange Steps, pulses or rotation change. + */ + updatePattern = isEuclidChange => { - my = my || {}; - - that = createMIDIProcessorBase(specs, my); + // euclidean pattern properties, changes in steps, pulses, rotation + if (isEuclidChange) { + euclidPattern = getEuclidPattern(params.steps, params.pulses); + euclidPattern = rotateEuclidPattern(euclidPattern, params.rotation); + } + + // playback properties, changes in isTriplets, rate, noteLength + const rate = params.is_triplets ? params.rate * (2 / 3) : params.rate; + const stepDuration = rate * PPQN; + noteDuration = params.note_length * PPQN; + duration = params.steps * stepDuration; + position = position % duration; + + // create array of note start times in ticks + pulsesOnly.length = 0; - initialize(); + for (var i = 0, n = euclidPattern.length; i < n; i++) { + if (euclidPattern[i]) { + pulsesOnly.push({ + startTime: i * stepDuration, + stepIndex: i + }); + } + } + }; + + that = createMIDIProcessorBase(data, that, my); - that.terminate = terminate; - that.process = process; - return that; + initialize(data); + + that.terminate = terminate; + that.process = process; + return that; } \ No newline at end of file From 835846971a5fcdfa1f065c0b116b98adc2577080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Mon, 14 Oct 2019 15:15:37 +0200 Subject: [PATCH 017/131] Processor base modules refactored. --- src/js/midi/connectorin.js | 119 +++++++++++++-------------- src/js/midi/connectorout.js | 153 ++++++++++++++++------------------- src/js/midi/processorbase.js | 48 +++++------ 3 files changed, 149 insertions(+), 171 deletions(-) diff --git a/src/js/midi/connectorin.js b/src/js/midi/connectorin.js index d39809e7..ff4830e5 100644 --- a/src/js/midi/connectorin.js +++ b/src/js/midi/connectorin.js @@ -1,68 +1,61 @@ /** * MIDI network processor in connector. */ -export default function createMIDIConnectorIn(specs, my) { - var that, - sources = [], - numSources = 0, - outputData = [], - data, - - /** - * Collects data from all processors this input is connected to. - * @return {Array} MIDI event data from all connected processors. - */ - getInputData = function() { - outputData.length = 0; - - for (let i = 0; i < numSources; i++) { - data = sources[i].getOutputData(); - for (let j = 0, p = data.length; j < p; j++) { - outputData.push({ ...data[j] }); - } - } - return outputData; - }, - - /** - * Connect a processor as source for this processor. - * @param {Object} processor Network MIDI processor. - */ - addConnection = function(processor) { - sources.push(processor); - numSources = sources.length; - }, - - /** - * Remove a processor as source for this processor. - * @param {Object} processor Network MIDI processor. - */ - removeConnection = function(processor) { - var n = numSources; - while (--n >= 0) { - if (processor === sources[n]) { - sources.splice(n, 1); - numSources = sources.length; - break; - } - } - }, - - /** - * Get number of connections. - * Used by the output port module to determine if - * @return {Number} Number of connections to this output processor. - */ - hasInputConnections = function() { - return numSources > 0; - }; - - my = my || {}; - my.getInputData = getInputData; +export default function createMIDIConnectorIn(data, that = {}, my = {}) { + const sources = [], + outputData = [], + + /** + * Collects data from all processors this input is connected to. + * @return {Array} MIDI event data from all connected processors. + */ + getInputData = function() { + outputData.length = 0; - that = specs.that || {}; - that.addConnection = addConnection; - that.removeConnection = removeConnection; - that.hasInputConnections = hasInputConnections; - return that; + sources.forEach(source => { + source.getOutputData().forEach(data => { + outputData.push({ ...data }); + }); + }); + + return outputData; + }, + + /** + * Connect a processor as source for this processor. + * @param {Object} processor Network MIDI processor. + */ + addConnection = function(processor) { + sources.push(processor); + }, + + /** + * Remove a processor as source for this processor. + * @param {Object} processor Network MIDI processor. + */ + removeConnection = function(processor) { + let i = sources.length; + while (--i >= 0) { + if (processor === sources[i]) { + sources.splice(i, 1); + break; + } + } + }, + + /** + * Get number of connections. + * Used by the output port module to determine if + * @return {Number} Number of connections to this output processor. + */ + hasInputConnections = function() { + return sources.length > 0; + }; + + my.getInputData = getInputData; + + that.addConnection = addConnection; + that.removeConnection = removeConnection; + that.hasInputConnections = hasInputConnections; + return that; } diff --git a/src/js/midi/connectorout.js b/src/js/midi/connectorout.js index b00e3d05..adea07b9 100644 --- a/src/js/midi/connectorout.js +++ b/src/js/midi/connectorout.js @@ -1,87 +1,76 @@ /** * MIDI network processor out connector. */ -export default function createMIDIConnectorOut(specs, my) { - var that, - outputData = [], - destinations = [], +export default function createMIDIConnectorOut(data, that = {}, my = {}) { + const outputData = [], + destinations = [], - /** - * Clear the output stack when event processing starts. - */ - clearOutputData = function() { - outputData.length = 0; - }, - - /** - * Set output data that is the result of this processor's processing. - * It will be collected by the processors attached to this output. - * @param {Object} eventData MIDI event data. - */ - setOutputData = function(eventData) { - outputData.push(eventData); - }, - - /** - * Public function for processors connected to this output to - * collect the data this processor's process function has produced. - * @return {Object} MIDI event data. - */ - getOutputData = function() { - return outputData; - }, - - /** - * Connect this processor's output to another processor's input. - * @param {Object} processor Processor to connect to. - */ - connect = function(processor) { - var isConnected = false, - n = destinations.length; - for (var i = 0; i < n; i++) { - if (processor === destinations[i]) { - isConnected = true; - break; - } - } - if (!isConnected) { - processor.addConnection(that); - destinations.push(processor); - } - }, - - /** - * Disconnect this processor's output from another processor's input. - * @param {Object} processor Processor to disconnect from, or undefined to remove all. - */ - disconnect = function(processor) { - var n = destinations.length; - while (--n >= 0) { - if (!processor || (processor && processor === destinations[n])) { - destinations[n].removeConnection(that); - destinations.splice(n, 1); - } - } - }, - - /** - * Get destination processors. - * Used to draw the connection cables on canvas. - * @return {Array} Processors this output connects to. - */ - getDestinations = function() { - return destinations; - }; - - my = my || {}; - my.clearOutputData = clearOutputData; - my.setOutputData = setOutputData; - - that = specs.that || {}; - - that.getDestinations = getDestinations; - that.getOutputData = getOutputData; - that.connect = connect; - that.disconnect = disconnect; - return that; + /** + * Clear the output stack when event processing starts. + */ + clearOutputData = function() { + outputData.length = 0; + }, + + /** + * Set output data that is the result of this processor's processing. + * It will be collected by the processors attached to this output. + * @param {Object} eventData MIDI event data. + */ + setOutputData = function(eventData) { + outputData.push(eventData); + }, + + /** + * Public function for processors connected to this output to + * collect the data this processor's process function has produced. + * @return {Object} MIDI event data. + */ + getOutputData = function() { + return outputData; + }, + + /** + * Connect this processor's output to another processor's input. + * @param {Object} processor Processor to connect to. + */ + connect = function(processor) { + const isConnected = destinations.find(destination => destination === processor); + if (!isConnected) { + processor.addConnection(that); + destinations.push(processor); + } + }, + + /** + * Disconnect this processor's output from another processor's input. + * @param {Object} processor Processor to disconnect from, or undefined to remove all. + */ + disconnect = function(processor) { + var n = destinations.length; + while (--n >= 0) { + if (!processor || (processor && processor === destinations[n])) { + destinations[n].removeConnection(that); + destinations.splice(n, 1); + } + } + }, + + /** + * Get destination processors. + * Used to draw the connection cables on canvas. + * @return {Array} Processors this output connects to. + */ + getDestinations = function() { + return destinations; + }; + + my.clearOutputData = clearOutputData; + my.setOutputData = setOutputData; + + that.getDestinations = getDestinations; + that.getOutputData = getOutputData; + that.connect = connect; + that.disconnect = disconnect; + return that; } diff --git a/src/js/midi/processorbase.js b/src/js/midi/processorbase.js index aa86e81e..60ab5dbe 100644 --- a/src/js/midi/processorbase.js +++ b/src/js/midi/processorbase.js @@ -4,30 +4,26 @@ import createMIDIConnectorOut from './connectorout.js'; /** * Base functionality for all MIDI processors. */ -export default function createMIDIProcessorBase(specs, my) { - var that, - - getType = function() { - return my.type; - }, - - getID = function() { - return my.id; - }; - - my = my || {}; - my.type = specs.data.type; - my.id = specs.data.id; - - that = specs.that || {}; - if (specs.data.inputs.allIds.length >= 1) { - that = createMIDIConnectorIn(specs, my); - } - if (specs.data.outputs.allIds.length >= 1) { - that = createMIDIConnectorOut(specs, my); - } - that.getType = getType; - that.getID = getID; - - return that; +export default function createMIDIProcessorBase(data, that = {}, my = {}) { + const getType = () => { + return my.type; + }, + + getID = () => { + return my.id; + }; + + my.type = data.type; + my.id = data.id; + + if (data.inputs.allIds.length >= 1) { + that = createMIDIConnectorIn(data, that, my); + } + if (data.outputs.allIds.length >= 1) { + that = createMIDIConnectorOut(data, that, my); + } + that.getType = getType; + that.getID = getID; + + return that; } From 25a9ec62f0abef452b61926e27c29278850b536c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Mon, 14 Oct 2019 17:43:08 +0200 Subject: [PATCH 018/131] EuclidFX precessor refactored. --- src/js/main.js | 3 +- .../processors/euclidfx/object3dController.js | 1 + src/js/processors/euclidfx/processor.js | 559 +++++++++--------- src/js/view/panels.js | 2 +- 4 files changed, 286 insertions(+), 279 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 9d5269e0..4c944345 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -21,11 +21,11 @@ import { dispatch, getActions, getState, persist, } from './state/store.js'; // import createAppView from './view/app.js'; // import createDialog from './view/dialog.js'; import { accessMidi } from './midi/midi.js'; -import createMIDINetwork from './midi/network.js'; import { setup as setupCanvas3d } from './webgl/canvas3d.js'; import { setup as setupConnections3d } from './webgl/connections3d.js'; import { setup as setupControls } from './view/controls.js'; import { setup as setupLibrary } from './view/library.js'; +import { setup as setupNetwork } from './midi/network.js'; import { setup as setupPanels } from './view/panels.js'; import { preloadProcessors } from './core/processor-loader.js'; import createPreferencesView from './view/preferences.js'; @@ -42,6 +42,7 @@ async function main() { setupCanvas3d(); setupConnections3d(); setupLibrary(); + setupNetwork(); persist(); } diff --git a/src/js/processors/euclidfx/object3dController.js b/src/js/processors/euclidfx/object3dController.js index 4c353c6f..48c23288 100644 --- a/src/js/processors/euclidfx/object3dController.js +++ b/src/js/processors/euclidfx/object3dController.js @@ -13,6 +13,7 @@ const TWO_PI = Math.PI * 2; export function createObject3dController(specs, my) { let that, + centreCircle3d, centreDot3d, select3d, pointer3d, diff --git a/src/js/processors/euclidfx/processor.js b/src/js/processors/euclidfx/processor.js index 4943a414..d31f6f0f 100644 --- a/src/js/processors/euclidfx/processor.js +++ b/src/js/processors/euclidfx/processor.js @@ -1,305 +1,310 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import createMIDIProcessorBase from '../../midi/processorbase.js'; import { PPQN } from '../../core/config.js'; import { getEuclidPattern, rotateEuclidPattern } from './utils.js'; -export function createProcessor(specs, my) { - let that, - store = specs.store, - position = 0, - duration = 0, - stepDuration = 0, - euclidPattern = [], - params = {}, - delayedEvents = []; +export function createProcessor(data, my = {}) { + let that, + duration = 0, + stepDuration = 0, + euclidPattern = [], + params = {}, + delayedEvents = []; - const initialize = function() { - document.addEventListener(store.STATE_CHANGE, handleStateChanges); - updateAllParams(specs.data.params.byId); - updatePattern(true); - }, + const initialize = function() { + document.addEventListener(STATE_CHANGE, handleStateChanges); + updateAllParams(data.params.byId); + updatePattern(true); + }, - terminate = function() { - document.removeEventListener(store.STATE_CHANGE, handleStateChanges); - }, + terminate = function() { + document.removeEventListener(STATE_CHANGE, handleStateChanges); + }, - handleStateChanges = function(e) { - switch (e.detail.action.type) { - case e.detail.actions.CHANGE_PARAMETER: - if (e.detail.action.processorID === my.id) { - updateAllParams(e.detail.state.processors.byId[my.id].params.byId); - switch (e.detail.action.paramKey) { - case 'steps': - updatePulsesAndRotation(); - updatePattern(true); - break; - case 'pulses': - case 'rotation': - updatePattern(true); - break; - case 'is_triplets': - case 'rate': - updatePattern(); - break; - case 'target': - case 'mode': - updateEffectSettings(); - break; - } - } - break; + /** + * Handle state changes. + * @param {Object} e + */ + handleStateChanges = e => { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.CHANGE_PARAMETER: + if (action.processorID === my.id) { + updateAllParams(state.processors.byId[my.id].params.byId); + switch (action.paramKey) { + case 'steps': + updatePulsesAndRotation(); + updatePattern(true); + break; + case 'pulses': + case 'rotation': + updatePattern(true); + break; + case 'is_triplets': + case 'rate': + updatePattern(); + break; + case 'target': + case 'mode': + updateEffectSettings(); + break; + } + } + break; - case e.detail.actions.RECREATE_PARAMETER: - if (e.detail.action.processorID === my.id) { - updateAllParams(e.detail.state.processors.byId[my.id].params.byId); - } - break; - } - }, - - /** - * Process events to happen in a time slice. This will - * - Get events waiting at the input - * - Process them according to the current parameter settings. - * - Send the processed events to the output. - * - Add the events to the processorEvents parameter for display in the view. - * - * Events are plain objects with properties: - * @param {String} type 'note' - * @param {Number} timestampTicks Event start time, measured from timeline start - * @param {Number} durationTicks - * @param {Number} channel 1 - 16 - * @param {Number} velocity 0 - 127 - * @param {Number} pitch 0 - 127 - * - * This method's parameters: - * @param {Number} scanStart Timespan start in ticks from timeline start. - * @param {Number} scanEnd Timespan end in ticks from timeline start. - * @param {Number} nowToScanStart Timespan from current timeline position to scanStart, in ticks - * @param {Number} ticksToMsMultiplier Duration of one tick in milliseconds. - * @param {Number} offset Time from doc start to timeline start in ticks. - * @param {Array} processorEvents Array to collect processor generated events to display in the view. - */ - process = function(scanStart, scanEnd, nowToScanStart, ticksToMsMultiplier, offset, processorEvents) { - - // clear the output event stack - my.clearOutputData(); + case actions.RECREATE_PARAMETER: + if (action.processorID === my.id) { + updateAllParams(state.processors.byId[my.id].params.byId); + } + break; + } + }, + + /** + * Process events to happen in a time slice. This will + * - Get events waiting at the input + * - Process them according to the current parameter settings. + * - Send the processed events to the output. + * - Add the events to the processorEvents parameter for display in the view. + * + * Events are plain objects with properties: + * @param {String} type 'note' + * @param {Number} timestampTicks Event start time, measured from timeline start + * @param {Number} durationTicks + * @param {Number} channel 1 - 16 + * @param {Number} velocity 0 - 127 + * @param {Number} pitch 0 - 127 + * + * This method's parameters: + * @param {Number} scanStart Timespan start in ticks from timeline start. + * @param {Number} scanEnd Timespan end in ticks from timeline start. + * @param {Number} nowToScanStart Timespan from current timeline position to scanStart, in ticks + * @param {Number} ticksToMsMultiplier Duration of one tick in milliseconds. + * @param {Number} offset Time from doc start to timeline start in ticks. + * @param {Array} processorEvents Array to collect processor generated events to display in the view. + */ + process = function(scanStart, scanEnd, nowToScanStart, ticksToMsMultiplier, offset, processorEvents) { + + // clear the output event stack + my.clearOutputData(); - // retrieve events waiting at the processor's input - const inputData = my.getInputData(); + // retrieve events waiting at the processor's input + const inputData = my.getInputData(); - // abort if there's nothing to process - if (inputData.length === 0) { - processDelayedEvents(scanStart, scanEnd); - return; - } + // abort if there's nothing to process + if (inputData.length === 0) { + processDelayedEvents(scanStart, scanEnd); + return; + } - // calculate the processed timespan's position within the pattern, - // taking into account the pattern looping during this timespan. - var localScanStart = scanStart % duration, - localScanEnd = scanEnd % duration, - localScanStart2 = false, - localScanEnd2; - if (localScanStart > localScanEnd) { - localScanStart2 = 0, - localScanEnd2 = localScanEnd; - localScanEnd = duration; - } + // calculate the processed timespan's position within the pattern, + // taking into account the pattern looping during this timespan. + let localScanStart = scanStart % duration, + localScanEnd = scanEnd % duration, + localScanStart2 = false, + localScanEnd2; + if (localScanStart > localScanEnd) { + localScanStart2 = 0; + localScanEnd2 = localScanEnd; + localScanEnd = duration; + } - for (let i = 0, n = inputData.length; i < n; i++) { - const event = inputData[i]; + inputData.forEach(event => { + let isDelayed = false; - let isDelayed = false; + // handle only MIDI Note events + if (event.type === 'note') { - // handle only MIDI Note events - if (event.type === 'note') { + // calculate the state of the effect at the event's time within the pattern + const stepIndex = Math.floor((event.timestampTicks % duration) / stepDuration), + state = euclidPattern[stepIndex], + effectValue = state ? params.high : params.low; + + // apply the effect to the event's target parameter + switch (params.target) { + case 'velocity': + event.velocity = params.isRelative ? event.velocity + effectValue : effectValue; + event.velocity = Math.max(0, Math.min(event.velocity, 127)); + break; + case 'pitch': + event.pitch = params.isRelative ? event.pitch + effectValue : effectValue; + event.pitch = Math.max(0, Math.min(event.pitch, 127)); + break; + case 'channel': + event.channel = params.isRelative ? event.channel + effectValue : effectValue; + event.channel = Math.max(1, Math.min(event.channel, 16)); + break; + case 'length': + const valueInTicks = (effectValue / 32) * PPQN * 4; // max 32 == 1 measure == PPQN * 4 + event.durationTicks = params.isRelative ? event.durationTicks + valueInTicks : valueInTicks; + event.durationTicks = Math.max(1, event.durationTicks); + break; + case 'delay': + if (effectValue > 0) { + const delayInTicks = Math.max(0, (effectValue / 32) * PPQN * 0.25); // 32 == 1 beat == PPQN + + // store note if delayed start time falls outside of the current scan range + if (event.timestampTicks + delayInTicks > scanEnd) { + delayedEvents.push({ + ...event, + timestampTicks: event.timestampTicks + delayInTicks + }); + isDelayed = true; + } else { + event.timestampTicks = event.timestampTicks + delayInTicks; + } + } + break; + case 'output': + // v2.2 + break; + } - // calculate the state of the effect at the event's time within the pattern - const stepIndex = Math.floor((event.timestampTicks % duration) / stepDuration), - state = euclidPattern[stepIndex], - effectValue = state ? params.high : params.low; - - // apply the effect to the event's target parameter - switch (params.target) { - case 'velocity': - event.velocity = params.isRelative ? event.velocity + effectValue : effectValue; - event.velocity = Math.max(0, Math.min(event.velocity, 127)); - break; - case 'pitch': - event.pitch = params.isRelative ? event.pitch + effectValue : effectValue; - event.pitch = Math.max(0, Math.min(event.pitch, 127)); - break; - case 'channel': - event.channel = params.isRelative ? event.channel + effectValue : effectValue; - event.channel = Math.max(1, Math.min(event.channel, 16)); - break; - case 'length': - const valueInTicks = (effectValue / 32) * PPQN * 4; // max 32 == 1 measure == PPQN * 4 - event.durationTicks = params.isRelative ? event.durationTicks + valueInTicks : valueInTicks; - event.durationTicks = Math.max(1, event.durationTicks); - break; - case 'delay': - if (effectValue > 0) { - const delayInTicks = Math.max(0, (effectValue / 32) * PPQN * 0.25); // 32 == 1 beat == PPQN - - // store note if delayed start time falls outside of the current scan range - if (event.timestampTicks + delayInTicks > scanEnd) { - delayedEvents.push({ - ...event, - timestampTicks: event.timestampTicks + delayInTicks - }); - isDelayed = true; - } else { - event.timestampTicks = event.timestampTicks + delayInTicks; - } - } - break; - case 'output': - // v2.2 - break; - } + // add events to processorEvents for the canvas to show them + if (!processorEvents[my.id]) { + processorEvents[my.id] = []; + } + + const delayFromNowToNoteStart = (event.timestampTicks - scanStart) * ticksToMsMultiplier; + processorEvents[my.id].push({ + stepIndex: stepIndex, + delayFromNowToNoteStart: delayFromNowToNoteStart, + delayFromNowToNoteEnd: delayFromNowToNoteStart + (event.durationTicks * ticksToMsMultiplier) + }); - // add events to processorEvents for the canvas to show them - if (!processorEvents[my.id]) { - processorEvents[my.id] = []; - } - - const delayFromNowToNoteStart = (event.timestampTicks - scanStart) * ticksToMsMultiplier; - processorEvents[my.id].push({ - stepIndex: stepIndex, - delayFromNowToNoteStart: delayFromNowToNoteStart, - delayFromNowToNoteEnd: delayFromNowToNoteStart + (event.durationTicks * ticksToMsMultiplier) - }); + // push the event to the processor's output + if (!isDelayed) { + my.setOutputData(event); + } + } + }); - // push the event to the processor's output - if (!isDelayed) { - my.setOutputData(event); - } - } - } + processDelayedEvents(scanStart, scanEnd); + }, + + /** + * Check if stored delayed events + * @param {Number} scanStart Timespan start in ticks from timeline start. + * @param {Number} scanEnd Timespan end in ticks from timeline start. + */ + processDelayedEvents = function(scanStart, scanEnd) { + let i = delayedEvents.length; + while (--i > -1) { + const timestampTicks = delayedEvents[i].timestampTicks; + if (scanStart <= timestampTicks && scanEnd > timestampTicks) { + my.setOutputData(delayedEvents.splice(i, 1)[0]); + } + } + }, - processDelayedEvents(scanStart, scanEnd); - }, - - /** - * Check if stored delayed events - * @param {Number} scanStart Timespan start in ticks from timeline start. - * @param {Number} scanEnd Timespan end in ticks from timeline start. - */ - processDelayedEvents = function(scanStart, scanEnd) { - var i = delayedEvents.length; - while (--i > -1) { - const timestampTicks = delayedEvents[i].timestampTicks; - if (scanStart <= timestampTicks && scanEnd > timestampTicks) { - my.setOutputData(delayedEvents.splice(i, 1)[0]); - } - } - }, + /** + * After a change of the steps parameter update the pulses and rotation parameters. + */ + updatePulsesAndRotation = function() { + dispatch(getActions().recreateParameter(my.id, 'pulses', { + max: params.steps, + value: Math.min(params.pulses, params.steps), + })); + dispatch(getActions().recreateParameter(my.id, 'rotation', { + max: params.steps - 1, + value: Math.min(params.rotation, params.steps - 1), + })); - /** - * After a change of the steps parameter update the pulses and rotation parameters. - */ - updatePulsesAndRotation = function() { - store.dispatch(store.getActions().recreateParameter(my.id, 'pulses', { - max: params.steps, - value: Math.min(params.pulses, params.steps), - })); - store.dispatch(store.getActions().recreateParameter(my.id, 'rotation', { - max: params.steps - 1, - value: Math.min(params.rotation, params.steps - 1), - })); + dispatch(getActions().changeParameter(my.id, 'pulses', params.pulses)); + dispatch(getActions().changeParameter(my.id, 'rotation', params.rotation)); + }, + + /** + * Update all pattern properties. + * @param {Boolean} isEuclidChange Steps, pulses or rotation change. + */ + updatePattern = function(isEuclidChange) { - store.dispatch(store.getActions().changeParameter(my.id, 'pulses', params.pulses)); - store.dispatch(store.getActions().changeParameter(my.id, 'rotation', params.rotation)); - }, - - /** - * Update all pattern properties. - * @param {Boolean} isEuclidChange Steps, pulses or rotation change. - */ - updatePattern = function(isEuclidChange) { - // euclidean pattern properties, changes in steps, pulses, rotation - if (isEuclidChange) { - euclidPattern = getEuclidPattern(params.steps, params.pulses); - euclidPattern = rotateEuclidPattern(euclidPattern, params.rotation); - } - - // playback properties, changes in isTriplets and rate - var rate = params.isTriplets ? params.rate * (2 / 3) : params.rate; - stepDuration = rate * PPQN; - duration = params.steps * stepDuration; - }, + // euclidean pattern properties, changes in steps, pulses, rotation + if (isEuclidChange) { + euclidPattern = getEuclidPattern(params.steps, params.pulses); + euclidPattern = rotateEuclidPattern(euclidPattern, params.rotation); + } + + // playback properties, changes in isTriplets and rate + const rate = params.isTriplets ? params.rate * (2 / 3) : params.rate; + stepDuration = rate * PPQN; + duration = params.steps * stepDuration; + }, - updateAllParams = function(parameters) { - params.steps = parameters.steps.value; - params.pulses = parameters.pulses.value; - params.rotation = parameters.rotation.value; - params.isTriplets = parameters.is_triplets.value; - params.rate = parameters.rate.value; - params.high = parameters.high.value; - params.low = parameters.low.value; - params.target = parameters.target.value; - params.isRelative = parameters.mode.value !== parameters.mode.default; - }, - - updateEffectSettings = function() { - let min, max, lowValue, highValue; + /** + * Store parameter values locally for quick access by the process function. + * @param {Object} parameters Processor's paramer data in state. + */ + updateAllParams = function(parameters) { + params.steps = parameters.steps.value; + params.pulses = parameters.pulses.value; + params.rotation = parameters.rotation.value; + params.isTriplets = parameters.is_triplets.value; + params.rate = parameters.rate.value; + params.high = parameters.high.value; + params.low = parameters.low.value; + params.target = parameters.target.value; + params.isRelative = parameters.mode.value !== parameters.mode.default; + }, + + updateEffectSettings = function() { + let min, max, lowValue, highValue; - // set minimum and maximum value according to target type - switch (params.target) { - case 'velocity': - min = params.isRelative ? -127 : 0; - max = 127; - lowValue = params.isRelative ? 0 : 50; - highValue = params.isRelative ? 0 : 100; - break; - case 'pitch': - min = params.isRelative ? -127 : 0; - max = 127; - lowValue = params.isRelative ? 0 : 58; - highValue = params.isRelative ? 0 : 60; - break; - case 'channel': - min = params.isRelative ? -16 : 1; - max = 16; - lowValue = params.isRelative ? 0 : 1; - highValue = params.isRelative ? 0 : 2; - break; - case 'length': - min = params.isRelative ? -32 : 0; - max = 32; - lowValue = params.isRelative ? 0 : 4; - highValue = params.isRelative ? 0 : 8; - break; - case 'delay': - min = params.isRelative ? 0 : 0; - max = 32; - lowValue = params.isRelative ? 0 : 0; - highValue = params.isRelative ? 0 : 2; - break; - case 'output': - min = 1; - max = 2; - lowValue = 1; - highValue = 2; - break; - } + // set minimum and maximum value according to target type + switch (params.target) { + case 'velocity': + min = params.isRelative ? -127 : 0; + max = 127; + lowValue = params.isRelative ? 0 : 50; + highValue = params.isRelative ? 0 : 100; + break; + case 'pitch': + min = params.isRelative ? -127 : 0; + max = 127; + lowValue = params.isRelative ? 0 : 58; + highValue = params.isRelative ? 0 : 60; + break; + case 'channel': + min = params.isRelative ? -16 : 1; + max = 16; + lowValue = params.isRelative ? 0 : 1; + highValue = params.isRelative ? 0 : 2; + break; + case 'length': + min = params.isRelative ? -32 : 0; + max = 32; + lowValue = params.isRelative ? 0 : 4; + highValue = params.isRelative ? 0 : 8; + break; + case 'delay': + min = params.isRelative ? 0 : 0; + max = 32; + lowValue = params.isRelative ? 0 : 0; + highValue = params.isRelative ? 0 : 2; + break; + case 'output': + min = 1; + max = 2; + lowValue = 1; + highValue = 2; + break; + } - // clamp parameter's value between minimum and maximum value - lowValue = Math.max(min, Math.min(lowValue, max)); - highValue = Math.max(min, Math.min(highValue, max)); + // clamp parameter's value between minimum and maximum value + lowValue = Math.max(min, Math.min(lowValue, max)); + highValue = Math.max(min, Math.min(highValue, max)); - // apply all new settings to the effect parameters - store.dispatch(store.getActions().recreateParameter(my.id, 'low', { min: min, max: max, value: lowValue })); - store.dispatch(store.getActions().recreateParameter(my.id, 'high', { min: min, max: max, value: highValue })); - }; + // apply all new settings to the effect parameters + dispatch(getActions().recreateParameter(my.id, 'low', { min: min, max: max, value: lowValue })); + dispatch(getActions().recreateParameter(my.id, 'high', { min: min, max: max, value: highValue })); + }; - my = my || {}; - - that = createMIDIProcessorBase(specs, my); + that = createMIDIProcessorBase(data, that, my); - initialize(); + initialize(); - that.terminate = terminate; - that.process = process; - return that; + that.terminate = terminate; + that.process = process; + return that; } diff --git a/src/js/view/panels.js b/src/js/view/panels.js index 50a6927c..97027cff 100644 --- a/src/js/view/panels.js +++ b/src/js/view/panels.js @@ -3,7 +3,7 @@ import createSettingsPanel from './settings.js'; import addWindowResizeCallback from '../view/windowresize.js'; import { getProcessorData } from '../core/processor-loader.js'; -const settingsViews = []; +let settingsViews = []; const panelsEl = document.querySelector('.panels'); const libraryEl = document.querySelector('.library'); const helpEl = document.querySelector('.help'); From c53e403d93daf220e9becaba4e418945ab182bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Mon, 14 Oct 2019 17:53:28 +0200 Subject: [PATCH 019/131] Output processor refactored. --- .../processors/output/object3dController.js | 1 + src/js/processors/output/processor.js | 274 +++++++++--------- 2 files changed, 137 insertions(+), 138 deletions(-) diff --git a/src/js/processors/output/object3dController.js b/src/js/processors/output/object3dController.js index 41147b52..e1470e0b 100644 --- a/src/js/processors/output/object3dController.js +++ b/src/js/processors/output/object3dController.js @@ -4,6 +4,7 @@ import createObject3dControllerBase from '../../webgl/object3dControllerBase.js' export function createObject3dController(specs, my) { let that, + centreCircle3d, select3d, initialize = function() { diff --git a/src/js/processors/output/processor.js b/src/js/processors/output/processor.js index dcd1b18a..34bc003b 100644 --- a/src/js/processors/output/processor.js +++ b/src/js/processors/output/processor.js @@ -1,145 +1,143 @@ +import { dispatch, getActions, getState, STATE_CHANGE, } from '../../state/store.js'; import createMIDIProcessorBase from '../../midi/processorbase.js'; import { getMIDIPortByID } from '../../midi/midi.js'; /** * MIDI output port processor. */ -export function createProcessor(specs, my) { - let that, - store = specs.store, - midiOutput, - params = {}; - - const initialize = function() { - document.addEventListener(store.STATE_CHANGE, handleStateChange); - updatePortsParameter(store.getState()); - updateAllParams(store.getState().processors.byId[my.id].params.byId); - updateMIDIPort(); - }, - - terminate = function() { - document.removeEventListener(store.STATE_CHANGE, handleStateChange); - }, - - handleStateChange = function(e) { - switch (e.detail.action.type) { - - case e.detail.actions.CHANGE_PARAMETER: - if (e.detail.action.processorID === my.id) { - updateAllParams(e.detail.state.processors.byId[my.id].params.byId); - switch (e.detail.action.paramKey) { - case 'port': - updateMIDIPort(); - break; - } - } - break; - - case e.detail.actions.CREATE_MIDI_PORT: - case e.detail.actions.UPDATE_MIDI_PORT: - case e.detail.actions.TOGGLE_MIDI_PREFERENCE: - updatePortsParameter(e.detail.state); - break; - } - }, - - /** - * Process events to happen in a time slice. - * @param {Number} scanStart Timespan start in ticks from timeline start. - * @param {Number} scanEnd Timespan end in ticks from timeline start. - * @param {Number} nowToScanStart Timespan from current timeline position to scanStart. - * @param {Number} ticksToMsMultiplier Duration of one tick in milliseconds. - * @param {Number} offset Time from doc start to timeline start in ticks. - */ - process = function(scanStart, scanEnd, nowToScanStart, ticksToMsMultiplier, offset) { - - // retrieve events waiting at the processor's input - const inputData = my.getInputData(), - origin = performance.now() - (offset * ticksToMsMultiplier), - n = inputData.length; - - if (midiOutput && midiOutput.state === 'connected') { - for (var i = 0; i < n; i++) { - let item = inputData[i], - - // item.timestampTicks is time since transport play started - timestamp = origin + (item.timestampTicks * ticksToMsMultiplier), - duration = item.durationTicks * ticksToMsMultiplier; - - switch (item.type) { - case 'note': - midiOutput.send([0x90 + (item.channel - 1), item.pitch, item.velocity], timestamp); - midiOutput.send([0x80 + (item.channel - 1), item.pitch, 0], timestamp + duration); - break; - } - } - } - }, - - updateAllParams = function(parameters) { - params.port = parameters.port.value; - params.portName = parameters.port.model.find(element => element.value === params.port).label; - }, - - /** - * Retrieve the MIDI port the MIDI notes are sent to. - * After a port parameter change. - */ - updateMIDIPort = function() { - midiOutput = getMIDIPortByID(params.port); - - // update the processor's name parameter - store.dispatch(store.getActions().changeParameter(my.id, 'name', params.portName)); - }, - - /** - * Update the ports parameter with the current available ports. - */ - updatePortsParameter = function(state) { - - // rebuild the parameter's model and recreate the parameter - const portsModel = [ - { label: 'No output', value: 'none' } - ]; - state.ports.allIds.forEach(portID => { - const port = state.ports.byId[portID]; - if (port.type === 'output' && port.networkEnabled && port.state === 'connected') { - portsModel.push({ label: port.name, value: port.id }); - } - }); - store.dispatch(store.getActions().recreateParameter(my.id, 'port', { model: portsModel })); - - // set the parameter's value - const recreatedState = store.getState(), - portParam = recreatedState.processors.byId[my.id].params.byId.port, - value = portParam.value, - model = portParam.model; - let item = model.find(element => element.value === value); - item = item || model.find(element => element.value === 'none'); - - store.dispatch(store.getActions().changeParameter(my.id, 'port', item.value)); - store.dispatch(store.getActions().changeParameter(my.id, 'name', item.label)); - }, - - setEnabled = function(isEnabled) { - my.isEnabled = isEnabled; - }, - - getMIDIPortID = function() { - return portID; - }; - - - my = my || {}; - my.isEnabled = true; - - that = createMIDIProcessorBase(specs, my); - - initialize(); - - that.terminate = terminate; - that.process = process; - that.setEnabled = setEnabled; - that.getMIDIPortID = getMIDIPortID; - return that; +export function createProcessor(data, my = {}) { + let that, + midiOutput, + params = {}; + + const initialize = function() { + document.addEventListener(STATE_CHANGE, handleStateChange); + updatePortsParameter(getState()); + updateAllParams(getState().processors.byId[my.id].params.byId); + updateMIDIPort(); + }, + + terminate = function() { + document.removeEventListener(STATE_CHANGE, handleStateChange); + }, + + handleStateChange = function(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.CHANGE_PARAMETER: + if (action.processorID === my.id) { + updateAllParams(state.processors.byId[my.id].params.byId); + switch (action.paramKey) { + case 'port': + updateMIDIPort(); + break; + } + } + break; + + case actions.CREATE_MIDI_PORT: + case actions.UPDATE_MIDI_PORT: + case actions.TOGGLE_MIDI_PREFERENCE: + updatePortsParameter(state); + break; + } + }, + + /** + * Process events to happen in a time slice. + * @param {Number} scanStart Timespan start in ticks from timeline start. + * @param {Number} scanEnd Timespan end in ticks from timeline start. + * @param {Number} nowToScanStart Timespan from current timeline position to scanStart. + * @param {Number} ticksToMsMultiplier Duration of one tick in milliseconds. + * @param {Number} offset Time from doc start to timeline start in ticks. + */ + process = function(scanStart, scanEnd, nowToScanStart, ticksToMsMultiplier, offset) { + + // retrieve events waiting at the processor's input + const inputData = my.getInputData(), + origin = performance.now() - (offset * ticksToMsMultiplier), + n = inputData.length; + + if (midiOutput && midiOutput.state === 'connected') { + for (var i = 0; i < n; i++) { + const { channel, durationTicks, pitch, timestampTicks, type, velocity, } = inputData[i]; + + // item.timestampTicks is time since transport play started + const timestamp = origin + (timestampTicks * ticksToMsMultiplier); + const duration = durationTicks * ticksToMsMultiplier; + + switch (type) { + case 'note': + midiOutput.send([0x90 + (channel - 1), pitch, velocity], timestamp); + midiOutput.send([0x80 + (channel - 1), pitch, 0], timestamp + duration); + break; + } + } + } + }, + + updateAllParams = function(parameters) { + params.port = parameters.port.value; + params.portName = parameters.port.model.find(element => element.value === params.port).label; + }, + + /** + * Retrieve the MIDI port the MIDI notes are sent to. + * After a port parameter change. + */ + updateMIDIPort = function() { + midiOutput = getMIDIPortByID(params.port); + + // update the processor's name parameter + dispatch(getActions().changeParameter(my.id, 'name', params.portName)); + }, + + /** + * Update the ports parameter with the current available ports. + */ + updatePortsParameter = function(state) { + + // rebuild the parameter's model and recreate the parameter + const portsModel = [ + { label: 'No output', value: 'none' } + ]; + state.ports.allIds.forEach(portID => { + const port = state.ports.byId[portID]; + if (port.type === 'output' && port.networkEnabled && port.state === 'connected') { + portsModel.push({ label: port.name, value: port.id }); + } + }); + dispatch(getActions().recreateParameter(my.id, 'port', { model: portsModel })); + + // set the parameter's value + const recreatedState = getState(), + portParam = recreatedState.processors.byId[my.id].params.byId.port, + value = portParam.value, + model = portParam.model; + let item = model.find(element => element.value === value); + item = item || model.find(element => element.value === 'none'); + + dispatch(getActions().changeParameter(my.id, 'port', item.value)); + dispatch(getActions().changeParameter(my.id, 'name', item.label)); + }, + + setEnabled = function(isEnabled) { + my.isEnabled = isEnabled; + }, + + getMIDIPortID = function() { + return portID; + }; + + my.isEnabled = true; + + that = createMIDIProcessorBase(data, that, my); + + initialize(); + + that.terminate = terminate; + that.process = process; + that.setEnabled = setEnabled; + that.getMIDIPortID = getMIDIPortID; + return that; } From 3a95c55af5ac7e7029968548f89bde99e84f2777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 10:51:18 +0200 Subject: [PATCH 020/131] Preferences panel refactored. --- src/js/main.js | 3 +- src/js/view/midi_base.js | 178 +++++++++++++------------- src/js/view/midi_input.js | 33 +++-- src/js/view/midi_output.js | 33 +++-- src/js/view/preferences.js | 248 +++++++++++++++++++++++++------------ 5 files changed, 286 insertions(+), 209 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 4c944345..5bb27790 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -28,7 +28,7 @@ import { setup as setupLibrary } from './view/library.js'; import { setup as setupNetwork } from './midi/network.js'; import { setup as setupPanels } from './view/panels.js'; import { preloadProcessors } from './core/processor-loader.js'; -import createPreferencesView from './view/preferences.js'; +import { setup as setupPreferences } from './view/preferences.js'; import createRemoteView from './view/remote.js'; import createTransport from './core/transport.js'; import { showDialog } from './view/dialog.js'; @@ -43,6 +43,7 @@ async function main() { setupConnections3d(); setupLibrary(); setupNetwork(); + setupPreferences(); persist(); } diff --git a/src/js/view/midi_base.js b/src/js/view/midi_base.js index 75c8f143..1f43587f 100644 --- a/src/js/view/midi_base.js +++ b/src/js/view/midi_base.js @@ -1,97 +1,95 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; + /** * MIDI input or output port processor view. */ -export default function createMIDIBaseView(specs, my) { - var that, - parentEl = specs.parentEl, - port = specs.port, +export default function createMIDIBaseView(data, that = {}, my = {}) { + const { id, isInput, name, networkEnabled, parentEl, port, remoteEnabled, syncEnabled, } = data; - initialize = function() { - // find template, add clone to midi ports list - let template = document.querySelector('#template-midi-port'); - let clone = template.content.cloneNode(true); - my.el = clone.firstElementChild; - parentEl.appendChild(my.el); - - // set data-connected="true" to make the element visible - my.el.dataset.connected = true; - - // show label - my.el.querySelector('.midi-port__label').innerHTML = specs.name; - - // find checkboxes - my.networkEl = my.el.querySelector('.midi-port__network'); - my.syncEl = my.el.querySelector('.midi-port__sync'); - my.remoteEl = my.el.querySelector('.midi-port__remote'); - - // set checkboxes - my.networkEl.querySelector('[type=checkbox]').checked = specs.networkEnabled; - my.syncEl.querySelector('[type=checkbox]').checked = specs.syncEnabled; - my.remoteEl.querySelector('[type=checkbox]').checked = specs.remoteEnabled; - - // add DOM event listeners - my.networkEl.addEventListener('change', function(e) { - if (!e.currentTarget.dataset.disabled) { - my.store.dispatch(my.store.getActions().toggleMIDIPreference(my.id, 'networkEnabled')); - } - }); - my.syncEl.addEventListener('change', function(e) { - if (!e.currentTarget.dataset.disabled) { - my.store.dispatch(my.store.getActions().toggleMIDIPreference(my.id, 'syncEnabled')); - } - }); - my.remoteEl.addEventListener('change', function(e) { - if (!e.currentTarget.dataset.disabled) { - my.store.dispatch(my.store.getActions().toggleMIDIPreference(my.id, 'remoteEnabled')); - } - }); + const initialize = function() { - // listen to state updates - document.addEventListener(my.store.STATE_CHANGE, (e) => { - switch (e.detail.action.type) { + // find template, add clone to midi ports list + const template = document.querySelector('#template-midi-port'); + const clone = template.content.cloneNode(true); + my.el = clone.firstElementChild; + parentEl.appendChild(my.el); + + // set data-connected="true" to make the element visible + my.el.dataset.connected = true; + + // show label + my.el.querySelector('.midi-port__label').innerHTML = name; + + // find checkboxes + my.networkEl = my.el.querySelector('.midi-port__network'); + my.syncEl = my.el.querySelector('.midi-port__sync'); + my.remoteEl = my.el.querySelector('.midi-port__remote'); + + // set checkboxes + my.networkEl.querySelector('[type=checkbox]').checked = networkEnabled; + my.syncEl.querySelector('[type=checkbox]').checked = syncEnabled; + my.remoteEl.querySelector('[type=checkbox]').checked = remoteEnabled; + + // add DOM event listeners + my.networkEl.addEventListener('change', function(e) { + if (!e.currentTarget.dataset.disabled) { + dispatch(getActions().toggleMIDIPreference(my.id, 'networkEnabled')); + } + }); + my.syncEl.addEventListener('change', function(e) { + if (!e.currentTarget.dataset.disabled) { + dispatch(getActions().toggleMIDIPreference(my.id, 'syncEnabled')); + } + }); + my.remoteEl.addEventListener('change', function(e) { + if (!e.currentTarget.dataset.disabled) { + dispatch(getActions().toggleMIDIPreference(my.id, 'remoteEnabled')); + } + }); - case e.detail.actions.TOGGLE_MIDI_PREFERENCE: - case e.detail.actions.CREATE_PROJECT: - const port = e.detail.state.ports.byId[my.id]; - if (port) { - my.networkEl.querySelector('[type=checkbox]').checked = port.networkEnabled; - my.syncEl.querySelector('[type=checkbox]').checked = port.syncEnabled; - my.remoteEl.querySelector('[type=checkbox]').checked = port.remoteEnabled; - } else { - console.log(`MIDI port with id ${my.id} not found.`); - } - break; - } - }); - }, - - /** - * Called before this view is deleted. - */ - terminate = function() { - if (my.el && parentEl) { - parentEl.removeChild(my.el); - } - }, - - getID = function() { - return my.id; - }; - - my = my || {}; - my.store = specs.store; - my.isInput = specs.isInput; - my.id = specs.id; - my.el; - my.networkEl; - my.syncEl; - my.remoteEl; - - that = that || {}; - - initialize(); - - that.terminate = terminate; - that.getID = getID; - return that; + // listen to state updates + document.addEventListener(STATE_CHANGE, e => { + const { state, action, actions, } = e.detail; + switch (action.type) { + + case actions.TOGGLE_MIDI_PREFERENCE: + case actions.CREATE_PROJECT: + const port = state.ports.byId[my.id]; + if (port) { + my.networkEl.querySelector('[type=checkbox]').checked = port.networkEnabled; + my.syncEl.querySelector('[type=checkbox]').checked = port.syncEnabled; + my.remoteEl.querySelector('[type=checkbox]').checked = port.remoteEnabled; + } else { + console.log(`MIDI port with id ${my.id} not found.`); + } + break; + } + }); + }, + + /** + * Called before this view is deleted. + */ + terminate = function() { + if (my.el && parentEl) { + parentEl.removeChild(my.el); + } + }, + + getID = function() { + return my.id; + }; + + my.isInput = isInput; + my.id = id; + my.el; + my.networkEl; + my.syncEl; + my.remoteEl; + + initialize(); + + that.terminate = terminate; + that.getID = getID; + return that; } diff --git a/src/js/view/midi_input.js b/src/js/view/midi_input.js index 25e22b92..2564569b 100644 --- a/src/js/view/midi_input.js +++ b/src/js/view/midi_input.js @@ -3,23 +3,20 @@ import createMIDIBaseView from './midi_base.js'; /** * MIDI Input processor view. */ -export default function createMIDIInputView(specs, my) { - var that, - - /** - * This init function is called after the base view's initialise function, - * so properties of on 'my' are available. - */ - init = function() { - my.networkEl.dataset.disabled = 'true'; - my.networkEl.querySelector('input').setAttribute('disabled', 'disabled'); - }; - - my = my || {}; - - that = createMIDIBaseView(specs, my); - - init(); +export default function createMIDIInputView(data, that = {}, my = {}) { + + /** + * This init function is called after the base view's initialise function, + * so properties of on 'my' are available. + */ + const init = function() { + my.networkEl.dataset.disabled = 'true'; + my.networkEl.querySelector('input').setAttribute('disabled', 'disabled'); + }; + + that = createMIDIBaseView(data, that, my); + + init(); - return that; + return that; } diff --git a/src/js/view/midi_output.js b/src/js/view/midi_output.js index 844c6ec0..dd9b545d 100644 --- a/src/js/view/midi_output.js +++ b/src/js/view/midi_output.js @@ -3,25 +3,22 @@ import createMIDIBaseView from './midi_base.js'; /** * MIDI Output processor view. */ -export default function createMIDIOutputView(specs, my) { - var that, +export default function createMIDIOutputView(data, that = {}, my = {}) { - /** - * This init function is called after the base view's initialise function, - * so properties of on 'my' are available. - */ - init = function() { - my.syncEl.dataset.disabled = 'true'; - my.syncEl.querySelector('input').setAttribute('disabled', 'disabled'); - my.remoteEl.dataset.disabled = 'true'; - my.remoteEl.querySelector('input').setAttribute('disabled', 'disabled'); - }; - - my = my || {}; - - that = createMIDIBaseView(specs, my); + /** + * This init function is called after the base view's initialise function, + * so properties of on 'my' are available. + */ + const init = function() { + my.syncEl.dataset.disabled = 'true'; + my.syncEl.querySelector('input').setAttribute('disabled', 'disabled'); + my.remoteEl.dataset.disabled = 'true'; + my.remoteEl.querySelector('input').setAttribute('disabled', 'disabled'); + }; - init(); + that = createMIDIBaseView(data, that, my); + + init(); - return that; + return that; } diff --git a/src/js/view/preferences.js b/src/js/view/preferences.js index 851453f4..2cf28438 100644 --- a/src/js/view/preferences.js +++ b/src/js/view/preferences.js @@ -1,95 +1,179 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; import createMIDIInputView from './midi_input.js'; import createMIDIOutputView from './midi_output.js'; +const midiInputsEl = document.querySelector('.prefs__inputs'); +const midiOutputsEl = document.querySelector('.prefs__outputs'); +const themeCheckEl = document.querySelector('.prefs__dark-theme'); +const midiPortViews = []; + +export function setup() { + addEventListeners(); +} + +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); + themeCheckEl.addEventListener('change', e => { + dispatch(getActions().setTheme(e.target.checked)); + }); +} + +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.SET_THEME: + updateControl('dark-theme', state.theme === 'dark'); + break; + + case actions.CREATE_MIDI_PORT: + case actions.UPDATE_MIDI_PORT: + case actions.MIDI_PORT_CHANGE: + updateMIDIPortViews(state.ports); + break; + } +} + +/** + * Callback function to update one of the controls after if the + * preference's state changed. + * @param {String} key Key that indicates the control. + * @param {Boolean} value Value of the control. + */ +function updateControl(key, value) { + switch (key) { + case 'dark-theme': + themeCheckEl.checked = value; + break; + } +} + /** - * Preferences settings view. + * Update lists of ports after a change. + * @param {Array} ports MIDI port objects. */ -export default function createPreferencesView(specs) { - var that, - store = specs.store, - preferencesEl = document.querySelector('.prefs'), - midiInputsEl = document.querySelector('.prefs__inputs'), - midiOutputsEl = document.querySelector('.prefs__outputs'), - midiPortViews = [], - controls = { - darkTheme: { - type: 'checkbox', - input: document.querySelector('.prefs__dark-theme') - } - }, +function updateMIDIPortViews(ports) { + ports.allIds.forEach(id => { + const port = ports.byId[id]; + let view = midiPortViews.find(view => port.id === view.getID()); + if (view && port.state === 'disconnected') { + view.terminate(); + midiPortViews.splice(midiPortViews.findIndex(view => port.id === view.getID()), 1); + } + if (!view && port.state === 'connected') { + let createFunction, parentEl; + if (port.type === 'input') { + createFunction = createMIDIInputView; + parentEl = midiInputsEl; + } else { + createFunction = createMIDIOutputView; + parentEl = midiOutputsEl; + } + midiPortViews.push(createFunction({ + id: port.id, + name: port.name, + parentEl: parentEl, + isInput: port.type === 'input', + syncEnabled: port.syncEnabled, + remoteEnabled: port.remoteEnabled, + networkEnabled: port.networkEnabled + })); + } + }); +} - init = function() { - controls.darkTheme.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().setTheme(e.target.checked)); - }); +// /** +// * Preferences settings view. +// */ +// export default function createPreferencesView(specs) { +// var that, +// store = specs.store, +// preferencesEl = document.querySelector('.prefs'), +// midiInputsEl = document.querySelector('.prefs__inputs'), +// midiOutputsEl = document.querySelector('.prefs__outputs'), +// midiPortViews = [], +// controls = { +// darkTheme: { +// type: 'checkbox', +// input: document.querySelector('.prefs__dark-theme') +// } +// }, - document.addEventListener(store.STATE_CHANGE, (e) => { - switch (e.detail.action.type) { - case e.detail.actions.SET_THEME: - updateControl('dark-theme', e.detail.state.theme === 'dark'); - break; +// init = function() { +// controls.darkTheme.input.addEventListener('change', function(e) { +// store.dispatch(store.getActions().setTheme(e.target.checked)); +// }); + +// document.addEventListener(store.STATE_CHANGE, (e) => { +// switch (action.type) { +// case actions.SET_THEME: +// updateControl('dark-theme', state.theme === 'dark'); +// break; - case e.detail.actions.CREATE_MIDI_PORT: - case e.detail.actions.UPDATE_MIDI_PORT: - case e.detail.actions.MIDI_PORT_CHANGE: - updateMIDIPortViews(e.detail.state.ports); - break; - } - }); - }, +// case actions.CREATE_MIDI_PORT: +// case actions.UPDATE_MIDI_PORT: +// case actions.MIDI_PORT_CHANGE: +// updateMIDIPortViews(state.ports); +// break; +// } +// }); +// }, - /** - * Callback function to update one of the controls after if the - * preference's state changed. - * @param {String} key Key that indicates the control. - * @param {Boolean} value Value of the control. - */ - updateControl = function(key, value) { - switch (key) { - case 'dark-theme': - controls.darkTheme.input.checked = value; - break; - } - }, +// /** +// * Callback function to update one of the controls after if the +// * preference's state changed. +// * @param {String} key Key that indicates the control. +// * @param {Boolean} value Value of the control. +// */ +// updateControl = function(key, value) { +// switch (key) { +// case 'dark-theme': +// controls.darkTheme.input.checked = value; +// break; +// } +// }, - /** - * Update lists of ports after a change. - * @param {Array} ports MIDI port objects. - */ - updateMIDIPortViews = function(ports) { - ports.allIds.forEach(id => { - const port = ports.byId[id]; - let view = midiPortViews.find(view => port.id === view.getID()); - if (view && port.state === 'disconnected') { - view.terminate(); - midiPortViews.splice(midiPortViews.findIndex(view => port.id === view.getID()), 1); - } - if (!view && port.state === 'connected') { - let createFunction, parentEl; - if (port.type === 'input') { - createFunction = createMIDIInputView; - parentEl = midiInputsEl; - } else { - createFunction = createMIDIOutputView; - parentEl = midiOutputsEl; - } - midiPortViews.push(createFunction({ - store: store, - id: port.id, - name: port.name, - parentEl: parentEl, - isInput: port.type === 'input', - syncEnabled: port.syncEnabled, - remoteEnabled: port.remoteEnabled, - networkEnabled: port.networkEnabled - })); - } - }); - }; +// /** +// * Update lists of ports after a change. +// * @param {Array} ports MIDI port objects. +// */ +// updateMIDIPortViews = function(ports) { +// ports.allIds.forEach(id => { +// const port = ports.byId[id]; +// let view = midiPortViews.find(view => port.id === view.getID()); +// if (view && port.state === 'disconnected') { +// view.terminate(); +// midiPortViews.splice(midiPortViews.findIndex(view => port.id === view.getID()), 1); +// } +// if (!view && port.state === 'connected') { +// let createFunction, parentEl; +// if (port.type === 'input') { +// createFunction = createMIDIInputView; +// parentEl = midiInputsEl; +// } else { +// createFunction = createMIDIOutputView; +// parentEl = midiOutputsEl; +// } +// midiPortViews.push(createFunction({ +// store: store, +// id: port.id, +// name: port.name, +// parentEl: parentEl, +// isInput: port.type === 'input', +// syncEnabled: port.syncEnabled, +// remoteEnabled: port.remoteEnabled, +// networkEnabled: port.networkEnabled +// })); +// } +// }); +// }; - that = specs.that; +// that = specs.that; - init(); +// init(); - return that; -} +// return that; +// } From ceb9b645e7b9fce9eeef2dba3f00466a1830d000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 11:19:04 +0200 Subject: [PATCH 021/131] Remote panel refactored. --- src/js/main.js | 5 +- src/js/view/remote.js | 294 ++++++++++++++++++++++++------------ src/js/view/remote_group.js | 211 +++++++++++++------------- src/js/view/remote_item.js | 115 +++++++------- 4 files changed, 355 insertions(+), 270 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 5bb27790..8332c916 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -21,15 +21,15 @@ import { dispatch, getActions, getState, persist, } from './state/store.js'; // import createAppView from './view/app.js'; // import createDialog from './view/dialog.js'; import { accessMidi } from './midi/midi.js'; +import { preloadProcessors } from './core/processor-loader.js'; import { setup as setupCanvas3d } from './webgl/canvas3d.js'; import { setup as setupConnections3d } from './webgl/connections3d.js'; import { setup as setupControls } from './view/controls.js'; import { setup as setupLibrary } from './view/library.js'; import { setup as setupNetwork } from './midi/network.js'; import { setup as setupPanels } from './view/panels.js'; -import { preloadProcessors } from './core/processor-loader.js'; import { setup as setupPreferences } from './view/preferences.js'; -import createRemoteView from './view/remote.js'; +import { setup as setupRemote } from './view/remote.js'; import createTransport from './core/transport.js'; import { showDialog } from './view/dialog.js'; @@ -44,6 +44,7 @@ async function main() { setupLibrary(); setupNetwork(); setupPreferences(); + setupRemote(); persist(); } diff --git a/src/js/view/remote.js b/src/js/view/remote.js index 58623636..63b74df1 100644 --- a/src/js/view/remote.js +++ b/src/js/view/remote.js @@ -1,116 +1,212 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; import createRemoteGroupView from './remote_group.js'; +const listEl = document.querySelector('.remote__list'); +const groupViews = { + byId: {}, + allIds: [] +}; + +export function setup() { + addEventListeners(); +} + +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); +} + +function createRemoteGroups(state) { + state.assignments.allIds.forEach(assignID => { + const assignment = state.assignments.byId[assignID]; + if (!groupViews.byId[assignment.processorID]) { + createRemoteGroup(state.processors.byId[assignment.processorID]); + } + }); +} + +/** + * Create a container view to hold assigned parameter views. + * @param {Array} processors Processor list. + */ +function createRemoteGroup(processor) { + if (!groupViews.byId[processor.id]) { + groupViews.allIds.push(processor.id); + groupViews.byId[processor.id] = createRemoteGroupView({ + processorID: processor.id, + parentEl: listEl + }); + } +} + +/** + * Delete a container view to hold assigned parameter views. + * @param {Object} processor Processor with assignable parameters. + */ +function deleteRemoteGroups(processors) { + let n = groupViews.allIds.length; + for (let i = groupViews.allIds.length - 1; i >= 0; i--) { + const id = groupViews.allIds[i]; + if (!processors || !processors.byId || !processors.byId[id]) { + groupViews.allIds.splice(i, 1); + groupViews.byId[id].terminate(); + delete groupViews.byId[id]; + } + } +} + /** - * Overview list of all assigned MIDI controller assignments. + * Handle state changes. + * @param {Object} e */ -export default function createRemoteView(specs, my) { - var that, - store = specs.store, - listEl = document.querySelector('.remote__list'), - groupViews = { - byId: {}, - allIds: [] - }, +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { - init = function() { - document.addEventListener(store.STATE_CHANGE, (e) => { - switch (e.detail.action.type) { + case actions.CREATE_PROJECT: + deleteRemoteGroups(); + createRemoteGroups(state); + break; - case e.detail.actions.CREATE_PROJECT: - deleteRemoteGroups(); - createRemoteGroups(e.detail.state); - break; + case actions.ADD_PROCESSOR: + createRemoteGroup(state.processors.byId[action.data.id]); + break; + + case actions.DELETE_PROCESSOR: + deleteRemoteGroups(state.processors); + break; + + case actions.ASSIGN_EXTERNAL_CONTROL: + if (state.learnTargetProcessorID) { + const groupView = groupViews.byId[state.learnTargetProcessorID]; + if (!groupView) { + createRemoteGroups(state); + } else { + groupView.updateViews(state); + } + } + break; + + case actions.UNASSIGN_EXTERNAL_CONTROL: + const groupView = groupViews.byId[action.processorID]; + const processor = state.processors.byId[state.learnTargetProcessorID]; + if (groupView && processor) { + groupView.updateViews(state); + } + break; + } +} + +// /** +// * Overview list of all assigned MIDI controller assignments. +// */ +// export default function createRemoteView(specs, my) { +// var that, +// store = specs.store, +// listEl = document.querySelector('.remote__list'), +// groupViews = { +// byId: {}, +// allIds: [] +// }, + +// init = function() { +// document.addEventListener(store.STATE_CHANGE, (e) => { +// switch (action.type) { - case e.detail.actions.ADD_PROCESSOR: - createRemoteGroup(e.detail.state.processors.byId[e.detail.action.data.id]); - break; +// case actions.CREATE_PROJECT: +// deleteRemoteGroups(); +// createRemoteGroups(state); +// break; + +// case actions.ADD_PROCESSOR: +// createRemoteGroup(state.processors.byId[action.data.id]); +// break; - case e.detail.actions.DELETE_PROCESSOR: - deleteRemoteGroups(e.detail.state.processors); - break; +// case actions.DELETE_PROCESSOR: +// deleteRemoteGroups(state.processors); +// break; - case e.detail.actions.ASSIGN_EXTERNAL_CONTROL: - if (e.detail.state.learnTargetProcessorID) { - const groupView = groupViews.byId[e.detail.state.learnTargetProcessorID], - processor = e.detail.state.processors.byId[e.detail.state.learnTargetProcessorID]; - if (!groupView) { - createRemoteGroups(e.detail.state); - } else { - groupView.updateViews(e.detail.state); - } - } - break; +// case actions.ASSIGN_EXTERNAL_CONTROL: +// if (state.learnTargetProcessorID) { +// const groupView = groupViews.byId[state.learnTargetProcessorID], +// processor = state.processors.byId[state.learnTargetProcessorID]; +// if (!groupView) { +// createRemoteGroups(state); +// } else { +// groupView.updateViews(state); +// } +// } +// break; - case e.detail.actions.UNASSIGN_EXTERNAL_CONTROL: - const groupView = groupViews.byId[e.detail.action.processorID], - processor = e.detail.state.processors.byId[e.detail.state.learnTargetProcessorID]; - if (groupView && processor) { - groupView.updateViews(e.detail.state); - } - break; - } - }); - }, +// case actions.UNASSIGN_EXTERNAL_CONTROL: +// const groupView = groupViews.byId[action.processorID], +// processor = state.processors.byId[state.learnTargetProcessorID]; +// if (groupView && processor) { +// groupView.updateViews(state); +// } +// break; +// } +// }); +// }, - createRemoteGroups = function(state) { - state.assignments.allIds.forEach(assignID => { - const assignment = state.assignments.byId[assignID]; - if (!groupViews.byId[assignment.processorID]) { - createRemoteGroup(state.processors.byId[assignment.processorID]); - } - }); +// createRemoteGroups = function(state) { +// state.assignments.allIds.forEach(assignID => { +// const assignment = state.assignments.byId[assignID]; +// if (!groupViews.byId[assignment.processorID]) { +// createRemoteGroup(state.processors.byId[assignment.processorID]); +// } +// }); - // processors.allIds.forEach(id => { - // if (!groupViews.byId[id]) { - // const processor = processors.byId[id]; - // let hasAssignment = false; - // processor.params.allIds.forEach(id => { - // const param = processor.params.byId[id]; - // if (param.isMidiControllable && param.remoteChannel && param.remoteCC != null) { - // hasAssignment = true; - // } - // }); - // if (hasAssignment) { - // createRemoteGroup(processor); - // } - // } - // }); - }, +// // processors.allIds.forEach(id => { +// // if (!groupViews.byId[id]) { +// // const processor = processors.byId[id]; +// // let hasAssignment = false; +// // processor.params.allIds.forEach(id => { +// // const param = processor.params.byId[id]; +// // if (param.isMidiControllable && param.remoteChannel && param.remoteCC != null) { +// // hasAssignment = true; +// // } +// // }); +// // if (hasAssignment) { +// // createRemoteGroup(processor); +// // } +// // } +// // }); +// }, - /** - * Create a container view to hold assigned parameter views. - * @param {Array} processors Processor list. - */ - createRemoteGroup = function(processor) { - if (!groupViews.byId[processor.id]) { - groupViews.allIds.push(processor.id); - groupViews.byId[processor.id] = createRemoteGroupView({ - store: store, - processorID: processor.id, - parentEl: listEl - }); - } - }, +// /** +// * Create a container view to hold assigned parameter views. +// * @param {Array} processors Processor list. +// */ +// createRemoteGroup = function(processor) { +// if (!groupViews.byId[processor.id]) { +// groupViews.allIds.push(processor.id); +// groupViews.byId[processor.id] = createRemoteGroupView({ +// store: store, +// processorID: processor.id, +// parentEl: listEl +// }); +// } +// }, - /** - * Delete a container view to hold assigned parameter views. - * @param {Object} processor Processor with assignable parameters. - */ - deleteRemoteGroups = function(processors) { - let n = groupViews.allIds.length; - for (let i = groupViews.allIds.length - 1; i >= 0; i--) { - const id = groupViews.allIds[i]; - if (!processors || !processors.byId || !processors.byId[id]) { - groupViews.allIds.splice(i, 1); - groupViews.byId[id].terminate(); - delete groupViews.byId[id]; - } - } - }, +// /** +// * Delete a container view to hold assigned parameter views. +// * @param {Object} processor Processor with assignable parameters. +// */ +// deleteRemoteGroups = function(processors) { +// let n = groupViews.allIds.length; +// for (let i = groupViews.allIds.length - 1; i >= 0; i--) { +// const id = groupViews.allIds[i]; +// if (!processors || !processors.byId || !processors.byId[id]) { +// groupViews.allIds.splice(i, 1); +// groupViews.byId[id].terminate(); +// delete groupViews.byId[id]; +// } +// } +// }, - that = specs.that || {}; +// that = specs.that || {}; - init(); +// init(); - return that; -} +// return that; +// } diff --git a/src/js/view/remote_group.js b/src/js/view/remote_group.js index ffacc004..a9388802 100644 --- a/src/js/view/remote_group.js +++ b/src/js/view/remote_group.js @@ -1,128 +1,121 @@ +import { dispatch, getActions, getState, STATE_CHANGE, } from '../state/store.js'; import createRemoteItemView from './remote_item.js'; /** * Group within overview list of all assigned MIDI controller assignments. * The items are grouped by processor. */ -export default function createRemoteGroupView(specs, my) { - var that, - store = specs.store, - processorID = specs.processorID, - parentEl = specs.parentEl, - el, - listEl, - nameParam, - views = { - byId: {}, - allIds: [] - }, - - initialize = function() { - // create the DOM element. - let template = document.querySelector('#template-remote-group'); - let clone = template.content.cloneNode(true); - el = clone.firstElementChild; - parentEl.appendChild(el); - - listEl = el.querySelector('.remote__group-list'); +export default function createRemoteGroupView(data, that = {}, my = {}) { + const { parentEl, processorID, } = data; - const state = store.getState(); - setName(state.processors.byId[processorID].params.byId.name.value); - updateViews(state); + let el, + listEl, + views = { + byId: {}, + allIds: [] + }; + + const initialize = function() { - document.addEventListener(store.STATE_CHANGE, handleStateChange); - }, - - /** - * Called before this view is deleted. - */ - terminate = function() { - document.removeEventListener(store.STATE_CHANGE, handleStateChange); + // create the DOM element. + const template = document.querySelector('#template-remote-group'); + const clone = template.content.cloneNode(true); + el = clone.firstElementChild; + parentEl.appendChild(el); + + listEl = el.querySelector('.remote__group-list'); - views.allIds.forEach(id => { - views.byId[id].terminate(); - }); + const state = getState(); + setName(state.processors.byId[processorID].params.byId.name.value); + updateViews(state); - parentEl.removeChild(el); - views = null; - parentEl = null; - }, + document.addEventListener(STATE_CHANGE, handleStateChange); + }, + + /** + * Called before this view is deleted. + */ + terminate = function() { + document.removeEventListener(STATE_CHANGE, handleStateChange); - handleStateChange = function(e) { - switch (e.detail.action.type) { - case e.detail.actions.CHANGE_PARAMETER: - if (e.detail.action.processorID === processorID && - e.detail.action.paramKey === 'name') { - setName(e.detail.state.processors.byId[processorID].params.byId.name.value); - } - break; - } - }, + views.allIds.forEach(id => { + views.byId[id].terminate(); + }); - /** - * Update list to contain all assignments. - */ - updateViews = function(state) { - state.processors.byId[processorID].params.allIds.forEach(paramKey => { - - // search assignment for this parameter - let assignment; - state.assignments.allIds.forEach(assignID => { - const assign = state.assignments.byId[assignID]; - if (assign.processorID === processorID && assign.paramKey === paramKey) { - assignment = assign; - } - }); + parentEl.removeChild(el); + views = null; + }, - // create or delete the parameter's view - const view = views.byId[paramKey]; - if (assignment && !view) { - const param = state.processors.byId[processorID].params.byId[paramKey]; - addView(paramKey, param.label, assignment.remoteChannel, assignment.remoteCC); - } else if (!assignment && view) { - removeView(paramKey); - } - }); + /** + * Handle state changes. + * @param {Object} e + */ + handleStateChange = function(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.CHANGE_PARAMETER: + if (action.processorID === processorID && + action.paramKey === 'name') { + setName(state.processors.byId[processorID].params.byId.name.value); + } + break; + } + }, - // show group if there are assignments - el.dataset.hasAssignments = (views.allIds.length > 0); - }, + /** + * Update list to contain all assignments. + */ + updateViews = function(state) { + state.processors.byId[processorID].params.allIds.forEach(paramKey => { + + // search assignment for this parameter + let assignment; + state.assignments.allIds.forEach(assignID => { + const assign = state.assignments.byId[assignID]; + if (assign.processorID === processorID && assign.paramKey === paramKey) { + assignment = assign; + } + }); - addView = function(paramKey, paramLabel, remoteChannel, remoteCC) { - views.byId[paramKey] = createRemoteItemView({ - store, - paramKey, - paramLabel, - processorID, - remoteChannel, - remoteCC, - parentEl: listEl - }); - views.allIds.push(paramKey); - }, + // create or delete the parameter's view + const view = views.byId[paramKey]; + if (assignment && !view) { + const param = state.processors.byId[processorID].params.byId[paramKey]; + addView(paramKey, param.label, assignment.remoteChannel, assignment.remoteCC); + } else if (!assignment && view) { + removeView(paramKey); + } + }); - removeView = function(paramKey) { - views.byId[paramKey].terminate(); - delete views.byId[paramKey]; - views.allIds.splice(views.allIds.indexOf(paramKey), 1); - }, - - /** - * If a group has no assignments its header is hidden. - */ - updateGroupVisibility = function() { - el.dataset.hasAssignments = (itemViews.length > 0); - }, - - /** - * Set the group's header to the processor's name. - * @param {String} name Processor's name. - */ - setName = function(name) { - el.querySelector('.remote__group-header-label').innerHTML = name; - }; - - that = specs.that || {}; + // show group if there are assignments + el.dataset.hasAssignments = (views.allIds.length > 0); + }, + + addView = function(paramKey, paramLabel, remoteChannel, remoteCC) { + views.byId[paramKey] = createRemoteItemView({ + paramKey, + paramLabel, + processorID, + remoteChannel, + remoteCC, + parentEl: listEl + }); + views.allIds.push(paramKey); + }, + + removeView = function(paramKey) { + views.byId[paramKey].terminate(); + delete views.byId[paramKey]; + views.allIds.splice(views.allIds.indexOf(paramKey), 1); + }, + + /** + * Set the group's header to the processor's name. + * @param {String} name Processor's name. + */ + setName = function(name) { + el.querySelector('.remote__group-header-label').innerHTML = name; + }; initialize(); diff --git a/src/js/view/remote_item.js b/src/js/view/remote_item.js index cc035a3d..7e503a9e 100644 --- a/src/js/view/remote_item.js +++ b/src/js/view/remote_item.js @@ -1,67 +1,62 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; + /** * View for a parameter that's linked to a remote MIDI controller. * The items are grouped by processor. */ -export default function createRemoteItemView(specs, my) { - var that, - store = specs.store, - paramKey = specs.paramKey, - paramLabel = specs.paramLabel, - processorID = specs.processorID, - remoteChannel = specs.remoteChannel, - remoteCC = specs.remoteCC, - parentEl = specs.parentEl, - el, - - initialize = function() { - // create the DOM element. - let template = document.querySelector('#template-remote-item'); - let clone = template.content.cloneNode(true); - el = clone.firstElementChild; - el.querySelector('.remote__item-label').innerHTML = paramLabel; - el.querySelector('.remote__item-channel').innerHTML = remoteChannel; - el.querySelector('.remote__item-control').innerHTML = remoteCC; - parentEl.appendChild(el); - - // add DOM event listeners - el.querySelector('.remote__item-delete').addEventListener('click', onUnregisterClick); - }, - - /** - * Called before this view is deleted. - */ - terminate = function() { - el.querySelector('.remote__item-delete').removeEventListener('click', onUnregisterClick); - parentEl.removeChild(el); - parentEl = null; - }, - - /** - * Unassign button click handler. - * @param {Object} e Click event object. - */ - onUnregisterClick = function(e) { - store.dispatch(store.getActions().unassignExternalControl(processorID, paramKey)); - }, - - /** - * State of the parameter in the assignment process changed, - * the element will show this visually. - * @param {String} state New state of the parameter. - * @param {Function} callback Not used here. - */ - changeRemoteState = function(state, callback) { - switch (state) { - case 'assigned': - // TODO: normale tekst - break; - case 'inactive': - // TODO: tekst grijs of zoiets - break; - } - }; - - that = specs.that || {}; +export default function createRemoteItemView(data, that = {}, my = {}) { + const { paramKey, paramLabel, parentEl, processorID, remoteChannel, remoteCC, } = data; + + let el; + + const initialize = function() { + + // create the DOM element. + let template = document.querySelector('#template-remote-item'); + let clone = template.content.cloneNode(true); + el = clone.firstElementChild; + el.querySelector('.remote__item-label').innerHTML = paramLabel; + el.querySelector('.remote__item-channel').innerHTML = remoteChannel; + el.querySelector('.remote__item-control').innerHTML = remoteCC; + parentEl.appendChild(el); + + // add DOM event listeners + el.querySelector('.remote__item-delete').addEventListener('click', onUnregisterClick); + }, + + /** + * Called before this view is deleted. + */ + terminate = function() { + el.querySelector('.remote__item-delete').removeEventListener('click', onUnregisterClick); + parentEl.removeChild(el); + parentEl = null; + }, + + /** + * Unassign button click handler. + * @param {Object} e Click event object. + */ + onUnregisterClick = function(e) { + dispatch(getActions().unassignExternalControl(processorID, paramKey)); + }, + + /** + * State of the parameter in the assignment process changed, + * the element will show this visually. + * @param {String} state New state of the parameter. + * @param {Function} callback Not used here. + */ + changeRemoteState = function(state, callback) { + switch (state) { + case 'assigned': + // TODO: normale tekst + break; + case 'inactive': + // TODO: tekst grijs of zoiets + break; + } + }; initialize(); From 15d687c8f124887146a302b6d96174ca923a63d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 12:13:30 +0200 Subject: [PATCH 022/131] Transport refactored to Transport nd Sequencer modules. --- src/js/core/sequencer.js | 121 +++++++++ src/js/core/transport.js | 543 +++++++++++++-------------------------- src/js/main.js | 5 +- 3 files changed, 300 insertions(+), 369 deletions(-) create mode 100644 src/js/core/sequencer.js diff --git a/src/js/core/sequencer.js b/src/js/core/sequencer.js new file mode 100644 index 00000000..e89fe265 --- /dev/null +++ b/src/js/core/sequencer.js @@ -0,0 +1,121 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; +import { process } from '../midi/network.js'; +import {draw } from '../webgl/canvas3d.js'; +import { PPQN } from '../core/config.js'; + +/** + * Timing, transport and sequencing functionality. + * Divided in two sets of functionality, Transport and Sequencer. + * + * Unix epoch, page AudioContext Transport now, + * 01-01-1970 00:00:00 UTC load created start the present + * | | | | | + * |--------------------------|-------|-------//-----|--------//------| + * + * |------------------------------------------------------------------> Date.now() + * |---------------------------------------> performance.now() + * |-------------------------------> AudioContext.currentTime + */ + +let audioContextOffset = 0, + bpm = 0, + processorEvents = {}, + renderThrottleCounter = 0, + tickInMilliseconds = 0; + +/** + * @description Creates sequencer functionality. + * Takes time from transport to get music events from arrangement and + * drives components that process music events. + * @param {Object} specs External specifications. + * @param {Object} my Internally shared properties. + */ +export function setup (specs, my) { + addEventListeners(); + setBPM(); +} + +/** + * Get Beats Per Minute of the project. + * @return [Number] Beats Per Minute. + */ +export function getBPM () { + return bpm; +} + +/** + * Scan the arrangement for events and send them to concerned components. + * @param {Number} scanStart Start in ms of timespan to scan. + * @param {Number} scanEnd End in ms of timespan to scan. + * @param {Number} nowToScanStart Duration from now until start time in ms. + * @param {Number} offset Position of transport playhead in ms. + */ +export function scanEvents(scanStart, scanEnd, nowToScanStart, offset) { + process(msec2tick(scanStart), msec2tick(scanEnd), msec2tick(nowToScanStart), tickInMilliseconds, msec2tick(offset), processorEvents); +} + +/** + * Use Timing's requestAnimationFrame as clock for view updates. + * @param {Number} position Timing position, equal to performance.now(). + */ +export function updateView(position) { + if (renderThrottleCounter % 2 === 0) { + draw(msec2tick(position), processorEvents); + Object.keys(processorEvents).forEach(v => processorEvents[v] = []); + } + renderThrottleCounter++; +} + +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); +} + +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.CREATE_PROJECT: + setBPM(state.bpm); + break; + + case actions.SET_TEMPO: + setBPM(state.bpm); + break; + } +} + +/** + * Convert milliseconds to ticks. + */ +function msec2tick(sec) { + return sec / tickInMilliseconds; +} + +/** + * Set difference between AudioContext.currentTime and performance.now. + * Used to convert timing for AudioContext playback. + * @param {Number} acCurrentTime Timestamp in seconds. + */ +function setAudioContextOffset(acCurrentTime) { + audioContextOffset = performance.now() - (acCurrentTime * 1000); +} + +/** + * Set Beats Per Minute. + * @param {Number} newBpm New value for BPM. + */ +function setBPM(newBpm = 120) { + bpm = newBpm; + const beatInMilliseconds = 60000.0 / bpm; + tickInMilliseconds = beatInMilliseconds / PPQN; +} + +/** + * Convert ticks to milliseconds. + */ +function tick2msec(tick) { + return tick * tickInMilliseconds; +} diff --git a/src/js/core/transport.js b/src/js/core/transport.js index e0c454f1..9594366c 100644 --- a/src/js/core/transport.js +++ b/src/js/core/transport.js @@ -1,3 +1,6 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; +import { scanEvents, updateView, } from './sequencer.js'; + /** * Timing, transport and sequencing functionality. * Divided in two sets of functionality, Transport and Sequencer. @@ -12,197 +15,20 @@ * |-------------------------------> AudioContext.currentTime */ -/** - * @description Creates sequencer functionality. - * Takes time from transport to get music events from arrangement and - * drives components that process music events. - * @param {Object} specs External specifications. - * @param {Object} my Internally shared properties. - */ -export function createSequencer (specs, my) { - var that, - canvasView = specs.canvasView, - midiNetwork = specs.midiNetwork, - ppqn = 480, - bpm = 120, - lastBpm = bpm, - tickInMilliseconds, - audioContextOffset = 0, - timelineOffset = 0, - playbackQueue = [], - renderThrottleCounter = 0, - processorEvents = {}, - - /** - * Scan the arrangement for events and send them to concerned components. - * @param {Number} scanStart Start in ms of timespan to scan. - * @param {Number} scanEnd End in ms of timespan to scan. - * @param {Number} nowToScanStart Duration from now until start time in ms. - * @param {Number} offset Position of transport playhead in ms. - */ - scanEvents = function(scanStart, scanEnd, nowToScanStart, offset) { - midiNetwork.process(msec2tick(scanStart), msec2tick(scanEnd), msec2tick(nowToScanStart), tickInMilliseconds, msec2tick(offset), processorEvents); - }, - - /** - * Use Timing's requestAnimationFrame as clock for view updates. - * @param {Number} position Timing position, equal to performance.now(). - */ - updateView = function(position) { - if (renderThrottleCounter % 2 === 0) { - canvasView.draw(msec2tick(position), processorEvents); - Object.keys(processorEvents).forEach(v => processorEvents[v] = []); - } - renderThrottleCounter++; - }, - - /** - * Convert milliseconds to ticks. - */ - msec2tick = function (sec) { - return sec / tickInMilliseconds; - }, - - /** - * Convert ticks to milliseconds. - */ - tick2msec = function (tick) { - return tick * tickInMilliseconds; - }, - - /** - * Set Beats Per Minute. - * @param {Number} newBpm New value for BPM. - */ - setBPM = function(newBpm = 120) { - bpm = newBpm; - var beatInMilliseconds = 60000.0 / bpm; - tickInMilliseconds = beatInMilliseconds / ppqn; - // calculate change factor - var factor = lastBpm / bpm; - my.setLoopByFactor(factor); - }, - - /** - * Get Beats Per Minute of the project. - * @return [Number] Beats Per Minute. - */ - getBPM = function() { - return bpm; - }, - - /** - * Set difference between AudioContext.currentTime and performance.now. - * Used to convert timing for AudioContext playback. - * @param {Number} acCurrentTime Timestamp in seconds. - */ - setAudioContextOffset = function(acCurrentTime) { - audioContextOffset = performance.now() - (acCurrentTime * 1000); - }; - - my = my || {}; - my.setBPM = setBPM; - my.store = specs.store; - my.scanEvents = scanEvents; - my.updateView = updateView; - - that = specs.that || {}; - - that.setBPM = setBPM; - that.getBPM = getBPM; - that.setAudioContextOffset = setAudioContextOffset; - return that; -} - -/** - * Functionality to add synchronisation to external MIDI clock. - * MIDI clock sends clock events at 24 ppqn. - * @see https://en.wikipedia.org/wiki/MIDI_beat_clock - * - * The MIDI 'start' and 'stop' events just start and stop the transport. - * The MIDI 'clock' event adjusts the BPM tempo. - * - * BPM is calculated with the time difference between clock event timestamps. - */ -function createExternalClock (specs, my) { - var that, - isEnabled = false, - midiInput, - prevBPM = 0, - prevTimestamp = 0, - updateTimeout, - - /** - * Enable synchronisation to external MIDI clock. - * @param {Boolean} isEnabled True to synchronise to external MIDI clock. - * @param {Object} midiInputPort MIDI input port. - */ - setExternalClockEnabled = function(isEnabled, midiInputPort) { - if (isEnabled) { - midiInput = midiInputPort; - midiInput.addListener('start', 1, onStart); - midiInput.addListener('stop', 1, onStop); - midiInput.addListener('clock', 1, onClock); - } else { - if (midiInput) { - midiInput.removeListener('start', onStart); - midiInput.removeListener('stop', onStop); - midiInput.removeListener('clock', onClock); - } - midiInput = null; - } - }, - - /** - * Start transport. - */ - onStart = function() { - that.start(); - }, - - /** - * Stop transport. - */ - onStop = function() { - that.pause(); - that.rewind(); - }, - - /** - * Convert events at 24 ppqn to BPM, suppress jitter from unstable clocks. - * @param {Object} e Event from WebMIDI.js. - */ - onClock = function(e) { - if (prevTimestamp > 0) { - var newBPM = 60000 / ((e.timestamp - prevTimestamp) * 24); - var bpm = prevBPM ? ((prevBPM * 23) + newBPM) / 24 : newBPM; - prevBPM = bpm; - bpm = bpm.toFixed(1); - if (bpm != that.getBPM()) { - updateTempo(bpm); - } - } - prevTimestamp = e.timestamp; - }, - - /** - * Update tempo no more than once every 500ms. - * @param {Number} bpm The new changed BPM. - */ - updateTempo = function(bpm) { - if (!updateTimeout) { - that.setBPM(bpm); - updateTimeout = setTimeout(function() { - updateTimeout = 0; - }, 500); - } - }; - - that = specs.that || {}; - - that.setExternalClockEnabled = setExternalClockEnabled; - return that; -} +const timeWindow = 16.7; +const lookAhead = 200; + +let position = 0, + origin = 0, + scanStart = 0, + scanEnd = 0, + lastBpm = 0, + loopStart = 0, + loopEnd = 0, + wasRunning = false, + isRunning = false, + isLooping = false, + needsScan = false; /** * @description Creates transport timing functionality. @@ -210,193 +36,174 @@ function createExternalClock (specs, my) { * The timer can be started, stopped, rewound to zero and looped. * It defines a scan range that is just ahead of the play position * and that is meant to be used to scan for events to play. - * @param {Object} specs Options. - * @param {Object} my Properties shared between the functionalities of the object. */ -export default function createTransport(specs, my) { - var that, - position = 0, - origin = 0, - scanStart = 0, - scanEnd = 0, - lookAhead = 200, - loopStart = 0, - loopEnd = 0, - wasRunning = false, - isRunning = false, - isLooping = false, - needsScan = false, +export function setup() { + addEventListeners(); + run(); +} - init = function() { - document.addEventListener(my.store.STATE_CHANGE, (e) => { - switch (e.detail.action.type) { - case e.detail.actions.SET_TRANSPORT: - switch (e.detail.state.transport) { - case 'pause': - pause(); - break; - case 'play': - rewind(); - start(); - break; - case 'stop': - pause(); - rewind(); - break; - } - break; - - case e.detail.actions.CREATE_PROJECT: - my.setBPM(e.detail.state.bpm); - break; +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); + document.addEventListener('visibilitychange', handleVisbilityChange); +} - case e.detail.actions.SET_TEMPO: - my.setBPM(e.detail.state.bpm); - break; - } - }); +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.SET_TRANSPORT: + switch (state.transport) { + case 'pause': + pause(); + break; + case 'play': + rewind(); + start(); + break; + case 'stop': + pause(); + rewind(); + break; + } + break; - // stop playback if the page is hidden, continue when visible - document.addEventListener('visibilitychange', function() { - if (document.visibilityState === 'visible') { - if (wasRunning) { - start(); - } - } else { - wasRunning = isRunning; - if (wasRunning) { - pause(); - } - } - }); + case actions.CREATE_PROJECT: + case actions.SET_TEMPO: + setBPM(state.bpm); + break; + } +} - my.setBPM(); - }, - - /** - * Set the scan range. - * @param {Number} start Start timestamp of scan range. - */ - setScanRange = function (start) { - scanStart = start; - scanEnd = scanStart + lookAhead; - needsScan = true; - }, - - /** - * Updated the playhead position by adjusting the timeline origin. - * @param {Number} newOrigin Timeline origin timestamp. - */ - setOrigin = function(newOrigin) { - loopStart = loopStart - origin + newOrigin; - loopEnd = loopEnd - origin + newOrigin; - origin = newOrigin; - }, - - /** - * Timer using requestAnimationFrame that updates the transport timing. - */ - run = function() { - if (isRunning) { - position = performance.now(); - if (isLooping && position < loopEnd && scanStart < loopEnd && scanEnd > loopEnd) { - setOrigin(origin + (loopEnd - loopStart)); - } - if (scanEnd - position < 16.7) { - setScanRange(scanEnd); - } - if (needsScan) { - needsScan = false; - my.scanEvents(scanStart - origin, scanEnd - origin, scanStart - position, position - origin); - } - } - my.updateView(position - origin); - requestAnimationFrame(run); - }, - - /** - * Start the timer. - */ - start = function() { - var offset = position - origin; - position = performance.now(); - setOrigin(position - offset); - setScanRange(position); - isRunning = true; - }, - - /** - * Pause the timer. - */ - pause = function () { - isRunning = false; - }, - - /** - * Rewind the timer to timeline start. - */ - rewind = function () { - position = performance.now(); - setOrigin(position); - setScanRange(position); - }, +/** + * Stop playback if the page is hidden, continue when visible. + * @param {Object} e Event. + */ +function handleVisbilityChange(e) { + if (document.visibilityState === 'visible') { + if (wasRunning) { + start(); + } + } else { + wasRunning = isRunning; + if (wasRunning) { + pause(); + } + } +} - /** - * Toggle between stop and play. - */ - toggleStartStop = function() { - if (isRunning) { - pause(); - } else { - rewind(); - start(); - } - }, +/** + * Pause the timer. + */ +function pause () { + isRunning = false; +} - /** - * Set loop startpoint. - * @param {Number} position Loop start timestamp. - */ - setLoopStart = function (position) { - loopStart = origin + position; - }, +/** + * Rewind the timer to timeline start. + */ +function rewind() { + position = performance.now(); + setOrigin(position); + setScanRange(position); +} - /** - * Set loop endpoint. - * @param {Number} position Loop end timestamp. - */ - setLoopEnd = function (position) { - loopEnd = origin + position; - }, +/** + * Timer using requestAnimationFrame that updates the transport timing. + */ +function run() { + if (isRunning) { + position = performance.now(); + if (isLooping && position < loopEnd && scanStart < loopEnd && scanEnd > loopEnd) { + setOrigin(origin + (loopEnd - loopStart)); + } + if (scanEnd - position < timeWindow) { + setScanRange(scanEnd); + } + if (needsScan) { + needsScan = false; + scanEvents(scanStart - origin, scanEnd - origin, scanStart - position, position - origin); + } + } + updateView(position - origin); + requestAnimationFrame(run); +} + +/** + * Set Beats Per Minute. + * @param {Number} newBpm New value for BPM. + */ +function setBPM(newBpm = 120) { + // calculate change factor + const factor = lastBpm / newBpm; + lastBpm = newBpm; + setLoopByFactor(factor); +} - /** - * Set loop mode. - * @param {Boolean} isEnabled True to enable looping. - * @param {Number} position Loop start timestamp. - * @param {Number} position Loop end timestamp. - */ - setLoop = function (isEnabled, startPosition, endPosition) { - isLooping = isEnabled; - }, +/** + * Set loop mode. + * @param {Boolean} isEnabled True to enable looping. + * @param {Number} position Loop start timestamp. + * @param {Number} position Loop end timestamp. + */ +function setLoop(isEnabled, startPosition, endPosition) { + isLooping = isEnabled; +} - /** - * Change loop points by a factor if the tempo changes. - * @param {number} factor Time points multiplier. - */ - setLoopByFactor = function(factor) { - setLoopStart(loopStart * factor); - setLoopEnd(loopEnd * factor); - }; +/** + * Change loop points by a factor if the tempo changes. + * @param {number} factor Time points multiplier. + */ +function setLoopByFactor(factor) { + setLoopStart(loopStart * factor); + setLoopEnd(loopEnd * factor); +} + +/** + * Set loop endpoint. + * @param {Number} position Loop end timestamp. + */ +function setLoopEnd(position) { + loopEnd = origin + position; +} - my = my || {}; - my.setLoopByFactor = setLoopByFactor; - - that = createSequencer(specs, my); - that = createExternalClock(specs, my); +/** + * Set loop startpoint. + * @param {Number} position Loop start timestamp. + */ +function setLoopStart(position) { + loopStart = origin + position; +} - init(); - - that.run = run; +/** + * Updated the playhead position by adjusting the timeline origin. + * @param {Number} newOrigin Timeline origin timestamp. + */ +function setOrigin(newOrigin) { + loopStart = loopStart - origin + newOrigin; + loopEnd = loopEnd - origin + newOrigin; + origin = newOrigin; +} - return that; -}; +/** + * Set the scan range. + * @param {Number} start Start timestamp of scan range. + */ +function setScanRange(start) { + scanStart = start; + scanEnd = scanStart + lookAhead; + needsScan = true; +} + +/** + * Start the timer. + */ +function start() { + const offset = position - origin; + position = performance.now(); + setOrigin(position - offset); + setScanRange(position); + isRunning = true; +} diff --git a/src/js/main.js b/src/js/main.js index 8332c916..2bde752f 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -30,7 +30,8 @@ import { setup as setupNetwork } from './midi/network.js'; import { setup as setupPanels } from './view/panels.js'; import { setup as setupPreferences } from './view/preferences.js'; import { setup as setupRemote } from './view/remote.js'; -import createTransport from './core/transport.js'; +import { setup as setupTransport } from './core/transport.js'; +import { setup as setupSequencer } from './core/sequencer.js'; import { showDialog } from './view/dialog.js'; async function main() { @@ -45,6 +46,8 @@ async function main() { setupNetwork(); setupPreferences(); setupRemote(); + setupTransport(); + setupSequencer(); persist(); } From fa0e9e3b5a22b1ff05b179350f15732b1ada6639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 12:21:43 +0200 Subject: [PATCH 023/131] WindowResize module refactored. --- src/js/view/windowresize.js | 93 +++++++++++++++---------------------- 1 file changed, 38 insertions(+), 55 deletions(-) diff --git a/src/js/view/windowresize.js b/src/js/view/windowresize.js index 0735acbf..de21ef58 100644 --- a/src/js/view/windowresize.js +++ b/src/js/view/windowresize.js @@ -3,60 +3,43 @@ * Add callback functions that will be called on window resize, * but debounced to not be called more that every so many milliseconds. */ -var debouncedFunction, - callbacks = [], - delay = 250, - - /** - * Returns a function, that, as long as it continues to be invoked, - * will not be triggered. The function will be called after it - * stops being called for N milliseconds. If `immediate` is passed, - * trigger the function on the leading edge, instead of the trailing. - * @see https://davidwalsh.name/javascript-debounce-function - * @param {Function} func Function to call after delay. - * @param {Number} wait Milliseconds to wait before next call. - * @param {Boolean} immediate True to not wait. - */ - debounce = function(func, wait, immediate) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) { - func.apply(context, args); - } - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); - }; - }; +const callbacks = []; +const delay = 250; +let debouncedFunction; -export default function addWindowResize(specs, my) { - var that, - - /** - * Add callback function to be called on debounced resize. - * @param {Function} callback Callback function. - */ - addWindowResizeCallback = function(callback) { - callbacks.push(callback); - if (!debouncedFunction) { - debouncedFunction = debounce(function() { - callbacks.forEach(function(callbackFunction) { - callbackFunction(); - }); - }, delay); - window.addEventListener('resize', debouncedFunction); - } - }; - - my = my || {}; - my.addWindowResizeCallback = addWindowResizeCallback; - - that = specs.that || {}; - - return that; +export default function addWindowResizeCallback(callback) { + callbacks.push(callback); + if (!debouncedFunction) { + debouncedFunction = debounce(function() { + callbacks.forEach(function(callbackFunction) { + callbackFunction(); + }); + }, delay); + window.addEventListener('resize', debouncedFunction); + } +} + +/** + * Returns a function, that, as long as it continues to be invoked, + * will not be triggered. The function will be called after it + * stops being called for N milliseconds. If `immediate` is passed, + * trigger the function on the leading edge, instead of the trailing. + * @see https://davidwalsh.name/javascript-debounce-function + * @param {Function} func Function to call after delay. + * @param {Number} wait Milliseconds to wait before next call. + * @param {Boolean} immediate True to not wait. + */ +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; } From b7bf075c38a6c807a911f446579dd437f26a1101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 12:34:48 +0200 Subject: [PATCH 024/131] Refactor Object3dControllers. --- src/js/processors/epg/object3dController.js | 13 +++++-------- src/js/processors/euclidfx/object3dController.js | 13 +++++-------- src/js/processors/output/object3dController.js | 13 +++++-------- src/js/webgl/object3dControllerBase.js | 12 +++++------- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/src/js/processors/epg/object3dController.js b/src/js/processors/epg/object3dController.js index d233b914..4436ad95 100644 --- a/src/js/processors/epg/object3dController.js +++ b/src/js/processors/epg/object3dController.js @@ -16,9 +16,8 @@ import { const TWO_PI = Math.PI * 2; -export function createObject3dController(specs, my) { - let that, - centreDot3d, +export function createObject3dController(data, that = {}, my = {}) { + let centreDot3d, dots3d, pointer3d, polygon3d, @@ -46,11 +45,11 @@ export function createObject3dController(specs, my) { defaultColor = getTheme().colorHigh; - const params = specs.processorData.params.byId; + const params = data.processorData.params.byId; my.updateLabel(params.name.value); updateNecklace(params.steps.value, params.pulses.value, params.rotation.value, params.is_mute.value); updateDuration(params.steps.value, params.rate.value); - my.updateConnectMode(specs.isConnectMode); + my.updateConnectMode(data.isConnectMode); }, terminate = function() { @@ -397,10 +396,8 @@ export function createObject3dController(specs, my) { centerScale = 0; } }; - - my = my || {}; - that = createObject3dControllerBase(specs, my); + that = createObject3dControllerBase(data, that, my); initialize(); diff --git a/src/js/processors/euclidfx/object3dController.js b/src/js/processors/euclidfx/object3dController.js index 48c23288..823647f9 100644 --- a/src/js/processors/euclidfx/object3dController.js +++ b/src/js/processors/euclidfx/object3dController.js @@ -11,9 +11,8 @@ import { redrawShape } from '../../webgl/draw3dHelper.js'; const TWO_PI = Math.PI * 2; -export function createObject3dController(specs, my) { - let that, - centreCircle3d, +export function createObject3dController(data, that = {}, my = {}) { + let centreCircle3d, centreDot3d, select3d, pointer3d, @@ -44,13 +43,13 @@ export function createObject3dController(specs, my) { defaultColor = getTheme().colorHigh; - const params = specs.processorData.params.byId; + const params = data.processorData.params.byId; my.updateLabel(params.name.value); updateNecklace(params.steps.value, params.pulses.value, params.rotation.value); updateDuration(params.steps.value, params.rate.value); updateRotation(params.rotation.value); updatePointer(); - my.updateConnectMode(specs.isConnectMode); + my.updateConnectMode(data.isConnectMode); }, terminate = function() { @@ -271,10 +270,8 @@ export function createObject3dController(specs, my) { updateNoteAnimation(); } }; - - my = my || {}; - that = createObject3dControllerBase(specs, my); + that = createObject3dControllerBase(data, that, my); initialize(); diff --git a/src/js/processors/output/object3dController.js b/src/js/processors/output/object3dController.js index e1470e0b..c4e44716 100644 --- a/src/js/processors/output/object3dController.js +++ b/src/js/processors/output/object3dController.js @@ -2,9 +2,8 @@ import { dispatch, getActions, STATE_CHANGE, } from '../../state/store.js'; import { getTheme } from '../../state/selectors.js'; import createObject3dControllerBase from '../../webgl/object3dControllerBase.js'; -export function createObject3dController(specs, my) { - let that, - centreCircle3d, +export function createObject3dController(data, that = {}, my = {}) { + let centreCircle3d, select3d, initialize = function() { @@ -13,9 +12,9 @@ export function createObject3dController(specs, my) { document.addEventListener(STATE_CHANGE, handleStateChanges); - const params = specs.processorData.params.byId; + const params = data.processorData.params.byId; my.updateLabel(params.name.value); - my.updateConnectMode(specs.isConnectMode); + my.updateConnectMode(data.isConnectMode); }, terminate = function() { @@ -83,10 +82,8 @@ export function createObject3dController(specs, my) { draw = function(position, processorEvents) { }; - - my = my || {}; - that = createObject3dControllerBase(specs, my); + that = createObject3dControllerBase(data, that, my); initialize(); diff --git a/src/js/webgl/object3dControllerBase.js b/src/js/webgl/object3dControllerBase.js index 31790c72..3ec23bf4 100644 --- a/src/js/webgl/object3dControllerBase.js +++ b/src/js/webgl/object3dControllerBase.js @@ -5,16 +5,15 @@ import { getTheme } from '../state/selectors.js'; * Base object for all processor WebGL object controllers. * * @export - * @param {Object} specs + * @param {Object} data * @param {Object} my Shared properties. */ -export default function createObject3dControllerBase(specs, my) { - let that, +export default function createObject3dControllerBase(data, that, my) { /** * Update the pattern's name. */ - updateLabel = function(labelString) { + const updateLabel = function(labelString) { setText3d(my.label3d, labelString.toUpperCase(), getTheme().colorHigh); }, @@ -40,15 +39,14 @@ export default function createObject3dControllerBase(specs, my) { return my.id; }; - my.id = specs.object3d.userData.id; - my.object3d = specs.object3d; + my.id = data.object3d.userData.id; + my.object3d = data.object3d; my.hitarea3d = my.object3d.getObjectByName('hitarea'); my.label3d = my.object3d.getObjectByName('label'); my.updateLabel = updateLabel; my.updatePosition = updatePosition; my.updateConnectMode = updateConnectMode; - that = specs.that || {}; that.getID = getID; return that; } From b91172de98868cbea20a0b669ecba7920ac50dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 12:39:25 +0200 Subject: [PATCH 025/131] Processor utilities refactored. --- src/js/processors/epg/utils.js | 112 ++++++++++++++-------------- src/js/processors/euclidfx/utils.js | 112 ++++++++++++++-------------- 2 files changed, 112 insertions(+), 112 deletions(-) diff --git a/src/js/processors/epg/utils.js b/src/js/processors/epg/utils.js index e5ecec99..9405da10 100644 --- a/src/js/processors/epg/utils.js +++ b/src/js/processors/epg/utils.js @@ -8,17 +8,17 @@ const cache = {}; export function getEuclidPattern(steps, pulses) { - pulses = Math.min(steps, pulses); - const cacheKey = `${steps}_${pulses}`; - if (!cache[cacheKey]) { - cache[cacheKey] = createBjorklund(steps, pulses); - } - return cache[cacheKey].slice(0); + pulses = Math.min(steps, pulses); + const cacheKey = `${steps}_${pulses}`; + if (!cache[cacheKey]) { + cache[cacheKey] = createBjorklund(steps, pulses); + } + return cache[cacheKey].slice(0); } export function rotateEuclidPattern(pattern, rotation) { - const elementsToShift = pattern.splice(pattern.length - rotation); - return elementsToShift.concat(pattern); + const elementsToShift = pattern.splice(pattern.length - rotation); + return elementsToShift.concat(pattern); } /** @@ -28,53 +28,53 @@ export function rotateEuclidPattern(pattern, rotation) { * @return {Array} Array of Booleans that form the pattern. */ function createBjorklund(steps, pulses) { - if (pulses < 0 || steps < 0 || steps < pulses) { - return []; - } - - // Create the two arrays - let first = new Array(pulses).fill([1]); - let second = new Array(steps - pulses).fill([0]); - - let firstLength = first.length; - let minLength = Math.min(firstLength, second.length); - - let loopThreshold = 0; - // Loop until at least one array has length gt 2 (1 for first loop) - while (minLength > loopThreshold) { + if (pulses < 0 || steps < 0 || steps < pulses) { + return []; + } - // Allow only loopThreshold to be zero on the first loop - if (loopThreshold === 0) { - loopThreshold = 1; - } - - // For the minimum array loop and concat - for (var x = 0; x < minLength; x++) { - first[x] = Array.prototype.concat.call(first[x], second[x]); - } - - // if the second was the bigger array, slice the remaining elements/arrays and update - if (minLength === firstLength) { - second = Array.prototype.slice.call(second, minLength); - } - // Otherwise update the second (smallest array) with the remainders of the first - // and update the first array to include onlt the extended sub-arrays - else { - second = Array.prototype.slice.call(first, minLength); - first = Array.prototype.slice.call(first, 0, minLength); - } - firstLength = first.length; - minLength = Math.min(firstLength, second.length); - } - - // Build the final array - let pattern = []; - first.forEach(f => { - pattern = Array.prototype.concat.call(pattern, f); - }); - second.forEach(s => { - pattern = Array.prototype.concat.call(pattern, s); - }); - - return pattern; + // Create the two arrays + let first = new Array(pulses).fill([1]); + let second = new Array(steps - pulses).fill([0]); + + let firstLength = first.length; + let minLength = Math.min(firstLength, second.length); + + let loopThreshold = 0; + // Loop until at least one array has length gt 2 (1 for first loop) + while (minLength > loopThreshold) { + + // Allow only loopThreshold to be zero on the first loop + if (loopThreshold === 0) { + loopThreshold = 1; + } + + // For the minimum array loop and concat + for (var x = 0; x < minLength; x++) { + first[x] = Array.prototype.concat.call(first[x], second[x]); + } + + // if the second was the bigger array, slice the remaining elements/arrays and update + if (minLength === firstLength) { + second = Array.prototype.slice.call(second, minLength); + } + // Otherwise update the second (smallest array) with the remainders of the first + // and update the first array to include onlt the extended sub-arrays + else { + second = Array.prototype.slice.call(first, minLength); + first = Array.prototype.slice.call(first, 0, minLength); + } + firstLength = first.length; + minLength = Math.min(firstLength, second.length); + } + + // Build the final array + let pattern = []; + first.forEach(f => { + pattern = Array.prototype.concat.call(pattern, f); + }); + second.forEach(s => { + pattern = Array.prototype.concat.call(pattern, s); + }); + + return pattern; } diff --git a/src/js/processors/euclidfx/utils.js b/src/js/processors/euclidfx/utils.js index 6ef2c880..9405da10 100644 --- a/src/js/processors/euclidfx/utils.js +++ b/src/js/processors/euclidfx/utils.js @@ -8,17 +8,17 @@ const cache = {}; export function getEuclidPattern(steps, pulses) { - pulses = Math.min(steps, pulses); - const cacheKey = `${steps}_${pulses}`; - if (!cache[cacheKey]) { - cache[cacheKey] = createBjorklund(steps, pulses); - } - return cache[cacheKey].slice(0); + pulses = Math.min(steps, pulses); + const cacheKey = `${steps}_${pulses}`; + if (!cache[cacheKey]) { + cache[cacheKey] = createBjorklund(steps, pulses); + } + return cache[cacheKey].slice(0); } export function rotateEuclidPattern(pattern, rotation) { - const elementsToShift = pattern.splice(rotation); - return elementsToShift.concat(pattern); + const elementsToShift = pattern.splice(pattern.length - rotation); + return elementsToShift.concat(pattern); } /** @@ -28,53 +28,53 @@ export function rotateEuclidPattern(pattern, rotation) { * @return {Array} Array of Booleans that form the pattern. */ function createBjorklund(steps, pulses) { - if (pulses < 0 || steps < 0 || steps < pulses) { - return []; - } - - // Create the two arrays - let first = new Array(pulses).fill([1]); - let second = new Array(steps - pulses).fill([0]); - - let firstLength = first.length; - let minLength = Math.min(firstLength, second.length); - - let loopThreshold = 0; - // Loop until at least one array has length gt 2 (1 for first loop) - while (minLength > loopThreshold) { + if (pulses < 0 || steps < 0 || steps < pulses) { + return []; + } - // Allow only loopThreshold to be zero on the first loop - if (loopThreshold === 0) { - loopThreshold = 1; - } - - // For the minimum array loop and concat - for (var x = 0; x < minLength; x++) { - first[x] = Array.prototype.concat.call(first[x], second[x]); - } - - // if the second was the bigger array, slice the remaining elements/arrays and update - if (minLength === firstLength) { - second = Array.prototype.slice.call(second, minLength); - } - // Otherwise update the second (smallest array) with the remainders of the first - // and update the first array to include onlt the extended sub-arrays - else { - second = Array.prototype.slice.call(first, minLength); - first = Array.prototype.slice.call(first, 0, minLength); - } - firstLength = first.length; - minLength = Math.min(firstLength, second.length); - } - - // Build the final array - let pattern = []; - first.forEach(f => { - pattern = Array.prototype.concat.call(pattern, f); - }); - second.forEach(s => { - pattern = Array.prototype.concat.call(pattern, s); - }); - - return pattern; + // Create the two arrays + let first = new Array(pulses).fill([1]); + let second = new Array(steps - pulses).fill([0]); + + let firstLength = first.length; + let minLength = Math.min(firstLength, second.length); + + let loopThreshold = 0; + // Loop until at least one array has length gt 2 (1 for first loop) + while (minLength > loopThreshold) { + + // Allow only loopThreshold to be zero on the first loop + if (loopThreshold === 0) { + loopThreshold = 1; + } + + // For the minimum array loop and concat + for (var x = 0; x < minLength; x++) { + first[x] = Array.prototype.concat.call(first[x], second[x]); + } + + // if the second was the bigger array, slice the remaining elements/arrays and update + if (minLength === firstLength) { + second = Array.prototype.slice.call(second, minLength); + } + // Otherwise update the second (smallest array) with the remainders of the first + // and update the first array to include onlt the extended sub-arrays + else { + second = Array.prototype.slice.call(first, minLength); + first = Array.prototype.slice.call(first, 0, minLength); + } + firstLength = first.length; + minLength = Math.min(firstLength, second.length); + } + + // Build the final array + let pattern = []; + first.forEach(f => { + pattern = Array.prototype.concat.call(pattern, f); + }); + second.forEach(s => { + pattern = Array.prototype.concat.call(pattern, s); + }); + + return pattern; } From 5b933b808ae362773bca48f1541e469ea31b7005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 12:41:06 +0200 Subject: [PATCH 026/131] Processor types not in state anymore. --- src/js/core/config.js | 11 +---------- src/js/state/actions.js | 10 ++-------- src/js/state/reducers.js | 13 ------------- src/js/state/selectors.js | 1 - src/js/webgl/canvas3d.js | 3 +-- 5 files changed, 4 insertions(+), 34 deletions(-) diff --git a/src/js/core/config.js b/src/js/core/config.js index 983c15a0..83f4c2e0 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -6,15 +6,6 @@ */ export const PPQN = 480; -/** - * The processors available in the processors directory. - */ -export const processorTypes = { - epg: { name: 'Euclidean'}, - euclidfx: { name: 'Euclid FX'}, - output: { name: 'Output'}, -}; - const name = 'config'; export function getConfig() { @@ -59,4 +50,4 @@ export function setConfig(state) { } localStorage.setItem(name, JSON.stringify(data)); -} \ No newline at end of file +} diff --git a/src/js/state/actions.js b/src/js/state/actions.js index f047d1a5..ed1cb30b 100644 --- a/src/js/state/actions.js +++ b/src/js/state/actions.js @@ -1,12 +1,11 @@ import convertLegacyFile from '../core/convert_xml.js'; import { createUUID } from '../core/util.js'; -import { getConfig, setConfig, processorTypes } from '../core/config.js'; +import { getConfig, setConfig, } from '../core/config.js'; import { getAllMIDIPorts } from '../midi/midi.js'; import { showDialog } from '../view/dialog.js'; import { getProcessorData, } from '../core/processor-loader.js'; -const RESCAN_TYPES = 'RESCAN_TYPES', - CREATE_PROJECT = 'CREATE_PROJECT', +const CREATE_PROJECT = 'CREATE_PROJECT', SET_THEME = 'SET_THEME', CREATE_PROCESSOR = 'CREATE_PROCESSOR', ADD_PROCESSOR = 'ADD_PROCESSOR', @@ -366,11 +365,6 @@ export default { } }, - RESCAN_TYPES, - rescanTypes: () => { - return { type: RESCAN_TYPES, types: processorTypes }; - }, - SET_CAMERA_POSITION, setCameraPosition: (x, y, z, isRelative = false) => { return { type: SET_CAMERA_POSITION, x, y, z, isRelative, }; diff --git a/src/js/state/reducers.js b/src/js/state/reducers.js index 2e68371a..e6505d1b 100644 --- a/src/js/state/reducers.js +++ b/src/js/state/reducers.js @@ -39,10 +39,6 @@ const initialState = { showSettingsPanel: false, theme: 'dev', // 'light|dark' transport: 'stop', // 'play|pause|stop' - types: { - allIds: [], - byId: {}, - }, version: '2.1.0-beta.2', }; @@ -388,15 +384,6 @@ export default function reduce(state = initialState, action, actions = {}) { // reorder the processors orderProcessors(newState); return newState; - - case actions.RESCAN_TYPES: - return { - ...state, - types: { - allIds: Object.keys(action.types), - byId: action.types - } - }; case actions.SET_CAMERA_POSITION: const { x, y, z, isRelative } = action; diff --git a/src/js/state/selectors.js b/src/js/state/selectors.js index 77526432..c3c1ee46 100644 --- a/src/js/state/selectors.js +++ b/src/js/state/selectors.js @@ -17,7 +17,6 @@ export default function memoize(state, action = {}, actions) { switch (action.type) { case actions.CREATE_PROJECT: - case actions.RESCAN_TYPES: case actions.SET_THEME: setTheme(state); break; diff --git a/src/js/webgl/canvas3d.js b/src/js/webgl/canvas3d.js index 092f0468..ea6d5e3a 100644 --- a/src/js/webgl/canvas3d.js +++ b/src/js/webgl/canvas3d.js @@ -341,8 +341,7 @@ function handleStateChanges(e) { createProcessorViews(state); onWindowResize(); break; - - case actions.RESCAN_TYPES: + case actions.SET_THEME: setThemeOnWorld(); break; From 854002d1797a660c95e97caf397efd2f4de22051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 12:57:38 +0200 Subject: [PATCH 027/131] Actions refactored. --- src/js/core/util.js | 48 +++ src/js/state/actions.js | 629 ++++++++++++++++++---------------------- 2 files changed, 331 insertions(+), 346 deletions(-) diff --git a/src/js/core/util.js b/src/js/core/util.js index ccb2baa6..c24210e8 100644 --- a/src/js/core/util.js +++ b/src/js/core/util.js @@ -26,3 +26,51 @@ export function createUUID() { return v.toString(16); }); } + +/** + * Provide a default processor name. + * @param {Object} processor Processor to name. + * @return {String} Name for a newly created processor. + */ +export function getProcessorDefaultName(processors) { + let name, number, spaceIndex, + highestNumber = 0, + staticName = 'Processor'; + processors.allIds.forEach(id => { + name = processors.byId[id].params.byId.name.value; + if (name && name.indexOf(staticName) == 0) { + spaceIndex = name.lastIndexOf(' '); + if (spaceIndex != -1) { + number = parseInt(name.substr(spaceIndex), 10); + if (!isNaN(number)) { + highestNumber = Math.max(highestNumber, number); + } + } + } + }); + return `${staticName} ${highestNumber + 1}`; +} + +/** + * Convert a MIDI control value to a parameter value, depending on the parameter type. + * @param {Object} param Processor parameter. + * @param {Number} controllerValue MIDI controller value in the range 0 to 127. + */ +export function midiControlToParameterValue(param, controllerValue) { + const normalizedValue = controllerValue / 127; + switch (param.type) { + case 'integer': + return Math.round(param.min + (param.max - param.min) * normalizedValue); + case 'boolean': + return normalizedValue > .5; + case 'itemized': + if (normalizedValue === 1) { + return param.model[param.model.length - 1].value; + } + return param.model[Math.floor(normalizedValue * param.model.length)].value; + case 'string': + case 'position': + default: + return param.value; + } +} diff --git a/src/js/state/actions.js b/src/js/state/actions.js index ed1cb30b..9f539e3f 100644 --- a/src/js/state/actions.js +++ b/src/js/state/actions.js @@ -1,36 +1,36 @@ import convertLegacyFile from '../core/convert_xml.js'; -import { createUUID } from '../core/util.js'; +import { createUUID, getProcessorDefaultName, midiControlToParameterValue, } from '../core/util.js'; import { getConfig, setConfig, } from '../core/config.js'; import { getAllMIDIPorts } from '../midi/midi.js'; import { showDialog } from '../view/dialog.js'; import { getProcessorData, } from '../core/processor-loader.js'; -const CREATE_PROJECT = 'CREATE_PROJECT', - SET_THEME = 'SET_THEME', - CREATE_PROCESSOR = 'CREATE_PROCESSOR', - ADD_PROCESSOR = 'ADD_PROCESSOR', +const ADD_PROCESSOR = 'ADD_PROCESSOR', + ASSIGN_EXTERNAL_CONTROL = 'ASSIGN_EXTERNAL_CONTROL', + CREATE_MIDI_PORT = 'CREATE_MIDI_PORT', + CHANGE_PARAMETER = 'CHANGE_PARAMETER', + CONNECT_PROCESSORS = 'CONNECT_PROCESSORS', + CREATE_PROCESSOR = 'CREATE_PROCESSOR', + CREATE_PROJECT = 'CREATE_PROJECT', DELETE_PROCESSOR = 'DELETE_PROCESSOR', - SELECT_PROCESSOR = 'SELECT_PROCESSOR', - DRAG_SELECTED_PROCESSOR = 'DRAG_SELECTED_PROCESSOR', + DISCONNECT_PROCESSORS = 'DISCONNECT_PROCESSORS', DRAG_ALL_PROCESSORS = 'DRAG_ALL_PROCESSORS', - CHANGE_PARAMETER = 'CHANGE_PARAMETER', + DRAG_SELECTED_PROCESSOR = 'DRAG_SELECTED_PROCESSOR', + LIBRARY_DROP = 'LIBRARY_DROP', + RECEIVE_MIDI_CC = 'RECEIVE_MIDI_CC', RECREATE_PARAMETER = 'RECREATE_PARAMETER', + SELECT_PROCESSOR = 'SELECT_PROCESSOR', + SET_CAMERA_POSITION = 'SET_CAMERA_POSITION', SET_TEMPO = 'SET_TEMPO', - CREATE_MIDI_PORT = 'CREATE_MIDI_PORT', - UPDATE_MIDI_PORT = 'UPDATE_MIDI_PORT', - TOGGLE_MIDI_PREFERENCE = 'TOGGLE_MIDI_PREFERENCE', + SET_THEME = 'SET_THEME', + SET_TRANSPORT = 'SET_TRANSPORT', + TOGGLE_CONNECT_MODE = 'TOGGLE_CONNECT_MODE', TOGGLE_MIDI_LEARN_MODE = 'TOGGLE_MIDI_LEARN_MODE', TOGGLE_MIDI_LEARN_TARGET = 'TOGGLE_MIDI_LEARN_TARGET', - SET_TRANSPORT = 'SET_TRANSPORT', - RECEIVE_MIDI_CC = 'RECEIVE_MIDI_CC', - ASSIGN_EXTERNAL_CONTROL = 'ASSIGN_EXTERNAL_CONTROL', - UNASSIGN_EXTERNAL_CONTROL = 'UNASSIGN_EXTERNAL_CONTROL', + TOGGLE_MIDI_PREFERENCE = 'TOGGLE_MIDI_PREFERENCE', TOGGLE_PANEL = 'TOGGLE_PANEL', - TOGGLE_CONNECT_MODE = 'TOGGLE_CONNECT_MODE', - CONNECT_PROCESSORS = 'CONNECT_PROCESSORS', - DISCONNECT_PROCESSORS = 'DISCONNECT_PROCESSORS', - SET_CAMERA_POSITION = 'SET_CAMERA_POSITION', - LIBRARY_DROP = 'LIBRARY_DROP'; + UNASSIGN_EXTERNAL_CONTROL = 'UNASSIGN_EXTERNAL_CONTROL', + UPDATE_MIDI_PORT = 'UPDATE_MIDI_PORT'; // actions export default { @@ -73,353 +73,290 @@ export default { } }, - exportProject: () => { - return (dispatch, getState, getActions) => { - let jsonString = JSON.stringify(getState()), - blob = new Blob([jsonString], {type: 'application/json'}), - a = document.createElement('a'); - a.download = 'mpg.json'; - a.href = URL.createObjectURL(blob); - a.click(); - } - }, + exportProject: () => { + return (dispatch, getState, getActions) => { + let jsonString = JSON.stringify(getState()), + blob = new Blob([jsonString], {type: 'application/json'}), + a = document.createElement('a'); + a.download = 'mpg.json'; + a.href = URL.createObjectURL(blob); + a.click(); + } + }, - newProject: () => { - return (dispatch, getState, getActions) => { + newProject: () => { + return (dispatch, getState, getActions) => { - // create an empty initial state - dispatch(getActions().createProject()); + // create an empty initial state + dispatch(getActions().createProject()); - // add the existing MIDI ports - const existingMIDIPorts = getAllMIDIPorts(); - existingMIDIPorts.forEach(port => { - dispatch(getActions().midiAccessChange(port)); - }); + // add the existing MIDI ports + const existingMIDIPorts = getAllMIDIPorts(); + existingMIDIPorts.forEach(port => { + dispatch(getActions().midiAccessChange(port)); + }); - // recreate the state with the existing ports - dispatch(getActions().createProject(getState())); - } - }, - - setProject: (data) => { - return (dispatch, getState, getActions) => { - - // create an empty initial state - dispatch(getActions().createProject()); - - // add the existing MIDI ports - const existingMIDIPorts = getAllMIDIPorts(); - existingMIDIPorts.forEach(port => { - dispatch(getActions().midiAccessChange(port)); - }); - - // copy the port settings of existing ports - const existingPorts = { ...getState().ports } - - // copy the port settings defined in the project - const projectPorts = { ...data.ports }; - - // clear the project's port settings - data.ports.allIds = []; - data.ports.byId = {}; - - // add all existing ports to the project data - existingPorts.allIds.forEach(existingPortID => { - data.ports.allIds.push(existingPortID); - data.ports.byId[existingPortID] = existingPorts.byId[existingPortID]; - }); - - // set the existing ports to the project's settings, - // and create ports that do not exist - projectPorts.allIds.forEach(projectPortID => { - const projectPort = projectPorts.byId[projectPortID]; - let portExists = false; - existingPorts.allIds.forEach(existingPortID => { - if (existingPortID === projectPortID) { - - portExists = true; - - // project port's settings exists, update the settings - const existingPort = existingPorts.byId[existingPortID]; - existingPort.syncEnabled = projectPort.syncEnabled; - existingPort.remoteEnabled = projectPort.remoteEnabled; - existingPort.networkEnabled = projectPort.networkEnabled; - } - }); - - // port settings object doesn't exist, so create it, but disabled - if (!portExists) { - data.ports.allIds.push(projectPortID); - data.ports.byId[projectPortID] = { - id: projectPortID, - type: projectPort.type, - name: projectPort.name, - connection: 'closed', // closed | open | pending - state: 'disconnected', // disconnected | connected - syncEnabled: projectPort.syncEnabled, - remoteEnabled: projectPort.remoteEnabled, - networkEnabled: projectPort.networkEnabled - } - } - }); - - // create the project with the merged ports - dispatch(getActions().createProject(data)); - } - }, + // recreate the state with the existing ports + dispatch(getActions().createProject(getState())); + } + }, + + setProject: data => { + return (dispatch, getState, getActions) => { + + // create an empty initial state + dispatch(getActions().createProject()); + + // add the existing MIDI ports + const existingMIDIPorts = getAllMIDIPorts(); + existingMIDIPorts.forEach(port => { + dispatch(getActions().midiAccessChange(port)); + }); + + // copy the port settings of existing ports + const existingPorts = { ...getState().ports } + + // copy the port settings defined in the project + const projectPorts = { ...data.ports }; + + // clear the project's port settings + data.ports.allIds = []; + data.ports.byId = {}; + + // add all existing ports to the project data + existingPorts.allIds.forEach(existingPortID => { + data.ports.allIds.push(existingPortID); + data.ports.byId[existingPortID] = existingPorts.byId[existingPortID]; + }); + + // set the existing ports to the project's settings, + // and create ports that do not exist + projectPorts.allIds.forEach(projectPortID => { + const projectPort = projectPorts.byId[projectPortID]; + let portExists = false; + existingPorts.allIds.forEach(existingPortID => { + if (existingPortID === projectPortID) { + portExists = true; + + // project port's settings exists, update the settings + const existingPort = existingPorts.byId[existingPortID]; + existingPort.syncEnabled = projectPort.syncEnabled; + existingPort.remoteEnabled = projectPort.remoteEnabled; + existingPort.networkEnabled = projectPort.networkEnabled; + } + }); + + // port settings object doesn't exist, so create it, but disabled + if (!portExists) { + data.ports.allIds.push(projectPortID); + data.ports.byId[projectPortID] = { + id: projectPortID, + type: projectPort.type, + name: projectPort.name, + connection: 'closed', // closed | open | pending + state: 'disconnected', // disconnected | connected + syncEnabled: projectPort.syncEnabled, + remoteEnabled: projectPort.remoteEnabled, + networkEnabled: projectPort.networkEnabled + } + } + }); + + // create the project with the merged ports + dispatch(getActions().createProject(data)); + } + }, - CREATE_PROJECT, - createProject: (data) => { - return { type: CREATE_PROJECT, data }; - }, + CREATE_PROJECT, + createProject: data => ({ type: CREATE_PROJECT, data }), - SET_THEME, - setTheme: (themeName) => { - return { type: SET_THEME, themeName }; - }, + SET_THEME, + setTheme: themeName => ({ type: SET_THEME, themeName }), CREATE_PROCESSOR, createProcessor: data => { - return (dispatch, getState, getActions) => { - const configJson = getProcessorData(data.type, 'config'); - const id = data.id || `${data.type}_${createUUID()}`; - const fullData = {...configJson, ...data}; - fullData.id = id; - fullData.positionX = data.positionX; - fullData.positionY = data.positionY; - fullData.params.byId.name.value = data.name || getProcessorDefaultName(getState().processors); - dispatch(getActions().addProcessor(fullData)); - dispatch(getActions().selectProcessor(id)); + return (dispatch, getState, getActions) => { + const configJson = getProcessorData(data.type, 'config'); + const id = data.id || `${data.type}_${createUUID()}`; + const fullData = {...configJson, ...data}; + fullData.id = id; + fullData.positionX = data.positionX; + fullData.positionY = data.positionY; + fullData.params.byId.name.value = data.name || getProcessorDefaultName(getState().processors); + dispatch(getActions().addProcessor(fullData)); + dispatch(getActions().selectProcessor(id)); + } + }, + + ADD_PROCESSOR, + addProcessor: data => ({ type: ADD_PROCESSOR, data }), + + DELETE_PROCESSOR, + deleteProcessor: id => ({ type: DELETE_PROCESSOR, id }), + + SELECT_PROCESSOR, + selectProcessor: id => ({ type: SELECT_PROCESSOR, id }), + + DRAG_SELECTED_PROCESSOR, + dragSelectedProcessor: (x, y, z) => ({ type: DRAG_SELECTED_PROCESSOR, x, y, z }), + + DRAG_ALL_PROCESSORS, + dragAllProcessors: (x, y) => ({ type: DRAG_ALL_PROCESSORS, x, y }), + + CHANGE_PARAMETER, + changeParameter: (processorID, paramKey, paramValue) => { + return (dispatch, getState, getActions) => { + const { processors } = getState(); + const param = processors.byId[processorID].params.byId[paramKey]; + if (paramValue !== param.value) { + return { type: CHANGE_PARAMETER, processorID, paramKey, paramValue }; } + }; }, - ADD_PROCESSOR, - addProcessor: (data) => { - return { type: ADD_PROCESSOR, data }; - }, - - DELETE_PROCESSOR, - deleteProcessor: id => { - return { type: DELETE_PROCESSOR, id }; - }, - - SELECT_PROCESSOR, - selectProcessor: id => { - return { type: SELECT_PROCESSOR, id }; - }, - - DRAG_SELECTED_PROCESSOR, - dragSelectedProcessor: (x, y, z) => { - return { type: DRAG_SELECTED_PROCESSOR, x, y, z }; - }, - - DRAG_ALL_PROCESSORS, - dragAllProcessors: (x, y) => { - return { type: DRAG_ALL_PROCESSORS, x, y }; - }, - - CHANGE_PARAMETER, - changeParameter: (processorID, paramKey, paramValue) => { - return (dispatch, getState, getActions) => { - const { processors } = getState(); - const param = processors.byId[processorID].params.byId[paramKey]; - if (paramValue !== param.value) { - return { type: CHANGE_PARAMETER, processorID, paramKey, paramValue }; - } - }; - }, - - RECREATE_PARAMETER, - recreateParameter: (processorID, paramKey, paramObj) => { - return { type: RECREATE_PARAMETER, processorID, paramKey, paramObj }; - }, - - SET_TEMPO, - setTempo: value => { return { type: SET_TEMPO, value } }, - - CREATE_MIDI_PORT, - createMIDIPort: (portID, data) => { return { type: CREATE_MIDI_PORT, portID, data } }, - - UPDATE_MIDI_PORT, - updateMIDIPort: (portID, data) => { return { type: UPDATE_MIDI_PORT, portID, data } }, - - midiAccessChange: midiPort => { - return (dispatch, getState, getActions) => { - - // check if the port already exists - const state = getState(); - const portExists = state.ports.allIds.indexOf(midiPort.id) > -1; - - // create port or update existing - if (portExists) { - - // update existing port - dispatch(getActions().updateMIDIPort(midiPort.id, { - connection: midiPort.connection, - state: midiPort.state - })); - } else { - - // restore settings from config - const config = getConfig(); - let configPort = (config.ports && config.ports.byId) ? config.ports.byId[midiPort.id] : null; - - if (!configPort && config.ports && config.ports.allIds) { - for (let i = config.ports.allIds.length - 1; i >= 0; i--) { - const port = config.ports.byId[config.ports.allIds[i]]; - if (port.name === midiPort.name && port.type === midiPort.type) { - configPort = port; - break; - } - } - } - - // create port - dispatch(getActions().createMIDIPort(midiPort.id, { - id: midiPort.id, - type: midiPort.type, - name: midiPort.name, - connection: midiPort.connection, - state: midiPort.state, - networkEnabled: configPort ? configPort.networkEnabled : false, - syncEnabled: configPort ? configPort.syncEnabled : false, - remoteEnabled: configPort ? configPort.remoteEnabled : false - })); - } - - // store the changes in configuration - setConfig(getState()); - }; - }, - - TOGGLE_MIDI_PREFERENCE, - toggleMIDIPreference: (id, preferenceName, isEnabled) => ({ type: TOGGLE_MIDI_PREFERENCE, id, preferenceName, isEnabled }), - - TOGGLE_MIDI_LEARN_MODE, - toggleMIDILearnMode: () => ({ type: TOGGLE_MIDI_LEARN_MODE }), - - TOGGLE_MIDI_LEARN_TARGET, - toggleMIDILearnTarget: (processorID, parameterKey) => { - return (dispatch, getState, getActions) => { - const { learnTargetProcessorID, learnTargetParameterKey } = getState(); - if (processorID === learnTargetProcessorID && parameterKey === learnTargetParameterKey) { - return { type: TOGGLE_MIDI_LEARN_TARGET, processorID: null, parameterKey: null }; - } - return { type: TOGGLE_MIDI_LEARN_TARGET, processorID, parameterKey }; - } - }, - - SET_TRANSPORT, - setTransport: command => ({ type: SET_TRANSPORT, command }), - - RECEIVE_MIDI_CC, - receiveMIDIControlChange: (data) => { - return (dispatch, getState, getActions) => { - const state = getState(); - const remoteChannel = (data[0] & 0xf) + 1; - const remoteCC = data[1]; - - if (state.learnModeActive) { - dispatch(getActions().unassignExternalControl(state.learnTargetProcessorID, state.learnTargetParameterKey)); - dispatch(getActions().assignExternalControl(`assign_${createUUID()}`, state.learnTargetProcessorID, state.learnTargetParameterKey, remoteChannel, remoteCC)); - } else { - state.assignments.allIds.forEach(assignID => { - const assignment = state.assignments.byId[assignID]; - if (assignment.remoteChannel === remoteChannel && assignment.remoteCC === remoteCC) { - const param = state.processors.byId[assignment.processorID].params.byId[assignment.paramKey]; - const paramValue = midiControlToParameterValue(param, data[2]); - dispatch(getActions().changeParameter(assignment.processorID, assignment.paramKey, paramValue)); - } - }); - } - } - }, + RECREATE_PARAMETER, + recreateParameter: (processorID, paramKey, paramObj) => { + return { type: RECREATE_PARAMETER, processorID, paramKey, paramObj }; + }, - ASSIGN_EXTERNAL_CONTROL, - assignExternalControl: (assignID, processorID, paramKey, remoteChannel, remoteCC) => ({type: ASSIGN_EXTERNAL_CONTROL, assignID, processorID, paramKey, remoteChannel, remoteCC}), + SET_TEMPO, + setTempo: value => ({ type: SET_TEMPO, value }), + + CREATE_MIDI_PORT, + createMIDIPort: (portID, data) => { return { type: CREATE_MIDI_PORT, portID, data } }, + + UPDATE_MIDI_PORT, + updateMIDIPort: (portID, data) => { return { type: UPDATE_MIDI_PORT, portID, data } }, + + midiAccessChange: midiPort => { + return (dispatch, getState, getActions) => { + + // check if the port already exists + const state = getState(); + const portExists = state.ports.allIds.indexOf(midiPort.id) > -1; + + // create port or update existing + if (portExists) { + + // update existing port + dispatch(getActions().updateMIDIPort(midiPort.id, { + connection: midiPort.connection, + state: midiPort.state + })); + } else { + + // restore settings from config + const config = getConfig(); + let configPort = (config.ports && config.ports.byId) ? config.ports.byId[midiPort.id] : null; + + if (!configPort && config.ports && config.ports.allIds) { + for (let i = config.ports.allIds.length - 1; i >= 0; i--) { + const port = config.ports.byId[config.ports.allIds[i]]; + if (port.name === midiPort.name && port.type === midiPort.type) { + configPort = port; + break; + } + } + } + + // create port + dispatch(getActions().createMIDIPort(midiPort.id, { + id: midiPort.id, + type: midiPort.type, + name: midiPort.name, + connection: midiPort.connection, + state: midiPort.state, + networkEnabled: configPort ? configPort.networkEnabled : false, + syncEnabled: configPort ? configPort.syncEnabled : false, + remoteEnabled: configPort ? configPort.remoteEnabled : false + })); + } - UNASSIGN_EXTERNAL_CONTROL, - unassignExternalControl: (processorID, paramKey) => ({type: UNASSIGN_EXTERNAL_CONTROL, processorID, paramKey}), - - TOGGLE_PANEL, - togglePanel: panelName => ({type: TOGGLE_PANEL, panelName}), + // store the changes in configuration + setConfig(getState()); + }; + }, - TOGGLE_CONNECT_MODE, - toggleConnectMode: () => ({ type: TOGGLE_CONNECT_MODE }), + TOGGLE_MIDI_PREFERENCE, + toggleMIDIPreference: (id, preferenceName, isEnabled) => ({ type: TOGGLE_MIDI_PREFERENCE, id, preferenceName, isEnabled }), + + TOGGLE_MIDI_LEARN_MODE, + toggleMIDILearnMode: () => ({ type: TOGGLE_MIDI_LEARN_MODE }), + + TOGGLE_MIDI_LEARN_TARGET, + toggleMIDILearnTarget: (processorID, parameterKey) => { + return (dispatch, getState, getActions) => { + const { learnTargetProcessorID, learnTargetParameterKey } = getState(); + if (processorID === learnTargetProcessorID && parameterKey === learnTargetParameterKey) { + return { type: TOGGLE_MIDI_LEARN_TARGET, processorID: null, parameterKey: null }; + } + return { type: TOGGLE_MIDI_LEARN_TARGET, processorID, parameterKey }; + } + }, - CONNECT_PROCESSORS, - connectProcessors: payload => ({ type: CONNECT_PROCESSORS, payload, id: `conn_${createUUID()}` }), + SET_TRANSPORT, + setTransport: command => ({ type: SET_TRANSPORT, command }), + + RECEIVE_MIDI_CC, + receiveMIDIControlChange: data => { + return (dispatch, getState, getActions) => { + const state = getState(); + const remoteChannel = (data[0] & 0xf) + 1; + const remoteCC = data[1]; + + if (state.learnModeActive) { + dispatch(getActions().unassignExternalControl(state.learnTargetProcessorID, state.learnTargetParameterKey)); + dispatch(getActions().assignExternalControl(`assign_${createUUID()}`, state.learnTargetProcessorID, state.learnTargetParameterKey, remoteChannel, remoteCC)); + } else { + state.assignments.allIds.forEach(assignID => { + const assignment = state.assignments.byId[assignID]; + if (assignment.remoteChannel === remoteChannel && assignment.remoteCC === remoteCC) { + const param = state.processors.byId[assignment.processorID].params.byId[assignment.paramKey]; + const paramValue = midiControlToParameterValue(param, data[2]); + dispatch(getActions().changeParameter(assignment.processorID, assignment.paramKey, paramValue)); + } + }); + } + } + }, - DISCONNECT_PROCESSORS, - disconnectProcessors2: id => ({ type: DISCONNECT_PROCESSORS, id }), + ASSIGN_EXTERNAL_CONTROL, + assignExternalControl: (assignID, processorID, paramKey, remoteChannel, remoteCC) => ({type: ASSIGN_EXTERNAL_CONTROL, assignID, processorID, paramKey, remoteChannel, remoteCC}), - disconnectProcessors: id => { - return (dispatch, getState, getActions) => { - let state = getState(); - const connection = state.connections.byId[id]; - const sourceProcessor = state.processors.byId[connection.sourceProcessorID]; - const destinationProcessor = state.processors.byId[connection.destinationProcessorID]; + UNASSIGN_EXTERNAL_CONTROL, + unassignExternalControl: (processorID, paramKey) => ({type: UNASSIGN_EXTERNAL_CONTROL, processorID, paramKey}), + + TOGGLE_PANEL, + togglePanel: panelName => ({type: TOGGLE_PANEL, panelName}), - // disconnect the processors - dispatch(getActions().disconnectProcessors2(id)); - } - }, + TOGGLE_CONNECT_MODE, + toggleConnectMode: () => ({ type: TOGGLE_CONNECT_MODE }), - SET_CAMERA_POSITION, - setCameraPosition: (x, y, z, isRelative = false) => { - return { type: SET_CAMERA_POSITION, x, y, z, isRelative, }; - }, + CONNECT_PROCESSORS, + connectProcessors: payload => ({ type: CONNECT_PROCESSORS, payload, id: `conn_${createUUID()}` }), - LIBRARY_DROP, - libraryDrop: (processorType, x, y) => { - return { type: LIBRARY_DROP, processorType, x, y, }; - }, - } + DISCONNECT_PROCESSORS, + disconnectProcessors2: id => ({ type: DISCONNECT_PROCESSORS, id }), -/** - * Convert a MIDI control value to a parameter value, depending on the parameter type. - * @param {Object} param Processor parameter. - * @param {Number} controllerValue MIDI controller value in the range 0 to 127. - */ -function midiControlToParameterValue(param, controllerValue) { - const normalizedValue = controllerValue / 127; - switch (param.type) { - case 'integer': - return Math.round(param.min + (param.max - param.min) * normalizedValue); - case 'boolean': - return normalizedValue > .5; - case 'itemized': - if (normalizedValue === 1) { - return param.model[param.model.length - 1].value; - } - return param.model[Math.floor(normalizedValue * param.model.length)].value; - case 'string': - case 'position': - default: - return param.value; - } -} + disconnectProcessors: id => { + return (dispatch, getState, getActions) => { + let state = getState(); + const connection = state.connections.byId[id]; + const sourceProcessor = state.processors.byId[connection.sourceProcessorID]; + const destinationProcessor = state.processors.byId[connection.destinationProcessorID]; -/** - * Provide a default processor name. - * @param {Object} processor Processor to name. - * @return {String} Name for a newly created processor. - */ -function getProcessorDefaultName(processors) { - let name, number, spaceIndex, - highestNumber = 0, - staticName = 'Processor'; - processors.allIds.forEach(id => { - name = processors.byId[id].params.byId.name.value; - if (name && name.indexOf(staticName) == 0) { - spaceIndex = name.lastIndexOf(' '); - if (spaceIndex != -1) { - number = parseInt(name.substr(spaceIndex), 10); - if (!isNaN(number)) { - highestNumber = Math.max(highestNumber, number); - } - } - } - }); - return `${staticName} ${highestNumber + 1}`; + // disconnect the processors + dispatch(getActions().disconnectProcessors2(id)); + } + }, + + SET_CAMERA_POSITION, + setCameraPosition: (x, y, z, isRelative = false) => { + return { type: SET_CAMERA_POSITION, x, y, z, isRelative, }; + }, + + LIBRARY_DROP, + libraryDrop: (processorType, x, y) => { + return { type: LIBRARY_DROP, processorType, x, y, }; + }, } From 1a416e7ca291409cd46901ceed450ad661c4b6e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 14:55:52 +0200 Subject: [PATCH 028/131] Reducers refactored. --- src/js/state/reducers.js | 771 ++++++++++++++++++++------------------- 1 file changed, 386 insertions(+), 385 deletions(-) diff --git a/src/js/state/reducers.js b/src/js/state/reducers.js index e6505d1b..23556aa0 100644 --- a/src/js/state/reducers.js +++ b/src/js/state/reducers.js @@ -1,45 +1,45 @@ import orderProcessors from '../midi/network_ordering.js'; const initialState = { - assignments: { - allIds: [], - byId: {}, - }, - bpm: 120, - camera: { - x: 0, - y: 0, - z: 0, - }, - connections: { - allIds: [], - byId: {}, - }, - connectModeActive: false, - learnModeActive: false, - learnTargetParameterKey: null, - learnTargetProcessorID: null, - libraryDropPosition: { - type: null, - x: 0, - y: 0, - }, - ports: { - allIds: [], - byId: {}, - }, - processors: { - allIds: [], - byId: {}, - }, - selectedID: null, - showHelpPanel: false, - showLibraryPanel: true, - showPreferencesPanel: false, - showSettingsPanel: false, - theme: 'dev', // 'light|dark' - transport: 'stop', // 'play|pause|stop' - version: '2.1.0-beta.2', + assignments: { + allIds: [], + byId: {}, + }, + bpm: 120, + camera: { + x: 0, + y: 0, + z: 0, + }, + connections: { + allIds: [], + byId: {}, + }, + connectModeActive: false, + learnModeActive: false, + learnTargetParameterKey: null, + learnTargetProcessorID: null, + libraryDropPosition: { + type: null, + x: 0, + y: 0, + }, + ports: { + allIds: [], + byId: {}, + }, + processors: { + allIds: [], + byId: {}, + }, + selectedID: null, + showHelpPanel: false, + showLibraryPanel: true, + showPreferencesPanel: false, + showSettingsPanel: false, + theme: 'dev', // 'light|dark' + transport: 'stop', // 'play|pause|stop' + version: '2.1.0-beta.2', }; /** @@ -49,364 +49,365 @@ const initialState = { * @param {String} action.type */ export default function reduce(state = initialState, action, actions = {}) { - let newState; - switch(action.type) { + let newState; + switch(action.type) { - case actions.CREATE_PROJECT: - return { - ...initialState, - ...(action.data || {}), - transport: initialState.transport, - }; + case actions.CREATE_PROJECT: + return { + ...initialState, + ...(action.data || {}), + transport: initialState.transport, + }; - case actions.SET_THEME: - return { - ...state, - theme: state.theme === 'light' ? 'dark' : 'light', - }; + case actions.SET_THEME: + return { + ...state, + theme: state.theme === 'light' ? 'dark' : 'light', + }; - case actions.ADD_PROCESSOR: - newState = { - ...state, - showSettingsPanel: true, - processors: { - byId: { - ...state.processors.byId, - [action.data.id]: action.data - }, - allIds: [ ...state.processors.allIds ] - } }; + case actions.ADD_PROCESSOR: + newState = { + ...state, + showSettingsPanel: true, + processors: { + byId: { + ...state.processors.byId, + [action.data.id]: action.data + }, + allIds: [ ...state.processors.allIds ] + } }; - // array index depends on processor type - let numInputProcessors = newState.processors.allIds.filter(id => { newState.processors.byId[id].type === 'input' }).length; - switch (action.data.type) { - case 'input': - newState.processors.allIds.unshift(action.data.id); - numInputProcessors++; - break; - case 'output': - newState.processors.allIds.push(action.data.id); - break; - default: - newState.processors.allIds.splice(numInputProcessors, 0, action.data.id); - } - - return newState; - - case actions.DELETE_PROCESSOR: - const index = state.processors.allIds.indexOf(action.id); - - // delete the processor - newState = { - ...state, - processors: { - byId: { ...state.processors.byId }, - allIds: state.processors.allIds.filter(id => id !== action.id) - } }; - delete newState.processors.byId[action.id]; - - // delete all connections to and from the deleted processor - newState.connections = { - byId: { ...state.connections.byId }, - allIds: [ ...state.connections.allIds ] - } - for (let i = newState.connections.allIds.length -1, n = 0; i >= n; i--) { - const connectionID = newState.connections.allIds[i]; - const connection = newState.connections.byId[connectionID]; - if (connection.sourceProcessorID === action.id || connection.destinationProcessorID === action.id) { - newState.connections.allIds.splice(i, 1); - delete newState.connections.byId[connectionID]; - } - } + // array index depends on processor type + let numInputProcessors = newState.processors.allIds.filter(id => { newState.processors.byId[id].type === 'input' }).length; + switch (action.data.type) { + case 'input': + newState.processors.allIds.unshift(action.data.id); + numInputProcessors++; + break; + case 'output': + newState.processors.allIds.push(action.data.id); + break; + default: + newState.processors.allIds.splice(numInputProcessors, 0, action.data.id); + } + + return newState; + + case actions.DELETE_PROCESSOR: + const index = state.processors.allIds.indexOf(action.id); + + // delete the processor + newState = { + ...state, + processors: { + byId: { ...state.processors.byId }, + allIds: state.processors.allIds.filter(id => id !== action.id) + } }; + delete newState.processors.byId[action.id]; + + // delete all connections to and from the deleted processor + newState.connections = { + byId: { ...state.connections.byId }, + allIds: [ ...state.connections.allIds ] + } + for (let i = newState.connections.allIds.length -1, n = 0; i >= n; i--) { + const connectionID = newState.connections.allIds[i]; + const connection = newState.connections.byId[connectionID]; + if (connection.sourceProcessorID === action.id || connection.destinationProcessorID === action.id) { + newState.connections.allIds.splice(i, 1); + delete newState.connections.byId[connectionID]; + } + } - // select the next processor, if any, or a previous one - let newIndex; - if (newState.selectedID === action.id && newState.processors.allIds.length) { - if (newState.processors.allIds[index]) { - newIndex = index; - } else if (index > 0) { - newIndex = index - 1; - } else { - newIndex = 0; - } - newState.selectedID = newState.processors.allIds[newIndex]; - } - - // reorder the processors - orderProcessors(newState); + // select the next processor, if any, or a previous one + let newIndex; + if (newState.selectedID === action.id && newState.processors.allIds.length) { + if (newState.processors.allIds[index]) { + newIndex = index; + } else if (index > 0) { + newIndex = index - 1; + } else { + newIndex = 0; + } + newState.selectedID = newState.processors.allIds[newIndex]; + } + + // reorder the processors + orderProcessors(newState); - return newState; - - case actions.SELECT_PROCESSOR: - return { ...state, selectedID: action.id }; - - case actions.DRAG_SELECTED_PROCESSOR: - return { - ...state, - processors: { - allIds: [ ...state.processors.allIds ], - byId: Object.values(state.processors.byId).reduce((accumulator, processor) => { - if (processor.id === state.selectedID) { - accumulator[processor.id] = { ...processor, positionX: action.x, positionY: action.y, positionZ: action.z }; - } else { - accumulator[processor.id] = { ...processor }; - } - return accumulator; - }, {}) - } }; + return newState; + + case actions.SELECT_PROCESSOR: + return { ...state, selectedID: action.id }; + + case actions.DRAG_SELECTED_PROCESSOR: + return { + ...state, + processors: { + allIds: [ ...state.processors.allIds ], + byId: Object.values(state.processors.byId).reduce((accumulator, processor) => { + if (processor.id === state.selectedID) { + accumulator[processor.id] = { ...processor, positionX: action.x, positionY: action.y, positionZ: action.z }; + } else { + accumulator[processor.id] = { ...processor }; + } + return accumulator; + }, {}) + } + }; - case actions.DRAG_ALL_PROCESSORS: - return { - ...state, - processors: { - allIds: [ ...state.processors.allIds ], - byId: Object.values(state.processors.byId).reduce((accumulator, processor) => { - accumulator[processor.id] = { - ...processor, - positionX: processor.positionX + action.x, - positionY: processor.positionY + action.y }; - return accumulator; - }, {}) - } }; - - case actions.CHANGE_PARAMETER: - newState = { - ...state, - processors: { - byId: { ...state.processors.byId }, - allIds: [ ...state.processors.allIds ] - } }; - const param = newState.processors.byId[action.processorID].params.byId[action.paramKey]; - switch (param.type) { - case 'integer': - param.value = Math.max(param.min, Math.min(action.paramValue, param.max)); - break; - case 'boolean': - param.value = !!action.paramValue; - break; - case 'itemized': - param.value = action.paramValue; - break; - case 'string': - param.value = action.paramValue; - break; - } - return newState; - - case actions.RECREATE_PARAMETER: - // clone state - newState = { - ...state, - processors: { - byId: { ...state.processors.byId }, - allIds: [ ...state.processors.allIds ] - } }; - - // clone parameter, overwrite with new settings. - newState.processors.byId[action.processorID].params.byId[action.paramKey] = { - ...newState.processors.byId[action.processorID].params.byId[action.paramKey], - ...action.paramObj - }; - - return newState; - - case actions.SET_TEMPO: - return { ...state, bpm: action.value }; + case actions.DRAG_ALL_PROCESSORS: + return { + ...state, + processors: { + allIds: [ ...state.processors.allIds ], + byId: Object.values(state.processors.byId).reduce((accumulator, processor) => { + accumulator[processor.id] = { + ...processor, + positionX: processor.positionX + action.x, + positionY: processor.positionY + action.y }; + return accumulator; + }, {}) + } + }; + + case actions.CHANGE_PARAMETER: + newState = { + ...state, + processors: { + byId: { ...state.processors.byId }, + allIds: [ ...state.processors.allIds ] + } }; + const param = newState.processors.byId[action.processorID].params.byId[action.paramKey]; + switch (param.type) { + case 'integer': + param.value = Math.max(param.min, Math.min(action.paramValue, param.max)); + break; + case 'boolean': + param.value = !!action.paramValue; + break; + case 'itemized': + param.value = action.paramValue; + break; + case 'string': + param.value = action.paramValue; + break; + } + return newState; + + case actions.RECREATE_PARAMETER: + newState = { + ...state, + processors: { + byId: { ...state.processors.byId }, + allIds: [ ...state.processors.allIds ], + } }; + + // clone parameter, overwrite with new settings. + newState.processors.byId[action.processorID].params.byId[action.paramKey] = { + ...newState.processors.byId[action.processorID].params.byId[action.paramKey], + ...action.paramObj, + }; + + return newState; + + case actions.SET_TEMPO: + return { ...state, bpm: action.value }; - case actions.CREATE_MIDI_PORT: - return { - ...state, - ports: { - allIds: [ ...state.ports.allIds, action.portID ], - byId: { - ...state.ports.byId, - [action.portID]: action.data - } - } - }; + case actions.CREATE_MIDI_PORT: + return { + ...state, + ports: { + allIds: [ ...state.ports.allIds, action.portID ], + byId: { + ...state.ports.byId, + [action.portID]: action.data + }, + }, + }; - case actions.UPDATE_MIDI_PORT: - return { - ...state, - ports: { - allIds: [ ...state.ports.allIds ], - byId: Object.values(state.ports.byId).reduce((returnObject, port) => { - if (port.id === action.portID) { - returnObject[port.id] = { ...port, ...action.data }; - } else { - returnObject[port.id] = { ...port }; - } - return returnObject; - }, {}) - } - }; - - case actions.TOGGLE_MIDI_PREFERENCE: - return { - ...state, - ports: { - allIds: [ ...state.ports.allIds ], - byId: Object.values(state.ports.allIds).reduce((accumulator, portID) => { - if (portID === action.id) { - accumulator[portID] = { - ...state.ports.byId[portID], - [action.preferenceName]: typeof action.isEnabled === 'boolean' ? isEnabled : !state.ports.byId[action.id][action.preferenceName] - }; - } else { - accumulator[portID] = { ...state.ports.byId[portID] }; - } - return accumulator; - }, {}) - } - }; - - case actions.TOGGLE_MIDI_LEARN_MODE: - return { ...state, learnModeActive: !state.learnModeActive }; - - case actions.TOGGLE_MIDI_LEARN_TARGET: - return { - ...state, - learnTargetProcessorID: action.processorID, - learnTargetParameterKey: action.parameterKey - }; - - case actions.SET_TRANSPORT: - let value = action.command; - if (action.command === 'toggle') { - value = state.transport === 'play' ? 'pause' : 'play'; - } - return Object.assign({}, state, { - transport: value - }); + case actions.UPDATE_MIDI_PORT: + return { + ...state, + ports: { + allIds: [ ...state.ports.allIds ], + byId: Object.values(state.ports.byId).reduce((returnObject, port) => { + if (port.id === action.portID) { + returnObject[port.id] = { ...port, ...action.data }; + } else { + returnObject[port.id] = { ...port }; + } + return returnObject; + }, {}) + }, + }; + + case actions.TOGGLE_MIDI_PREFERENCE: + return { + ...state, + ports: { + allIds: [ ...state.ports.allIds ], + byId: Object.values(state.ports.allIds).reduce((accumulator, portID) => { + if (portID === action.id) { + accumulator[portID] = { + ...state.ports.byId[portID], + [action.preferenceName]: typeof action.isEnabled === 'boolean' ? isEnabled : !state.ports.byId[action.id][action.preferenceName] + }; + } else { + accumulator[portID] = { ...state.ports.byId[portID] }; + } + return accumulator; + }, {}), + }, + }; + + case actions.TOGGLE_MIDI_LEARN_MODE: + return { ...state, learnModeActive: !state.learnModeActive }; + + case actions.TOGGLE_MIDI_LEARN_TARGET: + return { + ...state, + learnTargetProcessorID: action.processorID, + learnTargetParameterKey: action.parameterKey + }; + + case actions.SET_TRANSPORT: + let value = action.command; + if (action.command === 'toggle') { + value = state.transport === 'play' ? 'pause' : 'play'; + } + return Object.assign({}, state, { + transport: value, + }); - case actions.ASSIGN_EXTERNAL_CONTROL: - return { - ...state, - assignments: { - allIds: [...state.assignments.allIds, action.assignID], - byId: { - ...state.assignments.byId, - [action.assignID]: { - remoteChannel: action.remoteChannel, - remoteCC: action.remoteCC, - processorID: action.processorID, - paramKey: action.paramKey - } - } - } - }; - - case actions.UNASSIGN_EXTERNAL_CONTROL: - return { - ...state, - assignments: { - allIds: state.assignments.allIds.reduce((accumulator, assignID) => { - const assignment = state.assignments.byId[assignID]; - if (assignment.processorID !== action.processorID || assignment.paramKey !== action.paramKey) { - accumulator.push(assignID); - } - return accumulator; - }, []), - byId: state.assignments.allIds.reduce((accumulator, assignID) => { - const assignment = state.assignments.byId[assignID]; - if (assignment.processorID !== action.processorID || assignment.paramKey !== action.paramKey) { - accumulator[assignID] = {...assignment}; - } - return accumulator; - }, {}) - } - }; - - case actions.TOGGLE_PANEL: - return { - ...state, - showHelpPanel: action.panelName === 'help' ? !state.showHelpPanel : state.showHelpPanel, - showPreferencesPanel: action.panelName === 'preferences' ? !state.showPreferencesPanel : state.showPreferencesPanel, - showSettingsPanel: action.panelName === 'settings' ? !state.showSettingsPanel : state.showSettingsPanel, - showLibraryPanel: action.panelName === 'library' ? !state.showLibraryPanel : state.showLibraryPanel - }; - - case actions.TOGGLE_CONNECT_MODE: - return { - ...state, - connectModeActive: !state.connectModeActive - }; - - case actions.CONNECT_PROCESSORS: + case actions.ASSIGN_EXTERNAL_CONTROL: + return { + ...state, + assignments: { + allIds: [...state.assignments.allIds, action.assignID], + byId: { + ...state.assignments.byId, + [action.assignID]: { + remoteChannel: action.remoteChannel, + remoteCC: action.remoteCC, + processorID: action.processorID, + paramKey: action.paramKey + }, + }, + }, + }; + + case actions.UNASSIGN_EXTERNAL_CONTROL: + return { + ...state, + assignments: { + allIds: state.assignments.allIds.reduce((accumulator, assignID) => { + const assignment = state.assignments.byId[assignID]; + if (assignment.processorID !== action.processorID || assignment.paramKey !== action.paramKey) { + accumulator.push(assignID); + } + return accumulator; + }, []), + byId: state.assignments.allIds.reduce((accumulator, assignID) => { + const assignment = state.assignments.byId[assignID]; + if (assignment.processorID !== action.processorID || assignment.paramKey !== action.paramKey) { + accumulator[assignID] = {...assignment}; + } + return accumulator; + }, {}), + } + }; + + case actions.TOGGLE_PANEL: + return { + ...state, + showHelpPanel: action.panelName === 'help' ? !state.showHelpPanel : state.showHelpPanel, + showPreferencesPanel: action.panelName === 'preferences' ? !state.showPreferencesPanel : state.showPreferencesPanel, + showSettingsPanel: action.panelName === 'settings' ? !state.showSettingsPanel : state.showSettingsPanel, + showLibraryPanel: action.panelName === 'library' ? !state.showLibraryPanel : state.showLibraryPanel, + }; + + case actions.TOGGLE_CONNECT_MODE: + return { + ...state, + connectModeActive: !state.connectModeActive, + }; + + case actions.CONNECT_PROCESSORS: - // abort if the connection already exists - for (let i = 0, n = state.connections.allIds.length; i < n; i++) { - const connection = state.connections.byId[state.connections.allIds[i]]; - if (connection.sourceProcessorID === action.payload.sourceProcessorID && - connection.sourceConnectorID === action.payload.sourceConnectorID && - connection.destinationProcessorID === action.payload.destinationProcessorID && - connection.destinationConnectorID === action.payload.destinationConnectorID) { - return state; - } - } + // abort if the connection already exists + for (let i = 0, n = state.connections.allIds.length; i < n; i++) { + const connection = state.connections.byId[state.connections.allIds[i]]; + if (connection.sourceProcessorID === action.payload.sourceProcessorID && + connection.sourceConnectorID === action.payload.sourceConnectorID && + connection.destinationProcessorID === action.payload.destinationProcessorID && + connection.destinationConnectorID === action.payload.destinationConnectorID) { + return state; + } + } - // add new connection - newState = { - ...state, - connections: { - byId: { ...state.connections.byId, [action.id]: action.payload }, - allIds: [ ...state.connections.allIds, action.id ] - }, - processors: { - byId: { ...state.processors.byId }, - allIds: [ ...state.processors.allIds ] - } - }; + // add new connection + newState = { + ...state, + connections: { + byId: { ...state.connections.byId, [action.id]: action.payload }, + allIds: [ ...state.connections.allIds, action.id ] + }, + processors: { + byId: { ...state.processors.byId }, + allIds: [ ...state.processors.allIds ] + }, + }; - // reorder the processors - orderProcessors(newState); - return newState; - - case actions.DISCONNECT_PROCESSORS: - newState = { - ...state, - connections: { - allIds: state.connections.allIds.reduce((accumulator, connectionID) => { - if (connectionID !== action.id) { - accumulator.push(connectionID) - } - return accumulator; - }, []), - byId: Object.values(state.connections.allIds).reduce((accumulator, connectionID) => { - if (connectionID !== action.id) { - accumulator[connectionID] = { ...state.connections.byId[connectionID] }; - } - return accumulator; - }, {}) - } - }; - - // reorder the processors - orderProcessors(newState); - return newState; - - case actions.SET_CAMERA_POSITION: - const { x, y, z, isRelative } = action; - return { - ...state, - camera: { - x: isRelative ? state.camera.x + x : x, - y: isRelative ? state.camera.y + y : y, - z: isRelative ? state.camera.z + z : z, - } - }; - - case actions.LIBRARY_DROP: - return { - ...state, - libraryDropPosition: { - type: action.processorType, - x: action.x, - y: action.y, - } - }; + // reorder the processors + orderProcessors(newState); + return newState; + + case actions.DISCONNECT_PROCESSORS: + newState = { + ...state, + connections: { + allIds: state.connections.allIds.reduce((accumulator, connectionID) => { + if (connectionID !== action.id) { + accumulator.push(connectionID) + } + return accumulator; + }, []), + byId: Object.values(state.connections.allIds).reduce((accumulator, connectionID) => { + if (connectionID !== action.id) { + accumulator[connectionID] = { ...state.connections.byId[connectionID] }; + } + return accumulator; + }, {}), + }, + }; + + // reorder the processors + orderProcessors(newState); + return newState; + + case actions.SET_CAMERA_POSITION: + const { x, y, z, isRelative } = action; + return { + ...state, + camera: { + x: isRelative ? state.camera.x + x : x, + y: isRelative ? state.camera.y + y : y, + z: isRelative ? state.camera.z + z : z, + }, + }; + + case actions.LIBRARY_DROP: + return { + ...state, + libraryDropPosition: { + type: action.processorType, + x: action.x, + y: action.y, + }, + }; - default: - return state ? state : initialState; - } + default: + return state ? state : initialState; + } }; From ff749234b1696c83b3259d725dcf0c41d2ad31b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 15:00:37 +0200 Subject: [PATCH 029/131] Connecting processors fixed. --- src/js/midi/network.js | 2 +- src/js/webgl/canvas3d.js | 13 ++++++++ src/js/webgl/connections3d.js | 60 ++++++++++++++++++++++++++--------- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/js/midi/network.js b/src/js/midi/network.js index c22f10b0..15697570 100644 --- a/src/js/midi/network.js +++ b/src/js/midi/network.js @@ -35,7 +35,7 @@ function connectProcessors(state) { state.connections.allIds.forEach(connectionID => { const connection = state.connections.byId[connectionID]; const sourceProcessor = processors.find(processor => processor.getID() === connection.sourceProcessorID); - const destinationProcessor = sourceProcessor.getDestinations().find(processor => processor.getID() === connection.destinationProcessorID); + const destinationProcessor = processors.find(processor => processor.getID() === connection.destinationProcessorID); if (!destinationProcessor) { sourceProcessor.connect(destinationProcessor); } diff --git a/src/js/webgl/canvas3d.js b/src/js/webgl/canvas3d.js index ea6d5e3a..a121334d 100644 --- a/src/js/webgl/canvas3d.js +++ b/src/js/webgl/canvas3d.js @@ -340,6 +340,7 @@ function handleStateChanges(e) { clearProcessorViews(); createProcessorViews(state); onWindowResize(); + toggleConnectMode(state); break; case actions.SET_THEME: @@ -353,6 +354,10 @@ function handleStateChanges(e) { case actions.LIBRARY_DROP: onDrop(state); break; + + case actions.TOGGLE_CONNECT_MODE: + toggleConnectMode(state); + break; } } @@ -474,6 +479,14 @@ function setThemeOnWorld() { renderer.setClearColor(new Color(getTheme().colorBackground)); } +/** + * Enter or leave application connect mode. + * @param {Boolean} isEnabled True to enable connect mode. + */ +function toggleConnectMode(state) { + isConnectMode = state.connectModeActive; +} + /** * Update the camera position to what's stored in the state. */ diff --git a/src/js/webgl/connections3d.js b/src/js/webgl/connections3d.js index b9236bb7..2e4001f1 100644 --- a/src/js/webgl/connections3d.js +++ b/src/js/webgl/connections3d.js @@ -1,26 +1,26 @@ import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; import { getTheme } from '../state/selectors.js'; -import { createCircleFilled, createCircleOutline, createShape, } from './draw3dHelper.js'; +import { createCircleFilled, createCircleOutline, createShape, redrawShape, } from './draw3dHelper.js'; import { getScene } from './canvas3d.js'; const { + CubicBezierCurve, Group, Vector2, } = THREE; -const state = { - sourceProcessorID: null, - sourceConnectorID: null, - sourceConnectorPosition: null, - }, - deleteButtonRadius = 2.0, +const deleteButtonRadius = 2.0, deleteCrossRadius = 0.8, dragHandleRadius = 1.5; -let lineMaterial, - currentCable, +let currentCable, currentCableDragHandle, - cablesGroup; + cablesGroup, + state = { + sourceProcessorID: null, + sourceConnectorID: null, + sourceConnectorPosition: null, + }; /** * Create connection between two processors. @@ -134,6 +134,36 @@ function createCable(connectionId, isConnectMode) { return cable; } +/** + * Enter or leave application connect mode. + * @param {Vector3} sourcePosition Cable start position. + * @param {Vector3} destinationPosition Cable end position. + */ +function drawCable(connectionID, sourcePosition, destinationPosition) { + const cable = cablesGroup.getObjectByName(connectionID); + if (cable) { + const distance = sourcePosition.distanceTo(destinationPosition); + const curveStrength = Math.min(distance / 2, 30); + const curve = new CubicBezierCurve( + sourcePosition.clone(), + sourcePosition.clone().sub(new Vector2(0, curveStrength)), + destinationPosition.clone().add(new Vector2(0, curveStrength)), + destinationPosition.clone() + ); + const points = curve.getPoints(50); + + redrawShape(cable, points, getTheme().colorLow); + + const deleteBtn = cable.getObjectByName('delete'); + if (deleteBtn) { + + // get mid point on cable + const position = points[Math.floor(points.length / 2)]; + deleteBtn.position.set(position.x, position.y, 0); + } + } +} + /** * Draw all cables acctording to the state. * @param {Object} state Application state. @@ -168,7 +198,7 @@ function handleStateChanges(e) { const { state, action, actions, } = e.detail; switch (action.type) { case actions.TOGGLE_CONNECT_MODE: - toggleConnectMode(state.connectModeActive); + toggleConnectMode(state); break; case actions.DELETE_PROCESSOR: @@ -187,12 +217,12 @@ function handleStateChanges(e) { updateTheme(); updateCables(state); drawCables(state); - toggleConnectMode(state.connectModeActive); + toggleConnectMode(state); break; case actions.SET_THEME: updateTheme(); - toggleConnectMode(state.connectModeActive); + toggleConnectMode(state); break; } } @@ -201,12 +231,12 @@ function handleStateChanges(e) { * Enter or leave application connect mode. * @param {Boolean} isEnabled True to enable connect mode. */ -function toggleConnectMode(isEnabled) { +function toggleConnectMode(state) { // toggle cable delete buttons cablesGroup.children.forEach(cable => { const deleteBtn = cable.getObjectByName('delete'); - deleteBtn.visible = isEnabled; + deleteBtn.visible = state.connectModeActive; }); } From 2afc562fc6e286409faf74842856fea7dcc1f86d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 15:19:29 +0200 Subject: [PATCH 030/131] Cleanup and small fixes. --- src/js/README.dev.md | 14 - src/js/main.js | 83 +----- src/js/view/app.js | 371 -------------------------- src/js/webgl/canvas3d.js | 476 ---------------------------------- src/js/webgl/connections3d.js | 290 --------------------- src/js/webgl/text3d.js | 1 - yarn.lock | 8 +- 7 files changed, 5 insertions(+), 1238 deletions(-) delete mode 100644 src/js/README.dev.md delete mode 100644 src/js/view/app.js diff --git a/src/js/README.dev.md b/src/js/README.dev.md deleted file mode 100644 index 8d0032d5..00000000 --- a/src/js/README.dev.md +++ /dev/null @@ -1,14 +0,0 @@ - -### Drag and drop from library - -The library's `drop` event handler dispatches a `createProcessor()` action -creator. - -This loads the processor type's `config.json` and then dispatches the -`ADD_PROCESSOR` and `SELECT_PROCESSOR` actions. - -In response to `ADD_PROCESSOR` canvas3D's `createProcessorViews()` function -creates and adds a WebGL 3D object for the processor. - -`createProcessorViews()` uses `positionX` and `positionY` canvas -coordinates to position the 3D object in the scene. \ No newline at end of file diff --git a/src/js/main.js b/src/js/main.js index 2bde752f..caecd96c 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -17,9 +17,6 @@ */ import { dispatch, getActions, getState, persist, } from './state/store.js'; - -// import createAppView from './view/app.js'; -// import createDialog from './view/dialog.js'; import { accessMidi } from './midi/midi.js'; import { preloadProcessors } from './core/processor-loader.js'; import { setup as setupCanvas3d } from './webgl/canvas3d.js'; @@ -32,7 +29,6 @@ import { setup as setupPreferences } from './view/preferences.js'; import { setup as setupRemote } from './view/remote.js'; import { setup as setupTransport } from './core/transport.js'; import { setup as setupSequencer } from './core/sequencer.js'; -import { showDialog } from './view/dialog.js'; async function main() { await accessMidi(); @@ -50,84 +46,7 @@ async function main() { setupSequencer(); persist(); + dispatch(getActions().setProject(getState())); } main(); - -/** - * Application startup. - */ -// function init() { -// // Create all objects that will be the modules of the app. -// var appView = {}, -// canvasView = {}, -// dialog = {}, -// libraryView = {}, -// midi = {}, -// midiNetwork = {}, -// preferencesView = {}, -// remoteView = {}, -// transport = {}; - -// const store = createStore({ -// actions: createActions(), -// reducers: createReducers() -// }); - -// // Add functionality to the modules and inject dependencies. -// createAppView({ -// that: appView, -// store -// }); -// createCanvas3d({ -// that: canvasView, -// store -// }); -// createDialog({ -// that: dialog, -// }); -// createLibraryView({ -// that: libraryView, -// store -// }); -// createMIDI({ -// that: midi, -// store -// }); -// createMIDINetwork({ -// that: midiNetwork, -// store -// }); -// createPreferencesView({ -// that: preferencesView, -// store -// }); -// createRemoteView({ -// that: remoteView, -// store -// }); -// createTransport({ -// that: transport, -// store, -// canvasView, -// midiNetwork -// }); - -// // scan installed processors -// store.dispatch(store.getActions().rescanTypes()); - -// // initialise -// midi.connect() -// .then(() => { -// store.persist(); -// transport.run(); -// }) -// .catch(errorMsg => { -// showDialog('No MIDI available', `The app was unable to find any MIDI ports. This is usually because the browser doesn't support Web MIDI. Check current browser support at Can I Use.

Error message:
${errorMsg}`); -// }); -// } - -// document.addEventListener('DOMContentLoaded', function() { -// console.log('DOMContentLoaded'); -// init(); -// }); diff --git a/src/js/view/app.js b/src/js/view/app.js deleted file mode 100644 index d48063c9..00000000 --- a/src/js/view/app.js +++ /dev/null @@ -1,371 +0,0 @@ -import createSettingsPanel from './settings.js'; -import addWindowResize from './windowresize.js'; - -/** - * Main application view. - */ -export default function createAppView(specs, my) { - let that, - store = specs.store, - rootEl = document.querySelector('#app'), - panelsEl = document.querySelector('.panels'), - libraryEl = document.querySelector('.library'), - helpEl = document.querySelector('.help'), - prefsEl = document.querySelector('.prefs'), - editEl = document.querySelector('.edit'), - editContentEl = document.querySelector('.edit .panel__content'), - remoteEl = document.querySelector('.remote'), - settingsViews = [], - panelHeaderHeight, - resetKeyCombo = [], - controls = { - new: { - type: 'checkbox', - input: document.querySelector('#file-new') - }, - import: { - type: 'checkbox', - input: document.querySelector('#file-import') - }, - export: { - type: 'checkbox', - input: document.querySelector('#file-export') - }, - play: { - type: 'checkbox', - input: document.getElementById('play-check') - }, - bpm: { - type: 'number', - input: document.getElementById('bpm-number') - }, - library: { - type: 'checkbox', - input: document.getElementById('library-check') - }, - remote: { - type: 'checkbox', - input: document.getElementById('learn-check') - }, - prefs: { - type: 'checkbox', - input: document.getElementById('prefs-check') - }, - edit: { - type: 'checkbox', - input: document.getElementById('edit-check') - }, - connections: { - type: 'checkbox', - input: document.getElementById('connections-check') - }, - help: { - type: 'checkbox', - input: document.getElementById('help-check') - } - }, - - init = function() { - controls.new.input.addEventListener('click', function(e) { - store.dispatch(store.getActions().newProject()); - }); - controls.import.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().importProject(e.target.files[0])); - }); - controls.export.input.addEventListener('click', function(e) { - store.dispatch(store.getActions().exportProject()); - }); - controls.play.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().setTransport('toggle')); - }); - controls.bpm.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().setTempo(controls.bpm.input.value)); - }); - controls.remote.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().toggleMIDILearnMode()); - }); - controls.library.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().togglePanel('library')); - }); - controls.prefs.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().togglePanel('preferences')); - }); - controls.edit.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().togglePanel('settings')); - }); - controls.connections.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().toggleConnectMode()); - }); - controls.help.input.addEventListener('change', function(e) { - store.dispatch(store.getActions().togglePanel('help')); - }); - - document.addEventListener('keyup', function(e) { - - // don't perform shortcuts while typing in a text input. - if (!(e.target.tagName.toLowerCase() == 'input' && e.target.getAttribute('type') == 'text')) { - switch (e.keyCode) { - case 32: // space - store.dispatch(store.getActions().setTransport('toggle')); - break; - - case 83: // s - console.log('state', store.getState()); - break; - } - } - resetKeyCombo.length = 0; - }); - - document.addEventListener('keydown', e => { - - // don't perform shortcuts while typing in a text input. - if (!(e.target.tagName.toLowerCase() == 'input' && e.target.getAttribute('type') == 'text')) { - switch (e.keyCode) { - case 82: // r - case 83: // s - case 84: // t - // clear all data on key combination 'rst' (reset) - resetKeyCombo.push(e.keyCode); - if (resetKeyCombo.indexOf(82) > -1 && resetKeyCombo.indexOf(83) > -1 && resetKeyCombo.indexOf(84) > -1) { - localStorage.clear(); - store.dispatch(store.getActions().newProject()); - } - break; - } - } - }); - - document.addEventListener(store.STATE_CHANGE, e => { - const { action, actions, state } = e.detail; - switch (action.type) { - - case actions.CREATE_PROJECT: - setProject(state); - showPanels(state); - controls.bpm.input.value = state.bpm; - break; - - case actions.ADD_PROCESSOR: - createSettingsViews(state); - renderLayout(); - break; - - case actions.DELETE_PROCESSOR: - deleteSettingsView(action.id); - showPanels(state); - selectSettingsView(state.selectedID); - renderLayout(); - break; - - case actions.SET_TRANSPORT: - controls.play.input.checked = state.transport === 'play'; - break; - - case actions.SET_TEMPO: - controls.bpm.input.value = state.bpm; - break; - - case actions.SELECT_PROCESSOR: - selectSettingsView(action.id); - // fallthrough intentional - case actions.TOGGLE_MIDI_LEARN_MODE: - case actions.TOGGLE_PANEL: - showPanels(state); - break; - } - }); - - // get panel header height from CSS. - var style = getComputedStyle(document.body); - panelHeaderHeight = parseInt(style.getPropertyValue('--header-height'), 10); - - my.addWindowResizeCallback(renderLayout); - renderLayout(); - }, - - /** - * Create settings controls view for a processor. - * @param {Object} processor MIDI processor to control with the settings. - */ - createSettingsViews = function(state) { - state.processors.allIds.forEach((id, i) => { - const processorData = state.processors.byId[id]; - let exists = false; - settingsViews.forEach(settingsView => { - if (settingsView.getID() === id) { - exists = true; - } - }); - if (!exists) { - fetch(`js/wh/processors/${processorData.type}/settings.html`) - .then( - response => response.text(), - error => console.log('An error occurred.', error) - ) - .then(html => { - settingsViews.splice(i, 0, createSettingsPanel({ - data: processorData, - store: store, - parentEl: editContentEl, - template: html, - isSelected: store.getState().selectedID === processorData.id - })); - }); - } - }); - }, - - /** - * Delete settings controls view for a processor. - * @param {String} id MIDI processor ID. - */ - deleteSettingsView = function(id) { - settingsViews = settingsViews.reduce((accumulator, view) => { - if (view.getID() === id) { - view.terminate(); - return accumulator; - } - return [...accumulator, view]; - }, []); - }, - - /** - * Show the settings controls view for a processor. - * @param {String} id MIDI processor ID. - */ - selectSettingsView = function(id) { - settingsViews.forEach(view => view.select(id)); - }, - - /** - * Set up a new project, create th esetting views. - * @param {Object} state App state object. - */ - setProject = function(state) { - var n = settingsViews.length; - while (--n >= 0) { - deleteSettingsView(settingsViews[n].getID()); - } - createSettingsViews(state); - }, - - /** - * Render the panels layout. - * @param {Boolean} leftColumn Render the left panel column. - * @param {Boolean} rightColumn Render the right panel column. - */ - renderLayout = function(leftColumn = true, rightColumn = true) { - if (leftColumn) { - renderColumnLayout(prefsEl, remoteEl, false); - } - if (rightColumn) { - renderColumnLayout(helpEl, editEl, true); - } - }, - - /** - * Render a column of the panels layout. - * @param {Object} topEl Bottom panel in the column. - * @param {Object} topEl Top panel in the column. - * @param {Boolean} isRightColumn True if the right column is being rendered. - */ - renderColumnLayout = function(topEl, btmEl, isRightColumn) { - const totalHeight = panelsEl.clientHeight, - columnWidth = document.querySelector('.panels__right').clientWidth, - topWidth = topEl.clientWidth, - btmWidth = btmEl.clientWidth, - isTopVisible = topEl.dataset.show == 'true', - isBtmVisible = btmEl.dataset.show == 'true', - topViewportEl = topEl.querySelector('.panel__viewport'), - btmViewportEl = btmEl.querySelector('.panel__viewport'); - - let topHeight, btmHeight, topContentHeight, btmContentHeight; - - // reset heights before measuring them - topViewportEl.style.height = 'auto'; - btmViewportEl.style.height = 'auto'; - - topHeight = topEl.clientHeight, - btmHeight = btmEl.clientHeight, - topContentHeight = topEl.querySelector('.panel__content').clientHeight, - btmContentHeight = btmEl.querySelector('.panel__content').clientHeight; - - if (isRightColumn && (topWidth + btmWidth < columnWidth)) { - if (topContentHeight + panelHeaderHeight > totalHeight) { - topViewportEl.style.height = totalHeight - panelHeaderHeight + 'px'; - } else { - topViewportEl.style.height = 'auto'; - } - if (btmContentHeight + panelHeaderHeight > totalHeight) { - btmViewportEl.style.height = totalHeight - panelHeaderHeight + 'px'; - } else { - btmViewportEl.style.height = 'auto'; - } - } else { - if (isTopVisible && isBtmVisible) { - let combinedHeight = topContentHeight + btmContentHeight + (panelHeaderHeight * 2); - if (combinedHeight > totalHeight) { - if (topContentHeight + panelHeaderHeight < totalHeight / 2) { - topViewportEl.style.height = prefsEl.topContentHeight + 'px'; - btmViewportEl.style.height = (totalHeight - topContentHeight - (panelHeaderHeight * 2)) + 'px'; - } else if (btmContentHeight + panelHeaderHeight < totalHeight / 2) { - topViewportEl.style.height = (totalHeight - btmContentHeight - (panelHeaderHeight * 2)) + 'px'; - btmViewportEl.style.height = remoteEl.topContentHeight + 'px'; - } else { - topViewportEl.style.height = ((totalHeight / 2) - panelHeaderHeight) + 'px'; - btmViewportEl.style.height = ((totalHeight / 2) - panelHeaderHeight) + 'px'; - } - } else { - topViewportEl.style.height = 'auto'; - btmViewportEl.style.height = 'auto'; - } - } else if (isTopVisible) { - if (topContentHeight + panelHeaderHeight > totalHeight) { - topViewportEl.style.height = totalHeight - panelHeaderHeight + 'px'; - } else { - topViewportEl.style.height = 'auto'; - } - } else if (isBtmVisible) { - if (btmContentHeight + panelHeaderHeight > totalHeight) { - btmViewportEl.style.height = totalHeight - panelHeaderHeight + 'px'; - } else { - btmViewportEl.style.height = 'auto'; - } - } - } - }, - - /** - * Set panels visibility. - * @param {Object} state App state. - */ - showPanels = function(state) { - helpEl.dataset.show = state.showHelpPanel; - controls.help.input.checked = state.showHelpPanel; - - prefsEl.dataset.show = state.showPreferencesPanel; - controls.prefs.input.checked = state.showPreferencesPanel; - - remoteEl.dataset.show = state.learnModeActive; - controls.remote.input.checked = state.learnModeActive; - - editEl.dataset.show = state.showSettingsPanel; - controls.edit.input.checked = state.showSettingsPanel; - - libraryEl.dataset.show = state.showLibraryPanel; - controls.library.input.checked = state.showLibraryPanel; - - controls.connections.input.checked = state.connectModeActive; - - renderLayout(); - }; - - my = my || {}; - - that = addWindowResize(specs, my); - - init(); - - return that; -} diff --git a/src/js/webgl/canvas3d.js b/src/js/webgl/canvas3d.js index a121334d..304be74c 100644 --- a/src/js/webgl/canvas3d.js +++ b/src/js/webgl/canvas3d.js @@ -509,479 +509,3 @@ function updateMouseRay(e) { // update the picking ray with the camera and mouse position raycaster.setFromCamera(mousePoint, camera); } - - -// export default function createCanvas3d(specs, my) { -// let that, -// store = specs.store, -// rootEl, -// canvasRect, -// renderer, -// camera, -// plane, -// mousePoint = new Vector2(), -// mousePointPrevious = new Vector2(), -// intersection = new Vector3(), -// raycaster = new Raycaster(), -// dragObject, -// dragObjectType, -// dragOffset = new Vector3(), -// allObjects = [], -// controllers = [], -// doubleClickCounter = 0, -// doubleClickDelay = 300, -// doubleClickTimer, - -// init = function() { - -// document.addEventListener(store.STATE_CHANGE, (e) => { -// switch (e.detail.action.type) { - -// case actions.SELECT_PROCESSOR: -// selectProcessorView(state); -// break; - -// case actions.ADD_PROCESSOR: -// createProcessorViews(state); -// break; - -// case actions.DELETE_PROCESSOR: -// deleteProcessorView(e.detail.action.id); -// selectProcessorView(state); -// break; - -// case actions.CREATE_PROJECT: -// setThemeOnWorld(); -// updateCamera(state); -// clearProcessorViews(); -// createProcessorViews(state); -// onWindowResize(); -// break; - -// case actions.RESCAN_TYPES: -// case actions.SET_THEME: -// setThemeOnWorld(); -// break; - -// case actions.SET_CAMERA_POSITION: -// updateCamera(state); -// break; - -// case actions.LIBRARY_DROP: -// onDrop(state); -// break; -// } -// }); - -// my.addWindowResizeCallback(onWindowResize); -// initWorld(); -// initDOMEvents(); -// onWindowResize(); -// draw(); -// }, - -// /** -// * Initialise DOM events for click, drag etcetera. -// */ -// initDOMEvents = function() { -// renderer.domElement.addEventListener('touchend', onClick); -// renderer.domElement.addEventListener('click', onClick); -// renderer.domElement.addEventListener('touchstart', onTouchStart); -// renderer.domElement.addEventListener('mousedown', onTouchStart); -// renderer.domElement.addEventListener('touchmove', dragMove); -// renderer.domElement.addEventListener('mousemove', dragMove); -// renderer.domElement.addEventListener('touchend', dragEnd); -// renderer.domElement.addEventListener('mouseup', dragEnd); - -// // prevent system doubleclick to interfere with the custom doubleclick -// renderer.domElement.addEventListener('dblclick', function(e) {e.preventDefault();}); -// }, - -// /** -// * Window resize event handler. -// */ -// onWindowResize = function() { -// canvasRect = renderer.domElement.getBoundingClientRect(); -// renderer.setSize(window.innerWidth, window.innerHeight - canvasRect.top); -// camera.aspect = window.innerWidth / (window.innerHeight - canvasRect.top); -// camera.updateProjectionMatrix(); -// canvasRect = renderer.domElement.getBoundingClientRect(); - -// // move camera further back when viewport height increases so objects stay the same size -// let scale = 0.15; -// let fieldOfView = camera.fov * (Math.PI / 180); // convert fov to radians -// let targetZ = canvasRect.height / (2 * Math.tan(fieldOfView / 2)); - -// setLineMaterialResolution(); - -// store.dispatch(store.getActions().setCameraPosition(camera.position.x, camera.position.y, targetZ * scale)); -// }, - -// /** -// * Drop of object dragged from library. -// * Create a new processor. -// */ -// onDrop = function(state) { -// const { type, x, y, } = state.libraryDropPosition; -// updateMouseRay({ clientX: x, clientY: y, }); -// if (raycaster.ray.intersectPlane(plane, intersection)) { -// store.dispatch(store.getActions().createProcessor({ -// type, -// positionX: intersection.x, -// positionY: intersection.y, -// positionZ: intersection.z, -// })); -// }; -// }, - -// /** -// * Separate click and doubleclick. -// * @see http://stackoverflow.com/questions/6330431/jquery-bind-double-click-and-single-click-separately -// */ -// onClick = function(e) { -// // separate click from doubleclick -// doubleClickCounter ++; -// if (doubleClickCounter == 1) { -// doubleClickTimer = setTimeout(function() { -// doubleClickCounter = 0; -// // implement single click behaviour here -// handleClick(e); -// }, doubleClickDelay); -// } else { -// clearTimeout(doubleClickTimer); -// doubleClickCounter = 0; -// // implement double click behaviour here -// } -// }, - -// /** -// * Select the object under the mouse. -// * Start dragging the object. -// */ -// onTouchStart = function(e) { -// // update picking ray -// updateMouseRay(e); -// mousePointPrevious = { ...mousePoint }; - -// // get intersected object3ds -// const intersects = raycaster.intersectObjects(allObjects, true); -// let outerObject = null; -// dragObjectType = 'background'; -// if (intersects.length) { - -// // test for processors -// let intersect = intersects.find(intersect => intersect.object.name === 'hitarea'); -// if (intersect) { -// // get topmost parent of closest object -// outerObject = getOuterParentObject(intersect.object); -// // select the touched processor -// store.dispatch(store.getActions().selectProcessor(outerObject.userData.id)); -// dragObjectType = 'processor'; -// } - -// // test for output connectors -// intersect = intersects.find(intersect => intersect.object.name === 'output'); -// if (intersect && my.isConnectMode) { -// // get outer parent of closest object -// outerObject = getOuterParentObject(intersect.object); -// my.dragStartConnection( -// outerObject.userData.id, -// intersect.object.userData.id, -// outerObject.clone().position.add(intersect.object.position)); -// dragObjectType = 'connection'; -// } -// } - -// if (dragObjectType === 'background') { -// outerObject = camera; -// } - -// dragStart(outerObject, mousePoint); -// }, - -// /** -// * Initialise object dragging. -// * @param {object} object3d The Object3D to be dragged. -// */ -// dragStart = function(object3d, mousePoint) { -// dragObject = object3d; -// // update the picking ray with the camera and mouse position -// raycaster.setFromCamera(mousePoint, camera); -// // if ray intersects plane, store point in vector 'intersection' -// if (raycaster.ray.intersectPlane(plane, intersection)) { -// switch (dragObjectType) { - -// case 'processor': -// // offset is the intersection point minus object position, -// // so distance from object to mouse -// dragOffset.copy(intersection).sub(dragObject.position); -// break; - -// case 'connection': -// break; - -// case 'background': -// dragOffset.copy(intersection).sub(dragObject.position); -// break; -// } -// rootEl.style.cursor = 'move'; -// } -// }, - -// /** -// * Drag a 3D object. -// * @param {Object} e Event. -// */ -// dragMove = function(e) { -// e.preventDefault(); - -// // update picking ray. -// updateMouseRay(e); -// switch (dragObjectType) { -// case 'processor': -// if (raycaster.ray.intersectPlane(plane, intersection)) { -// // set position of dragObject to the mouse intersection minus the offset -// const position = intersection.sub(dragOffset); -// store.dispatch(store.getActions().dragSelectedProcessor(intersection.x, intersection.y, position.z)); -// } -// break; - -// case 'background': -// const x = (mousePointPrevious.x - mousePoint.x) * 50; -// const y = (mousePointPrevious.y - mousePoint.y) * 50; -// store.dispatch(store.getActions().setCameraPosition(x, y, 0, true)); -// break; - -// case 'connection': -// if (raycaster.ray.intersectPlane(plane, intersection)) { -// my.dragMoveConnection(intersection); -// } -// break; - -// // when not dragging -// default: -// var intersects = raycaster.intersectObjects(allObjects, true); -// if (intersects.length > 0) { -// const intersectHitarea = intersects.find(intersect => intersect.object.name === 'hitarea'); -// if (intersectHitarea) { -// rootEl.style.cursor = 'pointer'; -// } else { -// rootEl.style.cursor = 'auto'; -// } -// } -// } -// mousePointPrevious = { ...mousePoint }; -// }, - -// /** -// * Dragging 3D object ended. -// * @param {Object} e Event. -// */ -// dragEnd = function(e) { -// e.preventDefault(); -// updateMouseRay(e); - -// switch (dragObjectType) { -// case 'connection': -// my.dragEndConnection(); - -// // test for input connectors -// const intersects = raycaster.intersectObjects(allObjects, true); -// const intersect = intersects.find(intersect => intersect.object.name === 'input'); -// if (intersect && my.isConnectMode) { -// const outerObject = getOuterParentObject(intersect.object); -// my.createConnection( -// outerObject.userData.id, -// intersect.object.userData.id); -// } -// break; -// } -// dragObject = null; -// dragObjectType = null; -// rootEl.style.cursor = 'auto'; -// }, - -// /** -// * Handle single mouse click. -// */ -// handleClick = function(e) { -// if (my.cablesGroup) { -// updateMouseRay(e); - -// // look for click on connection cable delete button -// const cableIntersects = raycaster.intersectObjects(my.cablesGroup.children, true); -// const deleteIntersect = cableIntersects.find(intersect => intersect.object.name === 'delete'); -// if (deleteIntersect) { -// store.dispatch(store.getActions().disconnectProcessors(deleteIntersect.object.userData.connectionId)); -// } -// } -// }, - -// /** -// * Set up the 3D world. -// */ -// initWorld = function() { - -// renderer = new WebGLRenderer({antialias: true}); -// renderer.setClearColor(new Color( getTheme().colorBackground || '#cccccc' )); - -// rootEl = document.querySelector('#canvas-container'); -// rootEl.appendChild(renderer.domElement); - -// my.scene = new Scene(); - -// camera = new PerspectiveCamera(45, 1, 1, 500); -// my.scene.add(camera); - -// plane = new Plane(); -// plane.name = 'plane'; -// plane.setFromNormalAndCoplanarPoint( -// camera.getWorldDirection(plane.normal), -// new Vector3(0,0,0)); -// }, - -// /** -// * Update the camera position to what's stored in the state. -// */ -// updateCamera = function(state) { -// camera.position.set(state.camera.x, state.camera.y, state.camera.z); -// }, - -// setThemeOnWorld = function() { -// renderer.setClearColor(new Color( getTheme().colorBackground )); -// }, - -// /** -// * Set a raycaster's ray to point from the camera to the mouse postion. -// * @param {event} mouseEvent Event rom which to get the mouse coordinates. -// */ -// updateMouseRay = function(e) { -// const x = isNaN(e.clientX) ? e.changedTouches[0].clientX : e.clientX; -// const y = isNaN(e.clientY) ? e.changedTouches[0].clientY : e.clientY; - -// // update mouse vector with mouse coordinated translated to viewport -// mousePoint.x = ((x - canvasRect.left) / canvasRect.width ) * 2 - 1; -// mousePoint.y = - ((y - canvasRect.top) / canvasRect.height ) * 2 + 1; - -// // update the picking ray with the camera and mouse position -// raycaster.setFromCamera(mousePoint, camera); -// }, - -// /** -// * Recursive function to get top level object of a group. -// * @param {object} object3d An Three.js Object3D. -// */ -// getOuterParentObject = function(object3d) { -// if (object3d.object && object3d.object.parent && object3d.object.parent.type !== 'Scene') { -// return getOuterParentObject(object3d.object.parent); -// } else if (object3d.parent && object3d.parent.type !== 'Scene') { -// return getOuterParentObject(object3d.parent); -// } -// if (object3d.object) { -// return object3d.object; -// } -// return object3d; -// }, - -// /** -// * Create canvas 2D object if it exists for the type. -// * @param {Array} data Array of current processors' state. -// */ -// createProcessorViews = async (state) => { -// const isConnectMode = state.connectModeActive; -// // state.processors.allIds.forEach((id, i) => { -// for (let id of state.processors.allIds) { -// const processorData = state.processors.byId[id]; -// const { inputs, outputs, positionX, positionY, positionZ, type } = processorData; -// const isExists = allObjects.find(obj3d => obj3d.userData.id === id); -// if (!isExists) { - -// // create the processor 3d object -// const object3dModule = await import(`../processors/${type}/object3d.js`); -// const object3d = object3dModule.createObject3d(id, inputs, outputs); -// object3d.position.set(positionX, positionY, positionZ); -// allObjects.push(object3d); -// my.scene.add(object3d); - -// // create controller for the object -// const controllerModule = await import(`../processors/${type}/object3dController.js`); -// const controller = controllerModule.createObject3dController({ object3d, processorData, store, isConnectMode, }); -// controller.updateSelectCircle(store.getState().selectedID); -// controllers.push(controller); -// } -// }; -// }, - -// /** -// * Show the selected state of the processors. -// */ -// selectProcessorView = function(state) { -// controllers.forEach(controller => { -// controller.updateSelectCircle(state.selectedID); -// }); -// }, - -// /** -// * Remove all processor objects from the scene -// * and delete all their controllers. -// */ -// clearProcessorViews = function() { -// // remove all processor 3D objects -// allObjects = allObjects.reduce((accumulator, object3D) => { -// my.scene.remove(object3D); -// return accumulator; -// }, []); - -// // remove all controllers -// controllers = controllers.reduce((accumulator, controller) => { -// controller.terminate(); -// return accumulator; -// }, []); -// }, - -// /** -// * Delete canvas 2D object when the processor is deleted. -// * @param {Object} processor MIDI processor for which the 3D object will be a view. -// */ -// deleteProcessorView = function(id) { -// // remove 3D object from allObjects -// allObjects = allObjects.reduce((accumulator, object3D) => { -// if (object3D.userData.id === id) { -// // remove 3D object from scene -// my.scene.remove(object3D); -// return accumulator; -// } -// return [...accumulator, object3D]; -// }, []); - -// // remove controller -// controllers = controllers.reduce((accumulator, controller) => { -// if (controller.getID() === id) { -// controller.terminate(); -// return accumulator; -// } -// return [...accumulator, controller]; -// }, []); -// }, - -// /** -// * Update any tween animations that are going on and redraw the canvases if needed. -// * @param {Number} position Transport playback position in ticks. -// * @param {Array} processorEvents Array of processor generated events to displayin the view. -// */ -// draw = function(position, processorEvents) { -// controllers.forEach(controller => controller.draw(position, processorEvents)); -// renderer.render(my.scene, camera); -// }; - -// my = my || {}; -// my.scene = null; - -// that = addWindowResize(specs, my); -// that = addConnections3d(specs, my); - -// init(); - -// that.draw = draw; -// return that; -// } diff --git a/src/js/webgl/connections3d.js b/src/js/webgl/connections3d.js index 2e4001f1..a511b60d 100644 --- a/src/js/webgl/connections3d.js +++ b/src/js/webgl/connections3d.js @@ -291,293 +291,3 @@ function setThemeColorRecursively(object3d, colorLow, colorHigh) { setThemeColorRecursively(childObject3d, colorLow, colorHigh); }); } - - - - -// export default function addConnections3d(specs, my) { -// let that, -// store = specs.store, -// state = { -// sourceProcessorID: null, -// sourceConnectorID: null, -// sourceConnectorPosition: null, -// }, -// lineMaterial, -// currentCable, -// currentCableDragHandle, -// cablesGroup, -// deleteButtonRadius = 2.0, -// deleteCrossRadius = 0.8, -// dragHandleRadius = 1.5, - -// init = function() { -// currentCableDragHandle = createCircleOutline(dragHandleRadius, getTheme().colorHigh); -// currentCableDragHandle.name = 'dragHandle'; - -// document.addEventListener(store.STATE_CHANGE, (e) => { -// switch (action.type) { - -// case actions.TOGGLE_CONNECT_MODE: -// toggleConnectMode(state.connectModeActive); -// break; - -// case actions.DELETE_PROCESSOR: -// case actions.CONNECT_PROCESSORS: -// case actions.DISCONNECT_PROCESSORS: -// updateCables(state); -// drawCables(state); -// break; - -// case actions.DRAG_SELECTED_PROCESSOR: -// case actions.DRAG_ALL_PROCESSORS: -// drawCables(state); -// break; - -// case actions.CREATE_PROJECT: -// updateTheme(); -// updateCables(state); -// drawCables(state); -// toggleConnectMode(state.connectModeActive); -// break; - -// case actions.SET_THEME: -// updateTheme(); -// toggleConnectMode(state.connectModeActive); -// break; -// } -// }); -// }, - -// /** -// * Start dragging a connection cable. -// * @param {String} sourceProcessorID -// * @param {String} sourceConnectorID -// * @param {Vector3} sourceConnectorPosition -// */ -// dragStartConnection = function(sourceProcessorID, sourceConnectorID, sourceConnectorPosition) { -// state = { ...state, sourceProcessorID, sourceConnectorID, sourceConnectorPosition, }; -// currentCable = createShape(); -// currentCable.name = 'currentCable'; -// cablesGroup.add(currentCable); - -// currentCableDragHandle.position.copy(sourceConnectorPosition); -// cablesGroup.add(currentCableDragHandle); -// }, - -// /** -// * Drag a connection cable. -// * @param {Vector3} position3d -// */ -// dragMoveConnection = function(position3d) { -// drawCable( -// currentCable.name, -// new Vector2(state.sourceConnectorPosition.x, state.sourceConnectorPosition.y), -// new Vector2(position3d.x, position3d.y)); -// currentCableDragHandle.position.copy(position3d); -// }, - -// /** -// * Drag connection cable ended. -// */ -// dragEndConnection = function() { -// currentCable.geometry.dispose(); -// cablesGroup.remove(currentCable); -// cablesGroup.remove(currentCableDragHandle); -// }, - -// /** -// * Create connection between two processors. -// * @param {String} destinationProcessorID Processor ID. -// * @param {String} destinationConnectorID Connector ID. -// */ -// createConnection = function(destinationProcessorID, destinationConnectorID) { -// store.dispatch(store.getActions().connectProcessors({ -// sourceProcessorID: state.sourceProcessorID, -// sourceConnectorID: state.sourceConnectorID, -// destinationProcessorID: destinationProcessorID, -// destinationConnectorID: destinationConnectorID, -// })); -// state.sourceProcessorID = null; -// state.sourceConnectorID = null; -// }, - -// /** -// * Create and delete cables acctording to the state. -// * @param {Object} state Application state. -// */ -// updateCables = function(state) { -// if (!cablesGroup) { -// cablesGroup = new Group(); -// my.cablesGroup = cablesGroup; -// my.scene.add(cablesGroup); -// } - -// // delete all removed cables -// let count = cablesGroup.children.length; -// while (--count >= 0) { -// const cable = cablesGroup.children[count]; -// if (state.connections.allIds.indexOf(cable.name) === -1) { -// cablesGroup.remove(cable); -// } -// } - -// // create all new cables -// state.connections.allIds.forEach(connectionId => { -// if (!cablesGroup.getObjectByName(connectionId)) { -// createCable(connectionId); -// } -// }); -// }, - -// /** -// * Draw all cables acctording to the state. -// * @param {String} connectionId Connection ID. -// * @return {Object} Cable object3d. -// */ -// createCable = function(connectionId) { -// const { colorLow } = getTheme(); - -// const cable = createShape(); -// cable.name = connectionId; -// cablesGroup.add(cable); - -// const deleteBtn = createCircleFilled(deleteButtonRadius, colorLow, 0); -// deleteBtn.name = 'delete'; -// deleteBtn.userData.connectionId = connectionId; -// deleteBtn.visible = my.isConnectMode; -// cable.add(deleteBtn); - -// const deleteBtnBorder = createCircleOutline(deleteButtonRadius, colorLow); -// deleteBtnBorder.name = 'deleteBorder'; -// deleteBtn.add(deleteBtnBorder); - -// const points1 = [ -// new Vector2(-deleteCrossRadius, -deleteCrossRadius), -// new Vector2(deleteCrossRadius, deleteCrossRadius), -// ]; -// const line1 = createShape(points1, colorLow); -// line1.name = 'deleteCross1'; -// deleteBtn.add(line1); - -// const points2 = [ -// new Vector2(-deleteCrossRadius, deleteCrossRadius), -// new Vector2(deleteCrossRadius, -deleteCrossRadius), -// ]; -// const line2 = createShape(points2, colorLow); -// line2.name = 'deleteCross2'; -// deleteBtn.add(line2); - -// return cable; -// }, - -// /** -// * Draw all cables acctording to the state. -// * @param {Object} state Application state. -// */ -// drawCables = function(state) { -// state.connections.allIds.forEach(connectionID => { -// const connection = state.connections.byId[connectionID]; -// const sourceProcessor = state.processors.byId[connection.sourceProcessorID]; -// const destinationProcessor = state.processors.byId[connection.destinationProcessorID]; - -// if (sourceProcessor && destinationProcessor) { -// const sourceConnector = sourceProcessor.outputs.byId[connection.sourceConnectorID]; -// const destinationConnector = destinationProcessor.inputs.byId[connection.destinationConnectorID]; - -// const cable = cablesGroup.getObjectByName(connectionID); -// drawCable( -// connectionID, -// new Vector2( -// sourceProcessor.positionX + sourceConnector.x, -// sourceProcessor.positionY + sourceConnector.y,), -// new Vector2( -// destinationProcessor.positionX + destinationConnector.x, -// destinationProcessor.positionY + destinationConnector.y,)); -// } -// }); -// }, - -// /** -// * Enter or leave application connect mode. -// * @param {Vector3} sourcePosition Cable start position. -// * @param {Vector3} destinationPosition Cable end position. -// */ -// drawCable = function(connectionID, sourcePosition, destinationPosition) { -// const cable = cablesGroup.getObjectByName(connectionID); -// if (cable) { -// const distance = sourcePosition.distanceTo(destinationPosition); -// const curveStrength = Math.min(distance / 2, 30); -// const curve = new CubicBezierCurve( -// sourcePosition.clone(), -// sourcePosition.clone().sub(new Vector2(0, curveStrength)), -// destinationPosition.clone().add(new Vector2(0, curveStrength)), -// destinationPosition.clone() -// ); -// const points = curve.getPoints(50); - -// redrawShape(cable, points, getTheme().colorLow); - -// const deleteBtn = cable.getObjectByName('delete'); -// if (deleteBtn) { - -// // get mid point on cable -// const position = points[Math.floor(points.length / 2)]; -// deleteBtn.position.set(position.x, position.y, 0); -// } -// } -// }, - -// /** -// * Enter or leave application connect mode. -// * @param {Boolean} isEnabled True to enable connect mode. -// */ -// toggleConnectMode = function(isEnabled) { -// my.isConnectMode = isEnabled; - -// // toggle cable delete buttons -// cablesGroup.children.forEach(cable => { -// const deleteBtn = cable.getObjectByName('delete'); -// deleteBtn.visible = my.isConnectMode; -// }); -// }, - -// /** -// * Update theme colors. -// */ -// updateTheme = function() { -// if (cablesGroup) { -// const { colorLow, colorHigh } = getTheme(); -// setThemeColorRecursively(cablesGroup, colorLow, colorHigh); -// } -// }, - -// /** -// * Loop through all the object3d's children to set the color. -// * @param {Object3d} object3d An Object3d of which to change the color. -// * @param {String} colorLow Hex color string of the low contrast color. -// * @param {String} colorHigh Hex color string of the high contrast color. -// */ -// setThemeColorRecursively = function(object3d, colorLow, colorHigh) { -// if (object3d.material && object3d.material.color) { -// object3d.material.color.set(colorLow); -// } -// object3d.children.forEach(childObject3d => { -// setThemeColorRecursively(childObject3d, colorLow, colorHigh); -// }); -// }; - -// my = my || {}; -// my.isConnectMode = false, -// my.cablesGroup = cablesGroup; -// my.dragStartConnection = dragStartConnection; -// my.dragMoveConnection = dragMoveConnection; -// my.dragEndConnection = dragEndConnection; -// my.createConnection = createConnection; - -// that = specs.that || {}; - -// init(); - -// return that; -// } diff --git a/src/js/webgl/text3d.js b/src/js/webgl/text3d.js index 33444f59..7e61e4ed 100644 --- a/src/js/webgl/text3d.js +++ b/src/js/webgl/text3d.js @@ -3,7 +3,6 @@ import { createText } from './draw3dHelper.js'; const { Group, - Path, Vector2, } = THREE; diff --git a/yarn.lock b/yarn.lock index 798b5103..298e93d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -742,10 +742,10 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -nw@0.41.2: - version "0.41.2" - resolved "https://registry.yarnpkg.com/nw/-/nw-0.41.2.tgz#23db65db96e4eeb9a9f7ad23e0eff69911530745" - integrity sha512-jFTX15htAgvhZM9scgiBvjQNAbIA5diaAjA9Qpqmfjkdj4b6L8HLdTXqB5/QofzqgeIIHYLmDHcAlw2EnJPI7Q== +nw@0.41.2-sdk: + version "0.41.2-sdk" + resolved "https://registry.yarnpkg.com/nw/-/nw-0.41.2-sdk.tgz#6113296c71051ac0b05878d6ed05479d2a559d9a" + integrity sha512-Ky/b6AXMaDtm1id4GwDwkCSjlC5HnfPiyApAUh8Cod84SMhAowU2QOkI6xPR4rGtDyLVraDXA4vmGR5CLd0aqA== dependencies: chalk "~1.1.3" decompress "^4.2.0" From e11baab9c6fe3183b309aa5614c617858bf4c638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 15:38:35 +0200 Subject: [PATCH 031/131] Small fixes. --- src/js/midi/network.js | 2 +- src/js/processors/euclidfx/object3dController.js | 5 ++--- src/js/state/reducers.js | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/js/midi/network.js b/src/js/midi/network.js index 15697570..17b0811f 100644 --- a/src/js/midi/network.js +++ b/src/js/midi/network.js @@ -36,7 +36,7 @@ function connectProcessors(state) { const connection = state.connections.byId[connectionID]; const sourceProcessor = processors.find(processor => processor.getID() === connection.sourceProcessorID); const destinationProcessor = processors.find(processor => processor.getID() === connection.destinationProcessorID); - if (!destinationProcessor) { + if (!sourceProcessor.getDestinations().includes(destinationProcessor)) { sourceProcessor.connect(destinationProcessor); } }); diff --git a/src/js/processors/euclidfx/object3dController.js b/src/js/processors/euclidfx/object3dController.js index 823647f9..821417d8 100644 --- a/src/js/processors/euclidfx/object3dController.js +++ b/src/js/processors/euclidfx/object3dController.js @@ -211,9 +211,8 @@ export function createObject3dController(data, that = {}, my = {}) { * @param {Number} position Position within pattern in ticks. */ showPlaybackPosition = function(position) { - pointerRotationPrevious = pointerRotation; - pointerRotation = TWO_PI * (-position % duration / duration); - necklace3d.rotation.z = pointerRotation; + pointerRotation = TWO_PI * (-position % duration / duration); + necklace3d.rotation.z = pointerRotation; }, /** diff --git a/src/js/state/reducers.js b/src/js/state/reducers.js index 23556aa0..3e127d67 100644 --- a/src/js/state/reducers.js +++ b/src/js/state/reducers.js @@ -32,7 +32,7 @@ const initialState = { allIds: [], byId: {}, }, - selectedID: null, + selectedId: null, showHelpPanel: false, showLibraryPanel: true, showPreferencesPanel: false, @@ -138,7 +138,7 @@ export default function reduce(state = initialState, action, actions = {}) { return newState; case actions.SELECT_PROCESSOR: - return { ...state, selectedID: action.id }; + return { ...state, selectedId: action.id }; case actions.DRAG_SELECTED_PROCESSOR: return { From 3ad7718a8822073be7fa1a82b005afbc43024721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 17:53:36 +0200 Subject: [PATCH 032/131] Presets panel, button and module added. --- src/css/presets.css | 29 ++ src/css/style.css | 1 + src/fonts/icomoon/demo.html | 74 ++--- src/fonts/icomoon/fonts/icomoon.eot | Bin 4504 -> 4720 bytes src/fonts/icomoon/fonts/icomoon.svg | 1 + src/fonts/icomoon/fonts/icomoon.ttf | Bin 4340 -> 4556 bytes src/fonts/icomoon/fonts/icomoon.woff | Bin 4416 -> 4632 bytes src/fonts/icomoon/selection.json | 468 +-------------------------- src/fonts/icomoon/style.css | 14 +- src/index.html | 17 + src/js/main.js | 2 + src/js/view/controls.js | 5 + src/js/view/panels.js | 2 + src/js/view/presets.js | 40 +++ 14 files changed, 137 insertions(+), 516 deletions(-) create mode 100644 src/css/presets.css create mode 100644 src/js/view/presets.js diff --git a/src/css/presets.css b/src/css/presets.css new file mode 100644 index 00000000..b7c51243 --- /dev/null +++ b/src/css/presets.css @@ -0,0 +1,29 @@ +/************************ + * presets + ************************/ + + .presets { + background-color: var(--panel-bg-color); + position: relative; + width: 340px; +} +.presets .panel__viewport { + background-color: var(--panel-bg-color); +} +.presets__list { + display: flex; + flex-wrap: wrap-reverse; + list-style: none; +} +.presets__item { + cursor: pointer; + flex-basis: 25%; + padding: 20px; + text-align: center; +} +.presets__item:hover { + background-color: var(--border-color); +} +.presets__item-label { + font-size: 1 rem; +} diff --git a/src/css/style.css b/src/css/style.css index ed17a0a2..b76b599f 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -2,6 +2,7 @@ @import 'library.css'; @import 'preferences.css'; @import 'rangeslider.css'; +@import 'presets.css'; @import 'remote.css'; @import 'settings.css'; diff --git a/src/fonts/icomoon/demo.html b/src/fonts/icomoon/demo.html index 383c4999..c53defaf 100755 --- a/src/fonts/icomoon/demo.html +++ b/src/fonts/icomoon/demo.html @@ -9,15 +9,27 @@
-

Font Name: icomoon (Glyphs: 14)

+

Font Name: icomoon (Glyphs: 15)

Grid Size: 16

- - - + + icon-camera +
+
+ + +
+
+ liga: + +
+
+
+
+ icon-cancel
@@ -31,9 +43,7 @@

Grid Size: 16

- - - + icon-ok
@@ -47,9 +57,7 @@

Grid Size: 16

- - - + icon-midi
@@ -63,9 +71,7 @@

Grid Size: 16

- - - + icon-cog
@@ -79,9 +85,7 @@

Grid Size: 16

- - - + icon-help
@@ -95,9 +99,7 @@

Grid Size: 16

- - - + icon-new
@@ -111,9 +113,7 @@

Grid Size: 16

- - - + icon-save
@@ -127,9 +127,7 @@

Grid Size: 16

- - - + icon-play
@@ -143,9 +141,7 @@

Grid Size: 16

- - - + icon-pause
@@ -159,9 +155,7 @@

Grid Size: 16

- - - + icon-stop
@@ -175,9 +169,7 @@

Grid Size: 16

- - - + icon-open
@@ -191,9 +183,7 @@

Grid Size: 16

- - - + icon-wrench
@@ -207,9 +197,7 @@

Grid Size: 16

- - - + icon-connections
@@ -223,9 +211,7 @@

Grid Size: 16

- - - + icon-list
@@ -249,7 +235,7 @@

Font Test Drive

-
  +
 
diff --git a/src/fonts/icomoon/fonts/icomoon.eot b/src/fonts/icomoon/fonts/icomoon.eot index 06e018790235008c544c2c966141a0a83dd973cb..3e3f9b90cbc6be7afe8ea0e32ee9d4b1775b1ecb 100755 GIT binary patch delta 536 zcmbQC{6U4SK!}0ijNn8zGZy*Rzgj0h@X*giQUH zcG39^42%mH7~br_BV`4fPA zpPc;UM6njhUqJpHAYUUlv7&%MiBW}tL4XCwSIA4uO`Yk*`5VXwiZQko6 z1lNG%8JL+FCr?gc>}KSg{DD!MmrsC=?>FB|J|mza#>tI>qML1*c-ZQJI{u$z@nmjb z5MVH5Z~~fSBrYZ@!p_FdrmkjgW^N{~3KU=$1d1ptDygfP3YwUkSu=uUj1_@0@{Goe zMj)QL8c2`07!We4IvW}o7&^Nc8Zt8K>$@>dvenjR)Y7v1bK6E62y7Z;xn*VL7**wD zgJpPR8~){GdV4c6`ue74`1&z3d${K@F&G%Qx)|s)G8!5=IU5-P7T0WlN<|_j?D7Y9HZk}#Q zN6`NpSUe}|@yiQ)GBARr5rpvM1b$OyPX>m`Q}_*Jm~&X(uy(PrvDL7h0xK0}RAS%* Pt7Za*qVVR={7V@D7$A4H delta 320 zcmeyMGDDedh9CpO7lDavW-NbQx7km0C>OlPz`(Evh!c`?6AR8npR$>FLZ*Jfm&gPL z2F3*p3_J%iQWH}Iex^=jVBiCaF_>il1=v?J9|rPg0Qo8zxg`}Aj~R{w`5<#Va`KZC z#U}_CFfj150QqXUi4_G5ii|2iz7CMDke8U7I@62uH<0fGw4<#czqo{f87RXba0MjK zz|73pJ~@T4n~`Jk2S#m9UIA9VUwkk43^%JVsj_XB + diff --git a/src/fonts/icomoon/fonts/icomoon.ttf b/src/fonts/icomoon/fonts/icomoon.ttf index 192e56bc49b08a34ba4a6878770e17cd594af6e3..cf034184644695055082f07e26680f901fcb9642 100755 GIT binary patch delta 548 zcmeyOct*LNfsuiMft#U$ftkU;KUm+0Ka2MjP-G7fCnV=47MzJa<-oweCHRu5G7|0Ihi za|6)9h73+X{YK(qq9W{U>}=|4=4R$*;;KLac0r(svZ9i@nyH|PxtTR1NXA$ZC?n5k z%xDDSsjGqXh>HOsld7|!fq|j3i=iPSqrScy<0M;cZAL9EyFa&Ww1L2;L6%!qR*q3s zPBvJEN4DW#UZ%Gf>l5X;mLdWOqo3y7$$$;m!X1OBn%4pLOd1 delta 318 zcmX@3{6(>zfsuiMft#U$ftkU;KUm+0Ka2MsP-G7fCnV=47MzJbWy8S0C zx1_@2F~e~nA7s8qPJXguqWA=1prQOMKmoPf#EJq2MMf1MUkAun$V<#ko$1B-8_4$o zTGCdKUt9uoC=du-0m(BkGq+DX(9Os(*@sb^lUIP1?-$<-KEurojH+y#D>)9cDcNsx z{Tt73^Ob=cWB~)iwKD>z5cK~B7U#)v{PM!i;9vl&fD%HJC-9q2KFO~y#L~p7z&eLb Rh%Esq1qv;p&0GS97yWNu&(U@&BG0vc!}E+#6%&c@EBu4Zm#ZYHh@6krzw ziYO~8sjHa^nwXneGlFD{6@fDHjK+*cAfCD!NRPM}5HhJc8yXlGI=dJeGBWDxyD?6( z)z)Uz(z5$=+eRA*Y#L;_Wo6|URpn%ZWq4#8{^ezQdowcn`le_2`Y|(mxaTo37#O&^ z80a%H8X7q{8yPXZ)7G~A#{kjI!~itTS4LDsL{!H2Z?2C|My8K16O*S$ZjOfs3j@RE z_v}a6lv;nimXGJR`O3fz6as>qr(4ny^#2AH&&k*L+~3WOfsp|S1RWT-LA1b^$&5nc6SYO^?~;R%BGk zOUz9Lip>C8-Uh-my*PgtR2%E-gE`6$O>HYNLQu7BhCZN4&avjFumFkCw$a0)^HZ(wnryoz65*clRN yU?mVjX!0F?)5)R&`Z6p{tO~4i*o4>;fHI6gEX1h9zz0^vH2ET<@aAHHrHlamaZlm^ diff --git a/src/fonts/icomoon/selection.json b/src/fonts/icomoon/selection.json index fffd2bc2..38f29459 100755 --- a/src/fonts/icomoon/selection.json +++ b/src/fonts/icomoon/selection.json @@ -1,467 +1 @@ -{ - "IcoMoonType": "selection", - "icons": [ - { - "icon": { - "paths": [ - "M741.376 755.712q0 22.528-15.36 38.912l-77.824 77.824q-16.384 15.36-38.912 15.36t-38.912-15.36l-167.936-168.96-167.936 168.96q-16.384 15.36-38.912 15.36t-38.912-15.36l-77.824-77.824q-16.384-16.384-16.384-38.912t16.384-38.912l167.936-167.936-167.936-167.936q-16.384-16.384-16.384-38.912t16.384-38.912l77.824-77.824q16.384-16.384 38.912-16.384t38.912 16.384l167.936 167.936 167.936-167.936q16.384-16.384 38.912-16.384t38.912 16.384l77.824 77.824q15.36 15.36 15.36 38.912t-15.36 38.912l-167.936 167.936 167.936 167.936q15.36 15.36 15.36 38.912z" - ], - "attrs": [ - {} - ], - "width": 804, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "cancel" - ], - "defaultCode": 59393, - "grid": 16 - }, - "attrs": [ - {} - ], - "properties": { - "order": 27, - "id": 20, - "name": "cancel", - "ligatures": "", - "prevSize": 32, - "code": 59399 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 0 - }, - { - "icon": { - "paths": [ - "M955.392 323.584q0 22.528-16.384 38.912l-491.52 491.52q-16.384 15.36-38.912 15.36t-38.912-15.36l-284.672-284.672q-15.36-16.384-15.36-38.912t15.36-38.912l77.824-77.824q16.384-16.384 38.912-16.384t38.912 16.384l167.936 168.96 374.784-375.808q16.384-16.384 38.912-16.384t38.912 16.384l77.824 77.824q16.384 15.36 16.384 38.912z" - ], - "attrs": [ - {} - ], - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "ok" - ], - "defaultCode": 59392, - "grid": 16 - }, - "attrs": [ - {} - ], - "properties": { - "order": 28, - "id": 19, - "name": "ok", - "ligatures": "", - "prevSize": 32, - "code": 59398 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 1 - }, - { - "icon": { - "paths": [ - "M512 1024c-282.77 0-512-229.23-512-512s229.23-512 512-512c282.77 0 512 229.23 512 512-0.325 282.639-229.361 511.675-511.969 512zM512 47.584c-256.49 0-464.416 207.926-464.416 464.416s207.926 464.416 464.416 464.416c256.49 0 464.416-207.926 464.416-464.416-0.298-256.371-208.047-464.119-464.388-464.416z", - "M462.132 953.1l-26.694-4.758c-209.349-37.86-366.067-218.704-366.067-436.152 0-244.585 198.276-442.861 442.861-442.861s442.861 198.276 442.861 442.861c0 214.517-152.522 393.41-355.037 434.153l-29.48 5.662-1.665-27.123c-1.736-27.787-24.697-49.666-52.768-49.666-28.423 0-51.607 22.43-52.816 50.556zM512 116.628c-218.306 0.104-395.237 177.1-395.237 395.42 0 185.886 128.266 341.814 301.119 384.106 16.258-39.97 53.869-68.663 98.182-68.663 43.642 0 80.783 27.831 94.644 66.714 172.112-44.776 296.761-198.909 296.761-382.156 0-218.384-177.036-395.42-395.42-395.42-0.017 0-0.034 0-0.052 0z", - "M274.986 600.030c-49.931 0-90.409-40.477-90.409-90.409s40.477-90.409 90.409-90.409c49.931 0 90.409 40.477 90.409 90.409s-40.477 90.409-90.409 90.409zM274.986 467.176c-23.652 0-42.825 19.174-42.825 42.825s19.174 42.825 42.825 42.825c23.652 0 42.825-19.174 42.825-42.825s-19.174-42.825-42.825-42.825z", - "M343.554 428.681c-49.932 0-90.409-40.477-90.409-90.409s40.477-90.409 90.409-90.409c49.931 0 90.409 40.477 90.409 90.409 0 25.025-10.167 47.675-26.597 64.045l-0.003 0.003c-16.277 16.287-38.768 26.362-63.613 26.362-0.069 0-0.138-0-0.207-0zM343.554 295.875c-0.011-0-0.024-0-0.038-0-23.652 0-42.825 19.174-42.825 42.825s19.174 42.825 42.825 42.825c23.652 0 42.825-19.174 42.825-42.825 0-11.864-4.824-22.601-12.618-30.357-7.693-7.704-18.325-12.469-30.070-12.469-0.035 0-0.070 0-0.106 0z", - "M513.19 356.068v0c-49.931 0-90.409-40.477-90.409-90.409s40.477-90.409 90.409-90.409c49.931 0 90.409 40.477 90.409 90.409s-40.477 90.409-90.409 90.409zM513.19 223.215c-23.652 0-42.825 19.174-42.825 42.825s19.174 42.825 42.825 42.825c23.652 0 42.825-19.174 42.825-42.825s-19.174-42.825-42.825-42.825z", - "M684.538 424.589c-0.052 0-0.115 0-0.177 0-24.851 0-47.349-10.074-63.633-26.361l-0-0c-16.432-16.373-26.599-39.023-26.599-64.048 0-49.931 40.477-90.409 90.409-90.409s90.409 40.477 90.409 90.409c0 49.931-40.477 90.409-90.409 90.409zM654.37 364.586c7.763 7.832 18.525 12.681 30.419 12.681 23.652 0 42.825-19.174 42.825-42.825s-19.174-42.825-42.825-42.825c-11.894 0-22.656 4.849-30.416 12.678-7.671 7.739-12.409 18.39-12.409 30.147s4.738 22.408 12.409 30.147z", - "M757.151 594.272c-49.931 0-90.409-40.477-90.409-90.409v0c0-49.931 40.477-90.409 90.409-90.409s90.409 40.477 90.409 90.409c0 49.931-40.477 90.409-90.409 90.409zM714.326 503.863c0 23.652 19.174 42.825 42.825 42.825v0c23.652 0 42.825-19.174 42.825-42.825s-19.174-42.825-42.825-42.825c-23.652 0-42.825 19.174-42.825 42.825z" - ], - "attrs": [ - {}, - {}, - {}, - {}, - {}, - {}, - {} - ], - "isMulticolor": false, - "isMulticolor2": false, - "grid": 16, - "tags": [ - "midi" - ] - }, - "attrs": [ - {}, - {}, - {}, - {}, - {}, - {}, - {} - ], - "properties": { - "order": 29, - "id": 18, - "name": "midi", - "prevSize": 32, - "code": 59648 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 2 - }, - { - "icon": { - "paths": [ - "M584.704 512q0-60.416-41.984-103.424t-103.424-43.008-103.424 43.008-43.008 103.424 43.008 103.424 103.424 43.008 103.424-43.008 41.984-103.424zM877.568 449.536v126.976q0 7.168-4.096 13.312t-11.264 7.168l-106.496 16.384q-10.24 30.72-21.504 52.224 19.456 27.648 60.416 78.848 6.144 6.144 6.144 13.312t-5.12 13.312q-15.36 21.504-56.32 62.464t-54.272 39.936q-7.168 0-14.336-5.12l-78.848-61.44q-25.6 13.312-52.224 21.504-9.216 77.824-16.384 106.496-4.096 16.384-20.48 16.384h-126.976q-8.192 0-14.336-5.12t-6.144-12.288l-16.384-105.472q-27.648-9.216-51.2-21.504l-80.896 61.44q-6.144 5.12-14.336 5.12t-14.336-6.144q-71.68-65.536-94.208-96.256-4.096-5.12-4.096-13.312 0-6.144 5.12-12.288 8.192-12.288 28.672-37.888t30.72-40.96q-15.36-28.672-23.552-56.32l-104.448-15.36q-7.168-1.024-11.264-7.168t-5.12-13.312v-126.976q0-7.168 5.12-13.312t10.24-7.168l106.496-16.384q8.192-25.6 22.528-52.224-23.552-32.768-61.44-78.848-6.144-7.168-6.144-14.336 0-5.12 5.12-12.288 15.36-20.48 56.32-61.44t54.272-40.96q7.168 0 15.36 5.12l78.848 61.44q24.576-13.312 51.2-21.504 9.216-77.824 17.408-106.496 3.072-16.384 20.48-16.384h126.976q7.168 0 13.312 5.12t7.168 12.288l15.36 105.472q28.672 9.216 52.224 20.48l80.896-60.416q5.12-5.12 13.312-5.12 7.168 0 14.336 5.12 73.728 68.608 94.208 97.28 4.096 5.12 4.096 12.288t-4.096 13.312q-9.216 12.288-29.696 37.888t-30.72 40.96q15.36 28.672 23.552 55.296l104.448 16.384q7.168 1.024 12.288 7.168t4.096 13.312z" - ], - "width": 878, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "cog" - ], - "defaultCode": 59397, - "grid": 16, - "attrs": [] - }, - "attrs": [], - "properties": { - "id": 16, - "order": 30, - "ligatures": "", - "prevSize": 32, - "code": 59397, - "name": "cog" - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 3 - }, - { - "icon": { - "paths": [ - "M402.432 717.824v137.216q0 9.216-7.168 15.36t-15.36 7.168h-137.216q-9.216 0-16.384-7.168t-7.168-15.36v-137.216q0-9.216 7.168-16.384t16.384-6.144h137.216q9.216 0 15.36 6.144t7.168 16.384zM582.656 374.784q0 30.72-8.192 57.344t-20.48 44.032-31.744 33.792-32.768 25.6-34.816 19.456q-23.552 13.312-38.912 37.888t-15.36 37.888q0 10.24-7.168 18.432t-16.384 9.216h-137.216q-8.192 0-14.336-11.264t-6.144-20.48v-26.624q0-47.104 37.888-89.088t80.896-61.44q33.792-16.384 48.128-32.768t14.336-43.008q0-24.576-26.624-41.984t-61.44-18.432q-36.864 0-61.44 16.384-20.48 14.336-61.44 65.536-7.168 9.216-17.408 9.216-7.168 0-14.336-4.096l-93.184-71.68q-8.192-6.144-9.216-14.336t3.072-16.384q91.136-151.552 265.216-151.552 46.080 0 92.16 17.408t82.944 47.104 60.416 73.728 23.552 90.112z" - ], - "width": 585, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "help" - ], - "defaultCode": 61736, - "grid": 16, - "attrs": [] - }, - "attrs": [], - "properties": { - "id": 17, - "order": 31, - "ligatures": "", - "prevSize": 32, - "code": 61736, - "name": "help" - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 4 - }, - { - "icon": { - "paths": [ - "M838.656 217.088q16.384 16.384 27.648 43.008t11.264 51.2v657.408q0 23.552-15.36 38.912t-38.912 16.384h-768q-23.552 0-38.912-16.384t-16.384-38.912v-913.408q0-23.552 16.384-38.912t38.912-16.384h512q22.528 0 50.176 11.264t43.008 27.648zM584.704 77.824v215.040h215.040q-5.12-17.408-12.288-23.552l-179.2-179.2q-6.144-7.168-23.552-12.288zM804.864 951.296v-585.728h-237.568q-23.552 0-38.912-16.384t-16.384-37.888v-238.592h-439.296v878.592h732.16z" - ], - "width": 878, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "doc" - ], - "defaultCode": 59392, - "grid": 16, - "attrs": [] - }, - "attrs": [], - "properties": { - "id": 10, - "order": 32, - "ligatures": "", - "prevSize": 32, - "name": "new", - "code": 59392 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 5 - }, - { - "icon": { - "paths": [ - "M219.136 877.568h439.296v-219.136h-439.296v219.136zM731.136 877.568h73.728v-512q0-8.192-6.144-21.504t-11.264-20.48l-160.768-159.744q-5.12-6.144-19.456-12.288t-22.528-5.12v237.568q0 22.528-15.36 38.912t-38.912 16.384h-329.728q-22.528 0-37.888-16.384t-16.384-38.912v-237.568h-73.728v731.136h73.728v-237.568q0-22.528 16.384-38.912t37.888-16.384h476.16q22.528 0 38.912 16.384t15.36 38.912v237.568zM512 347.136v-182.272q0-8.192-5.12-13.312t-13.312-5.12h-109.568q-7.168 0-13.312 5.12t-5.12 13.312v182.272q0 7.168 5.12 13.312t13.312 5.12h109.568q7.168 0 13.312-5.12t5.12-13.312zM877.568 365.568v530.432q0 22.528-15.36 38.912t-38.912 16.384h-768q-23.552 0-38.912-16.384t-16.384-38.912v-768q0-22.528 16.384-38.912t38.912-16.384h529.408q23.552 0 51.2 12.288t43.008 26.624l159.744 160.768q16.384 15.36 27.648 43.008t11.264 50.176z" - ], - "width": 878, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "floppy" - ], - "defaultCode": 59393, - "grid": 16, - "attrs": [] - }, - "attrs": [], - "properties": { - "id": 11, - "order": 33, - "ligatures": "", - "prevSize": 32, - "name": "save", - "code": 59393 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 6 - }, - { - "icon": { - "paths": [ - "M790.528 529.408l-758.784 421.888q-13.312 7.168-22.528 2.048t-9.216-20.48v-841.728q0-14.336 9.216-20.48t22.528 2.048l758.784 421.888q13.312 7.168 13.312 17.408t-13.312 17.408z" - ], - "width": 804, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "play" - ], - "defaultCode": 59394, - "grid": 16, - "attrs": [] - }, - "attrs": [], - "properties": { - "id": 12, - "order": 34, - "ligatures": "", - "prevSize": 32, - "name": "play", - "code": 59394 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 7 - }, - { - "icon": { - "paths": [ - "M877.568 109.568v804.864q0 14.336-10.24 25.6t-26.624 11.264h-291.84q-15.36 0-25.6-11.264t-11.264-25.6v-804.864q0-14.336 11.264-25.6t25.6-11.264h291.84q15.36 0 26.624 11.264t10.24 25.6zM365.568 109.568v804.864q0 14.336-10.24 25.6t-26.624 11.264h-291.84q-15.36 0-25.6-11.264t-11.264-25.6v-804.864q0-14.336 11.264-25.6t25.6-11.264h291.84q15.36 0 26.624 11.264t10.24 25.6z" - ], - "width": 878, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "pause" - ], - "defaultCode": 59395, - "grid": 16, - "attrs": [] - }, - "attrs": [], - "properties": { - "id": 13, - "order": 35, - "ligatures": "", - "prevSize": 32, - "name": "pause", - "code": 59395 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 8 - }, - { - "icon": { - "paths": [ - "M877.568 109.568v804.864q0 14.336-10.24 25.6t-26.624 11.264h-803.84q-15.36 0-25.6-11.264t-11.264-25.6v-804.864q0-14.336 11.264-25.6t25.6-11.264h803.84q15.36 0 26.624 11.264t10.24 25.6z" - ], - "width": 878, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "stop" - ], - "defaultCode": 59396, - "grid": 16, - "attrs": [] - }, - "attrs": [], - "properties": { - "id": 14, - "order": 36, - "ligatures": "", - "prevSize": 32, - "name": "stop", - "code": 59396 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 9 - }, - { - "icon": { - "paths": [ - "M1017.856 531.456q0-19.456-30.72-19.456h-621.568q-22.528 0-49.152 12.288t-39.936 29.696l-167.936 207.872q-11.264 13.312-11.264 22.528 0 20.48 30.72 20.48h621.568q23.552 0 49.152-13.312t40.96-29.696l167.936-207.872q10.24-12.288 10.24-22.528zM365.568 439.296h439.296v-92.16q0-22.528-16.384-38.912t-38.912-15.36h-328.704q-23.552 0-38.912-16.384t-16.384-38.912v-36.864q0-22.528-15.36-38.912t-38.912-15.36h-183.296q-22.528 0-38.912 15.36t-16.384 38.912v487.424l146.432-179.2q25.6-30.72 66.56-50.176t79.872-19.456zM1090.56 531.456q0 35.84-25.6 68.608l-168.96 207.872q-24.576 30.72-66.56 50.176t-79.872 19.456h-621.568q-52.224 0-90.112-37.888t-37.888-90.112v-548.864q0-52.224 37.888-90.112t90.112-37.888h183.296q52.224 0 90.112 37.888t37.888 90.112v18.432h310.272q53.248 0 90.112 37.888t37.888 90.112v92.16h109.568q30.72 0 57.344 13.312t37.888 40.96q8.192 17.408 8.192 37.888z" - ], - "width": 1097, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "folder-open-empty" - ], - "defaultCode": 61717, - "grid": 16, - "attrs": [] - }, - "attrs": [], - "properties": { - "id": 15, - "order": 37, - "ligatures": "", - "prevSize": 32, - "name": "open", - "code": 61717 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 10 - }, - { - "icon": { - "paths": [ - "M219.136 840.704q0-14.336-10.24-25.6t-25.6-10.24-25.6 10.24-11.264 25.6 11.264 25.6 25.6 11.264 25.6-11.264 10.24-25.6zM587.776 601.088l-390.144 390.144q-21.504 20.48-51.2 20.48t-52.224-20.48l-60.416-62.464q-21.504-20.48-21.504-51.2 0-29.696 21.504-52.224l389.12-389.12q22.528 56.32 65.536 99.328t99.328 65.536zM950.272 352.256q0 22.528-13.312 60.416-27.648 76.8-94.208 124.928t-147.456 47.104q-106.496 0-181.248-74.752t-74.752-181.248 74.752-180.224 181.248-75.776q32.768 0 68.608 10.24t61.44 26.624q9.216 6.144 9.216 15.36t-9.216 16.384l-166.912 96.256v128l110.592 61.44q2.048-2.048 45.056-27.648t76.8-46.080 40.96-20.48q8.192 0 13.312 5.12t5.12 14.336z" - ], - "width": 950, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "wrench" - ], - "defaultCode": 59392, - "grid": 16, - "attrs": [] - }, - "attrs": [], - "properties": { - "id": 21, - "order": 26, - "ligatures": "", - "prevSize": 32, - "code": 59400, - "name": "wrench" - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 11 - }, - { - "icon": { - "paths": [ - "M695.296 584.704q75.776 0 129.024 54.272t53.248 129.024-53.248 129.024-129.024 54.272-130.048-54.272-53.248-129.024q0-7.168 1.024-19.456l-205.824-102.4q-52.224 49.152-123.904 49.152-76.8 0-130.048-54.272t-53.248-129.024 53.248-129.024 130.048-54.272q71.68 0 123.904 49.152l205.824-102.4q-1.024-12.288-1.024-19.456 0-75.776 53.248-129.024t130.048-54.272 129.024 54.272 53.248 129.024-53.248 129.024-129.024 54.272q-72.704 0-124.928-49.152l-205.824 102.4q1.024 12.288 1.024 19.456t-1.024 19.456l205.824 102.4q52.224-49.152 124.928-49.152z" - ], - "attrs": [ - {} - ], - "width": 878, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "connections" - ], - "defaultCode": 61920, - "grid": 16 - }, - "attrs": [ - {} - ], - "properties": { - "order": 38, - "id": 22, - "name": "connections", - "ligatures": "", - "prevSize": 32, - "code": 61920 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 12 - }, - { - "icon": { - "paths": [ - "M0 806.912q0 30.72 21.504 52.224t52.224 21.504h32.768q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-32.768q-30.72 0-52.224 21.504t-21.504 52.224zM0 512q0 30.72 21.504 52.224t52.224 21.504h32.768q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-32.768q-30.72 0-52.224 21.504t-21.504 52.224zM0 217.088q0 30.72 21.504 52.224t52.224 21.504h32.768q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-32.768q-30.72 0-52.224 21.504t-21.504 52.224zM239.616 806.912q0 30.72 21.504 52.224t52.224 21.504h572.416q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-572.416q-30.72 0-52.224 21.504t-21.504 52.224zM239.616 512q0 30.72 21.504 52.224t52.224 21.504h572.416q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-572.416q-30.72 0-52.224 21.504t-21.504 52.224zM239.616 217.088q0 30.72 21.504 52.224t52.224 21.504h572.416q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-572.416q-30.72 0-52.224 21.504t-21.504 52.224z" - ], - "attrs": [ - {} - ], - "width": 959, - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "list" - ], - "defaultCode": 61449, - "grid": 16 - }, - "attrs": [ - {} - ], - "properties": { - "order": 39, - "id": 23, - "name": "list", - "ligatures": "", - "prevSize": 32, - "code": 61449 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 13 - } - ], - "height": 1024, - "metadata": { - "name": "icomoon" - }, - "preferences": { - "showGlyphs": true, - "showCodes": true, - "showQuickUse": true, - "showQuickUse2": true, - "showSVGs": true, - "fontPref": { - "prefix": "icon-", - "metadata": { - "fontFamily": "icomoon" - }, - "metrics": { - "emSize": 1024, - "baseline": 15 - }, - "embed": false - }, - "imagePref": { - "prefix": "icon-", - "png": true, - "useClassSelector": true, - "color": 0, - "bgColor": 16777215, - "classSelector": ".icon", - "height": 32, - "columns": 16, - "margin": 16 - }, - "historySize": 50, - "gridSize": 16, - "showGrid": true - } -} \ No newline at end of file +{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M548.864 384q67.584 0 115.712 48.128t48.128 116.736-48.128 115.712-115.712 48.128-116.736-48.128-48.128-115.712 48.128-116.736 116.736-48.128zM951.296 146.432q60.416 0 103.424 43.008t41.984 103.424v512q0 60.416-41.984 103.424t-103.424 43.008h-804.864q-60.416 0-103.424-43.008t-43.008-103.424v-512q0-60.416 43.008-103.424t103.424-43.008h128l28.672-77.824q11.264-27.648 39.936-48.128t59.392-20.48h292.864q29.696 0 58.368 20.48t39.936 48.128l29.696 77.824h128zM548.864 804.864q105.472 0 180.224-75.776t75.776-180.224-75.776-181.248-180.224-74.752-181.248 74.752-74.752 181.248 74.752 180.224 181.248 75.776z"],"attrs":[{}],"width":1097,"isMulticolor":false,"isMulticolor2":false,"tags":["camera"],"defaultCode":59396,"grid":16},"attrs":[{}],"properties":{"order":41,"id":0,"name":"camera","ligatures":"","prevSize":32,"code":59401},"setIdx":0,"setId":2,"iconIdx":0},{"icon":{"paths":["M741.376 755.712q0 22.528-15.36 38.912l-77.824 77.824q-16.384 15.36-38.912 15.36t-38.912-15.36l-167.936-168.96-167.936 168.96q-16.384 15.36-38.912 15.36t-38.912-15.36l-77.824-77.824q-16.384-16.384-16.384-38.912t16.384-38.912l167.936-167.936-167.936-167.936q-16.384-16.384-16.384-38.912t16.384-38.912l77.824-77.824q16.384-16.384 38.912-16.384t38.912 16.384l167.936 167.936 167.936-167.936q16.384-16.384 38.912-16.384t38.912 16.384l77.824 77.824q15.36 15.36 15.36 38.912t-15.36 38.912l-167.936 167.936 167.936 167.936q15.36 15.36 15.36 38.912z"],"attrs":[{}],"width":804,"isMulticolor":false,"isMulticolor2":false,"tags":["cancel"],"defaultCode":59393,"grid":16},"attrs":[{}],"properties":{"order":27,"id":20,"name":"cancel","ligatures":"","prevSize":32,"code":59399},"setIdx":1,"setId":1,"iconIdx":0},{"icon":{"paths":["M955.392 323.584q0 22.528-16.384 38.912l-491.52 491.52q-16.384 15.36-38.912 15.36t-38.912-15.36l-284.672-284.672q-15.36-16.384-15.36-38.912t15.36-38.912l77.824-77.824q16.384-16.384 38.912-16.384t38.912 16.384l167.936 168.96 374.784-375.808q16.384-16.384 38.912-16.384t38.912 16.384l77.824 77.824q16.384 15.36 16.384 38.912z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["ok"],"defaultCode":59392,"grid":16},"attrs":[{}],"properties":{"order":28,"id":19,"name":"ok","ligatures":"","prevSize":32,"code":59398},"setIdx":1,"setId":1,"iconIdx":1},{"icon":{"paths":["M512 1024c-282.77 0-512-229.23-512-512s229.23-512 512-512c282.77 0 512 229.23 512 512-0.325 282.639-229.361 511.675-511.969 512zM512 47.584c-256.49 0-464.416 207.926-464.416 464.416s207.926 464.416 464.416 464.416c256.49 0 464.416-207.926 464.416-464.416-0.298-256.371-208.047-464.119-464.388-464.416z","M462.132 953.1l-26.694-4.758c-209.349-37.86-366.067-218.704-366.067-436.152 0-244.585 198.276-442.861 442.861-442.861s442.861 198.276 442.861 442.861c0 214.517-152.522 393.41-355.037 434.153l-29.48 5.662-1.665-27.123c-1.736-27.787-24.697-49.666-52.768-49.666-28.423 0-51.607 22.43-52.816 50.556zM512 116.628c-218.306 0.104-395.237 177.1-395.237 395.42 0 185.886 128.266 341.814 301.119 384.106 16.258-39.97 53.869-68.663 98.182-68.663 43.642 0 80.783 27.831 94.644 66.714 172.112-44.776 296.761-198.909 296.761-382.156 0-218.384-177.036-395.42-395.42-395.42-0.017 0-0.034 0-0.052 0z","M274.986 600.030c-49.931 0-90.409-40.477-90.409-90.409s40.477-90.409 90.409-90.409c49.931 0 90.409 40.477 90.409 90.409s-40.477 90.409-90.409 90.409zM274.986 467.176c-23.652 0-42.825 19.174-42.825 42.825s19.174 42.825 42.825 42.825c23.652 0 42.825-19.174 42.825-42.825s-19.174-42.825-42.825-42.825z","M343.554 428.681c-49.932 0-90.409-40.477-90.409-90.409s40.477-90.409 90.409-90.409c49.931 0 90.409 40.477 90.409 90.409 0 25.025-10.167 47.675-26.597 64.045l-0.003 0.003c-16.277 16.287-38.768 26.362-63.613 26.362-0.069 0-0.138-0-0.207-0zM343.554 295.875c-0.011-0-0.024-0-0.038-0-23.652 0-42.825 19.174-42.825 42.825s19.174 42.825 42.825 42.825c23.652 0 42.825-19.174 42.825-42.825 0-11.864-4.824-22.601-12.618-30.357-7.693-7.704-18.325-12.469-30.070-12.469-0.035 0-0.070 0-0.106 0z","M513.19 356.068v0c-49.931 0-90.409-40.477-90.409-90.409s40.477-90.409 90.409-90.409c49.931 0 90.409 40.477 90.409 90.409s-40.477 90.409-90.409 90.409zM513.19 223.215c-23.652 0-42.825 19.174-42.825 42.825s19.174 42.825 42.825 42.825c23.652 0 42.825-19.174 42.825-42.825s-19.174-42.825-42.825-42.825z","M684.538 424.589c-0.052 0-0.115 0-0.177 0-24.851 0-47.349-10.074-63.633-26.361l-0-0c-16.432-16.373-26.599-39.023-26.599-64.048 0-49.931 40.477-90.409 90.409-90.409s90.409 40.477 90.409 90.409c0 49.931-40.477 90.409-90.409 90.409zM654.37 364.586c7.763 7.832 18.525 12.681 30.419 12.681 23.652 0 42.825-19.174 42.825-42.825s-19.174-42.825-42.825-42.825c-11.894 0-22.656 4.849-30.416 12.678-7.671 7.739-12.409 18.39-12.409 30.147s4.738 22.408 12.409 30.147z","M757.151 594.272c-49.931 0-90.409-40.477-90.409-90.409v0c0-49.931 40.477-90.409 90.409-90.409s90.409 40.477 90.409 90.409c0 49.931-40.477 90.409-90.409 90.409zM714.326 503.863c0 23.652 19.174 42.825 42.825 42.825v0c23.652 0 42.825-19.174 42.825-42.825s-19.174-42.825-42.825-42.825c-23.652 0-42.825 19.174-42.825 42.825z"],"attrs":[{},{},{},{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["midi"]},"attrs":[{},{},{},{},{},{},{}],"properties":{"order":29,"id":18,"name":"midi","prevSize":32,"code":59648},"setIdx":1,"setId":1,"iconIdx":2},{"icon":{"paths":["M584.704 512q0-60.416-41.984-103.424t-103.424-43.008-103.424 43.008-43.008 103.424 43.008 103.424 103.424 43.008 103.424-43.008 41.984-103.424zM877.568 449.536v126.976q0 7.168-4.096 13.312t-11.264 7.168l-106.496 16.384q-10.24 30.72-21.504 52.224 19.456 27.648 60.416 78.848 6.144 6.144 6.144 13.312t-5.12 13.312q-15.36 21.504-56.32 62.464t-54.272 39.936q-7.168 0-14.336-5.12l-78.848-61.44q-25.6 13.312-52.224 21.504-9.216 77.824-16.384 106.496-4.096 16.384-20.48 16.384h-126.976q-8.192 0-14.336-5.12t-6.144-12.288l-16.384-105.472q-27.648-9.216-51.2-21.504l-80.896 61.44q-6.144 5.12-14.336 5.12t-14.336-6.144q-71.68-65.536-94.208-96.256-4.096-5.12-4.096-13.312 0-6.144 5.12-12.288 8.192-12.288 28.672-37.888t30.72-40.96q-15.36-28.672-23.552-56.32l-104.448-15.36q-7.168-1.024-11.264-7.168t-5.12-13.312v-126.976q0-7.168 5.12-13.312t10.24-7.168l106.496-16.384q8.192-25.6 22.528-52.224-23.552-32.768-61.44-78.848-6.144-7.168-6.144-14.336 0-5.12 5.12-12.288 15.36-20.48 56.32-61.44t54.272-40.96q7.168 0 15.36 5.12l78.848 61.44q24.576-13.312 51.2-21.504 9.216-77.824 17.408-106.496 3.072-16.384 20.48-16.384h126.976q7.168 0 13.312 5.12t7.168 12.288l15.36 105.472q28.672 9.216 52.224 20.48l80.896-60.416q5.12-5.12 13.312-5.12 7.168 0 14.336 5.12 73.728 68.608 94.208 97.28 4.096 5.12 4.096 12.288t-4.096 13.312q-9.216 12.288-29.696 37.888t-30.72 40.96q15.36 28.672 23.552 55.296l104.448 16.384q7.168 1.024 12.288 7.168t4.096 13.312z"],"width":878,"isMulticolor":false,"isMulticolor2":false,"tags":["cog"],"defaultCode":59397,"grid":16,"attrs":[]},"attrs":[],"properties":{"id":16,"order":30,"ligatures":"","prevSize":32,"code":59397,"name":"cog"},"setIdx":1,"setId":1,"iconIdx":3},{"icon":{"paths":["M402.432 717.824v137.216q0 9.216-7.168 15.36t-15.36 7.168h-137.216q-9.216 0-16.384-7.168t-7.168-15.36v-137.216q0-9.216 7.168-16.384t16.384-6.144h137.216q9.216 0 15.36 6.144t7.168 16.384zM582.656 374.784q0 30.72-8.192 57.344t-20.48 44.032-31.744 33.792-32.768 25.6-34.816 19.456q-23.552 13.312-38.912 37.888t-15.36 37.888q0 10.24-7.168 18.432t-16.384 9.216h-137.216q-8.192 0-14.336-11.264t-6.144-20.48v-26.624q0-47.104 37.888-89.088t80.896-61.44q33.792-16.384 48.128-32.768t14.336-43.008q0-24.576-26.624-41.984t-61.44-18.432q-36.864 0-61.44 16.384-20.48 14.336-61.44 65.536-7.168 9.216-17.408 9.216-7.168 0-14.336-4.096l-93.184-71.68q-8.192-6.144-9.216-14.336t3.072-16.384q91.136-151.552 265.216-151.552 46.080 0 92.16 17.408t82.944 47.104 60.416 73.728 23.552 90.112z"],"width":585,"isMulticolor":false,"isMulticolor2":false,"tags":["help"],"defaultCode":61736,"grid":16,"attrs":[]},"attrs":[],"properties":{"id":17,"order":31,"ligatures":"","prevSize":32,"code":61736,"name":"help"},"setIdx":1,"setId":1,"iconIdx":4},{"icon":{"paths":["M838.656 217.088q16.384 16.384 27.648 43.008t11.264 51.2v657.408q0 23.552-15.36 38.912t-38.912 16.384h-768q-23.552 0-38.912-16.384t-16.384-38.912v-913.408q0-23.552 16.384-38.912t38.912-16.384h512q22.528 0 50.176 11.264t43.008 27.648zM584.704 77.824v215.040h215.040q-5.12-17.408-12.288-23.552l-179.2-179.2q-6.144-7.168-23.552-12.288zM804.864 951.296v-585.728h-237.568q-23.552 0-38.912-16.384t-16.384-37.888v-238.592h-439.296v878.592h732.16z"],"width":878,"isMulticolor":false,"isMulticolor2":false,"tags":["doc"],"defaultCode":59392,"grid":16,"attrs":[]},"attrs":[],"properties":{"id":10,"order":32,"ligatures":"","prevSize":32,"name":"new","code":59392},"setIdx":1,"setId":1,"iconIdx":5},{"icon":{"paths":["M219.136 877.568h439.296v-219.136h-439.296v219.136zM731.136 877.568h73.728v-512q0-8.192-6.144-21.504t-11.264-20.48l-160.768-159.744q-5.12-6.144-19.456-12.288t-22.528-5.12v237.568q0 22.528-15.36 38.912t-38.912 16.384h-329.728q-22.528 0-37.888-16.384t-16.384-38.912v-237.568h-73.728v731.136h73.728v-237.568q0-22.528 16.384-38.912t37.888-16.384h476.16q22.528 0 38.912 16.384t15.36 38.912v237.568zM512 347.136v-182.272q0-8.192-5.12-13.312t-13.312-5.12h-109.568q-7.168 0-13.312 5.12t-5.12 13.312v182.272q0 7.168 5.12 13.312t13.312 5.12h109.568q7.168 0 13.312-5.12t5.12-13.312zM877.568 365.568v530.432q0 22.528-15.36 38.912t-38.912 16.384h-768q-23.552 0-38.912-16.384t-16.384-38.912v-768q0-22.528 16.384-38.912t38.912-16.384h529.408q23.552 0 51.2 12.288t43.008 26.624l159.744 160.768q16.384 15.36 27.648 43.008t11.264 50.176z"],"width":878,"isMulticolor":false,"isMulticolor2":false,"tags":["floppy"],"defaultCode":59393,"grid":16,"attrs":[]},"attrs":[],"properties":{"id":11,"order":33,"ligatures":"","prevSize":32,"name":"save","code":59393},"setIdx":1,"setId":1,"iconIdx":6},{"icon":{"paths":["M790.528 529.408l-758.784 421.888q-13.312 7.168-22.528 2.048t-9.216-20.48v-841.728q0-14.336 9.216-20.48t22.528 2.048l758.784 421.888q13.312 7.168 13.312 17.408t-13.312 17.408z"],"width":804,"isMulticolor":false,"isMulticolor2":false,"tags":["play"],"defaultCode":59394,"grid":16,"attrs":[]},"attrs":[],"properties":{"id":12,"order":34,"ligatures":"","prevSize":32,"name":"play","code":59394},"setIdx":1,"setId":1,"iconIdx":7},{"icon":{"paths":["M877.568 109.568v804.864q0 14.336-10.24 25.6t-26.624 11.264h-291.84q-15.36 0-25.6-11.264t-11.264-25.6v-804.864q0-14.336 11.264-25.6t25.6-11.264h291.84q15.36 0 26.624 11.264t10.24 25.6zM365.568 109.568v804.864q0 14.336-10.24 25.6t-26.624 11.264h-291.84q-15.36 0-25.6-11.264t-11.264-25.6v-804.864q0-14.336 11.264-25.6t25.6-11.264h291.84q15.36 0 26.624 11.264t10.24 25.6z"],"width":878,"isMulticolor":false,"isMulticolor2":false,"tags":["pause"],"defaultCode":59395,"grid":16,"attrs":[]},"attrs":[],"properties":{"id":13,"order":35,"ligatures":"","prevSize":32,"name":"pause","code":59395},"setIdx":1,"setId":1,"iconIdx":8},{"icon":{"paths":["M877.568 109.568v804.864q0 14.336-10.24 25.6t-26.624 11.264h-803.84q-15.36 0-25.6-11.264t-11.264-25.6v-804.864q0-14.336 11.264-25.6t25.6-11.264h803.84q15.36 0 26.624 11.264t10.24 25.6z"],"width":878,"isMulticolor":false,"isMulticolor2":false,"tags":["stop"],"defaultCode":59396,"grid":16,"attrs":[]},"attrs":[],"properties":{"id":14,"order":36,"ligatures":"","prevSize":32,"name":"stop","code":59396},"setIdx":1,"setId":1,"iconIdx":9},{"icon":{"paths":["M1017.856 531.456q0-19.456-30.72-19.456h-621.568q-22.528 0-49.152 12.288t-39.936 29.696l-167.936 207.872q-11.264 13.312-11.264 22.528 0 20.48 30.72 20.48h621.568q23.552 0 49.152-13.312t40.96-29.696l167.936-207.872q10.24-12.288 10.24-22.528zM365.568 439.296h439.296v-92.16q0-22.528-16.384-38.912t-38.912-15.36h-328.704q-23.552 0-38.912-16.384t-16.384-38.912v-36.864q0-22.528-15.36-38.912t-38.912-15.36h-183.296q-22.528 0-38.912 15.36t-16.384 38.912v487.424l146.432-179.2q25.6-30.72 66.56-50.176t79.872-19.456zM1090.56 531.456q0 35.84-25.6 68.608l-168.96 207.872q-24.576 30.72-66.56 50.176t-79.872 19.456h-621.568q-52.224 0-90.112-37.888t-37.888-90.112v-548.864q0-52.224 37.888-90.112t90.112-37.888h183.296q52.224 0 90.112 37.888t37.888 90.112v18.432h310.272q53.248 0 90.112 37.888t37.888 90.112v92.16h109.568q30.72 0 57.344 13.312t37.888 40.96q8.192 17.408 8.192 37.888z"],"width":1097,"isMulticolor":false,"isMulticolor2":false,"tags":["folder-open-empty"],"defaultCode":61717,"grid":16,"attrs":[]},"attrs":[],"properties":{"id":15,"order":37,"ligatures":"","prevSize":32,"name":"open","code":61717},"setIdx":1,"setId":1,"iconIdx":10},{"icon":{"paths":["M219.136 840.704q0-14.336-10.24-25.6t-25.6-10.24-25.6 10.24-11.264 25.6 11.264 25.6 25.6 11.264 25.6-11.264 10.24-25.6zM587.776 601.088l-390.144 390.144q-21.504 20.48-51.2 20.48t-52.224-20.48l-60.416-62.464q-21.504-20.48-21.504-51.2 0-29.696 21.504-52.224l389.12-389.12q22.528 56.32 65.536 99.328t99.328 65.536zM950.272 352.256q0 22.528-13.312 60.416-27.648 76.8-94.208 124.928t-147.456 47.104q-106.496 0-181.248-74.752t-74.752-181.248 74.752-180.224 181.248-75.776q32.768 0 68.608 10.24t61.44 26.624q9.216 6.144 9.216 15.36t-9.216 16.384l-166.912 96.256v128l110.592 61.44q2.048-2.048 45.056-27.648t76.8-46.080 40.96-20.48q8.192 0 13.312 5.12t5.12 14.336z"],"width":950,"isMulticolor":false,"isMulticolor2":false,"tags":["wrench"],"defaultCode":59392,"grid":16,"attrs":[]},"attrs":[],"properties":{"id":21,"order":26,"ligatures":"","prevSize":32,"code":59400,"name":"wrench"},"setIdx":1,"setId":1,"iconIdx":11},{"icon":{"paths":["M695.296 584.704q75.776 0 129.024 54.272t53.248 129.024-53.248 129.024-129.024 54.272-130.048-54.272-53.248-129.024q0-7.168 1.024-19.456l-205.824-102.4q-52.224 49.152-123.904 49.152-76.8 0-130.048-54.272t-53.248-129.024 53.248-129.024 130.048-54.272q71.68 0 123.904 49.152l205.824-102.4q-1.024-12.288-1.024-19.456 0-75.776 53.248-129.024t130.048-54.272 129.024 54.272 53.248 129.024-53.248 129.024-129.024 54.272q-72.704 0-124.928-49.152l-205.824 102.4q1.024 12.288 1.024 19.456t-1.024 19.456l205.824 102.4q52.224-49.152 124.928-49.152z"],"attrs":[{}],"width":878,"isMulticolor":false,"isMulticolor2":false,"tags":["connections"],"defaultCode":61920,"grid":16},"attrs":[{}],"properties":{"order":38,"id":22,"name":"connections","ligatures":"","prevSize":32,"code":61920},"setIdx":1,"setId":1,"iconIdx":12},{"icon":{"paths":["M0 806.912q0 30.72 21.504 52.224t52.224 21.504h32.768q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-32.768q-30.72 0-52.224 21.504t-21.504 52.224zM0 512q0 30.72 21.504 52.224t52.224 21.504h32.768q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-32.768q-30.72 0-52.224 21.504t-21.504 52.224zM0 217.088q0 30.72 21.504 52.224t52.224 21.504h32.768q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-32.768q-30.72 0-52.224 21.504t-21.504 52.224zM239.616 806.912q0 30.72 21.504 52.224t52.224 21.504h572.416q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-572.416q-30.72 0-52.224 21.504t-21.504 52.224zM239.616 512q0 30.72 21.504 52.224t52.224 21.504h572.416q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-572.416q-30.72 0-52.224 21.504t-21.504 52.224zM239.616 217.088q0 30.72 21.504 52.224t52.224 21.504h572.416q30.72 0 52.224-21.504t21.504-52.224-21.504-52.224-52.224-21.504h-572.416q-30.72 0-52.224 21.504t-21.504 52.224z"],"attrs":[{}],"width":959,"isMulticolor":false,"isMulticolor2":false,"tags":["list"],"defaultCode":61449,"grid":16},"attrs":[{}],"properties":{"order":39,"id":23,"name":"list","ligatures":"","prevSize":32,"code":61449},"setIdx":1,"setId":1,"iconIdx":13}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showCodes":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon"},"metrics":{"emSize":1024,"baseline":15},"embed":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215,"classSelector":".icon","height":32,"columns":16,"margin":16},"historySize":50,"gridSize":16,"showGrid":true}} \ No newline at end of file diff --git a/src/fonts/icomoon/style.css b/src/fonts/icomoon/style.css index 3112f546..bf23db47 100755 --- a/src/fonts/icomoon/style.css +++ b/src/fonts/icomoon/style.css @@ -1,12 +1,13 @@ @font-face { font-family: 'icomoon'; - src: url('fonts/icomoon.eot?orbova'); - src: url('fonts/icomoon.eot?orbova#iefix') format('embedded-opentype'), - url('fonts/icomoon.ttf?orbova') format('truetype'), - url('fonts/icomoon.woff?orbova') format('woff'), - url('fonts/icomoon.svg?orbova#icomoon') format('svg'); + src: url('fonts/icomoon.eot?kj06k5'); + src: url('fonts/icomoon.eot?kj06k5#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?kj06k5') format('truetype'), + url('fonts/icomoon.woff?kj06k5') format('woff'), + url('fonts/icomoon.svg?kj06k5#icomoon') format('svg'); font-weight: normal; font-style: normal; + font-display: block; } [class^="icon-"], [class*=" icon-"] { @@ -24,6 +25,9 @@ -moz-osx-font-smoothing: grayscale; } +.icon-camera:before { + content: "\e809"; +} .icon-cancel:before { content: "\e807"; } diff --git a/src/index.html b/src/index.html index 97bbf19f..62f5f3d6 100644 --- a/src/index.html +++ b/src/index.html @@ -42,6 +42,10 @@ + +
+
+
+ Presets +
+
+
    +
    +
    @@ -265,6 +277,11 @@

    References

    + diff --git a/src/js/main.js b/src/js/main.js index caecd96c..c9b44cd0 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -26,6 +26,7 @@ import { setup as setupLibrary } from './view/library.js'; import { setup as setupNetwork } from './midi/network.js'; import { setup as setupPanels } from './view/panels.js'; import { setup as setupPreferences } from './view/preferences.js'; +import { setup as setupPresets } from './view/presets.js'; import { setup as setupRemote } from './view/remote.js'; import { setup as setupTransport } from './core/transport.js'; import { setup as setupSequencer } from './core/sequencer.js'; @@ -41,6 +42,7 @@ async function main() { setupLibrary(); setupNetwork(); setupPreferences(); + setupPresets(); setupRemote(); setupTransport(); setupSequencer(); diff --git a/src/js/view/controls.js b/src/js/view/controls.js index 26418de3..7ec24449 100644 --- a/src/js/view/controls.js +++ b/src/js/view/controls.js @@ -7,6 +7,7 @@ const exportEl = document.querySelector('#file-export'); const playEl = document.getElementById('play-check'); const bpmEl = document.getElementById('bpm-number'); const remoteEl = document.getElementById('learn-check'); +const presetsEl = document.getElementById('presets-check'); const libraryEl = document.getElementById('library-check'); const prefsEl = document.getElementById('prefs-check'); const editEl = document.getElementById('edit-check'); @@ -40,6 +41,9 @@ function addEventListeners() { remoteEl.addEventListener('change', e => { dispatch(actions.toggleMIDILearnMode()); }); + presetsEl.addEventListener('change', e => { + dispatch(actions.togglePanel('presets')); + }); libraryEl.addEventListener('change', e => { dispatch(actions.togglePanel('library')); }); @@ -131,5 +135,6 @@ function updateInputs(state) { remoteEl.checked = state.learnModeActive; editEl.checked = state.showSettingsPanel; libraryEl.checked = state.showLibraryPanel; + presetsEl.checked = state.showPresetsPanel; connectionsEl.checked = state.connectModeActive; } diff --git a/src/js/view/panels.js b/src/js/view/panels.js index 97027cff..16da34af 100644 --- a/src/js/view/panels.js +++ b/src/js/view/panels.js @@ -11,6 +11,7 @@ const prefsEl = document.querySelector('.prefs'); const editEl = document.querySelector('.edit'); const editContentEl = document.querySelector('.edit .panel__content'); const remoteEl = document.querySelector('.remote'); +const presetsEl = document.querySelector('.presets'); let panelHeaderHeight; export function setup() { @@ -215,5 +216,6 @@ function showPanels(state) { remoteEl.dataset.show = state.learnModeActive; editEl.dataset.show = state.showSettingsPanel; libraryEl.dataset.show = state.showLibraryPanel; + presetsEl.dataset.show = state.showPresetsPanel; renderLayout(); } diff --git a/src/js/view/presets.js b/src/js/view/presets.js new file mode 100644 index 00000000..54337620 --- /dev/null +++ b/src/js/view/presets.js @@ -0,0 +1,40 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; + +const listEl = document.querySelector('.presets__list'); + +export function setup() { + addEventListeners(); + build(); +} + +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); +} + +function build() { + const template = document.querySelector('#template-presets-item'); + for (let i = 0, n = 16; i < n; i++) { + const clone = template.content.cloneNode(true); + const el = clone.firstElementChild; + listEl.appendChild(el); + + el.querySelector('.presets__item-label').innerHTML = `${i + 1}.`; + el.dataset.index = i; + el.addEventListener('touchend', handleClick); + el.addEventListener('click', handleClick); + } +} + +function handleClick(e) { + dispatch(getActions().storePreset(e.target.dataset.index)); +} + +/** + * Handle state changes. + * @param {Object} e + */ +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + } +} From 1d843c5f7bc6e0d6d69fdcdbe62948432a191b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 15 Oct 2019 17:54:14 +0200 Subject: [PATCH 033/131] Preset store action, WIP. --- src/js/state/actions.js | 29 +++++++++++++++++++++++++---- src/js/state/reducers.js | 16 ++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/js/state/actions.js b/src/js/state/actions.js index 9f539e3f..8613f5d0 100644 --- a/src/js/state/actions.js +++ b/src/js/state/actions.js @@ -24,6 +24,7 @@ const ADD_PROCESSOR = 'ADD_PROCESSOR', SET_TEMPO = 'SET_TEMPO', SET_THEME = 'SET_THEME', SET_TRANSPORT = 'SET_TRANSPORT', + STORE_PRESET = 'STORE_PRESET', TOGGLE_CONNECT_MODE = 'TOGGLE_CONNECT_MODE', TOGGLE_MIDI_LEARN_MODE = 'TOGGLE_MIDI_LEARN_MODE', TOGGLE_MIDI_LEARN_TARGET = 'TOGGLE_MIDI_LEARN_TARGET', @@ -340,10 +341,10 @@ export default { disconnectProcessors: id => { return (dispatch, getState, getActions) => { - let state = getState(); - const connection = state.connections.byId[id]; - const sourceProcessor = state.processors.byId[connection.sourceProcessorID]; - const destinationProcessor = state.processors.byId[connection.destinationProcessorID]; + const { connections, processors, } = getState(); + const connection = connections.byId[id]; + const sourceProcessor = processors.byId[connection.sourceProcessorID]; + const destinationProcessor = processors.byId[connection.destinationProcessorID]; // disconnect the processors dispatch(getActions().disconnectProcessors2(id)); @@ -359,4 +360,24 @@ export default { libraryDrop: (processorType, x, y) => { return { type: LIBRARY_DROP, processorType, x, y, }; }, + + STORE_PRESET, + storePreset: index => { + return (dispatch, getState, getActions) => { + const { processors, } = getState(); + const preset = processors.allIds.reduce((accumulator, processorId) => { + const params = processors.byId[processorId].params; + return { + ...accumulator, + [processorId]: params.allIds.reduce((acc, paramId) => { + return { + ...acc, + [paramId]: params.byId[paramId].value, + }; + }, {}), + }; + }, {}); + return { type: STORE_PRESET, index, preset }; + } + } } diff --git a/src/js/state/reducers.js b/src/js/state/reducers.js index 3e127d67..4f3aa10b 100644 --- a/src/js/state/reducers.js +++ b/src/js/state/reducers.js @@ -28,6 +28,10 @@ const initialState = { allIds: [], byId: {}, }, + presets: { + allIds: [], + byId: {}, + }, processors: { allIds: [], byId: {}, @@ -36,6 +40,7 @@ const initialState = { showHelpPanel: false, showLibraryPanel: true, showPreferencesPanel: false, + showPresetsPanel: false, showSettingsPanel: false, theme: 'dev', // 'light|dark' transport: 'stop', // 'play|pause|stop' @@ -325,6 +330,7 @@ export default function reduce(state = initialState, action, actions = {}) { showPreferencesPanel: action.panelName === 'preferences' ? !state.showPreferencesPanel : state.showPreferencesPanel, showSettingsPanel: action.panelName === 'settings' ? !state.showSettingsPanel : state.showSettingsPanel, showLibraryPanel: action.panelName === 'library' ? !state.showLibraryPanel : state.showLibraryPanel, + showPresetsPanel: action.panelName === 'presets' ? !state.showPresetsPanel : state.showPresetsPanel, }; case actions.TOGGLE_CONNECT_MODE: @@ -406,6 +412,16 @@ export default function reduce(state = initialState, action, actions = {}) { y: action.y, }, }; + + case actions.STORE_PRESET: + console.log(action.index, action.preset); + return { + ...state, + presets: { + allIds: state.presets.allIds, + byId: state.presets.byId, + }, + }; default: return state ? state : initialState; From 472bd01ec4bafdb8b184f11c7f638a732790f75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Wed, 16 Oct 2019 18:00:24 +0200 Subject: [PATCH 034/131] Presets are stored in state. --- src/css/presets.css | 58 +++++++++++++++++++++++++++++++++++++--- src/css/style.css | 1 + src/index.html | 15 +++++++++-- src/js/state/actions.js | 14 +++++----- src/js/state/reducers.js | 21 ++++++++------- src/js/view/presets.js | 47 +++++++++++++++++++++++++++----- 6 files changed, 129 insertions(+), 27 deletions(-) diff --git a/src/css/presets.css b/src/css/presets.css index b7c51243..6b6fbaa1 100644 --- a/src/css/presets.css +++ b/src/css/presets.css @@ -15,15 +15,67 @@ flex-wrap: wrap-reverse; list-style: none; } +.presets__list[data-mode="edit"] .presets__item-load { + flex-basis: 50%; +} +.presets__list[data-mode="edit"] .presets__item-store { + display: block; +} .presets__item { cursor: pointer; + display: flex; flex-basis: 25%; - padding: 20px; +} +.presets__item-load { + flex-basis: 100%; + padding: 20px 10px; text-align: center; } -.presets__item:hover { - background-color: var(--border-color); +.presets__item-store { + display: none; + flex-basis: 50%; + padding: 20px 10px; + text-align: center; +} +.presets__item-load:hover, +.presets__item-store:hover { + background-color: var(--btn-active-bg-color); +} +.presets__item[data-state="set"] .presets__item-load { + background: repeating-linear-gradient( + -45deg, + transparent, + transparent 10px, + var(--panel-bg-color) 10px, + var(--panel-bg-color) 20px + ), + linear-gradient( + to bottom, + var(--btn-active-bg-color), + var(--btn-active-bg-color) + ); } .presets__item-label { font-size: 1 rem; } +.presets__edit {} +.presets__edit-check { + position: absolute; + margin-left: -9999px; + visibility: hidden; +} +.presets__edit-label { + cursor: pointer; + display: block; + padding: 10px; + text-align: center; +} +.presets__edit-label:hover { + background-color: var(--btn-active-bg-color); + color: var(--btn-hover-color); +} +.presets__edit-check:checked + .presets__edit-label { + color: var(--btn-active-color); +} +.presets__edit-check:checked + .presets__edit-label:hover { +} diff --git a/src/css/style.css b/src/css/style.css index b76b599f..fb9dfc14 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -120,6 +120,7 @@ div[data-theme='dev'] { --bg-color: #fff; --border-color: #ddd; --btn-color: #bbb; + --btn-hover-color: #aaa; --btn-active-color: #999; --btn-bg-color: #eee; --btn-active-bg-color: #ddd; diff --git a/src/index.html b/src/index.html index 62f5f3d6..55cacf2e 100644 --- a/src/index.html +++ b/src/index.html @@ -129,7 +129,13 @@ Presets
    -
      +
      +
        + +
        @@ -279,7 +285,12 @@

        References

        diff --git a/src/js/state/actions.js b/src/js/state/actions.js index 8613f5d0..942a5246 100644 --- a/src/js/state/actions.js +++ b/src/js/state/actions.js @@ -29,6 +29,7 @@ const ADD_PROCESSOR = 'ADD_PROCESSOR', TOGGLE_MIDI_LEARN_MODE = 'TOGGLE_MIDI_LEARN_MODE', TOGGLE_MIDI_LEARN_TARGET = 'TOGGLE_MIDI_LEARN_TARGET', TOGGLE_MIDI_PREFERENCE = 'TOGGLE_MIDI_PREFERENCE', + TOGGLE_PRESETS_MODE = 'TOGGLE_PRESETS_MODE', TOGGLE_PANEL = 'TOGGLE_PANEL', UNASSIGN_EXTERNAL_CONTROL = 'UNASSIGN_EXTERNAL_CONTROL', UPDATE_MIDI_PORT = 'UPDATE_MIDI_PORT'; @@ -295,6 +296,9 @@ export default { } }, + TOGGLE_PRESETS_MODE, + togglePresetsMode: () => ({ type: TOGGLE_PRESETS_MODE }), + SET_TRANSPORT, setTransport: command => ({ type: SET_TRANSPORT, command }), @@ -352,19 +356,17 @@ export default { }, SET_CAMERA_POSITION, - setCameraPosition: (x, y, z, isRelative = false) => { - return { type: SET_CAMERA_POSITION, x, y, z, isRelative, }; - }, + setCameraPosition: (x, y, z, isRelative = false) => ({ type: SET_CAMERA_POSITION, x, y, z, isRelative, }), LIBRARY_DROP, - libraryDrop: (processorType, x, y) => { - return { type: LIBRARY_DROP, processorType, x, y, }; - }, + libraryDrop: (processorType, x, y) => ({ type: LIBRARY_DROP, processorType, x, y, }), STORE_PRESET, storePreset: index => { return (dispatch, getState, getActions) => { const { processors, } = getState(); + + // create parameter key value objects for all processors const preset = processors.allIds.reduce((accumulator, processorId) => { const params = processors.byId[processorId].params; return { diff --git a/src/js/state/reducers.js b/src/js/state/reducers.js index 4f3aa10b..632f9b4c 100644 --- a/src/js/state/reducers.js +++ b/src/js/state/reducers.js @@ -28,10 +28,8 @@ const initialState = { allIds: [], byId: {}, }, - presets: { - allIds: [], - byId: {}, - }, + presets: [], + presetsEditModeActive: false, processors: { allIds: [], byId: {}, @@ -275,6 +273,9 @@ export default function reduce(state = initialState, action, actions = {}) { learnTargetProcessorID: action.processorID, learnTargetParameterKey: action.parameterKey }; + + case actions.TOGGLE_PRESETS_MODE: + return { ...state, presetsEditModeActive: !state.presetsEditModeActive }; case actions.SET_TRANSPORT: let value = action.command; @@ -413,15 +414,15 @@ export default function reduce(state = initialState, action, actions = {}) { }, }; - case actions.STORE_PRESET: - console.log(action.index, action.preset); + case actions.STORE_PRESET: { + const { index, preset, } = action; + const presets = [ ...state.presets, ]; + presets[index] = preset; return { ...state, - presets: { - allIds: state.presets.allIds, - byId: state.presets.byId, - }, + presets, }; + } default: return state ? state : initialState; diff --git a/src/js/view/presets.js b/src/js/view/presets.js index 54337620..19dc4724 100644 --- a/src/js/view/presets.js +++ b/src/js/view/presets.js @@ -1,6 +1,8 @@ import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; const listEl = document.querySelector('.presets__list'); +const editEl = document.querySelector('.presets__edit'); +const numPresets = 16; export function setup() { addEventListeners(); @@ -9,24 +11,36 @@ export function setup() { function addEventListeners() { document.addEventListener(STATE_CHANGE, handleStateChanges); + editEl.addEventListener('change', handleEditClick); } function build() { const template = document.querySelector('#template-presets-item'); - for (let i = 0, n = 16; i < n; i++) { + for (let i = 0, n = numPresets; i < n; i++) { const clone = template.content.cloneNode(true); const el = clone.firstElementChild; listEl.appendChild(el); - el.querySelector('.presets__item-label').innerHTML = `${i + 1}.`; + el.querySelector('.presets__item-load .presets__item-label').innerHTML = `${i + 1}.`; + el.querySelector('.presets__item-store .presets__item-label').innerHTML = '+'; el.dataset.index = i; - el.addEventListener('touchend', handleClick); - el.addEventListener('click', handleClick); + el.querySelector('.presets__item-load').addEventListener('touchend', handleLoadClick); + el.querySelector('.presets__item-load').addEventListener('click', handleLoadClick); + el.querySelector('.presets__item-store').addEventListener('touchend', handleStoreClick); + el.querySelector('.presets__item-store').addEventListener('click', handleStoreClick); } } -function handleClick(e) { - dispatch(getActions().storePreset(e.target.dataset.index)); +function handleLoadClick(e) { + dispatch(getActions().loadPreset(e.currentTarget.parentNode.dataset.index)); +} + +function handleStoreClick(e) { + dispatch(getActions().storePreset(e.currentTarget.parentNode.dataset.index)); +} + +function handleEditClick() { + dispatch(getActions().togglePresetsMode()); } /** @@ -36,5 +50,26 @@ function handleClick(e) { function handleStateChanges(e) { const { state, action, actions, } = e.detail; switch (action.type) { + + case actions.CREATE_PROJECT: + case actions.STORE_PRESET: + setPresetEditMode(state); + updateList(state); + break; + + case actions.TOGGLE_PRESETS_MODE: + setPresetEditMode(state); + break; + } +} + +function setPresetEditMode(state) { + editEl.checked = state.presetsEditModeActive; + listEl.dataset.mode = state.presetsEditModeActive ? 'edit' : ''; +} + +function updateList(state) { + for (let i = 0, n = numPresets; i < n; i++) { + listEl.children[i].dataset.state = state.presets[i] ? 'set' : ''; } } From 990e70001bbecfea63c3335bda7a57908ad13374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 17 Oct 2019 11:59:38 +0200 Subject: [PATCH 035/131] Preset loading added. --- src/css/presets.css | 6 +-- src/js/processors/epg/object3dController.js | 7 ++++ src/js/processors/epg/processor.js | 6 +++ .../processors/euclidfx/object3dController.js | 7 ++++ src/js/processors/euclidfx/processor.js | 7 ++++ src/js/state/actions.js | 6 ++- src/js/state/reducers.js | 39 +++++++++++++++++++ src/js/view/presets.js | 23 ++++++++++- src/js/view/setting/base.js | 4 ++ 9 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/css/presets.css b/src/css/presets.css index 6b6fbaa1..13590c37 100644 --- a/src/css/presets.css +++ b/src/css/presets.css @@ -15,10 +15,10 @@ flex-wrap: wrap-reverse; list-style: none; } -.presets__list[data-mode="edit"] .presets__item-load { +.presets__list.edit-mode .presets__item-load { flex-basis: 50%; } -.presets__list[data-mode="edit"] .presets__item-store { +.presets__list.edit-mode .presets__item-store { display: block; } .presets__item { @@ -41,7 +41,7 @@ .presets__item-store:hover { background-color: var(--btn-active-bg-color); } -.presets__item[data-state="set"] .presets__item-load { +.presets__item.is-set .presets__item-load { background: repeating-linear-gradient( -45deg, transparent, diff --git a/src/js/processors/epg/object3dController.js b/src/js/processors/epg/object3dController.js index 4436ad95..8fcdc8e3 100644 --- a/src/js/processors/epg/object3dController.js +++ b/src/js/processors/epg/object3dController.js @@ -88,6 +88,13 @@ export function createObject3dController(data, that = {}, my = {}) { case actions.DRAG_SELECTED_PROCESSOR: my.updatePosition(state); break; + + case actions.LOAD_PRESET: + const params = state.processors.byId[my.id].params.byId; + updateDuration(params.steps.value, params.rate.value); + updateNecklace(params.steps.value, params.pulses.value, params.rotation.value, params.is_mute.value); + updatePointer(params.is_mute.value); + break; case actions.TOGGLE_CONNECT_MODE: my.updateConnectMode(state.connectModeActive); diff --git a/src/js/processors/epg/processor.js b/src/js/processors/epg/processor.js index 229c24a6..8657791e 100644 --- a/src/js/processors/epg/processor.js +++ b/src/js/processors/epg/processor.js @@ -51,6 +51,12 @@ export function createProcessor(data, my = {}) { } } break; + + case actions.LOAD_PRESET: + updateAllParams(state.processors.byId[my.id].params.byId); + updatePulsesAndRotation(); + updatePattern(true); + break; } }, diff --git a/src/js/processors/euclidfx/object3dController.js b/src/js/processors/euclidfx/object3dController.js index 821417d8..c414a67e 100644 --- a/src/js/processors/euclidfx/object3dController.js +++ b/src/js/processors/euclidfx/object3dController.js @@ -85,6 +85,13 @@ export function createObject3dController(data, that = {}, my = {}) { case actions.DRAG_SELECTED_PROCESSOR: my.updatePosition(state); break; + + case actions.LOAD_PRESET: + const params = state.processors.byId[my.id].params.byId; + updateDuration(params.steps.value, params.rate.value); + updateNecklace(params.steps.value, params.pulses.value, params.rotation.value, params.is_mute.value); + updateRotation(params.rotation.value); + break; case actions.TOGGLE_CONNECT_MODE: my.updateConnectMode(state.connectModeActive); diff --git a/src/js/processors/euclidfx/processor.js b/src/js/processors/euclidfx/processor.js index d31f6f0f..a6f8b70c 100644 --- a/src/js/processors/euclidfx/processor.js +++ b/src/js/processors/euclidfx/processor.js @@ -51,6 +51,13 @@ export function createProcessor(data, my = {}) { } } break; + + case actions.LOAD_PRESET: + updateAllParams(state.processors.byId[my.id].params.byId); + updatePulsesAndRotation(); + updatePattern(true); + updateEffectSettings(); + break; case actions.RECREATE_PARAMETER: if (action.processorID === my.id) { diff --git a/src/js/state/actions.js b/src/js/state/actions.js index 942a5246..955676a7 100644 --- a/src/js/state/actions.js +++ b/src/js/state/actions.js @@ -16,7 +16,8 @@ const ADD_PROCESSOR = 'ADD_PROCESSOR', DISCONNECT_PROCESSORS = 'DISCONNECT_PROCESSORS', DRAG_ALL_PROCESSORS = 'DRAG_ALL_PROCESSORS', DRAG_SELECTED_PROCESSOR = 'DRAG_SELECTED_PROCESSOR', - LIBRARY_DROP = 'LIBRARY_DROP', + LIBRARY_DROP = 'LIBRARY_DROP', + LOAD_PRESET = 'LOAD_PRESET', RECEIVE_MIDI_CC = 'RECEIVE_MIDI_CC', RECREATE_PARAMETER = 'RECREATE_PARAMETER', SELECT_PROCESSOR = 'SELECT_PROCESSOR', @@ -361,6 +362,9 @@ export default { LIBRARY_DROP, libraryDrop: (processorType, x, y) => ({ type: LIBRARY_DROP, processorType, x, y, }), + LOAD_PRESET, + loadPreset: index => ({ type: LOAD_PRESET, index, }), + STORE_PRESET, storePreset: index => { return (dispatch, getState, getActions) => { diff --git a/src/js/state/reducers.js b/src/js/state/reducers.js index 632f9b4c..9fc9c455 100644 --- a/src/js/state/reducers.js +++ b/src/js/state/reducers.js @@ -413,6 +413,45 @@ export default function reduce(state = initialState, action, actions = {}) { y: action.y, }, }; + + case actions.LOAD_PRESET: { + const { index, } = action; + const { presets, processors, } = state; + const preset = presets[index]; + return { + ...state, + processors: { + allIds: [ ...processors.allIds ], + byId: processors.allIds.reduce((procAcc, processorId) => { + const processor = processors.byId[processorId]; + const processorPreset = preset[processorId]; + return { + ...procAcc, + [processorId]: { + ...processor, + params: { + allIds: [ ...processor.params.allIds ], + byId: processor.params.allIds.reduce((paramAcc, paramId) => { + const param = processor.params.byId[paramId]; + let newValue = param.value; + if (processorPreset && processorPreset.hasOwnProperty(paramId) && paramId !== 'name') { + newValue = processorPreset[paramId]; + } + return { + ...paramAcc, + [paramId]: { + ...param, + value: newValue, + }, + } + }, {}), + }, + }, + } + }, {}), + }, + }; + } case actions.STORE_PRESET: { const { index, preset, } = action; diff --git a/src/js/view/presets.js b/src/js/view/presets.js index 19dc4724..4acec7ec 100644 --- a/src/js/view/presets.js +++ b/src/js/view/presets.js @@ -14,6 +14,9 @@ function addEventListeners() { editEl.addEventListener('change', handleEditClick); } +/** + * Crete the 16 list items. + */ function build() { const template = document.querySelector('#template-presets-item'); for (let i = 0, n = numPresets; i < n; i++) { @@ -63,13 +66,29 @@ function handleStateChanges(e) { } } +/** + * Toggle snapshot edit mode. + * @param {Object} state App state. + */ function setPresetEditMode(state) { editEl.checked = state.presetsEditModeActive; - listEl.dataset.mode = state.presetsEditModeActive ? 'edit' : ''; + if (state.presetsEditModeActive) { + listEl.classList.add('edit-mode'); + } else { + listEl.classList.remove('edit-mode'); + } } +/** + * + * @param {Object} state App state. + */ function updateList(state) { for (let i = 0, n = numPresets; i < n; i++) { - listEl.children[i].dataset.state = state.presets[i] ? 'set' : ''; + if (state.presets[i]) { + listEl.children[i].classList.add('is-set'); + } else { + listEl.children[i].classList.remove('is-set'); + } } } diff --git a/src/js/view/setting/base.js b/src/js/view/setting/base.js index 90320cc7..715194b2 100644 --- a/src/js/view/setting/base.js +++ b/src/js/view/setting/base.js @@ -43,6 +43,10 @@ export default function createBaseSettingView(specs, my) { my.setValue(state.processors.byId[my.processorID].params.byId[my.key].value); } break; + + case actions.LOAD_PRESET: + my.setValue(state.processors.byId[my.processorID].params.byId[my.key].value); + break; case actions.RECREATE_PARAMETER: if (action.processorID === my.processorID && From 5f92559f7f33621e058e1ef99eff096c915bc78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 17 Oct 2019 12:24:18 +0200 Subject: [PATCH 036/131] Presets fix and styling. --- src/css/presets.css | 29 +++++++++++-------- .../processors/euclidfx/object3dController.js | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/css/presets.css b/src/css/presets.css index 13590c37..4df48dab 100644 --- a/src/css/presets.css +++ b/src/css/presets.css @@ -37,23 +37,28 @@ padding: 20px 10px; text-align: center; } +.presets__item-store .presets__item-label { + background-color: var(--btn-color); + color: var(--input-bg-color); + padding: 0 5px 2px; +} .presets__item-load:hover, .presets__item-store:hover { background-color: var(--btn-active-bg-color); } .presets__item.is-set .presets__item-load { - background: repeating-linear-gradient( - -45deg, - transparent, - transparent 10px, - var(--panel-bg-color) 10px, - var(--panel-bg-color) 20px - ), - linear-gradient( - to bottom, - var(--btn-active-bg-color), - var(--btn-active-bg-color) - ); + background: repeating-linear-gradient( + -45deg, + transparent, + transparent 10px, + var(--panel-bg-color) 10px, + var(--panel-bg-color) 20px + ), + linear-gradient( + to bottom, + var(--btn-active-bg-color), + var(--btn-active-bg-color) + ); } .presets__item-label { font-size: 1 rem; diff --git a/src/js/processors/euclidfx/object3dController.js b/src/js/processors/euclidfx/object3dController.js index c414a67e..935e9a6f 100644 --- a/src/js/processors/euclidfx/object3dController.js +++ b/src/js/processors/euclidfx/object3dController.js @@ -89,7 +89,7 @@ export function createObject3dController(data, that = {}, my = {}) { case actions.LOAD_PRESET: const params = state.processors.byId[my.id].params.byId; updateDuration(params.steps.value, params.rate.value); - updateNecklace(params.steps.value, params.pulses.value, params.rotation.value, params.is_mute.value); + updateNecklace(params.steps.value, params.pulses.value, params.rotation.value); updateRotation(params.rotation.value); break; From 22ecfb442f51c4e55128daf423706e3284b3314a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 17 Oct 2019 12:40:32 +0200 Subject: [PATCH 037/131] Bug fixed in selecting processors. --- src/js/state/reducers.js | 6 +++--- src/js/view/panels.js | 6 +++--- src/js/webgl/canvas3d.js | 4 ++-- src/js/webgl/object3dControllerBase.js | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/js/state/reducers.js b/src/js/state/reducers.js index 3e127d67..a6f5b95d 100644 --- a/src/js/state/reducers.js +++ b/src/js/state/reducers.js @@ -121,7 +121,7 @@ export default function reduce(state = initialState, action, actions = {}) { // select the next processor, if any, or a previous one let newIndex; - if (newState.selectedID === action.id && newState.processors.allIds.length) { + if (newState.selectedId === action.id && newState.processors.allIds.length) { if (newState.processors.allIds[index]) { newIndex = index; } else if (index > 0) { @@ -129,7 +129,7 @@ export default function reduce(state = initialState, action, actions = {}) { } else { newIndex = 0; } - newState.selectedID = newState.processors.allIds[newIndex]; + newState.selectedId = newState.processors.allIds[newIndex]; } // reorder the processors @@ -146,7 +146,7 @@ export default function reduce(state = initialState, action, actions = {}) { processors: { allIds: [ ...state.processors.allIds ], byId: Object.values(state.processors.byId).reduce((accumulator, processor) => { - if (processor.id === state.selectedID) { + if (processor.id === state.selectedId) { accumulator[processor.id] = { ...processor, positionX: action.x, positionY: action.y, positionZ: action.z }; } else { accumulator[processor.id] = { ...processor }; diff --git a/src/js/view/panels.js b/src/js/view/panels.js index 97027cff..5c54dd34 100644 --- a/src/js/view/panels.js +++ b/src/js/view/panels.js @@ -33,7 +33,7 @@ function addEventListeners() { * @param {Object} processor MIDI processor to control with the settings. */ function createSettingsViews(state) { - const { processors, selectedID, } = state; + const { processors, selectedId, } = state; processors.allIds.forEach((id, i) => { const processorData = processors.byId[id]; let exists = settingsViews.some(view => view.getID() === id); @@ -43,7 +43,7 @@ function createSettingsViews(state) { data: processorData, parentEl: editContentEl, template: settingsHTML, - isSelected: selectedID === processorData.id + isSelected: selectedId === processorData.id })); } }); @@ -84,7 +84,7 @@ function handleStateChanges(e) { case actions.DELETE_PROCESSOR: deleteSettingsView(action.id); showPanels(state); - selectSettingsView(state.selectedID); + selectSettingsView(state.selectedId); renderLayout(); break; diff --git a/src/js/webgl/canvas3d.js b/src/js/webgl/canvas3d.js index 304be74c..1debf41d 100644 --- a/src/js/webgl/canvas3d.js +++ b/src/js/webgl/canvas3d.js @@ -104,7 +104,7 @@ function clearProcessorViews() { * @param {Array} data Array of current processors' state. */ function createProcessorViews(state) { - const { connectModeActive, processors, selectedID, } = state; + const { connectModeActive, processors, selectedId, } = state; const isConnectMode = connectModeActive; for (let id of processors.allIds) { const processorData = processors.byId[id]; @@ -122,7 +122,7 @@ function createProcessorViews(state) { // create controller for the object const controllerModule = getProcessorData(type, 'object3dController'); const controller = controllerModule.createObject3dController({ object3d, processorData, isConnectMode, }); - controller.updateSelectCircle(selectedID); + controller.updateSelectCircle(selectedId); controllers.push(controller); } }; diff --git a/src/js/webgl/object3dControllerBase.js b/src/js/webgl/object3dControllerBase.js index 3ec23bf4..e2bea00e 100644 --- a/src/js/webgl/object3dControllerBase.js +++ b/src/js/webgl/object3dControllerBase.js @@ -21,7 +21,7 @@ export default function createObject3dControllerBase(data, that, my) { * Set the 3D pattern's position in the scene. */ updatePosition = function(state) { - if (state.selectedID === my.id) { + if (state.selectedId === my.id) { const data = state.processors.byId[my.id]; my.object3d.position.set(data.positionX, data.positionY, data.positionZ); } From 127e9e53847c3f0989c611b86c4717f09a590799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 17 Oct 2019 14:32:01 +0200 Subject: [PATCH 038/131] Selected preset indication in panel. --- src/css/presets.css | 27 +++++++++++++++++++++++++++ src/js/state/reducers.js | 7 +++++++ src/js/view/presets.js | 28 ++++++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/css/presets.css b/src/css/presets.css index 4df48dab..b6e5194c 100644 --- a/src/css/presets.css +++ b/src/css/presets.css @@ -26,6 +26,20 @@ display: flex; flex-basis: 25%; } +.presets__item.is-set.is-selected .presets__item-load { + background: repeating-linear-gradient( + -45deg, + transparent, + transparent 10px, + var(--panel-bg-color) 10px, + var(--panel-bg-color) 20px + ), + linear-gradient( + to bottom, + var(--btn-color), + var(--btn-color) + ); +} .presets__item-load { flex-basis: 100%; padding: 20px 10px; @@ -59,6 +73,19 @@ var(--btn-active-bg-color), var(--btn-active-bg-color) ); +}.presets__item.is-set .presets__item-load:hover { + background: repeating-linear-gradient( + -45deg, + transparent, + transparent 10px, + var(--panel-bg-color) 10px, + var(--panel-bg-color) 20px + ), + linear-gradient( + to bottom, + var(--btn-color), + var(--btn-color) + ); } .presets__item-label { font-size: 1 rem; diff --git a/src/js/state/reducers.js b/src/js/state/reducers.js index 9fc9c455..c544bf8e 100644 --- a/src/js/state/reducers.js +++ b/src/js/state/reducers.js @@ -28,6 +28,7 @@ const initialState = { allIds: [], byId: {}, }, + presetIndex: null, presets: [], presetsEditModeActive: false, processors: { @@ -177,6 +178,7 @@ export default function reduce(state = initialState, action, actions = {}) { case actions.CHANGE_PARAMETER: newState = { ...state, + presetIndex: null, processors: { byId: { ...state.processors.byId }, allIds: [ ...state.processors.allIds ] @@ -418,8 +420,12 @@ export default function reduce(state = initialState, action, actions = {}) { const { index, } = action; const { presets, processors, } = state; const preset = presets[index]; + if (!preset) { + return state; + } return { ...state, + presetIndex: index, processors: { allIds: [ ...processors.allIds ], byId: processors.allIds.reduce((procAcc, processorId) => { @@ -459,6 +465,7 @@ export default function reduce(state = initialState, action, actions = {}) { presets[index] = preset; return { ...state, + presetIndex: index, presets, }; } diff --git a/src/js/view/presets.js b/src/js/view/presets.js index 4acec7ec..91b40d4f 100644 --- a/src/js/view/presets.js +++ b/src/js/view/presets.js @@ -3,6 +3,7 @@ import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; const listEl = document.querySelector('.presets__list'); const editEl = document.querySelector('.presets__edit'); const numPresets = 16; +let selectedListEl = null; export function setup() { addEventListeners(); @@ -35,11 +36,11 @@ function build() { } function handleLoadClick(e) { - dispatch(getActions().loadPreset(e.currentTarget.parentNode.dataset.index)); + dispatch(getActions().loadPreset(parseInt(e.currentTarget.parentNode.dataset.index, 10))); } function handleStoreClick(e) { - dispatch(getActions().storePreset(e.currentTarget.parentNode.dataset.index)); + dispatch(getActions().storePreset(parseInt(e.currentTarget.parentNode.dataset.index))); } function handleEditClick() { @@ -58,10 +59,18 @@ function handleStateChanges(e) { case actions.STORE_PRESET: setPresetEditMode(state); updateList(state); + showSelectedIndex(state); break; case actions.TOGGLE_PRESETS_MODE: setPresetEditMode(state); + showSelectedIndex(state); + break; + + case actions.LOAD_PRESET: + case actions.CHANGE_PARAMETER: + case actions.TOGGLE_PANEL: + showSelectedIndex(state); break; } } @@ -79,6 +88,21 @@ function setPresetEditMode(state) { } } +function showSelectedIndex(state) { + const { presetIndex, showPresetsPanel, } = state; + console.log(typeof presetIndex === 'number', presetIndex, showPresetsPanel); + if (showPresetsPanel) { + if (selectedListEl){ + selectedListEl.classList.remove('is-selected'); + selectedListEl = null; + } + if (typeof presetIndex === 'number') { + selectedListEl = listEl.children[presetIndex]; + selectedListEl.classList.add('is-selected'); + } + } +} + /** * * @param {Object} state App state. From 5d1e664b6eaf109d835a8dbb012c86674aa80bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Thu, 17 Oct 2019 14:40:09 +0200 Subject: [PATCH 039/131] Cleanup. --- src/js/core/processor-loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/core/processor-loader.js b/src/js/core/processor-loader.js index 61c31b5f..bf02f025 100644 --- a/src/js/core/processor-loader.js +++ b/src/js/core/processor-loader.js @@ -60,7 +60,7 @@ export function preloadProcessors() { utils: results[5], }; }); - console.log('Processor data preloaded.', processors); + console.log('Processor data preloaded.'); resolve(); }); }); From 73e8a252b558a7260ee8cfe7d895dda50de4a52e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Mon, 21 Oct 2019 11:30:24 +0200 Subject: [PATCH 040/131] Midi module refactoring complete. --- src/js/main.js | 3 +- src/js/midi/midi.js | 397 +++++++++++++++++++---------------------- src/js/midi/network.js | 2 +- 3 files changed, 183 insertions(+), 219 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index caecd96c..94b24cd8 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -17,7 +17,7 @@ */ import { dispatch, getActions, getState, persist, } from './state/store.js'; -import { accessMidi } from './midi/midi.js'; +import { accessMidi, listenToMIDIPorts } from './midi/midi.js'; import { preloadProcessors } from './core/processor-loader.js'; import { setup as setupCanvas3d } from './webgl/canvas3d.js'; import { setup as setupConnections3d } from './webgl/connections3d.js'; @@ -46,6 +46,7 @@ async function main() { setupSequencer(); persist(); + listenToMIDIPorts(); dispatch(getActions().setProject(getState())); } diff --git a/src/js/midi/midi.js b/src/js/midi/midi.js index fe0dc4ec..fd53ffed 100644 --- a/src/js/midi/midi.js +++ b/src/js/midi/midi.js @@ -1,6 +1,12 @@ +import { dispatch, getActions, STATE_CHANGE, } from '../state/store.js'; -let midiAccess = null; +let midiAccess = null, + syncListeners, + remoteListeners; +/** + * Request access to the MIDI devices. + */ export function accessMidi() { return new Promise((resolve, reject) => { if (navigator.requestMIDIAccess) { @@ -8,7 +14,7 @@ export function accessMidi() { .then( access => { console.log('MIDI enabled.'); - midiAccess = access; + midiAccess = access; resolve(); }, () => { @@ -21,230 +27,187 @@ export function accessMidi() { }); } +/** + * Listen to MIDI events. + * @param {Object} midiAccessObj MidiAccess object. + */ +export function listenToMIDIPorts() { + const inputs = midiAccess.inputs.values(); + + for (let port = inputs.next(); port && !port.done; port = inputs.next()) { + port.value.onmidimessage = onMIDIMessage; + } + midiAccess.onstatechange = onAccessStateChange; -export default function createMIDI(specs) { - var that, - store = specs.store, - syncListeners = [], - remoteListeners = [], - - init = function() { - document.addEventListener(store.STATE_CHANGE, (e) => { - switch (e.detail.action.type) { - - case e.detail.actions.TOGGLE_MIDI_PREFERENCE: - updateMIDISyncListeners(e.detail.state.ports); - updateMIDIRemoteListeners(e.detail.state.ports); - break; - - case e.detail.actions.CREATE_MIDI_PORT: - case e.detail.actions.UPDATE_MIDI_PORT: - updateMIDISyncListeners(e.detail.state.ports); - updateMIDIRemoteListeners(e.detail.state.ports); - break; - } - }); - }, - - connect = function() { - return new Promise((resolve, reject) => { - requestAccess(resolve, reject, false); - }); - }, - - /** - * Request system for access to MIDI ports. - * @param {function} successCallback - * @param {function} failureCallback - * @param {boolean} sysex True if sysex data must be included. - */ - requestAccess = function(successCallback, failureCallback, sysex) { - if (navigator.requestMIDIAccess) { - navigator.requestMIDIAccess({ - sysex: !!sysex - }).then(function(_midiAccess) { - onAccessSuccess(_midiAccess); - successCallback(); - }, function() { - failureCallback('Request for MIDI access failed.'); - }); - } else { - failureCallback('Web MIDI API not available.'); - } - }, - - /** - * MIDI access request failed. - * @param {String} errorMessage - */ - onAccessFailure = function(errorMessage) { - console.log(errorMessage); - }, - - /** - * MIDI access request succeeded. - * @param {Object} midiAccessObj MidiAccess object. - */ - onAccessSuccess = function(_midiAccess) { - console.log('MIDI enabled.'); - midiAccess = _midiAccess; - - const inputs = midiAccess.inputs.values(); - const outputs = midiAccess.outputs.values(); - - for (let port = inputs.next(); port && !port.done; port = inputs.next()) { - port.value.onmidimessage = onMIDIMessage; - } - - midiAccess.onstatechange = onAccessStateChange; - }, - - /** - * MIDIAccess object statechange handler. - * If the change is the addition of a new port, create a port module. - * This handles MIDI devices that are connected after the app initialisation. - * Disconnected or reconnected ports are handled by the port modules. - * - * If this is - * @param {Object} e MIDIConnectionEvent object. - */ - onAccessStateChange = function(e) { - - // start listening to the new port - e.port.onmidimessage = onMIDIMessage; - - store.dispatch(store.getActions().midiAccessChange(e.port)); - }, - - /** - * Listen to enabled MIDI input ports. - */ - updateMIDISyncListeners = function(ports) { - syncListeners = []; - ports.allIds.forEach(id => { - const port = ports.byId[id]; - if (port.syncEnabled) { - syncListeners.push(port.id); - } - }); - }, - - /** - * Listen to enabled MIDI input ports. - */ - updateMIDIRemoteListeners = function(ports) { - remoteListeners = []; - ports.allIds.forEach(id => { - const port = ports.byId[id]; - if (port.remoteEnabled) { - remoteListeners.push(port.id); - } - }); - }, - - /** - * Handler for all incoming MIDI messages. - * @param {Object} e MIDIMessageEvent. - */ - onMIDIMessage = function(e) { - // console.log(e.data[0] & 0xf0, e.data[0] & 0x0f, e.target.id, e.data[0], e.data[1], e.data[2]); - switch (e.data[0] & 0xf0) { - case 240: - onSystemRealtimeMessage(e); - break; - case 176: // CC - onControlChangeMessage(e); - break; - case 144: // note on - case 128: // note off - // onNoteMessage(e); - break; - } - }, - - /** - * Eventlistener for incoming MIDI messages. - * data[1] and data[2] are undefined, - * for e.data[0] & 0xf: - * 8 = clock, 248 (11110000 | 00000100) - * 10 = start - * 11 = continue - * 12 = stop - * @see https://www.w3.org/TR/webmidi/#idl-def-MIDIMessageEvent - * @see https://www.midi.org/specifications/item/table-1-summary-of-midi-message - * @param {Object} e MIDIMessageEvent. - */ - onSystemRealtimeMessage = function(e) { - if (syncListeners.indexOf(e.target.id) > -1) { - switch (e.data[0]) { - case 248: // clock - // not implemented - break; - case 250: // start - store.dispatch(store.getActions().setTransport('play')); - break; - case 251: // continue - store.dispatch(store.getActions().setTransport('play')); - break; - case 252: // stop - store.dispatch(store.getActions().setTransport('pause')); - break; - } - } - }, - - /** - * MIDI Continuous Control message handler. - * @param {Object} e MIDIMessageEvent. - */ - onControlChangeMessage = function(e) { - if (remoteListeners.indexOf(e.target.id) > -1) { - store.dispatch(store.getActions().receiveMIDIControlChange(e.data)); - } - }; - - that = specs.that; - - init(); - - that.connect = connect; - return that; + addEventListeners(); +} + +/** + * Get all MIDI input and output ports. + * @returns {Array} Array of all ports. + */ +export function getAllMIDIPorts() { + const allPorts = []; + const inputs = midiAccess.inputs.values(); + const outputs = midiAccess.outputs.values(); + + for (let port = inputs.next(); port && !port.done; port = inputs.next()) { + allPorts.push(port.value); + } + + for (let port = outputs.next(); port && !port.done; port = outputs.next()) { + allPorts.push(port.value); + } + + return allPorts; } +/** + * Get a specific MIDI port by its ID. + * @param {String} id MIDI port ID. + */ export function getMIDIPortByID(id) { - const inputs = midiAccess.inputs.values(); - const outputs = midiAccess.outputs.values(); + const inputs = midiAccess.inputs.values(); + const outputs = midiAccess.outputs.values(); + + for (let port = inputs.next(); port && !port.done; port = inputs.next()) { + if (port.value.id === id) { + return port.value; + } + } + + for (let port = outputs.next(); port && !port.done; port = outputs.next()) { + if (port.value.id === id) { + return port.value; + } + } +} - for (let port = inputs.next(); port && !port.done; port = inputs.next()) { - if (port.value.id === id) { - return port.value; - } - } - - for (let port = outputs.next(); port && !port.done; port = outputs.next()) { - if (port.value.id === id) { - return port.value; - } - } +/** + * Listen to MIDI events. + * @param {Object} midiAccessObj MidiAccess object. + */ +function addEventListeners() { + document.addEventListener(STATE_CHANGE, handleStateChanges); } /** - * Get all MIDI input and output ports. - * @returns {Array} Array of all ports. + * Handle state changes. + * @param {Object} e Custom event. */ -export function getAllMIDIPorts() { - const allPorts = []; - const inputs = midiAccess.inputs.values(); - const outputs = midiAccess.outputs.values(); +function handleStateChanges(e) { + const { state, action, actions, } = e.detail; + switch (action.type) { + case actions.TOGGLE_MIDI_PREFERENCE: + updateMIDISyncListeners(state.ports); + updateMIDIRemoteListeners(state.ports); + break; + + case actions.CREATE_MIDI_PORT: + case actions.UPDATE_MIDI_PORT: + updateMIDISyncListeners(state.ports); + updateMIDIRemoteListeners(state.ports); + break; + } +} - for (let port = inputs.next(); port && !port.done; port = inputs.next()) { - allPorts.push(port.value); - } - - for (let port = outputs.next(); port && !port.done; port = outputs.next()) { - allPorts.push(port.value); - } +/** + * MIDIAccess object statechange handler. + * If the change is the addition of a new port, create a port module. + * This handles MIDI devices that are connected after the app initialisation. + * Disconnected or reconnected ports are handled by the port modules. + * + * @param {Object} e MIDIConnectionEvent object. + */ +function onAccessStateChange(e) { + e.port.onmidimessage = onMIDIMessage; + dispatch(getActions().midiAccessChange(e.port)); +} + +/** + * MIDI Continuous Control message handler. + * @param {Object} e MIDIMessageEvent. + */ +function onControlChangeMessage(e) { + if (remoteListeners.indexOf(e.target.id) > -1) { + dispatch(getActions().receiveMIDIControlChange(e.data)); + } +} + +/** + * Handler for all incoming MIDI messages. + * @param {Object} e MIDIMessageEvent. + */ +function onMIDIMessage(e) { + // console.log(e.data[0] & 0xf0, e.data[0] & 0x0f, e.target.id, e.data[0], e.data[1], e.data[2]); + switch (e.data[0] & 0xf0) { + case 240: + onSystemRealtimeMessage(e); + break; + case 176: // CC + onControlChangeMessage(e); + break; + case 144: // note on + case 128: // note off + // onNoteMessage(e); + break; + } +} - return allPorts; -} \ No newline at end of file +/** + * Eventlistener for incoming MIDI messages. + * data[1] and data[2] are undefined, + * for e.data[0] & 0xf: + * 8 = clock, 248 (11110000 | 00000100) + * 10 = start + * 11 = continue + * 12 = stop + * @see https://www.w3.org/TR/webmidi/#idl-def-MIDIMessageEvent + * @see https://www.midi.org/specifications/item/table-1-summary-of-midi-message + * @param {Object} e MIDIMessageEvent. + */ +function onSystemRealtimeMessage(e) { + if (syncListeners.indexOf(e.target.id) > -1) { + switch (e.data[0]) { + case 248: // clock + // TODO: Add remote MIDI clock sync. + break; + case 250: // start + dispatch(getActions().setTransport('play')); + break; + case 251: // continue + dispatch(getActions().setTransport('play')); + break; + case 252: // stop + dispatch(getActions().setTransport('pause')); + break; + } + } +} + +/** + * Listen to enabled MIDI input ports. + */ +function updateMIDISyncListeners(ports) { + syncListeners = []; + ports.allIds.forEach(portId => { + const { id, syncEnabled, } = ports.byId[portId]; + if (syncEnabled) { + syncListeners.push(id); + } + }); +} + +/** + * Listen to enabled MIDI input ports. + */ +function updateMIDIRemoteListeners(ports) { + remoteListeners = []; + ports.allIds.forEach(portId => { + const { id, remoteEnabled, } = ports.byId[portId]; + if (remoteEnabled) { + remoteListeners.push(id); + } + }); +} diff --git a/src/js/midi/network.js b/src/js/midi/network.js index 17b0811f..cee7248b 100644 --- a/src/js/midi/network.js +++ b/src/js/midi/network.js @@ -111,7 +111,7 @@ function disconnectProcessors(state) { /** * Handle state changes. - * @param {Object} e + * @param {Object} e Custom event. */ function handleStateChanges(e) { const { state, action, actions, } = e.detail; From 4451f15b50d5bf9be0bf6f44a2644bbc4fa72c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wouter=20Hisschemo=CC=88ller?= Date: Tue, 22 Oct 2019 10:59:47 +0200 Subject: [PATCH 041/131] Presets renamed to snapshots. --- src/css/{presets.css => snapshots.css} | 44 +++---- src/css/style.css | 2 +- src/index.html | 32 ++--- src/js/main.js | 4 +- src/js/processors/epg/object3dController.js | 2 +- src/js/processors/epg/processor.js | 2 +- .../processors/euclidfx/object3dController.js | 2 +- src/js/processors/euclidfx/processor.js | 2 +- src/js/state/actions.js | 22 ++-- src/js/state/reducers.js | 44 +++---- src/js/view/controls.js | 8 +- src/js/view/panels.js | 4 +- src/js/view/presets.js | 118 ------------------ src/js/view/setting/base.js | 2 +- src/js/view/snapshots.js | 118 ++++++++++++++++++ 15 files changed, 203 insertions(+), 203 deletions(-) rename src/css/{presets.css => snapshots.css} (68%) delete mode 100644 src/js/view/presets.js create mode 100644 src/js/view/snapshots.js diff --git a/src/css/presets.css b/src/css/snapshots.css similarity index 68% rename from src/css/presets.css rename to src/css/snapshots.css index b6e5194c..ee6f3e29 100644 --- a/src/css/presets.css +++ b/src/css/snapshots.css @@ -1,32 +1,32 @@ /************************ - * presets + * snapshots ************************/ - .presets { + .snapshots { background-color: var(--panel-bg-color); position: relative; width: 340px; } -.presets .panel__viewport { +.snapshots .panel__viewport { background-color: var(--panel-bg-color); } -.presets__list { +.snapshots__list { display: flex; flex-wrap: wrap-reverse; list-style: none; } -.presets__list.edit-mode .presets__item-load { +.snapshots__list.edit-mode .snapshots__item-load { flex-basis: 50%; } -.presets__list.edit-mode .presets__item-store { +.snapshots__list.edit-mode .snapshots__item-store { display: block; } -.presets__item { +.snapshots__item { cursor: pointer; display: flex; flex-basis: 25%; } -.presets__item.is-set.is-selected .presets__item-load { +.snapshots__item.is-set.is-selected .snapshots__item-load { background: repeating-linear-gradient( -45deg, transparent, @@ -40,27 +40,27 @@ var(--btn-color) ); } -.presets__item-load { +.snapshots__item-load { flex-basis: 100%; padding: 20px 10px; text-align: center; } -.presets__item-store { +.snapshots__item-store { display: none; flex-basis: 50%; padding: 20px 10px; text-align: center; } -.presets__item-store .presets__item-label { +.snapshots__item-store .snapshots__item-label { background-color: var(--btn-color); color: var(--input-bg-color); padding: 0 5px 2px; } -.presets__item-load:hover, -.presets__item-store:hover { +.snapshots__item-load:hover, +.snapshots__item-store:hover { background-color: var(--btn-active-bg-color); } -.presets__item.is-set .presets__item-load { +.snapshots__item.is-set .snapshots__item-load { background: repeating-linear-gradient( -45deg, transparent, @@ -73,7 +73,7 @@ var(--btn-active-bg-color), var(--btn-active-bg-color) ); -}.presets__item.is-set .presets__item-load:hover { +}.snapshots__item.is-set .snapshots__item-load:hover { background: repeating-linear-gradient( -45deg, transparent, @@ -87,27 +87,27 @@ var(--btn-color) ); } -.presets__item-label { +.snapshots__item-label { font-size: 1 rem; } -.presets__edit {} -.presets__edit-check { +.snapshots__edit {} +.snapshots__edit-check { position: absolute; margin-left: -9999px; visibility: hidden; } -.presets__edit-label { +.snapshots__edit-label { cursor: pointer; display: block; padding: 10px; text-align: center; } -.presets__edit-label:hover { +.snapshots__edit-label:hover { background-color: var(--btn-active-bg-color); color: var(--btn-hover-color); } -.presets__edit-check:checked + .presets__edit-label { +.snapshots__edit-check:checked + .snapshots__edit-label { color: var(--btn-active-color); } -.presets__edit-check:checked + .presets__edit-label:hover { +.snapshots__edit-check:checked + .snapshots__edit-label:hover { } diff --git a/src/css/style.css b/src/css/style.css index fb9dfc14..e2995ced 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -2,7 +2,7 @@ @import 'library.css'; @import 'preferences.css'; @import 'rangeslider.css'; -@import 'presets.css'; +@import 'snapshots.css'; @import 'remote.css'; @import 'settings.css'; diff --git a/src/index.html b/src/index.html index 55cacf2e..ddc0d5ec 100644 --- a/src/index.html +++ b/src/index.html @@ -42,9 +42,9 @@ - -