├── styles
├── external-node.mcss
├── codemirror.css
├── center-tab.mcss
├── source-node.mcss
├── modulator-node.mcss
├── processor-node.mcss
├── main-params.mcss
├── raw-editor.mcss
├── global-controller-node.mcss
├── node-spawner.mcss
├── chunk-node.mcss
├── clock.mcss
├── audio-slot.mcss
├── memory-usage.mcss
├── loop-position.mcss
├── scale-chooser.mcss
├── sample-chooser.mcss
├── index.js
├── toggle-chooser.mcss
├── helper.mcss
├── audio-meter.mcss
├── value-slots.mcss
├── button.mcss
├── controller-node.mcss
├── setup-node.mcss
├── param-list.mcss
├── node-collection.mcss
├── transport.mcss
├── recording-node.mcss
└── toggle-button.mcss
├── lib
├── package.json
├── periodic-waves
│ ├── ah.imag
│ ├── ah.real
│ ├── bass.imag
│ ├── bass.real
│ ├── ee.imag
│ ├── ee.real
│ ├── ooh.imag
│ ├── ooh.real
│ ├── ow.imag
│ ├── ow.real
│ ├── wide.imag
│ ├── wide.real
│ ├── brass.imag
│ ├── brass.real
│ ├── organ.imag
│ ├── organ.real
│ ├── piano.imag
│ ├── piano.real
│ ├── bass-fuzz.imag
│ ├── bass-fuzz.real
│ ├── bass-sub.imag
│ ├── bass-sub.real
│ ├── disonant.imag
│ ├── disonant.real
│ ├── soft-saw.imag
│ ├── soft-saw.real
│ ├── strings.imag
│ ├── strings.real
│ ├── throaty.imag
│ ├── throaty.real
│ ├── trombone.imag
│ ├── trombone.real
│ ├── wurlitzer.imag
│ ├── wurlitzer.real
│ ├── guitar-fuzz.imag
│ ├── guitar-fuzz.real
│ ├── convert.js
│ ├── raw.js
│ └── index.js
├── h.js
├── clamp.js
├── property.js
├── dom-event.js
├── param-negate.js
├── no-drop.js
├── read.js
├── find-item-by-path.js
├── copy-file.js
├── cancel-event.js
├── destroy-all.js
├── bind-event.js
├── set-route.js
├── hold-active-transform.js
├── get-value.js
├── file-event.js
├── params
│ ├── select.js
│ ├── editable.js
│ ├── text.js
│ ├── sample-chooser.js
│ ├── toggle-chooser.js
│ └── toggle-button.js
├── widgets
│ ├── collection.js
│ ├── header.js
│ ├── params.js
│ ├── midi-output.js
│ └── spawner.js
├── quantize-duration.js
├── zoom-event.js
├── active-indexes.js
├── random-color.js
├── is-triggerable.js
├── set-mapped-value.js
├── once-true.js
├── menu.js
├── array-stack.js
├── midi-to-param.js
├── get-param-value.js
├── render-node.js
├── stream-progress.js
├── quantize-to-square.js
├── key-collection.js
├── get-port-siblings.js
├── get-closest-point.js
├── resolve-node.js
├── attribute-hook.js
├── resolve-available.js
├── watch-struct.js
├── watch-buttons.js
├── double-bind.js
├── grid-slice-peaks.js
├── interpolate.js
├── update-param-references.js
├── value-at-time-getter.js
├── to-pcm.js
├── get-sound-offset.js
├── with-resolved.js
├── destroy-source-node.js
├── context-menu.js
├── map-watch-diff-stack.js
├── json-file.js
├── index-param.js
├── flash-array.js
├── mouse-position-event.js
├── param-from-number.js
├── on-trigger.js
├── sustained.js
├── callback-worker.js
├── cleaner.js
├── get-peaks.js
├── param-square.js
├── midi-clock-offset.js
├── detect-peaks.js
├── observ-keys.js
├── flag-param.js
├── global-controller.js
├── shape-slots.js
├── scale-interpolate.js
├── resolve-file-available.js
├── param-abs.js
├── midi-note.js
├── import-associated-files.js
├── mouse-drag-event.js
├── wave-hook.js
├── timeline-scheduler.js
├── wave-svg.new.js
├── test
│ └── test-grab-grid.js
├── watch-nodes-changed.js
├── apply-scale.js
├── assign-available-port.js
├── processor.js
├── param-sum.js
├── observ-rms.js
├── param-clamp.js
├── query-param.js
├── import-sample.js
├── rename-widget.js
└── extend-params.js
├── logo.png
├── .gitignore
├── nodes
├── clip
│ └── index.js
├── hold
│ ├── index.js
│ └── object.js
├── link-param
│ └── index.js
├── audio-buffer
│ └── index.js
├── offset
│ ├── index.js
│ └── object.js
├── random
│ └── index.js
├── trigger-value
│ └── index.js
├── link-modulator
│ ├── index.js
│ └── object.js
├── multiply
│ ├── index.js
│ └── object.js
├── quantize
│ ├── index.js
│ └── object.js
├── chromatic-scale
│ └── index.js
├── midi-cc
│ ├── index.js
│ └── object.js
├── project
│ └── index.js
├── recording
│ ├── ableton-export
│ │ ├── Project8_1.cfg
│ │ └── template.js
│ └── index.js
├── external-chunk
│ ├── index.js
│ └── view.js
├── setup
│ └── index.js
├── slot
│ └── index.js
├── eq
│ ├── index.js
│ ├── params.js
│ └── view.js
├── clap
│ ├── index.js
│ └── view.js
├── gain
│ ├── index.js
│ ├── view.js
│ └── object.js
├── kick
│ ├── index.js
│ └── view.js
├── noise
│ ├── index.js
│ └── view.js
├── pan
│ ├── index.js
│ ├── object.js
│ └── view.js
├── reverb
│ ├── types.js
│ └── index.js
├── snare
│ └── index.js
├── cymbal
│ ├── index.js
│ └── view.js
├── delay
│ └── index.js
├── dipper
│ ├── index.js
│ └── view.js
├── filter
│ ├── index.js
│ └── object.js
├── envelope
│ └── index.js
├── overdrive
│ ├── index.js
│ └── view.js
├── bitcrusher
│ ├── index.js
│ ├── view.js
│ └── object.js
├── compressor
│ ├── index.js
│ ├── view.js
│ └── object.js
├── pitchshift
│ ├── index.js
│ └── view.js
├── ring-modulator
│ ├── index.js
│ ├── object.js
│ └── view.js
├── ableton-link
│ ├── index.js
│ └── view.js
├── loop-grid-qwerty
│ ├── index.js
│ └── README.md
├── global-launch-control
│ └── index.js
├── granular
│ └── index.js
├── global-launch-control-xl
│ └── index.js
├── spatial-pan
│ ├── index.js
│ └── object.js
├── loop-grid-push
│ └── index.js
├── mixer-launch-control-xl
│ ├── index.js
│ └── set-lights.js
├── ping-pong-delay
│ └── index.js
├── freeverb
│ ├── index.js
│ ├── object.js
│ └── view.js
├── loop-grid-launchpad-mk2
│ └── index.js
├── loop-grid-launchpad
│ ├── index.js
│ └── state-lights.js
├── midi-sync-output
│ ├── index.js
│ └── view.js
├── oscillator-pulse
│ └── index.js
├── convolution-reverb
│ └── index.js
├── modulator-chunk
│ ├── index.js
│ └── object.js
├── oscillator
│ ├── index.js
│ └── shape-choices.js
├── sample
│ └── index.js
├── midi-out-chunk
│ └── index.js
├── mono-chromatic-chunk
│ └── index.js
├── slicer-chunk
│ └── index.js
├── midi-out
│ ├── index.js
│ └── view.js
├── lfo
│ └── index.js
├── meddler-chunk
│ ├── index.js
│ └── external.js
├── index.js
├── external-audio-input
│ ├── index.js
│ └── view.js
├── triggers-chunk
│ ├── index.js
│ └── external.js
├── synth-chunk
│ └── index.js
├── loop-grid
│ ├── compute-targets.js
│ └── compute-flags.js
└── chromatic-chunk
│ └── index.js
├── demo-project
├── DWS - OST
│ ├── bd01.opus
│ ├── bd08.opus
│ ├── cp01.opus
│ ├── cr01.opus
│ ├── hh01.opus
│ ├── hh02.opus
│ ├── oh01.opus
│ ├── sd12.opus
│ ├── synthit.opus
│ ├── cymbal (1).opus
│ ├── kick (11).opus
│ ├── snare (19).opus
│ ├── synthit2.opus
│ ├── tom (7) 1.opus
│ ├── hi hat (15).opus
│ ├── hi hat (22).opus
│ ├── hi hat (34).opus
│ ├── DOLLARS and TODAY and EDUCATION-Jc_RKi4rmzs.m4a
│ ├── cspan-output.json
│ └── synth.json
├── DWS - Synthesia
│ ├── Build.opus
│ └── Pitched.opus
├── DWS - Beep Boop
│ ├── 1490926241152.opus
│ └── Perc-output.json
├── DWS - Quantum Loop
│ ├── QL Rev_1-448.ogg
│ ├── QL Rev_0-1000.ogg
│ ├── QL Rev_1-1000.ogg
│ ├── QL Stab_5-1000.ogg
│ ├── QL Rev_450-1000.ogg
│ ├── Kick Gold 6_0-1000.ogg
│ ├── zara intro 1_14-28.ogg
│ ├── 1390801779250-24_2-906.ogg
│ ├── 140530145363057_0-1000.ogg
│ ├── 140530149429658_0-1000.ogg
│ ├── 140530150976559_0-1000.ogg
│ ├── zara intro 1_158-164.ogg
│ ├── zara intro 1_170-176.ogg
│ ├── zara intro 1_408-423.ogg
│ ├── zara intro 1_556-565.ogg
│ ├── zara intro 1_683-701.ogg
│ ├── zara intro 1_786-796.ogg
│ ├── zara intro 1_884-936.ogg
│ ├── 1390799216470-0_469-1000.ogg
│ ├── 1390799216470-0_470-773.ogg
│ ├── 1390799218452-1_418-1000.ogg
│ ├── 1390799218452-1_418-556.ogg
│ ├── 1390799329479-2_245-560.ogg
│ ├── 1390799329479-2_245-575.ogg
│ ├── 1390799329479-2_316-618.ogg
│ ├── 1390801779250-24_2-1000.ogg
│ ├── 140530143398856_11-1000.ogg
│ ├── bllooper.json
│ ├── Q Tinkle.json
│ ├── freedom.json
│ └── Q Warp.json
├── Launchpad Example
│ ├── Kick Gold 6_0-1000.ogg
│ ├── Clap Porter 1_0-1000.ogg
│ ├── HiHat_07_V01_2-1000.ogg
│ ├── Snare Gold 8_0-1000.ogg
│ ├── OpenHH AR70sOpen V127 2_0-1000.ogg
│ ├── pad.json
│ ├── bass.json
│ └── synth.json
├── Qwerty Keys Example
│ ├── Kick Gold 6_0-1000.ogg
│ ├── Clap Porter 1_0-1000.ogg
│ ├── HiHat_07_V01_2-1000.ogg
│ ├── Snare Gold 8_0-1000.ogg
│ ├── OpenHH AR70sOpen V127 2_0-1000.ogg
│ ├── bass.json
│ ├── pad.json
│ └── synth.json
└── project.json
├── views
└── window.html
└── scripts
├── link-lib.js
├── start.js
└── export-recording.js
/styles/external-node.mcss:
--------------------------------------------------------------------------------
1 | ExternalNode {
2 | $node
3 | }
4 |
--------------------------------------------------------------------------------
/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lib",
3 | "version": "0.0.0"
4 | }
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/logo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | **/backup
4 | demo-project/~recordings
5 |
--------------------------------------------------------------------------------
/lib/periodic-waves/ah.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/ah.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/ah.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/ah.real
--------------------------------------------------------------------------------
/lib/periodic-waves/bass.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/bass.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/bass.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/bass.real
--------------------------------------------------------------------------------
/lib/periodic-waves/ee.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/ee.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/ee.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/ee.real
--------------------------------------------------------------------------------
/lib/periodic-waves/ooh.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/ooh.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/ooh.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/ooh.real
--------------------------------------------------------------------------------
/lib/periodic-waves/ow.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/ow.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/ow.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/ow.real
--------------------------------------------------------------------------------
/lib/periodic-waves/wide.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/wide.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/wide.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/wide.real
--------------------------------------------------------------------------------
/nodes/clip/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'timeline/clip',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/nodes/hold/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'modulator/hold',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/nodes/link-param/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'linkParam',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/lib/periodic-waves/brass.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/brass.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/brass.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/brass.real
--------------------------------------------------------------------------------
/lib/periodic-waves/organ.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/organ.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/organ.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/organ.real
--------------------------------------------------------------------------------
/lib/periodic-waves/piano.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/piano.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/piano.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/piano.real
--------------------------------------------------------------------------------
/nodes/audio-buffer/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'AudioBuffer',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/nodes/offset/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'modulator/offset',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/nodes/random/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'modulator/random',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/nodes/trigger-value/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'slot/value',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/demo-project/DWS - OST/bd01.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/bd01.opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/bd08.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/bd08.opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/cp01.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/cp01.opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/cr01.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/cr01.opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/hh01.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/hh01.opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/hh02.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/hh02.opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/oh01.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/oh01.opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/sd12.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/sd12.opus
--------------------------------------------------------------------------------
/lib/periodic-waves/bass-fuzz.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/bass-fuzz.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/bass-fuzz.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/bass-fuzz.real
--------------------------------------------------------------------------------
/lib/periodic-waves/bass-sub.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/bass-sub.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/bass-sub.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/bass-sub.real
--------------------------------------------------------------------------------
/lib/periodic-waves/disonant.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/disonant.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/disonant.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/disonant.real
--------------------------------------------------------------------------------
/lib/periodic-waves/soft-saw.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/soft-saw.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/soft-saw.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/soft-saw.real
--------------------------------------------------------------------------------
/lib/periodic-waves/strings.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/strings.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/strings.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/strings.real
--------------------------------------------------------------------------------
/lib/periodic-waves/throaty.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/throaty.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/throaty.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/throaty.real
--------------------------------------------------------------------------------
/lib/periodic-waves/trombone.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/trombone.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/trombone.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/trombone.real
--------------------------------------------------------------------------------
/lib/periodic-waves/wurlitzer.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/wurlitzer.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/wurlitzer.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/wurlitzer.real
--------------------------------------------------------------------------------
/nodes/link-modulator/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'modulator/param',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/nodes/multiply/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'modulator/multiply',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/nodes/quantize/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'modulator/quantize',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/demo-project/DWS - OST/synthit.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/synthit.opus
--------------------------------------------------------------------------------
/lib/periodic-waves/guitar-fuzz.imag:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/guitar-fuzz.imag
--------------------------------------------------------------------------------
/lib/periodic-waves/guitar-fuzz.real:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/lib/periodic-waves/guitar-fuzz.real
--------------------------------------------------------------------------------
/nodes/chromatic-scale/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'modulator/scale',
3 | object: require('./object')
4 | }
5 |
--------------------------------------------------------------------------------
/demo-project/DWS - OST/cymbal (1).opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/cymbal (1).opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/kick (11).opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/kick (11).opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/snare (19).opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/snare (19).opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/synthit2.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/synthit2.opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/tom (7) 1.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/tom (7) 1.opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/hi hat (15).opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/hi hat (15).opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/hi hat (22).opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/hi hat (22).opus
--------------------------------------------------------------------------------
/demo-project/DWS - OST/hi hat (34).opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/hi hat (34).opus
--------------------------------------------------------------------------------
/demo-project/DWS - Synthesia/Build.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Synthesia/Build.opus
--------------------------------------------------------------------------------
/nodes/midi-cc/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'MIDI CC',
3 | node: 'slot/midi-cc',
4 | object: require('./object')
5 | }
6 |
--------------------------------------------------------------------------------
/nodes/project/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'project',
3 | render: require('./view'),
4 | object: require('./object')
5 | }
--------------------------------------------------------------------------------
/demo-project/DWS - Synthesia/Pitched.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Synthesia/Pitched.opus
--------------------------------------------------------------------------------
/styles/codemirror.css:
--------------------------------------------------------------------------------
1 | .ace-ambiance .ace_scroller {
2 | background-color: transparent !important;
3 | box-shadow: none !important;
4 | }
--------------------------------------------------------------------------------
/nodes/recording/ableton-export/Project8_1.cfg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/nodes/recording/ableton-export/Project8_1.cfg
--------------------------------------------------------------------------------
/styles/center-tab.mcss:
--------------------------------------------------------------------------------
1 | CenterTab {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: center;
6 | }
--------------------------------------------------------------------------------
/demo-project/DWS - Beep Boop/1490926241152.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Beep Boop/1490926241152.opus
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/QL Rev_1-448.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/QL Rev_1-448.ogg
--------------------------------------------------------------------------------
/lib/h.js:
--------------------------------------------------------------------------------
1 | module.exports = require('micro-css/h')(require('mutant/html-element'))
2 | module.exports.destroy = require('mutant/html-element').destroy
3 |
--------------------------------------------------------------------------------
/views/window.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/QL Rev_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/QL Rev_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/QL Rev_1-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/QL Rev_1-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/QL Stab_5-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/QL Stab_5-1000.ogg
--------------------------------------------------------------------------------
/nodes/external-chunk/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | node: 'externalChunk',
3 | render: require('./view'),
4 | object: require('./object')
5 | }
6 |
--------------------------------------------------------------------------------
/nodes/setup/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: "Setup",
3 | node: 'setup',
4 | render: require('./view'),
5 | object: require('./object')
6 | }
7 |
--------------------------------------------------------------------------------
/nodes/slot/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Slot',
3 | node: 'slot',
4 | object: require('./object'),
5 | render: require('./view')
6 | }
7 |
--------------------------------------------------------------------------------
/styles/source-node.mcss:
--------------------------------------------------------------------------------
1 | SourceNode {
2 | $node
3 |
4 | border-color: #4B5F5F
5 |
6 | header {
7 | background: #4B5F5F
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/QL Rev_450-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/QL Rev_450-1000.ogg
--------------------------------------------------------------------------------
/demo-project/Launchpad Example/Kick Gold 6_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Launchpad Example/Kick Gold 6_0-1000.ogg
--------------------------------------------------------------------------------
/styles/modulator-node.mcss:
--------------------------------------------------------------------------------
1 | ModulatorNode {
2 | $node
3 |
4 | border-color: #5a5538
5 |
6 | header {
7 | background: #5a5538
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/styles/processor-node.mcss:
--------------------------------------------------------------------------------
1 | ProcessorNode {
2 | $node
3 |
4 | border-color: #644B77
5 |
6 | header {
7 | background: #644B77
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/Kick Gold 6_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/Kick Gold 6_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/zara intro 1_14-28.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/zara intro 1_14-28.ogg
--------------------------------------------------------------------------------
/demo-project/Launchpad Example/Clap Porter 1_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Launchpad Example/Clap Porter 1_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/Launchpad Example/HiHat_07_V01_2-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Launchpad Example/HiHat_07_V01_2-1000.ogg
--------------------------------------------------------------------------------
/demo-project/Launchpad Example/Snare Gold 8_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Launchpad Example/Snare Gold 8_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/Qwerty Keys Example/Kick Gold 6_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Qwerty Keys Example/Kick Gold 6_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/1390801779250-24_2-906.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/1390801779250-24_2-906.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/140530145363057_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/140530145363057_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/140530149429658_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/140530149429658_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/140530150976559_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/140530150976559_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/zara intro 1_158-164.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/zara intro 1_158-164.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/zara intro 1_170-176.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/zara intro 1_170-176.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/zara intro 1_408-423.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/zara intro 1_408-423.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/zara intro 1_556-565.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/zara intro 1_556-565.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/zara intro 1_683-701.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/zara intro 1_683-701.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/zara intro 1_786-796.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/zara intro 1_786-796.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/zara intro 1_884-936.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/zara intro 1_884-936.ogg
--------------------------------------------------------------------------------
/demo-project/Qwerty Keys Example/Clap Porter 1_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Qwerty Keys Example/Clap Porter 1_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/Qwerty Keys Example/HiHat_07_V01_2-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Qwerty Keys Example/HiHat_07_V01_2-1000.ogg
--------------------------------------------------------------------------------
/demo-project/Qwerty Keys Example/Snare Gold 8_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Qwerty Keys Example/Snare Gold 8_0-1000.ogg
--------------------------------------------------------------------------------
/lib/clamp.js:
--------------------------------------------------------------------------------
1 | module.exports = function clamp (value, min, max) {
2 | if (typeof value !== 'number') return min
3 | return Math.min(max, Math.max(min, value))
4 | }
5 |
--------------------------------------------------------------------------------
/lib/property.js:
--------------------------------------------------------------------------------
1 | var Value = require('mutant/value')
2 |
3 | module.exports = function Property (defaultValue) {
4 | return Value(defaultValue, { defaultValue })
5 | }
6 |
--------------------------------------------------------------------------------
/nodes/recording/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Recording',
3 | node: 'recording',
4 | render: require('./view'),
5 | object: require('./object')
6 | }
7 |
--------------------------------------------------------------------------------
/styles/main-params.mcss:
--------------------------------------------------------------------------------
1 | MainParams {
2 | display: flex
3 |
4 | div + button {
5 | margin-left: 5px
6 | }
7 |
8 | div {
9 | flex: 1
10 | }
11 | }
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/1390799216470-0_469-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/1390799216470-0_469-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/1390799216470-0_470-773.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/1390799216470-0_470-773.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/1390799218452-1_418-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/1390799218452-1_418-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/1390799218452-1_418-556.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/1390799218452-1_418-556.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/1390799329479-2_245-560.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/1390799329479-2_245-560.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/1390799329479-2_245-575.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/1390799329479-2_245-575.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/1390799329479-2_316-618.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/1390799329479-2_316-618.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/1390801779250-24_2-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/1390801779250-24_2-1000.ogg
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/140530143398856_11-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - Quantum Loop/140530143398856_11-1000.ogg
--------------------------------------------------------------------------------
/lib/dom-event.js:
--------------------------------------------------------------------------------
1 | module.exports = function(fn, data, opts){
2 | var handler = {
3 | handleEvent: fn,
4 | data: data,
5 | opts: opts
6 | }
7 | return handler;
8 | }
--------------------------------------------------------------------------------
/lib/param-negate.js:
--------------------------------------------------------------------------------
1 | var Multiply = require('lib/param-multiply')
2 | module.exports = ParamNegate
3 |
4 | function ParamNegate (input) {
5 | return Multiply([input, -1])
6 | }
7 |
--------------------------------------------------------------------------------
/demo-project/Launchpad Example/OpenHH AR70sOpen V127 2_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Launchpad Example/OpenHH AR70sOpen V127 2_0-1000.ogg
--------------------------------------------------------------------------------
/demo-project/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "zoom": 1.1,
3 | "tempo": 85,
4 | "swing": 0.6006717158297845,
5 | "rawMode": false,
6 | "globalControllers": [],
7 | "node": "project"
8 | }
--------------------------------------------------------------------------------
/lib/no-drop.js:
--------------------------------------------------------------------------------
1 | module.exports = function(el){
2 | el.addEventListener('dragover', function(e){
3 | e.preventDefault()
4 | e.dataTransfer.dropEffect = 'none'
5 | }, true)
6 | }
--------------------------------------------------------------------------------
/lib/read.js:
--------------------------------------------------------------------------------
1 | module.exports = function(param){
2 | if (typeof param == 'function'){
3 | return param()
4 | } else if (param && param.read){
5 | return param.read()
6 | }
7 | }
--------------------------------------------------------------------------------
/nodes/eq/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'EQ',
3 | node: 'processor/eq',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/demo-project/Qwerty Keys Example/OpenHH AR70sOpen V127 2_0-1000.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/Qwerty Keys Example/OpenHH AR70sOpen V127 2_0-1000.ogg
--------------------------------------------------------------------------------
/nodes/clap/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Clap',
3 | node: 'source/clap',
4 | group: 'drumSources',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/gain/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Gain',
3 | node: 'processor/gain',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/kick/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Kick',
3 | node: 'source/kick',
4 | group: 'drumSources',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/noise/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Noise',
3 | node: 'source/noise',
4 | group: 'sources',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/pan/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Pan',
3 | node: 'processor/pan',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/reverb/types.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | ['Fast', 'processor/freeverb'],
3 | ['Generated Impulse', 'processor/reverb'],
4 | ['Convolution', 'processor/convolution-reverb']
5 | ]
6 |
--------------------------------------------------------------------------------
/nodes/snare/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Snare',
3 | node: 'source/snare',
4 | group: 'drumSources',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/demo-project/DWS - OST/DOLLARS and TODAY and EDUCATION-Jc_RKi4rmzs.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmckegg/loop-drop-app/HEAD/demo-project/DWS - OST/DOLLARS and TODAY and EDUCATION-Jc_RKi4rmzs.m4a
--------------------------------------------------------------------------------
/lib/find-item-by-path.js:
--------------------------------------------------------------------------------
1 | module.exports = findItemByPath
2 |
3 | function findItemByPath (items, path) {
4 | if (items) {
5 | return items.find(item => item.path() === path)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/cymbal/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Cymbal',
3 | node: 'source/cymbal',
4 | group: 'drumSources',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/delay/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Delay',
3 | node: 'processor/delay',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/dipper/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Dipper',
3 | node: 'processor/dipper',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/filter/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Filter',
3 | node: 'processor/filter',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/reverb/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Reverb',
3 | node: 'processor/reverb',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/lib/copy-file.js:
--------------------------------------------------------------------------------
1 | module.exports = function copyFile (from, to, fs, cb) {
2 | fs.createReadStream(from).on('error', cb)
3 | .pipe(fs.createWriteStream(to)).on('error', cb)
4 | .on('finish', cb)
5 | }
--------------------------------------------------------------------------------
/nodes/envelope/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Envelope',
3 | group: 'triggerModulators',
4 | node: 'modulator/adsr',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/overdrive/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Overdrive',
3 | node: 'processor/overdrive',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/bitcrusher/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Bitcrusher',
3 | group: 'processors',
4 | node: 'processor/bitcrusher',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/compressor/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Compressor',
3 | node: 'processor/compressor',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/pitchshift/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Pitchshift',
3 | node: 'processor/pitchshift',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/styles/raw-editor.mcss:
--------------------------------------------------------------------------------
1 | RawEditor {
2 | flex: 1
3 | position: relative;
4 |
5 | div {
6 | background-color: transparent
7 | position: absolute
8 | top:0;right:0;bottom:0;left:0
9 | }
10 | }
--------------------------------------------------------------------------------
/nodes/ring-modulator/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Ring Modulator',
3 | node: 'processor/ring-modulator',
4 | group: 'processors',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/lib/cancel-event.js:
--------------------------------------------------------------------------------
1 | module.exports = function (fn, data, opts) {
2 | var handler = {
3 | handleEvent: handle
4 | }
5 | return handler
6 | }
7 |
8 | function handle (ev) {
9 | ev.preventDefault()
10 | }
11 |
--------------------------------------------------------------------------------
/lib/destroy-all.js:
--------------------------------------------------------------------------------
1 | module.exports = function (obs) {
2 | Object.keys(obs).forEach(function (key) {
3 | if (obs[key] && typeof obs[key].destroy === 'function') {
4 | obs[key].destroy()
5 | }
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/ableton-link/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Ableton Link Sync',
3 | node: 'global/ableton-link',
4 | group: 'global-controllers',
5 | object: require('./object'),
6 | render: require('./view')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/loop-grid-qwerty/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Qwerty Keys',
3 | group: 'loop-grids',
4 | node: 'controller/qwerty',
5 | render: require('../loop-grid/view'),
6 | object: require('./object')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/recording/ableton-export/template.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var ejs = require('ejs')
3 | var Path = require('path')
4 |
5 | module.exports = ejs.compile(fs.readFileSync(Path.join(__dirname, 'template.ejs'), 'utf8'))
6 |
--------------------------------------------------------------------------------
/styles/global-controller-node.mcss:
--------------------------------------------------------------------------------
1 | GlobalControllerNode {
2 | $node
3 |
4 | border-color: #222
5 | border-width: 2px
6 |
7 | header {
8 | padding: 5px 3px;
9 | background-color: #333;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/bind-event.js:
--------------------------------------------------------------------------------
1 | module.exports = function (element, eventName, handler) {
2 | element.addEventListener(eventName, handler)
3 | return function unlisten () {
4 | element.removeEventListener(eventName, handler)
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lib/set-route.js:
--------------------------------------------------------------------------------
1 | module.exports = function setRoute (node, id, value) {
2 | if (node.routes) {
3 | node.routes.put(id, value)
4 | } else if (node.node && node.node.routes) {
5 | node.node.routes.put(id, value)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/global-launch-control/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Launch Control',
3 | portMatch: /^Launch Control(?! XL)/,
4 | node: 'global/launch-control',
5 | group: 'global-controllers',
6 | object: require('./object')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/granular/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Granular',
3 | node: 'source/granular',
4 | group: 'sources',
5 | spawn: false, // spawned via source/sample
6 | object: require('./object'),
7 | render: require('./view')
8 | }
9 |
--------------------------------------------------------------------------------
/nodes/global-launch-control-xl/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Launch Control XL',
3 | portMatch: /^Launch Control XL/,
4 | node: 'global/launch-control-xl',
5 | group: 'global-controllers',
6 | object: require('./object')
7 | }
8 |
--------------------------------------------------------------------------------
/nodes/spatial-pan/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Spatial Pan',
3 | spawn: false, // spawned by pan
4 | node: 'processor/spatial-pan',
5 | group: 'processors',
6 | object: require('./object'),
7 | render: require('../pan/view')
8 | }
9 |
--------------------------------------------------------------------------------
/styles/node-spawner.mcss:
--------------------------------------------------------------------------------
1 | NodeSpawner {
2 | background: #3D3D3D;
3 | border-top: 1px #444 solid;
4 | display: flex;
5 | padding: 5px;
6 | margin: -6px -8px;
7 | margin-top: 10px;
8 |
9 | * {
10 | margin-right: 3px
11 | }
12 | }
--------------------------------------------------------------------------------
/lib/hold-active-transform.js:
--------------------------------------------------------------------------------
1 | module.exports = function holdActive (input, active) {
2 | active.forEach(function(index){
3 | input.data[index] = {
4 | events: [[0, true]],
5 | length: 2
6 | }
7 | })
8 | return input
9 | }
10 |
--------------------------------------------------------------------------------
/nodes/loop-grid-push/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Ableton Push',
3 | group: 'loop-grids',
4 | portMatch: /^Ableton Push User/,
5 | node: 'controller/push',
6 | render: require('../loop-grid/view'),
7 | object: require('./object')
8 | }
9 |
--------------------------------------------------------------------------------
/nodes/mixer-launch-control-xl/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Launch Control XL',
3 | group: 'mixers',
4 | portMatch: /^Launch Control XL/,
5 | node: 'controller/launch-control-xl',
6 | render: require('./view'),
7 | object: require('./object')
8 | }
9 |
--------------------------------------------------------------------------------
/nodes/ping-pong-delay/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Delay',
3 | spawn: false, // spawned from delay node
4 | node: 'processor/ping-pong-delay',
5 | group: 'processors',
6 | object: require('./object'),
7 | render: require('../delay/view')
8 | }
9 |
--------------------------------------------------------------------------------
/nodes/freeverb/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Freeverb',
3 | spawn: false, // can be selected using "reverb" with type option
4 | node: 'processor/freeverb',
5 | group: 'processors',
6 | object: require('./object'),
7 | render: require('./view')
8 | }
9 |
--------------------------------------------------------------------------------
/nodes/loop-grid-launchpad-mk2/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Launchpad',
3 | group: 'loop-grids',
4 | portMatch: /^Launchpad (MK2|Pro)/,
5 | node: 'controller/launchpad-mk2',
6 | render: require('../loop-grid/view'),
7 | object: require('./object')
8 | }
9 |
--------------------------------------------------------------------------------
/nodes/loop-grid-launchpad/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Launchpad Mini',
3 | group: 'loop-grids',
4 | portMatch: /^Launchpad(?! (Pro|MK2))/,
5 | node: 'controller/launchpad',
6 | render: require('../loop-grid/view'),
7 | object: require('./object')
8 | }
9 |
--------------------------------------------------------------------------------
/nodes/midi-sync-output/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'MIDI Sync Out',
3 | portMatch: /./,
4 | outputOnly: true,
5 | node: 'global/midi-sync-output',
6 | group: 'global-controllers',
7 | object: require('./object'),
8 | render: require('./view')
9 | }
10 |
--------------------------------------------------------------------------------
/scripts/link-lib.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var join = require('path').join
3 |
4 | process.cwd(join(__dirname, '..'))
5 | fs.symlink(join('..','lib'), join('node_modules', 'lib'), 'dir', function(err) {
6 | if (!err) {
7 | console.log('node_modules/lib -> lib')
8 | }
9 | })
--------------------------------------------------------------------------------
/lib/get-value.js:
--------------------------------------------------------------------------------
1 | module.exports = getValue
2 |
3 | function getValue(object, defaultValue){
4 | if (object instanceof Object && !Array.isArray(object)){
5 | return getValue(object.value, defaultValue)
6 | } else {
7 | return object != null ? object : defaultValue
8 | }
9 | }
--------------------------------------------------------------------------------
/nodes/oscillator-pulse/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Oscillator (pulse)',
3 | node: 'source/oscillator-pulse',
4 | group: 'sources',
5 | spawn: false, // spawned by source/oscillator by setting type
6 | object: require('./object'),
7 | render: require('./view')
8 | }
9 |
--------------------------------------------------------------------------------
/lib/periodic-waves/convert.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 |
3 | writeArray('name.real', input.real)
4 | writeArray('name.imag', input.imag)
5 |
6 | function writeArray (file, array) {
7 | fs.writeFileSync(
8 | file, new Buffer(new Uint8Array(new Float32Array(array).buffer))
9 | )
10 | }
--------------------------------------------------------------------------------
/nodes/convolution-reverb/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Convolution Reverb',
3 | spawn: false, // can be selected using "reverb" with type option
4 | node: 'processor/convolution-reverb',
5 | group: 'processors',
6 | object: require('./object'),
7 | render: require('./view')
8 | }
9 |
--------------------------------------------------------------------------------
/lib/file-event.js:
--------------------------------------------------------------------------------
1 | module.exports = FileEventHandler
2 |
3 | function FileEventHandler(fn, data) {
4 | return {
5 | fn: fn,
6 | data: data,
7 | handleEvent: handleEvent
8 | }
9 | }
10 |
11 | function handleEvent(ev) {
12 | var value = ev.currentTarget.files[0]
13 | this.fn(value)
14 | }
--------------------------------------------------------------------------------
/nodes/modulator-chunk/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Modulator',
3 | group: 'modifierChunks',
4 | node: 'modulatorChunk',
5 | description: 'Modulate parameters on other chunks.',
6 | spawn: {
7 | flags: ['freezeSuppress']
8 | },
9 | object: require('./object'),
10 | render: require('./view')
11 | }
12 |
--------------------------------------------------------------------------------
/nodes/oscillator/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Oscillator',
3 | node: 'source/oscillator',
4 | group: 'sources',
5 | spawn: {
6 | amp: {
7 | node: 'modulator/adsr',
8 | value: 0.6,
9 | release: 0.01
10 | }
11 | },
12 | object: require('./object'),
13 | render: require('./view')
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/start.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var spawn = require('child_process').spawn
4 | var electron = require('electron')
5 | var join = require('path').join
6 |
7 | var app = spawn(electron, ['main.js'], {
8 | stdio: 'inherit',
9 | cwd: join(__dirname, '..')
10 | }).on('exit', function(i, m) {
11 | process.exit()
12 | })
13 |
--------------------------------------------------------------------------------
/lib/params/select.js:
--------------------------------------------------------------------------------
1 | var Select = require('lib/widgets/select')
2 | var extend = require('xtend')
3 |
4 | module.exports = SelectParam
5 |
6 | function SelectParam (param, opts) {
7 | return Select(set, param, extend(opts, {
8 | selectedValue: param
9 | }))
10 | }
11 |
12 | function set (value) {
13 | this.data.set(value)
14 | }
15 |
--------------------------------------------------------------------------------
/nodes/sample/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Sample',
3 | node: 'source/sample',
4 | group: 'sources',
5 | object: require('./object'),
6 | spawn: {
7 | amp: {
8 | node: 'modulator/adsr',
9 | value: 1,
10 | release: 0.01
11 | },
12 | mode: 'oneshot'
13 | },
14 | render: require('./view')
15 | }
16 |
--------------------------------------------------------------------------------
/styles/chunk-node.mcss:
--------------------------------------------------------------------------------
1 | ChunkNode {
2 | padding: 5px;
3 | flex: 1
4 | display: flex
5 | flex-direction: row
6 |
7 | div.options {
8 | flex: 1
9 | padding: 8px
10 | }
11 |
12 | div.slot {
13 | flex: 3
14 | background: #222;
15 | padding: 5px;
16 | box-shadow: 0 0 5px black
17 | overflow-y: scroll
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/lib/params/editable.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var EditableHook = require('lib/editable-hook')
3 |
4 | module.exports = Editable
5 |
6 | function Editable (param, opts) {
7 | // opts: onChange, enabled
8 | return h('span', {
9 | hooks: [
10 | EditableHook(param, opts)
11 | ]
12 | })
13 | }
14 |
15 | Editable.edit = EditableHook.edit
16 |
--------------------------------------------------------------------------------
/styles/clock.mcss:
--------------------------------------------------------------------------------
1 | Clock {
2 | border: 1px solid #333;
3 | background-color: #171817;
4 | font-size: 32px;
5 | padding: 4px 8px !important;
6 | margin: 4px;
7 | font-family: sans-serif;
8 | color: #686;
9 | border-radius: 3px;
10 | text-shadow: 0 0 1px black;
11 | box-shadow: inset 0 0 10px black;
12 |
13 | span {
14 | color: #ACA
15 | }
16 | }
--------------------------------------------------------------------------------
/lib/widgets/collection.js:
--------------------------------------------------------------------------------
1 | var Orderable = require('./orderable.js')
2 | var renderNode = require('lib/render-node')
3 | var map = require('mutant/map')
4 |
5 | module.exports = renderCollection
6 |
7 | function renderCollection (collection) {
8 | return map(collection, function (node) {
9 | return Orderable(node, renderNode(node))
10 | }, { maxTime: 16 })
11 | }
12 |
--------------------------------------------------------------------------------
/lib/widgets/header.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var send = require('mutant/send')
3 |
4 | module.exports = function (node, display, options) {
5 | var collection = node.context.collection
6 | return h('header', options, [
7 | display,
8 | h('button.remove Button -warn', {
9 | 'ev-click': send(collection.remove, node)
10 | }, 'X')
11 | ])
12 | }
13 |
--------------------------------------------------------------------------------
/lib/quantize-duration.js:
--------------------------------------------------------------------------------
1 | module.exports = quantizeDuration
2 |
3 | function quantizeDuration (value) {
4 | var grid = getGrid(value)
5 | return Math.round(value / grid) * grid
6 | }
7 |
8 | function getGrid (duration) {
9 | if (duration < 0.7) {
10 | return 0.5
11 | } else if (duration < 1.7) {
12 | return 1
13 | } else {
14 | return 2
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/styles/audio-slot.mcss:
--------------------------------------------------------------------------------
1 | AudioSlot {
2 | section {
3 | header {
4 | display: flex
5 | align-items: center
6 | button {
7 | margin-left: 10px
8 | }
9 | }
10 | border-radius: 3px
11 | background: #333
12 | padding: 6px 8px
13 | overflow: hidden
14 | }
15 | section + section {
16 | margin-top: 5px
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/zoom-event.js:
--------------------------------------------------------------------------------
1 | module.exports = ZoomEvent
2 |
3 | function ZoomEvent (fn, data) {
4 | var handler = {
5 | handleEvent: handle,
6 | fn: fn,
7 | data: data
8 | }
9 | return handler
10 | }
11 |
12 | function handle (ev) {
13 | if (ev.ctrlKey) {
14 | ev.preventDefault()
15 | ev.stopImmediatePropagation()
16 | this.fn(ev.deltaY, this.data)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/active-indexes.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 |
3 | module.exports = function activeIndexes (obs) {
4 | return computed([obs], function (grid) {
5 | var result = []
6 | grid.data.forEach(pushIndexIfPresent, result)
7 | return result
8 | })
9 | }
10 |
11 | function pushIndexIfPresent (value, index) {
12 | if (value) {
13 | this.push(index)
14 | }
15 | }
--------------------------------------------------------------------------------
/lib/random-color.js:
--------------------------------------------------------------------------------
1 | module.exports = randomColor
2 | function randomColor(mix){
3 | var red = Math.random()*256
4 | var green = Math.random()*256
5 | var blue = Math.random()*256
6 | if (mix != null) {
7 | red = (red + mix[0]) / 2
8 | green = (green + mix[1]) / 2
9 | blue = (blue + mix[2]) / 2
10 | }
11 | return [Math.floor(red),Math.floor(green),Math.floor(blue)]
12 | }
--------------------------------------------------------------------------------
/lib/is-triggerable.js:
--------------------------------------------------------------------------------
1 | module.exports = isTriggerable
2 |
3 | function isTriggerable (param) {
4 | if (typeof param.triggerable === 'boolean') {
5 | return param.triggerable
6 | } else {
7 | var slot = param.context && param.context.slot
8 | return !!(
9 | slot && slot.triggerOn &&
10 | slot().id !== 'output' // HACK: should handle this better
11 | )
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/set-mapped-value.js:
--------------------------------------------------------------------------------
1 | var watch = require('mutant/watch')
2 | module.exports = function(values, obs, target){
3 | var lastValue = undefined
4 | return watch(obs, function(data){
5 | for (var i=values.length-1;i>=0;i--) {
6 | if (data[i]) {
7 | if (target() !== values[i]) {
8 | target.set(values[i])
9 | }
10 | break
11 | }
12 | }
13 | })
14 | }
--------------------------------------------------------------------------------
/lib/once-true.js:
--------------------------------------------------------------------------------
1 | var watch = require('mutant/watch')
2 | module.exports = function onceTrue (value, fn) {
3 | var done = false
4 | var release = watch(value, (v) => {
5 | if (v && !done) {
6 | done = true
7 | setImmediate(doRelease)
8 | fn(v)
9 | }
10 | }, { nextTick: true })
11 |
12 | return release
13 |
14 | function doRelease () {
15 | release()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/nodes/midi-out-chunk/index.js:
--------------------------------------------------------------------------------
1 | var randomColor = require('lib/random-color')
2 |
3 | module.exports = {
4 | name: 'MIDI',
5 | node: 'chunk/midi-out',
6 | description: 'Output midi notes tuned to current scale.',
7 | group: 'simpleChunks',
8 | spawn: function () {
9 | return {
10 | color: randomColor()
11 | }
12 | },
13 | object: require('./object'),
14 | render: require('./view')
15 | }
16 |
--------------------------------------------------------------------------------
/nodes/mono-chromatic-chunk/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Chromatic (monophonic)',
3 | node: 'chunk/scale-mono',
4 | group: 'chunks',
5 | description: 'Monophonic version of chromatic chunk.',
6 | spawn: false, // spawned via chunk/scale
7 | external: true,
8 | renderExternal: require('../chromatic-chunk/external'),
9 | render: require('../chromatic-chunk/view'),
10 | object: require('./object')
11 | }
12 |
--------------------------------------------------------------------------------
/nodes/ring-modulator/object.js:
--------------------------------------------------------------------------------
1 | var Processor = require('lib/processor')
2 | var Oscillator = require('../oscillator/object')
3 |
4 | module.exports = RingModulatorNode
5 |
6 | function RingModulatorNode (context) {
7 | var node = context.audio.createGain()
8 |
9 | var obs = Processor(context, node, node, {
10 | carrier: Oscillator(context)
11 | })
12 |
13 | obs.carrier.connect(node.gain)
14 | return obs
15 | }
16 |
--------------------------------------------------------------------------------
/lib/periodic-waves/raw.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var path = require('path')
3 | var raw = {}
4 |
5 | fs.readdirSync(__dirname).forEach(function(file) {
6 | var ext = path.extname(file)
7 | if (ext === '.imag' || ext === '.real') {
8 | var base = path.basename(file, ext)
9 | var item = raw[base] = raw[base] || {}
10 | item[ext.slice(1)] = fs.readFileSync(path.join(__dirname, file))
11 | }
12 | })
13 |
14 | module.exports = raw
15 |
--------------------------------------------------------------------------------
/nodes/gain/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var ModRange = require('lib/params/mod-range')
4 |
5 | module.exports = function renderGain (node){
6 | return h('ProcessorNode -gain', [
7 | Header(node, h('span', 'Gain')),
8 | h('ParamList', [
9 | ModRange(node.gain, {
10 | defaultValue: 1,
11 | format: 'dB',
12 | flex: true
13 | })
14 | ])
15 | ])
16 | }
--------------------------------------------------------------------------------
/styles/memory-usage.mcss:
--------------------------------------------------------------------------------
1 | MemoryUsage {
2 | padding: 3px 6px;
3 | font-size: 10px;
4 | border: 1px solid #6e7b6e;
5 | background: #000;
6 | color: #ACA;
7 | margin-right: 3px
8 | cursor: pointer
9 |
10 | -critical {
11 | border: 1px solid #ff6b6b;
12 | background: #711313;
13 | color: #ffd6d6;
14 | }
15 |
16 | -warn {
17 | border: 1px solid #b5ad53;
18 | background: #655907;
19 | color: #fffb07;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/nodes/slicer-chunk/index.js:
--------------------------------------------------------------------------------
1 | var randomColor = require('lib/random-color')
2 |
3 | module.exports = {
4 | node: 'chunk/slicer',
5 | name: 'Slicer',
6 | description: 'Slice an audio file into pieces and distribute across controller.',
7 | group: 'simpleChunks',
8 | object: require('./object'),
9 | spawn: function () {
10 | return {
11 | color: randomColor(),
12 | chokeAll: true
13 | }
14 | },
15 | render: require('./view')
16 | }
17 |
--------------------------------------------------------------------------------
/lib/menu.js:
--------------------------------------------------------------------------------
1 | var electron = require('electron')
2 | var defaultMenu = require('electron-default-menu')
3 |
4 | var menu = defaultMenu(electron.app, electron.shell)
5 | menu.splice(1, 0, {
6 | label: 'Project',
7 | submenu: [
8 | {
9 | label: 'New Project...',
10 | click: (item, focusedWindow) => {
11 | electron.dialog.showMessageBox({ message: 'Do something', buttons: ['OK'] })
12 | }
13 | }
14 | ]
15 | })
16 | module.exports = menu
17 |
--------------------------------------------------------------------------------
/styles/loop-position.mcss:
--------------------------------------------------------------------------------
1 | LoopPosition {
2 | transform: translate3d(0,0,0)
3 | display: flex
4 | height: 8px;
5 | margin: 0 8px;
6 | div {
7 | flex: 1
8 | background: #323232
9 |
10 | -active {
11 | background: #63C763
12 | }
13 |
14 | :nth-child(4n) {
15 | margin-right: 4px
16 | }
17 |
18 | :last-child {
19 | margin-right: 0
20 | }
21 | }
22 |
23 | div + div {
24 | margin-left: 1px
25 | }
26 |
27 |
28 | }
--------------------------------------------------------------------------------
/lib/array-stack.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 |
3 | module.exports = ArrayStack
4 |
5 | function ArrayStack (items) {
6 | return computed(items, function(_) {
7 | var result = []
8 | for (var i = 0; i < arguments.length; i++) {
9 | var arr = arguments[i]
10 | for (var k = 0; k < arr.length; k++) {
11 | if (arr[k] != null) {
12 | result[k] = arr[k]
13 | }
14 | }
15 | }
16 | return result
17 | })
18 | }
--------------------------------------------------------------------------------
/nodes/midi-out/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'MIDI Note',
3 | node: 'source/midi-out',
4 | group: 'sources',
5 | spawn: function (context) {
6 | // HACK: enable midi output on chunk when midi note source added
7 | if (context.externalChunk && context.externalChunk.midiOutputEnabled) {
8 | context.externalChunk.midiOutputEnabled.set(true)
9 | }
10 | return {}
11 | },
12 | object: require('./object'),
13 | render: require('./view')
14 | }
15 |
--------------------------------------------------------------------------------
/styles/scale-chooser.mcss:
--------------------------------------------------------------------------------
1 | ScaleChooser {
2 |
3 | display: flex
4 | min-width: 150px
5 | flex: 1
6 |
7 | div.button {
8 | height: 20px
9 | flex: 1
10 | margin: 2px
11 | background: #3C493C
12 | border: 1px solid #2A2A2A
13 | border-radius: 3px
14 | cursor: pointer
15 |
16 | :hover {
17 | border-color: #666
18 | }
19 |
20 | -selected {
21 | background: #53AB53
22 | border-color: #336C20
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/lib/periodic-waves/index.js:
--------------------------------------------------------------------------------
1 | var raw = require('./raw')
2 |
3 | module.exports = function (audioContext) {
4 | var result = {}
5 | for (var k in raw) {
6 | result[k] = audioContext.createPeriodicWave(
7 | bufferAsFloat32(raw[k].real),
8 | bufferAsFloat32(raw[k].imag)
9 | )
10 | }
11 | return result
12 | }
13 |
14 | module.exports.raw = raw
15 |
16 | function bufferAsFloat32 (buffer) {
17 | return new Float32Array(new Uint8Array(buffer).buffer)
18 | }
19 |
--------------------------------------------------------------------------------
/lib/midi-to-param.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var ObservStruct = require('mutant/struct')
3 | var Observ = require('mutant/value')
4 |
5 | module.exports = MidiToParam
6 |
7 | function MidiToParam (context, id, value) {
8 | var obs = ObservStruct({
9 | id: Observ(id)
10 | })
11 |
12 | obs._type = 'RemoteParam'
13 | obs.currentValue = computed([value], fromMidi)
14 | return obs
15 | }
16 |
17 | function fromMidi (input) {
18 | return input / 128
19 | }
20 |
--------------------------------------------------------------------------------
/nodes/lfo/index.js:
--------------------------------------------------------------------------------
1 | var isTriggerable = require('lib/is-triggerable')
2 |
3 | module.exports = {
4 | name: 'LFO',
5 | group: 'modulators',
6 | node: 'modulator/lfo',
7 | object: require('./object'),
8 | render: require('./view'),
9 | spawn: function (context) {
10 | var result = {
11 | amp: 0.5,
12 | mode: 'add',
13 | value: 0.5
14 | }
15 | if (!isTriggerable({context})) {
16 | result.trigger = false
17 | }
18 | return result
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/get-param-value.js:
--------------------------------------------------------------------------------
1 | module.exports = function getParamValue (param, at) {
2 | if (param.currentValue) {
3 | var value = param.currentValue()
4 | if (typeof value === 'number') {
5 | return value
6 | } else if (value && value.getValueAtTime && param.context && param.context.audio) {
7 | return value.getValueAtTime(at || param.context.audio.currentTime)
8 | } else {
9 | console.warn('Unable to automate param.', param)
10 | return 0
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/nodes/meddler-chunk/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'Meddler',
3 | node: 'chunk/meddler',
4 | group: 'modifierChunks',
5 | description: 'Add effects to other chunks.',
6 | spawn: {
7 | slots: [{id: 'output', node: 'slot'}],
8 | inputs: ['input']
9 | },
10 | external: {
11 | shape: [1, 4],
12 | color: [255, 255, 0],
13 | minimised: true
14 | },
15 | renderExternal: require('./external'),
16 | render: require('./view'),
17 | object: require('./object')
18 | }
19 |
--------------------------------------------------------------------------------
/lib/params/text.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var DomEvent = require('lib/dom-event')
3 |
4 | module.exports = TextParam
5 |
6 | function TextParam (param, options) {
7 | options = options || {}
8 |
9 | return h('input', {
10 | 'type': 'text',
11 | 'size': options.size,
12 | 'placeholder': options.placeholder,
13 | 'value': param,
14 | 'ev-change': DomEvent(handle, param)
15 | })
16 | }
17 |
18 | function handle (event) {
19 | this.data.set(event.currentTarget.value)
20 | }
21 |
--------------------------------------------------------------------------------
/lib/render-node.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 |
3 | module.exports = function (node) {
4 | // render whatever the node is when it is passed to the renderNode function
5 | var descriptor = node()
6 | if (node && node.context && node.context.nodeInfo && descriptor) {
7 | var lookup = node.context.nodeInfo.lookup
8 | var info = lookup[descriptor.node]
9 | if (info && info.render) {
10 | return info.render(node)
11 | } else {
12 | return h('UnknownNode')
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/styles/sample-chooser.mcss:
--------------------------------------------------------------------------------
1 | SampleChooser {
2 | width: 80px;
3 | height: 22px;
4 | margin: 1px;
5 | padding: 3px;
6 |
7 | ::-webkit-file-upload-button {
8 | width: 100%;
9 | border: 1px solid #888;
10 | background: #6F6F6F;
11 | color: #D8D8D8;
12 | cursor: pointer;
13 | font-size: 90%;
14 | height: 18px;
15 |
16 | :hover {
17 | border: 1px solid #AA5;
18 | background: #993;
19 | color: #FFF;
20 | box-shadow: 0px 0px 5px #AA3;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/lib/stream-progress.js:
--------------------------------------------------------------------------------
1 | var pull = require('pull-stream')
2 | var Value = require('mutant/value')
3 |
4 | module.exports = StreamProgress
5 |
6 | function StreamProgress ({sampleRate = 44100, duration}) {
7 | var length = 0
8 | var blockSize = 32 * 2 / 8
9 | var progress = Value(0)
10 |
11 | var result = pull.through((data) => {
12 | length += data.length / blockSize
13 | progress.set(length / (duration * sampleRate))
14 | })
15 |
16 | result.value = progress
17 |
18 | return result
19 | }
20 |
--------------------------------------------------------------------------------
/styles/index.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var path = require('path')
3 | var compile = require('micro-css')
4 | var result = ''
5 | var additional = ''
6 |
7 | fs.readdirSync(__dirname).forEach(function(file){
8 | if (/\.mcss$/i.test(file)) {
9 | result += fs.readFileSync(path.resolve(__dirname, file), 'utf8') + '\n'
10 | }
11 |
12 | if (/\.css$/i.test(file)) {
13 | additional += fs.readFileSync(path.resolve(__dirname, file), 'utf8') + '\n'
14 | }
15 | })
16 |
17 | module.exports = compile(result) + additional
--------------------------------------------------------------------------------
/nodes/pan/object.js:
--------------------------------------------------------------------------------
1 | var Processor = require('lib/processor')
2 | var Param = require('lib/param')
3 | var Apply = require('lib/apply-param')
4 |
5 | module.exports = PanNode
6 |
7 | function PanNode (context) {
8 | var panner = context.audio.createStereoPanner()
9 |
10 | var releases = []
11 | var obs = Processor(context, panner, panner, {
12 | offset: Param(context, panner.pan.defaultValue)
13 | }, releases)
14 |
15 | releases.push(
16 | Apply(context.audio, panner.pan, obs.offset)
17 | )
18 |
19 | return obs
20 | }
21 |
--------------------------------------------------------------------------------
/nodes/gain/object.js:
--------------------------------------------------------------------------------
1 | var Processor = require('lib/processor')
2 | var Param = require('lib/param')
3 | var Apply = require('lib/apply-param')
4 |
5 | module.exports = GainNode
6 |
7 | function GainNode (context) {
8 | var node = context.audio.createGain()
9 | node.gain.value = 0
10 |
11 | var releases = []
12 | var obs = Processor(context, node, node, {
13 | gain: Param(context, node.gain.defaultValue)
14 | }, releases)
15 |
16 | releases.push(
17 | Apply(context.audio, node.gain, obs.gain)
18 | )
19 |
20 | return obs
21 | }
22 |
--------------------------------------------------------------------------------
/lib/quantize-to-square.js:
--------------------------------------------------------------------------------
1 | module.exports = function quantizeToSquare (value) {
2 | if (value > 16) {
3 | return Math.floor(value / 16) * 16
4 | } else if (value > 8) {
5 | return Math.floor(value / 8) * 8
6 | } else if (value > 4) {
7 | return Math.floor(value / 4) * 4
8 | } else if (value > 2) {
9 | return Math.floor(value / 2) * 2
10 | } else if (value > 1) {
11 | return Math.floor(value / 1) * 1
12 | } else if (value > 0.5) {
13 | return Math.floor(value / 0.5) * 0.5
14 | } else {
15 | return value
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/nodes/index.js:
--------------------------------------------------------------------------------
1 | var bulk = require('bulk-require')
2 | var nodes = bulk(__dirname, [ '*/index.js' ])
3 |
4 | var self = module.exports = []
5 | self.lookup = {}
6 | self.objectLookup = {}
7 | self.groupLookup = {}
8 |
9 | Object.keys(nodes).forEach(function (key) {
10 | var item = nodes[key].index
11 | self.push(item)
12 | self.lookup[item.node] = item
13 | self.objectLookup[item.node] = item.object
14 | if (item.group) {
15 | self.groupLookup[item.group] = self.groupLookup[item.group] || []
16 | self.groupLookup[item.group].push(item)
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/lib/key-collection.js:
--------------------------------------------------------------------------------
1 | var MutantMappedArray = require('mutant/mapped-array')
2 | var Value = require('mutant/value')
3 | var doubleBind = require('lib/double-bind')
4 |
5 | module.exports = KeyCollection
6 |
7 | function KeyCollection (parentContext) {
8 | var context = Object.create(parentContext)
9 |
10 | var obs = MutantMappedArray([], item => {
11 | var key = Value()
12 | key.context = context
13 | doubleBind(item, key)
14 | return key
15 | })
16 |
17 | context.collection = obs
18 | obs.context = parentContext
19 |
20 | return obs
21 | }
22 |
--------------------------------------------------------------------------------
/nodes/external-audio-input/index.js:
--------------------------------------------------------------------------------
1 | var resolve = require('mutant/resolve')
2 |
3 | module.exports = {
4 | name: 'External Audio Input',
5 | node: 'global/external-audio-input',
6 | group: 'global-controllers',
7 | object: require('./object'),
8 | render: require('./view')
9 | }
10 |
11 | module.exports.spawners = function (context) {
12 | var inputs = resolve(context.audioDevices.input)
13 | return inputs.map(device => {
14 | return {
15 | name: module.exports.name,
16 | node: module.exports.node,
17 | port: device.label
18 | }
19 | })
20 | }
21 |
--------------------------------------------------------------------------------
/lib/get-port-siblings.js:
--------------------------------------------------------------------------------
1 | module.exports = getPortSiblings
2 |
3 | function getPortSiblings (obs, controllers) {
4 | var index = null
5 | var matches = []
6 | controllers.forEach(function (c, i) {
7 | if (c.port && c.port() === obs.port()) {
8 | if (c === obs) {
9 | index = i
10 | }
11 | matches.push(i)
12 | }
13 | })
14 | return [
15 | controllers.get(matches[mod(index - 1, matches.length)]),
16 | controllers.get(matches[mod(index + 1, matches.length)])
17 | ]
18 | }
19 |
20 |
21 | function mod (n, m) {
22 | return ((n % m) + m) % m
23 | }
24 |
--------------------------------------------------------------------------------
/lib/get-closest-point.js:
--------------------------------------------------------------------------------
1 | module.exports = function getClosestPoint(markers, time) {
2 | if (markers && markers.length) {
3 | var prev = 0
4 | for (var i = 0; i < markers.length; i++) {
5 | var marker = markers[i]//-0.02
6 | if (time === marker) {
7 | return time
8 | } else if (marker > time) {
9 | var diff = marker - time
10 | var prevDiff = time - prev
11 | if (diff > prevDiff) {
12 | return prev
13 | } else {
14 | return marker
15 | }
16 | }
17 | prev = marker
18 | }
19 | }
20 | return time
21 | }
--------------------------------------------------------------------------------
/nodes/triggers-chunk/index.js:
--------------------------------------------------------------------------------
1 | var randomColor = require('lib/random-color')
2 |
3 | module.exports = {
4 | name: 'Triggers',
5 | node: 'chunk',
6 | group: 'chunks',
7 | description: 'A collection of triggerable audio slots.',
8 | spawn: {
9 | slots: [{id: 'output', node: 'slot'}],
10 | outputs: ['output']
11 | },
12 | external: function (context) {
13 | return {
14 | color: randomColor([255, 255, 255]),
15 | shape: [2, 4],
16 | minimised: true
17 | }
18 | },
19 | renderExternal: require('./external'),
20 | render: require('./view'),
21 | object: require('./object')
22 | }
23 |
--------------------------------------------------------------------------------
/lib/resolve-node.js:
--------------------------------------------------------------------------------
1 | module.exports = resolveNode
2 |
3 | function resolveNode (nodes, nodeName) {
4 | if (!nodeName) {
5 | return null
6 | }
7 |
8 | // quick lookup
9 | if (nodes[nodeName]) {
10 | return nodes[nodeName]
11 | }
12 |
13 | // walkies
14 | var node = nodes || {}
15 | while (nodeName && node) {
16 | var index = nodeName.indexOf('/')
17 | if (index < 0) {
18 | node = node[nodeName]
19 | nodeName = null
20 | } else {
21 | var key = nodeName.slice(0, index)
22 | nodeName = nodeName.slice(index + 1)
23 | node = node[key]
24 | }
25 | }
26 | return node
27 | }
28 |
--------------------------------------------------------------------------------
/lib/attribute-hook.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = AttributeHook;
4 |
5 | function AttributeHook(value) {
6 | if (!(this instanceof AttributeHook)) {
7 | return new AttributeHook(value);
8 | }
9 | this.value = value;
10 | }
11 |
12 | AttributeHook.prototype.hook = function (node, prop, prev) {
13 |
14 | node.setAttribute(prop, this.value);
15 | };
16 |
17 | AttributeHook.prototype.unhook = function (node, prop, next) {
18 | if (next && next._type === 'AttributeHook') {
19 | return;
20 | }
21 | node.removeAttribute(prop)
22 | };
23 |
24 | AttributeHook.prototype._type = 'AttributeHook';
25 |
--------------------------------------------------------------------------------
/lib/resolve-available.js:
--------------------------------------------------------------------------------
1 | module.exports = resolveAvailable
2 |
3 | function resolveAvailable (lookup, key, lastKey) {
4 | var base = key
5 | var incr = 1
6 |
7 | var numberMatch = /(.+) ([0-9]+)$/.exec(key)
8 | if (numberMatch) {
9 | base = numberMatch[1]
10 | incr = parseInt(numberMatch[2], 10)
11 | }
12 |
13 | var existing = (Array.isArray(lookup) ? lookup : Object.keys(lookup)).map(x => x.toLowerCase())
14 |
15 | while (existing.includes(key.toLowerCase()) && key.toLowerCase() !== (lastKey ? lastKey.toLowerCase() : null)) {
16 | incr += 1
17 | key = base + ' ' + incr
18 | }
19 |
20 | return key
21 | }
22 |
--------------------------------------------------------------------------------
/lib/watch-struct.js:
--------------------------------------------------------------------------------
1 | module.exports = watchStruct
2 |
3 | function watchStruct(struct, handlers){
4 | var removeListeners = Object.keys(handlers).map(watch, {struct: struct, handlers: handlers})
5 | return function unwatch(){
6 | removeListeners.forEach(invoke)
7 | removeListeners.length = 0
8 | }
9 | }
10 |
11 | function watch(key){
12 | var obs = this.struct[key]
13 | var handler = this.handlers[key]
14 | if (typeof obs === 'function' && typeof handler === 'function'){
15 | return obs(handler.bind(obs))
16 | }
17 | }
18 |
19 | function invoke(fn){
20 | if (typeof fn === 'function'){
21 | return fn()
22 | }
23 | }
--------------------------------------------------------------------------------
/nodes/loop-grid-qwerty/README.md:
--------------------------------------------------------------------------------
1 | loop-qwerty
2 | ===
3 |
4 | Qwerty keyboard bindings for [loop-grid](https://github.com/mmckegg/loop-grid).
5 |
6 | 
7 |
8 | [Watch "Using Loop Drop with a Qwerty Keyboard" on YouTube](http://youtu.be/tOpbRsDwYH4)
9 |
10 | ## Control Keys
11 |
12 | - **store**: space
13 | - **hold beat**: shift
14 | - **repeat**: 1-9 (none, 1, 2/3, 1/2, 1/3, 1/4, 1/6, 1/8, 3/4)
15 | - **suppress mode**: 0
16 | - **clear/flatten**: backspace
17 | - **undo**: -
18 | - **redo**: =
19 | - **halve length**: [
20 | - **double length**: ]
21 | - **next input**: tab / enter
22 | - **swap input**: alt (hold)
--------------------------------------------------------------------------------
/styles/toggle-chooser.mcss:
--------------------------------------------------------------------------------
1 | ToggleChooser {
2 |
3 | display: flex
4 | height: 18px
5 |
6 | span.title {
7 | margin: 3px 3px 0 0
8 | color: #CCC
9 | font-size: 90%
10 | font-weight: bold
11 | }
12 |
13 | div.choice {
14 |
15 | border: 1px solid #2A2A2A
16 | cursor: pointer
17 | padding: 1px 4px
18 | background: #3C493C
19 | border: 1px solid #2A2A2A
20 | cursor: pointer
21 | color: #9A9
22 |
23 | :hover {
24 | border-color: #3f7d55
25 | }
26 |
27 | -active {
28 | background: #3eb13e
29 | border-color: #84f55e !important
30 | color: white
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/watch-buttons.js:
--------------------------------------------------------------------------------
1 | module.exports = watchButtons
2 |
3 | function watchButtons(buttons, handlers){
4 | var removeListeners = Object.keys(handlers).map(watch, {buttons: buttons, handlers: handlers})
5 | return function unwatch(){
6 | removeListeners.forEach(invoke)
7 | removeListeners.length = 0
8 | }
9 | }
10 |
11 | function watch(key){
12 | var button = this.buttons[key]
13 | var handler = this.handlers[key]
14 | if (typeof button === 'function' && typeof handler === 'function'){
15 | return button(handler.bind(button))
16 | }
17 | }
18 |
19 | function invoke(fn){
20 | if (typeof fn === 'function'){
21 | return fn()
22 | }
23 | }
--------------------------------------------------------------------------------
/nodes/bitcrusher/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var Range = require('lib/params/range')
4 |
5 | module.exports = function renderBitcrusher (node) {
6 | return h('ProcessorNode -bitcrusher', [
7 | Header(node, h('span', 'Bitcrusher')),
8 | h('ParamList', [
9 | Range(node.bitDepth, {
10 | title: 'bit depth',
11 | defaultValue: 8,
12 | format: 'bit',
13 | flex: true
14 | }),
15 | Range(node.frequency, {
16 | title: 'freq',
17 | defaultValue: 1,
18 | format: 'sampleRatio',
19 | flex: true
20 | })
21 | ])
22 | ])
23 | }
--------------------------------------------------------------------------------
/styles/helper.mcss:
--------------------------------------------------------------------------------
1 | Helper {
2 | padding: 5px
3 | color: #777
4 | text-align: center
5 | font-size: 120%
6 |
7 | -webkit-user-select: text
8 | cursor: text
9 |
10 | -warning {
11 | header {
12 | color: #E09A2C
13 | }
14 | color: #CCC
15 | }
16 |
17 | header {
18 | font-size: 150%
19 | }
20 |
21 | a {
22 | color: #5A91D8
23 |
24 |
25 | img {
26 | transition: -webkit-filter, opacity, 1s ease
27 | -webkit-filter: saturate(5%)
28 | opacity: 0.3
29 | }
30 |
31 | :hover {
32 | img {
33 | -webkit-filter: saturate(100%)
34 | opacity: 1
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/nodes/offset/object.js:
--------------------------------------------------------------------------------
1 | var Param = require('lib/param')
2 | var ObservStruct = require('mutant/struct')
3 | var Sum = require('lib/param-sum')
4 |
5 | module.exports = OffsetModulator
6 |
7 | function OffsetModulator (context) {
8 | var obs = ObservStruct({
9 | offset: Param(context, 0),
10 | value: Param(context, 0)
11 | })
12 |
13 | obs.context = context
14 |
15 | obs.currentValue = Sum([
16 | obs.value.currentValue, obs.offset.currentValue
17 | ])
18 |
19 | obs.triggerOn = function (at) {
20 | Param.triggerOn(obs, at)
21 | }
22 |
23 | obs.triggerOff = function (at) {
24 | Param.triggerOff(obs, at)
25 | }
26 |
27 | return obs
28 | }
29 |
--------------------------------------------------------------------------------
/lib/double-bind.js:
--------------------------------------------------------------------------------
1 | module.exports = function doubleBind (a, b) {
2 | var updatingA = false
3 | var updatingB = false
4 |
5 | // initial sync
6 | b.set(a())
7 | a.set(b())
8 |
9 | var releaseA = a(function (value) {
10 | if (!updatingA && !updatingB) {
11 | updatingA = true
12 | b.set(value)
13 | updatingA = false
14 | }
15 | })
16 |
17 | var releaseB = b(function (value) {
18 | if (!updatingA && !updatingB) {
19 | updatingB = true
20 | a.set(value)
21 | updatingB = false
22 | }
23 | })
24 | return function release () {
25 | releaseA()
26 | releaseB()
27 | releaseA = releaseB = null
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/grid-slice-peaks.js:
--------------------------------------------------------------------------------
1 | var getPeaks = require('lib/get-peaks')
2 |
3 | module.exports = function (data, count, offset, cb) {
4 | offset = offset || [0, 1]
5 |
6 | var range = (offset[1] - offset[0]) * data.length
7 | var windowSize = Math.ceil(range / count)
8 | var start = Math.floor(offset[0] * data.length)
9 | var end = Math.floor(offset[1] * data.length)
10 | var searchStart = Math.floor(start + (windowSize / 2))
11 |
12 | getPeaks({data, windowSize, start: searchStart, end}, (err, peaks) => {
13 | if (err) return cb(err)
14 | cb(null, [[start, 1]].concat(peaks).map(function (item) {
15 | return item[0] / data.length
16 | }))
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/lib/interpolate.js:
--------------------------------------------------------------------------------
1 | module.exports = interpolate
2 |
3 | function interpolate(event, time){
4 | var to = event.at + (event.duration||0)
5 | if (time < event.at){
6 | return event.fromValue
7 | } else if (event.duration && time <= to){
8 | var range = event.value - event.fromValue
9 | var pos = (time - event.at) / event.duration
10 | if (event.mode === 'exp'){
11 | return event.fromValue + (range * (Math.pow(pos, 2)))
12 | } else if (event.mode === 'log'){
13 | return event.fromValue + (range * (Math.pow(pos, 1/4)))
14 | } else {
15 | return event.fromValue + (range * pos)
16 | }
17 | } else {
18 | return event.value
19 | }
20 | }
--------------------------------------------------------------------------------
/lib/update-param-references.js:
--------------------------------------------------------------------------------
1 | module.exports = updateParamReferences
2 |
3 | function updateParamReferences (node, oldId, newId) {
4 | var changed = false
5 | var result = JSON.stringify(node(), function (key, value) {
6 | if (value && value.node === 'linkParam' && value.param === oldId) {
7 | value = obtain(value)
8 | changed = true
9 | if (newId) {
10 | value.param = newId
11 | } else {
12 | return value.minValue
13 | }
14 | }
15 | return value
16 | })
17 |
18 | if (changed) {
19 | node.set(JSON.parse(result))
20 | }
21 | }
22 |
23 | function obtain (obj) {
24 | return JSON.parse(JSON.stringify(obj))
25 | }
26 |
--------------------------------------------------------------------------------
/lib/value-at-time-getter.js:
--------------------------------------------------------------------------------
1 | var resolve = require('mutant/resolve')
2 | var ParamSource = require('lib/param-source')
3 |
4 | module.exports = ValueAtTimeGetter
5 |
6 | function ValueAtTimeGetter (nodes, numberResult, reducer) {
7 | return function getValueAtTime (time) {
8 | var values = resolve(nodes).map(x => x.getValueAtTime && x.getValueAtTime(time) || 0)
9 | var numberValue = resolve(numberResult)
10 | if (ParamSource.isParam(numberValue)) {
11 | numberValue = numberValue.getValueAtTime(time)
12 | }
13 | if (typeof numberValue === 'number') {
14 | values = values.concat(numberValue)
15 | }
16 | return reducer(values)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/nodes/bitcrusher/object.js:
--------------------------------------------------------------------------------
1 | var Processor = require('lib/processor')
2 | var Property = require('lib/property')
3 | var Bitcrusher = require('bitcrusher')
4 | var watch = require('mutant/watch')
5 |
6 | module.exports = BitcrusherNode
7 |
8 | function BitcrusherNode (context) {
9 | var node = Bitcrusher(context.audio, { bufferSize: 256 })
10 |
11 | var obs = Processor(context, node, node, {
12 | bitDepth: Property(8),
13 | frequency: Property(1)
14 | })
15 |
16 | watch(obs.bitDepth, function (value) {
17 | node.bitDepth = value
18 | })
19 |
20 | watch(obs.frequency, function (value) {
21 | node.frequency = value
22 | })
23 |
24 | return obs
25 | }
26 |
--------------------------------------------------------------------------------
/lib/to-pcm.js:
--------------------------------------------------------------------------------
1 | var CallbackWorker = require('lib/callback-worker')
2 |
3 | module.exports = function (data, cb) {
4 | toPcm(data, (err, result) => {
5 | if (err) return cb(err)
6 | cb(null, Buffer.from(result.buffer))
7 | })
8 | }
9 |
10 | var toPcm = CallbackWorker(function (channelData, cb) {
11 | var channelCount = channelData.length
12 | var sampleCount = channelData[0].length
13 |
14 | var result = new Float32Array(channelCount * sampleCount)
15 |
16 | for (var i = 0; i < sampleCount; i++) {
17 | for (var c = 0; c < channelCount; c++) {
18 | result[i * channelCount + c] = channelData[c][i]
19 | }
20 | }
21 |
22 | cb(null, result, [result.buffer])
23 | })
24 |
--------------------------------------------------------------------------------
/lib/get-sound-offset.js:
--------------------------------------------------------------------------------
1 | module.exports = function getSoundOffset(buffer){
2 |
3 | if (!buffer) return
4 |
5 | var threshold = 0.01
6 |
7 | var data = buffer.getChannelData(0)
8 | var step = 32
9 | var width = buffer.length / step
10 |
11 | for(var i=0;i max){
20 | max = datum
21 | }
22 | }
23 |
24 | if (Math.max(Math.abs(min), Math.abs(max)) > threshold){
25 | var value = (i*step) / buffer.length
26 | return [value, 1]
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/nodes/multiply/object.js:
--------------------------------------------------------------------------------
1 | var Param = require('lib/param')
2 | var ObservStruct = require('mutant/struct')
3 | var Multiply = require('lib/param-multiply')
4 |
5 | module.exports = MultiplyModulator
6 |
7 | function MultiplyModulator (context) {
8 | var obs = ObservStruct({
9 | multiplier: Param(context, 0),
10 | value: Param(context, 0)
11 | })
12 |
13 | obs.context = context
14 |
15 | obs.currentValue = Multiply([
16 | obs.value.currentValue, obs.multiplier.currentValue
17 | ])
18 |
19 | obs.triggerOn = function (at) {
20 | Param.triggerOn(obs, at)
21 | }
22 |
23 | obs.triggerOff = function (at) {
24 | Param.triggerOff(obs, at)
25 | }
26 |
27 | return obs
28 | }
29 |
--------------------------------------------------------------------------------
/lib/with-resolved.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var extend = require('xtend')
3 |
4 | module.exports = withResolved
5 |
6 | function withResolved (obj, keys) {
7 | var result = computed(keys.map(function (k) { return obj[k] }).concat(obj), function (args) {
8 | var resolvedValues = Array.from(arguments).slice(0, -1)
9 | var value = extend(arguments[arguments.length - 1])
10 | keys.forEach(function (key, i) {
11 | value[key] = resolvedValues[i]
12 | })
13 | return value
14 | })
15 |
16 | for (var k in obj) {
17 | if (k !== 'set' && k !== 'destroy') {
18 | result[k] = obj[k]
19 | }
20 | }
21 |
22 | result.node = obj
23 | return result
24 | }
25 |
--------------------------------------------------------------------------------
/lib/destroy-source-node.js:
--------------------------------------------------------------------------------
1 | var destroyerCache = new WeakMap()
2 |
3 | module.exports = destroySourceNode
4 |
5 | function destroySourceNode (node) {
6 | node.disconnect()
7 |
8 | if (node.stop) {
9 | var destroyer = destroyerCache.get(node.context)
10 |
11 | if (!destroyer) {
12 | destroyer = node.context.createGain()
13 | destroyer.gain.value = 0
14 | destroyer.connect(node.context.destination)
15 | destroyerCache.set(node.context, destroyer)
16 | }
17 |
18 | // HACK: collect disconnected nodes to ensure they stop correctly
19 | // see https://bugs.chromium.org/p/chromium/issues/detail?id=717528
20 | node.connect(destroyer)
21 | node.stop()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/nodes/loop-grid-launchpad/state-lights.js:
--------------------------------------------------------------------------------
1 | var self = module.exports = function(r, g, flag){
2 | if (!r || r < 0) r = 0
3 | if (r > 3) r = 3
4 | if (!g || g < 0) g = 0
5 | if (g > 3) g = 3
6 | if (flag == 'flash') {
7 | flag = 8
8 | } else if (flag == 'buffer') {
9 | flag = 0
10 | } else {
11 | flag = 12
12 | }
13 |
14 | return ((16 * g) + r) + flag
15 | }
16 |
17 | self.off = 0
18 | self.greenLow = self(0,1)
19 | self.greenMed = self(0,2)
20 | self.green = self(0,3)
21 | self.greenFlash = self(0,3, 'flash')
22 | self.redLow = self(1,0)
23 | self.redMed = self(2,0)
24 | self.red = self(3,0)
25 | self.amberLow = self(1,1)
26 | self.amber = self(3,3)
27 | self.yellow = self(1,3)
--------------------------------------------------------------------------------
/styles/audio-meter.mcss:
--------------------------------------------------------------------------------
1 | AudioMeter {
2 | -webkit-transform: translate3d(0,0,0)
3 | margin: 3px;
4 |
5 | div {
6 | display: flex;
7 | margin: 1px;
8 | height: 6px;
9 | border-radius: 3px;
10 | overflow: hidden;
11 | border: 1px solid #222
12 |
13 | div {
14 | flex: 1
15 | border-radius: 1px
16 | background: #040
17 |
18 | -amber {
19 | background: #440
20 | -active {
21 | background: #FF0
22 | }
23 | }
24 |
25 | -red {
26 | background: #400
27 | -active {
28 | background: #F00
29 | }
30 | }
31 |
32 | -active {
33 | background: #0F0
34 | }
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/lib/params/sample-chooser.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var FileEvent = require('lib/file-event')
3 | var QueryParam = require('lib/query-param')
4 |
5 | var importSample = require('lib/import-sample')
6 |
7 | module.exports = SampleChooser
8 |
9 | function SampleChooser(node, opts){
10 | return h('input SampleChooser', {
11 | type: 'file',
12 | accept: 'audio/*,video/*',
13 | 'ev-change': FileEvent(handleChange, node)
14 | })
15 | }
16 |
17 | function handleChange(file){
18 | var node = this.data
19 | var context = this.data.context
20 |
21 | importSample(context, file.path, function(err, descriptor){
22 | for (var k in descriptor){
23 | QueryParam(node, k).set(descriptor[k])
24 | }
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/nodes/pan/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var ModRange = require('lib/params/mod-range')
4 | var ToggleButton = require('lib/params/toggle-button')
5 | var QueryParam = require('lib/query-param')
6 |
7 | module.exports = function renderPanNode (node) {
8 | return h('ProcessorNode -pan', [
9 | Header(node, h('span', 'Pan')),
10 | h('ParamList', [
11 | ToggleButton(QueryParam(node, 'node'), {
12 | title: 'Spatial',
13 | onValue: 'processor/spatial-pan',
14 | offValue: 'processor/pan'
15 | }),
16 | ModRange(node.offset, {
17 | defaultValue: 0,
18 | format: 'pan',
19 | flex: true
20 | })
21 | ])
22 | ])
23 | }
24 |
--------------------------------------------------------------------------------
/lib/context-menu.js:
--------------------------------------------------------------------------------
1 | var electron = require('electron')
2 | var Menu = electron.remote.Menu
3 | var MenuItem = electron.remote.MenuItem
4 | var BrowserWindow = electron.remote.BrowserWindow
5 |
6 | var contextEvent = null
7 | var menu = new Menu()
8 | menu.append(new MenuItem({
9 | label: 'Inspect Element',
10 | click: function() {
11 | var x = Math.round(contextEvent.clientX * window.rootContext.zoom())
12 | var y = Math.round(contextEvent.clientY * window.rootContext.zoom())
13 | BrowserWindow.getFocusedWindow().inspectElement(x, y)
14 | }
15 | }))
16 |
17 | window.addEventListener('contextmenu', function (e) {
18 | e.preventDefault();
19 | contextEvent = e
20 | menu.popup(electron.remote.getCurrentWindow())
21 | }, false)
22 |
--------------------------------------------------------------------------------
/nodes/dipper/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var ModRange = require('lib/params/mod-range')
4 | var Select = require('lib/params/select')
5 |
6 | var modeChoices = [
7 | ['Modulate', 'modulate'],
8 | ['Source', 'source']
9 | ]
10 |
11 | module.exports = function renderDipper (node) {
12 | return h('ProcessorNode -dipper', [
13 | Header(node, h('span', 'Dipper')),
14 | h('ParamList', [
15 | Select(node.mode, {
16 | defaultValue: 'modulate',
17 | options: modeChoices
18 | }),
19 |
20 | ModRange(node.ratio, {
21 | title: 'ratio',
22 | defaultValue: 1,
23 | format: 'ratio',
24 | flex: true
25 | })
26 | ])
27 | ])
28 | }
29 |
--------------------------------------------------------------------------------
/nodes/synth-chunk/index.js:
--------------------------------------------------------------------------------
1 | var randomColor = require('lib/random-color')
2 |
3 | module.exports = {
4 | name: 'Synth',
5 | node: 'chunk/synth',
6 | description: 'Basic subtractive synthesier using global scale specified.',
7 | group: 'simpleChunks',
8 | spawn: function () {
9 | return {
10 | color: randomColor(),
11 | osc1: {
12 | shape: 'sawtooth',
13 | amp: 0.4
14 | },
15 | amp: {
16 | node: 'modulator/adsr',
17 | value: 1,
18 | attack: 0.1,
19 | release: 0.5
20 | },
21 | filter: {
22 | type: "lowpass",
23 | frequency: 5400,
24 | Q: 1
25 | }
26 | }
27 | },
28 | object: require('./object'),
29 | render: require('./view')
30 | }
31 |
--------------------------------------------------------------------------------
/lib/map-watch-diff-stack.js:
--------------------------------------------------------------------------------
1 | var watch = require('mutant/watch')
2 | module.exports = function(values, obs, handler){
3 | var stack = []
4 | var lastValue = undefined
5 | return watch(obs, function(data){
6 | data._diff&&Object.keys(data._diff).forEach(function(key){
7 | var value = values[key]
8 | if (data._diff[key]){
9 | stack.push(value)
10 | } else {
11 | remove(stack, value)
12 | }
13 | })
14 |
15 | var top = stack[stack.length - 1]
16 | if (top !== lastValue) {
17 | if (top) {
18 | handler(top)
19 | }
20 | lastValue = top
21 | }
22 | })
23 | }
24 |
25 | function remove(array, item){
26 | var index = array.indexOf(item)
27 | if (~index){
28 | array.splice(index, 1)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/styles/value-slots.mcss:
--------------------------------------------------------------------------------
1 | ValueSlots {
2 | padding: 3px
3 |
4 | div.slot {
5 |
6 | -trigger {
7 | display: flex;
8 | padding: 3px;
9 | background: #555;
10 | border-radius: 3px;
11 | margin: 2px;
12 |
13 | span {
14 | display: block
15 | }
16 | }
17 |
18 | -spawn {
19 | padding: 3px;
20 | background: #888;
21 | border-radius: 3px;
22 | border: 1px solid #2A2A2A;
23 | cursor: pointer;
24 | width: 100%;
25 | position: relative;
26 | text-align: center;
27 | opacity: 0.5
28 | color: white
29 |
30 | :hover {
31 | opacity: 1
32 | color: #AFA
33 | background-color: #383
34 | border-color: #AFA
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/nodes/noise/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 |
4 | var Range = require('lib/params/range')
5 | var ModRange = require('lib/params/mod-range')
6 | var Select = require('lib/params/select')
7 |
8 | var types = [
9 | ['White', 'white'],
10 | ['Pink', 'pink']
11 | ]
12 |
13 | module.exports = function renderNoise (node) {
14 | return h('SourceNode -noise', [
15 | Header(node, h('span', [
16 | h('strong', 'Noise:'), ' ',
17 | h('span', node.type)
18 | ])),
19 | h('ParamList', [
20 | Select(node.type, {
21 | options: types
22 | }),
23 | ModRange(node.amp, {
24 | title: 'amp',
25 | defaultValue: 1,
26 | format: 'dB',
27 | flex: true
28 | })
29 | ])
30 | ])
31 | }
32 |
--------------------------------------------------------------------------------
/styles/button.mcss:
--------------------------------------------------------------------------------
1 | Button {
2 | cursor: pointer;
3 | border: none;
4 | border-radius: 5px;
5 | font-size: 12px;
6 | font-size: 10px;
7 | padding: 0 4px;
8 |
9 | background: #3A3A3A;
10 | color: #C3C3C3;
11 |
12 | :hover {
13 | background: #202020;
14 | color: #FFF;
15 | }
16 |
17 | :focus {
18 | outline-style: none;
19 | color: white;
20 | box-shadow: white 0 0 4px;
21 | }
22 |
23 | -main {
24 | padding: 5px
25 | height: auto
26 | }
27 |
28 | -spawn {
29 | :hover {
30 | background: #467545
31 | }
32 | }
33 |
34 | -warn {
35 | :hover {
36 | background: #A00
37 | }
38 | }
39 |
40 | -edit {
41 | border: 1px solid #666;
42 | padding: 0 6px;
43 | background: #3A3A3A;
44 | color: #DDD;
45 | }
46 | }
--------------------------------------------------------------------------------
/lib/widgets/params.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var map = require('mutant/map')
3 | var ModRange = require('lib/params/mod-range')
4 | var computed = require('mutant/computed')
5 |
6 | module.exports = renderParams
7 |
8 | function renderParams (node) {
9 | var paramNames = computed([node.params], (params) => params)
10 | return map(paramNames, function (paramName) {
11 | var param = node.paramValues.get(paramName)
12 | if (!param) {
13 | // ensure that it exists for backwards compat with old setups
14 | param = node.paramValues.put(paramName, 0)
15 | }
16 | return h('ParamList', [
17 | ModRange(param, {
18 | title: paramName,
19 | format: 'ratio1',
20 | flex: true,
21 | allowSpawnModulator: true
22 | })
23 | ])
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/lib/json-file.js:
--------------------------------------------------------------------------------
1 | var watch = require('mutant/watch')
2 |
3 | var PARSE_ERROR = {}
4 |
5 | module.exports = JsonFile
6 |
7 | function JsonFile (file, listener) {
8 | var lastSaved = null
9 |
10 | watch(file, function (data) {
11 | if (lastSaved !== data) {
12 | var parsed = tryParse(data)
13 | if (parsed !== PARSE_ERROR) {
14 | lastSaved = data
15 | data = parsed
16 | listener(data)
17 | }
18 | }
19 | })
20 |
21 | return function save (value) {
22 | var data = JSON.stringify(value, null, 2)
23 | if (lastSaved !== data) {
24 | lastSaved = data
25 | file.set(data)
26 | }
27 | }
28 | }
29 |
30 | function tryParse (data) {
31 | try {
32 | return JSON.parse(data)
33 | } catch (ex) {
34 | return PARSE_ERROR
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/nodes/link-modulator/object.js:
--------------------------------------------------------------------------------
1 | var Observ = require('mutant/value')
2 | var ObservStruct = require('mutant/struct')
3 | var computed = require('mutant/computed')
4 |
5 | var Param = require('lib/param')
6 | var Sum = require('lib/param-sum')
7 |
8 | module.exports = ParamModulator
9 |
10 | function ParamModulator (context) {
11 | var obs = ObservStruct({
12 | param: Observ(),
13 | value: Param(context, 0)
14 | })
15 |
16 | obs._type = 'ParamModulator'
17 |
18 | obs.context = context
19 |
20 | var offsetValue = computed([obs.param, context.paramLookup], function (param) {
21 | return context.paramLookup.get(param)
22 | })
23 |
24 | obs.currentValue = Sum([obs.value, offsetValue])
25 |
26 | obs.destroy = function () {
27 | Param.destroy(obs)
28 | }
29 |
30 | return obs
31 | }
32 |
--------------------------------------------------------------------------------
/demo-project/DWS - OST/cspan-output.json:
--------------------------------------------------------------------------------
1 | {"slots":[{"id":"output","node":"slot","volume":0.4837,"noteOffset":0,"processors":[{"node":"processor/reverb","time":0.752,"decay":2,"reverse":false,"cutoff":20000,"filterType":"lowpass","wet":1,"dry":1},{"node":"processor/overdrive","preBand":1.3341739535648693,"color":180,"postCut":{"node":"linkParam","minValue":5700,"maxValue":20,"param":"post cut","mode":"exp"},"gain":4.8752,"amp":1},{"node":"processor/ping-pong-delay","time":0.214,"sync":false,"feedback":0.6,"cutoff":20000,"filterType":"lowpass","wet":1,"dry":1},{"node":"processor/filter","frequency":{"node":"linkParam","minValue":27,"maxValue":20000,"param":"freq","mode":"exp"},"Q":1,"gain":0,"type":"highpass"}]}],"inputs":["input"],"outputs":["output"],"params":["post cut","freq","wet"],"selectedSlotId":"output","node":"chunk/meddler","scale":"$global"}
--------------------------------------------------------------------------------
/lib/widgets/midi-output.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Select = require('lib/params/select')
3 | var channelOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].map(number => [`Channel ${number}`, number])
4 | var Range = require('lib/params/range')
5 |
6 | module.exports = function (node) {
7 | return h('ParamList', [
8 | Select(node.outputMidiPort, {
9 | options: node.context.midiPorts.output,
10 | flex: true,
11 | missingPrefix: ' (disconnected)',
12 | includeBlank: 'No Midi Device'
13 | }),
14 | Select(node.outputMidiChannel, {
15 | options: channelOptions,
16 | flex: true
17 | }),
18 | Range(node.outputMidiTriggerOffset, {
19 | title: 'offset',
20 | defaultValue: 0,
21 | format: 'syncMs',
22 | flex: 'small'
23 | })
24 | ])
25 | }
26 |
--------------------------------------------------------------------------------
/styles/controller-node.mcss:
--------------------------------------------------------------------------------
1 | ControllerNode {
2 | position: relative
3 | margin-top: 6px
4 | background: #464545;
5 | border: 1px #444 solid;
6 | color: #EEE;
7 | border-radius: 4px;
8 | width: 260px;
9 | overflow: hidden
10 |
11 | header {
12 | display: flex
13 | padding: 5px 5px
14 |
15 | span {
16 | flex: 1
17 | }
18 |
19 | button.remove {
20 | opacity: 0.2
21 | margin: -1px 0
22 | }
23 |
24 | :hover {
25 | button.remove {
26 | opacity: 1
27 | }
28 | }
29 | }
30 |
31 | -input {
32 | border-color: #6D9466
33 | header {
34 | background-color: #4B5645
35 | }
36 | }
37 |
38 | section {
39 |
40 | display: flex
41 | flex-direction: column
42 |
43 | div.controls {
44 | flex: 1
45 | }
46 |
47 | }
48 |
49 |
50 | }
--------------------------------------------------------------------------------
/nodes/cymbal/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var ModRange = require('lib/params/mod-range')
4 |
5 | module.exports = function renderDrumSynth (node) {
6 | return h('SourceNode -drumSynth', [
7 | Header(node, h('span', [
8 | h('strong', 'Drum Synth:'), ' Cymbal'
9 | ])),
10 | h('ParamList', [
11 | ModRange(node.amp, {
12 | title: 'amp',
13 | defaultValue: 1,
14 | format: 'dB',
15 | flex: true
16 | }),
17 | ModRange(node.decay, {
18 | title: 'decay',
19 | defaultValue: 0.3,
20 | format: 'ms',
21 | flex: true
22 | }),
23 | ModRange(node.tune, {
24 | title: 'tune',
25 | defaultValue: 0,
26 | format: 'cents+',
27 | flex: true
28 | })
29 | ])
30 | ])
31 | }
32 |
--------------------------------------------------------------------------------
/nodes/pitchshift/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var Range = require('lib/params/range')
4 | var ModRange = require('lib/params/mod-range')
5 |
6 | module.exports = function renderPitchshift (node) {
7 | return h('ProcessorNode -pitchshift', [
8 | Header(node, h('span', 'Pitchshift')),
9 | h('ParamList', [
10 | Range(node.transpose, {
11 | title: 'transpose',
12 | defaultValue: 12,
13 | format: 'semitoneUp',
14 | flex: true
15 | }),
16 | ModRange(node.wet, {
17 | title: 'wet',
18 | defaultValue: 1,
19 | format: 'dB',
20 | flex: true
21 | }),
22 | ModRange(node.dry, {
23 | title: 'dry',
24 | defaultValue: 0,
25 | format: 'dB',
26 | flex: true
27 | })
28 | ])
29 | ])
30 | }
--------------------------------------------------------------------------------
/lib/index-param.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 |
3 | module.exports = IndexParam
4 |
5 | function IndexParam (param, index, formatter) {
6 | var result = computed([param], function (value) {
7 | var val = Array.isArray(value) ? value : []
8 | return val[index]
9 | })
10 |
11 | result.set = function (value) {
12 | var current = read(param)
13 | var val = Array.isArray(current) ? current : []
14 | var res = val.slice()
15 | if (typeof formatter === 'function') {
16 | value = formatter(value)
17 | }
18 | res[index] = value
19 | param.set(res)
20 | }
21 |
22 | result.context = param.context
23 | return result
24 | }
25 |
26 | function read (target) {
27 | if (typeof target === 'function') {
28 | return target()
29 | } else if (target && target.read) {
30 | return target.read()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/nodes/midi-out/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var ModRange = require('lib/params/mod-range')
4 |
5 | module.exports = function renderMidiOut (node) {
6 | return h('SourceNode -midiOut', [
7 | Header(node, h('span', [
8 | h('strong', 'MIDI Note Output')
9 | ])),
10 | h('ParamList', [
11 | ModRange(node.note, {
12 | title: 'pitch',
13 | format: 'midi',
14 | defaultValue: 69,
15 | flex: true
16 | }),
17 | ModRange(node.velocity, {
18 | title: 'velocity',
19 | defaultValue: 100,
20 | format: 'midi',
21 | flex: true
22 | }),
23 | ModRange(node.aftertouch, {
24 | title: 'aftertouch',
25 | defaultValue: 0,
26 | format: 'midi',
27 | flex: true
28 | })
29 | ])
30 | ])
31 | }
32 |
--------------------------------------------------------------------------------
/lib/flash-array.js:
--------------------------------------------------------------------------------
1 | var Observ = require('mutant/value')
2 |
3 | module.exports = FlashArray
4 |
5 | function FlashArray () {
6 | var obs = Observ([])
7 | var active = []
8 | var refreshing = false
9 |
10 | obs.flash = function(i, value, duration) {
11 |
12 | var obj = [i, value]
13 | active.push(obj)
14 | refresh()
15 |
16 | setTimeout(function() {
17 | active.splice(active.indexOf(obj), 1)
18 | refresh()
19 | }, duration)
20 |
21 | }
22 |
23 | return obs
24 |
25 | function refresh () {
26 | if (!refreshing) {
27 | refreshing = true
28 | window.requestAnimationFrame(refreshNow)
29 | }
30 | }
31 |
32 | function refreshNow() {
33 | obs.set(active.reduce(flatten, []))
34 | refreshing = false
35 | }
36 | }
37 |
38 | function flatten(result, obj) {
39 | result[obj[0]] = obj[1]
40 | return result
41 | }
--------------------------------------------------------------------------------
/lib/mouse-position-event.js:
--------------------------------------------------------------------------------
1 | module.exports = function (fn, data, opts) {
2 | var handler = {
3 | fn: fn,
4 | data: data || {},
5 | opts: opts || {},
6 | handleEvent: handle
7 | }
8 | return handler
9 | }
10 |
11 | function handle (ev) {
12 | ev.stopPropagation()
13 | var box = ev.currentTarget.getBoundingClientRect()
14 | this.fn({
15 | x: ev.clientX,
16 | y: ev.clientY,
17 | offsetWidth: ev.currentTarget.offsetWidth,
18 | offsetHeight: ev.currentTarget.offsetHeight,
19 | offsetX: ev.clientX - box.left,
20 | offsetY: ev.clientY - box.top,
21 | dataTransfer: ev.dataTransfer,
22 | currentTarget: ev.currentTarget,
23 |
24 | ctrlKey: ev.ctrlKey,
25 | shiftKey: ev.shiftKey,
26 | altKey: ev.altKey,
27 | metaKey: ev.metaKey,
28 |
29 | event: ev,
30 | target: ev.target,
31 | data: this.data
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/nodes/oscillator/shape-choices.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | ['Basic Shapes', [
3 | ['Sine', 'sine'],
4 | ['Square', 'square'],
5 | ['Pulse', 'pulse'], // spawns source/oscillator-pulse
6 | ['Sawtooth', 'sawtooth'],
7 | ['Triangle', 'triangle']
8 | ]],
9 | ['Wave Tables', [
10 | ['Bass', 'bass'],
11 | ['Bass Fuzz', 'bass-fuzz'],
12 | ['Bass Sub', 'bass-sub'],
13 | ['Brass', 'brass'],
14 | ['Disonant', 'disonant'],
15 | ['Guitar Fuzz', 'guitar-fuzz'],
16 | ['Organ', 'organ'],
17 | ['Piano', 'piano'],
18 | ['Soft Saw', 'soft-saw'],
19 | ['Strings', 'strings'],
20 | ['Throaty', 'throaty'],
21 | ['Trombone', 'trombone'],
22 | ['Wide', 'wide'],
23 | ['Wurlitzer', 'wurlitzer']
24 | ]],
25 | ['Phonemes', [
26 | ['Ah', 'ah'],
27 | ['Ee', 'ee'],
28 | ['Ow', 'ow'],
29 | ['Ooh', 'ooh']
30 | ]]
31 | ]
32 |
--------------------------------------------------------------------------------
/nodes/external-chunk/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var send = require('mutant/send')
3 | var computed = require('mutant/computed')
4 |
5 | module.exports = function renderExternal (node) {
6 | var lookup = node.context.nodeInfo.lookup
7 | return computed(node.nodeName, function (nodeName) {
8 | var info = lookup[nodeName]
9 | if (info && info.renderExternal) {
10 | return info.renderExternal(node)
11 | } else {
12 | var collection = node.context.collection
13 | return h('div ExternalNode', {
14 | style: {
15 | border: '2px solid transparent'
16 | }
17 | }, [
18 | h('header', [
19 | h('span', [node.id, ' (external)']),
20 | h('button.remove Button -warn', {
21 | 'ev-click': send(collection.remove, node)
22 | }, 'X')
23 | ])
24 | ])
25 | }
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/lib/param-from-number.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var Apply = require('lib/apply-param')
3 | var destroySourceNode = require('lib/destroy-source-node')
4 |
5 | module.exports = function NumberParam (audioContext, value) {
6 | var releases = []
7 | var result = audioContext.createGain()
8 | var target = result.gain
9 |
10 | return computed([value], function (value) {
11 | return result
12 | }, {
13 | comparer: (a, b) => a === b,
14 | onListen: function () {
15 | var voltage = audioContext.createConstantSource()
16 | voltage.start()
17 | voltage.connect(result)
18 | releases.push(
19 | Apply(audioContext, target, value),
20 | () => destroySourceNode(voltage)
21 | )
22 | },
23 | onUnlisten: function () {
24 | while (releases.length) {
25 | releases.pop()()
26 | }
27 | }
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/lib/on-trigger.js:
--------------------------------------------------------------------------------
1 | var Event = require('geval')
2 | var watch = require('mutant/watch')
3 |
4 | module.exports = function (items) {
5 | var releases = []
6 | var unwatch = null
7 |
8 | var result = Event(function (broadcast) {
9 | unwatch = watch(items, rebind)
10 | function rebind () {
11 | release()
12 | items.forEach(function (item, i) {
13 | if (item && item.node && item.node.onTrigger) {
14 | releases.push(item.node.onTrigger(function (data) {
15 | if (data.event === 'start') {
16 | broadcast(i)
17 | }
18 | }))
19 | }
20 | })
21 | }
22 | })
23 |
24 | result.destroy = function () {
25 | release()
26 | unwatch()
27 | }
28 |
29 | return result
30 | // scoped
31 |
32 | function release () {
33 | while (releases.length) {
34 | releases.pop()()
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/sustained.js:
--------------------------------------------------------------------------------
1 | var watch = require('mutant/watch')
2 | var computed = require('mutant/computed')
3 | var Value = require('mutant/value')
4 |
5 | module.exports = Sustained
6 |
7 | // only broadcast value changes once a truthy value has stayed constant for more than timeThreshold
8 |
9 | function Sustained (obs, timeThreshold, checkUpdateImmediately) {
10 | var outputValue = Value(obs())
11 |
12 | return computed(outputValue, v => v, {
13 | onListen: () => watch(obs, onChange)
14 | })
15 |
16 | function onChange (value) {
17 | if (checkUpdateImmediately && checkUpdateImmediately(value)) { // update immediately for falsy values
18 | clearTimeout()
19 | update()
20 | } else if (value !== outputValue()) {
21 | clearTimeout()
22 | setTimeout(update, timeThreshold)
23 | }
24 | }
25 |
26 | function update () {
27 | outputValue.set(obs())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/nodes/loop-grid/compute-targets.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var ArrayGrid = require('array-grid')
3 | var DictToCollection = require('mutant/dict-to-collection')
4 | var Lookup = require('mutant/lookup')
5 |
6 | module.exports = function computeTargets (chunkLookup, positions, shape) {
7 | var chunkGrids = Lookup(DictToCollection(chunkLookup), function (pair) {
8 | return [pair.key, pair.value.resolvedGrid]
9 | })
10 |
11 | return computed([chunkGrids, positions, shape], function (chunkGrids, positions, shape) {
12 | var grid = ArrayGrid([], shape)
13 | if (chunkGrids && positions) {
14 | Object.keys(positions).forEach(function (id) {
15 | var origin = positions[id]
16 | if (chunkGrids[id] && Array.isArray(origin)) {
17 | grid.place(origin[0], origin[1], chunkGrids[id])
18 | }
19 | })
20 | }
21 | return grid.data
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/nodes/ring-modulator/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var ModRange = require('lib/params/mod-range')
4 |
5 | var Select = require('lib/params/select')
6 | var shapeChoices = require('../oscillator/shape-choices')
7 |
8 | module.exports = function renderOscillator (node) {
9 | return h('ProcessorNode -ringModulator', [
10 | Header(node, h('span', [
11 | 'Ring Modulator'
12 | ])),
13 | h('ParamList', [
14 | Select(node.carrier.shape, {
15 | options: shapeChoices
16 | }),
17 | ModRange(node.carrier.amp, {
18 | title: 'amp',
19 | defaultValue: 1,
20 | format: 'dB',
21 | flex: true
22 | }),
23 | ModRange(node.carrier.frequency, {
24 | title: 'frequency',
25 | format: 'arfo',
26 | flex: true,
27 | defaultValue: 440
28 | })
29 | ])
30 | ])
31 | }
32 |
--------------------------------------------------------------------------------
/scripts/export-recording.js:
--------------------------------------------------------------------------------
1 | var Path = require('path')
2 | var fs = require('fs')
3 | var spawn = require('child_process').spawn
4 |
5 | var input = process.argv[2]
6 | var output = process.argv[3]
7 |
8 | if (!input || !output) {
9 | throw new Error('Must specify input.json and output file')
10 | }
11 |
12 | var dir = Path.dirname(input)
13 | var base = Path.basename(input, '.json')
14 |
15 | fs.readdir(dir, function (err, files) {
16 | if (err) throw err
17 | files = files.filter(f => matches(base, f))
18 |
19 | fs.writeFileSync(output + '.txt', files.map(x => `file '${Path.join(dir, x)}'`).join('\n'))
20 | var args = ['-f', 'concat', '-safe', '0', '-i', output + '.txt', '-af', 'volume=0.8', output]
21 | spawn('ffmpeg', args, {stdio: 'inherit'})
22 | })
23 |
24 | function matches (base, fileName) {
25 | var match = fileName.match(/^(.+)-([0-9]+)\.wav$/)
26 | return (match && match[1] === base)
27 | }
28 |
--------------------------------------------------------------------------------
/lib/callback-worker.js:
--------------------------------------------------------------------------------
1 | module.exports = CallbackWorker
2 |
3 | function CallbackWorker (fn) {
4 | var callbacks = {}
5 | var count = 0
6 |
7 | var worker = new global.Worker(global.URL.createObjectURL(
8 | new global.Blob([`(${WorkerProcess.toString()})(this, ${fn.toString()})`])
9 | ))
10 |
11 | worker.onmessage = function (e) {
12 | if (callbacks[e.data.id]) {
13 | callbacks[e.data.id](e.data.error, e.data.result)
14 | delete callbacks[e.data.id]
15 | }
16 | }
17 |
18 | return function (arg, cb) {
19 | callbacks[count] = cb
20 | worker.postMessage({arg, id: count})
21 | count += 1
22 | }
23 | }
24 |
25 | function WorkerProcess (worker, fn) {
26 | worker.onmessage = function (e) {
27 | var id = e.data.id
28 | fn(e.data.arg, cb)
29 | function cb (err, result, transferables) {
30 | worker.postMessage({err, result, id}, transferables)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/cleaner.js:
--------------------------------------------------------------------------------
1 | module.exports = Cleaner
2 |
3 | function Cleaner (audioContext) {
4 | // rubbish collection day
5 | var pendingCleanUp = []
6 |
7 | var timer = setInterval(function () {
8 | for (var i = pendingCleanUp.length - 1; i >= 0; i--) {
9 | var event = pendingCleanUp[i]
10 | // HACK: if the offset is less than 0.3, cancelled chokes may still be muted
11 | if (event.to && event.to < (audioContext.currentTime - 0.3)) {
12 | // if (event.to && event.to < audioContext.currentTime) {
13 | event.destroy()
14 | pendingCleanUp.splice(i, 1)
15 | }
16 | }
17 | }, 500)
18 |
19 | pendingCleanUp.clear = function () {
20 | while (pendingCleanUp.length) {
21 | pendingCleanUp.pop().destroy()
22 | }
23 | }
24 |
25 | pendingCleanUp.destroy = function () {
26 | clearInterval(timer)
27 | pendingCleanUp.clear()
28 | }
29 |
30 | return pendingCleanUp
31 | }
32 |
--------------------------------------------------------------------------------
/lib/get-peaks.js:
--------------------------------------------------------------------------------
1 | var CallbackWorker = require('lib/callback-worker')
2 | module.exports = CallbackWorker((opts, cb) => {
3 | var result = []
4 | var highestValue = 0
5 | var highestValuePos = 0
6 | var lastCross = 0
7 | var data = opts.data
8 | var windowSize = opts.windowSize
9 | var start = Math.max(0, opts.start)
10 | var step = opts.step || opts.windowSize
11 | var end = Math.min(opts.end, data.length)
12 |
13 | for (var i = start; i < end; i += step) {
14 | for (var pos = i; pos < windowSize + i; pos++) {
15 | var value = data[pos]
16 |
17 | if (value === 0 || (pos - lastCross > 128 && value < 0.01)) {
18 | lastCross = pos
19 | }
20 |
21 | if (value > highestValue) {
22 | highestValue = value
23 | highestValuePos = pos
24 | }
25 | }
26 | result.push([highestValuePos, highestValue])
27 | highestValue = 0
28 | }
29 |
30 | cb(null, result)
31 | })
32 |
--------------------------------------------------------------------------------
/lib/param-square.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var ParamTransform = require('lib/param-transform')
3 | var ParamSource = require('lib/param-source')
4 |
5 | module.exports = ParamSquare
6 |
7 | function ParamSquare (input) {
8 | return computed([input.currentValue || input], lambda, {
9 | comparer: ParamTransform.deepEqual
10 | })
11 | }
12 |
13 | function lambda (value) {
14 | if (value instanceof global.AudioNode) {
15 | return paramApplySquare(value)
16 | } else if (ParamSource.isParam(value)) {
17 | // lazy params
18 | return ParamSource.reduce([value], (values) => square(values[0]))
19 | } else {
20 | return square(value)
21 | }
22 | }
23 |
24 | function square (value) {
25 | return value * value
26 | }
27 |
28 | function paramApplySquare (param) {
29 | var output = param.context.createGain()
30 | param.connect(output)
31 | param.connect(output.gain)
32 | return output
33 | }
34 |
--------------------------------------------------------------------------------
/nodes/eq/params.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var ModRange = require('lib/params/mod-range')
3 |
4 | module.exports = function eqParams(node) {
5 | return h('ParamList', [
6 | ModRange(node.low, {
7 | title: 'low',
8 | defaultValue: 1,
9 | format: 'dBn',
10 | flex: 'small'
11 | }),
12 | ModRange(node.mid, {
13 | title: 'mid',
14 | defaultValue: 1,
15 | format: 'dBn',
16 | flex: 'small'
17 | }),
18 | ModRange(node.high, {
19 | title: 'high',
20 | defaultValue: 1,
21 | format: 'dBn',
22 | flex: 'small'
23 | }),
24 |
25 | ModRange(node.lowcut, {
26 | title: 'lowcut',
27 | format: 'arfo',
28 | flex: 'small',
29 | defaultValue: 0
30 | }),
31 |
32 | ModRange(node.highcut, {
33 | title: 'highcut',
34 | format: 'arfo',
35 | flex: 'small',
36 | defaultValue: 20000
37 | })
38 |
39 | ])
40 | }
--------------------------------------------------------------------------------
/lib/widgets/spawner.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var send = require('mutant/send')
3 | var spawnNode = require('lib/spawn-node')
4 |
5 | module.exports = Spawner
6 |
7 | function Spawner (collection, options) {
8 | var buttons = []
9 |
10 | for (var i = 0; i < options.nodes.length; i++) {
11 | var descriptor = options.nodes[i]
12 | if (descriptor && descriptor.spawn !== false) {
13 | buttons.push(h('button Button -main -spawn', {
14 | 'title': descriptor.description || '',
15 | 'ev-click': send(spawn, {
16 | descriptor: descriptor,
17 | collection: collection,
18 | onSpawn: options.onSpawn
19 | })
20 | }, '+ ' + descriptor.name))
21 | }
22 | }
23 |
24 | return h('NodeSpawner', buttons)
25 | }
26 |
27 | function spawn (opts) {
28 | spawnNode(opts.collection, opts.descriptor.node, function (err, node) {
29 | opts.onSpawn && opts.onSpawn(node)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/nodes/quantize/object.js:
--------------------------------------------------------------------------------
1 | var Quantize = require('lib/param-quantize')
2 | var Param = require('lib/param')
3 | var Property = require('lib/property')
4 | var ObservStruct = require('mutant/struct')
5 | var computed = require('mutant/computed')
6 | var Sum = require('lib/param-sum')
7 |
8 | module.exports = QuantizeModulator
9 |
10 | function QuantizeModulator (context) {
11 | var obs = ObservStruct({
12 | grid: Property(1),
13 | offset: Param(context, 0),
14 | value: Param(context, 0)
15 | })
16 |
17 | obs.context = context
18 |
19 | var quantized = computed(obs.grid, grid => {
20 | return Quantize(obs.value.currentValue, grid)
21 | })
22 |
23 | obs.currentValue = Sum([
24 | quantized, obs.offset.currentValue
25 | ])
26 |
27 | obs.triggerOn = function (at) {
28 | Param.triggerOn(obs, at)
29 | }
30 |
31 | obs.triggerOff = function (at) {
32 | Param.triggerOff(obs, at)
33 | }
34 |
35 | return obs
36 | }
37 |
--------------------------------------------------------------------------------
/nodes/chromatic-chunk/index.js:
--------------------------------------------------------------------------------
1 | var randomColor = require('lib/random-color')
2 |
3 | module.exports = {
4 | name: 'Chromatic',
5 | node: 'chunk/scale',
6 | group: 'chunks',
7 | description: 'Describe a single audio slot that is chromatically scaled over specified shape.',
8 | spawn: {
9 | templateSlot: {
10 | id: { $param: 'id' },
11 | noteOffset: {
12 | node: 'modulator/scale',
13 | value: { $param: 'value' },
14 | offset: { $param: 'offset' },
15 | scale: { $param: 'scale' }
16 | },
17 | node: 'slot',
18 | output: 'output'
19 | },
20 | outputs: ['output'],
21 | slots: [{id: 'output', node: 'slot'}],
22 | selectedSlotId: '$template'
23 | },
24 | external: function (context) {
25 | return {
26 | color: randomColor()
27 | }
28 | },
29 | renderExternal: require('./external'),
30 | render: require('./view'),
31 | object: require('./object')
32 | }
33 |
--------------------------------------------------------------------------------
/lib/midi-clock-offset.js:
--------------------------------------------------------------------------------
1 | var Value = require('mutant/value')
2 |
3 | module.exports = function MidiClockOffset (audioContext) {
4 | var obs = Value(window.performance.now() - audioContext.currentTime * 1000)
5 | var clockDriftChecker = audioContext.createScriptProcessor(1024 * 8, 0, 1)
6 | var lastDifference = 0
7 |
8 | clockDriftChecker.onaudioprocess = function (e) {
9 | var currentOffset = Math.round(window.performance.now() - audioContext.currentTime * 1000)
10 | var difference = currentOffset - obs()
11 |
12 | // remove jitter
13 | if (lastDifference !== -difference && currentOffset !== obs()) {
14 | lastDifference = currentOffset - obs()
15 | obs.set(Math.round(currentOffset))
16 | }
17 | }
18 |
19 | clockDriftChecker.connect(audioContext.destination)
20 |
21 | obs.destroy = function () {
22 | clockDriftChecker.disconnect()
23 | clockDriftChecker.onaudioprocess = null
24 | }
25 |
26 | return obs
27 | }
28 |
--------------------------------------------------------------------------------
/nodes/filter/object.js:
--------------------------------------------------------------------------------
1 | var Processor = require('lib/processor')
2 | var Property = require('lib/property')
3 | var Param = require('lib/param')
4 | var Apply = require('lib/apply-param')
5 | var Clamp = require('lib/param-clamp')
6 |
7 | module.exports = FilterNode
8 |
9 | function FilterNode (context) {
10 | var node = context.audio.createBiquadFilter()
11 |
12 | var releases = []
13 | var obs = Processor(context, node, node, {
14 | frequency: Param(context, node.frequency.defaultValue),
15 | Q: Param(context, node.Q.defaultValue),
16 | gain: Param(context, node.gain.defaultValue),
17 | type: Property(node.type)
18 | }, releases)
19 |
20 | obs.type(function (value) {
21 | node.type = value
22 | })
23 |
24 | releases.push(
25 | Apply(context.audio, node.frequency, Clamp(obs.frequency, 20, 20000)),
26 | Apply(context.audio, node.Q, obs.Q),
27 | Apply(context.audio, node.gain, obs.gain)
28 | )
29 |
30 | return obs
31 | }
32 |
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/bllooper.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","slots":[{"id":"output","node":"slot","noteOffset":0,"volume":1,"processors":[{"node":"processor/pitchshift","transpose":12,"wet":0.1229,"dry":0.8941},{"node":"processor/reverb","time":0.129,"decay":2,"reverse":false,"cutoff":20000,"filterType":"lowpass","wet":1,"dry":1},{"node":"processor/delay","time":0.25,"sync":true,"feedback":0.6,"cutoff":20000,"filterType":"lowpass","wet":1,"dry":1},{"node":"processor/overdrive","preBand":0.5,"color":135,"postCut":3000,"gain":1,"amp":1}]}],"outputs":["output"],"templateSlot":{"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"node":"slot","output":"output","volume":1,"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.6,"release":4.1,"attack":7.6},"noteOffset":0,"octave":-1,"detune":0,"shape":"sine"}]},"selectedSlotId":"output","scale":"$global","inputs":[],"params":[]}
--------------------------------------------------------------------------------
/lib/detect-peaks.js:
--------------------------------------------------------------------------------
1 | var getPeaks = require('lib/get-peaks')
2 |
3 | module.exports = function (data, count, offset, cb) {
4 | offset = offset || [0, 1]
5 |
6 | var range = (offset[1] - offset[0]) * data.length
7 | var step = Math.ceil(range / count / 2)
8 | var windowSize = Math.ceil(range / count)
9 | var start = Math.floor(offset[0] * data.length)
10 | var end = Math.floor(offset[1] * data.length)
11 |
12 | getPeaks({data, windowSize, step, start, end}, (err, peaks) => {
13 | if (err) return cb(err)
14 | peaks = [[start, 1]].concat(peaks)
15 | cb(peaks.filter(function (peak, i) {
16 | var prev = peaks[i - 1]
17 | return peak[0] < end && (!prev || (peak[0] - prev[0]) > step / 4)
18 | }).sort(function (a, b) {
19 | return a[1] - b[1]
20 | }).slice(-count).sort(function (a, b) {
21 | return a[0] - b[0]
22 | }).map(function (item) {
23 | return Math.min(offset[1], item[0] / data.length)
24 | }))
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/lib/observ-keys.js:
--------------------------------------------------------------------------------
1 | var ObservStruct = require('mutant/struct')
2 | var Observ = require('mutant/value')
3 | var watch = require('mutant/watch')
4 |
5 | module.exports = ObservKeys
6 |
7 | function ObservKeys (obs, mapping) {
8 | var keys = Object.keys(mapping)
9 | var lookup = keys.reduce(function (result, key) {
10 | if (Array.isArray(mapping[key])) {
11 | mapping[key].forEach(function (k) {
12 | result[k] = key
13 | })
14 | } else {
15 | result[mapping[key]] = key
16 | }
17 | return result
18 | }, {})
19 |
20 | var obj = keys.reduce(function (result, key) {
21 | result[key] = Observ(null)
22 | return result
23 | }, {})
24 |
25 | var struct = ObservStruct(obj)
26 |
27 | watch(obs, function (down) {
28 | struct.set(down.reduce(function (result, code) {
29 | var key = lookup[code]
30 | if (key != null) result[key] = true
31 | return result
32 | }, {}))
33 | })
34 |
35 | return struct
36 | }
37 |
--------------------------------------------------------------------------------
/nodes/overdrive/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var ModRange = require('lib/params/mod-range')
4 |
5 | module.exports = function renderOverdrive (node) {
6 | return h('ProcessorNode -overdrive', [
7 | Header(node, h('span', 'Overdrive')),
8 | h('ParamList', [
9 | ModRange(node.preBand, {
10 | title: 'pre band',
11 | defaultValue: 0.5,
12 | format: 'ratio',
13 | flex: true
14 | }),
15 | ModRange(node.color, {
16 | title: 'color',
17 | defaultValue: 800,
18 | format: 'arfo',
19 | flex: true
20 | }),
21 | ModRange(node.gain, {
22 | title: 'gain',
23 | defaultValue: 1,
24 | format: 'dB',
25 | flex: true
26 | }),
27 | ModRange(node.postCut, {
28 | title: 'post cut',
29 | defaultValue: 3000,
30 | format: 'arfo',
31 | flex: true
32 | })
33 | ])
34 | ])
35 | }
36 |
--------------------------------------------------------------------------------
/lib/flag-param.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 |
3 | module.exports = FlagParam
4 |
5 | function FlagParam (param, flag) {
6 | var result = computed([param], function (value) {
7 | return Array.isArray(value) ? !!~value.indexOf(flag) : false
8 | })
9 |
10 | result.set = function (value) {
11 | var current = read(param)
12 | var val = Array.isArray(current) ? current.slice() : []
13 |
14 | var index = val.indexOf(flag)
15 | var currentState = !!~index
16 | var newState = !!value
17 |
18 | if (newState !== currentState) {
19 | if (newState) {
20 | val.push(flag)
21 | } else {
22 | val.splice(index, 1)
23 | }
24 | }
25 |
26 | param.set(val)
27 | }
28 |
29 | result.context = param.context
30 | return result
31 | }
32 |
33 | function read (target) {
34 | if (typeof target === 'function') {
35 | return target()
36 | } else if (target && target.read) {
37 | return target.read()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/global-controller.js:
--------------------------------------------------------------------------------
1 | var MidiStream = require('midi-stream')
2 | var extend = require('xtend')
3 |
4 | var portMatch = [
5 | [/$Launch Control/, 'global-controller/launch-control']
6 | ]
7 |
8 | var portChoices = Observ([])
9 | MidiStream.watchPortNames(function (ports) {
10 | obs.portChoices.set(ports.filter(matchPort))
11 | })
12 |
13 |
14 | function GlobalController (context) {
15 | var obs = Node(context)
16 | obs.portChoices = portChoices
17 |
18 | var lastPortName = null
19 | obs(function (data) {
20 | if (data && data.port !== lastPortName) {
21 | lastPortName = data.port
22 | var nodeName = matchPort(data.port)
23 | if (nodeName) {
24 | obs.set(extend(data, {
25 | node: nodeName
26 | }))
27 | }
28 | }
29 | })
30 |
31 | return obs
32 | }
33 |
34 | function matchPort (name) {
35 | for (var i=0;i {
9 | var newLength = (Array.isArray(shape) && shape[0] * shape[1]) || 0
10 | var currentLength = result.length
11 | if (currentLength !== newLength) {
12 | for (var i = currentLength; i < newLength; i++) {
13 | result[i] = i
14 | }
15 | result.length = newLength
16 | return result
17 | } else {
18 | return computed.NO_CHANGE
19 | }
20 | })
21 | }
22 |
23 | function deepMatch (obs) {
24 | var lastValue = null
25 | return computed(obs, function (value) {
26 | value = value == null ? null : value
27 | if (!deepEqual(lastValue, value)) {
28 | lastValue = JSON.parse(JSON.stringify(value))
29 | return value
30 | } else {
31 | return computed.NO_CHANGE
32 | }
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/nodes/compressor/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var ModRange = require('lib/params/mod-range')
4 |
5 | module.exports = function renderCompressor (node){
6 | return h('ProcessorNode -compressor', [
7 | Header(node, h('span', 'Compressor')),
8 | h('ParamList', [
9 | ModRange(node.threshold, {
10 | title: 'threshold',
11 | format: 'dBn',
12 | flex: true
13 | }),
14 | ModRange(node.knee, {
15 | title: 'knee',
16 | format: 'dBn',
17 | flex: true
18 | }),
19 | ModRange(node.ratio, {
20 | title: 'ratio',
21 | format: 'compressionRatio',
22 | flex: true
23 | }),
24 | ModRange(node.attack, {
25 | title: 'attack',
26 | format: 'ms',
27 | flex: true
28 | }),
29 | ModRange(node.release, {
30 | title: 'release',
31 | format: 'ms',
32 | flex: true
33 | })
34 | ])
35 | ])
36 | }
37 |
--------------------------------------------------------------------------------
/nodes/hold/object.js:
--------------------------------------------------------------------------------
1 | var ParamSource = require('lib/param-source')
2 | var Param = require('lib/param')
3 | var ObservStruct = require('mutant/struct')
4 | var resolve = require('mutant/resolve')
5 |
6 | module.exports = HoldModulator
7 |
8 | function HoldModulator (context) {
9 | var obs = ObservStruct({
10 | attack: Param(context, 0),
11 | value: Param(context, 0)
12 | })
13 |
14 | obs.context = context
15 | obs.currentValue = ParamSource(context, 0)
16 |
17 | obs.triggerOn = function (at) {
18 | var value = obs.value.getValueAtTime(at)
19 | var attackTime = obs.attack.getValueAtTime(at)
20 | if (attackTime) {
21 | obs.currentValue.cancelScheduledValues(at)
22 | obs.currentValue.setTargetAtTime(value, at, attackTime / 8)
23 | } else {
24 | obs.currentValue.setValueAtTime(value, at)
25 | }
26 | Param.triggerOn(obs, at)
27 | }
28 |
29 | obs.triggerOff = function (at) {
30 | Param.triggerOff(obs, at)
31 | }
32 |
33 | return obs
34 | }
35 |
--------------------------------------------------------------------------------
/demo-project/DWS - Beep Boop/Perc-output.json:
--------------------------------------------------------------------------------
1 | {
2 | "slots": [
3 | {
4 | "id": "output",
5 | "output": null,
6 | "volume": 1,
7 | "sources": [],
8 | "processors": [
9 | {
10 | "threshold": -45.67430775096497,
11 | "knee": 30,
12 | "ratio": 12,
13 | "attack": 0.003000000026077032,
14 | "release": 0.25,
15 | "node": "processor/compressor"
16 | },
17 | {
18 | "time": 0.461,
19 | "decay": 2.88,
20 | "reverse": true,
21 | "cutoff": 20000,
22 | "filterType": "lowpass",
23 | "wet": 1,
24 | "dry": 1,
25 | "node": "processor/reverb"
26 | }
27 | ],
28 | "noteOffset": 0,
29 | "node": "slot"
30 | }
31 | ],
32 | "inputs": [
33 | "input"
34 | ],
35 | "outputs": [
36 | "output"
37 | ],
38 | "params": [],
39 | "selectedSlotId": "output",
40 | "node": "chunk/meddler",
41 | "scale": "$global"
42 | }
--------------------------------------------------------------------------------
/lib/scale-interpolate.js:
--------------------------------------------------------------------------------
1 | module.exports = scaleInterpolate
2 |
3 | function scaleInterpolate (currentValue, toValue, state) {
4 | var difference = absDifference(currentValue, toValue)
5 | if (difference != null && difference > 3 && (!state.lastSync || Date.now() - state.lastSync > 50)) {
6 | if (state.interpolatingFrom == null) {
7 | state.interpolatingFrom = toValue
8 | }
9 | if (absDifference(state.interpolatingFrom, currentValue) < difference) {
10 | if (currentValue <= 0) {
11 | toValue = 1
12 | } else {
13 | toValue = toValue / state.interpolatingFrom * currentValue
14 | }
15 | } else {
16 | state.interpolatingFrom = toValue
17 | toValue = currentValue
18 | }
19 | state.lastSync = null
20 | } else {
21 | state.interpolatingFrom = null
22 | state.lastSync = Date.now()
23 | }
24 |
25 | return toValue
26 | }
27 |
28 | function absDifference (a, b) {
29 | if (a != null && b != null) {
30 | return Math.abs(a - b)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/Q Tinkle.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","slots":[{"id":"output","processors":[{"node":"processor/filter","frequency":11000,"Q":1.55654342083404,"gain":10.802892825059846,"type":"peaking"}],"node":"slot","noteOffset":0,"volume":1}],"outputs":["output"],"selectedSlotId":"output","scale":{"notes":[0,2,4,7,9],"offset":3},"templateSlot":{"sources":[{"node":"source/oscillator","amp":0.5905,"shape":"sawtooth","octave":-1,"noteOffset":{"node":"modulator/adsr","release":0.059,"value":0},"detune":0}],"processors":[{"node":"processor/filter","frequency":{"node":"modulator/adsr","release":0,"value":3000,"decay":0.918,"sustain":0.15461363678111523},"type":"highpass","Q":1.1822679254505744,"gain":0},{"node":"processor/delay","sync":true,"time":0.25,"feedback":0.6535,"cutoff":20000,"filterType":"lowpass","wet":0.9828,"dry":1}],"volume":0.3992,"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"output":"output","node":"slot"},"inputs":[],"params":[]}
--------------------------------------------------------------------------------
/lib/resolve-file-available.js:
--------------------------------------------------------------------------------
1 | var getExt = require('path').extname
2 | var getBaseName = require('path').basename
3 | var getDirName = require('path').dirname
4 | var join = require('path').join
5 |
6 | module.exports = resolveAvailable
7 |
8 | function resolveAvailable (path, fs, cb) {
9 | // check if file exists,
10 | // if so increment number and try again
11 | // otherwise return path
12 |
13 | var ext = getExt(path)
14 | var base = getBaseName(path, ext)
15 | var dir = getDirName(path)
16 | var numberMatch = /(^.+) ([0-9]+)$/.exec(base)
17 |
18 | fs.exists(path, function(exists){
19 | if (exists){
20 | if (numberMatch){
21 | var number = parseInt(numberMatch[2]) + 1
22 | var fileName = numberMatch[1] + ' ' + number + ext
23 | resolveAvailable(join(dir, fileName), fs, cb)
24 | } else {
25 | var fileName = base + ' 1' + ext
26 | resolveAvailable(join(dir, fileName), fs, cb)
27 | }
28 | } else {
29 | cb(null, path)
30 | }
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/nodes/meddler-chunk/external.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var renderRouting = require('lib/widgets/routing')
3 | var renderChunk = require('lib/widgets/chunk')
4 | var renderParams = require('lib/widgets/params')
5 | var ToggleButton = require('lib/params/toggle-button')
6 | var FlagParam = require('lib/flag-param')
7 |
8 | module.exports = function (external) {
9 | return renderChunk(external, {
10 | external: true,
11 | extraHeader: h('span.type', ['meddler']),
12 | main: [
13 | h('section', [
14 | renderParams(external),
15 | h('ParamList', [
16 | h('div -block', [
17 | h('div.extTitle', 'Use Global'),
18 | h('ParamList -compact', [
19 | ToggleButton(FlagParam(external.flags, 'noRepeat'), {
20 | title: 'Repeat',
21 | onValue: false,
22 | offValue: true
23 | })
24 | ])
25 | ]),
26 | renderRouting(external)
27 | ])
28 | ])
29 | ]
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/lib/param-abs.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var ParamTransform = require('lib/param-transform')
3 | var ParamSource = require('lib/param-source')
4 | var absCurve = new Float32Array([1, 0, 1])
5 |
6 | module.exports = ParamSquare
7 |
8 | function ParamSquare (input) {
9 | return computed([input.currentValue || input], lambda, {
10 | comparer: ParamTransform.deepEqual
11 | })
12 | }
13 |
14 | function lambda (value) {
15 | if (value instanceof global.AudioNode) {
16 | return paramApplyAbs(value)
17 | } else if (ParamSource.isParam(value)) {
18 | // lazy params
19 | return ParamSource.reduce([value], (values) => abs(values[0]))
20 | } else {
21 | return abs(value)
22 | }
23 | }
24 |
25 | function abs (value) {
26 | return Math.abs(value)
27 | }
28 |
29 | function paramApplyAbs (param) {
30 | // TODO: this should probably handle values greater than 1, right now clips
31 | var output = param.context.createWaveShaper()
32 | output.curve = absCurve
33 | param.connect(output)
34 | return output
35 | }
36 |
--------------------------------------------------------------------------------
/lib/midi-note.js:
--------------------------------------------------------------------------------
1 | var clamp = require('lib/clamp')
2 |
3 | module.exports = MidiNote
4 |
5 | function MidiNote (context, opts) {
6 | if (!(this instanceof MidiNote)) return new MidiNote(context, opts)
7 | this.offset = context.midiClockOffset() + (opts.offset || 0)
8 | this.output = opts.output
9 | this.velocity = clamp(Math.round(opts.velocity), 1, 127)
10 | this.channel = clamp(Math.round(opts.channel) || 1, 1, 16)
11 | this.note = clamp(Math.round(opts.note), 0, 127)
12 | this.state = 0
13 | }
14 |
15 | MidiNote.prototype.start = function (at) {
16 | if (this.state === 0) {
17 | this.output.write([144 + this.channel - 1, this.note, this.velocity], getMidiTime(this.offset, at))
18 | this.state = 1
19 | }
20 | }
21 |
22 | MidiNote.prototype.stop = function (at) {
23 | if (this.state === 1) {
24 | this.output.write([128 + this.channel - 1, this.note, 0], getMidiTime(this.offset, at))
25 | this.state = 2
26 | }
27 | }
28 |
29 | function getMidiTime (offset, at) {
30 | return at ? (at * 1000) + offset : window.performance.now()
31 | }
32 |
--------------------------------------------------------------------------------
/nodes/compressor/object.js:
--------------------------------------------------------------------------------
1 | var Processor = require('lib/processor')
2 | var Param = require('lib/param')
3 | var Apply = require('lib/apply-param')
4 |
5 | module.exports = CompressorNode
6 |
7 | function CompressorNode (context) {
8 | var node = context.audio.createDynamicsCompressor()
9 | node.ratio.value = 20
10 | node.threshold.value = -1
11 |
12 | var releases = []
13 | var obs = Processor(context, node, node, {
14 | threshold: Param(context, node.threshold.defaultValue),
15 | knee: Param(context, node.knee.defaultValue),
16 | ratio: Param(context, node.ratio.defaultValue),
17 | attack: Param(context, node.attack.defaultValue),
18 | release: Param(context, node.release.defaultValue)
19 | }, releases)
20 |
21 | releases.push(
22 | Apply(context.audio, node.threshold, obs.threshold),
23 | Apply(context.audio, node.knee, obs.knee),
24 | Apply(context.audio, node.ratio, obs.ratio),
25 | Apply(context.audio, node.attack, obs.attack),
26 | Apply(context.audio, node.release, obs.release)
27 | )
28 |
29 | return obs
30 | }
31 |
--------------------------------------------------------------------------------
/nodes/modulator-chunk/object.js:
--------------------------------------------------------------------------------
1 | var Property = require('lib/property')
2 | var Slots = require('lib/slots')
3 | var lookup = require('mutant/lookup')
4 | var ParamSum = require('lib/param-sum')
5 | var BaseChunk = require('lib/base-chunk')
6 | var destroyAll = require('lib/destroy-all')
7 | var MutantMap = require('mutant/map')
8 |
9 | module.exports = ModulatorChunk
10 |
11 | function ModulatorChunk (parentContext) {
12 | var context = Object.create(parentContext)
13 | var slots = Slots(context)
14 | context.slotLookup = lookup(slots, 'id')
15 |
16 | var obs = BaseChunk(context, {
17 | slots: slots,
18 | color: Property([0, 0, 0])
19 | })
20 |
21 | obs._type = 'ModulatorChunk'
22 | obs.context = context
23 | context.chunk = obs
24 |
25 | // TODO: ParamSum should just be able to accept obs.slots directly
26 |
27 | var values = MutantMap(obs.slots, function (slot) {
28 | return slot.currentValue
29 | })
30 |
31 | obs.currentValue = ParamSum(values)
32 |
33 | obs.destroy = function () {
34 | destroyAll(obs)
35 | }
36 |
37 | return obs
38 | }
39 |
--------------------------------------------------------------------------------
/lib/import-associated-files.js:
--------------------------------------------------------------------------------
1 | var resolvePath = require('path').resolve
2 | var fs = require('fs')
3 | var each = require('async-each')
4 |
5 | module.exports = function (descriptor, originalDirectory, targetDirectory, cb) {
6 | each(getFiles(descriptor), (file, next) => {
7 | var from = resolvePath(originalDirectory, file)
8 | var to = resolvePath(targetDirectory, file)
9 | fs.exists(from, (exists) => {
10 | if (exists) {
11 | fs.exists(to, (exists) => {
12 | if (!exists) {
13 | copyFile(from, to, next)
14 | } else {
15 | next()
16 | }
17 | })
18 | } else {
19 | next()
20 | }
21 | })
22 | }, cb)
23 | }
24 |
25 | function copyFile (from, to, cb) {
26 | fs.createReadStream(from).pipe(fs.createWriteStream(to)).on('finish', cb)
27 | }
28 |
29 | function getFiles (descriptor) {
30 | var result = []
31 | JSON.stringify(descriptor, function (key, value) {
32 | if (value && (value.node === 'AudioBuffer')) {
33 | result.push(value.src)
34 | }
35 | return value
36 | })
37 | return result
38 | }
39 |
--------------------------------------------------------------------------------
/lib/mouse-drag-event.js:
--------------------------------------------------------------------------------
1 | module.exports = function (fn, data, opts) {
2 | var handler = {
3 | fn: fn,
4 | data: data || {},
5 | opts: opts || {},
6 | handleEvent: handle
7 | }
8 | return handler
9 | }
10 |
11 | function handle (ev) {
12 | if (ev.type === 'mousedown') {
13 | ev.preventDefault()
14 | document.documentElement.addEventListener('mousemove', this)
15 | document.documentElement.addEventListener('mouseup', this)
16 | this.target = ev.target
17 | this.startX = ev.clientX
18 | this.startY = ev.clientY
19 | } else if (ev.type === 'mouseup') {
20 | document.documentElement.removeEventListener('mousemove', this)
21 | document.documentElement.removeEventListener('mouseup', this)
22 | }
23 |
24 | this.fn({
25 | x: ev.clientX,
26 | y: ev.clientY,
27 | offsetX: ev.clientX - this.startX,
28 | offsetY: ev.clientY - this.startY,
29 | altKey: ev.altKey,
30 | metaKey: ev.metaKey,
31 | ctrlKey: ev.ctrlKey,
32 | shiftKey: ev.shiftKey,
33 | type: ev.type,
34 | target: this.target,
35 | currentTarget: ev.currentTarget
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/lib/wave-hook.js:
--------------------------------------------------------------------------------
1 | var WaveSvg = require('lib/wave-svg')
2 | var Path = require('path')
3 | var resolve = require('mutant/resolve')
4 |
5 | module.exports = WaveHook
6 |
7 | var cache = {}
8 |
9 | function WaveHook (context, src) {
10 | return function (element) {
11 | var path = Path.resolve(resolve(context.cwd), src)
12 | if (!cache[path]) {
13 | cache[path] = WaveSvg(path, context)
14 | }
15 |
16 | var svg = cache[path]
17 | element.innerHTML = ''
18 |
19 | waitForLoad(svg, function () {
20 | element.innerHTML = svg()
21 | var innerElement = element.querySelector('svg')
22 | if (innerElement) {
23 | svg.onAppendChild(function (fragment) {
24 | innerElement.insertAdjacentHTML('afterbegin', fragment)
25 | })
26 | }
27 | })
28 |
29 | return svg.destroy
30 | }
31 | }
32 |
33 | function waitForLoad (svg, cb) {
34 | if (svg()) {
35 | process.nextTick(cb)
36 | } else {
37 | var release = svg(function (val) {
38 | if (val) {
39 | process.nextTick(release)
40 | cb()
41 | }
42 | })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/timeline-scheduler.js:
--------------------------------------------------------------------------------
1 | module.exports = AudioTimelineScheduler
2 |
3 | function AudioTimelineScheduler (audioContext) {
4 | var listeners = []
5 | var timer = null
6 | var lastTime = audioContext.currentTime
7 | var lastSchedule = null
8 |
9 | var obs = function (listener) {
10 | if (!listeners.length) {
11 | lastTime = audioContext.currentTime
12 | listeners.push(listener)
13 | schedule()
14 | timer = setInterval(schedule, 50)
15 | } else {
16 | listener(lastSchedule)
17 | listeners.push(listener)
18 | }
19 | return function remove () {
20 | var index = listeners.indexOf(listener)
21 | if (~index) listeners.splice(index, 1)
22 | if (!listeners.length) {
23 | clearInterval(timer)
24 | }
25 | }
26 | }
27 |
28 | return obs
29 |
30 | // scoped
31 |
32 | function schedule () {
33 | var to = audioContext.currentTime + 0.1
34 | var data = [lastTime, to - lastTime]
35 | lastTime = to
36 | for (var i = 0;i < listeners.length;i++) {
37 | listeners[i](data)
38 | }
39 | lastSchedule = data
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/wave-svg.new.js:
--------------------------------------------------------------------------------
1 | module.exports = WaveSvg
2 |
3 | function WaveSvg (path, options) {
4 | return computed(path, function (path) {
5 | var svg = Value()
6 | var svgPath = path + '.svg'
7 | options.fs.readFile(svgPath, 'utf8', function (err, value) {
8 | if (err) {
9 | decodeRange = RangeDecoder(path, options, function (err, res) {
10 | if (err) return onDone && onDone(err)
11 | meta = res
12 | remaining = meta.duration
13 | nextChunk()
14 | })
15 |
16 | // cue points
17 | var timePath = path + '.time'
18 | options.fs.readFile(timePath, function (err, buffer) {
19 | if (!err) {
20 | var data = new Float32Array(new Uint8Array(buffer).buffer)
21 | markerPath = svg('path', {
22 | d: getMarkerPath(data, scale, height),
23 | fill: 'rgba(255,255,255,0.1)'
24 | })
25 | refresh()
26 | }
27 | })
28 | } else {
29 | obs.set(value)
30 | onDone && onDone(null, value)
31 | }
32 | })
33 | return svg
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/nodes/clap/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 |
4 | var Range = require('lib/params/range')
5 | var ModRange = require('lib/params/mod-range')
6 | var Select = require('lib/params/select')
7 |
8 | module.exports = function renderDrumSynth (node){
9 | var data = node()
10 |
11 | return h('SourceNode -drumSynth', [
12 | Header(node, h('span', [
13 | h('strong', 'Drum Synth:'), ' Clap'
14 | ])),
15 | h('ParamList', [
16 | ModRange(node.amp, {
17 | title: 'amp',
18 | defaultValue: 1,
19 | format: 'dB',
20 | flex: true
21 | }),
22 | ModRange(node.decay, {
23 | title: 'decay',
24 | defaultValue: 0.5,
25 | format: 'ms',
26 | flex: true
27 | }),
28 | ModRange(node.tone, {
29 | title: 'tone',
30 | defaultValue: 0.5,
31 | format: 'ratio',
32 | flex: true
33 | }),
34 | ModRange(node.density, {
35 | title: 'density',
36 | defaultValue: 0.2,
37 | format: 'ratio',
38 | flex: true
39 | })
40 | ])
41 | ])
42 | }
43 |
--------------------------------------------------------------------------------
/lib/test/test-grab-grid.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert')
2 | var GrabGrid = require('../grab-grid')
3 | var ObservGrid = require('observ-grid')
4 | var Observ = require('mutant/value')
5 |
6 | var grid = ObservGrid([0,0,0,0], [2,2])
7 |
8 | var grab = GrabGrid(grid)
9 | var changes = []
10 |
11 | var release1 = grab(function (value) {
12 | changes.push([1, value.data])
13 | })
14 |
15 | grid.set(0, 0, 1)
16 | grid.set(0, 1, 10)
17 |
18 | var release2 = grab(function (value) {
19 | changes.push([2, value.data])
20 | })
21 |
22 | var release3 = grab(function (value) {
23 | changes.push([3, value.data])
24 | }, { exclude: [0, 1]})
25 |
26 | grid.set(0, 1, 1)
27 | grid.set(0, 0, 0)
28 | grid.set(1, 1, 1)
29 |
30 | var release4 = grab(function (value) {
31 | changes.push([4, value.data])
32 | }, { exclude: Observ([2])})
33 |
34 | grid.set(1, 0, 1)
35 |
36 | assert.deepEqual(changes, [
37 | [ 1, [] ],
38 | [ 1, [ 1 ] ],
39 | [ 1, [ 1, 10 ] ],
40 | [ 2, [] ],
41 | [ 3, [] ],
42 | [ 1, [ 1, null ] ],
43 | [ 2, [ , 1 ] ],
44 | [ 1, [ null, null ] ],
45 | [ 3, [ , , , 1 ] ],
46 | [ 4, [] ],
47 | [ 3, [ , , 1, 1 ] ]
48 | ])
49 |
--------------------------------------------------------------------------------
/lib/watch-nodes-changed.js:
--------------------------------------------------------------------------------
1 | var forEach = require('mutant/for-each')
2 | var forEachPair = require('mutant/for-each-pair')
3 |
4 | module.exports = watchNodesChanged
5 |
6 | function watchNodesChanged (collectionOrLookup, fn) {
7 | var nodes = new global.Set()
8 | return collectionOrLookup(function (value) {
9 | var currentItems = new global.Set()
10 | var changed = false
11 |
12 | if (Array.isArray(value)) {
13 | forEach(collectionOrLookup, function (item) {
14 | currentItems.add(item)
15 | if (!nodes.has(item)) {
16 | nodes.add(item)
17 | changed = true
18 | }
19 | })
20 | } else {
21 | forEachPair(collectionOrLookup, function (key, item) {
22 | currentItems.add(item)
23 | if (!nodes.has(item)) {
24 | nodes.add(item)
25 | changed = true
26 | }
27 | })
28 | }
29 |
30 | Array.from(nodes.values()).forEach(function (node) {
31 | if (!currentItems.has(node)) {
32 | nodes.delete(node)
33 | changed = true
34 | }
35 | })
36 |
37 | if (changed) {
38 | fn()
39 | }
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/styles/setup-node.mcss:
--------------------------------------------------------------------------------
1 | SetupNode {
2 | padding: 4px
3 | display: flex
4 | flex-direction: column
5 | flex: 1
6 | overflow: hidden
7 |
8 | div.main {
9 |
10 | display: flex
11 | flex: 1
12 | min-height: 0
13 |
14 | div.controllers {
15 | flex: 1
16 | }
17 |
18 | div.chunks {
19 | width: 350px
20 | }
21 |
22 | div {
23 | overflow-y: auto
24 | overflow-x: hidden
25 | }
26 |
27 | }
28 | div.options {
29 |
30 | display: flex;
31 | margin: -4px;
32 | background: #222;
33 | box-shadow: 0 0 5px #000;
34 | margin-top: 1px;
35 | z-index: 100;
36 | flex-shrink: 0
37 |
38 | section {
39 | padding: 14px;
40 | display: flex
41 | h1 {
42 | flex: none
43 | margin-right: 5px
44 | }
45 | div.param {
46 | flex: 1
47 | display: flex
48 | margin: 3px;
49 | }
50 | div.chooser {
51 | flex: 1
52 | display: flex
53 | }
54 | }
55 |
56 | section.scale {
57 | flex: 1
58 | }
59 |
60 | section.volume {
61 | width: 350px
62 | }
63 |
64 | }
65 | }
--------------------------------------------------------------------------------
/lib/apply-scale.js:
--------------------------------------------------------------------------------
1 | var defaultScale = {
2 | offset: 0,
3 | notes: [0, 2, 4, 5, 7, 9, 11]
4 | }
5 |
6 | module.exports = applyScale
7 |
8 | function applyScale (base, scale) {
9 | var offset = scale && scale.offset || defaultScale.offset
10 | var notes = scale && scale.notes || defaultScale.notes
11 | base = Math.round(base * 100000000) / 100000000 // weed out floating point errors
12 |
13 | var multiplier = Math.floor(base / notes.length)
14 | var scalePosition = mod(base, notes.length)
15 | var absScalePosition = Math.floor(scalePosition)
16 | var fraction = scalePosition - absScalePosition
17 |
18 | var note = notes[absScalePosition] + offset
19 |
20 | if (fraction) {
21 | var interval = getInterval(absScalePosition, notes)
22 | return note + (interval * fraction) + (multiplier * 12)
23 | } else {
24 | return note + (multiplier * 12)
25 | }
26 | }
27 |
28 | function getInterval (current, notes) {
29 | if (current >= notes.length - 1) {
30 | return 12 + notes[0] - notes[current]
31 | } else {
32 | return notes[current + 1] - notes[current]
33 | }
34 | }
35 |
36 | function mod (n, m) {
37 | return ((n % m) + m) % m
38 | }
39 |
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/freedom.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","slots":[{"id":"output","node":"slot","noteOffset":0,"volume":1,"processors":[{"node":"processor/delay","time":{"node":"modulator/lfo","trigger":true,"value":0.058,"amp":13.47},"sync":false,"feedback":0.7891,"cutoff":1900,"filterType":"lowpass","wet":1,"dry":1},{"node":"processor/overdrive","preBand":0.5,"color":1800,"postCut":10000,"gain":1.9182,"amp":1},{"node":"processor/reverb","time":1.4,"decay":2,"reverse":true,"cutoff":20000,"filterType":"lowpass","wet":1,"dry":1},{"node":"processor/pitchshift","transpose":12,"wet":0.2865,"dry":0.8719}]}],"outputs":["output"],"templateSlot":{"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"node":"slot","output":"output","volume":1,"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.6,"release":0.01},"noteOffset":0,"octave":0,"detune":0,"shape":"sawtooth"}],"processors":[{"node":"processor/filter","frequency":{"node":"modulator/adsr","release":0.1,"value":3800,"attack":0.357},"Q":1,"gain":0,"type":"lowpass"}]},"selectedSlotId":"output","scale":"$global","inputs":[],"params":[]}
--------------------------------------------------------------------------------
/lib/params/toggle-chooser.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var send = require('mutant/send')
3 | var computed = require('mutant/computed')
4 |
5 | module.exports = ToggleChooser
6 |
7 | function ToggleChooser (param, opts) {
8 | return h('ToggleChooser', [
9 | h('span.title', opts.title),
10 | computed([opts.options], function (options) {
11 | return options.map(function (option) {
12 | if (Array.isArray(option)) {
13 | return h('div.choice', {
14 | classList: computed([param, option[1]], activeWhenEqual),
15 | events: {
16 | click: send(param.set, option[1])
17 | }
18 | }, option[0])
19 | } else if (typeof option === 'string' || typeof option === 'number') {
20 | return h('div.choice', {
21 | classList: computed([param, option], activeWhenEqual),
22 | events: {
23 | click: send(param.set, option)
24 | }
25 | }, String(option))
26 | }
27 | })
28 | })
29 | ])
30 | }
31 |
32 | function activeWhenEqual (a, b) {
33 | if (a === b || (a == null && b == null)) {
34 | return '-active'
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/nodes/mixer-launch-control-xl/set-lights.js:
--------------------------------------------------------------------------------
1 | module.exports = setLights
2 |
3 | var message = [240, 0, 32, 41, 2, 17, 120, 8]
4 | var off = 0
5 |
6 | function setLights (state, stream) {
7 | var currentState = {}
8 | stream(function (port) {
9 | if (port) {
10 | var toUpdate = []
11 | Object.keys(currentState).forEach(function (key) {
12 | var id = parseInt(key, 10)
13 | var value = currentState[key] || off
14 | toUpdate.push(id, value)
15 | })
16 | if (toUpdate.length) {
17 | stream.write(message.concat(toUpdate.slice(0, 64), 247))
18 | if (toUpdate.length > 64) {
19 | stream.write(message.concat(toUpdate.slice(64), 247))
20 | }
21 | }
22 | }
23 | })
24 | return state(function (values) {
25 | var toUpdate = []
26 | values.forEach(function (value, id) {
27 | if (!same(currentState[id], value)) {
28 | currentState[id] = value || 0
29 | toUpdate.push(id, value)
30 | }
31 | })
32 | if (toUpdate.length) {
33 | stream.write(message.concat(toUpdate, 247))
34 | }
35 | })
36 | }
37 |
38 | function same (a, b) {
39 | return a === b
40 | }
41 |
--------------------------------------------------------------------------------
/nodes/freeverb/object.js:
--------------------------------------------------------------------------------
1 | var watch = require('mutant/watch')
2 |
3 | var Freeverb = require('freeverb')
4 | var Processor = require('lib/processor')
5 | var Property = require('lib/property')
6 |
7 | var Param = require('lib/param')
8 | var Apply = require('lib/apply-param')
9 | var Multiply = require('lib/param-multiply')
10 |
11 | module.exports = FreeverbNode
12 |
13 | function FreeverbNode (context) {
14 | var reverb = Freeverb(context.audio)
15 | var output = context.audio.createGain()
16 | reverb.connect(output)
17 |
18 | var releases = []
19 | var obs = Processor(context, reverb, output, {
20 | roomSize: Param(context, 0.8),
21 | dampening: Param(context, 3000),
22 | wet: Param(context, 1),
23 | dry: Param(context, 1)
24 | }, releases)
25 |
26 | releases.push(
27 | Apply(context.audio, reverb.wet, Multiply([obs.wet, 1 / 4])),
28 | Apply(context.audio, reverb.dry, obs.dry)
29 | )
30 |
31 | reverb.combFilters.forEach((combFilter) => {
32 | releases.push(
33 | Apply(context.audio, combFilter.resonance, obs.roomSize),
34 | Apply(context.audio, combFilter.dampening, obs.dampening)
35 | )
36 | })
37 |
38 | return obs
39 | }
40 |
--------------------------------------------------------------------------------
/lib/assign-available-port.js:
--------------------------------------------------------------------------------
1 | module.exports = assignAvailablePort
2 |
3 | function assignAvailablePort (node) {
4 | var nodeInfo = node.context.nodeInfo.lookup[node().node]
5 | if (nodeInfo && node.port && nodeInfo.portMatch) {
6 | if (!node.port() || !nodeInfo.portMatch.exec(node.port())) {
7 | var availablePorts = node.context.midiPorts().filter(function (name) {
8 | return nodeInfo.portMatch.exec(name)
9 | })
10 |
11 | var usedPorts = []
12 | node.context.collection.forEach(function (controller) {
13 | if (controller) {
14 | var name = controller.port && controller.port()
15 | if (availablePorts.includes(name)) {
16 | usedPorts.push(name)
17 | }
18 | }
19 | })
20 |
21 | var portName = getRarest(availablePorts.concat(usedPorts))
22 | node.port.set(portName)
23 | }
24 | }
25 | }
26 |
27 | function getRarest (array) {
28 | var ranked = array.reduce(function (result, item) {
29 | result[item] = (result[item] || 0) + 1
30 | return result
31 | }, {})
32 |
33 | return Object.keys(ranked).sort(function (a, b) {
34 | return ranked[a] - ranked[b]
35 | })[0]
36 | }
37 |
--------------------------------------------------------------------------------
/nodes/midi-cc/object.js:
--------------------------------------------------------------------------------
1 | var Struct = require('mutant/struct')
2 | var computed = require('mutant/computed')
3 | var applyMidiParam = require('lib/apply-midi-param')
4 | var Property = require('lib/property')
5 | var Param = require('lib/param')
6 | var destroyAll = require('lib/destroy-all')
7 | var clamp = require('lib/clamp')
8 |
9 | module.exports = MidiCCNode
10 |
11 | function MidiCCNode (context) {
12 | var port = context.outputMidiPort
13 | var channel = context.outputMidiChannel
14 |
15 | var obs = Struct({
16 | code: Property(1),
17 | value: Param(context, 0)
18 | })
19 |
20 | Param.triggerOn(obs, context.audio.currentTime)
21 |
22 | var message = computed([channel, obs.code], (channel, code) => {
23 | var channelOffset = clamp(channel, 1, 16) - 1
24 | if (code === 'PC') {
25 | return [192 + channelOffset]
26 | } else {
27 | return [176 + channelOffset, code]
28 | }
29 | })
30 |
31 | var releaseMidiParam = applyMidiParam(context, { port, message }, obs.value)
32 |
33 | obs.resend = releaseMidiParam.resend
34 |
35 | obs.destroy = function () {
36 | destroyAll(obs)
37 | releaseMidiParam()
38 | }
39 |
40 | obs.context = context
41 |
42 | return obs
43 | }
44 |
--------------------------------------------------------------------------------
/lib/processor.js:
--------------------------------------------------------------------------------
1 | var ObservStruct = require('mutant/struct')
2 | var Param = require('lib/param')
3 |
4 | module.exports = ProcessorNode
5 |
6 | function ProcessorNode (context, input, output, params, releases) {
7 | var obs = ObservStruct(params, {merge: true})
8 |
9 | obs.input = input
10 | obs.output = output
11 | obs.connect = output.connect.bind(output)
12 | obs.disconnect = output.disconnect.bind(output)
13 | obs.getReleaseDuration = Param.getReleaseDuration.bind(this, obs)
14 | obs.getAttackDuration = Param.getAttackDuration.bind(this, obs)
15 |
16 | obs.context = context
17 |
18 | obs.triggerOn = function (at) {
19 | at = at || context.audio.currentTime
20 | Param.triggerOn(obs, at)
21 | }
22 |
23 | obs.triggerOff = function (at) {
24 | at = at || context.audio.currentTime
25 | var stopAt = obs.getReleaseDuration(at) + at
26 | Param.triggerOff(obs, stopAt)
27 | }
28 |
29 | obs.destroy = function () {
30 | while (releases && releases.length) {
31 | releases.pop()()
32 | }
33 | Object.keys(obs).forEach(function (key) {
34 | if (obs[key] && typeof obs[key].destroy === 'function') {
35 | obs[key].destroy()
36 | }
37 | })
38 | }
39 |
40 | return obs
41 | }
42 |
--------------------------------------------------------------------------------
/nodes/eq/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var ModRange = require('lib/params/mod-range')
4 | var Select = require('lib/params/select')
5 |
6 | module.exports = function renderFilter(node) {
7 | return h('ProcessorNode -filter', [
8 | Header(node, h('span', 'EQ')),
9 | h('section', [
10 | h('ParamList', [
11 | ModRange(node.low, {
12 | title: 'low',
13 | defaultValue: 0,
14 | format: 'dBn',
15 | flex: 'small'
16 | }),
17 | ModRange(node.mid, {
18 | title: 'mid',
19 | defaultValue: 0,
20 | format: 'dBn',
21 | flex: 'small'
22 | }),
23 | ModRange(node.high, {
24 | title: 'high',
25 | defaultValue: 0,
26 | format: 'dBn',
27 | flex: 'small'
28 | }),
29 | ModRange(node.highcut, {
30 | title: 'highcut',
31 | format: 'arfo',
32 | flex: 'small',
33 | defaultValue: 20000
34 | }),
35 | ModRange(node.lowcut, {
36 | title: 'lowcut',
37 | format: 'arfo',
38 | flex: 'small',
39 | defaultValue: 0
40 | })
41 | ])
42 | ])
43 | ])
44 | }
45 |
--------------------------------------------------------------------------------
/nodes/ableton-link/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var send = require('mutant/send')
3 | var computed = require('mutant/computed')
4 | var when = require('mutant/when')
5 | var Range = require('lib/params/range')
6 |
7 | module.exports = renderMidiSync
8 |
9 | function renderMidiSync (controller) {
10 | var collection = controller.context.collection
11 | var name = computed(controller, x => x.name)
12 | return h('GlobalControllerNode', [
13 | h('header', [
14 | h('span.name', [
15 | h('strong', [name, ': ']), ' ',
16 | plural(controller.peerCount, 'peer', 'peers')
17 | ]),
18 | h('button.remove Button -warn', {
19 | 'ev-click': send(collection.remove, controller)
20 | }, 'X')
21 | ]),
22 | h('section', [
23 | h('ParamList', [
24 | Range(controller.syncOffset, {
25 | title: 'sync offset',
26 | defaultValue: 0,
27 | format: 'syncMs',
28 | flex: true
29 | })
30 | ])
31 | ])
32 | ])
33 | }
34 |
35 | function plural (...args) {
36 | return computed(args, (value, singular, plural) => {
37 | if (value === 1) {
38 | return `${value} ${singular}`
39 | } else {
40 | return `${value} ${plural}`
41 | }
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/lib/param-sum.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var Connector = require('lib/connector')
3 | var ParamTransform = require('lib/param-transform')
4 | var ParamFromNumber = require('lib/param-from-number')
5 | var ValueAtTimeGetter = require('lib/value-at-time-getter')
6 |
7 | module.exports = ParamSum
8 |
9 | function ParamSum (inputs) {
10 | return ParamTransform(inputs, sumParams, sumValues)
11 | }
12 |
13 | function sumParams (audioContext, params, number) {
14 | var connections = new Connector()
15 | var getValueAtTime = ValueAtTimeGetter(params, number, sumValues)
16 |
17 | return computed([params, ParamFromNumber(audioContext, number)], function (params, numberParam) {
18 | connections.clear()
19 |
20 | var result = audioContext.createGain()
21 |
22 | for (var i = 0; i < params.length; i++) {
23 | connections.add(params[i], result)
24 | }
25 |
26 | if (numberParam) {
27 | connections.add(numberParam, result)
28 | }
29 |
30 | result.getValueAtTime = getValueAtTime
31 | return result
32 | }, {
33 | onListen: () => connections.connect(),
34 | onUnlisten: () => connections.disconnect()
35 | })
36 | }
37 |
38 | function sumValues (values) {
39 | return values.reduce((a, b) => a + b, 0)
40 | }
41 |
--------------------------------------------------------------------------------
/demo-project/Qwerty Keys Example/bass.json:
--------------------------------------------------------------------------------
1 | {"scale":"$global","templateSlot":{"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.2271,"release":0.382,"attack":0},"frequency":440,"shape":"sawtooth","octave":-3,"noteOffset":0,"detune":0},{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.4223,"attack":0,"release":0.378},"frequency":440,"octave":-3,"noteOffset":0,"detune":0,"shape":"sine"}],"processors":[{"node":"processor/filter","frequency":{"node":"modulator/adsr","value":5700,"attack":0,"release":0,"sustain":0.018729838057848537,"decay":0.341},"Q":0.2828344239468248,"gain":0,"type":"lowpass"}],"chokeGroup":"bass","volume":0.5788,"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"output":"output","node":"slot"},"slots":[{"id":"output","output":null,"volume":1,"sources":[],"processors":[{"mode":"modulate","ratio":0.43649348257397047,"node":"processor/dipper"},{"frequency":6100,"Q":2.1336110493933447,"gain":-5.183449928990768,"type":"peaking","node":"processor/filter"},{"mode":"modulate","ratio":0.43649348257397047,"node":"processor/dipper"}],"noteOffset":0,"node":"slot"}],"inputs":[],"outputs":["output"],"params":[],"selectedSlotId":"output","node":"chunk/scale"}
--------------------------------------------------------------------------------
/nodes/kick/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var computed = require('mutant/computed')
4 | var ModRange = require('lib/params/mod-range')
5 | var Select = require('lib/params/select')
6 |
7 | var types = [
8 | ['808', '808'],
9 | ['909', '909']
10 | ]
11 |
12 | module.exports = function renderDrumSynth (node) {
13 | return h('SourceNode -drumSynth', [
14 | Header(node, h('span', [
15 | h('strong', 'Drum Synth:'), ' Kick ',
16 | h('span', computed(node, d => d.node))
17 | ])),
18 | h('ParamList', [
19 | Select(node.type, {
20 | options: types
21 | }),
22 | ModRange(node.amp, {
23 | title: 'amp',
24 | defaultValue: 1,
25 | format: 'dB',
26 | flex: true
27 | }),
28 | ModRange(node.tone, {
29 | title: 'tone',
30 | defaultValue: 0.5,
31 | format: 'ratio',
32 | flex: true
33 | }),
34 | ModRange(node.decay, {
35 | title: 'decay',
36 | defaultValue: 0.5,
37 | format: 'ms',
38 | flex: true
39 | }),
40 | ModRange(node.tune, {
41 | title: 'tune',
42 | defaultValue: 0,
43 | format: 'cents+',
44 | flex: true
45 | })
46 | ])
47 | ])
48 | }
49 |
--------------------------------------------------------------------------------
/lib/observ-rms.js:
--------------------------------------------------------------------------------
1 | var Value = require('mutant/value')
2 | var gainToDecibels = require('decibels/from-gain')
3 |
4 | module.exports = ObservRms
5 |
6 | function ObservRms (audioNode) {
7 | var value = [-Infinity, -Infinity]
8 | var obs = Value(value)
9 | var broadcast = obs.set
10 | obs.set = null
11 |
12 | var audioContext = audioNode.context
13 |
14 | var meter = audioContext.createScriptProcessor(512 * 2, 2, 2)
15 | audioNode.connect(meter)
16 |
17 | var lastL = 0
18 | var lastR = 0
19 | var smoothing = 0.8
20 |
21 | meter.onaudioprocess = function (e) {
22 | var inputL = e.inputBuffer.getChannelData(0)
23 | var inputR = e.inputBuffer.getChannelData(1)
24 | var rmsL = Math.max(rms(inputL), lastL * smoothing)
25 | var rmsR = Math.max(rms(inputR), lastR * smoothing)
26 | if (rmsL !== lastL || rmsR !== lastR) {
27 | value[0] = gainToDecibels(rmsL)
28 | value[1] = gainToDecibels(rmsR)
29 | broadcast(value)
30 | lastL = rmsL
31 | lastR = rmsR
32 | }
33 | }
34 |
35 | meter.connect(audioContext.destination)
36 |
37 | return obs
38 | }
39 |
40 | function rms (input) {
41 | var sum = 0
42 | for (var i = 0; i < input.length; i++) {
43 | sum += input[i] * input[i]
44 | }
45 | return Math.sqrt(sum / input.length)
46 | }
47 |
--------------------------------------------------------------------------------
/nodes/freeverb/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var Header = require('lib/widgets/header')
3 | var Range = require('lib/params/range')
4 | var ModRange = require('lib/params/mod-range')
5 | var Select = require('lib/params/select')
6 | var QueryParam = require('lib/query-param')
7 | var nodeChoices = require('../reverb/types')
8 |
9 | module.exports = function renderFreeverb (node) {
10 | return h('ProcessorNode -reverb', [
11 | Header(node, h('span', 'Reverb')),
12 | h('ParamList', [
13 | Select(QueryParam(node, 'node'), {
14 | defaultValue: 'processor/freeverb',
15 | options: nodeChoices
16 | }),
17 | ModRange(node.roomSize, {
18 | title: 'room size',
19 | defaultValue: 0.8,
20 | format: 'ratio1Log',
21 | flex: true
22 | }),
23 | ModRange(node.dampening, {
24 | title: 'dampening',
25 | defaultValue: 3000,
26 | format: 'arfo',
27 | flex: true
28 | }),
29 | ModRange(node.wet, {
30 | title: 'wet',
31 | defaultValue: 1,
32 | format: 'dB',
33 | flex: true
34 | }),
35 | ModRange(node.dry, {
36 | title: 'dry',
37 | defaultValue: 1,
38 | format: 'dB',
39 | flex: true
40 | })
41 | ])
42 | ])
43 | }
44 |
--------------------------------------------------------------------------------
/styles/param-list.mcss:
--------------------------------------------------------------------------------
1 | ParamList {
2 | margin: 5px
3 | display: flex
4 | flex-wrap: wrap
5 |
6 | -compact {
7 | margin: 0
8 |
9 | button {
10 | margin: 0
11 | }
12 |
13 | button + button {
14 | margin-left: 4px
15 | }
16 |
17 | }
18 |
19 | table {
20 | (td.title){
21 | width: 90%
22 | }
23 | }
24 |
25 | select {
26 | cursor: pointer
27 | margin: 4px
28 | -flex {
29 | flex: 1
30 | }
31 | }
32 |
33 | input {
34 | [type='text'] {
35 | flex: 1
36 | flex-shrink: 0
37 | margin: 4px
38 | }
39 | }
40 |
41 | button {
42 | margin: 4px
43 | height: 18px
44 | }
45 |
46 | div {
47 | div.title {
48 | font-size: 80%
49 | font-weight: bold
50 | position: absolute
51 | padding: 4px 0 0 2px
52 | z-index: 1
53 | opacity: 0.6
54 | pointer-events: none
55 | }
56 |
57 | div.extTitle {
58 | font-size: 80%
59 | color: #AAA
60 | }
61 |
62 | margin: 4px;
63 | display: flex
64 |
65 | -block {
66 | display: block
67 | select {
68 | margin: 0
69 | }
70 | }
71 |
72 | -flex {
73 | flex: 1 300px
74 | }
75 |
76 | -flexSmall {
77 | flex: 1 100px
78 | }
79 |
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/nodes/loop-grid/compute-flags.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var ArrayGrid = require('array-grid')
3 |
4 | module.exports = function computeFlags (obsChunkLookup, obsPositions, obsShape) {
5 | return computed([obsChunkLookup, obsPositions, obsShape], function (chunkLookup, positions, shape) {
6 | var grid = ArrayGrid([], shape)
7 | if (chunkLookup && positions) {
8 | Object.keys(positions).forEach(function (id) {
9 | var chunk = obsChunkLookup.get(id)
10 | var origin = positions[id]
11 | if (chunk && chunk.flags && chunk.flags() && chunk.grid) {
12 | var flags = chunk.flags()
13 | var chunkGrid = chunk.grid()
14 |
15 | var result = []
16 | if (Array.isArray(flags)) {
17 | if (flags.length) {
18 | chunkGrid.data.forEach(function (id, i) {
19 | result[i] = flags
20 | })
21 | }
22 | } else {
23 | chunkGrid.data.forEach(function (id, i) {
24 | if (flags[id]) {
25 | result[i] = flags[id]
26 | }
27 | })
28 | }
29 |
30 | grid.place(origin[0], origin[1], new ArrayGrid(result, chunkGrid.shape))
31 | }
32 | })
33 | }
34 | return grid
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/styles/node-collection.mcss:
--------------------------------------------------------------------------------
1 | NodeCollection {
2 | padding: 4px
3 |
4 | h1 {
5 | button.condense {
6 | float: right
7 | margin-top: 3px
8 | cursor: pointer
9 | opacity: 0.4
10 | height: 18px
11 | width: 18px
12 | border-radius: 3px
13 | margin-right: 3px
14 | border: none
15 | background: transparent
16 | background-repeat: no-repeat
17 | background-position: center
18 | background-image: svg(condense)
19 |
20 | :hover {
21 | opacity: 0.8
22 | background-color: #888
23 | }
24 |
25 | :focus {
26 | outline: none
27 | opacity: 0.8
28 | }
29 |
30 | :active {
31 | opacity: 1
32 | }
33 |
34 | @svg condense {
35 | width: 8px
36 | height: 10px
37 | content: ""
38 | path {
39 | fill: #FFF
40 | }
41 | }
42 | }
43 | }
44 |
45 | -wrap {
46 | display: flex
47 | flex-wrap: wrap
48 | div {
49 | margin: 4px
50 | }
51 | }
52 |
53 | -across {
54 | overflow: hidden
55 | display: block
56 | div {
57 | display: block
58 | float: left
59 | margin: 4px
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/lib/param-clamp.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var ParamSource = require('lib/param-source')
3 | var clamp = require('lib/clamp')
4 |
5 | module.exports = ParamClamp
6 |
7 | function ParamClamp (param, min, max) {
8 | return computed([param.currentValue || param, min, max], getClampedParam)
9 | }
10 |
11 | function getClampedParam (param, min, max) {
12 | if (param instanceof global.AudioNode) {
13 | var range = max - min
14 | var shaper = param.context.createWaveShaper()
15 | shaper.curve = new Float32Array([min, max])
16 | scale(param, 2 / range, -min - (range / 2)).connect(shaper)
17 | return shaper
18 | } else if (ParamSource.isParam(param)) {
19 | return ParamSource.reduce([param], (values) => clamp(values[0], min, max))
20 | } else if (typeof param === 'number') {
21 | return clamp(param, min, max)
22 | }
23 | }
24 |
25 | function scale (param, multiplier, offset) {
26 | var sum = param.context.createGain()
27 | var offsetParam = getValue(offset, param)
28 | offsetParam.connect(sum)
29 | param.connect(sum)
30 | sum.gain.value = multiplier
31 | return sum
32 | }
33 |
34 | function getValue (value, param) {
35 | var result = param.context.createWaveShaper()
36 | result.curve = new Float32Array([value, value])
37 | param.connect(result)
38 | return result
39 | }
40 |
--------------------------------------------------------------------------------
/nodes/triggers-chunk/external.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var when = require('mutant/when')
3 |
4 | var renderRouting = require('lib/widgets/routing')
5 | var renderChunk = require('lib/widgets/chunk')
6 | var renderParams = require('lib/widgets/params')
7 | var renderMidiOutputOptions = require('lib/widgets/midi-output')
8 | var ToggleButton = require('lib/params/toggle-button')
9 | var FlagParam = require('lib/flag-param')
10 |
11 | module.exports = function (external) {
12 | var node = external.node
13 | return renderChunk(external, {
14 | volume: true,
15 | external: true,
16 | main: [
17 | h('section', [
18 | renderParams(external),
19 | h('ParamList', [
20 | h('div -block', [
21 | h('div.extTitle', 'Use Global'),
22 | h('ParamList -compact', [
23 | ToggleButton(FlagParam(external.flags, 'noRepeat'), {
24 | title: 'Repeat',
25 | onValue: false,
26 | offValue: true
27 | })
28 | ])
29 | ]),
30 | renderRouting(external)
31 | ])
32 | ]),
33 | when(node.midiOutputEnabled, [
34 | h('h1', 'Midi Output'),
35 | h('section', [
36 | renderMidiOutputOptions(external)
37 | ])
38 | ])
39 | ]
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/nodes/external-audio-input/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var send = require('mutant/send')
3 | var when = require('mutant/when')
4 | var computed = require('mutant/computed')
5 | var ToggleButton = require('lib/params/toggle-button')
6 |
7 | module.exports = renderExternalAudioInput
8 |
9 | function renderExternalAudioInput (controller) {
10 | var collection = controller.context.collection
11 | var name = computed(controller, x => x.name)
12 | var port = controller.port
13 | return h('GlobalControllerNode', {
14 | classList: [
15 | when(controller.minimised, '-minimised')
16 | ]
17 | }, [
18 | h('header', [
19 | h('button.twirl', {
20 | 'ev-click': send(toggleParam, controller.minimised)
21 | }),
22 | h('span.name', [
23 | h('strong', name), when(port, [': ', port])
24 | ]),
25 | h('button.remove Button -warn', {
26 | 'ev-click': send(collection.remove, controller)
27 | }, 'X')
28 | ]),
29 | h('section', [
30 | h('ParamList', [
31 | ToggleButton(controller.includeInRecording, {
32 | title: 'Include in recording'
33 | }),
34 | ToggleButton(controller.monitor, {
35 | title: 'Monitor'
36 | })
37 | ])
38 | ])
39 | ])
40 | }
41 |
42 | function toggleParam (param) {
43 | param.set(!param())
44 | }
45 |
--------------------------------------------------------------------------------
/nodes/midi-sync-output/view.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var send = require('mutant/send')
3 | var when = require('mutant/when')
4 | var computed = require('mutant/computed')
5 |
6 | module.exports = renderMidiSync
7 |
8 | function renderMidiSync (controller) {
9 | var collection = controller.context.collection
10 | var name = computed(controller, x => x.name)
11 | var port = controller.port
12 | return h('GlobalControllerNode', {
13 | classList: [
14 | when(controller.minimised, '-minimised')
15 | ]
16 | }, [
17 | h('header', [
18 | h('button.twirl', {
19 | 'ev-click': send(toggleParam, controller.minimised)
20 | }),
21 | h('span.name', [
22 | h('strong', name), when(port, [': ', port])
23 | ]),
24 | h('button.remove Button -warn', {
25 | 'ev-click': send(collection.remove, controller)
26 | }, 'X')
27 | ]),
28 | h('section', [
29 | h('ParamList', [
30 | h('ModParam', [
31 | h('button.action', {'ev-click': send(controller.startSync)}, 'Send Start'), ' ',
32 | h('button.action', {
33 | 'ev-click': send(controller.stop),
34 | style: {'margin-left': '5px'}
35 | }, 'Send Stop')
36 | ])
37 | ])
38 | ])
39 | ])
40 | }
41 |
42 | function toggleParam (param) {
43 | param.set(!param())
44 | }
45 |
--------------------------------------------------------------------------------
/nodes/spatial-pan/object.js:
--------------------------------------------------------------------------------
1 | var Processor = require('lib/processor')
2 | var Param = require('lib/param')
3 | var Apply = require('lib/apply-param')
4 | var Multiply = require('lib/param-multiply')
5 | var ParamClamp = require('lib/param-clamp')
6 |
7 | module.exports = SpatialPanNode
8 |
9 | function SpatialPanNode (context) {
10 | var delayL = context.audio.createDelay(0.04)
11 | var delayR = context.audio.createDelay(0.04)
12 | var splitter = context.audio.createChannelSplitter(2)
13 | var merger = context.audio.createChannelMerger(2)
14 | var panner = context.audio.createStereoPanner()
15 |
16 | splitter.channelCount = 2
17 | splitter.channelCountMode = 'explicit'
18 |
19 | splitter.connect(delayL, 0)
20 | splitter.connect(delayR, 1)
21 | delayL.connect(merger, 0, 0)
22 | delayR.connect(merger, 0, 1)
23 | merger.connect(panner)
24 |
25 | var releases = []
26 |
27 | var obs = Processor(context, splitter, panner, {
28 | offset: Param(context, 0.1)
29 | }, releases)
30 |
31 | releases.push(
32 | Apply(context.audio, delayL.delayTime, Multiply([
33 | ParamClamp(obs.offset, 0, 1), 0.003
34 | ])),
35 | Apply(context.audio, delayR.delayTime, Multiply([
36 | ParamClamp(obs.offset, -1, 0), -0.003
37 | ])),
38 | Apply(context.audio, panner.pan, Multiply([
39 | obs.offset, 0.5
40 | ]))
41 | )
42 | return obs
43 | }
44 |
--------------------------------------------------------------------------------
/demo-project/DWS - Quantum Loop/Q Warp.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","slots":[{"id":"output","processors":[{"node":"processor/dipper","ratio":1.0182533675212468,"mode":"modulate"},{"node":"processor/filter","frequency":2500,"Q":1,"gain":0,"type":"lowpass"}],"node":"slot","noteOffset":0,"volume":1.0426}],"outputs":["output"],"selectedSlotId":"$template","scale":"$global","templateSlot":{"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.5796,"attack":0.01974395807139611,"release":0.016},"octave":-4,"noteOffset":0,"detune":0,"shape":"sine"},{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.5851,"attack":0.2996237677372736,"release":0.014485743365267778,"startValue":0},"detune":{"node":"modulator/adsr","value":2.0888980159988684,"attack":0.3372630042653024},"octave":0,"noteOffset":0}],"processors":[{"node":"processor/overdrive","cut":9231.437537779038,"gain":8,"color":61,"band":1.2825678005957046,"preBand":0.6636364748326865,"postCut":3000,"amp":1},{"node":"processor/filter","frequency":{"node":"modulator/lfo","value":1968.1946381662178,"amp":1713.1688465179009,"rate":6.6658595641646485,"mode":"add","trigger":true}},{"node":"processor/delay","time":0.1026393404866564}],"volume":1.0115,"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"output":"output","node":"slot"},"inputs":[]}
--------------------------------------------------------------------------------
/demo-project/Launchpad Example/pad.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","slots":[{"id":"output","processors":[{"node":"processor/dipper"}],"node":"slot"}],"outputs":["output"],"selectedSlotId":"$template","scale":"$global","templateSlot":{"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0,"attack":2.256754490111679,"release":0.957235496743463},"frequency":440,"octave":-2,"noteOffset":0},{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.6,"attack":1.7766753830242663,"release":1.4995717713989172},"shape":"triangle","detune":{"node":"modulator/lfo","value":0,"amp":18.014529823658684,"rate":11.214029444513175,"mode":"add","trigger":true},"octave":-1,"noteOffset":0},{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.2915,"attack":0.7702878926489645,"release":1.3246224917130511},"frequency":440,"shape":"square","octave":0,"noteOffset":0},{"node":"source/oscillator","amp":0.0272,"frequency":440,"shape":"sawtooth","octave":1,"noteOffset":5}],"processors":[{"node":"processor/overdrive","color":19755.784240480218,"postCut":{"node":"modulator/lfo","value":4204.3912087086155,"rate":0.4707476299044454,"amp":2541.3271807080737,"mode":"add","trigger":true}},{"node":"processor/delay"}],"volume":0.2713,"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"output":"output","node":"slot"},"inputs":[],"params":[]}
--------------------------------------------------------------------------------
/demo-project/Qwerty Keys Example/pad.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","slots":[{"id":"output","processors":[{"node":"processor/dipper"}],"node":"slot"}],"outputs":["output"],"selectedSlotId":"$template","scale":"$global","templateSlot":{"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0,"attack":2.256754490111679,"release":0.957235496743463},"frequency":440,"octave":-2,"noteOffset":0},{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.6,"attack":1.7766753830242663,"release":1.4995717713989172},"shape":"triangle","detune":{"node":"modulator/lfo","value":0,"amp":18.014529823658684,"rate":11.214029444513175,"mode":"add","trigger":true},"octave":-1,"noteOffset":0},{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.2915,"attack":0.7702878926489645,"release":1.3246224917130511},"frequency":440,"shape":"square","octave":0,"noteOffset":0},{"node":"source/oscillator","amp":0.0272,"frequency":440,"shape":"sawtooth","octave":1,"noteOffset":5}],"processors":[{"node":"processor/overdrive","color":19755.784240480218,"postCut":{"node":"modulator/lfo","value":4204.3912087086155,"rate":0.4707476299044454,"amp":2541.3271807080737,"mode":"add","trigger":true}},{"node":"processor/delay"}],"volume":0.2713,"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"output":"output","node":"slot"},"inputs":[],"params":[]}
--------------------------------------------------------------------------------
/lib/query-param.js:
--------------------------------------------------------------------------------
1 | var jsonQuery = require('json-query')
2 | var computed = require('mutant/computed')
3 | var resolve = require('mutant/resolve')
4 |
5 | module.exports = QueryParam
6 |
7 | function QueryParam (param, query, defaultValue) {
8 | var result = computed([param], function (value) {
9 | var res = jsonQuery(query, {data: value})
10 |
11 | // fallback to inner value
12 | res = res.value === undefined && param.node
13 | ? jsonQuery(query, {data: read(param.node)})
14 | : res
15 |
16 | return res.value === undefined ? defaultValue : res.value
17 | })
18 |
19 | result.set = function (value) {
20 | var newObject = obtain(resolve(param))
21 |
22 | var res = jsonQuery(query, {data: newObject, force: defaultValue})
23 | var obj = jsonQuery.lastParent(res)
24 |
25 | if (obj) {
26 | if (value === undefined) {
27 | delete obj[res.key]
28 | } else {
29 | obj[res.key] = value
30 | }
31 | param.set(newObject)
32 | return true
33 | } else {
34 | return false
35 | }
36 | }
37 |
38 | result.context = param.context
39 |
40 | return result
41 | }
42 |
43 | function obtain (obj) {
44 | return JSON.parse(JSON.stringify(obj))
45 | }
46 |
47 | function read (target) {
48 | if (typeof target === 'function') {
49 | return target()
50 | } else if (target && target.read) {
51 | return target.read()
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/import-sample.js:
--------------------------------------------------------------------------------
1 | var getBase = require('path').basename
2 | var getSoundOffset = require('lib/get-sound-offset')
3 | var resolveFileAvailable = require('lib/resolve-file-available')
4 | var copyFile = require('lib/copy-file')
5 |
6 | module.exports = importSample
7 |
8 | function importSample (context, filePath, cb) {
9 | var fileObject = context.fileObject
10 |
11 | var fileName = getBase(filePath)
12 | var from = filePath
13 | var to = fileObject.resolvePath('./' + fileName)
14 |
15 | if (from === to) {
16 | // already imported
17 | console.log('already imported')
18 | getInfo(context, fileObject, to, cb)
19 | } else {
20 | resolveFileAvailable(to, context.fs, function (err, to) {
21 | if (err) return cb && cb(err)
22 | copyFile(from, to, context.fs, function () {
23 | getInfo(context, fileObject, to, cb)
24 | })
25 | })
26 | }
27 | }
28 |
29 | function getInfo (context, fileObject, path, cb) {
30 | var relativeSrc = fileObject.relative(path)
31 | var obs = context.nodes.AudioBuffer(context)
32 |
33 | obs.currentValue(function (buffer) {
34 | if (buffer) {
35 | cb && cb(null, {
36 | buffer: { node: 'AudioBuffer', src: relativeSrc },
37 | offset: getSoundOffset(buffer) || [0, 1]
38 | })
39 |
40 | // destroy after callback to avoid double decode
41 | obs.destroy()
42 | }
43 | })
44 | obs.set({ src: relativeSrc })
45 | }
46 |
--------------------------------------------------------------------------------
/lib/rename-widget.js:
--------------------------------------------------------------------------------
1 | var getBaseName = require('path').basename
2 | var getExtName = require('path').extname
3 | var h = require('lib/h')
4 |
5 |
6 | module.exports = renameElement
7 |
8 | function renameElement (fileName, saveRename, cancelRename, data) {
9 | var element = h('span')
10 | var extname = getExtName(fileName)
11 |
12 | element.textContent = getBaseName(fileName, extname)
13 | element.contentEditable = true
14 |
15 | element.onkeydown = function (e) {
16 | if (e.keyCode === 13) {
17 | save()
18 | return false
19 | } else if (e.keyCode === 27) {
20 | cancelRename()
21 | return false
22 | }
23 | }
24 | element.onblur = function handleRenameBlur (e) {
25 | setImmediate(function () {
26 | if (element.parentNode) {
27 | // only save if still active!
28 | save()
29 | }
30 | })
31 | }
32 |
33 | setImmediate(function () {
34 | selectInside(element)
35 | })
36 |
37 | return element
38 |
39 | // scoped
40 |
41 | function save () {
42 | var value = element.textContent.trim() + extname
43 | if (value !== fileName) {
44 | saveRename(value)
45 | } else {
46 | cancelRename()
47 | }
48 | }
49 | }
50 |
51 | function selectInside (element) {
52 | var range = document.createRange()
53 | range.selectNodeContents(element)
54 | var sel = window.getSelection()
55 | sel.removeAllRanges()
56 | sel.addRange(range)
57 | }
58 |
--------------------------------------------------------------------------------
/demo-project/Launchpad Example/bass.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","slots":[{"id":"output","processors":[{"node":"processor/dipper","ratio":1.1194323036880616},{"node":"processor/reverb","time":0.068,"decay":2,"reverse":false,"cutoff":20000,"filterType":"lowpass","wet":4.599,"dry":1},{"node":"processor/overdrive","gain":5.743,"preBand":0.10639269092972742,"color":220,"postCut":2600,"amp":1},{"node":"processor/filter","frequency":190,"Q":1,"gain":-17.048777781243015,"type":"peaking"},{"node":"processor/filter","frequency":6900,"Q":0.38228166905568806,"gain":-18.276826037753082,"type":"peaking"}],"node":"slot","noteOffset":0,"volume":1}],"outputs":["output"],"selectedSlotId":"$template","scale":"$global","templateSlot":{"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.3625,"release":0.3044558742176154,"attack":0},"frequency":440,"shape":"sawtooth","octave":-3,"noteOffset":0},{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.1504,"attack":0,"release":0.10700607022835093},"frequency":440,"octave":-3,"noteOffset":0}],"processors":[{"node":"processor/filter","frequency":{"node":"modulator/adsr","value":1177.1673952359295,"attack":0,"release":0,"sustain":0.4120783242203967,"decay":0.2291123240186734},"Q":0.26548506516605186}],"chokeGroup":"bass","volume":0.5788,"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"output":"output","node":"slot"},"inputs":[],"params":[]}
--------------------------------------------------------------------------------
/lib/params/toggle-button.js:
--------------------------------------------------------------------------------
1 | var h = require('lib/h')
2 | var send = require('mutant/send')
3 | var computed = require('mutant/computed')
4 | var Set = require('mutant/set')
5 | var resolve = require('mutant/resolve')
6 |
7 | module.exports = function (param, options) {
8 | var value = computed([param, options.defaultValue || false], function (value, defaultValue) {
9 | return value == null ? defaultValue : value
10 | })
11 |
12 | var onValue = 'onValue' in options ? options.onValue : true
13 | var offValue = 'offValue' in options ? options.offValue : false
14 |
15 | var caption = computed([value, options.title, options.offTitle], function (value, title, offTitle) {
16 | return value === onValue ? title : (offTitle || title)
17 | })
18 |
19 | var classList = Set(options.classList)
20 |
21 | if (!options.custom) {
22 | classList.add('ToggleButton')
23 | }
24 |
25 | classList.add(computed([value], function (value) {
26 | return value === onValue ? '-active' : null
27 | }))
28 |
29 | return h('button', {
30 | classList: classList,
31 | title: options.description || '',
32 | 'ev-click': send(toggleValue, {
33 | param: param,
34 | onValue: onValue,
35 | offValue: offValue,
36 | value: value
37 | })
38 | }, caption)
39 | }
40 |
41 | function toggleValue (ev) {
42 | if (resolve(ev.value) !== ev.onValue) {
43 | ev.param.set(resolve(ev.onValue))
44 | } else {
45 | ev.param.set(resolve(ev.offValue))
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/styles/transport.mcss:
--------------------------------------------------------------------------------
1 | Transport {
2 | display: flex
3 | margin: -2px 0
4 |
5 | button {
6 | display: block;
7 | height: 34px
8 | width: 34px
9 | border-radius: 2px;
10 | border: #6B806B 1px solid;
11 | background-color: #2F2F2F;
12 | background-position: center;
13 | background-repeat: no-repeat;
14 | cursor: pointer;
15 | margin: 0 1px
16 | box-shadow: 1px 1px 3px #111
17 |
18 | -beginning {
19 | background-image: svg(beginning)
20 | }
21 |
22 | -play {
23 | background-image: svg(play)
24 | -active {
25 | background-image: svg(play -active)
26 | background-color: #264525;
27 | border-color: #58AE56;
28 | box-shadow: 0 0 10px #26611C
29 | }
30 | }
31 |
32 | :focus {
33 | outline-style: none;
34 | box-shadow: rgb(109, 232, 101) 0 0 2px;
35 | }
36 | }
37 |
38 | button.splice {
39 | margin-left: 10px
40 | color: #AAA
41 | font-size: 15px
42 | }
43 |
44 | @svg play {
45 | height: 16px
46 | width: 16px
47 | content: ""
48 |
49 | path {
50 | fill: #AAA
51 | }
52 |
53 | -active {
54 | path {
55 | fill: #AFA
56 | }
57 | }
58 | }
59 |
60 | @svg beginning {
61 | height: 16px
62 | width: 16px
63 | content: ""
64 |
65 | path {
66 | fill: #AAA
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/demo-project/Launchpad Example/synth.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","slots":[{"id":"output","processors":[{"node":"processor/filter","frequency":7600,"Q":1,"gain":2.1870010300322242,"type":"peaking"},{"node":"processor/reverb","time":0.379,"decay":2,"reverse":false,"cutoff":20000,"filterType":"lowpass","wet":1,"dry":1},{"node":"processor/overdrive","preBand":0.5,"color":3400,"postCut":22000,"gain":1.2688,"amp":1}],"node":"slot","noteOffset":0,"volume":1}],"outputs":["output"],"selectedSlotId":"$template","scale":"$global","templateSlot":{"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.737,"attack":0.022438594109078478,"release":0.021323813414298707},"shape":"sawtooth","detune":{"node":"modulator/lfo","value":0,"amp":13.678323601175872,"rate":7.662933566816159,"mode":"add","trigger":true},"octave":-1,"noteOffset":0},{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.3468,"attack":0.39887686883483425,"release":0.2153259646038883},"frequency":440,"shape":"square","octave":0,"noteOffset":0}],"processors":[{"node":"processor/filter","frequency":{"node":"modulator/adsr","value":17464.588838816086,"attack":0.049,"startValue":300.53407259519037,"decay":0.191,"sustain":0.05523259137853783},"type":"lowpass","Q":1,"gain":0},{"node":"processor/filter","frequency":528.7087523748194,"type":"highpass"}],"volume":0.3783,"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"output":"output","node":"slot"},"inputs":[],"params":[]}
--------------------------------------------------------------------------------
/demo-project/DWS - OST/synth.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","templateSlot":{"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"node":"slot","output":"output","volume":1,"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.3665,"release":2.9,"decay":{"node":"linkParam","minValue":0.1,"maxValue":2,"param":"decay","mode":"linear"},"sustain":0.02},"frequency":440,"noteOffset":0,"octave":0,"detune":0,"shape":"square"}]},"outputs":["output"],"slots":[{"id":"output","node":"slot","volume":0.6647,"noteOffset":0,"processors":[{"node":"processor/reverb"},{"node":"processor/ring-modulator","carrier":{"amp":0.8894,"frequency":440,"noteOffset":0,"octave":0,"detune":0,"shape":"sawtooth"}},{"node":"processor/filter","frequency":{"node":"linkParam","minValue":17000,"maxValue":140,"param":"cutoff","mode":"exp"},"Q":1,"gain":0,"type":"lowpass"},{"node":"processor/filter","frequency":475,"Q":1,"gain":0,"type":"highpass"},{"node":"processor/ping-pong-delay","time":0.3333333333333333,"sync":true,"feedback":{"node":"linkParam","minValue":0.139,"maxValue":0.8617,"param":"wet","mode":"linear"},"cutoff":20000,"filterType":"lowpass","wet":{"node":"linkParam","minValue":0,"maxValue":0.8602,"param":"wet","mode":"linear"},"dry":1},{"node":"processor/dipper","mode":"modulate","ratio":{"node":"linkParam","minValue":0,"maxValue":1.2087565425730242,"param":"wet","mode":"linear"}}]}],"selectedSlotId":"output","scale":"$global","inputs":[],"params":["cutoff","decay","wet"]}
--------------------------------------------------------------------------------
/demo-project/Qwerty Keys Example/synth.json:
--------------------------------------------------------------------------------
1 | {"node":"chunk/scale","slots":[{"id":"output","processors":[{"node":"processor/filter","frequency":7600,"Q":1,"gain":2.1870010300322242,"type":"peaking"},{"node":"processor/reverb","time":0.379,"decay":2,"reverse":false,"cutoff":20000,"filterType":"lowpass","wet":1,"dry":1},{"node":"processor/overdrive","preBand":0.5,"color":3400,"postCut":22000,"gain":1.2688,"amp":1}],"node":"slot","noteOffset":0,"volume":1}],"outputs":["output"],"selectedSlotId":"$template","scale":"$global","templateSlot":{"sources":[{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.737,"attack":0.022438594109078478,"release":0.021323813414298707},"shape":"sawtooth","detune":{"node":"modulator/lfo","value":0,"amp":13.678323601175872,"rate":7.662933566816159,"mode":"add","trigger":true},"octave":-1,"noteOffset":0},{"node":"source/oscillator","amp":{"node":"modulator/adsr","value":0.3468,"attack":0.39887686883483425,"release":0.2153259646038883},"frequency":440,"shape":"square","octave":0,"noteOffset":0,"detune":0}],"processors":[{"node":"processor/filter","frequency":{"node":"modulator/adsr","value":17464.588838816086,"attack":0.049,"startValue":300.53407259519037,"decay":0.191,"sustain":0.05523259137853783},"type":"lowpass","Q":1,"gain":0},{"node":"processor/filter","frequency":528.7087523748194,"type":"highpass"}],"volume":0.3783,"id":{"$param":"id"},"noteOffset":{"node":"modulator/scale","value":{"$param":"value"},"offset":{"$param":"offset"},"scale":{"$param":"scale"}},"output":"output","node":"slot"},"inputs":[],"params":[]}
--------------------------------------------------------------------------------
/styles/recording-node.mcss:
--------------------------------------------------------------------------------
1 | RecordingNode {
2 | display: flex
3 | flex-direction: column
4 | flex: 1
5 | overflow: hidden
6 |
7 | div.main {
8 | flex: 1
9 | display: flex
10 |
11 | :focus {
12 | outline: none
13 | }
14 | }
15 |
16 | div.options {
17 |
18 | display: flex;
19 | background: #222;
20 | box-shadow: 0 0 5px #000;
21 | margin-top: 1px;
22 | z-index: 100;
23 | flex-shrink: 0
24 | justify-content: space-between
25 | position: relative
26 |
27 | progress {
28 | position: absolute;
29 | left: 0;
30 | right: 0;
31 | top: -8px;
32 | width: 100%;
33 | }
34 |
35 | section {
36 | padding: 14px;
37 | display: flex
38 | h1 {
39 | flex: none
40 | margin-right: 5px
41 | }
42 | progress {
43 | margin-left: 10px
44 | height: 30px
45 | width: 200px
46 | }
47 | div.param {
48 | flex: 1
49 | display: flex
50 | margin: 3px;
51 | }
52 | div.chooser {
53 | flex: 1
54 | display: flex
55 | }
56 |
57 | button.export {
58 | background-color: #444;
59 | border: 1px solid #777;
60 | color: #FFF;
61 | box-shadow: 1px 1px 3px #111;
62 | cursor: pointer;
63 |
64 | :hover {
65 | background-color: #222;
66 | }
67 |
68 | :focus {
69 | border-color: white
70 | outline: none
71 | }
72 | }
73 | }
74 |
75 | }
76 | }
--------------------------------------------------------------------------------
/lib/extend-params.js:
--------------------------------------------------------------------------------
1 | var computed = require('mutant/computed')
2 | var MutantArray = require('mutant/array')
3 | var resolveAvailable = require('lib/resolve-available')
4 |
5 | module.exports = function extendParams (obs) {
6 | var paramOverrideStack = MutantArray([])
7 | obs.overrideParams = function (params) {
8 | paramOverrideStack.push(params)
9 | return function release () {
10 | paramOverrideStack.delete(params)
11 | }
12 | }
13 |
14 | var raw = {}
15 |
16 | var paramLookup = computed([obs.params, obs.paramValues, paramOverrideStack], function (params, values, overrides) {
17 | var result = {}
18 | var rawResult = {}
19 | for (var i = 0; i < params.length; i++) {
20 | var key = params[i]
21 | var override = paramOverrideStack.get(paramOverrideStack.getLength() - 1)
22 | if (override && override[i] != null) {
23 | result[key] = typeof override[i] === 'function' ? override[i]() : override[i] || 0
24 | rawResult[key] = override[i]
25 | } else {
26 | result[key] = values && values[key] || 0
27 | rawResult[key] = obs.paramValues.get(key)
28 | }
29 | }
30 | raw = rawResult
31 | return result
32 | })
33 |
34 | paramLookup.get = function (key) {
35 | return raw[key]
36 | }
37 |
38 | paramLookup.keys = function (key) {
39 | return Object.keys(raw)
40 | }
41 |
42 | obs.context.paramLookup = paramLookup
43 |
44 | obs.resolveAvailableParam = function (id, lastId) {
45 | return resolveAvailable(obs.params(), id, lastId)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/styles/toggle-button.mcss:
--------------------------------------------------------------------------------
1 | ToggleButton {
2 | text-decoration: none;
3 | font-size: 90%;
4 | padding: 2px 5px;
5 | border-radius: 4px;
6 | display: inline-block;
7 | border: #949a94 1px solid;
8 | background: #6F6F6F;
9 | color: #D0D0D0;
10 | cursor: pointer;
11 |
12 | -main {
13 | border-radius: 0
14 | padding: 5px 7px
15 | font-size: 110%
16 | display: block
17 |
18 | :after {
19 | border-radius: 4px;
20 | width: 8px;
21 | height: 8px;
22 | }
23 | }
24 |
25 | -active {
26 | background: #325736;
27 | :after {
28 | background: #82D182;
29 | }
30 | }
31 |
32 | -record {
33 | border: #888888 1px solid
34 |
35 | :after {
36 | background: #AEAEAE;
37 | }
38 |
39 | :focus {
40 | box-shadow: rgb(232, 109, 101) 0 0 2px;
41 | }
42 |
43 | :hover {
44 | border-color: #BD8489;
45 | color: #FFEEEE;
46 | }
47 |
48 | -active {
49 | background: #573236;
50 |
51 | :after {
52 | background: #D18282;
53 | }
54 | }
55 | }
56 |
57 | :focus {
58 | outline-style: none;
59 | color: white;
60 | box-shadow: rgb(109, 232, 101) 0 0 2px;
61 | }
62 |
63 | :hover {
64 | border-color: #84BD89;
65 | color: #D6FFC9;
66 | }
67 |
68 | :after {
69 | display: inline-block;
70 | border-radius: 3px;
71 | width: 6px;
72 | height: 6px;
73 | background: #AAA4A4;
74 | content: '';
75 | margin-left: 3px;
76 | vertical-align: middle;
77 | margin-bottom: 1px;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------