synthing

a waveform sequencing synth on the web
Log | Files | Refs | Submodules

commit c9db96557ad4f3ef4f86491ecced10c9cec07851
parent f46244201a8f673b964a016208c249db860f2945
Author: Massimo Siboldi <mdsiboldi@gmail.com>
Date:   Wed,  6 Dec 2017 15:31:30 -0800

A set of bulk changes. Fews days worth, all together in one commit!

Diffstat:
Mlog.org | 7+++++++
Msrc/App/index.js | 164++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/Polyphonic.js | 13++++++++-----
Msrc/Synth/.tern-port | 4++--
Msrc/Synth/index.js | 4+---
Msrc/WaveEditor/index.js | 30++++++++++++++++++------------
Asrc/WaveManager/.tern-port | 2++
Asrc/WaveManager/index.js | 22++++++++++++++++++++++
Asrc/WaveMaster.js | 17+++++++++++++++++
Asrc/Waves.js | 0
10 files changed, 206 insertions(+), 57 deletions(-)

diff --git a/log.org b/log.org @@ -11,3 +11,10 @@ ** the synth *** whenever the wave changes, it should be put into the osc node *** whenever settings in UI change, they should change the nodes related to those settings. +* <2017-12-03 Sun 19:18> +** waveform becomes an array +*** DONE be able to update, remove waveform at idx i +*** DONE have convenience methods to update "active" waveform, acting the same as before +*** DONE be able to change active waveform +* <2017-12-03 Sun 21:35> +here's an idea: make modules in vanilla js and interact with them via react. We'll get some reusability in other projects and keep react doing viewstuff, not statelogicstuff diff --git a/src/App/index.js b/src/App/index.js @@ -1,69 +1,163 @@ import { h, Component } from 'preact'; import WaveEditor from '../WaveEditor/'; +import WaveManager from '../WaveManager/'; import Synth from '../Synth/'; import './App.css'; import consts from '../consts.js'; -import helpers from '../helpers.js'; + +const initialWave = new Array(consts.BUF_SIZE).fill(0); + +const immObjArray = { + update: (arr, idx, opts) => { + const newArr = arr.slice(); + newArr[idx] = Object.assign({}, arr[idx], opts) + return newArr; + }, + add: (arr, idx, opts) => { + const newArr = arr.slice(); + newArr.splice(idx, 0, opts); + return newArr; + }, + remove: (arr, idx) => { + const newArr = arr.slice(); + newArr.splice(idx, 1); + return newArr; + } +} + +const boolArray = { + setLength: (ba, newLength) => { + let newBa = ba.slice(0, newLength); + if (ba.length < newLength) { + newBa = newBa.concat(new Array(newLength - ba.length).fill(false)); + } + return newBa; + }, + create: (length) => { + return new Array(length).fill(false); + }, + update: (ba, idx, val) => { + const newBa = ba.slice(); + newBa[idx] = val; + return newBa; + } +} + + class App extends Component { constructor() { super(); + let initBeats = 4; this.state = { - waveform: new Array(consts.BUF_SIZE).fill(0.5), + waveforms: [{ + waveform: initialWave.slice(), + beats: boolArray.create(initBeats) + }], + numBeats: initBeats, + editingWaveformIdx: 0, mouseData: { down: false, pos: {x: 0, y: 0} } } } - updateWaveform = (waveform) => { - if (!waveform) { - waveform = new Array(consts.BUF_SIZE).fill(0.0); - } + + editingWaveform = () => this.state.waveforms[this.state.editingWaveformIdx].waveform + + updateWaveform = (idx = this.state.editingWaveformIdx, opts) => { this.setState({ - waveform + waveforms: immObjArray.update(this.state.waveforms, idx, opts) }); } - handleMouseMove = (ev) => { + + removeWaveform = (idx) => { + const waveforms = immObjArray.remove(this.state.waveforms, idx); this.setState({ - mouseData: Object.assign({}, this.state.mouseData, { - x: ev.x, - y: ev.y, - }) + waveforms, + editingWaveformIdx: Math.min( + this.state.editingWaveformIdx, + waveforms.length - 1 + ) }); } - handleMouseDown = (ev) => { - this.setState({ - mouseData: Object.assign({}, this.state.mouseData, { - down: true, - downTarget: ev.target - }) + + changeEditingWaveform = (i) => this.setState({editingWaveformIdx: i}) + + addWaveform = (waveform = new Array(consts.BUF_SIZE).fill(0), at = this.state.waveforms.length, isEditing = false) => { + const waveforms = immObjArray.add(this.state.waveforms, at, { + waveform, + beats: boolArray.create(this.state.numBeats) }); + + const state = { + waveforms + }; + + if (isEditing) { + state.editingWaveformIdx = at; + } + + this.setState(state); } - handleMouseUp = (ev) => { + setBeats = (newNumBeats) => { + newNumBeats = Math.max(newNumBeats, 1); this.setState({ - mouseData: Object.assign({}, this.state.mouseData, { - down: false, - downTarget: null + numBeats: newNumBeats, + waveforms: this.state.waveforms.map((val, idx) => { + let ret = Object.assign({}, val, { + beats: boolArray.setLength(val.beats, newNumBeats) + }); + return ret; }) - }); - }; + }) + } + render() { + const waves = this.state.waveforms.map((form, idx) => { + return ( + <WaveManager + activate={this.changeEditingWaveform.bind(this, idx)} + remove={this.removeWaveform.bind(this, idx)} + duplicate={() => { + let pleaseActivate = false; + if (this.state.editingWaveformIdx === idx) { + pleaseActivate = true; + } + this.addWaveform(this.state.waveforms[idx].waveform.slice(), idx + 1, pleaseActivate); + }} + activated={idx === this.state.editingWaveformIdx} + beats={this.state.waveforms[idx].beats} + updateBeat={(i, val) => { + console.log('updating', i, val); + this.updateWaveform(idx, { + beats: boolArray.update( + this.state.waveforms[idx].beats, + i, + val + ) + }); + }} + ></WaveManager> + ); + }) return ( - <div - className="App" - onMouseDown={this.handleMouseDown} - onMouseMove={this.handleMouseMove} - onMouseUp={this.handleMouseUp} - > - <button onClick={() => this.updateWaveform()}>click to update</button> + <div + className="App" + > <WaveEditor mouseData={this.state.mouseData} - waveform={this.state.waveform} - updateWaveform={this.updateWaveform} + waveform={this.editingWaveform()} + updateWaveform={(waveform) => { + this.updateWaveform(this.state.editingWaveformIdx, {waveform}); + }} ></WaveEditor> - <Synth waveform={this.state.waveform}></Synth> - </div> + <button onClick={() => {this.setBeats(this.state.numBeats + 1)}}>+ beat</button> + <button onClick={() => {this.setBeats(this.state.numBeats - 1)}}>- beat</button> + <button onClick={() => this.addWaveform()}>+</button> + {waves} + <Synth waveform={this.editingWaveform()}></Synth> + </div> ); } } diff --git a/src/Polyphonic.js b/src/Polyphonic.js @@ -6,6 +6,7 @@ export default class Polyphonic { constructor(audioContext) { this.voices = []; this.audioContext = audioContext; + this.periodicWave = null; } addVoice(note) { let osc = this.audioContext.createOscillator(); @@ -13,8 +14,8 @@ export default class Polyphonic { gain.gain.value = 0.5; osc.connect(gain); osc.frequency.value = note.frequency; - if (this.wave) { - osc.setPeriodicWave(this.wave); + if (this.periodicWave) { + osc.setPeriodicWave(this.periodicWave); } gain.connect(this.audioContext.destination); osc.start(); @@ -34,10 +35,12 @@ export default class Polyphonic { } changeWave(waveform) { FFT.forward(waveform); - const periodicWave = this.ac.createPeriodicWave(new Float32Array(FFT.real), - new Float32Array(FFT.imag)); + this.periodicWave = this.audioContext.createPeriodicWave( + new Float32Array(FFT.real), + new Float32Array(FFT.imag) + ); for (let voice of this.voices) { - voice.osc.setPeriodicWave(periodicWave); + voice.osc.setPeriodicWave(this.periodicWave); } } } diff --git a/src/Synth/.tern-port b/src/Synth/.tern-port @@ -1 +1 @@ -32787 -\ No newline at end of file +39991 +\ No newline at end of file diff --git a/src/Synth/index.js b/src/Synth/index.js @@ -1,6 +1,6 @@ import { Component } from 'preact'; import Polyphonic from '../Polyphonic'; -import AudioKeys from '../../AudioKeys/dist/audiokeys.min.js'; +import AudioKeys from '../../AudioKeys/dist/audiokeys.js'; export default class Synth extends Component { componentWillMount() { @@ -10,7 +10,6 @@ export default class Synth extends Component { return false; } componentWillReceiveProps(newProps) { - if (this.props.waveform !== newProps.waveform); updateAudio(newProps.waveform); } render() { @@ -30,7 +29,6 @@ function startAudio() { keyboard.up((note) => { P.removeVoice(note); }); - } function updateAudio(waveform) { diff --git a/src/WaveEditor/index.js b/src/WaveEditor/index.js @@ -37,6 +37,23 @@ const smoothZoneRange = function (waveData, begin, end) { export default class waveEditor extends Component { componentDidMount() { drawArea(this.props.waveform, this.canvasRef); + const handleMove = (ev) => { + this.updateWaveform({ + x: ev.x, + y: ev.y + }, this.props.waveform); + } + document.addEventListener('mousedown', (ev) => { + if (ev.target === this.canvasRef) { + document.addEventListener('mousemove', handleMove); + } + }); + document.addEventListener('mouseup', (ev) => { + document.removeEventListener('mousemove', handleMove); + this.setState({ + prevZone: null + }); + }); } componentWillUpdate() { return false; @@ -74,27 +91,16 @@ export default class waveEditor extends Component { } componentWillReceiveProps(newProps) { - const drawing = newProps.mouseData.down && - newProps.mouseData.downTarget === this.canvasRef; - if (newProps.waveform !== this.props.waveform) { drawArea(newProps.waveform, this.canvasRef); } - else if (drawing) { - this.updateWaveform(newProps.mouseData, newProps.waveform); - } - if (!drawing) { - this.setState({ - prevZone: null - }) - } } render() { return ( <canvas height={400} width={800} - ref={(canvas) => this.canvasRef = canvas}> + ref={(canvas) => {this.canvasRef = canvas}}> </canvas> ); } diff --git a/src/WaveManager/.tern-port b/src/WaveManager/.tern-port @@ -0,0 +1 @@ +36609 +\ No newline at end of file diff --git a/src/WaveManager/index.js b/src/WaveManager/index.js @@ -0,0 +1,22 @@ +import { h, Component} from 'preact'; + +export default class WaveManager extends Component { + render() { + console.log(this.props.beats); + return ( + <div class="wave-manager"> + <div class="buttons"> + {this.props.activated ? 'x' : ''} + <button onClick={this.props.activate}>activate</button> + <button onClick={this.props.remove}>remove</button> + <button onClick={this.props.duplicate}>dupe</button> + </div> + <div class="beats"> + {this.props.beats.map((val, idx) => { + return <input type="checkbox" checked={val} onChange={(ev) => {this.props.updateBeat(idx, ev.target.checked)}}></input> + })} + </div> + </div> + ); + } +} diff --git a/src/WaveMaster.js b/src/WaveMaster.js @@ -0,0 +1,17 @@ +export default { + update: (arr, idx, opts) => { + const newArr = arr.slice(); + Object.assign({}, arr[idx], opts) + return newArr; + } + add: (arr, idx, opts) => { + const newArr = arr.slice(); + newArr.splice(idx, 0, opts); + return newArr; + } + remove: (arr, idx) => { + const newArr = arr.slice(); + newArr.splice(idx, 1); + return newArr; + } +} diff --git a/src/Waves.js b/src/Waves.js