From 276f5432c3136a5851809a16caefe7e9173e47a1 Mon Sep 17 00:00:00 2001 From: Bamdad Sabbagh Date: Sun, 14 Nov 2021 22:55:34 +0100 Subject: [PATCH 1/3] perf(UI): rename `neuron-card` to `select-card` --- index.html | 4 +- src/app/devices/controller.device.ts | 30 ++++++------- src/app/ui/network.ui.ts | 6 +-- .../{neuron-card.ui.ts => select-card.ui.ts} | 44 +++++++++---------- src/app/ui/ui.ts | 4 +- styles.css | 10 ++--- 6 files changed, 49 insertions(+), 49 deletions(-) rename src/app/ui/{neuron-card.ui.ts => select-card.ui.ts} (86%) diff --git a/index.html b/index.html index 297c3ad..8d48e03 100644 --- a/index.html +++ b/index.html @@ -793,8 +793,8 @@
Novation Launch Control XL (Controller)
- -
+ +
Source Weight diff --git a/src/app/devices/controller.device.ts b/src/app/devices/controller.device.ts index 652e341..80cda83 100644 --- a/src/app/devices/controller.device.ts +++ b/src/app/devices/controller.device.ts @@ -1,7 +1,7 @@ import { devicePrototype } from './device/device.prototype'; import { rangeMap } from '../utils/range-map'; import { playgroundFacade } from '../facades/playground.facade'; -import { neuronCardUi } from '../ui/neuron-card.ui'; +import { selectCardUi } from '../ui/select-card.ui'; import { networkState } from '../state/network.state'; import { mappingsState } from '../state/mappings.state'; import { mappingsUi } from '../ui/mappings.ui'; @@ -264,15 +264,15 @@ controllerDevice.attachControlsToNeuron = function (selectedNode: number): void 0, 127, 0, - neuronCardUi.options.learningRate.length - 1, + selectCardUi.options.learningRate.length - 1, ).toString (), ); - const learningRate = neuronCardUi.options.learningRate[learningRateOptionIndex]; + const learningRate = selectCardUi.options.learningRate[learningRateOptionIndex]; if (learningRate !== links[index].source.learningRate) { networkState.updateSourceLearningRate (index, learningRate); - neuronCardUi.setLearningRate (index, learningRate); + selectCardUi.setLearningRate (index, learningRate); playgroundFacade.updateUI (); } } @@ -292,15 +292,15 @@ controllerDevice.attachControlsToNeuron = function (selectedNode: number): void 0, 127, 0, - neuronCardUi.options.activation.length - 1, + selectCardUi.options.activation.length - 1, ).toString (), ); - const activation = neuronCardUi.options.activation[activationOptionIndex]; + const activation = selectCardUi.options.activation[activationOptionIndex]; if (activation !== links[index].source.activation.name) { networkState.updateSourceActivation (index, activation); - neuronCardUi.setActivation (index, activation); + selectCardUi.setActivation (index, activation); playgroundFacade.updateUI (); } } @@ -322,15 +322,15 @@ controllerDevice.attachControlsToNeuron = function (selectedNode: number): void 0, 127, 0, - neuronCardUi.options.regularization.length - 1, + selectCardUi.options.regularization.length - 1, ).toString (), ); - const regularization = neuronCardUi.options.regularization[regularizationOptionIndex]; + const regularization = selectCardUi.options.regularization[regularizationOptionIndex]; if (regularization !== links[index].source.regularization.name) { networkState.updateSourceRegularization (index, regularization); - neuronCardUi.setRegularization (index, regularization); + selectCardUi.setRegularization (index, regularization); playgroundFacade.updateUI (); } } @@ -342,15 +342,15 @@ controllerDevice.attachControlsToNeuron = function (selectedNode: number): void 0, 127, 0, - neuronCardUi.options.regularizationRate.length - 1, + selectCardUi.options.regularizationRate.length - 1, ).toString (), ); - const regularizationRate = neuronCardUi.options.regularizationRate[regularizationRateOptionIndex]; + const regularizationRate = selectCardUi.options.regularizationRate[regularizationRateOptionIndex]; if (regularizationRate !== links[index].source.regularizationRate) { networkState.updateSourceRegularizationRate (index, regularizationRate); - neuronCardUi.setRegularizationRate (index, regularizationRate); + selectCardUi.setRegularizationRate (index, regularizationRate); playgroundFacade.updateUI (); } } @@ -394,7 +394,7 @@ controllerDevice.attachControlsToNeuron = function (selectedNode: number): void if (links[index].hasSnapped) { networkState.setWeight (index, value); - neuronCardUi.setWeight (index, value); + selectCardUi.setWeight (index, value); playgroundFacade.updateWeightsUI (); this.playNote ({ note: this.settings.outputByInput[inputNote], @@ -413,7 +413,7 @@ controllerDevice.attachControlsToNeuron = function (selectedNode: number): void const value = rangeMap (e.value, 0, 127, -1, 1); if (value.toFixed (2) !== links[index].source.bias.toFixed (2)) { links[index].source.bias = value; - neuronCardUi.setBias (index, value); + selectCardUi.setBias (index, value); playgroundFacade.updateBiasesUI (); } } diff --git a/src/app/ui/network.ui.ts b/src/app/ui/network.ui.ts index 8125ebb..ada0a4d 100644 --- a/src/app/ui/network.ui.ts +++ b/src/app/ui/network.ui.ts @@ -2,7 +2,7 @@ import * as d3 from 'd3'; import { networkState } from '../state/network.state'; import { selectorDevice } from '../devices/selector.device'; import { playgroundFacade } from '../facades/playground.facade'; -import { neuronCardUi } from './neuron-card.ui'; +import { selectCardUi } from './select-card.ui'; import { controllerDevice } from '../devices/controller.device'; /** @@ -22,7 +22,7 @@ networkUi.toggleNeuron = function (index: number) { networkState.toggleNeuron (index); - neuronCardUi.updateCard (); + selectCardUi.updateCard (); playgroundFacade.updateWeightsUI (); selectorDevice.setNeuronLight ({ @@ -66,7 +66,7 @@ networkUi.toggleNodeSelection = function (nodeIndex: number, isSelected: boolean const canvas = d3.select (`#canvas-${nodeIndex}`); canvas.classed ('selected', isSelected); - neuronCardUi.updateCard (); + selectCardUi.updateCard (); selectorDevice.setNeuronLight ({ index: nodeIndex, isSelected }); controllerDevice.onSelectionEvent (); diff --git a/src/app/ui/neuron-card.ui.ts b/src/app/ui/select-card.ui.ts similarity index 86% rename from src/app/ui/neuron-card.ui.ts rename to src/app/ui/select-card.ui.ts index e81d008..182bfb7 100644 --- a/src/app/ui/neuron-card.ui.ts +++ b/src/app/ui/select-card.ui.ts @@ -2,10 +2,10 @@ import * as d3 from 'd3'; import { playgroundFacade } from '../facades/playground.facade'; import { networkState } from '../state/network.state'; -export const neuronCardUi = Object.create (null); +export const selectCardUi = Object.create (null); -neuronCardUi.nodeSelectors = { - node: '#neuron-card', +selectCardUi.nodeSelectors = { + node: '#select-card', row: 'div.row:not(.header)', weight: 'input.weight', bias: 'input.bias', @@ -15,20 +15,20 @@ neuronCardUi.nodeSelectors = { regularizationRate: 'div.regularization-rate', }; -neuronCardUi.placeholders = { +selectCardUi.placeholders = { undefined: 'ΓΈ', multi: 'multi.', disabled: 'disabled', }; -neuronCardUi.options = { +selectCardUi.options = { learningRate: [0.00001, 0.0001, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10], activation: ['relu', 'tanh', 'sigmoid', 'linear'], regularization: ['none', 'L1', 'L2'], regularizationRate: [0, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10], }; -neuronCardUi.init = function () { +selectCardUi.init = function () { this.fetchCard (); this.createLearningRates (); this.createActivations (); @@ -37,7 +37,7 @@ neuronCardUi.init = function () { this.attachEvents (); }; -neuronCardUi.fetchCard = function () { +selectCardUi.fetchCard = function () { this.node = d3.select (this.nodeSelectors.node); this.rows = this.node.selectAll (this.nodeSelectors.row)[0]; this.weights = this.node.selectAll (this.nodeSelectors.weight)[0]; @@ -48,7 +48,7 @@ neuronCardUi.fetchCard = function () { this.regularizationRates = this.node.selectAll (this.nodeSelectors.regularizationRate)[0]; }; -neuronCardUi.createOptions = function (parent, options) { +selectCardUi.createOptions = function (parent, options) { const select = document.createElement ('select'); options.forEach ((option) => { @@ -61,31 +61,31 @@ neuronCardUi.createOptions = function (parent, options) { parent.appendChild (select); }; -neuronCardUi.createLearningRates = function () { +selectCardUi.createLearningRates = function () { this.learningRates.forEach ((learningRate) => { this.createOptions (learningRate, this.options.learningRate); }); }; -neuronCardUi.createActivations = function () { +selectCardUi.createActivations = function () { this.activations.forEach ((activation) => { this.createOptions (activation, this.options.activation); }); }; -neuronCardUi.createRegularizations = function () { +selectCardUi.createRegularizations = function () { this.regularizations.forEach ((regularization) => { this.createOptions (regularization, this.options.regularization); }); }; -neuronCardUi.createRegularizationRates = function () { +selectCardUi.createRegularizationRates = function () { this.regularizationRates.forEach ((regularizationRate) => { this.createOptions (regularizationRate, this.options.regularizationRate); }); }; -neuronCardUi.updateCard = function () { +selectCardUi.updateCard = function () { const { selectedNodes } = playgroundFacade; if (selectedNodes.length === 0) { this.node.style ('display', 'none'); @@ -146,7 +146,7 @@ neuronCardUi.updateCard = function () { } }; -neuronCardUi.attachEvents = function () { +selectCardUi.attachEvents = function () { if (this.weights) { this.weights.forEach ((weight, index) => { weight.onchange = (e: InputEvent) => { @@ -195,7 +195,7 @@ neuronCardUi.attachEvents = function () { } }; -neuronCardUi.setInput = function (pool, index, payload) { +selectCardUi.setInput = function (pool, index, payload) { const isFocused = pool[index] === document.activeElement; if (isFocused) { return; @@ -215,15 +215,15 @@ neuronCardUi.setInput = function (pool, index, payload) { } }; -neuronCardUi.setWeight = function (index, weight?) { +selectCardUi.setWeight = function (index, weight?) { this.setInput (this.weights, index, weight); }; -neuronCardUi.setBias = function (index, bias?) { +selectCardUi.setBias = function (index, bias?) { this.setInput (this.biases, index, bias); }; -neuronCardUi.setDropdown = function (pool, index, payload) { +selectCardUi.setDropdown = function (pool, index, payload) { const didNotChange = pool[index].children[0].value === payload; if (didNotChange) { return; @@ -243,18 +243,18 @@ neuronCardUi.setDropdown = function (pool, index, payload) { } }; -neuronCardUi.setLearningRate = function (index, learningRate?) { +selectCardUi.setLearningRate = function (index, learningRate?) { this.setDropdown (this.learningRates, index, learningRate); }; -neuronCardUi.setActivation = function (index, activation?) { +selectCardUi.setActivation = function (index, activation?) { this.setDropdown (this.activations, index, activation); }; -neuronCardUi.setRegularization = function (index, regularization?) { +selectCardUi.setRegularization = function (index, regularization?) { this.setDropdown (this.regularizations, index, regularization); }; -neuronCardUi.setRegularizationRate = function (index, regularizationRate?) { +selectCardUi.setRegularizationRate = function (index, regularizationRate?) { this.setDropdown (this.regularizationRates, index, regularizationRate); }; diff --git a/src/app/ui/ui.ts b/src/app/ui/ui.ts index 3aa2795..13d5d9f 100644 --- a/src/app/ui/ui.ts +++ b/src/app/ui/ui.ts @@ -1,7 +1,7 @@ import { mappingsUi } from './mappings.ui'; import { buttonsUi } from './buttons.ui'; import { notificationsUi } from './notifications.ui'; -import { neuronCardUi } from './neuron-card.ui'; +import { selectCardUi } from './select-card.ui'; import { devicesUi } from './devices.ui'; import { helpUi } from './help.ui'; @@ -16,7 +16,7 @@ ui.init = async function () { await notificationsUi.init (); mappingsUi.init (); buttonsUi.init (); - neuronCardUi.init (); + selectCardUi.init (); devicesUi.init (); helpUi.init (); }; diff --git a/styles.css b/styles.css index 971bb45..3609fe8 100644 --- a/styles.css +++ b/styles.css @@ -1079,9 +1079,9 @@ Help Dialog } /** -Neuron card +Select card */ -#neuron-card { +#select-card { display: none; flex-direction: column; @@ -1103,16 +1103,16 @@ Neuron card text-align: center; } -#neuron-card .row { +#select-card .row { display: grid; grid-gap: 5px; grid-template-columns: 40px repeat(6, 70px); } -#neuron-card .header { +#select-card .header { font-style: italic; } -#neuron-card input { +#select-card input { width: 60px; } From ad429bb8fa532e7eed311fce9d8f85cc2a7b5c0b Mon Sep 17 00:00:00 2001 From: Bamdad Sabbagh Date: Sun, 14 Nov 2021 23:31:07 +0100 Subject: [PATCH 2/3] perf(device/prototype): apply `stroustrup` code style + set default interval at `200` ms --- src/app/devices/device/device.prototype.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/devices/device/device.prototype.ts b/src/app/devices/device/device.prototype.ts index c0b2aec..70ea770 100644 --- a/src/app/devices/device/device.prototype.ts +++ b/src/app/devices/device/device.prototype.ts @@ -118,7 +118,7 @@ devicePrototype.clearBlinkingNote = function (note) { devicePrototype.blinkNote = function ({ note, color, - interval = 800, + interval = 200, }): void { this.clearBlinkingNote (note); this.blinkingNotes[note] = setInterval (() => { @@ -133,7 +133,7 @@ devicePrototype.blinkNote = function ({ devicePrototype.playOrBlinkNote = function ({ note, color, - interval = 800, + interval = 200, duration = 3600000, }) { if (typeof this.blinkingNotes?.[note] === 'undefined') { @@ -144,14 +144,16 @@ devicePrototype.playOrBlinkNote = function ({ color, duration, }); - } else if (this.blinkingNotes?.[note] === null) { + } + else if (this.blinkingNotes?.[note] === null) { // not blinking this.blinkNote ({ note, color, interval, }); - } else { + } + else { // already blinking this.clearBlinkingNote (note); setTimeout (() => { From f82122c2b7a8e58893f9cc2bcc4bf3c4b1e1966a Mon Sep 17 00:00:00 2001 From: Bamdad Sabbagh Date: Mon, 15 Nov 2021 01:01:51 +0100 Subject: [PATCH 3/3] feat: add LAYER mode (card + selector + controller) + add small various improvements --- index.html | 94 +++++++++++++++++- src/app/devices/controller.device.ts | 140 ++++++++++++++++++++++++++- src/app/devices/selector.device.ts | 61 ++++++++++-- src/app/facades/playground.facade.ts | 2 + src/app/state/network.state.ts | 67 +++++++++++++ src/app/ui/layer-card.ui.ts | 39 ++++++++ src/app/ui/network.ui.ts | 21 ++-- src/app/ui/playground.ui.ts | 7 -- src/app/ui/select-card.ui.ts | 6 +- src/app/ui/ui.ts | 2 + src/playground/playground.ts | 5 +- styles.css | 14 ++- 12 files changed, 421 insertions(+), 37 deletions(-) create mode 100644 src/app/ui/layer-card.ui.ts diff --git a/index.html b/index.html index 8d48e03..9106178 100644 --- a/index.html +++ b/index.html @@ -794,7 +794,7 @@
Novation Launch Control XL (Controller)
-
+
Source Weight @@ -910,6 +910,98 @@
Novation Launch Control XL (Controller)
+ +
+
+ Neuron + Bias + Learning rate + Activation + Regularization + Regul. rate +
+
+ #1 + +
+
+
+
+
+
+ #2 + +
+
+
+
+
+
+ #3 + +
+
+
+
+
+
+ #4 + +
+
+
+
+
+
+ #5 + +
+
+
+
+
+
+ #6 + +
+
+
+
+
+
+ #7 + +
+
+
+
+
+
+ #8 + +
+
+
+
+
+
+ diff --git a/src/app/devices/controller.device.ts b/src/app/devices/controller.device.ts index 80cda83..0180f0e 100644 --- a/src/app/devices/controller.device.ts +++ b/src/app/devices/controller.device.ts @@ -6,6 +6,7 @@ import { networkState } from '../state/network.state'; import { mappingsState } from '../state/mappings.state'; import { mappingsUi } from '../ui/mappings.ui'; import { playgroundUi } from '../ui/playground.ui'; +import { layerCardUi } from '../ui/layer-card.ui'; /** * Controller is a unique device that controls the playground. @@ -80,15 +81,22 @@ controllerDevice.drawLights = function () { * Set the mode of the controller. */ controllerDevice.updateMode = function () { + if (!this.isInitialized) { + return; + } + this.removeListeners (); - if (this.isDefaultMode) { + if (networkState.isLayerMode) { + this.setLayerMode (); + } + else if (this.isDefaultMode) { this.setDefaultMode (); } else if (this.isSingleMode) { this.setSingleMode (); } - else { + else if (this.isMultipleMode) { this.setMultipleMode (); } }; @@ -250,7 +258,7 @@ controllerDevice.attachControlsToNeuron = function (selectedNode: number): void // first row: learning rate if (this.settings.rows.firstPots.indexOf (inputNote) !== -1) { - const index = inputNote - this.settings.rows.firstPots[0]; + const index = this.settings.rows.firstPots.indexOf (inputNote); if (typeof links[index]?.source === 'undefined') { return; } @@ -278,7 +286,7 @@ controllerDevice.attachControlsToNeuron = function (selectedNode: number): void } // second row: activation else if (this.settings.rows.secondPots.indexOf (inputNote) !== -1) { - const index = inputNote - this.settings.rows.secondPots[0]; + const index = this.settings.rows.secondPots.indexOf (inputNote); if (typeof links[index]?.source === 'undefined') { return; } @@ -438,3 +446,127 @@ Object.defineProperty (controllerDevice, 'isMultipleMode', { return playgroundFacade.selectedNodes.length > 1; }, }); + +controllerDevice.setLayerMode = function () { + this.attachButtonsToNeuron (); + this.attachControlsToLayer (); +}; + +controllerDevice.attachControlsToLayer = function (): void { + const neurons = networkState.neurons[networkState.selectedLayerIndex]; + + this.playNotes ({ + firstNote: this.settings.lights.first, + lastNote: this.settings.lights.last, + color: this.settings.colors.yellow, + }); + + this.addControlListener ((e) => { + const inputNote = e.controller.number; + + // first row: learning rate + if (this.settings.rows.firstPots.indexOf (inputNote) !== -1) { + const index = this.settings.rows.firstPots.indexOf (inputNote); + if (neurons[index].isEnabled === false) { + return; + } + + const learningRateOptionIndex = parseInt ( + rangeMap ( + e.value, + 0, + 127, + 0, + selectCardUi.options.learningRate.length - 1, + ).toString (), + ); + + const learningRate = selectCardUi.options.learningRate[learningRateOptionIndex]; + + if (learningRate !== neurons[index].learningRate) { + networkState.setLearningRate (parseInt (neurons[index].id), learningRate); + layerCardUi.setLearningRate (index, learningRate); + } + } + else if (this.settings.rows.secondPots.indexOf (inputNote) !== -1) { + const index = this.settings.rows.secondPots.indexOf (inputNote); + if (neurons[index].isEnabled === false) { + return; + } + + const activationOptionIndex = parseInt ( + rangeMap ( + e.value, + 0, + 127, + 0, + selectCardUi.options.activation.length - 1, + ).toString (), + ); + + const activation = selectCardUi.options.activation[activationOptionIndex]; + + if (activation !== neurons[index].activation.name) { + networkState.setActivation (parseInt (neurons[index].id), activation); + layerCardUi.setActivation (index, activation); + } + } + else if (this.settings.rows.thirdPots.indexOf (inputNote) !== -1) { + const index = this.settings.rows.thirdPots.indexOf (inputNote); + if (neurons[index].isEnabled === false) { + return; + } + + // regularization (shifted) + if (this.shifted[index] === true) { + const regularizationOptionIndex = parseInt ( + rangeMap ( + e.value, + 0, + 127, + 0, + selectCardUi.options.regularization.length - 1, + ).toString (), + ); + + const regularization = selectCardUi.options.regularization[regularizationOptionIndex]; + + if (regularization !== neurons[index].regularization.name) { + networkState.setRegularization (parseInt (neurons[index].id), regularization); + layerCardUi.setRegularization (index, regularization); + } + } + // regularization rate + else { + const regularizationRateOptionIndex = parseInt ( + rangeMap ( + e.value, + 0, + 127, + 0, + selectCardUi.options.regularizationRate.length - 1, + ).toString (), + ); + + const regularizationRate = selectCardUi.options.regularizationRate[regularizationRateOptionIndex]; + + if (regularizationRate !== neurons[index].regularizationRate) { + networkState.setRegularizationRate (parseInt (neurons[index].id), regularizationRate); + layerCardUi.setRegularizationRate (index, regularizationRate); + } + } + } + else if (this.settings.rows.faders.indexOf (inputNote) !== -1) { + const index = this.settings.rows.faders.indexOf (inputNote); + if (neurons[index].isEnabled === false) { + return; + } + const value = rangeMap (e.value, 0, 127, -1, 1); + if (value.toFixed (2) !== neurons[index].bias.toFixed (2)) { + neurons[index].bias = value; + layerCardUi.setBias (index, value); + playgroundFacade.updateBiasesUI (); + } + } + }); +}; diff --git a/src/app/devices/selector.device.ts b/src/app/devices/selector.device.ts index 938de0b..763b091 100644 --- a/src/app/devices/selector.device.ts +++ b/src/app/devices/selector.device.ts @@ -2,6 +2,8 @@ import { devicePrototype } from './device/device.prototype'; import { playgroundFacade } from '../facades/playground.facade'; import { networkState } from '../state/network.state'; import { networkUi } from '../ui/network.ui'; +import { layerCardUi } from '../ui/layer-card.ui'; +import { controllerDevice } from './controller.device'; export const selectorDevice = Object.create (devicePrototype); @@ -68,6 +70,10 @@ selectorDevice.drawInputs = function (): void { */ selectorDevice.attachInputs = function (): void { this.addNoteListener ('on', (e) => { + if (networkState.isLayerMode) { + return; + } + const flatIndex = this.getGridFlatIndex (e.note.number); if (!(flatIndex >= 0 && flatIndex <= 6)) { @@ -75,7 +81,7 @@ selectorDevice.attachInputs = function (): void { } const { id } = networkState.getInputByIndex (flatIndex); - networkUi.toggleInput (id, true); + networkUi.toggleInput (id); }); }; @@ -134,8 +140,11 @@ selectorDevice.drawNeurons = function (): void { */ selectorDevice.attachNeurons = function (): void { this.addNoteListener ('on', (e) => { - const flatIndex = this.getGridFlatIndex (e.note.number); + if (networkState.isLayerMode) { + return; + } + const flatIndex = this.getGridFlatIndex (e.note.number); if (!(flatIndex >= 8 && flatIndex <= 55)) { return; } @@ -238,8 +247,11 @@ selectorDevice.drawOutputWeights = function (): void { */ selectorDevice.attachOutputWeights = function (): void { this.addNoteListener ('on', (e) => { - const flatIndex = this.getGridFlatIndex (e.note.number); + if (networkState.isLayerMode) { + return; + } + const flatIndex = this.getGridFlatIndex (e.note.number); if (!(flatIndex >= 56 && flatIndex <= 63)) { return; } @@ -309,11 +321,44 @@ selectorDevice.attachLayers = function () { // listen for changes this.addControlListener ((e) => { const inputNote = e.controller.number; - if (layerPads.includes (inputNote)) { - this.playOrBlinkNote ({ - note: inputNote, - color: this.settings.colorByState.layer, - }); + if (layerPads.indexOf (inputNote) !== -1) { + const index = layerPads.indexOf (inputNote); + + // no layer selected + if (networkState.selectedLayerIndex === null) { + networkState.selectedLayerIndex = index; + this.playOrBlinkNote ({ + note: inputNote, + color: this.settings.colorByState.layer, + }); + } + // switch layer + else if (index !== networkState.selectedLayerIndex) { + // unselect previous layer + const previousLayer = layerPads[networkState.selectedLayerIndex]; + this.playOrBlinkNote ({ + note: previousLayer, + color: this.settings.colorByState.layer, + }); + + // select new layer + networkState.selectedLayerIndex = index; + this.playOrBlinkNote ({ + note: inputNote, + color: this.settings.colorByState.layer, + }); + } + // toggle layer + else { + networkState.selectedLayerIndex = null; + this.playOrBlinkNote ({ + note: inputNote, + color: this.settings.colorByState.layer, + }); + } + + controllerDevice.updateMode (); + layerCardUi.updateCard (); } }, true); }; diff --git a/src/app/facades/playground.facade.ts b/src/app/facades/playground.facade.ts index df4caab..df06bd2 100644 --- a/src/app/facades/playground.facade.ts +++ b/src/app/facades/playground.facade.ts @@ -9,6 +9,7 @@ import { updateWeightsUI, updateBiasesUI, } from '../../playground/playground'; +import { selectorDevice } from '../devices/selector.device'; export const playgroundFacade = Object.create (null); @@ -62,5 +63,6 @@ Object.defineProperty (playgroundFacade, 'isPlaying', { playgroundFacade.togglePlayback = function () { player.playOrPause (); + selectorDevice.updateLightPlayback (); return this.isPlaying; }; diff --git a/src/app/state/network.state.ts b/src/app/state/network.state.ts index 7bb2783..64e54fc 100644 --- a/src/app/state/network.state.ts +++ b/src/app/state/network.state.ts @@ -203,20 +203,87 @@ networkState.updateSourceLearningRate = function (index, value) { }); }; +networkState.setLearningRate = function (index: number, value: number): void { + const { neuron } = this.getNeuron (index); + neuron.learningRate = value; +}; + networkState.updateSourceActivation = function (index, value) { this.getSelectedNeurons ().forEach ((neuron) => { neuron.inputLinks[index].source.activation = activations[value]; }); }; +networkState.setActivation = function (index: number, name: string): void { + if (typeof index === 'undefined') { + throw new Error ('index must be defined'); + } + if (typeof index !== 'number') { + throw new Error ('index must be a number'); + } + if (typeof name === 'undefined') { + throw new Error ('name must be defined'); + } + if (typeof name !== 'string') { + throw new Error ('name must be a string'); + } + + const { neuron } = this.getNeuron (index); + neuron.activation = activations[name]; +}; + networkState.updateSourceRegularization = function (index, value) { this.getSelectedNeurons ().forEach ((neuron) => { neuron.inputLinks[index].source.regularization = regularizations[value]; }); }; +networkState.setRegularization = function (index: number, name: string) { + if (typeof index === 'undefined') { + throw new Error ('index must be defined'); + } + if (typeof index !== 'number') { + throw new Error ('index must be a number'); + } + if (typeof name === 'undefined') { + throw new Error ('name must be defined'); + } + if (typeof name !== 'string') { + throw new Error ('name must be a string'); + } + + const { neuron } = this.getNeuron (index); + neuron.regularization = regularizations[name]; +}; + networkState.updateSourceRegularizationRate = function (index, value) { this.getSelectedNeurons ().forEach ((neuron) => { neuron.inputLinks[index].source.regularizationRate = value; }); }; + +networkState.setRegularizationRate = function (index: number, value: number) { + if (typeof index === 'undefined') { + throw new Error ('index must be defined'); + } + if (typeof index !== 'number') { + throw new Error ('index must be a number'); + } + if (typeof value === 'undefined') { + throw new Error ('value must be defined'); + } + if (typeof value !== 'number') { + throw new Error ('value must be a number'); + } + + const { neuron } = this.getNeuron (index); + neuron.regularizationRate = value; +}; + +networkState.selectedLayerIndex = null; + +Object.defineProperty (networkState, 'isLayerMode', { + get () { + return this.selectedLayerIndex !== null; + }, +}); diff --git a/src/app/ui/layer-card.ui.ts b/src/app/ui/layer-card.ui.ts new file mode 100644 index 0000000..f977ef6 --- /dev/null +++ b/src/app/ui/layer-card.ui.ts @@ -0,0 +1,39 @@ +import { selectCardUi } from './select-card.ui'; +import { networkState } from '../state/network.state'; + +export const layerCardUi = Object.create (selectCardUi); + +layerCardUi.nodeSelectors = { + ...layerCardUi.nodeSelectors, + node: '#layer-card', +}; + +layerCardUi.updateCard = function () { + if (networkState.selectedLayerIndex === null) { + this.node.style ('display', 'none'); + return; + } + + const neurons = networkState.neurons[networkState.selectedLayerIndex]; + + neurons.forEach ((neuron, index) => { + this.biases[index].value = neuron.bias.toFixed (3); + this.biases[index].disabled = !neuron.isEnabled; + + this.learningRates[index].children[0].value = neuron.learningRate; + this.learningRates[index].children[0].disabled = !neuron.isEnabled; + + this.activations[index].children[0].value = neuron.activation.name; + this.activations[index].children[0].disabled = !neuron.isEnabled; + + this.regularizations[index].children[0].value = neuron.regularization.name; + this.regularizations[index].children[0].disabled = !neuron.isEnabled; + + this.regularizationRates[index].value = neuron.regularizationRate; + this.regularizationRates[index].children[0].disabled = !neuron.isEnabled; + }); + + this.node.style ('display', 'flex'); + + requestAnimationFrame (this.updateCard.bind (this)); +}; diff --git a/src/app/ui/network.ui.ts b/src/app/ui/network.ui.ts index ada0a4d..4a93d32 100644 --- a/src/app/ui/network.ui.ts +++ b/src/app/ui/network.ui.ts @@ -11,6 +11,10 @@ import { controllerDevice } from '../devices/controller.device'; export const networkUi = Object.create (null); networkUi.toggleNeuron = function (index: number) { + if (networkState.isLayerMode) { + return; + } + const { isEnabled } = networkState.getNeuron (index); const nextEnabled = !isEnabled; const canvas = d3.select (`#canvas-${index}`); @@ -31,15 +35,16 @@ networkUi.toggleNeuron = function (index: number) { }); }; -networkUi.toggleInput = function (slug: string, render = false) { +networkUi.toggleInput = function (slug: string) { + if (networkState.isLayerMode) { + return; + } + const input = networkState.toggleInput (slug); // DOM - if (render) { - // todo to deprecate - const canvas = d3.select (`#canvas-${slug}`); - canvas.classed ('disabled', !input.isEnabled); - } + const canvas = d3.select (`#canvas-${slug}`); + canvas.classed ('disabled', !input.isEnabled); playgroundFacade.updateWeightsUI (); @@ -50,6 +55,10 @@ networkUi.toggleInput = function (slug: string, render = false) { }; networkUi.toggleNodeSelection = function (nodeIndex: number, isSelected: boolean) { + if (networkState.isLayerMode) { + return; + } + if (typeof nodeIndex !== 'number') { throw new Error ('nodeId is not a number'); } diff --git a/src/app/ui/playground.ui.ts b/src/app/ui/playground.ui.ts index 3db403e..24d6bcf 100644 --- a/src/app/ui/playground.ui.ts +++ b/src/app/ui/playground.ui.ts @@ -1,8 +1,6 @@ import { isTabActive } from '../utils/is-tab-active'; import { rangeMap } from '../utils/range-map'; import { mappingsUi } from './mappings.ui'; -import { playgroundFacade } from '../facades/playground.facade'; -import { selectorDevice } from '../devices/selector.device'; export const playgroundUi = Object.create (null); @@ -79,8 +77,3 @@ playgroundUi.updateParameter = function (name: string, value: number): void { throw new Error (`${parameter.tagName} target not handled`); } }; - -playgroundUi.togglePlayback = function () { - playgroundFacade.togglePlayback (); - selectorDevice.updateLightPlayback (); -}; diff --git a/src/app/ui/select-card.ui.ts b/src/app/ui/select-card.ui.ts index 182bfb7..133e92c 100644 --- a/src/app/ui/select-card.ui.ts +++ b/src/app/ui/select-card.ui.ts @@ -40,8 +40,8 @@ selectCardUi.init = function () { selectCardUi.fetchCard = function () { this.node = d3.select (this.nodeSelectors.node); this.rows = this.node.selectAll (this.nodeSelectors.row)[0]; - this.weights = this.node.selectAll (this.nodeSelectors.weight)[0]; - this.biases = this.node.selectAll (this.nodeSelectors.bias)[0]; + this.weights = this.node.selectAll (this.nodeSelectors.weight)[0] || []; + this.biases = this.node.selectAll (this.nodeSelectors.bias)[0] || []; this.learningRates = this.node.selectAll (this.nodeSelectors.learningRate)[0]; this.activations = this.node.selectAll (this.nodeSelectors.activation)[0]; this.regularizations = this.node.selectAll (this.nodeSelectors.regularization)[0]; @@ -92,7 +92,7 @@ selectCardUi.updateCard = function () { return; } - this.node.style ('display', 'block'); + this.node.style ('display', 'flex'); this.rows.forEach ((row, index) => { // single selection diff --git a/src/app/ui/ui.ts b/src/app/ui/ui.ts index 13d5d9f..08611c2 100644 --- a/src/app/ui/ui.ts +++ b/src/app/ui/ui.ts @@ -4,6 +4,7 @@ import { notificationsUi } from './notifications.ui'; import { selectCardUi } from './select-card.ui'; import { devicesUi } from './devices.ui'; import { helpUi } from './help.ui'; +import { layerCardUi } from './layer-card.ui'; export const ui = Object.create (null); @@ -17,6 +18,7 @@ ui.init = async function () { mappingsUi.init (); buttonsUi.init (); selectCardUi.init (); + layerCardUi.init (); devicesUi.init (); helpUi.init (); }; diff --git a/src/playground/playground.ts b/src/playground/playground.ts index 31a950c..96b3684 100644 --- a/src/playground/playground.ts +++ b/src/playground/playground.ts @@ -30,7 +30,7 @@ import { AppendingLineChart } from './linechart'; import * as d3 from 'd3'; import { Coolearning } from '../coolearning/coolearning'; import { networkUi } from '../app/ui/network.ui'; -import { playgroundUi } from '../app/ui/playground.ui'; +import { playgroundFacade } from '../app/facades/playground.facade'; Coolearning (); @@ -198,7 +198,7 @@ function makeGUI () { d3.select ('#play-pause-button').on ('click', function () { // Change the button's content. userHasInteracted (); - playgroundUi.togglePlayback (); + playgroundFacade.togglePlayback (); }); player.onPlayPause (isPlaying => { @@ -578,7 +578,6 @@ function drawNode (cx: number, cy: number, nodeId: string, isInput: boolean, if (isInput) { div.on ('click', function () { networkUi.toggleInput (nodeId); - div.classed ('disabled', !div.classed ('disabled')); }); } if (isInput) { diff --git a/styles.css b/styles.css index 3609fe8..36693b1 100644 --- a/styles.css +++ b/styles.css @@ -1079,15 +1079,19 @@ Help Dialog } /** -Select card +Cool cards */ -#select-card { +.cool-card { display: none; flex-direction: column; + justify-content: center; + align-items: center; position: absolute; left: 50%; top: 5px; + + height: 195px; transform: translate(-50%, 0); z-index: 1000; @@ -1103,16 +1107,16 @@ Select card text-align: center; } -#select-card .row { +.cool-card .row { display: grid; grid-gap: 5px; grid-template-columns: 40px repeat(6, 70px); } -#select-card .header { +.cool-card .header { font-style: italic; } -#select-card input { +.cool-card input { width: 60px; }