├── .gitignore ├── LICENSE ├── README.md ├── assets ├── buffers.json ├── maps.json ├── patches │ ├── aerial drone.json │ ├── dark factory.json │ ├── ocean.json │ ├── rain forest.json │ └── temple rain.json └── tilesheet.png ├── audio ├── EMT244 1s.json ├── EMT244 1s.mp3 ├── EP Chicago.json ├── EP Chicago.mp3 ├── Fat Bass.json ├── Fat Bass.mp3 ├── aerial.mp3 ├── after daydream.mp3 ├── angelus.json ├── angelus.mp3 ├── atrem rain.json ├── atrem rain.mp3 ├── belllong.json ├── belllong.mp3 ├── binaural rain.mp3 ├── boat.json ├── boat.mp3 ├── campfire.json ├── campfire.mp3 ├── car passing.json ├── car passing.mp3 ├── cicada summer.json ├── cicada summer.mp3 ├── cicada temple.mp3 ├── city1.json ├── city1.mp3 ├── clock.json ├── clock.mp3 ├── crossroads.json ├── crossroads.mp3 ├── crume1.mp3 ├── crystal cave.mp3 ├── cuckoo.json ├── cuckoo.mp3 ├── damu drums1.json ├── damu drums1.mp3 ├── damu drums2.mp3 ├── disco tom.json ├── drop.json ├── drop.mp3 ├── dusk to night.json ├── dusk to night.mp3 ├── equatorial forest.json ├── equatorial forest.mp3 ├── factory hall.json ├── factory hall.mp3 ├── fan.json ├── fan.mp3 ├── fireworks.json ├── fireworks.mp3 ├── fishdog.json ├── fishdog.mp3 ├── forest.mp3 ├── fridge.json ├── fridge.mp3 ├── gangas.json ├── gangas.mp3 ├── harbor.json ├── harbor.mp3 ├── hats line.json ├── hexagonal room.json ├── hexagonal room.mp3 ├── highway.json ├── highway.mp3 ├── hihat.json ├── hihat.mp3 ├── ice bubbles.json ├── ice bubbles.mp3 ├── ice cube.json ├── ice cube.mp3 ├── ice cube2.json ├── ice cube2.mp3 ├── jungle.json ├── jungle.mp3 ├── mechanics1.json ├── mechanics1.mp3 ├── mechanics2.json ├── mechanics2.mp3 ├── mechanics3.json ├── mechanics3.mp3 ├── mechanics4.json ├── mechanics4.mp3 ├── mechanics5.json ├── mechanics5.mp3 ├── mechanics6.json ├── mechanics6.mp3 ├── mechanics7.json ├── mechanics7.mp3 ├── mechanics8.json ├── mechanics8.mp3 ├── medium room.json ├── medium room.mp3 ├── motor1.json ├── motor1.mp3 ├── motor2.json ├── motor2.mp3 ├── motor3.json ├── motor3.mp3 ├── ocean crushing.json ├── ocean crushing.mp3 ├── old device.json ├── old device.mp3 ├── pebbles.json ├── pebbles.mp3 ├── pen.json ├── pen.mp3 ├── pool1.json ├── pool1.mp3 ├── pool2.json ├── pool2.mp3 ├── pool3.json ├── pool3.mp3 ├── pool4.json ├── pool4.mp3 ├── railway station.json ├── railway station.mp3 ├── rooster.json ├── rooster.mp3 ├── sailing ship.json ├── sailing ship.mp3 ├── sea pebbles.json ├── sea pebbles.mp3 ├── sea rock.json ├── sea rock.mp3 ├── sea sand.json ├── sea sand.mp3 ├── singing bowl.json ├── singing bowl.mp3 ├── stock exchange.json ├── stock exchange.mp3 ├── strangehead.json ├── strangehead.mp3 ├── summer night.json ├── summer night.mp3 ├── suzu1.json ├── suzu1.mp3 ├── suzu2.json ├── suzu2.mp3 ├── suzu3.json ├── suzu3.mp3 ├── suzu4.json ├── suzu4.mp3 ├── theater.json ├── theater.mp3 ├── train1.json ├── train1.mp3 ├── triangular room.json ├── triangular room.mp3 ├── tropical forest.json ├── tropical forest.mp3 ├── tunnel.json ├── tunnel.mp3 ├── ueno gong1.json ├── ueno gong1.mp3 ├── ueno gong2.json ├── ueno gong2.mp3 ├── ueno gong3.json ├── ueno gong3.mp3 ├── water runoff.json ├── water runoff.mp3 ├── water spring.json ├── water spring.mp3 ├── water stream.json ├── water stream.mp3 ├── white noise.json ├── woodpecker peck.json ├── woodpecker peck.mp3 ├── woodpecker squeak.json ├── woodpecker squeak.mp3 └── x_b.mp3 ├── build ├── editor.js ├── index.js ├── modular.min.js ├── styles.css └── synthEdit.css ├── editor.html ├── fonts ├── Big Pixel demo.otf ├── kongtext.ttf ├── november.ttf └── telegrama.otf ├── img ├── background.png ├── close.png ├── connector │ ├── in-B-fill.png │ ├── in-B.png │ ├── in-G-fill.png │ ├── in-G.png │ ├── in-R-fill.png │ ├── in-R.png │ ├── in-Y-fill.png │ ├── in-Y.png │ ├── out-B-fill.png │ ├── out-B.png │ ├── out-G-fill.png │ ├── out-G.png │ ├── out-R-fill.png │ ├── out-R.png │ ├── out-Y-fill.png │ └── out-Y.png ├── iconApplication.png ├── iconIR.png ├── iconLoop.png ├── iconSave.png ├── iconShot.png ├── iconSine.png ├── jack-connect.png ├── jack-free.png ├── knob.png ├── knobMark.png └── syntheditBG.png ├── index.html ├── main.js ├── package.json ├── project.pixelbox ├── settings.json ├── src ├── core │ ├── AudioConnector.js │ ├── Buffer.js │ ├── Button.js │ ├── Cable.js │ ├── Connector.js │ ├── EventConnector.js │ ├── EventEmitter.js │ ├── Knob.js │ ├── MIDI.js │ ├── Module.js │ ├── ParamConnector.js │ ├── Patch.js │ ├── audioContext.js │ ├── connectors.js │ ├── moduleCategories.js │ ├── modules.js │ └── utils.js ├── data │ ├── BufferData.js │ ├── ProceduralBuffer.js │ └── dataTypes.js ├── editor.js ├── loaders │ ├── loadAudioBuffer.js │ └── sendRequest.js ├── main.js ├── modular.js ├── modules │ ├── Amp.js │ ├── AutoBang.js │ ├── AutoXFade.js │ ├── Bang.js │ ├── BufferSlice.js │ ├── BufferTrim.js │ ├── Context.js │ ├── ControlChange.js │ ├── Convolver.js │ ├── DateBang.js │ ├── Delay.js │ ├── Envelope.js │ ├── EventDelay.js │ ├── EventPool.js │ ├── Fade.js │ ├── Filter.js │ ├── FilterMod.js │ ├── Gain.js │ ├── LFO.js │ ├── MidiIn.js │ ├── ModDelay.js │ ├── ModPanner.js │ ├── NoteDetect.js │ ├── OnLoadBang.js │ ├── OneShotSampler.js │ ├── Oscillator.js │ ├── Panner.js │ ├── PlaybackRate.js │ ├── RandomBang.js │ ├── Sampler.js │ ├── SlowLFO.js │ ├── TestModule.js │ ├── Volume.js │ ├── XFadeSampler.js │ ├── index.js │ └── noteOnFilter.js ├── synthesizers │ ├── disco │ │ ├── editor.js │ │ └── index.js │ ├── hats │ │ ├── editor.js │ │ └── index.js │ ├── index.js │ └── noize │ │ └── index.js └── ui │ ├── Panel.js │ ├── audioEditor.js │ ├── beforeClose.js │ ├── bufferLibrary.js │ ├── buttonGUI.js │ ├── cableGUI.js │ ├── connectorGUI.js │ ├── connectorMenu.js │ ├── constants.js │ ├── domUtils.js │ ├── dropFile.js │ ├── knobGUI.js │ ├── menuHeader.js │ ├── moduleGUI.js │ ├── moduleLibrary.js │ ├── moduleManager.js │ ├── onWindowResize.js │ ├── overlay.js │ └── synthEditor │ ├── Container.js │ ├── Knob.js │ ├── Label.js │ ├── SynthEditorPanel.js │ ├── TextInput.js │ ├── constants.js │ └── index.js └── tools ├── commands.js ├── commands └── audio.js └── settings.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Dependency directory 3 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 4 | node_modules 5 | 6 | # Ignore Mac OS files 7 | .DS_Store 8 | 9 | # Ignore archive build 10 | build.zip 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Cedric Stoquer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![modularlogo](https://cloud.githubusercontent.com/assets/2462139/22728956/d35dafa2-ee23-11e6-92d0-35c3ee9c479f.png) 3 | 4 | # webAudio doodle 5 | 6 | *Modular* is a cool interface to easily create and test webAudio routing configurations. 7 | Similar to an analog modular synthesizer, you add *modules* in a *patch*. 8 | A module can be a simple webAudio feature or a more complex configuration 9 | and can holds some controls in the form of *knob*, *button*, etc. 10 | Then, the modules can simply be connected together with cables. 11 | 12 | UI is strongly influenced by Clavia's *Nord Modular*'s editor. 13 | 14 | ![modular](https://cloud.githubusercontent.com/assets/2462139/22196853/96e456cc-e192-11e6-873a-3a63371107f5.png) 15 | 16 | 17 | # [Try the Online Demo!](http://cstoquer.github.io/modular/) 18 | 19 | Nota: Save and Load features are disabled in this demo. 20 | Patch can be exported (`top menu > Patch > Export patch`), and imported by drag & dropping a patch file in the window. 21 | 22 | ![construct](https://cloud.githubusercontent.com/assets/2462139/23090673/19d6b99a-f5e7-11e6-9426-d805ed32de74.gif) 23 | 24 | # Features 25 | 26 | ## Built-in buffer editor 27 | 28 | ![waveedit](https://cloud.githubusercontent.com/assets/2462139/22394204/e3917d2e-e55c-11e6-9ac7-6904c9961e55.png) 29 | 30 | View the waveform of the audio buffers. 31 | Edit loop points, tags and properties of the audio files and save these as a meta file. 32 | 33 | ## Procedural buffer and synth editor 34 | 35 | ![gif](https://user-images.githubusercontent.com/2462139/28860257-c719a258-7796-11e7-8681-baffa629dcae.gif) 36 | 37 | Generate audio buffer with built-in synthesizers, or program your own synth with their own GUI using a simple `SynthEditor` API. 38 | 39 | ## MIDI support 40 | 41 | If your browser supports webMIDI, you can have your MIDI keyboard or controller send events to modules. 42 | 43 | ![midi](https://cloud.githubusercontent.com/assets/2462139/22738936/ec574c4c-ee4c-11e6-9ff6-c589e8ee1034.png) 44 | 45 | ## Search audio in the library 46 | 47 | Filter the audio files in library by type and tag. 48 | 49 | ![tag](https://cloud.githubusercontent.com/assets/2462139/23090564/12689672-f5e4-11e6-9af2-830282961982.gif) 50 | -------------------------------------------------------------------------------- /assets/maps.json: -------------------------------------------------------------------------------- 1 | { 2 | "_type": "maps", 3 | "maps": [ 4 | { 5 | "w": 8, 6 | "h": 4, 7 | "name": "map", 8 | "sheet": "", 9 | "data": "!!|J" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /assets/patches/aerial drone.json: -------------------------------------------------------------------------------- 1 | {"_type":"modularPatch","version":1,"modules":[{"_type":"LFO","id":0,"x":2,"y":17,"controls":{"frequency":9.189118911891228},"persistent":[0]},{"_type":"Volume","id":1,"x":2,"y":20,"controls":{"volume":41}},{"_type":"Volume","id":2,"x":3,"y":19,"controls":{"volume":68}},{"_type":"Context","id":3,"x":4,"y":26},{"_type":"ModPanner","id":4,"x":3,"y":17},{"_type":"FilterMod","id":5,"x":3,"y":14,"controls":{},"persistent":[1]},{"_type":"Sampler","id":6,"x":3,"y":10,"controls":{"rate":-6.341708542713576}},{"_type":"Buffer","id":7,"x":3,"y":8,"arguments":[{"_type":"BufferData","id":"damu drums1","uri":"audio/damu drums1.mp3","loop":true,"ir":false,"start":0.027,"end":-0.017,"tag":[]}]},{"_type":"RandomBang","id":8,"x":1,"y":8,"controls":{"min":-62.505050505050505,"max":-53.898989898989896}},{"_type":"Buffer","id":9,"x":4,"y":10,"arguments":[{"_type":"BufferData","id":"ueno gong2","uri":"audio/ueno gong2.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","temple","japan"]}]},{"_type":"Buffer","id":10,"x":5,"y":6,"arguments":[{"_type":"BufferData","id":"aerial","uri":"audio/aerial.mp3","loop":true,"ir":false,"start":0.027,"end":-0.033,"tag":[]}]},{"_type":"Delay","id":11,"x":7,"y":14,"controls":{"delay":-21.373737373737356}},{"_type":"Volume","id":12,"x":6,"y":12,"controls":{"volume":38}},{"_type":"Filter","id":13,"x":6,"y":15,"controls":{"cut":-25.212765957446777,"res":-67.6},"persistent":[0]},{"_type":"Volume","id":14,"x":5,"y":11,"controls":{"volume":22}},{"_type":"Sampler","id":15,"x":5,"y":7,"controls":{"rate":58.658291457286424}},{"_type":"Volume","id":16,"x":5,"y":20,"controls":{"volume":-11}},{"_type":"Fade","id":17,"x":2,"y":9,"controls":{"target":-14,"duration":-37.3467336683417}},{"_type":"AutoBang","id":18,"x":1,"y":12,"controls":{"duration":-35}},{"_type":"RandomBang","id":19,"x":4,"y":7,"controls":{"min":-60.50505050505052,"max":-38.898989898989896}},{"_type":"Fade","id":20,"x":2,"y":12,"controls":{"target":-68,"duration":-62.3467336683417}},{"_type":"OneShotSampler","id":21,"x":4,"y":15,"controls":{"volume":-10}},{"_type":"Filter","id":22,"x":4,"y":18,"controls":{"cut":-28.212765957446777,"res":-54.599999999999994},"persistent":[0]},{"_type":"Buffer","id":23,"x":4,"y":11,"arguments":[{"_type":"BufferData","id":"ueno gong1","uri":"audio/ueno gong1.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","temple","japan"]}]},{"_type":"Buffer","id":24,"x":4,"y":12,"arguments":[{"_type":"BufferData","id":"ueno gong3","uri":"audio/ueno gong3.mp3","loop":false,"ir":false,"start":0.029462585034013604,"end":7.940789115646258,"tag":["bell","temple","japan"]}]},{"_type":"EventPool","id":25,"x":4,"y":13,"controls":{}},{"_type":"Convolver","id":26,"x":4,"y":22,"controls":{}},{"_type":"Buffer","id":27,"x":3,"y":23,"arguments":[{"_type":"BufferData","id":"factory hall","uri":"audio/factory hall.mp3","loop":false,"ir":true,"start":0,"end":0,"tag":["real space","room","reverb"]}]}],"cables":["0:OUT--1:IN","1:OUT--4:pan","2:OUT--3:DEST","2:IN--4:OUT","7:data--6:buffer","11:OUT--13:IN","13:OUT--12:IN","12:OUT--11:IN","10:data--15:buffer","15:OUT--14:IN","14:OUT--12:IN","15:OUT--16:IN","16:IN--13:OUT","16:OUT--3:DEST","6:OUT--5:IN","5:OUT--4:IN","5:CUT--17:OUT","18:OUT--20:TRG","20:OUT--5:CUT","8:OUT--17:TRG","19:OUT--21:trigger","21:OUT--22:IN","24:data--25:IN","23:data--25:IN","9:data--25:IN","25:OUT--21:buffer","19:OUT--25:TRG","27:data--26:buffer","3:DEST--26:OUT","26:IN--22:OUT"]} -------------------------------------------------------------------------------- /assets/patches/ocean.json: -------------------------------------------------------------------------------- 1 | {"_type":"modularPatch","version":1,"modules":[{"_type":"Buffer","id":0,"x":3,"y":6,"arguments":[{"_type":"BufferData","id":"campfire","uri":"audio/campfire.mp3","loop":true,"ir":false,"start":0.02844897959183674,"end":-0.015,"tag":["field","fire","night"]}]},{"_type":"Buffer","id":1,"x":2,"y":11,"arguments":[{"_type":"BufferData","id":"pebbles","uri":"audio/pebbles.mp3","loop":true,"ir":false,"start":0.027066666666666666,"end":17.884533333333337,"tag":["ocean","pebble","wave"]}]},{"_type":"Buffer","id":2,"x":4,"y":11,"arguments":[{"_type":"BufferData","id":"ocean crushing","uri":"audio/ocean crushing.mp3","loop":true,"ir":false,"start":0.02903945578231292,"end":19.20509387755102,"tag":["ocean","wave"]}]},{"_type":"Sampler","id":3,"x":4,"y":12,"controls":{"rate":-10.341708542713576}},{"_type":"Sampler","id":4,"x":3,"y":7,"controls":{"rate":-32.341708542713576}},{"_type":"Sampler","id":5,"x":2,"y":12,"controls":{"rate":-1.341708542713576}},{"_type":"Volume","id":6,"x":4,"y":15,"controls":{"volume":44}},{"_type":"Volume","id":7,"x":3,"y":10,"controls":{"volume":-31}},{"_type":"Volume","id":8,"x":2,"y":15,"controls":{"volume":-29}},{"_type":"Context","id":9,"x":3,"y":22},{"_type":"Filter","id":10,"x":3,"y":13,"controls":{"cut":-7.2127659574468055,"res":-50.599999999999994},"persistent":[1]},{"_type":"Buffer","id":11,"x":1,"y":12,"arguments":[{"_type":"BufferData","id":"sailing ship","uri":"audio/sailing ship.mp3","loop":true,"ir":false,"start":0.029028571428571428,"end":19.877314285714288,"tag":["boat","ocean"]}]},{"_type":"Sampler","id":12,"x":1,"y":13,"controls":{"rate":-37.341708542713576}},{"_type":"Volume","id":13,"x":1,"y":16,"controls":{"volume":-34}}],"cables":["6:OUT--9:DEST","6:IN--3:OUT","2:data--3:buffer","0:data--4:buffer","4:OUT--7:IN","5:OUT--8:IN","1:data--5:buffer","7:OUT--10:IN","10:OUT--9:DEST","8:OUT--9:DEST","12:OUT--13:IN","11:data--12:buffer","13:OUT--9:DEST"]} -------------------------------------------------------------------------------- /assets/patches/rain forest.json: -------------------------------------------------------------------------------- 1 | {"_type":"modularPatch","version":1,"modules":[{"_type":"Buffer","id":0,"x":1,"y":2,"arguments":[{"_type":"BufferData","id":"forest","uri":"audio/forest.mp3","loop":true,"ir":false,"start":0.027,"end":-0.021,"tag":["field","nature","forest","bird"]}]},{"_type":"Buffer","id":1,"x":0,"y":11,"arguments":[{"_type":"BufferData","id":"water stream","uri":"audio/water stream.mp3","loop":true,"ir":false,"start":0.04086202504683514,"end":-0.035,"tag":["field","water","stream"]}]},{"_type":"SlowLFO","id":2,"x":4,"y":11,"controls":{"frequency":-0.686870222139845},"persistent":[0]},{"_type":"Sampler","id":3,"x":1,"y":3,"controls":{"rate":-29.341708542713576}},{"_type":"Context","id":4,"x":3,"y":20},{"_type":"Volume","id":5,"x":1,"y":6,"controls":{"volume":-57}},{"_type":"Delay","id":6,"x":2,"y":12,"controls":{"delay":-8.373737373737356}},{"_type":"Volume","id":7,"x":2,"y":9,"controls":{"volume":-21}},{"_type":"Filter","id":8,"x":3,"y":6,"controls":{"cut":-51,"res":-68},"persistent":[1]},{"_type":"Sampler","id":9,"x":3,"y":3,"controls":{"rate":-10.341709213640229}},{"_type":"Volume","id":10,"x":3,"y":11,"controls":{"volume":-61}},{"_type":"Buffer","id":11,"x":3,"y":2,"arguments":[{"_type":"BufferData","id":"dusk to night","uri":"audio/dusk to night.mp3","loop":true,"ir":false,"start":0.053526530612244896,"end":12.34141224489796,"tag":["field","nature","insect","night"]}]},{"_type":"Volume","id":12,"x":0,"y":15,"controls":{"volume":-45}},{"_type":"ModPanner","id":13,"x":3,"y":14},{"_type":"Sampler","id":14,"x":0,"y":12,"controls":{"rate":-20.341708542713576}},{"_type":"Panner","id":15,"x":0,"y":18,"controls":{"pan":-27}},{"_type":"Buffer","id":16,"x":5,"y":5,"arguments":[{"_type":"BufferData","id":"equatorial forest","uri":"audio/equatorial forest.mp3","loop":true,"ir":false,"start":0.027978231292517013,"end":10.110699319727892,"tag":["tropical","nature","forest","insect","rain"]}]},{"_type":"Volume","id":17,"x":5,"y":12,"controls":{"volume":10}},{"_type":"Sampler","id":18,"x":5,"y":7,"controls":{"rate":-8.341708542713576}},{"_type":"AutoXFade","id":19,"x":2,"y":17,"controls":{"duration":5.384615384615358,"volume":68}},{"_type":"RandomBang","id":20,"x":2,"y":23,"controls":{"min":-10.50505050505052,"max":41.101010101010104}},{"_type":"Sampler","id":21,"x":1,"y":21,"controls":{"rate":11.658291457286396}},{"_type":"Buffer","id":22,"x":1,"y":20,"arguments":[{"_type":"BufferData","id":"atrem rain","uri":"audio/atrem rain.mp3","loop":true,"ir":false,"start":0.026677551020408165,"end":13.451102040816327,"tag":["rain"]}]},{"_type":"Volume","id":23,"x":1,"y":24,"controls":{"volume":1}}],"cables":["0:data--3:buffer","3:OUT--5:IN","6:OUT--4:DEST","6:OUT--7:IN","7:OUT--6:IN","9:OUT--8:IN","8:OUT--10:IN","11:data--9:buffer","2:OUT--13:pan","10:OUT--13:IN","13:OUT--4:DEST","1:data--14:buffer","14:OUT--12:IN","6:IN--15:OUT","12:OUT--15:IN","16:data--18:buffer","18:OUT--17:IN","17:OUT--4:DEST","5:OUT--19:A","19:OUT--6:IN","20:OUT--19:TRG","22:data--21:buffer","21:OUT--23:IN","23:OUT--19:B"]} -------------------------------------------------------------------------------- /assets/patches/temple rain.json: -------------------------------------------------------------------------------- 1 | {"_type":"modularPatch","version":1,"modules":[{"_type":"Buffer","id":0,"x":4,"y":6,"arguments":[{"_type":"BufferData","id":"water runoff","uri":"audio/water runoff.mp3","loop":true,"ir":false,"start":0.027946938775510202,"end":13.06485306122449,"tag":["water","cave","drop"]}]},{"_type":"EventPool","id":1,"x":5,"y":6,"controls":{}},{"_type":"XFadeSampler","id":2,"x":5,"y":9,"controls":{"fadeIn":-39.368421052631575,"fadeOut":-39.368421052631575,"volume":24}},{"_type":"Context","id":3,"x":6,"y":25},{"_type":"OnLoadBang","id":4,"x":4,"y":5,"controls":{}},{"_type":"Buffer","id":5,"x":4,"y":7,"arguments":[{"_type":"BufferData","id":"water spring","uri":"audio/water spring.mp3","loop":true,"ir":false,"start":0.038462585034013605,"end":13.398266666666666,"tag":["water","stream","nature"]}]},{"_type":"Buffer","id":6,"x":6,"y":5,"arguments":[{"_type":"BufferData","id":"ueno gong1","uri":"audio/ueno gong1.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","temple","japan"]}]},{"_type":"Buffer","id":7,"x":6,"y":6,"arguments":[{"_type":"BufferData","id":"ueno gong2","uri":"audio/ueno gong2.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","temple","japan"]}]},{"_type":"Buffer","id":8,"x":6,"y":7,"arguments":[{"_type":"BufferData","id":"ueno gong3","uri":"audio/ueno gong3.mp3","loop":false,"ir":false,"start":0.029462585034013604,"end":7.940789115646258,"tag":["bell","temple","japan"]}]},{"_type":"EventPool","id":9,"x":7,"y":7,"controls":{}},{"_type":"RandomBang","id":10,"x":7,"y":4,"controls":{"min":-66.5050505050505,"max":-32.898989898989896}},{"_type":"OneShotSampler","id":11,"x":7,"y":12,"controls":{"volume":-40}},{"_type":"Convolver","id":12,"x":8,"y":15,"controls":{}},{"_type":"Volume","id":13,"x":8,"y":11,"controls":{"volume":65}},{"_type":"Buffer","id":14,"x":3,"y":12,"arguments":[{"_type":"BufferData","id":"atrem rain","uri":"audio/atrem rain.mp3","loop":true,"ir":false,"start":0.026677551020408165,"end":13.451102040816327,"tag":["rain"]}]},{"_type":"Buffer","id":15,"x":7,"y":16,"arguments":[{"_type":"BufferData","id":"factory hall","uri":"audio/factory hall.mp3","loop":false,"ir":true,"start":0,"end":0,"tag":["real space","room","reverb"]}]},{"_type":"Sampler","id":16,"x":3,"y":13,"controls":{"rate":-17.341708542713576}},{"_type":"Amp","id":17,"x":3,"y":16},{"_type":"Volume","id":18,"x":2,"y":13,"controls":{"volume":3}},{"_type":"SlowLFO","id":20,"x":2,"y":10,"controls":{"frequency":45.313129777860155},"persistent":[0]},{"_type":"Volume","id":21,"x":4,"y":19,"controls":{"volume":68}},{"_type":"Buffer","id":22,"x":6,"y":8,"arguments":[{"_type":"BufferData","id":"drop","uri":"audio/drop.mp3","loop":false,"ir":false,"start":0.028299319727891153,"end":0.6508843537414966,"tag":["water","drop"]}]},{"_type":"PlaybackRate","id":23,"x":7,"y":9,"controls":{"rate":-7.341708542713576}},{"_type":"Buffer","id":24,"x":3,"y":21,"arguments":[{"_type":"BufferData","id":"sailing ship","uri":"audio/sailing ship.mp3","loop":true,"ir":false,"start":0.029028571428571428,"end":19.877314285714288,"tag":["boat","ocean"]}]},{"_type":"Sampler","id":25,"x":3,"y":22,"controls":{"rate":-20.341708370189565}},{"_type":"Volume","id":26,"x":3,"y":25,"controls":{"volume":-32}},{"_type":"RandomBang","id":27,"x":5,"y":3,"controls":{"min":63.494949494949495,"max":68}}],"cables":["0:data--1:IN","1:OUT--2:buffer","2:OUT--3:DEST","5:data--1:IN","10:OUT--9:TRG","11:OUT--3:DEST","12:OUT--3:DEST","11:OUT--13:IN","13:OUT--12:IN","15:data--12:buffer","14:data--16:buffer","20:OUT--18:IN","18:OUT--17:MOD","16:OUT--17:IN","17:OUT--21:IN","21:OUT--3:DEST","22:data--9:IN","8:data--9:IN","7:data--9:IN","6:data--9:IN","9:OUT--11:buffer","23:OUT--11:trigger","10:OUT--23:IN","25:OUT--26:IN","26:OUT--3:DEST","24:data--25:buffer","27:OUT--1:TRG","4:OUT--1:TRG"]} -------------------------------------------------------------------------------- /assets/tilesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/assets/tilesheet.png -------------------------------------------------------------------------------- /audio/EMT244 1s.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"EMT244 1s","uri":"audio/EMT244 1s.mp3","loop":false,"ir":true,"start":0.03258650805674443,"end":0.8154518006988665,"tag":["reverb","hardware"]} -------------------------------------------------------------------------------- /audio/EMT244 1s.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/EMT244 1s.mp3 -------------------------------------------------------------------------------- /audio/EP Chicago.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"EP Chicago","uri":"audio/EP Chicago.mp3","loop":false,"ir":false,"start":0.027742346938775506,"end":1.8682775510204082,"tag":["EP","synth","note","music"]} -------------------------------------------------------------------------------- /audio/EP Chicago.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/EP Chicago.mp3 -------------------------------------------------------------------------------- /audio/Fat Bass.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"Fat Bass","uri":"audio/Fat Bass.mp3","loop":false,"ir":false,"start":0.03325500900738183,"end":2.2618762991307637,"tag":["synth","note","analog","music"]} -------------------------------------------------------------------------------- /audio/Fat Bass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/Fat Bass.mp3 -------------------------------------------------------------------------------- /audio/aerial.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/aerial.mp3 -------------------------------------------------------------------------------- /audio/after daydream.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/after daydream.mp3 -------------------------------------------------------------------------------- /audio/angelus.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"angelus","uri":"audio/angelus.mp3","loop":true,"ir":false,"start":0.02903945578231292,"end":9.580016326530611,"tag":["bell","church","field"]} -------------------------------------------------------------------------------- /audio/angelus.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/angelus.mp3 -------------------------------------------------------------------------------- /audio/atrem rain.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"atrem rain","uri":"audio/atrem rain.mp3","loop":true,"ir":false,"start":0.026677551020408165,"end":13.451102040816327,"tag":["rain"]} -------------------------------------------------------------------------------- /audio/atrem rain.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/atrem rain.mp3 -------------------------------------------------------------------------------- /audio/belllong.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"belllong","uri":"audio/belllong.mp3","loop":false,"ir":false,"start":0.027166978458049888,"end":1.67146505574452,"tag":["bell"]} -------------------------------------------------------------------------------- /audio/belllong.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/belllong.mp3 -------------------------------------------------------------------------------- /audio/binaural rain.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/binaural rain.mp3 -------------------------------------------------------------------------------- /audio/boat.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"boat","uri":"audio/boat.mp3","loop":true,"ir":false,"start":0.029485671768707485,"end":15.700300461781936,"tag":["boat"]} -------------------------------------------------------------------------------- /audio/boat.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/boat.mp3 -------------------------------------------------------------------------------- /audio/campfire.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"campfire","uri":"audio/campfire.mp3","loop":true,"start":0.02844897959183674,"end":-0.015,"tag":["field","fire","night"]} -------------------------------------------------------------------------------- /audio/campfire.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/campfire.mp3 -------------------------------------------------------------------------------- /audio/car passing.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"car passing","uri":"audio/car passing.mp3","loop":true,"ir":false,"start":0.02733469387755102,"end":14.153461224489796,"tag":["car","road","field"]} -------------------------------------------------------------------------------- /audio/car passing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/car passing.mp3 -------------------------------------------------------------------------------- /audio/cicada summer.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"cicada summer","uri":"audio/cicada summer.mp3","loop":true,"ir":false,"start":0.02695229828042328,"end":7.3718379039115645,"tag":["ciccada","insect","nature","field","summer"]} -------------------------------------------------------------------------------- /audio/cicada summer.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/cicada summer.mp3 -------------------------------------------------------------------------------- /audio/cicada temple.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/cicada temple.mp3 -------------------------------------------------------------------------------- /audio/city1.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"city1","uri":"audio/city1.mp3","loop":true,"ir":false,"start":0.03172789115646258,"end":15.206385034013604,"tag":["city","field"]} -------------------------------------------------------------------------------- /audio/city1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/city1.mp3 -------------------------------------------------------------------------------- /audio/clock.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"clock","uri":"audio/clock.mp3","loop":true,"ir":false,"start":0.02949795918367347,"end":7.676338775510204,"tag":["mechanic","clock"]} -------------------------------------------------------------------------------- /audio/clock.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/clock.mp3 -------------------------------------------------------------------------------- /audio/crossroads.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"crossroads","uri":"audio/crossroads.mp3","loop":true,"ir":false,"start":0.02782857142857143,"end":25.420737414965988,"tag":["city","road","field","car"]} -------------------------------------------------------------------------------- /audio/crossroads.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/crossroads.mp3 -------------------------------------------------------------------------------- /audio/crume1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/crume1.mp3 -------------------------------------------------------------------------------- /audio/crystal cave.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/crystal cave.mp3 -------------------------------------------------------------------------------- /audio/cuckoo.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"cuckoo","uri":"audio/cuckoo.mp3","loop":true,"ir":false,"start":0.08289523809523809,"end":7.010568707482993,"tag":["bird","nature"]} -------------------------------------------------------------------------------- /audio/cuckoo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/cuckoo.mp3 -------------------------------------------------------------------------------- /audio/damu drums1.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"damu drums1","uri":"audio/damu drums1.mp3","loop":true,"ir":false,"start":0.02720816326530612,"end":5.259695238095238,"tag":["music","drum","hiphop"]} -------------------------------------------------------------------------------- /audio/damu drums1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/damu drums1.mp3 -------------------------------------------------------------------------------- /audio/damu drums2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/damu drums2.mp3 -------------------------------------------------------------------------------- /audio/disco tom.json: -------------------------------------------------------------------------------- 1 | { 2 | "_type":"ProceduralBuffer", 3 | "synthesizer":"disco", 4 | "params": { 5 | "freq":440, 6 | "mod":300, 7 | "ampDuration": 0.7, 8 | "ampCurve": 0.2, 9 | "modDuration": 0.7, 10 | "modCurve": 0.5 11 | }, 12 | "loop":false, 13 | "tag":["music", "synth", "sfx"] 14 | } -------------------------------------------------------------------------------- /audio/drop.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"drop","uri":"audio/drop.mp3","loop":false,"ir":false,"start":0.028299319727891153,"end":0.6508843537414966,"tag":["water","drop"]} -------------------------------------------------------------------------------- /audio/drop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/drop.mp3 -------------------------------------------------------------------------------- /audio/dusk to night.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"dusk to night","uri":"audio/dusk to night.mp3","loop":true,"start":0.053526530612244896,"end":12.34141224489796,"tag":["field","nature","insect","night"]} -------------------------------------------------------------------------------- /audio/dusk to night.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/dusk to night.mp3 -------------------------------------------------------------------------------- /audio/equatorial forest.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"equatorial forest","uri":"audio/equatorial forest.mp3","loop":true,"ir":false,"start":0.027978231292517013,"end":10.110699319727892,"tag":["tropical","nature","forest","insect","rain"]} -------------------------------------------------------------------------------- /audio/equatorial forest.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/equatorial forest.mp3 -------------------------------------------------------------------------------- /audio/factory hall.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"factory hall","uri":"audio/factory hall.mp3","loop":false,"ir":true,"start":0,"end":0,"tag":["real space","room","reverb"]} -------------------------------------------------------------------------------- /audio/factory hall.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/factory hall.mp3 -------------------------------------------------------------------------------- /audio/fan.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"fan","uri":"audio/fan.mp3","loop":true,"ir":false,"start":0.027586394557823135,"end":3.842361904761905,"tag":["motor","fan"]} -------------------------------------------------------------------------------- /audio/fan.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/fan.mp3 -------------------------------------------------------------------------------- /audio/fireworks.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"fireworks","uri":"audio/fireworks.mp3","loop":true,"ir":false,"start":0.04865714285714286,"end":23.575009523809523,"tag":["outdoor","explosion","field"]} -------------------------------------------------------------------------------- /audio/fireworks.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/fireworks.mp3 -------------------------------------------------------------------------------- /audio/fishdog.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"fishdog","uri":"audio/fishdog.mp3","loop":true,"ir":false,"start":0.028334693877551023,"end":13.91088163265306,"tag":["noise"]} -------------------------------------------------------------------------------- /audio/fishdog.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/fishdog.mp3 -------------------------------------------------------------------------------- /audio/forest.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/forest.mp3 -------------------------------------------------------------------------------- /audio/fridge.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"fridge","uri":"audio/fridge.mp3","loop":true,"ir":false,"start":0.028473469387755104,"end":8.510008163265304,"tag":["motor","house"]} -------------------------------------------------------------------------------- /audio/fridge.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/fridge.mp3 -------------------------------------------------------------------------------- /audio/gangas.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"gangas","uri":"audio/gangas.mp3","loop":true,"ir":false,"start":0.031857142857142855,"end":17.4386,"tag":["tropical","field","nature","insect"]} -------------------------------------------------------------------------------- /audio/gangas.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/gangas.mp3 -------------------------------------------------------------------------------- /audio/harbor.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"harbor","uri":"audio/harbor.mp3","loop":true,"ir":false,"start":0.029338775510204078,"end":15.632677551020407,"tag":["ocean","boat","wave"]} -------------------------------------------------------------------------------- /audio/harbor.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/harbor.mp3 -------------------------------------------------------------------------------- /audio/hats line.json: -------------------------------------------------------------------------------- 1 | { 2 | "_type":"ProceduralBuffer", 3 | "synthesizer":"hats", 4 | "params": { 5 | "uri":"audio/hihat.mp3", 6 | "offset": 1070, 7 | "pattern":"O.......C.....ccO.......C...o...", 8 | "polyphony":2, 9 | "tempo":200, 10 | "close":0.3, 11 | "accent":0.2 12 | }, 13 | "loop":true, 14 | "tag":["music", "hats"] 15 | } -------------------------------------------------------------------------------- /audio/hexagonal room.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"hexagonal room","uri":"audio/hexagonal room.mp3","loop":false,"ir":true,"start":0.016718367346938778,"end":3.3381006802721087,"tag":["real space","reverb","room"]} -------------------------------------------------------------------------------- /audio/hexagonal room.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/hexagonal room.mp3 -------------------------------------------------------------------------------- /audio/highway.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"highway","uri":"audio/highway.mp3","loop":true,"ir":false,"start":0.030824489795918363,"end":10.010644897959184,"tag":["road","motor"]} -------------------------------------------------------------------------------- /audio/highway.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/highway.mp3 -------------------------------------------------------------------------------- /audio/hihat.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"hihat","uri":"audio/hihat.mp3","loop":false,"ir":false,"start":0.02475141667995323,"end":0.4953959183673469,"tag":[]} -------------------------------------------------------------------------------- /audio/hihat.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/hihat.mp3 -------------------------------------------------------------------------------- /audio/ice bubbles.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"ice bubbles","uri":"audio/ice bubbles.mp3","loop":true,"ir":false,"start":0.02834285714285715,"end":12.941213605442178,"tag":["ice","bubble"]} -------------------------------------------------------------------------------- /audio/ice bubbles.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/ice bubbles.mp3 -------------------------------------------------------------------------------- /audio/ice cube.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"ice cube","uri":"audio/ice cube.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["ice"]} -------------------------------------------------------------------------------- /audio/ice cube.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/ice cube.mp3 -------------------------------------------------------------------------------- /audio/ice cube2.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"ice cube2","uri":"audio/ice cube2.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["ice"]} -------------------------------------------------------------------------------- /audio/ice cube2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/ice cube2.mp3 -------------------------------------------------------------------------------- /audio/jungle.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"jungle","uri":"audio/jungle.mp3","loop":true,"ir":false,"start":0.02907891156462585,"end":19.230185034013605,"tag":["jungle","field","nature"]} -------------------------------------------------------------------------------- /audio/jungle.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/jungle.mp3 -------------------------------------------------------------------------------- /audio/mechanics1.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"mechanics1","uri":"audio/mechanics1.mp3","loop":true,"ir":false,"start":0.027323809523809525,"end":3.9249727891156456,"tag":["industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/mechanics1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/mechanics1.mp3 -------------------------------------------------------------------------------- /audio/mechanics2.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"mechanics2","uri":"audio/mechanics2.mp3","loop":true,"ir":false,"start":0.027609523809523813,"end":5.802854421768708,"tag":["industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/mechanics2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/mechanics2.mp3 -------------------------------------------------------------------------------- /audio/mechanics3.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"mechanics3","uri":"audio/mechanics3.mp3","loop":true,"ir":false,"start":0.0275265306122449,"end":7.302941496598638,"tag":["industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/mechanics3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/mechanics3.mp3 -------------------------------------------------------------------------------- /audio/mechanics4.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"mechanics4","uri":"audio/mechanics4.mp3","loop":true,"ir":false,"start":0.02721073672524565,"end":4.144521023478836,"tag":["inustrial","mechanic"]} -------------------------------------------------------------------------------- /audio/mechanics4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/mechanics4.mp3 -------------------------------------------------------------------------------- /audio/mechanics5.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"mechanics5","uri":"audio/mechanics5.mp3","loop":true,"ir":false,"start":0.027477551020408164,"end":5.15204081632653,"tag":["industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/mechanics5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/mechanics5.mp3 -------------------------------------------------------------------------------- /audio/mechanics6.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"mechanics6","uri":"audio/mechanics6.mp3","loop":true,"ir":false,"start":0.026360544217687073,"end":4.023673469387755,"tag":["industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/mechanics6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/mechanics6.mp3 -------------------------------------------------------------------------------- /audio/mechanics7.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"mechanics7","uri":"audio/mechanics7.mp3","loop":true,"ir":false,"start":0.030775510204081636,"end":9.813284353741498,"tag":["industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/mechanics7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/mechanics7.mp3 -------------------------------------------------------------------------------- /audio/mechanics8.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"mechanics8","uri":"audio/mechanics8.mp3","loop":true,"ir":false,"start":0.031855782312925164,"end":13.238155102040816,"tag":["industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/mechanics8.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/mechanics8.mp3 -------------------------------------------------------------------------------- /audio/medium room.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"medium room","uri":"audio/medium room.mp3","loop":false,"ir":true,"start":0,"end":0,"tag":["reverb","real space","room"]} -------------------------------------------------------------------------------- /audio/medium room.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/medium room.mp3 -------------------------------------------------------------------------------- /audio/motor1.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"motor1","uri":"audio/motor1.mp3","loop":true,"ir":false,"start":0.02782040816326531,"end":3.7326367346938776,"tag":["motor","industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/motor1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/motor1.mp3 -------------------------------------------------------------------------------- /audio/motor2.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"motor2","uri":"audio/motor2.mp3","loop":true,"ir":false,"start":0.027827210884353734,"end":4.705753741496598,"tag":["motor","industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/motor2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/motor2.mp3 -------------------------------------------------------------------------------- /audio/motor3.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"motor3","uri":"audio/motor3.mp3","loop":true,"ir":false,"start":0.027387755102040817,"end":4.294040816326532,"tag":["motor","industrial","mechanic"]} -------------------------------------------------------------------------------- /audio/motor3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/motor3.mp3 -------------------------------------------------------------------------------- /audio/ocean crushing.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"ocean crushing","uri":"audio/ocean crushing.mp3","loop":true,"ir":false,"start":0.02903945578231292,"end":19.20509387755102,"tag":["ocean","wave"]} -------------------------------------------------------------------------------- /audio/ocean crushing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/ocean crushing.mp3 -------------------------------------------------------------------------------- /audio/old device.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"old device","uri":"audio/old device.mp3","loop":true,"ir":false,"start":0.027460950491307635,"end":2.235817799272487,"tag":["noise"]} -------------------------------------------------------------------------------- /audio/old device.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/old device.mp3 -------------------------------------------------------------------------------- /audio/pebbles.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"pebbles","uri":"audio/pebbles.mp3","loop":true,"ir":false,"start":0.027066666666666666,"end":17.884533333333337,"tag":["ocean","pebble","wave"]} -------------------------------------------------------------------------------- /audio/pebbles.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/pebbles.mp3 -------------------------------------------------------------------------------- /audio/pen.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"pen","uri":"audio/pen.mp3","loop":true,"ir":false,"start":0.036514285714285714,"end":4.882885714285714,"tag":["pen"]} -------------------------------------------------------------------------------- /audio/pen.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/pen.mp3 -------------------------------------------------------------------------------- /audio/pool1.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"pool1","uri":"audio/pool1.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["pool","ball"]} -------------------------------------------------------------------------------- /audio/pool1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/pool1.mp3 -------------------------------------------------------------------------------- /audio/pool2.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"pool2","uri":"audio/pool2.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["pool","ball"]} -------------------------------------------------------------------------------- /audio/pool2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/pool2.mp3 -------------------------------------------------------------------------------- /audio/pool3.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"pool3","uri":"audio/pool3.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["pool","ball"]} -------------------------------------------------------------------------------- /audio/pool3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/pool3.mp3 -------------------------------------------------------------------------------- /audio/pool4.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"pool4","uri":"audio/pool4.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["pool","ball"]} -------------------------------------------------------------------------------- /audio/pool4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/pool4.mp3 -------------------------------------------------------------------------------- /audio/railway station.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"railway station","uri":"audio/railway station.mp3","loop":true,"ir":false,"start":0.027697959183673466,"end":18.30930612244898,"tag":["train"]} -------------------------------------------------------------------------------- /audio/railway station.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/railway station.mp3 -------------------------------------------------------------------------------- /audio/rooster.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"rooster","uri":"audio/rooster.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bird"]} -------------------------------------------------------------------------------- /audio/rooster.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/rooster.mp3 -------------------------------------------------------------------------------- /audio/sailing ship.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"sailing ship","uri":"audio/sailing ship.mp3","loop":true,"ir":false,"start":0.029028571428571428,"end":19.877314285714288,"tag":["boat","ocean"]} -------------------------------------------------------------------------------- /audio/sailing ship.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/sailing ship.mp3 -------------------------------------------------------------------------------- /audio/sea pebbles.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"sea pebbles","uri":"audio/sea pebbles.mp3","loop":true,"ir":false,"start":0.031229931972789112,"end":26.05255238095238,"tag":["ocean","pebble","wave"]} -------------------------------------------------------------------------------- /audio/sea pebbles.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/sea pebbles.mp3 -------------------------------------------------------------------------------- /audio/sea rock.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"sea rock","uri":"audio/sea rock.mp3","loop":true,"ir":false,"start":0.02784757889266818,"end":15.686376672335603,"tag":["ocean"]} -------------------------------------------------------------------------------- /audio/sea rock.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/sea rock.mp3 -------------------------------------------------------------------------------- /audio/sea sand.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"sea sand","uri":"audio/sea sand.mp3","loop":true,"ir":false,"start":0.030599999999999995,"end":21.737333333333336,"tag":["ocean","wave"]} -------------------------------------------------------------------------------- /audio/sea sand.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/sea sand.mp3 -------------------------------------------------------------------------------- /audio/singing bowl.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"singing bowl","uri":"audio/singing bowl.mp3","loop":false,"ir":false,"start":0.0270952380952381,"end":14.814127891156463,"tag":["bell"]} -------------------------------------------------------------------------------- /audio/singing bowl.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/singing bowl.mp3 -------------------------------------------------------------------------------- /audio/stock exchange.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"stock exchange","uri":"audio/stock exchange.mp3","loop":true,"ir":false,"start":0.027342857142857144,"end":18.08022857142857,"tag":["field","room","city","people"]} -------------------------------------------------------------------------------- /audio/stock exchange.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/stock exchange.mp3 -------------------------------------------------------------------------------- /audio/strangehead.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"strangehead","uri":"audio/strangehead.mp3","loop":true,"ir":false,"start":0.028824489795918364,"end":8.36914693877551,"tag":["noise"]} -------------------------------------------------------------------------------- /audio/strangehead.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/strangehead.mp3 -------------------------------------------------------------------------------- /audio/summer night.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"summer night","uri":"audio/summer night.mp3","loop":true,"start":0.05488163265306123,"end":19.46671836734694,"tag":["field","nature","insect","night"]} -------------------------------------------------------------------------------- /audio/summer night.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/summer night.mp3 -------------------------------------------------------------------------------- /audio/suzu1.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"suzu1","uri":"audio/suzu1.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","wind","chime"]} -------------------------------------------------------------------------------- /audio/suzu1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/suzu1.mp3 -------------------------------------------------------------------------------- /audio/suzu2.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"suzu2","uri":"audio/suzu2.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","wind","chime"]} -------------------------------------------------------------------------------- /audio/suzu2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/suzu2.mp3 -------------------------------------------------------------------------------- /audio/suzu3.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"suzu3","uri":"audio/suzu3.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","wind","chime"]} -------------------------------------------------------------------------------- /audio/suzu3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/suzu3.mp3 -------------------------------------------------------------------------------- /audio/suzu4.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"suzu4","uri":"audio/suzu4.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","wind","chime"]} -------------------------------------------------------------------------------- /audio/suzu4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/suzu4.mp3 -------------------------------------------------------------------------------- /audio/theater.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"theater","uri":"audio/theater.mp3","loop":true,"ir":false,"start":0.028122448979591836,"end":8.27275918367347,"tag":["people","room","talking","theater"]} -------------------------------------------------------------------------------- /audio/theater.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/theater.mp3 -------------------------------------------------------------------------------- /audio/train1.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"train1","uri":"audio/train1.mp3","loop":true,"ir":false,"start":0.026632618811413447,"end":17.594976664068405,"tag":["train"]} -------------------------------------------------------------------------------- /audio/train1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/train1.mp3 -------------------------------------------------------------------------------- /audio/triangular room.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"triangular room","uri":"audio/triangular room.mp3","loop":false,"ir":true,"start":0,"end":0,"tag":["reverb","room","real space"]} -------------------------------------------------------------------------------- /audio/triangular room.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/triangular room.mp3 -------------------------------------------------------------------------------- /audio/tropical forest.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"tropical forest","uri":"audio/tropical forest.mp3","loop":true,"ir":false,"start":0.031608134920634925,"end":25.260167115457296,"tag":["forest","nature","tropical","bird","insect"]} -------------------------------------------------------------------------------- /audio/tropical forest.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/tropical forest.mp3 -------------------------------------------------------------------------------- /audio/tunnel.json: -------------------------------------------------------------------------------- 1 | { 2 | "_type":"BufferData", 3 | "id":"tunnel", 4 | "uri":"audio/tunnel.mp3", 5 | "loop":false, 6 | "ir":true, 7 | "start":0, 8 | "end":0, 9 | "tag":["reverb","real space"] 10 | } -------------------------------------------------------------------------------- /audio/tunnel.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/tunnel.mp3 -------------------------------------------------------------------------------- /audio/ueno gong1.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"ueno gong1","uri":"audio/ueno gong1.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","temple","japan"]} -------------------------------------------------------------------------------- /audio/ueno gong1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/ueno gong1.mp3 -------------------------------------------------------------------------------- /audio/ueno gong2.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"ueno gong2","uri":"audio/ueno gong2.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bell","temple","japan"]} -------------------------------------------------------------------------------- /audio/ueno gong2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/ueno gong2.mp3 -------------------------------------------------------------------------------- /audio/ueno gong3.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"ueno gong3","uri":"audio/ueno gong3.mp3","loop":false,"ir":false,"start":0.029462585034013604,"end":7.940789115646258,"tag":["bell","temple","japan"]} -------------------------------------------------------------------------------- /audio/ueno gong3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/ueno gong3.mp3 -------------------------------------------------------------------------------- /audio/water runoff.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"water runoff","uri":"audio/water runoff.mp3","loop":true,"ir":false,"start":0.027946938775510202,"end":13.06485306122449,"tag":["water","cave","drop"]} -------------------------------------------------------------------------------- /audio/water runoff.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/water runoff.mp3 -------------------------------------------------------------------------------- /audio/water spring.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"water spring","uri":"audio/water spring.mp3","loop":true,"ir":false,"start":0.038462585034013605,"end":13.398266666666666,"tag":["water","stream","nature"]} -------------------------------------------------------------------------------- /audio/water spring.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/water spring.mp3 -------------------------------------------------------------------------------- /audio/water stream.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"water stream","uri":"audio/water stream.mp3","loop":true,"start":0.04086202504683514,"end":-0.035,"tag":["field","water","stream"]} -------------------------------------------------------------------------------- /audio/water stream.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/water stream.mp3 -------------------------------------------------------------------------------- /audio/white noise.json: -------------------------------------------------------------------------------- 1 | { 2 | "_type":"ProceduralBuffer", 3 | "synthesizer":"noize", 4 | "params":{ 5 | "length":0.5 6 | }, 7 | "loop":true, 8 | "tag":["noise"] 9 | } -------------------------------------------------------------------------------- /audio/woodpecker peck.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"woodpecker peck","uri":"audio/woodpecker peck.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bird"]} -------------------------------------------------------------------------------- /audio/woodpecker peck.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/woodpecker peck.mp3 -------------------------------------------------------------------------------- /audio/woodpecker squeak.json: -------------------------------------------------------------------------------- 1 | {"_type":"BufferData","id":"woodpecker squeak","uri":"audio/woodpecker squeak.mp3","loop":false,"ir":false,"start":0,"end":0,"tag":["bird"]} -------------------------------------------------------------------------------- /audio/woodpecker squeak.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/woodpecker squeak.mp3 -------------------------------------------------------------------------------- /audio/x_b.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/audio/x_b.mp3 -------------------------------------------------------------------------------- /build/synthEdit.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "unispace"; 3 | src: url("../fonts/telegrama.otf") format("woff"); 4 | } 5 | 6 | .synthEdit-root { 7 | background-color: #313536; 8 | background-image: url(../img/syntheditBG.png); 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | .synthEdit-header { 14 | display: flex; 15 | flex-direction: row; 16 | flex-wrap: nowrap; 17 | } 18 | 19 | .synthEdit-header-spacer { 20 | display: inline-block; 21 | flex-grow: 1; 22 | } 23 | 24 | .synthedit-header-synthName { 25 | user-select: none; 26 | cursor: default; 27 | display: inline-block; 28 | height: 20px; 29 | border-radius: 3px; 30 | background-color: #AAA; 31 | margin: 2px; 32 | vertical-align: top; 33 | line-height: 20px; 34 | padding: 0 6px; 35 | } 36 | 37 | .synthedit-button { 38 | display: inline-block; 39 | width: 20px; 40 | height: 20px; 41 | border-radius: 3px; 42 | background-repeat: no-repeat; 43 | background-position: 50% 50%; 44 | background-color: #AAA; 45 | margin: 2px; 46 | } 47 | 48 | .synthedit-button:hover { 49 | background-color: #FF0; 50 | } 51 | 52 | 53 | .synthEdit-container { 54 | border: solid 4px #BBB; 55 | margin: 6px; 56 | /*padding: 6px;*/ 57 | position: absolute; 58 | border-radius: 8px; 59 | } 60 | 61 | .synthEdit-knob { 62 | width: 32px; 63 | height: 32px; 64 | position: absolute; 65 | cursor: pointer; 66 | } 67 | 68 | .synthEdit-knob-canvas { 69 | width: 32px; 70 | height: 32px; 71 | } 72 | 73 | .synthEdit-textInput { 74 | position: absolute; 75 | height: 32px; 76 | font-family: "unispace"; 77 | font-size: 13px; 78 | background-color: #242f38; 79 | color: #9cebff; 80 | border-radius: 4px; 81 | border-color: #5f5e5e; 82 | } 83 | 84 | .synthEdit-toggle { 85 | width: 28px; 86 | height: 28px; 87 | border: solid 2px; 88 | position: absolute; 89 | } 90 | 91 | .synthEdit-label { 92 | user-select: none; 93 | cursor: default; 94 | position: absolute; 95 | font-family: "unispace"; 96 | font-size: 13px; 97 | color: #BBB; 98 | text-align: center; 99 | height: 16px; 100 | line-height: 17px; 101 | vertical-align: middle; 102 | } 103 | -------------------------------------------------------------------------------- /editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Modular 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /fonts/Big Pixel demo.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/fonts/Big Pixel demo.otf -------------------------------------------------------------------------------- /fonts/kongtext.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/fonts/kongtext.ttf -------------------------------------------------------------------------------- /fonts/november.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/fonts/november.ttf -------------------------------------------------------------------------------- /fonts/telegrama.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/fonts/telegrama.otf -------------------------------------------------------------------------------- /img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/background.png -------------------------------------------------------------------------------- /img/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/close.png -------------------------------------------------------------------------------- /img/connector/in-B-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/in-B-fill.png -------------------------------------------------------------------------------- /img/connector/in-B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/in-B.png -------------------------------------------------------------------------------- /img/connector/in-G-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/in-G-fill.png -------------------------------------------------------------------------------- /img/connector/in-G.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/in-G.png -------------------------------------------------------------------------------- /img/connector/in-R-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/in-R-fill.png -------------------------------------------------------------------------------- /img/connector/in-R.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/in-R.png -------------------------------------------------------------------------------- /img/connector/in-Y-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/in-Y-fill.png -------------------------------------------------------------------------------- /img/connector/in-Y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/in-Y.png -------------------------------------------------------------------------------- /img/connector/out-B-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/out-B-fill.png -------------------------------------------------------------------------------- /img/connector/out-B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/out-B.png -------------------------------------------------------------------------------- /img/connector/out-G-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/out-G-fill.png -------------------------------------------------------------------------------- /img/connector/out-G.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/out-G.png -------------------------------------------------------------------------------- /img/connector/out-R-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/out-R-fill.png -------------------------------------------------------------------------------- /img/connector/out-R.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/out-R.png -------------------------------------------------------------------------------- /img/connector/out-Y-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/out-Y-fill.png -------------------------------------------------------------------------------- /img/connector/out-Y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/connector/out-Y.png -------------------------------------------------------------------------------- /img/iconApplication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/iconApplication.png -------------------------------------------------------------------------------- /img/iconIR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/iconIR.png -------------------------------------------------------------------------------- /img/iconLoop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/iconLoop.png -------------------------------------------------------------------------------- /img/iconSave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/iconSave.png -------------------------------------------------------------------------------- /img/iconShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/iconShot.png -------------------------------------------------------------------------------- /img/iconSine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/iconSine.png -------------------------------------------------------------------------------- /img/jack-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/jack-connect.png -------------------------------------------------------------------------------- /img/jack-free.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/jack-free.png -------------------------------------------------------------------------------- /img/knob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/knob.png -------------------------------------------------------------------------------- /img/knobMark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/knobMark.png -------------------------------------------------------------------------------- /img/syntheditBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cstoquer/modular/d53f30aa2761eadbc8b19e0a4f2f32f953c85989/img/syntheditBG.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | modular 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var electron = require('electron'); 2 | var app = electron.app; // Module to control application life. 3 | var BrowserWindow = electron.BrowserWindow; // Module to create native browser window. 4 | 5 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 6 | // Keep a global reference of the window object, if you don't, the window will 7 | // be closed automatically when the JavaScript object is garbage collected. 8 | var mainWindow; 9 | 10 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 11 | function createWindow () { 12 | // Create the browser window. 13 | mainWindow = new BrowserWindow({ 14 | width: 1000, // /!\ window size with OS border (not the inner size) 15 | height: 800, 16 | icon: __dirname + '/img/iconApplication.png', 17 | frame: true // add OS window border 18 | }); 19 | 20 | // remove menu bar 21 | mainWindow.setMenu(null); 22 | 23 | // and load the index.html of the app. 24 | // console.log(__dirname) 25 | // mainWindow.loadURL("file://${__dirname}/index.html") 26 | mainWindow.loadURL("file://" + __dirname + "/editor.html"); 27 | 28 | // Open the DevTools. 29 | // mainWindow.webContents.openDevTools(); 30 | 31 | // Emitted when the window is closed. 32 | mainWindow.on('closed', function () { 33 | // Dereference the window object, usually you would store windows 34 | // in an array if your app supports multi windows, this is the time 35 | // when you should delete the corresponding element. 36 | mainWindow = null; 37 | }) 38 | } 39 | 40 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 41 | // This method will be called when Electron has finished 42 | // initialization and is ready to create browser windows. 43 | // Some APIs can only be used after this event occurs. 44 | app.on('ready', createWindow); 45 | 46 | // Quit when all windows are closed. 47 | app.on('window-all-closed', function () { 48 | // On OS X it is common for applications and their menu bar 49 | // to stay active until the user quits explicitly with Cmd + Q 50 | if (process.platform !== 'darwin') app.quit(); 51 | }) 52 | 53 | app.on('activate', function () { 54 | // On OS X it's common to re-create a window in the app when the 55 | // dock icon is clicked and there are no other windows open. 56 | if (mainWindow === null) createWindow(); 57 | }) 58 | 59 | // In this file you can include the rest of your app's specific main process 60 | // code. You can also put them in separate files and require them here. 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modular", 3 | "version": "1.0.0", 4 | "description": "modular webAudio", 5 | "author": "Cedric Stoquer", 6 | "main": "main.js", 7 | "scripts": { 8 | "start": "pixelbox", 9 | "build": "browserify src/modular.js | derequire | uglifyjs --compress --mangle > build/modular.min.js", 10 | "editor": "browserify src/editor.js | derequire | replace-stream /_REQUIRE_/ require | uglifyjs --compress --mangle > build/editor.js", 11 | "build": "npm run editor && electron-packager . --icon=icon.ico" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/cstoquer/modular.git" 16 | }, 17 | "dependencies": { 18 | }, 19 | "devDependencies": { 20 | "pixelbox": "1.0.4", 21 | "browserify": "13.0.0", 22 | "uglify-js": "2.7.5", 23 | "derequire": "2.0.6", 24 | "replace-stream": "0.0.1", 25 | "electron": "^1.3.4" 26 | }, 27 | "license": "MIT" 28 | } 29 | -------------------------------------------------------------------------------- /project.pixelbox: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Modular", 3 | "version": "2.0.0", 4 | "screen": { 5 | "width": 800, 6 | "height": 600, 7 | "pixelSize": { 8 | "width": 1, 9 | "height": 1 10 | }, 11 | "fullscreen": false, 12 | "resizable": true 13 | }, 14 | 15 | "tileSize": [8, 8], 16 | 17 | "palette": [ 18 | "rgba(0, 0, 0, 0)", 19 | "rgba(0, 0, 0, 0.1)" 20 | ], 21 | 22 | "controls": { 23 | "up": 38, 24 | "down": 40, 25 | "left": 37, 26 | "right": 39, 27 | "A": 32, 28 | "B": 88 29 | }, 30 | 31 | "touchEvent": { 32 | "multiTouch": false, 33 | "disableContextMenu": false, 34 | "hideMousePointer": false 35 | }, 36 | 37 | "loaderColors": [0, 1], 38 | 39 | "components": { 40 | "pixelboxCore": false, 41 | "keyboard": false, 42 | "TINA": false, 43 | "AudioManager": false 44 | } 45 | } -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "screen": { 3 | "width": 64, 4 | "height": 10, 5 | "pixelSize": [2, 2], 6 | "fullscreen": false 7 | }, 8 | 9 | "tileSize": [8, 8], 10 | 11 | "palette": [ 12 | "rgba(0, 0, 0, 0)", 13 | "rgba(0, 0, 0, 0.1)" 14 | ], 15 | 16 | "controls": { 17 | "up": 38, 18 | "down": 40, 19 | "left": 37, 20 | "right": 39, 21 | "A": 32, 22 | "B": 88 23 | }, 24 | 25 | "touchEvent": { 26 | "multiTouch": false, 27 | "disableContextMenu": false, 28 | "hideMousePointer": false 29 | }, 30 | 31 | "loaderColors": [0, 1], 32 | 33 | "components": { 34 | "pixelboxCore": false, 35 | "keyboard": false, 36 | "TINA": false, 37 | "AudioManager": false 38 | } 39 | } -------------------------------------------------------------------------------- /src/core/AudioConnector.js: -------------------------------------------------------------------------------- 1 | var connectors = require('./connectors'); 2 | var Connector = require('./Connector'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function AudioConnector(module, id, descriptor) { 6 | this.endPoint = module; // redefined at bind 7 | this.connections = []; 8 | Connector.call(this, module, id, descriptor); 9 | } 10 | inherits(AudioConnector, Connector); 11 | AudioConnector.prototype.color = '#fd870e'; 12 | AudioConnector.prototype.type = 'audio'; 13 | 14 | AudioConnector.prototype.bind = function (module, id, descriptor) { 15 | // find endPoint reference 16 | var endPoint = descriptor.endPoint.split('.'); 17 | this.endPoint = module; 18 | for (var i = 0; i < endPoint.length; i++) { 19 | this.endPoint = this.endPoint[endPoint[i]]; 20 | } 21 | 22 | // reconnect previous connections if endPoint changed 23 | var connections = this.connections; 24 | this.connections = []; 25 | for (var i = connections.length - 1; i >= 0; i--) { 26 | this.connect(connections[i]); 27 | } 28 | }; 29 | 30 | AudioConnector.prototype.connect = function (connector) { 31 | Connector.prototype.connect.call(this, connector); 32 | // save connections for rebind 33 | this._addConnection(connector); 34 | connector._addConnection(this); 35 | }; 36 | 37 | AudioConnector.prototype.disconnect = function (connector) { 38 | Connector.prototype.disconnect.call(this, connector); 39 | // remove saved connections 40 | this._removeConnection(connector); 41 | connector._removeConnection(this); 42 | }; 43 | 44 | AudioConnector.prototype._addConnection = function (connector) { 45 | this.connections.push(connector); 46 | }; 47 | 48 | AudioConnector.prototype._removeConnection = function (connector) { 49 | var index = this.connections.indexOf(connector); 50 | if (index === -1) return; // happen to the second connector when a cable is removed 51 | this.connections.splice(index, 1); 52 | }; 53 | 54 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 55 | function AudioInput(module, id, descriptor) { 56 | AudioConnector.call(this, module, id, descriptor); 57 | } 58 | inherits(AudioInput, AudioConnector); 59 | AudioInput.prototype.cssClassName = 'audioIn'; 60 | AudioInput.prototype.way = 'input'; 61 | connectors.register(AudioInput, 'input', 'audio'); 62 | 63 | AudioInput.prototype.connect = function (connector) { 64 | AudioConnector.prototype.connect.call(this, connector); 65 | connector.endPoint.connect(this.endPoint); 66 | }; 67 | 68 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 69 | function AudioOutput(module, id, descriptor) { 70 | AudioConnector.call(this, module, id, descriptor); 71 | } 72 | inherits(AudioOutput, AudioConnector); 73 | AudioOutput.prototype.cssClassName = 'audioOut'; 74 | AudioOutput.prototype.way = 'output'; 75 | connectors.register(AudioOutput, 'output', 'audio'); 76 | 77 | AudioOutput.prototype.connect = function (connector) { 78 | AudioConnector.prototype.connect.call(this, connector); 79 | this.endPoint.connect(connector.endPoint); 80 | }; 81 | 82 | AudioOutput.prototype.disconnect = function (connector) { 83 | AudioConnector.prototype.disconnect.call(this, connector); 84 | this.endPoint.disconnect(connector.endPoint); 85 | }; 86 | 87 | // AudioParam can be connected to AudioNode output 88 | // the two following functions allow this 89 | AudioOutput.prototype.isCompatible = function (connector) { 90 | if (connector.type === 'param' && connector.way === 'input') return true; 91 | return AudioConnector.prototype.isCompatible.call(this, connector); 92 | }; -------------------------------------------------------------------------------- /src/core/Buffer.js: -------------------------------------------------------------------------------- 1 | var Module = require('./Module'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function Buffer(bufferData) { 5 | Module.call(this, bufferData); 6 | 7 | var className = 'bufferAudio'; 8 | if (bufferData.ir) className = 'bufferIR'; 9 | if (bufferData.type === 'ProceduralBuffer') className = 'bufferProcedural'; 10 | this.addClassName(className); 11 | this.setTitle(bufferData.id); 12 | 13 | this.buffer = null; 14 | 15 | if (!bufferData) return console.warn('Buffer module should be initialized with buffer data.'); 16 | 17 | var t = this; 18 | 19 | // load buffer 20 | bufferData.loadAudioBuffer(function onBufferLoaded(error) { 21 | if (error) { 22 | console.error('Could not load buffer', bufferData, error); 23 | t.addClassName('bufferFailed'); 24 | return; 25 | } 26 | t.buffer = bufferData; 27 | t.onLoad(); 28 | }); 29 | } 30 | inherits(Buffer, Module); 31 | 32 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 33 | Buffer.prototype.onConnect = function (connector) { 34 | if (!this.buffer) return; 35 | this.$data.emitTo(connector, { _type: 'buffer', buffer: this.buffer }); 36 | }; 37 | 38 | Buffer.prototype.onLoad = function () { 39 | this.$data.emit({ _type: 'buffer', buffer: this.buffer }); 40 | }; 41 | 42 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 43 | Buffer.prototype.descriptor = { 44 | type: 'Buffer', 45 | size: 1, 46 | outputs: { data: { type: 'event', x:5, y:0, label: null, onConnect: 'onConnect' } } 47 | }; 48 | 49 | module.exports = Buffer; 50 | -------------------------------------------------------------------------------- /src/core/Button.js: -------------------------------------------------------------------------------- 1 | 2 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 3 | /** Button 4 | * 5 | * @author Cedric Stoquer 6 | */ 7 | function Button(module, id, descriptor) { 8 | this.initGUI(module, id, descriptor); 9 | 10 | // init references 11 | this.bind(module, id, descriptor); 12 | } 13 | 14 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 15 | Button.prototype.initGUI = function (module, id, descriptor) {}; 16 | Button.prototype.setTitle = function (text) {}; 17 | 18 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 19 | Button.prototype.bind = function (module, id, descriptor) { 20 | // button action 21 | var endPointDescription = descriptor.endPoint; 22 | if (!endPointDescription) return; 23 | 24 | // find endPoint function 25 | var endPoint = module; 26 | endPointDescription = endPointDescription.split('.'); 27 | var funcName = endPointDescription.pop(); 28 | for (var i = 0; i < endPointDescription.length; i++) { 29 | endPoint = endPoint[endPointDescription[i]]; 30 | } 31 | 32 | // bind references 33 | this.caller = endPoint; 34 | this.endPoint = this.caller[funcName]; 35 | }; 36 | 37 | module.exports = Button; 38 | -------------------------------------------------------------------------------- /src/core/Cable.js: -------------------------------------------------------------------------------- 1 | 2 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 3 | /** Cable 4 | * link two connectors 5 | * 6 | * @author Cedric Stoquer 7 | */ 8 | function Cable(a, b, color) { 9 | this.endPointA = a; 10 | this.endPointB = b; 11 | this.color = color || '#555'; 12 | this.id = this.getId(a, b); 13 | 14 | this.x = 0; // start point x 15 | this.y = 0; // start point y 16 | this.a = 0; // control point 1 x 17 | this.b = 0; // control point 1 y 18 | this.c = 0; // control point 2 x 19 | this.d = 0; // control point 2 y 20 | this.w = 0; // end point x 21 | this.h = 0; // end point y 22 | 23 | this.update(); 24 | 25 | a.module.addCable(this); 26 | b.module.addCable(this); 27 | } 28 | module.exports = Cable; 29 | 30 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 31 | Cable.prototype.getId = function (a, b) { 32 | return a.module.id + ':' + a.id + '--' + b.module.id + ':' + b.id; 33 | }; 34 | 35 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 36 | Cable.prototype.draw = function () {}; 37 | Cable.prototype.update = function () {}; 38 | 39 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 40 | Cable.prototype.disconnect = function () { 41 | if (this.endPointA) { 42 | // remove connections 43 | this.endPointA.disconnect(this.endPointB); 44 | this.endPointB.disconnect(this.endPointA); 45 | 46 | // remove cables references from modules 47 | this.endPointA.module.removeCable(this); 48 | this.endPointB.module.removeCable(this); 49 | } 50 | this.endPointA = null; 51 | this.endPointB = null; 52 | }; 53 | -------------------------------------------------------------------------------- /src/core/Connector.js: -------------------------------------------------------------------------------- 1 | var connectors = require('./connectors'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | /** Connector Abstract class 5 | * 6 | * @author Cedric Stoquer 7 | */ 8 | function Connector(module, id, descriptor) { 9 | this.module = module; 10 | this.id = id; 11 | this.x = descriptor.x; 12 | this.y = descriptor.y; 13 | this.singleConnection = descriptor.singleConnection === undefined ? false : !!descriptor.singleConnection; 14 | 15 | this.initGUI(module, id, descriptor); 16 | this.bind(module, id, descriptor); 17 | } 18 | module.exports = Connector; 19 | 20 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 21 | Connector.prototype.cssClassName = 'connector'; 22 | Connector.prototype.color = '#2da8ff'; 23 | Connector.prototype.type = 'none'; 24 | Connector.prototype.way = 'input'; 25 | connectors.register(Connector, 'input', 'none'); 26 | 27 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 28 | Connector.prototype.bind = function (module, id, descriptor) { 29 | /* virtual */ 30 | }; 31 | 32 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 33 | Connector.prototype.connect = function (connector) { 34 | var patch = this.module.patch; 35 | 36 | // check if one of the connector is a single connection 37 | if (this.singleConnection) patch.disconnect(this); 38 | if (connector.singleConnection) patch.disconnect(connector); 39 | 40 | // add cable 41 | patch.addCable(this, connector, this.color); 42 | }; 43 | 44 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 45 | Connector.prototype.disconnect = function (connector) { 46 | /* virtual */ 47 | }; 48 | 49 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 50 | Connector.prototype.isCompatible = function (connector) { 51 | // TODO: We suppose both connector's modules are in the same patch. 52 | // Do we want patches to be able to connect together ? 53 | if (connector === this) return false; 54 | if (this.type !== connector.type) return false; 55 | if (this.way === connector.way) return false; 56 | return true; 57 | }; 58 | 59 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 60 | Connector.prototype.initGUI = function (module, id, descriptor) {}; -------------------------------------------------------------------------------- /src/core/EventConnector.js: -------------------------------------------------------------------------------- 1 | var connectors = require('./connectors'); 2 | var Connector = require('./Connector'); 3 | 4 | var EVENT_CABLE_COLOR = '#2da8ff'; 5 | 6 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 7 | function getEndPoint(module, endPointDescriptor) { 8 | endPointDescriptor = endPointDescriptor || ''; 9 | endPointDescriptor = endPointDescriptor.split('.'); 10 | endPoint = module; 11 | for (var i = 0; i < endPointDescriptor.length; i++) { 12 | endPoint = endPoint[endPointDescriptor[i]]; 13 | } 14 | return endPoint 15 | } 16 | 17 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 18 | function EventInput(module, id, descriptor) { 19 | // this.module = module; 20 | Connector.call(this, module, id, descriptor); 21 | this.onConnect = descriptor.onConnect ? getEndPoint(module, descriptor.onConnect) : null; 22 | this.onDisconnect = descriptor.onDisconnect ? getEndPoint(module, descriptor.onDisconnect) : null; 23 | } 24 | inherits(EventInput, Connector); 25 | EventInput.prototype.cssClassName = 'eventIn'; 26 | EventInput.prototype.color = EVENT_CABLE_COLOR; 27 | EventInput.prototype.type = 'event'; 28 | EventInput.prototype.way = 'input'; 29 | connectors.register(EventInput, 'input', 'event'); 30 | 31 | EventInput.prototype.bind = function (module, id, descriptor) { 32 | // An event input endPoint is a reference to a function of the module 33 | // that will be called when an event comes in. 34 | this.endPoint = getEndPoint(module, descriptor.endPoint); 35 | }; 36 | 37 | EventInput.prototype.connect = function (connector) { 38 | // Connector.prototype.connect.call(this, connector); 39 | connector.connect(this); 40 | if (this.onConnect) this.onConnect.call(this.module, connector); 41 | }; 42 | 43 | EventInput.prototype.disconnect = function (connector) { 44 | if (this.onDisconnect) this.onDisconnect.call(this.module, connector); 45 | }; 46 | 47 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 48 | function EventOutput(module, id, descriptor) { 49 | Connector.call(this, module, id, descriptor); 50 | // event output endPoints is an array of EventInput connector references. 51 | this.connections = []; 52 | this.onConnect = descriptor.onConnect ? getEndPoint(module, descriptor.onConnect) : null; 53 | this.onDisconnect = descriptor.onDisconnect ? getEndPoint(module, descriptor.onDisconnect) : null; 54 | } 55 | inherits(EventOutput, Connector); 56 | EventOutput.prototype.cssClassName = 'eventOut'; 57 | EventOutput.prototype.color = EVENT_CABLE_COLOR; 58 | EventOutput.prototype.type = 'event'; 59 | EventOutput.prototype.way = 'output'; 60 | connectors.register(EventOutput, 'output', 'event'); 61 | 62 | EventOutput.prototype.connect = function (connector) { 63 | Connector.prototype.connect.call(this, connector); 64 | this.connections.push(connector); 65 | if (this.onConnect) this.onConnect.call(this.module, connector); 66 | }; 67 | 68 | EventOutput.prototype.disconnect = function (connector) { 69 | var index = this.connections.indexOf(connector); 70 | if (index === -1) return; 71 | this.connections.splice(index, 1); 72 | if (this.onDisconnect) this.onDisconnect.call(this.module, connector); 73 | }; 74 | 75 | EventOutput.prototype.emit = function (event) { 76 | event = event || {}; 77 | for (var i = 0; i < this.connections.length; i++) { 78 | var connector = this.connections[i]; 79 | // bind to the correct 'this' value (the connector's module) 80 | connector.endPoint.call(connector.module, event, this); 81 | } 82 | }; 83 | 84 | EventOutput.prototype.emitTo = function (connector, event) { 85 | connector.endPoint.call(connector.module, event, this); 86 | }; 87 | -------------------------------------------------------------------------------- /src/core/EventEmitter.js: -------------------------------------------------------------------------------- 1 | function EventEmitter() { 2 | this._events = {}; 3 | }; 4 | 5 | module.exports = EventEmitter; 6 | 7 | EventEmitter.listenerCount = function (emitter, event) { 8 | var handlers = emitter._events[event]; 9 | return handlers ? handlers.length : 0; 10 | }; 11 | 12 | EventEmitter.prototype.on = function (event, fn) { 13 | if (typeof fn !== 'function') throw new TypeError('Tried to register non-function as event handler: ' + event); 14 | 15 | // we emit first, because if event is "newListener" it would go recursive 16 | // this.emit('newListener', event, fn); 17 | 18 | var allHandlers = this._events; 19 | var eventHandlers = allHandlers[event]; 20 | if (eventHandlers === undefined) { 21 | // first event handler for this event type 22 | allHandlers[event] = [fn]; 23 | } else { 24 | eventHandlers.push(fn); 25 | } 26 | 27 | return this; 28 | }; 29 | 30 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 31 | 32 | EventEmitter.prototype.once = function (event, fn) { 33 | if (!fn.once) { 34 | fn.once = 1; 35 | } else { 36 | fn.once += 1; 37 | } 38 | 39 | return this.on(event, fn); 40 | }; 41 | 42 | EventEmitter.prototype.removeListener = function (event, handler) { 43 | var handlers = this._events[event]; 44 | if (handlers !== undefined) { 45 | var index = handlers.indexOf(handler); 46 | if (index === -1) { 47 | console.warn('No event listener registered', event, handler) 48 | return this; 49 | } 50 | 51 | handlers.splice(index, 1); 52 | if (handlers.length === 0) delete this._events[event]; 53 | } 54 | return this; 55 | }; 56 | 57 | EventEmitter.prototype.off = EventEmitter.prototype.removeListener; 58 | 59 | EventEmitter.prototype.removeAllListeners = function (event) { 60 | if (event) { 61 | delete this._events[event]; 62 | } else { 63 | this._events = {}; 64 | } 65 | return this; 66 | }; 67 | 68 | EventEmitter.prototype.hasListeners = function (event) { 69 | return this._events[event] !== undefined; 70 | }; 71 | 72 | EventEmitter.prototype.listeners = function (event) { 73 | var handlers = this._events[event]; 74 | if (handlers !== undefined) return handlers.slice(); 75 | 76 | return []; 77 | }; 78 | 79 | EventEmitter.prototype.emit = function (event) { 80 | var handlers = this._events[event]; 81 | if (handlers === undefined) return false; 82 | 83 | // copy handlers into a new array, so that handler removal doesn't affect array length 84 | handlers = handlers.slice(); 85 | 86 | var hadListener = false; 87 | 88 | // copy all arguments, but skip the first (the event name) 89 | var args = []; 90 | for (var i = 1; i < arguments.length; i++) { 91 | args.push(arguments[i]); 92 | } 93 | 94 | for (var i = 0, len = handlers.length; i < len; i++) { 95 | var handler = handlers[i]; 96 | 97 | handler.apply(this, args); 98 | hadListener = true; 99 | 100 | if (handler.once) { 101 | if (handler.once > 1) { 102 | handler.once--; 103 | } else { 104 | delete handler.once; 105 | } 106 | 107 | this.removeListener(event, handler); 108 | } 109 | } 110 | 111 | return hadListener; 112 | }; -------------------------------------------------------------------------------- /src/core/Knob.js: -------------------------------------------------------------------------------- 1 | var map = require('./utils').map; 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | /** Knob 5 | * 6 | * @author Cedric Stoquer 7 | */ 8 | function Knob(module, id, descriptor) { 9 | this.value = 0; 10 | this.x = descriptor.x; 11 | this.y = descriptor.y; 12 | this.min = descriptor.min || 0; 13 | this.max = descriptor.max !== undefined ? descriptor.max : 1; 14 | this.int = descriptor.int || false; 15 | 16 | this.initGUI(module, id, descriptor); 17 | 18 | // initialise 19 | this.bind(module, id, descriptor); 20 | } 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | Knob.prototype.bind = function (module, id, descriptor) { 24 | this.id = id; 25 | this.module = module; 26 | this.valueId = descriptor.value || 'value'; 27 | this.endPoint = module; 28 | 29 | var endPoint = descriptor.endPoint; 30 | if (endPoint) { 31 | endPoint = endPoint.split('.'); 32 | for (var i = 0; i < endPoint.length; i++) { 33 | this.endPoint = this.endPoint[endPoint[i]]; 34 | } 35 | // set default min max from AudioParam if exist 36 | // if (this.endPoint.minValue !== undefined) this.min = this.endPoint.minValue; 37 | // if (this.endPoint.maxValue !== undefined) this.max = this.endPoint.maxValue; 38 | } 39 | 40 | this.initValue(); 41 | }; 42 | 43 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 44 | /** Initialise Knob properties according to the endPoint current value */ 45 | Knob.prototype.initValue = function () { 46 | var value = this.endPoint[this.valueId]; 47 | if (value === undefined) value = 0; 48 | this.value = map(value, this.min, this.max, -68, 68); 49 | this.updateGUI(); 50 | }; 51 | 52 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 53 | /** Set endPoint value in respect to the knob internal value */ 54 | Knob.prototype.updateValue = function () { 55 | var value = map(this.value, -68, 68, this.min, this.max); 56 | if (this.int) value = ~~Math.round(value); 57 | this.endPoint[this.valueId] = value; 58 | this.displayValue(value); 59 | }; 60 | 61 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 62 | Knob.prototype.getState = function () { 63 | return this.value; 64 | }; 65 | 66 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 67 | Knob.prototype.setState = function (value) { 68 | this.value = value; 69 | this.updateValue(); 70 | this.updateGUI(); 71 | }; 72 | 73 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 74 | Knob.prototype.initGUI = function (module, id, descriptor) {}; 75 | Knob.prototype.updateGUI = function () {}; 76 | Knob.prototype.displayValue = function (value) {}; 77 | 78 | module.exports = Knob; 79 | -------------------------------------------------------------------------------- /src/core/MIDI.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('./EventEmitter'); 2 | 3 | MIDI_MESSAGE_TYPES = { 4 | 8: 'note off', 5 | 9: 'note on', 6 | 10: 'aftertouch', 7 | 11: 'control change', 8 | 12: 'program change', 9 | 13: 'channel pressure', 10 | 14: 'pitch bend' 11 | }; 12 | 13 | var MIDI = new EventEmitter(); 14 | 15 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 16 | function onMessage(message) { 17 | var data = message.data; 18 | var status = data[0]; 19 | if (status >= 240) return; // TODO: system common messages 20 | var typeId = (status & 240) >> 4; 21 | var type = MIDI_MESSAGE_TYPES[typeId]; 22 | if (!type) return; 23 | var channel = (status & 15); 24 | var data1 = data[1]; 25 | var data2 = data[2]; 26 | 27 | var e = { _type: 'midi message', channel: channel, midiType: type }; 28 | 29 | switch (type) { 30 | case 'note on': 31 | case 'note off': 32 | e.note = data1; 33 | e.velocity = data2; 34 | break; 35 | case 'control change': 36 | e.control = data1; 37 | e.value = data2; 38 | break; 39 | // TODO: aftertouch 40 | // TODO: program change 41 | // TODO: channel pressure 42 | // TODO: pitch bend 43 | default: 44 | e.data1 = data1; 45 | e.data2 = data2; 46 | } 47 | 48 | MIDI.emit('message', e); 49 | } 50 | 51 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 52 | function onMidiFailure(error) { 53 | console.error('requestMIDIAccess failed', error); 54 | } 55 | 56 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 57 | function onMidiSuccess(midiAccess) { 58 | var inputs = midiAccess.inputs.values(); 59 | for (var input = inputs.next(); input && !input.done; input = inputs.next()) { 60 | input.value.onmidimessage = onMessage; 61 | } 62 | } 63 | 64 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 65 | var isOpened = false; 66 | 67 | module.exports = MIDI; 68 | 69 | MIDI.open = function openMidi() { 70 | if (isOpened) return; 71 | isOpened = true; 72 | navigator.requestMIDIAccess({ sysex: false }).then(onMidiSuccess, onMidiFailure); 73 | }; -------------------------------------------------------------------------------- /src/core/ParamConnector.js: -------------------------------------------------------------------------------- 1 | var connectors = require('./connectors'); 2 | var Connector = require('./Connector'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function ParamConnector(module, id, descriptor) { 6 | this.connections = []; 7 | Connector.call(this, module, id, descriptor); 8 | } 9 | inherits(ParamConnector, Connector); 10 | // ParamConnector.prototype.color = '#a9ee22'; 11 | ParamConnector.prototype.color = '#2da8ff'; 12 | ParamConnector.prototype.type = 'param'; 13 | 14 | ParamConnector.prototype.connect = function (connector) { 15 | Connector.prototype.connect.call(this, connector); 16 | // save connections for rebind 17 | this._addConnection(connector); 18 | connector._addConnection(this); 19 | }; 20 | 21 | ParamConnector.prototype.disconnect = function (connector) { 22 | Connector.prototype.disconnect.call(this, connector); 23 | // remove saved connections 24 | this._removeConnection(connector); 25 | connector._removeConnection(this); 26 | }; 27 | 28 | ParamConnector.prototype._addConnection = function (connector) { 29 | this.connections.push(connector); 30 | }; 31 | 32 | ParamConnector.prototype._removeConnection = function (connector) { 33 | var index = this.connections.indexOf(connector); 34 | if (index === -1) return; // happen to the second connector when a cable is removed 35 | this.connections.splice(index, 1); 36 | }; 37 | 38 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 39 | function ParamInput(module, id, descriptor) { 40 | this.endPoint = module; // redefined at bind 41 | this.min = descriptor.min === undefined ? 0 : descriptor.min; 42 | this.max = descriptor.max === undefined ? 1 : descriptor.max; 43 | ParamConnector.call(this, module, id, descriptor); 44 | } 45 | inherits(ParamInput, ParamConnector); 46 | ParamInput.prototype.cssClassName = 'paramIn'; 47 | ParamInput.prototype.way = 'input'; 48 | connectors.register(ParamInput, 'input', 'param'); 49 | 50 | ParamInput.prototype.bind = function (module, id, descriptor) { 51 | // find endPoint reference 52 | var endPoint = descriptor.endPoint.split('.'); 53 | this.endPoint = module; 54 | for (var i = 0; i < endPoint.length; i++) { 55 | this.endPoint = this.endPoint[endPoint[i]]; 56 | } 57 | 58 | // reconnect previous connections if endPoint changed 59 | var connections = this.connections; 60 | this.connections = []; 61 | for (var i = connections.length - 1; i >= 0; i--) { 62 | this.connect(connections[i]); 63 | } 64 | }; 65 | 66 | // AudioParam can be connected to AudioNode output 67 | // the two following functions allow this 68 | ParamInput.prototype.isCompatible = function (connector) { 69 | if (connector.type === 'audio' && connector.way === 'output') return true; 70 | return ParamConnector.prototype.isCompatible.call(this, connector); 71 | }; 72 | 73 | ParamInput.prototype.connect = function (connector) { 74 | ParamConnector.prototype.connect.call(this, connector); 75 | if (connector.type === 'audio' && connector.way === 'output') { 76 | connector.endPoint.connect(this.endPoint); 77 | } 78 | }; 79 | 80 | 81 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 82 | function ParamOutput(module, id, descriptor) { 83 | ParamConnector.call(this, module, id, descriptor); 84 | } 85 | inherits(ParamOutput, ParamConnector); 86 | ParamOutput.prototype.cssClassName = 'paramOut'; 87 | ParamOutput.prototype.way = 'output'; 88 | connectors.register(ParamOutput, 'output', 'param'); 89 | 90 | ParamOutput.prototype.setAutomation = function (func) { 91 | var connections = this.connections; 92 | for (var i = 0; i < connections.length; i++) { 93 | var connector = connections[i]; 94 | func(connector.endPoint, connector.min, connector.max); 95 | } 96 | }; 97 | 98 | -------------------------------------------------------------------------------- /src/core/audioContext.js: -------------------------------------------------------------------------------- 1 | var AudioContext = window.AudioContext || window.webkitAudioContext; 2 | var audioContext = new AudioContext(); 3 | module.exports = audioContext; -------------------------------------------------------------------------------- /src/core/connectors.js: -------------------------------------------------------------------------------- 1 | /** ConnectorFactory 2 | * 3 | * @author Cedric Stoquer 4 | */ 5 | connectors = { 6 | input: {}, 7 | output: {} 8 | }; 9 | 10 | exports.register = function (ConnectorClass, way, type) { 11 | if (!connectors[way]) return; 12 | connectors[way][type] = ConnectorClass; 13 | }; 14 | 15 | exports.getConnector = function (way, type) { 16 | if (!connectors[way]) return undefined; 17 | return connectors[way][type]; 18 | }; 19 | 20 | require('./AudioConnector'); 21 | require('./EventConnector'); 22 | require('./ParamConnector'); 23 | -------------------------------------------------------------------------------- /src/core/moduleCategories.js: -------------------------------------------------------------------------------- 1 | exports.SAMPLER = 'Sampler'; 2 | exports.OSC = 'Osc'; 3 | exports.GAIN = 'Gain'; 4 | exports.FILTER = 'Filter'; 5 | exports.ENVELOPE = 'Envelope'; 6 | exports.EFFECT = 'Effect'; 7 | exports.DATA = 'Data'; 8 | exports.CONTROL = 'Control'; 9 | exports.IN_OUT = 'In/Out'; 10 | // exports.OTHER = 'misc'; -------------------------------------------------------------------------------- /src/core/modules.js: -------------------------------------------------------------------------------- 1 | var MODULES_CONSTRUCTOR_BY_ID = {}; 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | exports.add = function (ModuleConstructor, category) { 5 | var descriptor = ModuleConstructor.prototype.descriptor; 6 | descriptor._category = category; 7 | MODULES_CONSTRUCTOR_BY_ID[descriptor.type] = ModuleConstructor; 8 | }; 9 | 10 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 11 | exports.getModuleConstructor = function (type) { 12 | return MODULES_CONSTRUCTOR_BY_ID[type]; 13 | }; 14 | 15 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 16 | exports.getList = function () { 17 | return MODULES_CONSTRUCTOR_BY_ID; 18 | }; 19 | -------------------------------------------------------------------------------- /src/core/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility functions 3 | */ 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | /** map a value from an input interval [iMin ~ iMax] to an output interval [oMin ~ oMax] 6 | * preconditions: iMin != iMax 7 | */ 8 | exports.map = function (value, iMin, iMax, oMin, oMax) { 9 | return oMin + (oMax - oMin) * (value - iMin) / (iMax - iMin); 10 | }; 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | /** Make a deep copy of an object */ 14 | // exports.copyObject = function (object) { 15 | 16 | // function copyObject(source) { 17 | // if (typeof source === 'object') { 18 | // if (Array.isArray(source)) { 19 | // var arrayCopy = []; 20 | // for (var i = 0; i < source.length; i++) { 21 | // arrayCopy.push(copyObject(source[i])); 22 | // } 23 | // return arrayCopy; 24 | // } else { 25 | // // we assume it's a map object 26 | // var objectCopy = {}; 27 | // for (var key in source) { 28 | // objectCopy[key] = copyObject(source[key]); 29 | // } 30 | // return objectCopy; 31 | // } 32 | // } else { 33 | // // we assume it is a simple type 34 | // return source; 35 | // } 36 | // } 37 | 38 | // return copyObject(object); 39 | // }; 40 | 41 | exports.copyObject = function (object) { 42 | return JSON.parse(JSON.stringify(object)); 43 | }; 44 | -------------------------------------------------------------------------------- /src/data/BufferData.js: -------------------------------------------------------------------------------- 1 | var loadAudioBuffer = require('../loaders/loadAudioBuffer'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function BufferData(id, data) { 5 | this.id = id; 6 | this.buffer = undefined; 7 | this.uri = data.uri; 8 | this.loop = data.loop || false; 9 | this.ir = data.ir || false; 10 | this.start = data.start || 0; 11 | this.end = data.end || 0; 12 | this.tag = data.tag || []; 13 | } 14 | 15 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 16 | // static method 17 | 18 | BufferData.deserialize = function (data) { 19 | // TODO: check for this BufferData existence in the database 20 | return new BufferData(data.id, data); 21 | }; 22 | 23 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 24 | BufferData.prototype.serialize = function () { 25 | return { 26 | _type: 'BufferData', 27 | id: this.id, 28 | uri: this.uri, 29 | loop: this.loop, 30 | ir: this.ir, 31 | start: this.start, 32 | end: this.end, 33 | tag: this.tag 34 | }; 35 | }; 36 | 37 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 38 | BufferData.prototype.loadAudioBuffer = function (cb) { 39 | // check if buffer is already loaded 40 | if (this.buffer) return window.setTimeout(cb, 0); 41 | 42 | var t = this; 43 | 44 | loadAudioBuffer(this.uri, function onLoad(error, buffer) { 45 | if (error) return cb(error); 46 | t.buffer = buffer; 47 | return cb(); 48 | }); 49 | }; 50 | 51 | module.exports = BufferData; 52 | -------------------------------------------------------------------------------- /src/data/ProceduralBuffer.js: -------------------------------------------------------------------------------- 1 | var synthesizers = require('../synthesizers'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function ProceduralBuffer(id, data) { 5 | this.id = id; 6 | this.buffer = undefined; // audio buffer 7 | this.synthesizer = data.synthesizer; // synthesizer id 8 | this.params = data.params; // synth parameters 9 | 10 | // normal bufferData compatibility 11 | this.loop = data.loop || false; 12 | this.ir = data.ir || false; 13 | this.start = data.start || 0; 14 | this.end = data.end || 0; 15 | this.tag = data.tag || []; 16 | } 17 | 18 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 19 | // static methods & attribute 20 | 21 | ProceduralBuffer.deserialize = function (data) { 22 | // TODO: check for this ProceduralBuffer existence in the database 23 | return new ProceduralBuffer(data.id, data); 24 | }; 25 | 26 | ProceduralBuffer.prototype.type = 'ProceduralBuffer'; 27 | 28 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 29 | ProceduralBuffer.prototype.serialize = function () { 30 | return { 31 | _type: 'ProceduralBuffer', 32 | id: this.id, 33 | synthesizer: this.synthesizer, 34 | params: this.params, 35 | loop: this.loop, 36 | ir: this.ir, 37 | start: this.start, 38 | end: this.end, 39 | tag: this.tag 40 | }; 41 | }; 42 | 43 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 44 | ProceduralBuffer.prototype.loadAudioBuffer = function (cb) { 45 | // check if buffer is already generated 46 | if (this.buffer) return window.setTimeout(cb, 0); 47 | this.generateBuffer(cb); 48 | }; 49 | 50 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 51 | ProceduralBuffer.prototype.generateBuffer = function (cb) { 52 | // get proper synthesizer 53 | var synth = synthesizers.getSynth(this.synthesizer); 54 | if (!synth) { 55 | // defer callback 56 | window.setTimeout(function () { 57 | cb('Synthesizer ' + this.synthesizer + ' does not exists.'); 58 | }, 0); 59 | return; 60 | } 61 | 62 | synth.generate(this, cb); 63 | }; 64 | 65 | module.exports = ProceduralBuffer; 66 | -------------------------------------------------------------------------------- /src/data/dataTypes.js: -------------------------------------------------------------------------------- 1 | var DATA_TYPES = { 2 | 'BufferData': require('./BufferData'), 3 | 'ProceduralBuffer': require('./ProceduralBuffer') 4 | }; 5 | 6 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 7 | function deserialize(data) { 8 | var type = data._type; 9 | var DataType = DATA_TYPES[type]; 10 | if (!DataType) return data; 11 | return DataType.deserialize(data); 12 | } 13 | 14 | exports.deserialize = deserialize; 15 | 16 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 17 | exports.initializeDatabase = function (database) { 18 | for (var id in database) { 19 | var data = database[id]; 20 | if (!data.id) data.id = id; 21 | database[id] = deserialize(data); 22 | } 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /src/editor.js: -------------------------------------------------------------------------------- 1 | 2 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 3 | window.inherits = function (Child, Parent) { 4 | Child.prototype = Object.create(Parent.prototype, { 5 | constructor: { 6 | value: Child, 7 | enumerable: false, 8 | writable: true, 9 | configurable: true 10 | } 11 | }); 12 | }; 13 | 14 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 15 | function loadJson(path, cb) { 16 | var xobj = new XMLHttpRequest(); 17 | xobj.onreadystatechange = function () { 18 | if (~~xobj.readyState !== 4) return; 19 | if (~~xobj.status !== 200) return cb('xhrError:' + xobj.status); 20 | return cb && cb(null, JSON.parse(xobj.response)); 21 | }; 22 | xobj.open('GET', path, true); 23 | xobj.send(); 24 | } 25 | 26 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 27 | loadJson('assets/buffers.json', function onAssetsLoaded(error, assets) { 28 | if (error) return console.error(error); 29 | window.assets = { buffers: assets }; 30 | var main = require('./main.js'); 31 | }); 32 | -------------------------------------------------------------------------------- /src/loaders/loadAudioBuffer.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | 3 | module.exports = function (uri, cb) { 4 | var xobj = new XMLHttpRequest(); 5 | xobj.responseType = 'arraybuffer'; 6 | 7 | xobj.onreadystatechange = function onXhrStateChange() { 8 | if (~~xobj.readyState !== 4) return; 9 | if (~~xobj.status !== 200 && ~~xobj.status !== 0) { 10 | return cb('xhrError:' + xobj.status); 11 | } 12 | audioContext.decodeAudioData(xobj.response, function onSuccess(buffer) { 13 | return cb(null, buffer); 14 | }, cb); 15 | }; 16 | 17 | xobj.open('GET', uri, true); 18 | xobj.send(); 19 | }; 20 | -------------------------------------------------------------------------------- /src/loaders/sendRequest.js: -------------------------------------------------------------------------------- 1 | var IS_ELECTRON = (function () { 2 | try { 3 | var isElectron = _REQUIRE_('electron'); 4 | return !!isElectron; 5 | } catch (e) { 6 | return false; 7 | } 8 | })(); 9 | 10 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 11 | if (IS_ELECTRON) { 12 | module.exports = _REQUIRE_('electron').remote._REQUIRE_('./tools/commands'); 13 | } else { 14 | module.exports = function (data, cb) { 15 | var xobj = new XMLHttpRequest(); 16 | xobj.open('POST', 'req', true); 17 | xobj.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); 18 | xobj.onreadystatechange = function () { 19 | if (~~xobj.readyState !== 4) return; 20 | if (~~xobj.status !== 200) return cb && cb('xhr:' + xobj.status); 21 | var res = JSON.parse(xobj.response); 22 | return cb && cb(res.error, res.result); 23 | }; 24 | xobj.send(JSON.stringify(data)); 25 | }; 26 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main file used only for MODULAR tool ui 3 | * This file is not used for builing the MODULAR stand-alone library 4 | */ 5 | 6 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 7 | // core & modules 8 | 9 | require('./modules'); 10 | require('./data/dataTypes').initializeDatabase(window.assets.buffers); 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | // UI 14 | 15 | require('./ui/moduleGUI'); 16 | require('./ui/connectorGUI'); 17 | require('./ui/cableGUI'); 18 | require('./ui/knobGUI'); 19 | require('./ui/buttonGUI'); 20 | 21 | require('./ui/menuHeader'); // require all panels 22 | require('./ui/moduleManager'); 23 | require('./ui/dropFile'); 24 | require('./ui/onWindowResize'); 25 | 26 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 27 | // register synthesizer editors 28 | var synthEditor = require('./ui/synthEditor'); 29 | synthEditor.register('disco', require('./synthesizers/disco/editor')); 30 | synthEditor.register('hats', require('./synthesizers/hats/editor')); 31 | 32 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 33 | require('./modular'); 34 | -------------------------------------------------------------------------------- /src/modular.js: -------------------------------------------------------------------------------- 1 | /** 2 | * main file being builded for Electron 3 | */ 4 | window.inherits = require('inherits'); 5 | 6 | // require('./modules'); 7 | // require('./data/dataTypes'); 8 | 9 | window.MODULAR = { 10 | Patch: require('./core/Patch'), // for loading patch 11 | Synths: require('./synthesizers'), // for adding external synthesizers 12 | }; 13 | -------------------------------------------------------------------------------- /src/modules/Amp.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Amp() { 6 | this.node = audioContext.createGain(); 7 | this.node.gain.value = 0; 8 | Module.call(this); 9 | } 10 | inherits(Amp, Module); 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | Amp.prototype.descriptor = { 14 | type: 'Amp', 15 | name: 'Amp', 16 | size: 2, 17 | inputs: { 18 | IN: { type: 'audio', x:0.0, y:1, endPoint: 'node', label: 'IN' }, 19 | MOD: { type: 'param', x:3.5, y:0, endPoint: 'node.gain', label: 'MOD' }, 20 | }, 21 | outputs: { OUT: { type: 'audio', x:3.5, y:1, endPoint: 'node', label: 'OUT' } } 22 | }; 23 | 24 | module.exports = Amp; -------------------------------------------------------------------------------- /src/modules/AutoBang.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function AutoBang() { 5 | this.data = null; 6 | this.duration = 5; 7 | this.timeout = null; 8 | 9 | Module.call(this); 10 | 11 | this.scheduleNext(); 12 | } 13 | inherits(AutoBang, Module); 14 | 15 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 16 | AutoBang.prototype.onDataIn = function (data) { 17 | this.data = data; 18 | }; 19 | 20 | AutoBang.prototype.scheduleNext = function () { 21 | var t = this; 22 | this.timeout = window.setTimeout(function () { 23 | t.$OUT.emit(this.data); 24 | t.scheduleNext(); 25 | }, this.duration * 1000); 26 | }; 27 | 28 | AutoBang.prototype.remove = function () { 29 | // cancel timeout on unload 30 | if (this.timeout !== null) { 31 | window.clearTimeout(this.timeout); 32 | this.timeout = null; 33 | } 34 | Module.prototype.remove.call(this); 35 | }; 36 | 37 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 38 | AutoBang.prototype.descriptor = { 39 | type: 'AutoBang', 40 | name: 'AutoBang', 41 | size: 2, 42 | inputs: { IN: { type: 'event', x:0, y:1, label: 'DATA', endPoint: 'onDataIn' } }, 43 | outputs: { OUT: { type: 'event', x:2.3, y:1 } }, 44 | controls: { 45 | duration: { type: 'knob', x: 4.0, y: 0.1, min: 1, max: 10, value: 'duration' }, 46 | } 47 | }; 48 | 49 | module.exports = AutoBang; -------------------------------------------------------------------------------- /src/modules/AutoXFade.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function AutoXFade() { 6 | this.gainA = audioContext.createGain(); 7 | this.gainB = audioContext.createGain(); 8 | this.mix = audioContext.createGain(); 9 | 10 | // internal connections 11 | this.gainA.connect(this.mix); 12 | this.gainB.connect(this.mix); 13 | 14 | // cross fade duration 15 | this.duration = 5; 16 | this.channel = false; // false = channel A, true = channel B 17 | 18 | // initiatise audioParam 19 | this.gainA.gain.value = 1; 20 | this.gainB.gain.value = 0; 21 | 22 | Module.call(this); 23 | } 24 | inherits(AutoXFade, Module); 25 | 26 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 27 | AutoXFade.prototype.trigger = function () { 28 | var currentTime = audioContext.currentTime; 29 | 30 | this.channel = !this.channel; 31 | 32 | var valueA = this.gainA.gain.value; 33 | var valueB = this.gainB.gain.value; 34 | 35 | var targetA = this.channel ? 0 : 1; 36 | var targetB = this.channel ? 1 : 0; 37 | 38 | this.gainA.gain.cancelScheduledValues(0); 39 | this.gainB.gain.cancelScheduledValues(0); 40 | 41 | this.gainA.gain.setValueAtTime(valueA, currentTime); 42 | this.gainA.gain.linearRampToValueAtTime(targetA, currentTime + this.duration); 43 | 44 | this.gainB.gain.setValueAtTime(valueB, currentTime); 45 | this.gainB.gain.linearRampToValueAtTime(targetB, currentTime + this.duration); 46 | }; 47 | 48 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 49 | AutoXFade.prototype.descriptor = { 50 | type: 'AutoXFade', 51 | name: 'AutoXFade', 52 | size: 4, 53 | inputs: { 54 | A: { type: 'audio', x:0.0, y:0.8, endPoint: 'gainA', label: 'A' }, 55 | B: { type: 'audio', x:0.0, y:1.8, endPoint: 'gainB', label: 'B' }, 56 | TRG: { type: 'event', x:0.0, y:3.2, endPoint: 'trigger', label: 'TRG' }, 57 | }, 58 | outputs: { 59 | OUT: { type: 'audio', x:3.5, y:3.2, endPoint: 'mix', label: 'OUT' } 60 | }, 61 | controls: { 62 | duration: { type: 'knob', x: 2.2, y: 0.8, min: 0.5, max: 20, endPoint: null, value: 'duration', label: 'TIME' }, 63 | volume: { type: 'knob', x: 4.2, y: 0.8, min: 0, max: 1, endPoint: 'mix.gain', value: 'value', label: 'VOL' }, 64 | } 65 | }; 66 | 67 | module.exports = AutoXFade; -------------------------------------------------------------------------------- /src/modules/Bang.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function Bang() { 5 | Module.call(this); 6 | this.data = null; 7 | } 8 | inherits(Bang, Module); 9 | 10 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 11 | Bang.prototype.onDataIn = function (data) { 12 | this.data = data; 13 | }; 14 | 15 | Bang.prototype.pushButton = function () { 16 | this.$OUT.emit(this.data); 17 | }; 18 | 19 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 20 | Bang.prototype.descriptor = { 21 | type: 'Bang', 22 | name: 'Bang', 23 | size: 2, 24 | inputs: { IN: { type: 'event', x:3.5, y:0, label: 'DATA', endPoint: 'onDataIn', singleConnection: true } }, 25 | outputs: { OUT: { type: 'event', x:3.5, y:1, label: 'OUT' } }, 26 | controls: { BTN: { type: 'button', x: 1.8, y: 0.1, endPoint: 'pushButton' } } 27 | }; 28 | 29 | module.exports = Bang; -------------------------------------------------------------------------------- /src/modules/BufferTrim.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function BufferTrim() { 6 | this.buffer = null; 7 | Module.call(this); 8 | } 9 | inherits(BufferTrim, Module); 10 | 11 | BufferTrim.prototype.setBuffer = function (event) { 12 | if (event._type !== 'buffer') return; 13 | 14 | var sourceBuffer = event.buffer.buffer; 15 | var numberOfChannels = sourceBuffer.numberOfChannels; 16 | var sampleRate = sourceBuffer.sampleRate; 17 | var offset = event.buffer.start * sampleRate; 18 | var bufferLength = sourceBuffer.length - offset; 19 | 20 | // trim end 21 | if (event.buffer.end) { 22 | if (event.buffer.end > 0) { 23 | bufferLength = (event.buffer.end - event.buffer.start) * sampleRate; 24 | } else { 25 | bufferLength += event.buffer.end * sampleRate; 26 | } 27 | } 28 | 29 | var buffer = audioContext.createBuffer(numberOfChannels, bufferLength, sampleRate); 30 | 31 | for (var channel = 0; channel < numberOfChannels; channel++) { 32 | // buffer.copyToChannel(sourceBuffer.getChannelData(channel), channel, 0); 33 | sourceBuffer.copyFromChannel(buffer.getChannelData(channel), channel, offset); 34 | } 35 | 36 | this.buffer = { 37 | buffer: buffer, 38 | start: 0, 39 | end: 0, // TODO 40 | loop: false // TODO 41 | }; 42 | 43 | this.$data.emit({ _type: 'buffer', buffer: this.buffer }); 44 | }; 45 | 46 | BufferTrim.prototype.onConnect = function (connector) { 47 | if (!this.buffer) return; 48 | this.$data.emitTo(connector, { _type: 'buffer', buffer: this.buffer }); 49 | }; 50 | 51 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 52 | BufferTrim.prototype.descriptor = { 53 | type: 'BufferTrim', 54 | name: 'BufferTrim', 55 | size: 2, 56 | inputs: { buffer: { type: 'event', x:0, y:1, endPoint: 'setBuffer', label: 'BUF', singleConnection: true } }, 57 | outputs: { data: { type: 'event', x:3, y:1, label: 'TRIM', onConnect: 'onConnect' } }, 58 | controls: { } 59 | }; 60 | 61 | module.exports = BufferTrim; -------------------------------------------------------------------------------- /src/modules/Context.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Context() { 6 | this.node = audioContext; 7 | Module.call(this); 8 | } 9 | inherits(Context, Module); 10 | 11 | Context.prototype.descriptor = { 12 | type: 'Context', 13 | name: 'Context', 14 | size: 1, 15 | inputs: { DEST: { type: 'audio', x:3, y:0, endPoint: 'node.destination', label: 'DEST' } } 16 | }; 17 | 18 | module.exports = Context; -------------------------------------------------------------------------------- /src/modules/ControlChange.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | var map = require('../core/utils').map; 4 | 5 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 6 | function ControlChange() { 7 | this.channel = 0; 8 | this.control = 0; 9 | this.learn = false; 10 | this.glide = 0.1; 11 | Module.call(this); 12 | this._setTitle(); 13 | } 14 | inherits(ControlChange, Module); 15 | 16 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 17 | ControlChange.prototype.onMessage = function (event) { 18 | if (event._type !== 'midi message') return; 19 | if (event.midiType !== 'control change') return; 20 | 21 | if (this.learn) { 22 | this.channel = event.channel; 23 | this.control = event.control; 24 | this.learn = false; 25 | this.setBorder(''); 26 | this._setTitle(); 27 | return; 28 | } 29 | 30 | if (event.channel !== this.channel) return; 31 | if (event.control !== this.control) return; 32 | 33 | var currentTime = audioContext.currentTime; 34 | 35 | var glide = this.glide; 36 | var value = map(event.value, 0, 127, 0, 1); 37 | 38 | this.$OUT.setAutomation(function (param, min, max) { 39 | param.cancelScheduledValues(0); 40 | param.setValueAtTime(param.value, currentTime); 41 | param.linearRampToValueAtTime(map(value, 0, 1, min, max), currentTime + glide); 42 | }); 43 | }; 44 | 45 | ControlChange.prototype.midiLearn = function () { 46 | this.learn = true; 47 | this.setBorder('#F00'); 48 | }; 49 | 50 | ControlChange.prototype._setTitle = function () { 51 | this.setTitle('CC#' + this.channel + ':' + this.control); 52 | }; 53 | 54 | ControlChange.prototype.setState = function (state) { 55 | Module.prototype.setState.call(this, state); 56 | this._setTitle(); 57 | }; 58 | 59 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 60 | ControlChange.prototype.descriptor = { 61 | type: 'ControlChange', 62 | name: 'Control Change', 63 | size: 3, 64 | inputs: { IN: { type: 'event', x:0, y:1, label: 'IN', endPoint: 'onMessage' } }, 65 | outputs: { OUT: { type: 'param', x:0, y:2, label: 'OUT' } }, 66 | controls: { 67 | glide: { type: 'knob', x: 4, y: 0.5, min: 0, max: 1, endPoint: '', value: 'glide', label: 'GLID' }, 68 | midiLearn: { type: 'button', x: 2.3, y: 1, endPoint: 'midiLearn' } 69 | }, 70 | persistent: ['channel', 'control'] 71 | }; 72 | 73 | module.exports = ControlChange; -------------------------------------------------------------------------------- /src/modules/Convolver.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Convolver() { 6 | this.node = audioContext.createConvolver(); 7 | this.bufferData = null; 8 | Module.call(this); 9 | } 10 | inherits(Convolver, Module); 11 | 12 | Convolver.prototype.setBuffer = function (event) { 13 | if (event._type !== 'buffer') return; 14 | 15 | // a buffer can not be set again, we need to create new convolver. 16 | if (this.bufferData) { 17 | // remove current convolver 18 | this.node.disconnect(); 19 | 20 | // create a new convolver 21 | this.node = audioContext.createConvolver(); 22 | this.bufferData = null; 23 | 24 | // rebind everything 25 | this.rebind(); 26 | } 27 | 28 | this.bufferData = event.buffer; 29 | var buffer = this.bufferData.buffer; 30 | this.node.buffer = buffer; 31 | } 32 | 33 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 34 | Convolver.prototype.descriptor = { 35 | type: 'Convolver', 36 | name: 'Convolver', 37 | size: 3, 38 | inputs: { 39 | IN: { type: 'audio', x:3, y:1, endPoint: 'node', label: 'IN' }, 40 | buffer: { type: 'event', x:0, y:1, endPoint: 'setBuffer', label: 'BUF', /*singleConnection: true*/ }, 41 | }, 42 | outputs: { OUT: { type: 'audio', x:3, y:2, endPoint: 'node', label: 'OUT' } }, 43 | controls: {} 44 | }; 45 | 46 | module.exports = Convolver; -------------------------------------------------------------------------------- /src/modules/DateBang.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function DateBang() { 5 | this._hour = 12; 6 | this._minute = 0; 7 | this.data = null; 8 | this.dateTimeout = null; 9 | this.knobTimeout = null; 10 | Module.call(this); 11 | } 12 | inherits(DateBang, Module); 13 | 14 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 15 | DateBang.prototype.onDataIn = function (event) { 16 | this.data = data; 17 | }; 18 | 19 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 20 | DateBang.prototype.updateTimeout = function () { 21 | var t = this; 22 | 23 | if (this.knobTimeout) window.clearTimeout(this.knobTimeout); 24 | 25 | // wait 1 seconds before setting the bang timeout in case the user is still changing data. 26 | this.knobTimeout = window.setTimeout(function () { 27 | t.setTimeout(); 28 | }, 1000); 29 | }; 30 | 31 | DateBang.prototype.setTimeout = function () { 32 | var t = this; 33 | 34 | // cancel previous timeout 35 | if (this.dateTimeout) window.clearTimeout(this.dateTimeout); 36 | 37 | // compute when next timeout should occurr (in minutes) 38 | var now = new Date(); 39 | var n = now.getHours() * 60 + now.getMinutes(); 40 | var d = this._hour * 60 + this._minute; 41 | 42 | var delay = d - n; 43 | if (delay <= 0) delay = 24 * 60 + delay; 44 | 45 | // set timeout 46 | t.dateTimeout = window.setTimeout(function () { 47 | t.dateTimeout = null; 48 | t.$OUT.emit(t.data); 49 | t.setTimeout(); 50 | }, delay * 60 * 1000); 51 | }; 52 | 53 | DateBang.prototype.remove = function () { 54 | if (this.knobTimeout) window.clearTimeout(this.knobTimeout); 55 | if (this.dateTimeout) window.clearTimeout(this.dateTimeout); 56 | Module.prototype.remove.call(this); 57 | }; 58 | 59 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 60 | Object.defineProperty(DateBang.prototype, 'hour', { 61 | get: function() { 62 | return this._hour; 63 | }, 64 | set: function(value) { 65 | this._hour = value; 66 | this.updateTimeout(); 67 | } 68 | }); 69 | 70 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 71 | Object.defineProperty(DateBang.prototype, 'minute', { 72 | get: function() { 73 | return this._minute; 74 | }, 75 | set: function(value) { 76 | this._minute = value; 77 | this.updateTimeout(); 78 | } 79 | }); 80 | 81 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 82 | DateBang.prototype.descriptor = { 83 | type: 'DateBang', 84 | name: 'DateBang', 85 | size: 3, 86 | inputs: { IN: { type: 'event', x:3.5, y:0, label: 'IN', endPoint: 'onDataIn', singleConnection: true } }, 87 | outputs: { OUT: { type: 'event', x:3.5, y:2, label: 'OUT' } }, 88 | controls: { 89 | hour: { type: 'knob', x: 0, y: 0.6, min: 0, max: 23, int: true, endPoint: null, value: 'hour', label: 'HOUR' }, 90 | minute: { type: 'knob', x: 1.8, y: 0.6, min: 0, max: 59, int: true, endPoint: null, value: 'minute', label: 'MIN' }, 91 | } 92 | }; 93 | 94 | module.exports = DateBang; -------------------------------------------------------------------------------- /src/modules/Delay.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Delay() { 6 | this.node = audioContext.createDelay(1); 7 | Module.call(this); 8 | } 9 | inherits(Delay, Module); 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | Delay.prototype.descriptor = { 13 | type: 'Delay', 14 | name: 'Delay', 15 | size: 3, 16 | inputs: { IN: { type: 'audio', x:0.0, y:1, endPoint: 'node', label: 'IN' } }, 17 | outputs: { OUT: { type: 'audio', x:3.6, y:1, endPoint: 'node', label: 'OUT' } }, 18 | controls: { 19 | delay: { type: 'knob', x: 2.0, y: 0.3, min: 0.01, max: 1.0, endPoint: 'node.delayTime', value: 'value', label: 'TIME' }, 20 | } 21 | }; 22 | 23 | module.exports = Delay; -------------------------------------------------------------------------------- /src/modules/Envelope.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Envelope() { 6 | Module.call(this); 7 | } 8 | inherits(Envelope, Module); 9 | 10 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 11 | Envelope.prototype.onTrigger = function () { 12 | var currentTime = audioContext.currentTime; 13 | 14 | var ATTACK = 1; 15 | var SUSTAIN = 1; 16 | var RELEASE = 2; 17 | 18 | this.$OUT.setAutomation(function (param, min, max) { 19 | var value = param.value; 20 | param.cancelScheduledValues(0); 21 | 22 | param.setValueAtTime(value, currentTime); 23 | param.linearRampToValueAtTime(max, currentTime + ATTACK); 24 | param.setValueAtTime(max, currentTime + ATTACK + SUSTAIN); 25 | param.linearRampToValueAtTime(min, currentTime + ATTACK + SUSTAIN + RELEASE); 26 | }); 27 | }; 28 | 29 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 30 | Envelope.prototype.descriptor = { 31 | type: 'Envelope', 32 | name: 'Envelope', 33 | size: 2, 34 | inputs: { TRG: { type: 'event', x:0, y:1, label: 'TRG', endPoint: 'onTrigger' } }, 35 | outputs: { OUT: { type: 'param', x:3.5, y:1, label: 'OUT' } }, 36 | controls: {} 37 | }; 38 | 39 | module.exports = Envelope; -------------------------------------------------------------------------------- /src/modules/EventDelay.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function EventDelay() { 5 | this.delay = 10; 6 | Module.call(this); 7 | } 8 | inherits(EventDelay, Module); 9 | 10 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 11 | EventDelay.prototype.onDataIn = function (event) { 12 | var t = this; 13 | 14 | // FIXME: cancel timeout on destroy 15 | window.setTimeout(function () { 16 | try { 17 | t.$OUT.emit(event); 18 | } catch (e) { 19 | // noop 20 | } 21 | }, this.delay * 1000); 22 | }; 23 | 24 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 25 | EventDelay.prototype.descriptor = { 26 | type: 'EventDelay', 27 | name: 'EventDelay', 28 | size: 2, 29 | inputs: { IN: { type: 'event', x:0, y:0.9, label: 'IN', endPoint: 'onDataIn' } }, 30 | outputs: { OUT: { type: 'event', x:2, y:0.9, label: 'OUT' } }, 31 | controls: { delay: { type: 'knob', x: 4, y: 0, min: 1, max: 200, int: true, endPoint: null, value: 'delay' } } 32 | }; 33 | 34 | module.exports = EventDelay; -------------------------------------------------------------------------------- /src/modules/EventPool.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function EventPool() { 5 | Module.call(this); 6 | this.map = new WeakMap(); 7 | this.data = []; 8 | this.index = 0; 9 | } 10 | inherits(EventPool, Module); 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | EventPool.prototype.removeData = function (connector) { 14 | var data = this.map.get(connector); 15 | if (!data) return; 16 | this.map.delete(connector); 17 | var index = this.data.indexOf(data); 18 | if (index === -1) return console.error('data not in the pool'); 19 | this.data.splice(index, 1); 20 | }; 21 | 22 | EventPool.prototype.onDataIn = function (data, connector) { 23 | this.removeData(connector); 24 | this.map.set(connector, data); 25 | this.data.push(data); 26 | }; 27 | 28 | EventPool.prototype.onDisconnect = function (connector) { 29 | this.removeData(connector); 30 | }; 31 | 32 | EventPool.prototype.onTrigger = function (event) { 33 | if (this.data.length === 0) return; 34 | // TODO: input event can control which data to emit (next, previous, same, random) 35 | this.index = (this.index + 1) % this.data.length; // next data 36 | this.$OUT.emit(this.data[this.index]); 37 | }; 38 | 39 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 40 | EventPool.prototype.descriptor = { 41 | type: 'EventPool', 42 | name: 'EventPool', 43 | size: 2, 44 | inputs: { 45 | IN: { type: 'event', x:0, y:1, label: 'DATA', endPoint: 'onDataIn', onDisconnect: 'onDisconnect' }, 46 | TRG: { type: 'event', x:3.5, y:0, label: 'TRG', endPoint: 'onTrigger' } 47 | }, 48 | outputs: { 49 | OUT: { type: 'event', x:3.5, y:1, label: 'OUT' } 50 | }, 51 | controls: {} 52 | }; 53 | 54 | module.exports = EventPool; -------------------------------------------------------------------------------- /src/modules/Fade.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | var map = require('../core/utils').map; 4 | 5 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 6 | function Fade() { 7 | this.target = 0.5; 8 | this.duration = 4; 9 | Module.call(this); 10 | } 11 | inherits(Fade, Module); 12 | 13 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 14 | Fade.prototype.onTrigger = function () { 15 | var currentTime = audioContext.currentTime; 16 | var target = this.target; 17 | var duration = this.duration; 18 | 19 | this.$OUT.setAutomation(function (param, min, max) { 20 | var value = param.value; 21 | param.cancelScheduledValues(0); 22 | param.setValueAtTime(value, currentTime); 23 | param.linearRampToValueAtTime(map(target, 0, 1, min, max), currentTime + duration); 24 | }); 25 | }; 26 | 27 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 28 | Fade.prototype.descriptor = { 29 | type: 'Fade', 30 | name: 'Fade', 31 | size: 3, 32 | inputs: { TRG: { type: 'event', x:0, y:1, label: 'TRG', endPoint: 'onTrigger' } }, 33 | outputs: { OUT: { type: 'param', x:0, y:2, label: 'ENV' } }, 34 | controls: { 35 | target: { type: 'knob', x: 2.1, y: 0.3, min: 0, max: 1, value: 'target', label: 'TO' }, 36 | duration: { type: 'knob', x: 4.0, y: 0.3, min: 0.1, max: 20, value: 'duration', label: 'TIME' }, 37 | } 38 | }; 39 | 40 | module.exports = Fade; -------------------------------------------------------------------------------- /src/modules/Filter.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | var FILTER_TYPE_ENUM = [ 5 | { id: 'lowpass', caption: 'LP' }, 6 | { id: 'highpass', caption: 'HP' }, 7 | { id: 'bandpass', caption: 'BP' }, 8 | // 'lowshelf', 9 | // 'highshelf', 10 | // 'peaking', 11 | // 'notch', 12 | // 'allpass' 13 | ]; 14 | 15 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 16 | function Filter() { 17 | this.node = audioContext.createBiquadFilter(); 18 | this._filterType = 0; 19 | this.node.type = FILTER_TYPE_ENUM[this._filterType].id; 20 | 21 | Module.call(this); 22 | this.$$type.setTitle(FILTER_TYPE_ENUM[this._filterType].caption); 23 | } 24 | inherits(Filter, Module); 25 | 26 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 27 | Filter.prototype.switchType = function () { 28 | this.filterType += 1; 29 | }; 30 | 31 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 32 | Object.defineProperty(Filter.prototype, 'filterType', { 33 | get: function() { 34 | return this._filterType; 35 | }, 36 | set: function(value) { 37 | this._filterType = value % FILTER_TYPE_ENUM.length; 38 | this.node.type = FILTER_TYPE_ENUM[this._filterType].id; 39 | this.$$type.setTitle(FILTER_TYPE_ENUM[this._filterType].caption); 40 | } 41 | }); 42 | 43 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 44 | Filter.prototype.descriptor = { 45 | type: 'Filter', 46 | name: 'Filter', 47 | size: 4, 48 | inputs: { IN: { type: 'audio', x:0.5, y:3, endPoint: 'node', label: 'IN' } }, 49 | outputs: { OUT: { type: 'audio', x:3.0, y:3, endPoint: 'node', label: 'OUT' } }, 50 | controls: { 51 | cut: { type: 'knob', x: 2.0, y: 0.3, min: 10.0, max: 8000.0, endPoint: 'node.frequency', value: 'value', label: 'CUT' }, 52 | res: { type: 'knob', x: 4.0, y: 0.3, min: 0.00, max: 40.0, endPoint: 'node.Q', value: 'value', label: 'REZ' }, 53 | type: { type: 'button', x: 0.2, y: 1.2, endPoint: 'switchType' } 54 | }, 55 | persistent: ['filterType'] 56 | }; 57 | 58 | module.exports = Filter; -------------------------------------------------------------------------------- /src/modules/FilterMod.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Filter = require('./Filter'); 3 | 4 | var FILTER_TYPE_ENUM = [ 5 | { id: 'lowpass', caption: 'LP' }, 6 | { id: 'highpass', caption: 'HP' }, 7 | { id: 'bandpass', caption: 'BP' }, 8 | // 'lowshelf', 9 | // 'highshelf', 10 | // 'peaking', 11 | // 'notch', 12 | // 'allpass' 13 | ]; 14 | 15 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 16 | function FilterMod() { 17 | Filter.call(this); 18 | } 19 | inherits(FilterMod, Filter); 20 | 21 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 22 | FilterMod.prototype.descriptor = { 23 | type: 'FilterMod', 24 | name: 'FilterMod', 25 | size: 3, 26 | inputs: { 27 | IN: { type: 'audio', x: 2.2, y: 1, endPoint: 'node', label: 'IN' }, 28 | CUT: { type: 'param', x: 0.0, y: 1, endPoint: 'node.frequency', label: 'CUT', min: 10.0, max: 8000 }, 29 | RES: { type: 'param', x: 0.0, y: 2, endPoint: 'node.Q', label: 'RES', min: 0.00, max: 40 } 30 | }, 31 | outputs: { OUT: { type: 'audio', x: 2.2, y: 2, endPoint: 'node', label: 'OUT' } }, 32 | controls: { 33 | // cut: { type: 'knob', x: 2.0, y: 0.3, min: 10.0, max: 8000.0, endPoint: 'node.frequency', value: 'value', label: 'CUT' }, 34 | // res: { type: 'knob', x: 4.0, y: 0.3, min: 0.00, max: 40.0, endPoint: 'node.Q', value: 'value', label: 'REZ' }, 35 | type: { type: 'button', x: 4.2, y: 1.1, endPoint: 'switchType' } 36 | }, 37 | persistent: ['filterType'] 38 | }; 39 | 40 | module.exports = FilterMod; -------------------------------------------------------------------------------- /src/modules/Gain.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Gain() { 6 | this.node = audioContext.createGain(); 7 | Module.call(this); 8 | } 9 | inherits(Gain, Module); 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | Gain.prototype.descriptor = { 13 | type: 'Gain', 14 | name: 'Gain x100', 15 | size: 3, 16 | inputs: { IN: { type: 'audio', x:3.5, y:0.2, endPoint: 'node', label: 'IN' } }, 17 | outputs: { OUT: { type: 'audio', x:3.5, y:2, endPoint: 'node', label: 'OUT' } }, 18 | controls: { gain: { type: 'knob', x: 1.5, y: 0.5, min: 1.0, max: 100.0, endPoint: 'node.gain', value: 'value', label: 'GAIN' } } 19 | }; 20 | 21 | module.exports = Gain; -------------------------------------------------------------------------------- /src/modules/LFO.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | WAVEFORM_TYPE_ENUM = [ 5 | { id: 'sine', caption: 'sin' }, 6 | { id: 'square', caption: 'sqr' }, 7 | { id: 'sawtooth', caption: 'saw' }, 8 | { id: 'triangle', caption: 'tri' }, 9 | // { id: 'custom', caption: 'usr' } 10 | ]; 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | function LFO() { 14 | this._waveType = 0; 15 | 16 | this.node = audioContext.createOscillator(); 17 | this.node.frequency.value = 2.0; 18 | this.node.type = WAVEFORM_TYPE_ENUM[this._waveType].id; 19 | this.node.start(); 20 | Module.call(this); 21 | 22 | this.$$type.setTitle(WAVEFORM_TYPE_ENUM[this._waveType].caption); 23 | } 24 | inherits(LFO, Module); 25 | 26 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 27 | LFO.prototype.switchType = function () { 28 | this.waveform += 1; 29 | }; 30 | 31 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 32 | Object.defineProperty(LFO.prototype, 'waveform', { 33 | get: function() { 34 | return this._waveType; 35 | }, 36 | set: function(value) { 37 | this._waveType = value % WAVEFORM_TYPE_ENUM.length; 38 | this.node.type = WAVEFORM_TYPE_ENUM[this._waveType].id; 39 | this.$$type.setTitle(WAVEFORM_TYPE_ENUM[this._waveType].caption); 40 | } 41 | }); 42 | 43 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 44 | LFO.prototype.descriptor = { 45 | type: 'LFO', 46 | name: 'LFO', 47 | size: 3, 48 | inputs: {}, 49 | outputs: { OUT: { type: 'audio', x:5, y:1, endPoint: 'node', label: null } }, 50 | controls: { 51 | frequency: { type: 'knob', x: 2.5, y: 0.3, min: 0.001, max: 10.0, endPoint: 'node.frequency', value: 'value', label: 'FREQ' }, 52 | type: { type: 'button', x: 0.2, y: 1.2, endPoint: 'switchType' } 53 | }, 54 | persistent: ['waveform'] 55 | }; 56 | 57 | module.exports = LFO; -------------------------------------------------------------------------------- /src/modules/MidiIn.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | var MIDI = require('../core/MIDI'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function MidiIn() { 6 | Module.call(this); 7 | MIDI.open(); 8 | var t = this; 9 | 10 | this.onMessage = function (event) { 11 | t.$OUT.emit(event); 12 | }; 13 | 14 | MIDI.on('message', this.onMessage); 15 | } 16 | inherits(MidiIn, Module); 17 | 18 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 19 | MidiIn.prototype.remove = function () { 20 | MIDI.removeListener('message', this.onMessage); 21 | Module.prototype.remove.call(this); 22 | }; 23 | 24 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 25 | MidiIn.prototype.descriptor = { 26 | type: 'MidiIn', 27 | name: 'MidiIn', 28 | size: 1, 29 | inputs: {}, 30 | outputs: { OUT: { type: 'event', x: 5, y: 0 } }, 31 | controls: {} 32 | }; 33 | 34 | module.exports = MidiIn; -------------------------------------------------------------------------------- /src/modules/ModDelay.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function ModDelay() { 6 | this.node = audioContext.createDelay(1); 7 | Module.call(this); 8 | } 9 | inherits(ModDelay, Module); 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | ModDelay.prototype.descriptor = { 13 | type: 'ModDelay', 14 | name: 'ModDelay', 15 | size: 2, 16 | inputs: { 17 | time: { type: 'param', x:0, y:1, endPoint: 'node.delayTime', label: 'TIME' }, 18 | IN: { type: 'audio', x:3.6, y:0, endPoint: 'node', label: 'IN' }, 19 | }, 20 | outputs: { OUT: { type: 'audio', x:3.6, y:1, endPoint: 'node', label: 'OUT' } }, 21 | controls: {} 22 | }; 23 | 24 | module.exports = ModDelay; -------------------------------------------------------------------------------- /src/modules/ModPanner.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function ModPanner() { 6 | this.node = audioContext.createStereoPanner(); 7 | Module.call(this); 8 | } 9 | inherits(ModPanner, Module); 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | ModPanner.prototype.descriptor = { 13 | type: 'ModPanner', 14 | name: 'ModPan', 15 | size: 2, 16 | inputs: { 17 | IN: { type: 'audio', x:3.5, y:0, endPoint: 'node', label: 'IN' }, 18 | pan: { type: 'param', x:0.0, y:1, endPoint: 'node.pan', label: 'PAN' }, 19 | }, 20 | outputs: { OUT: { type: 'audio', x:3.5, y:1, endPoint: 'node', label: 'OUT' } } 21 | }; 22 | 23 | module.exports = ModPanner; -------------------------------------------------------------------------------- /src/modules/NoteDetect.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | var MIDI_NOTE_C4 = 60; 4 | 5 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 6 | function NoteDetect() { 7 | this.note = MIDI_NOTE_C4; 8 | Module.call(this); 9 | } 10 | inherits(NoteDetect, Module); 11 | 12 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 13 | NoteDetect.prototype.onEvent = function (event) { 14 | if (event._type !== 'midi message') return; 15 | if (event.midiType !== 'note on') return; 16 | if (event.note !== this.note) return; 17 | 18 | this.$OUT.emit(event); 19 | }; 20 | 21 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 22 | NoteDetect.prototype.descriptor = { 23 | type: 'NoteDetect', 24 | name: 'note detect', 25 | size: 3, 26 | inputs: { IN: { type: 'event', x:4, y:0, endPoint: 'onEvent' } }, 27 | outputs: { OUT: { type: 'event', x:5, y:0 } }, 28 | controls: { 29 | note: { type: 'knob', x: 1.5, y: 0.5, min: 0, max: 127, endPoint: null, value: 'note', label: 'NOTE', int: true } 30 | } 31 | }; 32 | 33 | module.exports = NoteDetect; -------------------------------------------------------------------------------- /src/modules/OnLoadBang.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function OnLoadBang() { 5 | Module.call(this); 6 | this.data = null; 7 | var t = this; 8 | window.setTimeout(function () { 9 | t.$OUT.emit(t.data); 10 | }, 0) 11 | } 12 | inherits(OnLoadBang, Module); 13 | 14 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 15 | OnLoadBang.prototype.onDataIn = function (data) { 16 | this.data = data; 17 | }; 18 | 19 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 20 | OnLoadBang.prototype.descriptor = { 21 | type: 'OnLoadBang', 22 | name: 'OnLoad', 23 | size: 1, 24 | inputs: { IN: { type: 'event', x:4, y:0, endPoint: 'onDataIn', singleConnection: true } }, 25 | outputs: { OUT: { type: 'event', x:5, y:0 } }, 26 | controls: {} 27 | }; 28 | 29 | module.exports = OnLoadBang; -------------------------------------------------------------------------------- /src/modules/OneShotSampler.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function OneShotSampler() { 6 | this.node = audioContext.createGain(); 7 | this.bufferData = null; 8 | Module.call(this); 9 | } 10 | inherits(OneShotSampler, Module); 11 | 12 | OneShotSampler.prototype.setBuffer = function (event) { 13 | if (event._type !== 'buffer') return; 14 | this.bufferData = event.buffer; 15 | }; 16 | 17 | OneShotSampler.prototype.trigger = function (event) { 18 | if (!this.bufferData) return; 19 | 20 | var bufferSource = audioContext.createBufferSource(); 21 | bufferSource.connect(this.node); 22 | bufferSource.buffer = this.bufferData.buffer; 23 | 24 | // event can contain some data to alter the way the sample is played: 25 | if (event.playbackRate) bufferSource.playbackRate.value = event.playbackRate; 26 | var offset = event.offset || 0; 27 | // TODO: duration (not nullable, must be >= 0) 28 | 29 | bufferSource.start(0, offset); 30 | }; 31 | 32 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 33 | OneShotSampler.prototype.descriptor = { 34 | type: 'OneShotSampler', 35 | name: 'OneShot', 36 | size: 3, 37 | inputs: { 38 | buffer: { type: 'event', x:0, y:2, endPoint: 'setBuffer', label: 'BUF' }, 39 | trigger: { type: 'event', x:0, y:1, endPoint: 'trigger', label: 'TRIG' } 40 | }, 41 | outputs: { OUT: { type: 'audio', x:5, y:2, endPoint: 'node' } }, 42 | controls: { volume: { type: 'knob', x: 2.8, y: 0.5, min: 0, max: 1, endPoint: 'node.gain', value: 'value', label: 'VOL' } } 43 | }; 44 | 45 | module.exports = OneShotSampler; -------------------------------------------------------------------------------- /src/modules/Oscillator.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Oscillator() { 6 | this.node = audioContext.createOscillator(); 7 | this.node.frequency.value = 220.0; 8 | // this.node.type = 'square'; 9 | this.node.start(); 10 | Module.call(this); 11 | } 12 | inherits(Oscillator, Module); 13 | 14 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 15 | Oscillator.prototype.descriptor = { 16 | type: 'Oscillator', 17 | name: 'Oscillator', 18 | size: 3, 19 | inputs: { detune: { type: 'param', x:0, y:1, endPoint: 'node.detune', label: 'DTN' } }, 20 | outputs: { OUT: { type: 'audio', x:0, y:2, endPoint: 'node', label: 'OUT' } }, 21 | controls: { frequency: { type: 'knob', x: 3.7, y: 0.3, min: 110.0, max: 880.0, endPoint: 'node.frequency', value: 'value', label: 'FREQ' } } 22 | }; 23 | 24 | module.exports = Oscillator; -------------------------------------------------------------------------------- /src/modules/Panner.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Panner() { 6 | this.node = audioContext.createStereoPanner(); 7 | Module.call(this); 8 | } 9 | inherits(Panner, Module); 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | Panner.prototype.descriptor = { 13 | type: 'Panner', 14 | name: 'Pan', 15 | size: 2, 16 | inputs: { IN: { type: 'audio', x:3.5, y: 0, endPoint: 'node', label: 'IN' } }, 17 | outputs: { OUT: { type: 'audio', x:3.5, y: 1, endPoint: 'node', label: 'OUT' } }, 18 | controls: { pan: { type: 'knob', x: 1.5, y: 0, min: -1, max: 1, endPoint: 'node.pan', value: 'value' } } 19 | }; 20 | 21 | module.exports = Panner; -------------------------------------------------------------------------------- /src/modules/PlaybackRate.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function PlaybackRate() { 5 | this.playbackRate = 1; 6 | this.data = null; 7 | Module.call(this); 8 | } 9 | inherits(PlaybackRate, Module); 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | PlaybackRate.prototype.onDataIn = function (event) { 13 | // adding circular check object 14 | // if (!event._circular) event._circular = {}; 15 | // if (event._circular[this.id]) return; // circular loop detected 16 | // event._circular[this.id] = true; 17 | 18 | // adding or overwriting attribute 19 | event.playbackRate = this.playbackRate; 20 | 21 | // keep event copy for onConnect FIXME: only last event is kept 22 | this.data = event; 23 | 24 | // emit event 25 | this.$OUT.emit(event); 26 | }; 27 | 28 | PlaybackRate.prototype.onConnect = function (connector) { 29 | if (!this.data) return; 30 | this.$OUT.emitTo(connector, this.data); 31 | }; 32 | 33 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 34 | PlaybackRate.prototype.descriptor = { 35 | type: 'PlaybackRate', 36 | name: 'PlaybackRate', 37 | size: 3, 38 | inputs: { IN: { type: 'event', x:0.5, y:1.5, endPoint: 'onDataIn' } }, 39 | outputs: { OUT: { type: 'event', x:4.5, y:1.5, onConnect: 'onConnect' } }, 40 | controls: { rate: { type: 'knob', x: 2, y: 1, min: 0.01, max: 2, endPoint: null, value: 'playbackRate' } } 41 | }; 42 | 43 | module.exports = PlaybackRate; -------------------------------------------------------------------------------- /src/modules/RandomBang.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | var map = require('../core/utils').map; 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function RandomBang() { 6 | this.data = null; 7 | this.min = 5; 8 | this.max = 20; 9 | this.timeout = null; 10 | 11 | Module.call(this); 12 | 13 | this.scheduleNext(); 14 | } 15 | inherits(RandomBang, Module); 16 | 17 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 18 | RandomBang.prototype.onDataIn = function (data) { 19 | this.data = data; 20 | }; 21 | 22 | RandomBang.prototype.scheduleNext = function () { 23 | var t = this; 24 | var duration = map(Math.random(), 0, 1, this.min, this.max); 25 | this.timeout = window.setTimeout(function () { 26 | t.$OUT.emit(this.data); 27 | t.scheduleNext(); 28 | }, duration * 1000); 29 | }; 30 | 31 | RandomBang.prototype.remove = function () { 32 | // cancel timeout on unload 33 | if (this.timeout !== null) { 34 | window.clearTimeout(this.timeout); 35 | this.timeout = null; 36 | } 37 | Module.prototype.remove.call(this); 38 | }; 39 | 40 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 41 | RandomBang.prototype.descriptor = { 42 | type: 'RandomBang', 43 | name: 'RandomBang', 44 | size: 3, 45 | inputs: { IN: { type: 'event', x:5, y:1, endPoint: 'onDataIn' } }, 46 | outputs: { OUT: { type: 'event', x:5, y:2 } }, 47 | controls: { 48 | min: { type: 'knob', x: 0.5, y: 0.5, min: 1, max: 100, value: 'min', label: 'MIN' }, 49 | max: { type: 'knob', x: 2.5, y: 0.5, min: 1, max: 100, value: 'max', label: 'MAX' }, 50 | } 51 | }; 52 | 53 | module.exports = RandomBang; -------------------------------------------------------------------------------- /src/modules/Sampler.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Sampler() { 6 | this.node = audioContext.createBufferSource(); 7 | this.bufferData = null; 8 | Module.call(this); 9 | } 10 | inherits(Sampler, Module); 11 | 12 | Sampler.prototype.setBuffer = function (event) { 13 | if (event._type !== 'buffer') return; 14 | 15 | // a buffer can not be set again, we need to create new bufferSource. 16 | if (this.bufferData) { 17 | // save bufferSource state 18 | var rate = this.node.playbackRate.value; 19 | 20 | // remove current bufferSource 21 | this.node.disconnect(); 22 | 23 | // create a new bufferSource 24 | this.node = audioContext.createBufferSource(); 25 | this.bufferData = null; 26 | 27 | // set back saved state 28 | this.node.playbackRate.value = rate; 29 | 30 | // rebind everything 31 | this.rebind(); 32 | } 33 | 34 | this.bufferData = event.buffer; 35 | 36 | var buffer = this.bufferData.buffer; 37 | var loop = this.bufferData.loop || false; 38 | 39 | this.node.buffer = buffer; 40 | this.node.loop = loop; 41 | 42 | if (loop) { 43 | // set loop points 44 | var loopStart = this.bufferData.start || 0; 45 | var loopEnd = this.bufferData.end || buffer.duration; 46 | // When loop end point is negative, we set endPoint from the end of the buffer 47 | if (loopEnd < 0) loopEnd = buffer.duration + loopEnd; 48 | if (loopEnd < 0) loopEnd = 0; 49 | 50 | this.node.loopStart = loopStart; 51 | this.node.loopEnd = loopEnd; 52 | } 53 | 54 | this.node.start(); 55 | } 56 | 57 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 58 | Sampler.prototype.descriptor = { 59 | type: 'Sampler', 60 | name: 'Sampler', 61 | size: 3, 62 | inputs: { buffer: { type: 'event', x:0, y:1, endPoint: 'setBuffer', label: 'BUF', /*singleConnection: true*/ } }, 63 | outputs: { OUT: { type: 'audio', x:0, y:2, endPoint: 'node', label: 'OUT' } }, 64 | controls: { rate: { type: 'knob', x: 3.7, y: 0.3, min: 0.01, max: 2, endPoint: 'node.playbackRate', value: 'value', label: 'RATE' } } 65 | }; 66 | 67 | module.exports = Sampler; -------------------------------------------------------------------------------- /src/modules/SlowLFO.js: -------------------------------------------------------------------------------- 1 | var LFO = require('./LFO'); 2 | var audioContext = require('../core/audioContext'); 3 | 4 | 5 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 6 | function SlowLFO() { 7 | LFO.call(this); 8 | this.node.frequency.value = 0.005; 9 | this.$$frequency.initValue(); 10 | } 11 | inherits(SlowLFO, LFO); 12 | 13 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 14 | SlowLFO.prototype.descriptor = { 15 | type: 'SlowLFO', 16 | name: 'SlowLFO', 17 | size: 3, 18 | inputs: {}, 19 | outputs: { OUT: { type: 'audio', x:5, y:1, endPoint: 'node', label: null } }, 20 | controls: { 21 | frequency: { type: 'knob', x: 2.8, y: 0.3, min: 0.0001, max: 0.01, endPoint: 'node.frequency', value: 'value', label: 'FREQ' }, 22 | type: { type: 'button', x: 0.2, y: 1.2, endPoint: 'switchType' } 23 | }, 24 | persistent: ['waveform'] 25 | }; 26 | 27 | module.exports = SlowLFO; -------------------------------------------------------------------------------- /src/modules/TestModule.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 4 | function TestModule() { 5 | Module.call(this); 6 | } 7 | inherits(TestModule, Module); 8 | 9 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 10 | TestModule.prototype.doStuff = function (data) { 11 | if (data._type === 'color') { 12 | this.setTitle('#' + data.hex); 13 | this.setColor('#' + data.hex); 14 | } 15 | }; 16 | 17 | TestModule.prototype.onConnect = function (connector) { 18 | console.log('module has connected', this, connector); 19 | }; 20 | 21 | TestModule.prototype.pushButton = function () { 22 | var hex = ('000' + (~~(Math.random() * 4096)).toString(16)).substr(-3); 23 | this.$B.emit({ _type: 'color', hex: hex }); 24 | }; 25 | 26 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 27 | TestModule.prototype.descriptor = { 28 | type: 'TestModule', 29 | name: 'TestModule', 30 | size: 5, 31 | inputs: { 32 | A: { type: 'event', x:0.2, y:1, label: 'A', endPoint: 'doStuff' }, 33 | }, 34 | outputs: { 35 | B: { type: 'event', x:3.2, y:1, label: 'B', onConnect: 'onConnect' } 36 | }, 37 | controls: { 38 | a: { type: 'knob', x: 0.1, y: 2.3, label: 'KNB' }, 39 | b: { type: 'knob', x: 2.1, y: 2.3, label: 'KNB' }, 40 | c: { type: 'button', x: 4.1, y: 2.3, label: 'BTN', endPoint: 'pushButton' } 41 | } 42 | }; 43 | 44 | module.exports = TestModule; -------------------------------------------------------------------------------- /src/modules/Volume.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function Volume() { 6 | this.node = audioContext.createGain(); 7 | Module.call(this); 8 | } 9 | inherits(Volume, Module); 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | Volume.prototype.descriptor = { 13 | type: 'Volume', 14 | name: 'Volume', 15 | size: 3, 16 | inputs: { IN: { type: 'audio', x:3.5, y:0.2, endPoint: 'node', label: 'IN' } }, 17 | outputs: { OUT: { type: 'audio', x:3.5, y:2, endPoint: 'node', label: 'OUT' } }, 18 | controls: { volume: { type: 'knob', x: 1.5, y: 0.5, min: 0.0, max: 1.0, endPoint: 'node.gain', value: 'value', label: 'VOL' } } 19 | }; 20 | 21 | module.exports = Volume; -------------------------------------------------------------------------------- /src/modules/XFadeSampler.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../core/audioContext'); 2 | var Module = require('../core/Module'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function XFadeSampler() { 6 | // main mix 7 | this.node = audioContext.createGain(); 8 | 9 | // current sample 10 | this.bufferSource = null; 11 | this.fadeGain = null; 12 | 13 | // fade durations 14 | this.fadeIn = 5; 15 | this.fadeOut = 5; 16 | 17 | Module.call(this); 18 | } 19 | inherits(XFadeSampler, Module); 20 | 21 | XFadeSampler.prototype.setBuffer = function (event) { 22 | if (event._type !== 'buffer') return; 23 | var bufferData = event.buffer; 24 | 25 | var currentTime = audioContext.currentTime; 26 | 27 | if (this.bufferSource) { 28 | // fade out previous sound 29 | var previousBufferSource = this.bufferSource; 30 | var previousFadeGain = this.fadeGain; 31 | var param = previousFadeGain.gain; 32 | param.cancelScheduledValues(0); 33 | param.setValueAtTime(param.value, currentTime); 34 | param.linearRampToValueAtTime(0, currentTime + this.fadeOut); 35 | 36 | // schedule stop and disconnections 37 | window.setTimeout(function disconnectBufferSource() { 38 | previousBufferSource.stop(); 39 | previousBufferSource.disconnect(); 40 | previousFadeGain.disconnect(); 41 | }, this.fadeOut * 1000); 42 | } 43 | 44 | // create new sound 45 | var bufferSource = audioContext.createBufferSource(); 46 | var fadeGain = audioContext.createGain(); 47 | bufferSource.connect(fadeGain); 48 | fadeGain.connect(this.node); 49 | 50 | bufferSource.buffer = bufferData.buffer; 51 | 52 | // event can contain some data to alter the way the sample is played: 53 | if (event.playbackRate) bufferSource.playbackRate.value = event.playbackRate; 54 | var offset = event.offset || 0; 55 | // TODO: duration (not nullable, must be >= 0) 56 | 57 | // fade in new sound 58 | var param = fadeGain.gain; 59 | param.setValueAtTime(0, currentTime); 60 | param.linearRampToValueAtTime(1, currentTime + this.fadeIn); 61 | 62 | // set loop 63 | var loop = bufferData.loop || false; 64 | bufferSource.loop = loop; 65 | 66 | if (loop) { 67 | // set loop points 68 | var loopStart = bufferData.start || 0; 69 | var loopEnd = bufferData.end || bufferData.buffer.duration; 70 | // When loop end point is negative, we set endPoint from the end of the buffer 71 | if (loopEnd < 0) loopEnd = bufferData.buffer.duration + loopEnd; 72 | if (loopEnd < 0) loopEnd = 0; 73 | 74 | bufferSource.loopStart = loopStart; 75 | bufferSource.loopEnd = loopEnd; 76 | } 77 | 78 | // start sound 79 | bufferSource.start(0, offset); 80 | 81 | // keep references 82 | this.bufferSource = bufferSource; 83 | this.fadeGain = fadeGain; 84 | }; 85 | 86 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 87 | XFadeSampler.prototype.descriptor = { 88 | type: 'XFadeSampler', 89 | name: 'XFadeSampler', 90 | size: 4, 91 | inputs: { 92 | buffer: { type: 'event', x:0.5, y:3.1, endPoint: 'setBuffer', label: 'BUF' } 93 | }, 94 | outputs: { OUT: { type: 'audio', x:4.5, y:3.1, endPoint: 'node' } }, 95 | controls: { 96 | fadeIn: { type: 'knob', x: 0, y: 0.7, min: 1, max: 20, endPoint: null, value: 'fadeIn', label: 'IN' }, 97 | fadeOut: { type: 'knob', x: 2, y: 0.7, min: 1, max: 20, endPoint: null, value: 'fadeOut', label: 'OUT' }, 98 | volume: { type: 'knob', x: 4, y: 0.7, min: 0, max: 1, endPoint: 'node.gain', value: 'value', label: 'VOL' }, 99 | } 100 | }; 101 | 102 | module.exports = XFadeSampler; -------------------------------------------------------------------------------- /src/modules/index.js: -------------------------------------------------------------------------------- 1 | var modules = require('../core/modules'); 2 | var CATEGORY = require('../core/moduleCategories'); 3 | 4 | 5 | modules.add(require('../core/Buffer'), CATEGORY.DATA); 6 | modules.add(require('./BufferTrim'), CATEGORY.DATA); 7 | modules.add(require('./BufferSlice'), CATEGORY.DATA); 8 | modules.add(require('./TestModule'), CATEGORY.DATA); 9 | modules.add(require('./EventPool'), CATEGORY.DATA); 10 | modules.add(require('./EventDelay'), CATEGORY.DATA); 11 | modules.add(require('./PlaybackRate'), CATEGORY.DATA); 12 | 13 | // event 14 | modules.add(require('./Bang'), CATEGORY.CONTROL); 15 | modules.add(require('./AutoBang'), CATEGORY.CONTROL); 16 | modules.add(require('./RandomBang'), CATEGORY.CONTROL); 17 | modules.add(require('./DateBang'), CATEGORY.CONTROL); 18 | modules.add(require('./OnLoadBang'), CATEGORY.CONTROL); 19 | 20 | // MIDI 21 | modules.add(require('./MidiIn'), CATEGORY.CONTROL); 22 | modules.add(require('./NoteOnFilter'), CATEGORY.CONTROL); 23 | modules.add(require('./ControlChange'), CATEGORY.CONTROL); 24 | modules.add(require('./NoteDetect'), CATEGORY.CONTROL); 25 | 26 | // Oscillator, LFO 27 | modules.add(require('./Oscillator'), CATEGORY.OSC); 28 | modules.add(require('./LFO'), CATEGORY.OSC); 29 | modules.add(require('./SlowLFO'), CATEGORY.OSC); 30 | 31 | // envelope 32 | modules.add(require('./Envelope'), CATEGORY.ENVELOPE); 33 | modules.add(require('./Fade'), CATEGORY.ENVELOPE); 34 | 35 | // amp, pan 36 | modules.add(require('./Volume'), CATEGORY.GAIN); 37 | modules.add(require('./Amp'), CATEGORY.GAIN); 38 | modules.add(require('./AutoXFade'), CATEGORY.GAIN); 39 | modules.add(require('./Gain'), CATEGORY.GAIN); 40 | modules.add(require('./Panner'), CATEGORY.GAIN); 41 | modules.add(require('./ModPanner'), CATEGORY.GAIN); 42 | 43 | // sampler 44 | modules.add(require('./Sampler'), CATEGORY.SAMPLER); 45 | modules.add(require('./OneShotSampler'), CATEGORY.SAMPLER); 46 | modules.add(require('./XFadeSampler'), CATEGORY.SAMPLER); 47 | 48 | // filter 49 | modules.add(require('./Filter'), CATEGORY.FILTER); 50 | modules.add(require('./FilterMod'), CATEGORY.FILTER); 51 | 52 | // reverb, delay, fx 53 | modules.add(require('./Convolver'), CATEGORY.EFFECT); 54 | modules.add(require('./Delay'), CATEGORY.EFFECT); 55 | modules.add(require('./ModDelay'), CATEGORY.EFFECT); 56 | 57 | // out 58 | modules.add(require('./Context'), CATEGORY.IN_OUT); 59 | -------------------------------------------------------------------------------- /src/modules/noteOnFilter.js: -------------------------------------------------------------------------------- 1 | var Module = require('../core/Module'); 2 | 3 | var MIDI_NOTE_C4 = 60; 4 | 5 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 6 | function noteOnFilter() { 7 | Module.call(this); 8 | } 9 | inherits(noteOnFilter, Module); 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | noteOnFilter.prototype.onEvent = function (event) { 13 | if (event._type !== 'midi message') return; 14 | if (event.midiType !== 'note on') return; 15 | 16 | var pitch = event.note - MIDI_NOTE_C4; 17 | event.playbackRate = Math.pow(2, pitch / 12); 18 | 19 | this.$OUT.emit(event); 20 | }; 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | noteOnFilter.prototype.descriptor = { 24 | type: 'noteOnFilter', 25 | name: 'noteOn', 26 | size: 1, 27 | inputs: { IN: { type: 'event', x:4, y:0, endPoint: 'onEvent' } }, 28 | outputs: { OUT: { type: 'event', x:5, y:0 } }, 29 | controls: { 30 | } 31 | }; 32 | 33 | module.exports = noteOnFilter; -------------------------------------------------------------------------------- /src/synthesizers/disco/editor.js: -------------------------------------------------------------------------------- 1 | exports.create = function (editor, params) { 2 | editor.resize(10, 9); 3 | 4 | editor.addContainer(0, 1, 4, 8); 5 | editor.addContainer(3, 1, 4, 8); 6 | editor.addContainer(6, 1, 4, 8); 7 | 8 | editor.addLabel(1, 0, 2, 'OSC'); 9 | editor.addLabel(4, 0, 2, 'MOD'); 10 | editor.addLabel(7, 0, 2, 'AMP'); 11 | 12 | editor.addLabel(1, 4, 2, 'frq'); editor.addKnob(1, 2).bind(params, 'freq', 300, 2000).autoUpdate(); 13 | 14 | editor.addLabel(4, 4, 2, 'mod'); editor.addKnob(4, 2).bind(params, 'mod', -800, 800).autoUpdate(); 15 | editor.addLabel(4, 7, 2, 'cv'); editor.addKnob(4, 5).bind(params, 'modCurve', 0.1, 3).autoUpdate(); 16 | 17 | editor.addLabel(7, 4, 2, 'd'); editor.addKnob(7, 2).bind(params, 'envDuration', 0.1, 3).autoUpdate(); 18 | editor.addLabel(7, 7, 2, 'cv'); editor.addKnob(7, 5).bind(params, 'ampCurve', 0.1, 3).autoUpdate(); 19 | }; 20 | -------------------------------------------------------------------------------- /src/synthesizers/disco/index.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../../core/audioContext'); 2 | var copyObject = require('../../core/utils').copyObject; 3 | 4 | var SAMPLE_RATE = 44100; 5 | var PI2 = Math.PI * 2; 6 | 7 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 8 | function Osc() { 9 | this.freq = 440; 10 | this.t = 0; 11 | } 12 | 13 | Osc.prototype.reset = function () { 14 | this.t = 0; 15 | }; 16 | 17 | Osc.prototype.tic = function () { 18 | var inc = this.freq / SAMPLE_RATE; 19 | this.t += inc; 20 | if (this.t > 1) this.t -= 1; 21 | return Math.sin(this.t * PI2); 22 | }; 23 | 24 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 25 | function Envelope() { 26 | this.duration = 1; 27 | this.curve = 0; 28 | this.t = 0; 29 | } 30 | 31 | Envelope.prototype.reset = function () { 32 | this.t = 0; 33 | this.v = 1; 34 | }; 35 | 36 | Envelope.prototype.tic = function() { 37 | // TODO: optimize 38 | var d = this.duration * SAMPLE_RATE; 39 | if (this.t > d) return; 40 | this.t += 1; 41 | this.v = Math.pow(1 - this.t / d, this.curve); 42 | }; 43 | 44 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 45 | function Synth() { 46 | // components 47 | this.osc = new Osc(); 48 | this.modEnv = new Envelope(); 49 | this.ampEnv = new Envelope(); 50 | 51 | // attributes 52 | this.freq = 440; 53 | this.fmod = 30; 54 | } 55 | 56 | Synth.prototype.reset = function () { 57 | this.osc.reset(); 58 | this.modEnv.reset(); 59 | this.ampEnv.reset(); 60 | }; 61 | 62 | Synth.prototype.tic = function () { 63 | // TODO: don't need to update each frame 64 | this.modEnv.tic(); 65 | this.ampEnv.tic(); 66 | 67 | this.osc.freq = this.freq + this.modEnv.v * this.fmod; 68 | 69 | return this.osc.tic() * this.ampEnv.v; 70 | }; 71 | 72 | var synth = new Synth(); 73 | 74 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 75 | var DEFAULT_PARAMS = { 76 | freq: 440, 77 | mod: 300, 78 | envDuration: 0.7, 79 | ampCurve: 0.2, 80 | modCurve: 0.5 81 | }; 82 | 83 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 84 | exports.generate = function (bufferData, cb) { 85 | // get sound informations 86 | var params = bufferData.params || copyObject(DEFAULT_PARAMS); 87 | var length = params.ampDuration || 1; 88 | 89 | // set synth params 90 | synth.reset(); 91 | synth.freq = params.freq || DEFAULT_PARAMS.freq; 92 | synth.fmod = params.mod || DEFAULT_PARAMS.mod; 93 | synth.ampEnv.duration = params.envDuration || DEFAULT_PARAMS.envDuration; 94 | synth.modEnv.duration = params.envDuration || DEFAULT_PARAMS.envDuration; 95 | synth.ampEnv.curve = params.ampCurve || DEFAULT_PARAMS.ampCurve; 96 | synth.modEnv.curve = params.modCurve || DEFAULT_PARAMS.modCurve; 97 | 98 | // create buffer 99 | var buffer = audioContext.createBuffer(1, length * SAMPLE_RATE, SAMPLE_RATE); 100 | var channelData = buffer.getChannelData(0); 101 | 102 | // generate buffer data 103 | for (var i = 0; i < channelData.length; i++) { 104 | channelData[i] = synth.tic(); 105 | } 106 | 107 | // append data 108 | bufferData.buffer = buffer; 109 | bufferData.start = 0; 110 | bufferData.end = length; 111 | 112 | // defer the callback 113 | return window.setTimeout(cb, 0); 114 | }; 115 | -------------------------------------------------------------------------------- /src/synthesizers/hats/editor.js: -------------------------------------------------------------------------------- 1 | exports.create = function (editor, params) { 2 | editor.resize(18, 9); 3 | 4 | editor.addContainer( 0, 0, 18, 5); 5 | editor.addContainer( 0, 4, 6, 5); 6 | editor.addContainer( 5, 4, 6, 5); 7 | editor.addContainer(10, 4, 8, 5); 8 | 9 | editor.addLabel(1, 1, 1, 'PATTERN'); 10 | 11 | editor.addTextInput(1, 2, 16).bind(params, 'pattern').autoUpdate(); 12 | editor.addKnob( 1, 5).bind(params, 'tempo', 50, 300).setAsInt().autoUpdate(); 13 | editor.addKnob( 3, 5).bind(params, 'polyphony', 1, 8).setAsInt().autoUpdate(); 14 | editor.addKnob( 6, 5).bind(params, 'close', 0.1, 0.8).autoUpdate(); 15 | editor.addKnob( 8, 5).bind(params, 'accent', 0.1, 0.8).autoUpdate(); 16 | editor.addKnob(15, 5).bind(params, 'offset', 0, 5000).autoUpdate(); 17 | 18 | editor.addLabel(1, 7, 2, 'Tmp'); 19 | editor.addLabel(3, 7, 2, 'Poly'); 20 | editor.addLabel(6, 7, 2, 'Cls'); 21 | editor.addLabel(8, 7, 2, 'Acc'); 22 | editor.addLabel(14, 7, 3, 'offset'); 23 | }; -------------------------------------------------------------------------------- /src/synthesizers/hats/index.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../../core/audioContext'); 2 | var loadAudioBuffer = require('../../loaders/loadAudioBuffer'); 3 | 4 | var EPSILON = 0.0001; 5 | 6 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 7 | function Voice(sample) { 8 | this.sample = sample; 9 | this.head = 0; // position of reading head in the sample 10 | this.decay = 1; // envelope decay 11 | this.env = 1; // current envelope value 12 | this.volume = 1; // final volume (accent) 13 | this.stopped = true; 14 | } 15 | 16 | Voice.prototype.start = function (decay, volume) { 17 | this.decay = decay; 18 | this.volume = volume; 19 | this.head = 0; 20 | this.env = 1; 21 | this.stopped = false; 22 | }; 23 | 24 | Voice.prototype.play = function () { 25 | if (this.stopped) return 0; 26 | if (this.head >= this.sample.length) { 27 | this.stopped = true; 28 | return 0; 29 | } 30 | 31 | var sample = this.sample[this.head++]; 32 | var volume = this.env * this.volume; 33 | sample *= volume; 34 | this.env *= this.decay; 35 | 36 | if (volume < EPSILON) this.stopped = true; 37 | 38 | return sample; 39 | }; 40 | 41 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 42 | exports.generate = function (bufferData, cb) { 43 | 44 | // get sound informations 45 | var params = bufferData.params; 46 | var pattern = params.pattern || 'C...'; 47 | var offset = params.offset || 0; 48 | var polyphony = params.polyphony || 1; 49 | var tempo = params.tempo || 120; 50 | var close = params.close || 0.5; 51 | var accent = params.accent || 0.5; 52 | 53 | // load sample buffer 54 | loadAudioBuffer(params.uri, function onBufferLoaded(error, sample) { 55 | if (error) return cb(error); 56 | 57 | // create voices 58 | var sampleLength = sample.length - offset; 59 | var sampleData = new Float32Array(sampleLength); 60 | sample.copyFromChannel(sampleData, 0, offset); 61 | var voices = []; 62 | for (var i = 0; i < polyphony; i++) { 63 | voices.push(new Voice(sampleData)); 64 | } 65 | 66 | // pattern values 67 | var sampleRate = sample.sampleRate; 68 | var stepSize = ~~(sampleRate * 15 / tempo); // 1 step = 1/4 beat 69 | var length = stepSize * pattern.length; 70 | 71 | // close ratio 72 | close = Math.pow(close, 10 / sampleLength); 73 | 74 | // note values 75 | var NOTES = { 76 | 'O': { volume: 1, decay: 1 }, 77 | 'o': { volume: accent, decay: 1 }, 78 | 'C': { volume: 1, decay: close }, 79 | 'c': { volume: accent, decay: close } 80 | }; 81 | 82 | // create pattern buffer 83 | var buffer = audioContext.createBuffer(1, length, sampleRate); 84 | 85 | // generate buffer data 86 | var channelData = buffer.getChannelData(0); 87 | var step = 0; 88 | var stepPosition = 0; 89 | var currentVoice = 0; 90 | for (var t = 0; t < length; t++) { 91 | if (stepPosition-- === 0) { 92 | stepPosition = stepSize; 93 | 94 | // trigger next note 95 | var note = NOTES[pattern[step]]; 96 | // TODO: '|' 97 | if (note) { 98 | currentVoice = (currentVoice + 1) % voices.length; 99 | voices[currentVoice].start(note.decay, note.volume); 100 | } 101 | step++; 102 | } 103 | var value = 0; 104 | for (var i = 0; i < voices.length; i++) { 105 | value += voices[i].play(); 106 | } 107 | channelData[t] = value; 108 | } 109 | 110 | // TODO: continue to fill buffer from buffer start if voices are not stopped 111 | 112 | // append data 113 | bufferData.buffer = buffer; 114 | bufferData.start = 0; 115 | bufferData.end = buffer.duration; 116 | 117 | return cb(); 118 | }); 119 | } -------------------------------------------------------------------------------- /src/synthesizers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Synthesizers can be added at runtime (before a patch requiring it is loaded) 3 | * 4 | * a synth is a module that expose a method `generate` taking two parameters: 5 | * - a {BufferData} instance, with informations about synth in bufferData.params 6 | * - a callback {function} to call once the synth has filled the buffer with audio 7 | * 8 | * The synth is responsible for creating the audio buffer, and filling it and set 9 | * the `start` and `end` properties of the bufferData. 10 | * 11 | * Callback should be deffered if the generate function is synchronous, in order 12 | * to be consistent with Buffer API that needs to load audio. 13 | */ 14 | 15 | var synthEditor = require('../ui/synthEditor'); 16 | 17 | var SYNTHESIZERS = { 18 | 'noize': require('./noize'), 19 | 'hats': require('./hats'), 20 | 'disco': require('./disco'), 21 | }; 22 | 23 | exports.getSynth = function (id) { 24 | return SYNTHESIZERS[id]; 25 | }; 26 | 27 | exports.addSynth = function (id, synth, editor) { 28 | SYNTHESIZERS[id] = synth; 29 | if (editor) synthEditor.register(id, editor); 30 | }; 31 | -------------------------------------------------------------------------------- /src/synthesizers/noize/index.js: -------------------------------------------------------------------------------- 1 | var audioContext = require('../../core/audioContext'); 2 | 3 | var SAMPLE_RATE = 44100; 4 | var m_brown = 0.0; 5 | 6 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 7 | function white() { 8 | return Math.random() - 0.5; 9 | } 10 | 11 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 12 | function brown() { 13 | while (true) { 14 | var r = white(); 15 | m_brown += r; 16 | if (m_brown < -8.0 || m_brown > 8.0) m_brown -= r; 17 | else break; 18 | } 19 | return m_brown / 16; 20 | } 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | function colored(color) { 24 | return brown() * color + white() * (1 - color); 25 | } 26 | 27 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 28 | exports.generate = function (bufferData, cb) { 29 | // get sound informations 30 | var params = bufferData.params; 31 | var length = params.length || 1; 32 | var color = params.color || 0; 33 | var noisef = colored; 34 | if (color < 0) noisef = white; 35 | if (color > 1) noisef = brown; 36 | 37 | // create buffer 38 | var buffer = audioContext.createBuffer(1, length * SAMPLE_RATE, SAMPLE_RATE); 39 | 40 | // generate buffer data 41 | var channelData = buffer.getChannelData(0); 42 | for (var i = 0; i < channelData.length; i++) { 43 | channelData[i] = noisef(color); 44 | } 45 | 46 | // append data 47 | bufferData.buffer = buffer; 48 | bufferData.start = 0; 49 | bufferData.end = length; 50 | 51 | // defer the callback 52 | return window.setTimeout(cb, 0); 53 | } -------------------------------------------------------------------------------- /src/ui/Panel.js: -------------------------------------------------------------------------------- 1 | var domUtils = require('./domUtils'); 2 | var createDiv = domUtils.createDiv; 3 | var makeButton = domUtils.makeButton; 4 | var makeDragable = domUtils.makeDragable; 5 | 6 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 7 | /** Panel 8 | * 9 | * @author Cedric Stoquer 10 | */ 11 | function Panel() { 12 | this._dom = createDiv('panel'); 13 | 14 | var t = this; 15 | 16 | // handle header for drag & move 17 | var handle = createDiv('handle', this._dom); 18 | makeDragable(handle, this._dom); 19 | 20 | // zIndex 21 | handle.addEventListener('mousedown', function (e) { 22 | t.setOnTop(); 23 | }); 24 | 25 | // title 26 | this.title = createDiv('panelTitle', handle); 27 | 28 | // close button 29 | var closeButton = createDiv('closeButton', handle); 30 | makeButton(closeButton, function onPress() { 31 | t.close(); 32 | }); 33 | } 34 | 35 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 36 | var topPanel = null; 37 | Panel.prototype.setOnTop = function () { 38 | if (topPanel) topPanel.style.zIndex = null; 39 | this._dom.style.zIndex = 11; 40 | topPanel = this._dom; 41 | }; 42 | 43 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 44 | Panel.prototype.setTitle = function (title) { 45 | this.title.innerText = title; 46 | }; 47 | 48 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 49 | Panel.prototype.open = function () { 50 | this._dom.style.display = ''; 51 | this.setOnTop(); 52 | }; 53 | 54 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 55 | Panel.prototype.close = function () { 56 | // TODO update checkbox in menu header 57 | this._dom.style.display = 'none'; 58 | }; 59 | 60 | module.exports = Panel; 61 | -------------------------------------------------------------------------------- /src/ui/beforeClose.js: -------------------------------------------------------------------------------- 1 | var sendRequest = require('../loaders/sendRequest'); 2 | 3 | var FLAGS = { 4 | audio: false 5 | }; 6 | 7 | exports.setFlag = function (id) { 8 | FLAGS[id] = true; 9 | }; 10 | 11 | window.onbeforeunload = function beforeClosing() { 12 | if (FLAGS.audio) sendRequest({ command: 'audio.generateLibrary' }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/ui/buttonGUI.js: -------------------------------------------------------------------------------- 1 | var Button = require('../core/Button'); 2 | var constants = require('./constants'); 3 | var domUtils = require('./domUtils'); 4 | var createDiv = domUtils.createDiv; 5 | 6 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 7 | Button.prototype.initGUI = function (module, id, descriptor) { 8 | // create dom 9 | var dom = this._dom = createDiv('moduleButton', module._dom); 10 | dom.style.left = (descriptor.x * constants.CONNECTOR_GRID_SIZE) + 'px'; 11 | dom.style.top = (descriptor.y * constants.CONNECTOR_GRID_SIZE) + 'px'; 12 | if (descriptor.label) createDiv('label knobLabel', dom).innerText = descriptor.label; 13 | this._title = createDiv('moduleButtonTitle', dom); 14 | 15 | // set mouse event 16 | var t = this; 17 | dom.addEventListener('mousedown', function click(e) { 18 | e.stopPropagation(); 19 | e.preventDefault(); 20 | t.endPoint.call(t.caller); 21 | }); 22 | }; 23 | 24 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 25 | Button.prototype.setTitle = function (text) { 26 | this._title.innerText = text; 27 | }; 28 | -------------------------------------------------------------------------------- /src/ui/cableGUI.js: -------------------------------------------------------------------------------- 1 | var Cable = require('../core/Cable'); 2 | var constants = require('./constants'); 3 | var ctx = require('./overlay').ctx; 4 | 5 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 6 | Cable.prototype.draw = function () { 7 | ctx.strokeStyle = this.color; 8 | ctx.beginPath(); 9 | ctx.moveTo(this.x, this.y); 10 | ctx.bezierCurveTo(this.a, this.b, this.c, this.d, this.w, this.h); 11 | ctx.stroke(); 12 | }; 13 | 14 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 15 | Cable.prototype.update = function () { 16 | this.x = this.endPointA.module.x * constants.MODULE_WIDTH + this.endPointA.x * constants.CONNECTOR_GRID_SIZE + 8; 17 | this.y = this.endPointA.module.y * constants.MODULE_HEIGHT + this.endPointA.y * constants.CONNECTOR_GRID_SIZE + 8; 18 | this.w = this.endPointB.module.x * constants.MODULE_WIDTH + this.endPointB.x * constants.CONNECTOR_GRID_SIZE + 8; 19 | this.h = this.endPointB.module.y * constants.MODULE_HEIGHT + this.endPointB.y * constants.CONNECTOR_GRID_SIZE + 8; 20 | 21 | var w = (this.w - this.x) / 2; 22 | var h = (this.h - this.y) / 2; 23 | 24 | this.a = ~~(this.x + w * Math.random() + 10 * Math.random() - 5); 25 | this.b = ~~(this.y + h * Math.random() + 10 * Math.random() - 5); 26 | this.c = ~~(this.x + w * (Math.random() + 1) + 10 * Math.random() - 5); 27 | this.d = ~~(this.y + h * (Math.random() + 1) + 10 * Math.random() - 5); 28 | }; 29 | -------------------------------------------------------------------------------- /src/ui/connectorGUI.js: -------------------------------------------------------------------------------- 1 | var Connector = require('../core/Connector'); 2 | var constants = require('./constants'); 3 | var createDiv = require('./domUtils').createDiv; 4 | 5 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 6 | Connector.prototype.initGUI = function (module, id, descriptor) { 7 | var dom = this._dom = createDiv('connector ' + this.cssClassName, module._dom); 8 | if (descriptor.label) createDiv('label connectorLabel', dom).innerText = descriptor.label; 9 | 10 | if (this.x === undefined) { 11 | // TODO: remove this 12 | dom.style.position = 'relative' 13 | } else { 14 | dom.style.left = (this.x * constants.CONNECTOR_GRID_SIZE + 1) + 'px'; 15 | dom.style.top = (this.y * constants.CONNECTOR_GRID_SIZE + 1) + 'px'; 16 | } 17 | 18 | dom.connector = this; 19 | 20 | var t = this; 21 | dom.addEventListener('mousedown', function mouseStart(e) { 22 | e.stopPropagation(); 23 | e.preventDefault(); 24 | window.moduleManager.startConnection(t, e); 25 | }); 26 | }; 27 | 28 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 29 | Connector.prototype.setState = function () { 30 | this._dom.className = 'connector ' + this.cssClassName + (this._nConnection > 0 ? '-fill' : ''); 31 | }; -------------------------------------------------------------------------------- /src/ui/constants.js: -------------------------------------------------------------------------------- 1 | exports.MODULE_WIDTH = 92; 2 | exports.MODULE_HEIGHT = 16; 3 | exports.CONNECTOR_GRID_SIZE = 15; 4 | 5 | // creating CSS class for module size accordingly to constants 6 | (function createModuleSizeStyle() { 7 | var cssStyle = document.createElement('style'); 8 | cssStyle.type = 'text/css'; 9 | var w = exports.MODULE_WIDTH - 3; // border is 1px, hence the -2 10 | for (var i = 1; i < 10; i++) { 11 | var h = exports.MODULE_HEIGHT * i - 3; 12 | var rules = document.createTextNode('.x' + i + ' { width: ' + w + 'px; height: ' + h + 'px; }'); 13 | cssStyle.appendChild(rules); 14 | document.getElementsByTagName('head')[0].appendChild(cssStyle); 15 | } 16 | })(); 17 | -------------------------------------------------------------------------------- /src/ui/domUtils.js: -------------------------------------------------------------------------------- 1 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 2 | /** @module domUtils 3 | * @desc dom utilities 4 | * @author Cedric Stoquer 5 | */ 6 | var DOCUMENT_BODY = document.getElementsByTagName('body')[0]; 7 | 8 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 9 | exports.createDom = function (type, className, parent) { 10 | parent = parent || DOCUMENT_BODY; 11 | var dom = document.createElement(type); 12 | parent.appendChild(dom); 13 | if (className) dom.className = className; 14 | return dom; 15 | }; 16 | 17 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 18 | exports.createDiv = function (className, parent) { 19 | return exports.createDom('div', className, parent); 20 | }; 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | exports.removeDom = function (dom, parent) { 24 | parent = parent || DOCUMENT_BODY; 25 | parent.removeChild(dom); 26 | }; 27 | 28 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 29 | exports.makeButton = function (dom, onClic) { 30 | dom.addEventListener('mousedown', function (e) { 31 | e.stopPropagation(); 32 | e.preventDefault(); 33 | onClic(e, dom); 34 | }); 35 | return dom; 36 | }; 37 | 38 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 39 | function startDrag(dom, e) { 40 | var d = document; 41 | 42 | rect = dom.getBoundingClientRect(); 43 | 44 | var startX = e.clientX - rect.left; 45 | var startY = e.clientY - rect.top; 46 | 47 | function dragMove(e) { 48 | e.preventDefault(); 49 | dom.style.left = (e.clientX - startX) + 'px'; 50 | dom.style.top = (e.clientY - startY) + 'px'; 51 | } 52 | 53 | function dragEnd(e) { 54 | e.preventDefault(); 55 | d.removeEventListener('mouseup', dragEnd); 56 | d.removeEventListener('mousemove', dragMove); 57 | } 58 | 59 | d.addEventListener('mousemove', dragMove, false); 60 | d.addEventListener('mouseup', dragEnd, false); 61 | } 62 | 63 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 64 | exports.makeDragable = function (handle, target) { 65 | target = target || handle; 66 | handle.addEventListener('mousedown', function (e) { 67 | e.stopPropagation(); 68 | e.preventDefault(); 69 | startDrag(target, e); 70 | }); 71 | return handle; 72 | }; 73 | -------------------------------------------------------------------------------- /src/ui/dropFile.js: -------------------------------------------------------------------------------- 1 | var audioEditor = require('./audioEditor'); 2 | var BufferData = require('../data/BufferData'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | function readJson(file) { 6 | var reader = new FileReader(); 7 | reader.onload = function (e) { 8 | var contents = e.target.result; 9 | var data; 10 | try { 11 | data = JSON.parse(contents); 12 | } catch (error) { 13 | return console.error(error); 14 | } 15 | if (data._type && data._type === 'modularPatch') { 16 | window.moduleManager.setPatch(data); 17 | } 18 | }; 19 | reader.readAsText(file); 20 | } 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | function readMp3(file) { 24 | var id = file.name.split('.'); 25 | id.pop(); 26 | id = id.join('.'); 27 | 28 | // create a bufferData for this file if it doesn't exist yet 29 | var bufferData = window.assets.buffers[id]; 30 | if (!bufferData) { 31 | bufferData = new BufferData(id, { uri: 'audio/' + file.name }); 32 | } 33 | audioEditor.setBuffer(bufferData); 34 | audioEditor.open(); 35 | } 36 | 37 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 38 | function openFiles(e) { 39 | var files = e.dataTransfer.files; 40 | var file = files[0]; // TODO: all files 41 | 42 | var ext = file.name.split('.').pop(); 43 | 44 | switch (ext) { 45 | case 'json': readJson(file); break; 46 | case 'mp3': readMp3(file); break; 47 | } 48 | } 49 | 50 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 51 | // drag & drop patch files 52 | document.body.addEventListener('dragover', function handleDragOver(e) { 53 | e.stopPropagation(); 54 | e.preventDefault(); 55 | e.dataTransfer.dropEffect = 'copy'; 56 | }, false); 57 | 58 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 59 | document.body.addEventListener('drop', function (e) { 60 | e.stopPropagation(); 61 | e.preventDefault(); 62 | openFiles(e); 63 | }, false); 64 | 65 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 66 | document.addEventListener('drop', function (e) { 67 | e.preventDefault(); 68 | e.stopPropagation(); 69 | openFiles(e); 70 | }); 71 | 72 | document.addEventListener('dragover', function (e) { 73 | e.preventDefault(); 74 | e.stopPropagation(); 75 | }); 76 | -------------------------------------------------------------------------------- /src/ui/knobGUI.js: -------------------------------------------------------------------------------- 1 | var Knob = require('../core/Knob'); 2 | var constants = require('./constants'); 3 | var createDiv = require('./domUtils').createDiv; 4 | 5 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 6 | Knob.prototype.initGUI = function (module, id, descriptor) { 7 | // create dom elements 8 | var dom = this._dom = createDiv('knob', module._dom); 9 | dom.style.left = (this.x * constants.CONNECTOR_GRID_SIZE + 2) + 'px'; 10 | dom.style.top = (this.y * constants.CONNECTOR_GRID_SIZE + 2) + 'px'; 11 | this._mark = createDiv('knob knobMark', dom); 12 | if (descriptor.label) createDiv('label knobLabel', dom).innerText = descriptor.label; 13 | 14 | this.valueDiplay = createDiv('knobValueDisplay', dom); 15 | this.valueDiplay.style.display = 'none'; 16 | if (!descriptor.label) this.valueDiplay.className += ' knobValueNoLabel'; 17 | 18 | var t = this; 19 | dom.addEventListener('mousedown', function mouseStart(e) { 20 | e.stopPropagation(); 21 | e.preventDefault(); 22 | 23 | // var startX = e.clientX; 24 | var startY = e.clientY; 25 | var startV = t.value; 26 | t.valueDiplay.style.display = ''; 27 | 28 | 29 | function mouseMove(e) { 30 | e.preventDefault(); 31 | var delta = Math.max(-68, Math.min(68, startV + startY - e.clientY)); 32 | t._mark.style.transform = 'rotate(' + (delta * 2) + 'deg)'; 33 | t.value = delta; 34 | // TODO: add an option to not updating value in real time 35 | t.updateValue(); 36 | } 37 | 38 | function mouseUp(e) { 39 | e.preventDefault(); 40 | document.removeEventListener('mousemove', mouseMove); 41 | document.removeEventListener('mouseup', mouseUp); 42 | t.valueDiplay.style.display = 'none'; 43 | t.updateValue(); 44 | } 45 | 46 | document.addEventListener('mousemove', mouseMove, false); 47 | document.addEventListener('mouseup', mouseUp, false); 48 | }); 49 | }; 50 | 51 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 52 | Knob.prototype.updateGUI = function () { 53 | this._mark.style.transform = 'rotate(' + (this.value * 2) + 'deg)'; 54 | }; 55 | 56 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 57 | Knob.prototype.displayValue = function (value) { 58 | // TODO: custom display 59 | 60 | var size = 5; 61 | var multiplier = ''; 62 | 63 | if (value === 0) { 64 | // keep 0; 65 | } else if (Math.abs(value) < 0.1) { 66 | value *= 1000; 67 | multiplier = 'm'; 68 | size = 4; 69 | } else if (Math.abs(value) >= 1000) { 70 | value /= 1000; 71 | multiplier = 'K'; 72 | size = 4; 73 | } 74 | 75 | this.valueDiplay.innerText = value.toString().substring(0, size) + multiplier;; 76 | }; -------------------------------------------------------------------------------- /src/ui/moduleLibrary.js: -------------------------------------------------------------------------------- 1 | var Panel = require('./Panel'); 2 | var categories = require('../core/moduleCategories'); 3 | var modules = require('../core/modules'); 4 | var domUtils = require('./domUtils'); 5 | var makeButton = domUtils.makeButton; 6 | var createDiv = domUtils.createDiv; 7 | 8 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 9 | /** ModuleLibrary 10 | * 11 | * @author Cedric Stoquer 12 | */ 13 | function ModuleLibrary() { 14 | Panel.call(this); 15 | this._dom.style.left = '150px'; // TODO 16 | 17 | this.current = null; // currently opened id 18 | this.tabs = {}; // map of tabs by id 19 | this.lists = {}; // map of list by tab id 20 | this.tabHolder = createDiv('libraryTabHolder', this._dom); 21 | this.listHolder = createDiv('libraryListHolder', this._dom); 22 | // this.list = createDiv('libraryList', this._dom); 23 | 24 | // create tabs from categories 25 | for (var category in categories) { 26 | this.addTab(categories[category]); 27 | } 28 | 29 | this.addEntries(modules.getList()); 30 | } 31 | inherits(ModuleLibrary, Panel); 32 | 33 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 34 | ModuleLibrary.prototype.addTab = function (id) { 35 | if (this.tabs[id]) return this.tabs[id]; 36 | var tab = createDiv('libraryTab', this.tabHolder); 37 | var list = createDiv('libraryList', this.listHolder); 38 | list.style.height = '150px'; 39 | list.style.display = 'none'; 40 | 41 | tab.innerText = id; 42 | 43 | this.tabs[id] = tab; 44 | this.lists[id] = list; 45 | 46 | if (!this.current) this.selectTab(id); 47 | 48 | var self = this; 49 | makeButton(tab, function onClick() { 50 | self.selectTab(id); 51 | }); 52 | 53 | return tab; 54 | }; 55 | 56 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 57 | ModuleLibrary.prototype.selectTab = function (id) { 58 | if (this.current === id) return; 59 | 60 | if (this.current) { 61 | this.lists[this.current].style.display = 'none'; 62 | this.tabs[this.current].style.backgroundColor = ''; 63 | } 64 | 65 | this.current = id; 66 | this.lists[id].style.display = ''; 67 | this.tabs[this.current].style.backgroundColor = '#FF0'; 68 | }; 69 | 70 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 71 | ModuleLibrary.prototype.addEntries = function (library) { 72 | for (var id in library) { 73 | this.addEntry(library[id]); 74 | } 75 | }; 76 | 77 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 78 | ModuleLibrary.prototype.addEntry = function (ModuleConstructor) { 79 | var descriptor = ModuleConstructor.prototype.descriptor; 80 | var category = descriptor._category; 81 | 82 | if (!descriptor.name) return; 83 | 84 | // add entry in UI window 85 | var list = this.lists[category]; 86 | if (!list) { 87 | // TODO 88 | return console.error('category is not registered'); 89 | } 90 | 91 | var button = createDiv('libraryEntry', list); 92 | button.textContent = descriptor.name; 93 | button.addEventListener('mousedown', function onClick(e) { 94 | var module = window.moduleManager.addModule(new ModuleConstructor()); 95 | window.moduleManager.startDrag(module, e); 96 | }); 97 | 98 | // TODO: tags 99 | }; 100 | 101 | var moduleLibrary = new ModuleLibrary(); 102 | module.exports = moduleLibrary; 103 | -------------------------------------------------------------------------------- /src/ui/onWindowResize.js: -------------------------------------------------------------------------------- 1 | var resetCanvas = require('./overlay').reset; 2 | var moduleManager = require('./moduleManager'); 3 | 4 | var timeout = null; 5 | 6 | window.addEventListener('resize', function (e) { 7 | if (timeout !== null) { 8 | window.clearTimeout(timeout); 9 | } 10 | 11 | timeout = window.setTimeout(function () { 12 | resetCanvas(); 13 | moduleManager.drawCables(); 14 | timeout = null; 15 | }, 50); 16 | }); -------------------------------------------------------------------------------- /src/ui/overlay.js: -------------------------------------------------------------------------------- 1 | function resizeCanvas(canvas) { 2 | canvas.height = window.innerHeight; 3 | canvas.width = window.innerWidth; 4 | canvas.style.width = canvas.width + 'px'; 5 | canvas.style.height = canvas.height + 'px'; 6 | } 7 | 8 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 9 | var canvas = document.getElementById('cableCanvas'); 10 | var overlay = document.getElementById('overlayCanvas'); 11 | var ctx = canvas.getContext('2d'); 12 | var overCtx = overlay.getContext('2d'); 13 | 14 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 15 | function resetCanvas() { 16 | resizeCanvas(canvas); 17 | resizeCanvas(overlay); 18 | 19 | ctx.lineCap = 'round'; 20 | ctx.shadowColor = '#000'; 21 | ctx.shadowBlur = 3; 22 | ctx.lineWidth = 3; 23 | ctx.shadowOffsetX = 1; 24 | ctx.shadowOffsetY = 1; 25 | 26 | overCtx.lineWidth = 3; 27 | overCtx.strokeStyle = '#444'; 28 | overCtx.lineCap = 'butt'; 29 | overCtx.setLineDash([3, 3]); 30 | } 31 | 32 | resetCanvas(); 33 | 34 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 35 | exports.ctx = ctx; 36 | exports.overCtx = overCtx; 37 | exports.reset = resetCanvas; 38 | -------------------------------------------------------------------------------- /src/ui/synthEditor/Container.js: -------------------------------------------------------------------------------- 1 | var constants = require('./constants'); 2 | var domUtils = require('../domUtils'); 3 | var createDom = domUtils.createDom; 4 | var createDiv = domUtils.createDiv; 5 | var makeButton = domUtils.makeButton; 6 | var removeDom = domUtils.removeDom; 7 | var GRID_SIZE = constants.GRID_SIZE; 8 | 9 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 10 | function Container(parent) { 11 | this.dom = createDiv('synthEdit-container', parent.dom); 12 | } 13 | module.exports = Container; 14 | 15 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 16 | /** set size in unit (grid based) */ 17 | Container.prototype.rect = function(x, y, w, h) { 18 | this.dom.style.left = x * GRID_SIZE + 'px'; 19 | this.dom.style.top = y * GRID_SIZE + 'px'; 20 | this.dom.style.width = w * GRID_SIZE - 20 + 'px'; 21 | this.dom.style.height = h * GRID_SIZE - 20 + 'px'; 22 | return this; 23 | }; -------------------------------------------------------------------------------- /src/ui/synthEditor/Label.js: -------------------------------------------------------------------------------- 1 | var constants = require('./constants'); 2 | var domUtils = require('../domUtils'); 3 | var createDom = domUtils.createDom; 4 | var createDiv = domUtils.createDiv; 5 | var makeButton = domUtils.makeButton; 6 | var removeDom = domUtils.removeDom; 7 | var GRID_SIZE = constants.GRID_SIZE; 8 | 9 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 10 | function Label(parent) { 11 | this.dom = createDiv('synthEdit-label', parent.dom); 12 | } 13 | module.exports = Label; 14 | 15 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 16 | Label.prototype.position = function (x, y, w) { 17 | this.dom.style.left = x * GRID_SIZE + 'px'; 18 | this.dom.style.top = y * GRID_SIZE + 'px'; 19 | this.dom.style.width = w * GRID_SIZE + 'px'; 20 | return this; 21 | }; 22 | 23 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 24 | Label.prototype.text = function (text) { 25 | this.dom.innerText = text; 26 | return this; 27 | }; 28 | 29 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 30 | Label.prototype.color = function (color) { 31 | this.dom.style.color = constants.getColor(color).hi; 32 | return this; 33 | }; -------------------------------------------------------------------------------- /src/ui/synthEditor/TextInput.js: -------------------------------------------------------------------------------- 1 | var constants = require('./constants'); 2 | var domUtils = require('../domUtils'); 3 | var map = require('../../core/utils').map; 4 | var createDom = domUtils.createDom; 5 | var createDiv = domUtils.createDiv; 6 | var makeButton = domUtils.makeButton; 7 | var GRID_SIZE = constants.GRID_SIZE; 8 | 9 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 10 | function TextInput(parent) { 11 | this.editor = parent; // TODO: should we allow parent to be any else than editor? 12 | this.dom = createDom('input', 'synthEdit-textInput', parent.dom); 13 | this._obj = null; 14 | this._attribute = null; 15 | this._autoUpdate = false; 16 | 17 | this.dom.type = 'text'; 18 | this._initMouseEvents(); 19 | } 20 | module.exports = TextInput; 21 | 22 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 23 | TextInput.prototype.bind = function (obj, attribute) { 24 | this._obj = obj; 25 | this._attribute = attribute; 26 | 27 | this.dom.value = obj[attribute]; 28 | return this; 29 | }; 30 | 31 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 32 | TextInput.prototype.autoUpdate = function () { 33 | this._autoUpdate = true; 34 | return this; 35 | }; 36 | 37 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 38 | TextInput.prototype.position = function (x, y, w) { 39 | this.dom.style.left = x * GRID_SIZE + 'px'; 40 | this.dom.style.top = y * GRID_SIZE + 'px'; 41 | this.dom.style.width = w * GRID_SIZE + 'px'; 42 | return this; 43 | }; 44 | 45 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 46 | TextInput.prototype._initMouseEvents = function () { 47 | var self = this; 48 | this.dom.addEventListener('change', function (e) { 49 | if (!self._obj) return; 50 | self._obj[self._attribute] = self.dom.value; 51 | if (self._autoUpdate) { 52 | self.editor.updateBuffer(); 53 | } 54 | }); 55 | }; 56 | 57 | -------------------------------------------------------------------------------- /src/ui/synthEditor/constants.js: -------------------------------------------------------------------------------- 1 | exports.GRID_SIZE = 16; 2 | 3 | var COLORS = [ 4 | { hi: '#BBB', low: '#444' }, 5 | { hi: '#FB0203', low: '#400615' }, 6 | { hi: '#07FC0B', low: '#024415' }, 7 | { hi: '#00F', low: '#004' }, 8 | { hi: '#FF0', low: '#440' }, 9 | { hi: '#FF02FF', low: '#410158' }, 10 | { hi: '#61F5E2', low: '#275359' }, 11 | ]; 12 | 13 | exports.COLORS = COLORS; 14 | 15 | var colorLength = COLORS.length; 16 | 17 | exports.getColor = function (color) { 18 | return COLORS[~~color % colorLength]; 19 | }; -------------------------------------------------------------------------------- /src/ui/synthEditor/index.js: -------------------------------------------------------------------------------- 1 | var SynthEditorPanel = require('./SynthEditorPanel'); 2 | 3 | var synthEditorPanel = new SynthEditorPanel(); 4 | // module.exports = synthEditor; 5 | // window.synthEditor = synthEditor; 6 | 7 | 8 | var editorBuilders = {}; 9 | 10 | exports.register = function (synthId, editorBuilder) { 11 | editorBuilders[synthId] = editorBuilder; 12 | }; 13 | 14 | exports.hasEditor = function (synthId) { 15 | return !!editorBuilders[synthId]; 16 | }; 17 | 18 | exports.open = function (synthId, bufferData) { 19 | synthEditorPanel.init(synthId, bufferData); 20 | var editorBuilder = editorBuilders[synthId]; 21 | if (!editorBuilder) return console.error('there is no editor for synth "' + synthId + '"'); 22 | editorBuilder.create(synthEditorPanel, bufferData.params); 23 | // TODO: bind editor header menu with bufferData 24 | // - open audio editor 25 | // - play 26 | // - generate 27 | // - loop property 28 | // - tags ? 29 | // - save ? 30 | 31 | synthEditorPanel.open(); 32 | synthEditorPanel.setOnTop(); 33 | }; 34 | -------------------------------------------------------------------------------- /tools/commands.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 5 | var commands = {}; 6 | 7 | function addCommandModule(moduleName, modulePath) { 8 | var module = require(modulePath); 9 | commands[moduleName] = {}; 10 | for (var keys = Object.keys(module), i = 0; i < keys.length; i++) { 11 | var commandName = keys[i]; 12 | var command = module[commandName]; 13 | if (typeof command !== 'function') continue; 14 | commands[moduleName][commandName] = command; 15 | } 16 | } 17 | 18 | function getCommandModules(dir) { 19 | var fileList = fs.readdirSync(dir); 20 | 21 | for (var i = 0; i < fileList.length; i++) { 22 | var fileName = fileList[i]; 23 | var moduleName = path.parse(fileName).name; 24 | var modulePath = path.join(dir, fileName); 25 | addCommandModule(moduleName, modulePath); 26 | } 27 | } 28 | 29 | getCommandModules(path.join(process.cwd(), 'tools/commands')); // project's custom commands 30 | 31 | function noop() {}; 32 | 33 | //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 34 | function commandRequest(message, cb) { 35 | cb = cb || noop; 36 | 37 | if (!message.command) return cb('Empty command'); 38 | var command = message.command.split('.'); 39 | if (command.length !== 2) return cb('Incorrect command format'); 40 | 41 | var moduleId = command[0]; 42 | var commandId = command[1]; 43 | 44 | if (!commands[moduleId] || !commands[moduleId][commandId]) return cb('Unknown command'); 45 | 46 | console.log('\033[101mCOMMAND\033[0m ' + moduleId + '.' + commandId); 47 | commands[moduleId][commandId](message, cb); 48 | } 49 | 50 | module.exports = commandRequest; 51 | -------------------------------------------------------------------------------- /tools/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "disposition": { 3 | "mapEditor": { 4 | "x": 128, 5 | "y": 21, 6 | "width": 280, 7 | "height": 216 8 | }, 9 | "tilesheet": { 10 | "x": 416, 11 | "y": 21, 12 | "zoom": 2 13 | }, 14 | "tileBrush": { 15 | "x": 0, 16 | "y": 181, 17 | "width": 120, 18 | "height": 56, 19 | "foldConfig": {} 20 | }, 21 | "palette": { 22 | "x": 0, 23 | "y": 261 24 | }, 25 | "fileExplorer": { 26 | "x": 0, 27 | "y": 21, 28 | "width": 120, 29 | "height": 136, 30 | "assets": { 31 | "maps.json": {} 32 | } 33 | } 34 | }, 35 | "windowSize": { 36 | "width": 685, 37 | "height": 331 38 | } 39 | } --------------------------------------------------------------------------------