commit e58f4210bcaff9b5f24db582641f271ad8829a41
parent d403e80f994b8a43a863dc3783ed65172e585741
Author: Massimo Siboldi <mdsiboldi@gmail.com>
Date: Fri, 16 Mar 2018 21:36:12 -0700
Merge pull request #1 from mdsib/help-menu
Help menu + more
Diffstat:
12 files changed, 174 insertions(+), 22 deletions(-)
diff --git a/package-lock.json b/package-lock.json
@@ -5837,6 +5837,11 @@
"integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
"dev": true
},
+ "keymage": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/keymage/-/keymage-1.1.3.tgz",
+ "integrity": "sha1-JsZbT5TM7cBK4pQP+Az1K6/n7kE="
+ },
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
diff --git a/package.json b/package.json
@@ -9,6 +9,7 @@
"dependencies": {
"dsp.js": "^1.0.1",
"envelope-generator": "^3.0.0",
+ "keymage": "^1.1.3",
"preact": "^8.2.6",
"preact-redux": "^2.0.3",
"redux": "^3.7.2",
diff --git a/src/App/index.js b/src/App/index.js
@@ -6,6 +6,8 @@ import CircleButton from '../CircleButton/';
import HSlider from '../HSlider/';
import Param from '../Param/';
import Wheel from '../Wheel/';
+import Help from '../Help/';
+import Keybindings from '../Keybindings/';
import './App.css';
import '../iconfont/style.css';
import consts from '../consts.js';
@@ -21,7 +23,6 @@ const Adsr = (props) => (
name={aspect.name}
minVal={0}
maxVal={aspect.maxVal}
- step={0.1}
update={(newVal) => {props.update(aspect.name, newVal)}}
>
<Wheel percent={props.adsr[aspect.name] / aspect.maxVal} />
@@ -122,7 +123,7 @@ class App extends Component {
})
return (
<div
- className="App"
+ class="App"
onKeyDown={this.keyHandler}
>
<div>
@@ -155,7 +156,6 @@ class App extends Component {
name="bpm"
minVal={20}
maxVal={600}
- step={1}
val={this.props.bpm}
update={this.props.setBpm}
/>
@@ -163,7 +163,6 @@ class App extends Component {
name="beats"
minVal={3}
maxVal={16}
- step="1"
val={this.props.numBeats}
update={this.props.setNumBeats}
/>
@@ -179,6 +178,8 @@ class App extends Component {
volume={this.props.volume}
adsr={this.props.adsr}
></Synth>
+ <Help />
+ <Keybindings />
</div>
);
}
diff --git a/src/ClickOutside/index.js b/src/ClickOutside/index.js
@@ -0,0 +1,27 @@
+import { h, Component } from 'preact';
+
+export default class ClickOutside extends Component {
+ handleClick = (ev) => {
+ if (!this.elRef.contains(ev.target)) {
+ console.log('actioning');
+ this.props.action();
+ }
+ }
+ componentDidMount() {
+ console.log('mounting')
+ // setTimeout ensures the click event doesn't get triggered while mounting
+ window.setTimeout(() => {
+ document.addEventListener('click', this.handleClick);
+ }, 0);
+ }
+ componentWillUnmount() {
+ document.removeEventListener('click', this.handleClick);
+ }
+ render() {
+ return (
+ <div ref={ref => {this.elRef = ref}}>
+ {this.props.children}
+ </div>
+ );
+ }
+}
diff --git a/src/Help/Help.css b/src/Help/Help.css
@@ -0,0 +1,49 @@
+.help-modal {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(255, 255, 255, 0.3);
+}
+
+.help-container {
+ box-sizing: border-box;
+ background: aqua;
+ padding: 30px 60px;
+ margin: 30px;
+ overflow-y: auto;
+ max-height: calc(100vh - 60px);
+ max-width: 900px;
+}
+
+.help-tag {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: aqua;
+ position: fixed;
+ bottom: 25px;
+ right: 25px;
+ border-radius: 9999px;
+ height: 2em;
+ width: 2em;
+ z-index: 100;
+ cursor: pointer;
+ opacity: 0.8;
+}
+
+.help-tag:hover {
+ opacity: 1;
+}
+
+.keyboard {
+ width: 100%;
+}
+
+* {
+ text-align: left;
+}
diff --git a/src/Help/index.js b/src/Help/index.js
@@ -0,0 +1,44 @@
+import { h } from 'preact';
+import { connect } from 'preact-redux';
+import ClickOutside from '../ClickOutside/';
+import './Help.css';
+
+const setHelpOpen = (value) => ({type: 'SET_HELP_OPEN', value});
+
+export default connect(state => ({open: state.helpOpen}), {setHelpOpen})((props) => {
+ const closeIfOpen = () => {
+ if (props.open)
+ props.setHelpOpen(false);
+ }
+ const modal = props.open ? (
+ <div class="help-modal">
+ <ClickOutside action={closeIfOpen}>
+ <div class="help-container">
+ <h1>Help</h1>
+ <h2>Keybindings</h2>
+ <img class="keyboard" src="../../AudioKeys/images/audiokeys-mapping-rows1.jpg" />
+ <h2>About</h2>
+ Synthing allows you to experiment with and visualize a waveform's effect on generated sound. Works best in Chrome.
+ <h2>Overview</h2>
+ <ul>
+ <li>Draw on the waveform to alter it.</li>
+ <li>Use the sequencer to activate different waveforms at different times. You can mute, solo, and mix waveforms.</li>
+ <li>Press play to start the sequencer, and feel free to change the bpm to make it faster or slower.</li>
+ </ul>
+ </div>
+ </ClickOutside>
+ </div>
+ ) : '';
+
+ return (
+ <div class="help">
+ <div
+ class="help-tag"
+ onClick={props.setHelpOpen.bind(null, !props.open)}
+ >
+ ?
+ </div>
+ {modal}
+ </div>
+ );
+})
diff --git a/src/Param/index.js b/src/Param/index.js
@@ -4,25 +4,34 @@ import './style.css';
export default class Param extends Component {
componentDidMount() {
+ const setUpMove = () => {
+ this.internalVal = this.props.val;
+ }
const handleMove = (ev) => {
- let step = this.props.step || 0.5;
- let newVal = this.props.val - (ev.movementY * step);
- if (typeof(this.props.minVal) === 'number' && (newVal < this.props.minVal)) {
- newVal = this.props.minVal;
- } else if (typeof(this.props.maxVal) === 'number' && (newVal > this.props.maxVal)) {
- newVal = this.props.maxVal;
- }
- this.props.update(newVal);
+ // calculate how much to adjust the value given the
+ // min and max values of the param, the speed of the mouse movement,
+ // and the precision of the param.
+ const maxMov = 300 / (this.props.precision ? this.props.precision + 1 : 1);
+ const ratioMov = ev.movementY / maxMov;
+ const expRatioMov = -Math.sign(ratioMov) * Math.pow(Math.abs(ratioMov), 1.3);
+ const newVal = (expRatioMov * (this.props.maxVal - this.props.minVal)) + this.internalVal;
+ this.internalVal = helpers.bounded(
+ newVal,
+ this.props.minVal,
+ this.props.maxVal
+ );
+ this.props.update(Number(this.internalVal.toFixed(this.props.precision)));
}
- helpers.clickNDrag(this.paramRef, null, handleMove, null);
+ helpers.clickNDrag(this.paramRef, setUpMove, handleMove, null);
}
handleChange(e) {
this.props.update(e.target.value);
}
getNumString() {
- return (this.props.precision ?
- this.props.val.toFixed(this.props.precision) :
- this.props.val) + (this.props.suffix || '');
+ return (this.props.precision !== undefined && this.props.val.toFixed
+ ? this.props.val.toFixed(this.props.precision)
+ : this.props.val)
+ + (this.props.suffix || '');
}
render() {
const inputId = `param-${this.props.name}`;
diff --git a/src/WaveEditor/index.js b/src/WaveEditor/index.js
@@ -74,7 +74,7 @@ export default class waveEditor extends Component {
class="wave-editor"
ref={(div) => {this.divRef = div}}
>
- <WaveTable waveform={this.props.waveform} />
+ <WaveTable resize={true} waveform={this.props.waveform} />
</div>
);
}
diff --git a/src/WaveManager/index.js b/src/WaveManager/index.js
@@ -52,7 +52,6 @@ export default class WaveManager extends Component {
<div class="beats">
<div onClick={this.props.activate}>
<WaveTable
- resize={true}
height={40}
width={75}
waveform={this.props.tone.waveform.slice()}
diff --git a/src/WaveTable/index.js b/src/WaveTable/index.js
@@ -1,4 +1,4 @@
-import { h, Component} from 'preact';
+import { h, Component } from 'preact';
import helpers from '../helpers';
import consts from '../consts';
import './style.css';
@@ -33,8 +33,9 @@ export default class WaveTable extends Component {
}
if (this.props.resize) {
window.addEventListener('resize', (ev) => {
+ drawArea(this.props.waveform, this.canvasRef);
this.forceUpdate();
- })
+ });
}
}
componentWillReceiveProps(newProps) {
@@ -42,7 +43,7 @@ export default class WaveTable extends Component {
}
render() {
const height = this.props.height || 400;
- const width = this.props.width || window.innerWidth - 120;
+ const width = this.props.width || window.innerWidth - 100;
return (
<canvas
class="wave-table"
diff --git a/src/helpers.js b/src/helpers.js
@@ -11,7 +11,16 @@ export default {
partial,
oneTime,
linear: (m, x, b) => (m * x) + b,
- bounded: (val, min, max) => val < min ? min : (val > max ? max : val),
+ bounded: (val, min, max) => {
+ if (min !== undefined && min !== null && val < min) {
+ return min;
+ }
+ else if (max !== undefined && max !== null && val > max) {
+ return max;
+ }
+ else
+ return val;
+ },
scale: (buf, amt) => buf.map(val => val * amt),
add: (arr1, arr2) => arr1.map((v, i) => v + arr2[i]),
soon: (fn, ms=0) => {
diff --git a/src/store.js b/src/store.js
@@ -11,6 +11,7 @@ const initialState = {
playing: false,
numBeats,
editingToneIdx: 0,
+ helpOpen: false,
adsr: {
attack: 0.3,
decay: 1,
@@ -133,6 +134,12 @@ const globalReducer = (state, action) => {
state.tones.length - 2
);
break;
+ case 'SET_HELP_OPEN':
+ updates.helpOpen = action.value;
+ break;
+ case 'TOGGLE_HELP_OPEN':
+ updates.helpOpen = !state.helpOpen;
+ break;
default:
break;
}