diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..8073759 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,27 @@ +version: 2 +jobs: + build: + working_directory: ~/responsive-analog-read + docker: + - image: circleci/node:10-stretch + + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "docs/yarn.lock" }} + - v1-dependencies- + - run: cd docs && yarn && yarn install-emcc + - save_cache: + paths: + - docs/node_modules + - docs/emsdk-portable.tar.gz + - docs/emsdk-portable + key: v1-dependencies-{{ checksum "docs/yarn.lock" }} + + - deploy: + command: | + if [ "${CIRCLE_BRANCH}" == "master" ] || [ "${CIRCLE_BRANCH}" == "release/version-2" ]; then + git config --global -l && git config --global user.email circleci@circleci && git config --global user.name CircleCI && cd docs && yarn deploy + fi + diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..aa93a69 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,json}] +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d23e38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Project dependencies +.cache +node_modules + +# site / gatsby +/docs/public + +# emscripten +/docs/emsdk-portable +/docs/emsdk-portable.tar.gz* + +# misc +*.log.* +*.log +.DS_Store diff --git a/README.md b/README.md index afccaf1..0de4b49 100644 --- a/README.md +++ b/README.md @@ -1,168 +1,15 @@ # ResponsiveAnalogRead ![ResponsiveAnalogRead](http://damienclarke.me/content/1-code/3-responsive-analog-read/thumbnail.jpg) -ResponsiveAnalogRead is an Arduino library for eliminating noise in analogRead inputs without decreasing responsiveness. It sets out to achieve the following: +TODO - make that image par of the docs build -1. Be able to reduce large amounts of noise when reading a signal. So if a voltage is unchanging aside from noise, the values returned should never change due to noise alone. -2. Be extremely responsive (i.e. not sluggish) when the voltage changes quickly. -3. Have the option to be responsive when a voltage *stops* changing - when enabled the values returned must stop changing almost immediately after. When this option is enabled, a very small sacrifice in accuracy is permitted. -4. The returned values must avoid 'jumping' up several numbers at once, especially when the input signal changes very slowly. It's better to transition smoothly as long as that smooth transition is short. - -You can preview the way the algorithm works with [sleep enabled](http://codepen.io/dxinteractive/pen/zBEbpP) (minimising the time spend transitioning between values) and with [sleep disabled](http://codepen.io/dxinteractive/pen/ezdJxL) (transitioning responsively and accurately but smooth). - -An article discussing the design of the algorithm can be found [here](http://damienclarke.me/code/posts/writing-a-better-noise-reducing-analogread). - -## How to use - -Here's a basic example: - -```Arduino -// include the ResponsiveAnalogRead library -#include - -// define the pin you want to use -const int ANALOG_PIN = A0; - -// make a ResponsiveAnalogRead object, pass in the pin, and either true or false depending on if you want sleep enabled -// enabling sleep will cause values to take less time to stop changing and potentially stop changing more abruptly, -// where as disabling sleep will cause values to ease into their correct position smoothly and with slightly greater accuracy -ResponsiveAnalogRead analog(ANALOG_PIN, true); - -// the next optional argument is snapMultiplier, which is set to 0.01 by default -// you can pass it a value from 0 to 1 that controls the amount of easing -// increase this to lessen the amount of easing (such as 0.1) and make the responsive values more responsive -// but doing so may cause more noise to seep through if sleep is not enabled - -void setup() { - // begin serial so we can see analog read values through the serial monitor - Serial.begin(9600); -} - -void loop() { - // update the ResponsiveAnalogRead object every loop - analog.update(); - - Serial.print(analog.getRawValue()); - Serial.print("\t"); - Serial.print(analog.getValue()); - - // if the responsive value has change, print out 'changed' - if(analog.hasChanged()) { - Serial.print("\tchanged"); - } - - Serial.println(""); - delay(20); -} -``` - -### Using your own ADC - -```Arduino -#include - -ResponsiveAnalogRead analog(0, true); - -void setup() { - // begin serial so we can see analog read values through the serial monitor - Serial.begin(9600); -} - -void loop() { - // read from your ADC - // update the ResponsiveAnalogRead object every loop - int reading = YourADCReadMethod(); - analog.update(reading); - Serial.print(analog.getValue()); - - Serial.println(""); - delay(20); -} -``` - -### Smoothing multiple inputs - -```Arduino -#include - -ResponsiveAnalogRead analogOne(A1, true); -ResponsiveAnalogRead analogTwo(A2, true); - -void setup() { - // begin serial so we can see analog read values through the serial monitor - Serial.begin(9600); -} - -void loop() { - // update the ResponsiveAnalogRead objects every loop - analogOne.update(); - analogTwo.update(); - - Serial.print(analogOne.getValue()); - Serial.print(analogTwo.getValue()); - - Serial.println(""); - delay(20); -} -``` - -## How to install - -In the Arduino IDE, go to Sketch > Include libraries > Manage libraries, and search for ResponsiveAnalogRead. -You can also just use the files directly from the src folder. - -Look at the example in the examples folder for an idea on how to use it in your own projects. -The source files are also heavily commented, so check those out if you want fine control of the library's behaviour. - -## Constructor arguments - -- `pin` - int, the pin to read (e.g. A0). -- `sleepEnable` - boolean, sets whether sleep is enabled. Defaults to true. Enabling sleep will cause values to take less time to stop changing and potentially stop changing more abruptly, where as disabling sleep will cause values to ease into their correct position smoothly. -- `snapMultiplier` - float, a value from 0 to 1 that controls the amount of easing. Defaults to 0.01. Increase this to lessen the amount of easing (such as 0.1) and make the responsive values more responsive, but doing so may cause more noise to seep through if sleep is not enabled. - -## Basic methods - -- `int getValue() // get the responsive value from last update` -- `int getRawValue() // get the raw analogRead() value from last update` -- `bool hasChanged() // returns true if the responsive value has changed during the last update` -- `void update(); // updates the value by performing an analogRead() and calculating a responsive value based off it` -- `void update(int rawValue); // updates the value by accepting a raw value and calculating a responsive value based off it (version 1.1.0+)` -- `bool isSleeping() // returns true if the algorithm is in sleep mode (version 1.1.0+)` - -## Other methods (settings) - -### Sleep - -- `void enableSleep()` -- `void disableSleep()` - -Sleep allows you to minimise the amount of responsive value changes over time. Increasingly small changes in the output value to be ignored, so instead of having the responsiveValue slide into position over a couple of seconds, it stops when it's "close enough". It's enabled by default. Here's a summary of how it works: - -1. "Sleep" is when the output value decides to ignore increasingly small changes. -2. When it sleeps, it is less likely to start moving again, but a large enough nudge will wake it up and begin responding as normal. -3. It classifies changes in the input voltage as being "active" or not. A lack of activity tells it to sleep. - -### Activity threshold -- `void setActivityThreshold(float newThreshold) // the amount of movement that must take place for it to register as activity and start moving the output value. Defaults to 4.0. (version 1.1+)` - -### Snap multiplier -- `void setSnapMultiplier(float newMultiplier)` - -SnapMultiplier is a value from 0 to 1 that controls the amount of easing. Increase this to lessen the amount of easing (such as 0.1) and make the responsive values more responsive, but doing so may cause more noise to seep through when sleep is not enabled. - -### Edge snapping -- `void enableEdgeSnap() // edge snap ensures that values at the edges of the spectrum (0 and 1023) can be easily reached when sleep is enabled` - -### Analog resolution -- `void setAnalogResolution(int resolution)` - -If your ADC is something other than 10bit (1024), set that using this. +Something something version 2. ## License Licensed under the MIT License (MIT) -Copyright (c) 2016, Damien Clarke +Copyright (c) 2018, Damien Clarke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/docs/.babelrc b/docs/.babelrc new file mode 100644 index 0000000..ec330fb --- /dev/null +++ b/docs/.babelrc @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["prismjs", { + "languages": ["cpp"] + }] + ] +} diff --git a/docs/.editorconfig b/docs/.editorconfig new file mode 100644 index 0000000..aa93a69 --- /dev/null +++ b/docs/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,json}] +indent_size = 2 diff --git a/docs/.eslintignore b/docs/.eslintignore new file mode 100644 index 0000000..3d2d1f7 --- /dev/null +++ b/docs/.eslintignore @@ -0,0 +1,4 @@ +**/*-test.js +**/gastby-node.js +**/parcels-docs +**/node_modules \ No newline at end of file diff --git a/docs/.eslintrc b/docs/.eslintrc new file mode 100644 index 0000000..ebc2466 --- /dev/null +++ b/docs/.eslintrc @@ -0,0 +1,10 @@ +{ + "extends": [ + "eslint-config-blueflag", + "eslint-config-blueflag/react", + "eslint-config-blueflag/flow" + ], + "rules": { + "newline-per-chained-call": "off" + } +} diff --git a/docs/gatsby-browser.js b/docs/gatsby-browser.js new file mode 100644 index 0000000..d168926 --- /dev/null +++ b/docs/gatsby-browser.js @@ -0,0 +1,7 @@ +/** + * Implement Gatsby's Browser APIs in this file. + * + * See: https://www.gatsbyjs.org/docs/browser-apis/ + */ + + // You can delete this file if you're not using it \ No newline at end of file diff --git a/docs/gatsby-config.js b/docs/gatsby-config.js new file mode 100644 index 0000000..6a4826f --- /dev/null +++ b/docs/gatsby-config.js @@ -0,0 +1,19 @@ +// @flow +module.exports = { + pathPrefix: '/ResponsiveAnalogRead', + siteMetadata: { + title: 'ResponsiveAnalogRead' + }, + plugins: [ + 'gatsby-plugin-sass', + 'gatsby-plugin-react-helmet', + { + resolve: 'gatsby-transformer-remark', + options: { + plugins: [ + `gatsby-remark-prismjs` + ] + } + } + ] +}; diff --git a/docs/gatsby-node.js b/docs/gatsby-node.js new file mode 100644 index 0000000..26681a8 --- /dev/null +++ b/docs/gatsby-node.js @@ -0,0 +1,19 @@ +/** + * Implement Gatsby's Node APIs in this file. + * + * See: https://www.gatsbyjs.org/docs/node-apis/ + */ + +exports.modifyWebpackConfig = ({config}) => { + return config + .loader("wasm", { + test: /\.wasm$/, + loaders: ['buffer-loader'] + }) + .merge({ + node: { + fs: 'empty', + child_process: 'empty' + } + }); +}; diff --git a/docs/gatsby-ssr.js b/docs/gatsby-ssr.js new file mode 100644 index 0000000..cdad094 --- /dev/null +++ b/docs/gatsby-ssr.js @@ -0,0 +1,7 @@ +/** + * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. + * + * See: https://www.gatsbyjs.org/docs/ssr-apis/ + */ + + // You can delete this file if you're not using it \ No newline at end of file diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..dfe742a --- /dev/null +++ b/docs/package.json @@ -0,0 +1,44 @@ +{ + "name": "responsive-analog-read-site", + "private": true, + "description": "responsive-analog-read-site", + "version": "0.0.0", + "author": "Damien Clarke ", + "dependencies": { + "babel-plugin-prismjs": "^1.0.2", + "blueflag-test": "^0.18.1", + "bruce": "^5.0.0", + "buffer-loader": "^0.0.1", + "chart.js": "^2.7.2", + "dcme-style": "^0.12.1", + "gatsby": "^1.9.247", + "gatsby-link": "^1.6.40", + "gatsby-plugin-react-helmet": "^2.0.10", + "gatsby-plugin-sass": "^1.0.26", + "gatsby-remark-prismjs": "^1.2.21", + "gatsby-transformer-remark": "^1.7.34", + "gh-pages": "^1.1.0", + "goose-css": "^0.12.0", + "mdx-loader": "^1.1.0", + "mdxc": "^1.1.1", + "numeral": "^2.0.6", + "parcels-react": "^0.12.0", + "prismjs": "^1.15.0", + "rc-slider": "^8.6.1", + "react-animation-frame": "^1.0.1", + "react-chartjs-2": "^2.7.2", + "react-helmet": "^5.2.0", + "stampy": "^0.39.0", + "unmutable": "^0.29.2" + }, + "scripts": { + "build-gatsby": "gatsby build", + "build": "yarn wasm-build && gatsby build", + "deploy": "yarn wasm-build && gatsby build --prefix-paths && gh-pages -d public", + "watch": "yarn wasm-build && gatsby develop", + "test": "echo \"Error: no test specified\" && exit 1", + "install-emcc": "./wasm/install-emcc.sh", + "path-emcc": "./wasm/path-emcc.sh", + "wasm-build": "./wasm/build.sh" + } +} diff --git a/docs/src/api/ApiView.jsx b/docs/src/api/ApiView.jsx new file mode 100644 index 0000000..8683019 --- /dev/null +++ b/docs/src/api/ApiView.jsx @@ -0,0 +1,16 @@ +// @flow +import React from 'react'; +import Code from '../component/Code'; +import {Box, Message, Text} from 'dcme-style'; + +export default () => + API + .pin() + + void pin(int pin); + + Sets the analog input pin that will be read. + + You don't need to set this if you will be using your own ADC or passing in your own values to .read() + +; diff --git a/docs/src/component/Code.jsx b/docs/src/component/Code.jsx new file mode 100644 index 0000000..a4bcbed --- /dev/null +++ b/docs/src/component/Code.jsx @@ -0,0 +1,20 @@ +// @flow +import React from 'react'; +import type {Node} from 'react'; +import Prism from 'prismjs'; +import {Terminal} from 'dcme-style'; + +type Props = { + children: Node, + modifier?: string +}; + +export default class Code extends React.Component { + render(): Node { + let {children, modifier = ""} = this.props; + let __html = Prism.highlight(children, Prism.languages.cpp, 'cpp'); + return +
+        ;
+    }
+}
diff --git a/docs/src/component/Structure.jsx b/docs/src/component/Structure.jsx
new file mode 100644
index 0000000..b9bd918
--- /dev/null
+++ b/docs/src/component/Structure.jsx
@@ -0,0 +1,30 @@
+// @flow
+import type {ComponentType} from 'react';
+import type {Node} from 'react';
+
+import React from 'react';
+
+type Props = {
+    layout?: ComponentType,
+    elements?: *
+};
+
+export default class Structure extends React.Component {
+
+    render(): Node {
+        let {layout, elements = {}} = this.props;
+        let Layout = layout || this.constructor.layout;
+        let props = this.constructor.elements.reduce((props: *, element: string): * => {
+            let fn = this[element];
+            if(!fn) {
+                throw new Error(`"${element}" method on Structure is not defined`);
+            }
+            return {
+                ...props,
+                [element]: fn
+            };
+        }, {});
+
+        return ;
+    }
+}
diff --git a/docs/src/demo/DemoCode.js b/docs/src/demo/DemoCode.js
new file mode 100644
index 0000000..dd01164
--- /dev/null
+++ b/docs/src/demo/DemoCode.js
@@ -0,0 +1,86 @@
+// @flow
+import type Parcel from 'parcels-react';
+
+let render = ({instanciations, setup, reads, values, delay}) => `#include 
+#include 
+
+${instanciations}
+
+void setup() {
+  Serial.begin(9600);${setup}
+}
+
+void loop() {
+${reads}
+
+${values}
+
+  // more code here...${delay}
+}`;
+
+export default (demoParcel: Parcel): string => {
+    let {
+        amount,
+        noisefloor,
+        smoothEnabled,
+        glideEnabled,
+        settleEnabled,
+        doubleReadEnabled,
+        smooth,
+        glide,
+        settleTime,
+        settleThreshold,
+        delay,
+        pin
+    } = demoParcel.value();
+
+    let glideIsDefault = demoParcel.get('glide').meta().isDefault;
+    let smoothIsDefault = demoParcel.get('smooth').meta().isDefault;
+    let settleThresholdIsDefault = demoParcel.get('settleThreshold').meta().isDefault;
+
+    let instances = Array(amount || 0)
+        .fill(null)
+        .map((u, index) => index + 1);
+
+    let instanciations = instances
+        .map(key => `ResponsiveAnalogRead analog${key};`)
+        .join("\n");
+
+    let renderSetup = (key) => [
+        pin && `analog${key}.pin(A${key})`,
+        noisefloor && `analog${key}.noisefloor(${noisefloor.toFixed(1)});`,
+        glideEnabled && `analog${key}.glide(${glideIsDefault ? "" : glide.toFixed(1)});`,
+        smoothEnabled && `analog${key}.smooth(${smoothIsDefault ? "" : smooth.toFixed(1)});`,
+        settleEnabled && `analog${key}.settle(${settleTime.toFixed(0)}${settleThresholdIsDefault ? "" : ", " + settleThreshold.toFixed(1)});`,
+        doubleReadEnabled && `analog${key}.doubleRead();`
+    ]
+        .filter(ii => ii)
+        .map(ii => "  " + ii)
+        .join("\n");
+
+    let setup = instances
+        .map(renderSetup)
+        .join("\n\n");
+
+    if(setup) {
+        setup = `\n\n${setup}`;
+    }
+
+    let reads = instances
+        .map(key => `  analog${key}.read();`)
+        .join("\n");
+
+    let values = instances
+        .map(key => `  Serial.print("Analog${key} value: ");\n  Serial.println(analog${key}.value());\n  if(analog${key}.hasChanged()) {\n    Serial.println("Analog${key} changed");\n  }`)
+        .join("\n\n");
+
+    let delayString = delay ? `\n  delay(${delay});` : ``;
+
+    return render({
+        instanciations,
+        setup,
+        reads,
+        values,
+        delay: delayString
+    });
+};
diff --git a/docs/src/demo/DemoCodeSettingsStructure.jsx b/docs/src/demo/DemoCodeSettingsStructure.jsx
new file mode 100644
index 0000000..4ab0aac
--- /dev/null
+++ b/docs/src/demo/DemoCodeSettingsStructure.jsx
@@ -0,0 +1,173 @@
+// @flow
+import type {ComponentType} from 'react';
+import type {Node} from 'react';
+import type Parcel from 'parcels-react';
+
+import React from 'react';
+import {PureParcel} from 'parcels-react';
+import {Box, Grid, GridItem, Text, Toggle} from 'dcme-style';
+import Structure from '../component/Structure';
+import {exp, numberToExp, numberToFloor} from '../util/ParcelModifiers';
+import DemoSliderStructure from './DemoSliderStructure';
+
+type Props = {
+    demoParcel: Parcel,
+    layout?: ComponentType,
+    sliderHeight: number
+};
+
+type LayoutProps = {
+    noisefloor: () => Node,
+    glide: () => Node,
+    smooth: () => Node,
+    settleTime: () => Node,
+    settleThreshold: () => Node,
+    glideEnabled: () => Node,
+    smoothEnabled: () => Node,
+    settleEnabled: () => Node,
+    doubleReadEnabled: () => Node
+};
+
+export default class DemoCodeSettingsStructure extends Structure {
+
+    renderSlider = ({field, label, enabled, decimal = 1}: *): Node => {
+        let {demoParcel, sliderHeight} = this.props;
+        let curve = 5;
+        let range = 50;
+        let sliderMax = exp(curve)(range);
+        let value = demoParcel
+            .get(field)
+            .value()
+            .toFixed(decimal);
+
+        return ;
+    };
+
+    renderToggle = ({field, label}: *): Node => {
+        let {demoParcel} = this.props;
+        return 
+            
+        ;
+    };
+
+    static elements = ['noisefloor', 'glide', 'smooth', 'settleTime', 'settleThreshold', 'doubleReadEnabled', 'glideEnabled', 'smoothEnabled', 'settleEnabled'];
+
+    static layout = ({noisefloor, glide, smooth, settleTime, settleThreshold, doubleReadEnabled, glideEnabled, smoothEnabled, settleEnabled}: LayoutProps): Node => {
+        return 
+            
+                
+                    {glideEnabled()}
+                    {smoothEnabled()}
+                    {settleEnabled()}
+                    {doubleReadEnabled()}
+                
+                {noisefloor()}
+                {glide()}
+                {smooth()}
+                {settleTime()}
+                {settleThreshold()}
+                
+            
+        ;
+    };
+
+    noisefloor = (): Node => {
+        let {demoParcel, sliderHeight} = this.props;
+        let {min, max} = demoParcel.value();
+        let curve = 5;
+        let range = max - min;
+        let sliderMax = exp(curve)(range);
+        let value = demoParcel
+            .get('noisefloor')
+            .value()
+            .toFixed(1);
+
+        return ;
+    };
+
+    glide = (): Node => {
+        return this.renderSlider({
+            field: 'glide',
+            label: 'glide',
+            enabled: 'glideEnabled'
+        });
+    };
+
+    smooth = (): Node => {
+        return this.renderSlider({
+            field: 'smooth',
+            label: 'smooth',
+            enabled: 'smoothEnabled'
+        });
+    };
+
+    settleTime = (): Node => {
+        return this.renderSlider({
+            field: 'settleTime',
+            label: 'settleTime',
+            enabled: 'settleEnabled',
+            decimal: 0
+        });
+    };
+
+    settleThreshold = (): Node => {
+        return this.renderSlider({
+            field: 'settleThreshold',
+            label: 'settleThreshold',
+            enabled: 'settleEnabled'
+        });
+    };
+
+    glideEnabled = (): Node => {
+        return this.renderToggle({
+            field: 'glideEnabled',
+            label: 'glide'
+        });
+    };
+
+    smoothEnabled = (): Node => {
+        return this.renderToggle({
+            field: 'smoothEnabled',
+            label: 'smooth'
+        });
+    };
+
+    settleEnabled = (): Node => {
+        return this.renderToggle({
+            field: 'settleEnabled',
+            label: 'settle'
+        });
+    };
+
+    doubleReadEnabled = (): Node => {
+        if(this.props.demoParcel.get('amount').value() < 2) {
+            return null;
+        }
+
+        return this.renderToggle({
+            field: 'doubleReadEnabled',
+            label: 'doubleRead'
+        });
+    };
+}
diff --git a/docs/src/demo/DemoControlStructure.jsx b/docs/src/demo/DemoControlStructure.jsx
new file mode 100644
index 0000000..76a5eb8
--- /dev/null
+++ b/docs/src/demo/DemoControlStructure.jsx
@@ -0,0 +1,96 @@
+// @flow
+import type {ComponentType} from 'react';
+import type {Node} from 'react';
+import type Parcel from 'parcels-react';
+
+import React from 'react';
+import Structure from '../component/Structure';
+import DemoSliderStructure from './DemoSliderStructure';
+import {numberToExp, exp, numberToFloor} from '../util/ParcelModifiers';
+import {Box, Grid, GridItem} from 'dcme-style';
+
+type Props = {
+    demoParcel: Parcel,
+    height: number,
+    layout?: ComponentType,
+    simulationParcel: Parcel
+};
+
+type LayoutProps = {
+    input: () => Node,
+    noise: () => Node,
+    raw: () => Node,
+    output: () => Node
+};
+
+export default class DemoControlStructure extends Structure {
+
+    renderSlider = ({valueParcel, label, ...otherProps}: *): Node => {
+        let {demoParcel, height} = this.props;
+        let {min, max} = demoParcel.value();
+        return ;
+    };
+
+    static elements = ['input', 'noise', 'raw', 'output'];
+
+    static layout = ({input, noise, raw, output}: LayoutProps): Node => {
+        return 
+            
+                {input()}
+                {noise()}
+                {raw()}
+                {output()}
+            
+        ;
+    };
+
+    input = (): Node => {
+        return this.renderSlider({
+            label: 'control',
+            valueParcel: this.props.demoParcel.get('input')
+        });
+    };
+
+    noise = (): Node => {
+        let {demoParcel, height} = this.props;
+        let {min, max} = demoParcel.value();
+        let curve = 5;
+        let range = max - min;
+        let sliderMax = exp(curve)(range);
+        let value = demoParcel.get('noise').value();
+        value = value.toFixed(value >= 10 ? 0 : 1);
+
+        return ;
+    };
+
+    raw = (): Node => {
+        return this.renderSlider({
+            label: 'raw',
+            disabled: true,
+            valueParcel: this.props.simulationParcel.get('raw')
+        });
+    };
+
+    output = (): Node => {
+        return this.renderSlider({
+            label: 'output',
+            disabled: true,
+            valueParcel: this.props.simulationParcel.get('output')
+        });
+    };
+}
diff --git a/docs/src/demo/DemoControlView.jsx b/docs/src/demo/DemoControlView.jsx
new file mode 100644
index 0000000..01cc451
--- /dev/null
+++ b/docs/src/demo/DemoControlView.jsx
@@ -0,0 +1,3 @@
+// @flow
+import DemoControlStructure from './DemoControlStructure';
+export default DemoControlStructure;
diff --git a/docs/src/demo/DemoGraphSettingsStructure.jsx b/docs/src/demo/DemoGraphSettingsStructure.jsx
new file mode 100644
index 0000000..f6014e8
--- /dev/null
+++ b/docs/src/demo/DemoGraphSettingsStructure.jsx
@@ -0,0 +1,97 @@
+// @flow
+import type {ComponentType} from 'react';
+import type {Node} from 'react';
+import type Parcel from 'parcels-react';
+
+import React from 'react';
+import {PureParcel} from 'parcels-react';
+import {Grid, GridItem, Input, Select, Text, Toggle} from 'dcme-style';
+import Structure from '../component/Structure';
+import {numberToString} from '../util/ParcelModifiers';
+
+type Props = {
+    demoParcel: Parcel,
+    layout?: ComponentType
+};
+
+type LayoutProps = {
+    play: () => Node,
+    max: () => Node,
+    min: () => Node,
+    zoom: () => Node
+};
+
+const ZOOM_OPTIONS = [
+    {value: "1", label: "1x"},
+    {value: "2", label: "2x"},
+    {value: "4", label: "4x"},
+    {value: "8", label: "8x"},
+    {value: "16", label: "16x"},
+    {value: "32", label: "32x"},
+    {value: "64", label: "64x"}
+];
+
+export default class DemoGraphSettingsStructure extends Structure {
+
+    static elements = ['play', 'max', 'min', 'zoom'];
+
+    static layout = ({play, zoom, min, max}: LayoutProps): Node => {
+        return 
+            {play()}
+            {zoom()}
+            {min()}
+            {max()}
+        ;
+    };
+
+    play = (): Node => {
+        let {demoParcel} = this.props;
+        return ;
+    };
+
+    max = (): Node => {
+        let {demoParcel} = this.props;
+        return ;
+    };
+
+    min = (): Node => {
+        let {demoParcel} = this.props;
+        return ;
+    };
+
+    zoom = (): Node => {
+        let {demoParcel} = this.props;
+        return