├── .gitignore
├── 404.md
├── LICENSE
├── README.md
├── _layouts
└── default.html
├── app.html
├── index.md
├── jams
├── arithmetic.json
├── clock01.json
├── clock02.json
├── concepts
│ ├── bassSequence.json
│ ├── chordArp.json
│ ├── complexRouting.json
│ ├── delayedMelody.json
│ ├── digicat.json
│ ├── kickDrum.json
│ ├── midiArpeggio.json
│ ├── mixture01.json
│ └── sampleChopping.json
├── flange.json
├── linearad.json
├── note2freq.json
├── quad.json
├── sampler.json
├── scopes.json
├── sineosc01.json
├── sineosc02.json
└── switch.json
├── pages
├── cover.jpg
├── docs.md
├── examples.md
└── history.md
└── src
├── ApplicationPreference.js
├── AudioModule.js
├── AudioModules
├── Clock.js
├── ClockDivider.js
├── Delay.js
├── Flange.js
├── LinearAD.js
├── MidiKeyboard.js
├── MonoMerge.js
├── Multiplier.js
├── Note2Freq.js
├── Number.js
├── Output.js
├── Plus.js
├── QuadMixer.js
├── Rand.js
├── Readout.js
├── Remainder.js
├── Sampler.js
├── Scope.js
├── Sequencer16.js
├── SineOSC.js
├── Switch.js
├── TextDisplay.js
└── XYScope.js
├── Desktop.js
├── Graphics.js
├── Interface.js
├── InterfaceElements
├── InterfaceContextMenu.js
├── InterfaceMenuBar.js
├── InterfaceSprinkles.js
├── InterfaceWindow.js
└── WindowComponents
│ ├── WindowButton.js
│ ├── WindowCheckBox.js
│ ├── WindowColor.js
│ ├── WindowFileUpload.js
│ ├── WindowIntegerSlider.js
│ ├── WindowListOptions.js
│ ├── WindowNumber.js
│ ├── WindowPalette.js
│ ├── WindowText.js
│ └── WindowTextField.js
├── JAMS.Interactions.js
├── JAMS.js
├── ListenerList.js
├── MIDI.js
├── Utils
├── FileSaver.min.js
└── WaveReader.js
├── jams.plugins.js
├── misaki.png
└── misaki3x.png
/.gitignore:
--------------------------------------------------------------------------------
1 | sketches/
2 | _site/
--------------------------------------------------------------------------------
/404.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | permalink: /404.html
4 | ---
5 |
6 | # 404! Not Found!
7 |
8 | Looks like you've hit a dead-end.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 KHOIN
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | JAMS: Just A Modular System
2 | =====
3 |
4 | [Homepage](http://jams.systems)
5 |
6 | 
7 |
8 | A Modular System that makes use of the Web Audio API.
9 |
10 | ## Features
11 |
12 | * Modules
13 | * Arithmetics (Adding, Multiplying, Modulus)
14 | * Sine Oscillator, with flexible Phase Modulation routing.
15 | * Clocks and Clock Divider
16 | * MIDI modules. Current version of JAMS only has MIDI note to Hertz converter.
17 | * Sequencer. Increment by pulses, 16-step sequencer.
18 | * Switch
19 | * Envelopes: Attack-Decay.
20 | * A simple Sampler with inputs for start/end, trigger restart, and playback rate.
21 | * Effects: Flange, Delay
22 | * Miscellaneous: 4-channel sum, Oscilloscope, XY Scope, Merging L/R.
23 | * Loading WAV files to Sampler.
24 | * Saving samples with projects. Currently, storing at 15-bit per sample.
25 |
26 | ## Acknowledgments
27 |
28 | JAMS would not be possible without the help and inspiration from:
29 |
30 | * Werner Van Belle from [BpmDj](http://bpmdj.yellowcouch.org/) for tips on feedback sine phase modulation and [Flange.js](src/AudioModules/Flange.js)
31 | * [Audiotool](https://audiotool.com/) team and friends for motivation and inspiration for electronic music and experimental music.
32 | * [@stagas](https://github.com/stagas) from [Wavepot](https://wavepot.com/) for my first playground into DSP. Some modules in JAMS have code borrowed from [OpenDSP](https://github.com/opendsp)
33 | * [my roommate](https://github.com/pensono) for how suggestions on stereo data flow and WAV decoding.
34 | * [@mog](https://github.com/mog) from [trbl.at](trbl.at) for WebMIDI helper class.
35 |
36 | ## Other third-parties
37 |
38 | * JAMS uses a modified/cropped version of Misaki 8x8 font. [Source](http://www.geocities.jp/littlimi/misaki.htm) [License](http://www.geocities.jp/littlimi/font.htm)
39 | * FileSaver.js [GitHub](https://github.com/eligrey/FileSaver.js/)
40 |
41 | ## License
42 |
43 | [MIT](LICENSE)
--------------------------------------------------------------------------------
/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ page.title }}
5 |
6 |
7 |
8 |
9 |
23 |
24 |
25 |
26 | JAMS: Just A Modular System
27 |
28 |
29 | Home
30 | Examples
31 | Documentation
32 | Development
33 | Launch App
34 |
35 |
38 |
41 |
42 |
--------------------------------------------------------------------------------
/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | JAMS
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
84 |
85 |
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: JAMS - Just A Modular System
4 | ---
5 |
6 | **JAMS** is a **J**ust **A** **M**odular **S**ystem written in Javascript, and runs in your Web browser. It currently makes use of the deprecating Web Audio's [ScriptProcessorNode](https://developer.mozilla.org/en-US/docs/Web/API/ScriptProcessorNode). Check out the GitHub [repo](https://github.com/khoin/JAMS)!
7 |
8 | Please lower your speaker/headphones volume before opening the app.
9 |
10 | 
11 | _JAMS as of 2017, July 29th_
12 |
13 | # Usage & Development
14 |
15 | I originally created this with the intention of arbitrary phase modulation between different sine oscillators. It now has sequencing, envelopes, switches and sampling. You can make some cool noises out of it. The old version had an interface module with MIDI; I will bring it back in this version as well as MPE integration.
16 |
17 | See the [Development History](/history).
18 |
19 | # Examples & Documentation
20 |
21 | A list of examples per modules and per concepts is available in the [Examples](/examples) section.
22 |
23 | [Documentation](/docs) for each module is also available.
24 |
25 | # Acknowledgments
26 |
27 | JAMS would not be possible without the help and inspiration from:
28 |
29 | * Werner Van Belle from [BpmDj](http://bpmdj.yellowcouch.org/) for tips on feedback sine phase modulation and Flange.
30 | * [Audiotool](https://audiotool.com/) team and friends for motivation and inspiration for electronic music and experimental music.
31 | * @stagas from [Wavepot](https://wavepot.com/) for my first playground into DSP. Some modules in JAMS have code borrowed from [OpenDSP](https://github.com/opendsp).
32 | * my roommate for suggestions on stereo data flow and WAV decoding.
33 | * @mog for MIDI helper class
--------------------------------------------------------------------------------
/jams/arithmetic.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":978,"y":527.5,"inputs":[],"params":[],"name":"OUT"},{"id":2,"type":"Number","x":659,"y":426,"inputs":[],"params":[1,200],"name":"number"},{"id":3,"type":"Number","x":659.5,"y":473,"inputs":[],"params":[1,200],"name":"number"},{"id":4,"type":"Plus","x":777.5,"y":442,"inputs":[{"id":2,"index":0},{"id":3,"index":0}],"params":[],"name":"plus"},{"id":5,"type":"Readout","x":866.5,"y":442,"inputs":[{"id":4,"index":0}],"params":[],"name":"Readout"},{"id":6,"type":"Number","x":658.5,"y":509.5,"inputs":[],"params":[7,200],"name":"number"},{"id":7,"type":"Multiplier","x":778.5,"y":528,"inputs":[{"id":6,"index":0},{"id":8,"index":0}],"params":[],"name":"multiplier"},{"id":8,"type":"Number","x":658.5,"y":560.5,"inputs":[],"params":[3,200],"name":"number"},{"id":9,"type":"Readout","x":866,"y":527.5,"inputs":[{"id":7,"index":0}],"params":[],"name":"Readout"},{"id":10,"type":"Remainder","x":781.5,"y":609.5,"inputs":[{"id":11,"index":0},{"id":12,"index":0}],"params":[],"name":"remainder"},{"id":11,"type":"Number","x":661.5,"y":591.5,"inputs":[],"params":[9,200],"name":"number"},{"id":12,"type":"Number","x":662.5,"y":642,"inputs":[],"params":[4,200],"name":"number"},{"id":13,"type":"Readout","x":867,"y":610,"inputs":[{"id":10,"index":0}],"params":[],"name":"Readout"}]
--------------------------------------------------------------------------------
/jams/clock01.json:
--------------------------------------------------------------------------------
1 | [{"id":14,"type":"Output","x":973,"y":484,"inputs":[],"params":[],"name":"OUT"},{"id":15,"type":"Clock","x":603,"y":387,"inputs":[],"params":[120],"name":"clock"},{"id":16,"type":"Readout","x":823,"y":374,"inputs":[{"id":15,"index":0}],"params":[],"name":"Readout"},{"id":18,"type":"Remainder","x":785,"y":486,"inputs":[{"id":21,"index":0},{"id":19,"index":0}],"params":[],"name":"remainder"},{"id":19,"type":"Number","x":568,"y":499,"inputs":[],"params":[440,200],"name":"number"},{"id":20,"type":"SineOSC","x":861,"y":485,"inputs":[{"id":18,"index":0}],"params":[],"name":"SINE OSC"},{"id":21,"type":"Multiplier","x":723,"y":434,"inputs":[{"id":15,"index":0},{"id":19,"index":0}],"params":[],"name":"multiplier"},{"id":23,"type":"Readout","x":870,"y":436,"inputs":[{"id":18,"index":0}],"params":[],"name":"Readout"},{"id":26,"type":"TextDisplay","x":719,"y":546,"inputs":[],"params":["First output of Clock simply\ngives the number of seconds since\nthe app launches",270,30],"name":"number"}]
--------------------------------------------------------------------------------
/jams/clock02.json:
--------------------------------------------------------------------------------
1 | [{"id":14,"type":"Output","x":960,"y":475,"inputs":[],"params":[],"name":"OUT"},{"id":15,"type":"Clock","x":646,"y":413,"inputs":[],"params":[120],"name":"clock"},{"id":16,"type":"ClockDivider","x":671,"y":460,"inputs":[{"id":15,"index":1}],"params":[],"name":"cdivider"},{"id":17,"type":"Sequencer16","x":783.5,"y":490,"inputs":[{"id":16,"index":2}],"params":[{"0":1.4285714626312256,"1":0,"2":0,"3":0,"4":0,"5":1.571428656578064,"6":0,"7":0,"8":0,"9":0,"10":1.1428571939468384,"11":0,"12":0,"13":0,"14":0,"15":1.8571430444717407}],"name":"seq16"},{"id":18,"type":"Readout","x":797.5,"y":442,"inputs":[{"id":17,"index":0}],"params":[],"name":"Readout"},{"id":19,"type":"TextDisplay","x":748,"y":573.5,"inputs":[],"params":["Clock also outputs pulses at 1/64\nbased on the BPM which you can\nchange in Properties (right click\nthe module). ClockDivider divides\nthose pulses.",280,60],"name":"number"}]
--------------------------------------------------------------------------------
/jams/concepts/bassSequence.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":996,"y":474,"inputs":[{"id":8,"index":0}],"params":[],"name":"OUT"},{"id":2,"type":"Clock","x":533,"y":422,"inputs":[],"params":[120],"name":"clock"},{"id":3,"type":"ClockDivider","x":568,"y":476,"inputs":[{"id":2,"index":1}],"params":[],"name":"cdivider"},{"id":4,"type":"Sequencer16","x":678.5,"y":461,"inputs":[{"id":3,"index":3}],"params":[{"0":0,"1":3.2857139110565186,"2":2.5714287757873535,"3":5.142856597900391,"4":2.4285712242126465,"5":5.714285373687744,"6":4.571428298950195,"7":7.428569793701172,"8":4.857142448425293,"9":7.142856121063232,"10":6.999998569488525,"11":9.428573608398438,"12":6,"13":9.428572654724121,"14":8.285712242126465,"15":4.000000476837158}],"name":"seq16"},{"id":5,"type":"Note2Freq","x":709.5,"y":363,"inputs":[{"id":9,"index":0}],"params":[440,0,12],"name":"number"},{"id":6,"type":"SineOSC","x":812.5,"y":445,"inputs":[{"id":5,"index":0},{"id":8,"index":0}],"params":[],"name":"SINE OSC"},{"id":7,"type":"LinearAD","x":780.5,"y":541,"inputs":[{"id":3,"index":2},null,{"id":11,"index":0}],"params":[],"name":"linearad"},{"id":8,"type":"Multiplier","x":901.5,"y":478.5,"inputs":[{"id":6,"index":0},{"id":7,"index":0}],"params":[],"name":"multiplier"},{"id":9,"type":"Plus","x":730,"y":405,"inputs":[{"id":10,"index":0},{"id":4,"index":0}],"params":[],"name":"plus"},{"id":10,"type":"Number","x":600,"y":382.5,"inputs":[],"params":[40,200],"name":"number"},{"id":11,"type":"Number","x":636,"y":568,"inputs":[],"params":[0.10499999999999941,200],"name":"number"}]
--------------------------------------------------------------------------------
/jams/concepts/chordArp.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":1162.5,"y":330,"inputs":[{"id":4,"index":0}],"params":[],"name":"OUT"},{"id":2,"type":"Clock","x":293.5,"y":122.5,"inputs":[],"params":[60],"name":"clock"},{"id":3,"type":"ClockDivider","x":319.5,"y":199,"inputs":[{"id":2,"index":1}],"params":[],"name":"cdivider"},{"id":4,"type":"QuadMixer","x":1093,"y":382,"inputs":[{"id":31,"index":0},{"id":21,"index":0},{"id":25,"index":0},{"id":27,"index":0}],"params":[],"name":"quadmixer"},{"id":5,"type":"SineOSC","x":891.5,"y":188.5,"inputs":[{"id":10,"index":0},{"id":31,"index":0}],"params":[],"name":"SINE OSC"},{"id":6,"type":"SineOSC","x":909.5,"y":285,"inputs":[{"id":9,"index":0},{"id":21,"index":0}],"params":[],"name":"SINE OSC"},{"id":7,"type":"SineOSC","x":922,"y":407.5,"inputs":[{"id":11,"index":0},{"id":25,"index":0}],"params":[],"name":"SINE OSC"},{"id":8,"type":"SineOSC","x":931.5,"y":533,"inputs":[{"id":14,"index":0},{"id":27,"index":0}],"params":[],"name":"SINE OSC"},{"id":9,"type":"Multiplier","x":826,"y":289,"inputs":[{"id":10,"index":0},{"id":12,"index":0}],"params":[],"name":"multiplier"},{"id":10,"type":"Note2Freq","x":647,"y":162.5,"inputs":[{"id":16,"index":0}],"params":[440,0,12],"name":"number"},{"id":11,"type":"Multiplier","x":852,"y":413.5,"inputs":[{"id":10,"index":0},{"id":13,"index":0}],"params":[],"name":"multiplier"},{"id":12,"type":"Number","x":704.5,"y":297.5,"inputs":[],"params":[1.25,200],"name":"number"},{"id":13,"type":"Number","x":706.5,"y":430.5,"inputs":[],"params":[1.5,200],"name":"number"},{"id":14,"type":"Multiplier","x":851,"y":525.5,"inputs":[{"id":15,"index":0},{"id":10,"index":0}],"params":[],"name":"multiplier"},{"id":15,"type":"Number","x":722.5,"y":536,"inputs":[],"params":[1.8,200],"name":"number"},{"id":16,"type":"Plus","x":564,"y":146.5,"inputs":[{"id":17,"index":0},{"id":18,"index":0}],"params":[],"name":"plus"},{"id":17,"type":"Number","x":399.5,"y":116.5,"inputs":[],"params":[50,200],"name":"number"},{"id":18,"type":"Sequencer16","x":435.5,"y":182.5,"inputs":[{"id":3,"index":4}],"params":[{"0":12.28571605682373,"1":5.285715103149414,"2":10.714284896850586,"3":3.857142925262451,"4":8.571429252624512,"5":2.2857143878936768,"6":7.7142839431762695,"7":0,"8":4.571426868438721,"9":5.142856597900391,"10":10.142857551574707,"11":3.7142856121063232,"12":8.71428394317627,"13":2.4285712242126465,"14":7.999999523162842,"15":0}],"name":"seq16"},{"id":20,"type":"LinearAD","x":611,"y":360.5,"inputs":[{"id":33,"index":0}],"params":[],"name":"linearad"},{"id":21,"type":"Multiplier","x":910.5,"y":343.5,"inputs":[{"id":20,"index":0},{"id":6,"index":0}],"params":[],"name":"multiplier"},{"id":24,"type":"LinearAD","x":611,"y":475,"inputs":[{"id":28,"index":0}],"params":[],"name":"linearad"},{"id":25,"type":"Multiplier","x":931,"y":468,"inputs":[{"id":24,"index":0},{"id":7,"index":0}],"params":[],"name":"multiplier"},{"id":26,"type":"LinearAD","x":609,"y":587,"inputs":[{"id":30,"index":0}],"params":[],"name":"linearad"},{"id":27,"type":"Multiplier","x":943,"y":586,"inputs":[{"id":26,"index":0},{"id":8,"index":0}],"params":[],"name":"multiplier"},{"id":28,"type":"Delay","x":474,"y":474,"inputs":[{"id":33,"index":0},{"id":29,"index":0}],"params":[],"name":"Delay"},{"id":29,"type":"Number","x":262,"y":490,"inputs":[],"params":[6000,200],"name":"number"},{"id":30,"type":"Delay","x":475,"y":588,"inputs":[{"id":28,"index":0},{"id":29,"index":0}],"params":[],"name":"Delay"},{"id":31,"type":"Multiplier","x":900,"y":241,"inputs":[{"id":32,"index":0},{"id":5,"index":0}],"params":[],"name":"multiplier"},{"id":32,"type":"LinearAD","x":615,"y":259,"inputs":[{"id":3,"index":4}],"params":[],"name":"linearad"},{"id":33,"type":"Delay","x":478,"y":360,"inputs":[{"id":3,"index":4},{"id":29,"index":0}],"params":[],"name":"Delay"}]
--------------------------------------------------------------------------------
/jams/concepts/complexRouting.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":998.5,"y":424,"inputs":[{"id":4,"index":0}],"params":[],"name":"OUT"},{"id":2,"type":"SineOSC","x":719,"y":423,"inputs":[null,{"id":3,"index":0}],"params":[],"name":"SINE OSC"},{"id":3,"type":"SineOSC","x":718.5,"y":502.5,"inputs":[{"id":5,"index":0},{"id":6,"index":0}],"params":[],"name":"SINE OSC"},{"id":4,"type":"QuadMixer","x":905,"y":423.5,"inputs":[{"id":2,"index":0},{"id":3,"index":0}],"params":[],"name":"quadmixer"},{"id":5,"type":"Number","x":592.5,"y":493.5,"inputs":[],"params":[110,200],"name":"number"},{"id":6,"type":"SineOSC","x":716.5,"y":577,"inputs":[{"id":7,"index":0},{"id":9,"index":0}],"params":[],"name":"SINE OSC"},{"id":7,"type":"Number","x":600.5,"y":559.5,"inputs":[],"params":[55.42000000000007,200],"name":"number"},{"id":9,"type":"Plus","x":832.5,"y":527,"inputs":[{"id":6,"index":0},{"id":3,"index":0}],"params":[],"name":"plus"}]
--------------------------------------------------------------------------------
/jams/concepts/delayedMelody.json:
--------------------------------------------------------------------------------
1 | [{"id":34,"type":"Output","x":1186,"y":490.5,"inputs":[{"id":55,"index":0}],"params":[],"name":"OUT"},{"id":35,"type":"Switch","x":756,"y":463,"inputs":[{"id":46,"index":0},{"id":36,"index":0},{"id":37,"index":0},{"id":38,"index":0},{"id":39,"index":0},{"id":40,"index":0},{"id":41,"index":0},{"id":42,"index":0}],"params":[7.099999999999996],"name":"number"},{"id":36,"type":"Number","x":588.5,"y":479,"inputs":[],"params":[0,200],"name":"number"},{"id":37,"type":"Number","x":589.5,"y":498.5,"inputs":[],"params":[2,200],"name":"number"},{"id":38,"type":"Number","x":589.5,"y":516.5,"inputs":[],"params":[4,200],"name":"number"},{"id":39,"type":"Number","x":588.5,"y":538,"inputs":[],"params":[5,200],"name":"number"},{"id":40,"type":"Number","x":590.5,"y":562.5,"inputs":[],"params":[7,200],"name":"number"},{"id":41,"type":"Number","x":589,"y":589,"inputs":[],"params":[9,200],"name":"number"},{"id":42,"type":"Number","x":595,"y":626,"inputs":[],"params":[11,200],"name":"number"},{"id":43,"type":"Clock","x":557.5,"y":318,"inputs":[],"params":[120],"name":"clock"},{"id":44,"type":"ClockDivider","x":562.5,"y":380,"inputs":[{"id":43,"index":1}],"params":[],"name":"cdivider"},{"id":45,"type":"Rand","x":652.5,"y":427.5,"inputs":[{"id":44,"index":3}],"params":[],"name":"linear decay"},{"id":46,"type":"Multiplier","x":734,"y":405.5,"inputs":[{"id":40,"index":0},{"id":45,"index":0}],"params":[],"name":"multiplier"},{"id":47,"type":"Plus","x":851,"y":444.5,"inputs":[{"id":48,"index":0},{"id":35,"index":0}],"params":[],"name":"plus"},{"id":48,"type":"Number","x":832,"y":392.5,"inputs":[],"params":[55,200],"name":"number"},{"id":49,"type":"SineOSC","x":953.5,"y":506,"inputs":[{"id":59,"index":0},{"id":49,"index":0}],"params":[],"name":"SINE OSC"},{"id":50,"type":"Note2Freq","x":825.5,"y":505,"inputs":[{"id":47,"index":0}],"params":[440,0,12],"name":"number"},{"id":51,"type":"SineOSC","x":961.5,"y":561.5,"inputs":[{"id":52,"index":0},{"id":51,"index":0}],"params":[],"name":"SINE OSC"},{"id":52,"type":"Delay","x":844.5,"y":568,"inputs":[{"id":50,"index":0},{"id":57,"index":0}],"params":[],"name":"Delay"},{"id":54,"type":"Plus","x":1054.5,"y":524,"inputs":[{"id":49,"index":0},{"id":51,"index":0}],"params":[],"name":"plus"},{"id":55,"type":"Multiplier","x":1100,"y":476.5,"inputs":[{"id":56,"index":0},{"id":54,"index":0}],"params":[],"name":"multiplier"},{"id":56,"type":"Number","x":1059,"y":414.5,"inputs":[],"params":[0.33,200],"name":"number"},{"id":57,"type":"Number","x":729,"y":605.5,"inputs":[],"params":[48000,200],"name":"number"},{"id":59,"type":"Plus","x":940.5,"y":465,"inputs":[{"id":60,"index":0},{"id":50,"index":0}],"params":[],"name":"plus"},{"id":60,"type":"Number","x":961.5,"y":434,"inputs":[],"params":[1,200],"name":"number"}]
--------------------------------------------------------------------------------
/jams/concepts/kickDrum.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":1042,"y":466.5,"inputs":[{"id":13,"index":0}],"params":[],"name":"OUT"},{"id":2,"type":"SineOSC","x":946.5,"y":432.5,"inputs":[{"id":12,"index":0},{"id":13,"index":0}],"params":[],"name":"SINE OSC"},{"id":3,"type":"Clock","x":610,"y":393.5,"inputs":[],"params":[120],"name":"clock"},{"id":4,"type":"ClockDivider","x":548.5,"y":455,"inputs":[{"id":3,"index":1}],"params":[],"name":"cdivider"},{"id":5,"type":"LinearAD","x":660,"y":528,"inputs":[{"id":4,"index":4},{"id":10,"index":0},{"id":11,"index":0}],"params":[],"name":"linearad"},{"id":6,"type":"Number","x":737,"y":408,"inputs":[],"params":[40,200],"name":"number"},{"id":7,"type":"Multiplier","x":870,"y":553,"inputs":[{"id":9,"index":0},{"id":8,"index":0}],"params":[],"name":"multiplier"},{"id":8,"type":"Number","x":745,"y":554.5,"inputs":[],"params":[150,200],"name":"number"},{"id":9,"type":"Multiplier","x":783.5,"y":496.5,"inputs":[{"id":5,"index":0},{"id":5,"index":0}],"params":[],"name":"multiplier"},{"id":10,"type":"Number","x":514.5,"y":545.5,"inputs":[],"params":[0.0001,200],"name":"number"},{"id":11,"type":"Number","x":542.5,"y":572.5,"inputs":[],"params":[0.09899999999999944,200],"name":"number"},{"id":12,"type":"Plus","x":864,"y":420,"inputs":[{"id":6,"index":0},{"id":7,"index":0}],"params":[],"name":"plus"},{"id":13,"type":"Multiplier","x":942,"y":497,"inputs":[{"id":2,"index":0},{"id":9,"index":0}],"params":[],"name":"multiplier"}]
--------------------------------------------------------------------------------
/jams/concepts/midiArpeggio.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":960,"y":475,"inputs":[{"id":2,"index":0}],"params":[],"name":"OUT"},{"id":2,"type":"QuadMixer","x":873,"y":475.5,"inputs":[{"id":3,"index":0}],"params":[],"name":"quadmixer"},{"id":3,"type":"SineOSC","x":726.5,"y":498,"inputs":[{"id":7,"index":0},{"id":14,"index":0}],"params":[],"name":"SINE OSC"},{"id":4,"type":"ClockDivider","x":361.5,"y":468,"inputs":[{"id":5,"index":1}],"params":[],"name":"cdivider"},{"id":5,"type":"Clock","x":362,"y":392.5,"inputs":[],"params":[100],"name":"clock"},{"id":6,"type":"Sequencer16","x":469.5,"y":571.5,"inputs":[{"id":4,"index":2},{"id":10,"index":0}],"params":[{"0":10.571430206298828,"1":2.5714271068573,"2":3.0000035762786865,"3":5.142857074737549,"4":10.714286804199219,"5":2.714287042617798,"6":3.000000238418579,"7":0.7142860889434814,"8":10.00000286102295,"9":2.7142884731292725,"10":3.4285707473754883,"11":5.428570747375488,"12":7.857141017913818,"13":2.1428587436676025,"14":3.571428060531616,"15":0.8571439385414124}],"name":"seq16"},{"id":7,"type":"Note2Freq","x":598,"y":506.5,"inputs":[{"id":8,"index":0}],"params":[440,0,12],"name":"number"},{"id":8,"type":"Plus","x":594,"y":457,"inputs":[{"id":9,"index":1},{"id":6,"index":0}],"params":[],"name":"plus"},{"id":9,"type":"MidiKeyboard","x":453.5,"y":391.5,"inputs":[],"params":[{"0":128,"1":49,"2":64},1],"name":"midiin"},{"id":10,"type":"LinearAD","x":612.5,"y":368.5,"inputs":[{"id":9,"index":0},{"id":11,"index":0},{"id":12,"index":0}],"params":[],"name":"linearad"},{"id":11,"type":"Number","x":450,"y":318.5,"inputs":[],"params":[0,200],"name":"number"},{"id":12,"type":"Number","x":428.5,"y":355,"inputs":[],"params":[0.001,200],"name":"number"},{"id":13,"type":"LinearAD","x":651,"y":541.5,"inputs":[{"id":4,"index":2},null,{"id":21,"index":0}],"params":[],"name":"linearad"},{"id":14,"type":"Multiplier","x":895.5,"y":564,"inputs":[{"id":13,"index":0},{"id":17,"index":0}],"params":[],"name":"multiplier"},{"id":15,"type":"Multiplier","x":759.5,"y":600.5,"inputs":[{"id":3,"index":0},{"id":16,"index":0}],"params":[],"name":"multiplier"},{"id":16,"type":"SineOSC","x":656,"y":607,"inputs":[{"id":6,"index":0},{"id":3,"index":0}],"params":[],"name":"SINE OSC"},{"id":17,"type":"Multiplier","x":846,"y":621,"inputs":[{"id":15,"index":0},{"id":18,"index":0}],"params":[],"name":"multiplier"},{"id":18,"type":"Plus","x":678.5,"y":669,"inputs":[{"id":20,"index":0},{"id":19,"index":0}],"params":[],"name":"plus"},{"id":19,"type":"Number","x":483.5,"y":698.5,"inputs":[],"params":[1,200],"name":"number"},{"id":20,"type":"Rand","x":410.5,"y":616,"inputs":[{"id":4,"index":2}],"params":[],"name":"linear decay"},{"id":21,"type":"Number","x":492.5,"y":527,"inputs":[],"params":[0.24499999999999947,200],"name":"number"}]
--------------------------------------------------------------------------------
/jams/concepts/mixture01.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":775,"y":269,"inputs":[{"id":10,"index":0}],"params":[],"name":"OUT"},{"id":2,"type":"SineOSC","x":409,"y":526,"inputs":[{"id":29,"index":0},{"id":38,"index":0}],"params":[],"name":"SINE OSC"},{"id":3,"type":"Flange","x":690.5,"y":330,"inputs":[{"id":41,"index":0},null,null,{"id":12,"index":0},{"id":28,"index":0},null,{"id":27,"index":0}],"params":[],"name":"flange"},{"id":4,"type":"Multiplier","x":258.5,"y":454,"inputs":[{"id":8,"index":0},{"id":2,"index":0}],"params":[],"name":"multiplier"},{"id":5,"type":"Clock","x":9.5,"y":288,"inputs":[],"params":[78],"name":"clock"},{"id":6,"type":"Sequencer16","x":125.5,"y":320.5,"inputs":[{"id":7,"index":2}],"params":[{"0":0.14285726845264435,"1":1.571428656578064,"2":0.4285714626312256,"3":1.2857142686843872,"4":1.2857142686843872,"5":0.714285671710968,"6":1.2857143878936768,"7":0.8571428060531616,"8":2.2857143878936768,"9":0,"10":1.7142858505249023,"11":0,"12":0,"13":1.571428656578064,"14":0,"15":1.571428656578064}],"name":"seq16"},{"id":7,"type":"ClockDivider","x":4,"y":375.5,"inputs":[{"id":5,"index":1}],"params":[],"name":"cdivider"},{"id":8,"type":"LinearAD","x":318.5,"y":338.5,"inputs":[{"id":6,"index":0},null,{"id":26,"index":0}],"params":[],"name":"linearad"},{"id":10,"type":"QuadMixer","x":804.5,"y":365.5,"inputs":[{"id":3,"index":0}],"params":[],"name":"quadmixer"},{"id":11,"type":"Multiplier","x":407.5,"y":383.5,"inputs":[{"id":8,"index":0},{"id":2,"index":0}],"params":[],"name":"multiplier"},{"id":12,"type":"Number","x":571,"y":333,"inputs":[],"params":[0.4450000000000005,200],"name":"number"},{"id":13,"type":"Sequencer16","x":113,"y":430.5,"inputs":[{"id":7,"index":2}],"params":[{"0":0.714285671710968,"1":10.857138633728027,"2":0.1428583562374115,"3":12.142858505249023,"4":6.142854690551758,"5":0.14285744726657867,"6":1.4285722970962524,"7":0.4285719692707062,"8":5.142855644226074,"9":-0.14285606145858765,"10":7.5714287757873535,"11":-0.2857150435447693,"12":-0.2857140302658081,"13":12.14285659790039,"14":-0.7142860293388367,"15":19.000003814697266}],"name":"seq16"},{"id":14,"type":"Note2Freq","x":245.5,"y":562,"inputs":[{"id":15,"index":0}],"params":[440,0,12],"name":"number"},{"id":15,"type":"Plus","x":173.5,"y":569.5,"inputs":[{"id":13,"index":0},{"id":37,"index":0}],"params":[],"name":"plus"},{"id":16,"type":"Number","x":-24,"y":611,"inputs":[],"params":[40,200],"name":"number"},{"id":17,"type":"Delay","x":575.5,"y":413.5,"inputs":[{"id":23,"index":0},{"id":50,"index":0}],"params":[],"name":"Delay"},{"id":19,"type":"Number","x":393.5,"y":593,"inputs":[],"params":[0.4649999999999994,200],"name":"number"},{"id":21,"type":"Multiplier","x":521.5,"y":497,"inputs":[{"id":19,"index":0},{"id":17,"index":0}],"params":[],"name":"multiplier"},{"id":23,"type":"Plus","x":507.5,"y":380.5,"inputs":[{"id":11,"index":0},{"id":21,"index":0}],"params":[],"name":"plus"},{"id":24,"type":"Multiplier","x":285,"y":512,"inputs":[{"id":4,"index":0},{"id":25,"index":0}],"params":[],"name":"multiplier"},{"id":25,"type":"Number","x":172,"y":530.5,"inputs":[],"params":[1.2200000000000042,200],"name":"number"},{"id":26,"type":"Number","x":224.5,"y":412.5,"inputs":[],"params":[0.530000000000001,200],"name":"number"},{"id":27,"type":"Number","x":582.5,"y":490.5,"inputs":[],"params":[0.049999999999999836,200],"name":"number"},{"id":28,"type":"Number","x":568,"y":380,"inputs":[],"params":[0.18499999999999994,200],"name":"number"},{"id":29,"type":"Multiplier","x":327.5,"y":595,"inputs":[{"id":14,"index":0},{"id":30,"index":0}],"params":[],"name":"multiplier"},{"id":30,"type":"Switch","x":250,"y":647,"inputs":[{"id":31,"index":0},{"id":33,"index":0},{"id":34,"index":0},{"id":35,"index":0}],"params":[3.700000000000001],"name":"number"},{"id":31,"type":"Multiplier","x":161.5,"y":626,"inputs":[{"id":8,"index":0},{"id":32,"index":0}],"params":[],"name":"multiplier"},{"id":32,"type":"Number","x":46.5,"y":641.5,"inputs":[],"params":[3,200],"name":"number"},{"id":33,"type":"Number","x":111,"y":662.5,"inputs":[],"params":[0.5,200],"name":"number"},{"id":34,"type":"Number","x":113.5,"y":686.5,"inputs":[],"params":[1.5,200],"name":"number"},{"id":35,"type":"Number","x":110,"y":711,"inputs":[],"params":[2,200],"name":"number"},{"id":36,"type":"Sequencer16","x":6.5,"y":517.5,"inputs":[{"id":7,"index":4}],"params":[{"0":3.7142858505249023,"1":3.4285731315612793,"2":3.2857141494750977,"3":3.428571939468384,"4":10.142860412597656,"5":10.714288711547852,"6":10.714286804199219,"7":10.28571605682373,"8":1.7142858505249023,"9":1.142857551574707,"10":1.7142854928970337,"11":1.7142858505249023,"12":9.142858505249023,"13":9.714287757873535,"14":8.142857551574707,"15":8.571429252624512}],"name":"seq16"},{"id":37,"type":"Plus","x":100.5,"y":586.5,"inputs":[{"id":36,"index":0},{"id":16,"index":0}],"params":[],"name":"plus"},{"id":38,"type":"Plus","x":515,"y":626.5,"inputs":[{"id":24,"index":0},{"id":39,"index":0}],"params":[],"name":"plus"},{"id":39,"type":"SineOSC","x":359.5,"y":674,"inputs":[{"id":40,"index":0}],"params":[],"name":"SINE OSC"},{"id":40,"type":"Number","x":340.5,"y":640.5,"inputs":[],"params":[5,200],"name":"number"},{"id":41,"type":"MonoMerge","x":735.5,"y":505,"inputs":[{"id":17,"index":0},{"id":42,"index":0}],"params":[],"name":"multiplier"},{"id":42,"type":"Delay","x":636,"y":541.5,"inputs":[{"id":17,"index":0},{"id":45,"index":0}],"params":[],"name":"Delay"},{"id":43,"type":"Number","x":537,"y":598.5,"inputs":[],"params":[100,200],"name":"number"},{"id":45,"type":"Plus","x":666,"y":609,"inputs":[{"id":43,"index":0},{"id":46,"index":0}],"params":[],"name":"plus"},{"id":46,"type":"Multiplier","x":628,"y":679,"inputs":[{"id":48,"index":0},{"id":47,"index":0}],"params":[],"name":"multiplier"},{"id":47,"type":"Number","x":466,"y":728,"inputs":[],"params":[50,200],"name":"number"},{"id":48,"type":"SineOSC","x":511,"y":679,"inputs":[{"id":49,"index":0}],"params":[],"name":"SINE OSC"},{"id":49,"type":"Number","x":359,"y":718,"inputs":[],"params":[0.8799999999999999,200],"name":"number"},{"id":50,"type":"Number","x":428,"y":447,"inputs":[],"params":[18000,200],"name":"number"}]
--------------------------------------------------------------------------------
/jams/flange.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":905,"y":480,"inputs":[],"params":[],"name":"OUT"},{"id":2,"type":"Sampler","x":552,"y":463,"inputs":[{"id":6,"index":0}],"params":[["䀀䁒䂣䃶䅅䆚䇪䈻䊏䋞䌲䎃䏔䐦䑸䓊䔚䕯䖽䘒䙢䚴䜆䝘䞩䟼䡌䢠䣯䥃䦔䧥䨸䪈䫜䬫䭿䯐䰢䱴䳄䴘䵨䶻下也亮伂佒侥俶偉備僭儽冐几刴劃勗匧卺友吝呯咿唔啢喷嘆噚嚪图坍垢埰塅墔壧夹妋姜娭媁嫐嬥孴寇尙屩岽崍嵠嶱布幕度廸彊徛忭怿悐惣愴憆懘戩扼拌挞捱揁搕摥撶攋教断旾晐暡更杄枘柨栺梌棞椰榁槔樣橸櫆欜歪殽氐江沶洁浘涧淺湋溜滯潀澒濤瀵炇烙焫煼燏爟牲狄猔獩玷琌瑛璯瓾畓疢痶癆皘盫眻瞏矝砳碁磖礥祹秉稝穫竁笏筣箳簆籗粪系経綟緰繂纔绦缶羊翙-}ÑġŴDŽȘɦʽ̊͟ίЁѓҥӶՉ֙ؽڏܱۢޅߕࠨࡺ࣊झ९ীਓશଇ్ಡೱൄඔ෨ุຊໜ༮ྀ࿒ဢၶჅᄙᅩᆼሎኲጂፕᎥᏹᑉᒜᓭᔾᖑᗢᘴᚅᛘᜨ៍ᡱᣁᤔᥥᦸᨈᩜ᪫᭐ᮠ᱃Ი᳨ᴺᶍᷜḰẁồἥὶΈ’₼ℐⅠ↴∃≗⊧⋹⍌⎜⏯⑀⒑ⓥ┴▉◗☭♺⛐✟❲⟃⠕⡧⢸⤋⥛⦯⧿⩑⪤⫴⭇⮘⯪ⰼⲍⳟⴰⶄⷓ⸧⻊⼜⽬⿀』つコㄆㅘㆩㇻ㉌㊟㋰㍂㎓㏥㐸㒇㓜㔫㕿㗏㘢㙳㛅㜗㝨㞻㠋㡞㢯㤁㥒㦦㧴㩊㪗㫮㬻㮑㯟㰳㲅㳔㴩㵹㷊㸟","䀀䁒䂣䃶䅅䆚䇪䈻䊏䋞䌲䎃䏔䐦䑸䓊䔚䕯䖽䘒䙢䚴䜆䝘䞩䟼䡌䢠䣯䥃䦔䧥䨸䪈䫜䬫䭿䯐䰢䱴䳄䴘䵨䶻下也亮伂佒侥俶偉備僭儽冐几刴劃勗匧卺友吝呯咿唔啢喷嘆噚嚪图坍垢埰塅墔壧夹妋姜娭媁嫐嬥孴寇尙屩岽崍嵠嶱布幕度廸彊徛忭怿悐惣愴憆懘戩扼拌挞捱揁搕摥撶攋教断旾晐暡更杄枘柨栺梌棞椰榁槔樣橸櫆欜歪殽氐江沶洁浘涧淺湋溜滯潀澒濤瀵炇烙焫煼燏爟牲狄猔獩玷琌瑛璯瓾畓疢痶癆皘盫眻瞏矝砳碁磖礥祹秉稝穫竁笏筣箳簆籗粪系経綟緰繂纔绦缶羊翙-}ÑġŴDŽȘɦʽ̊͟ίЁѓҥӶՉ֙ؽڏܱۢޅߕࠨࡺ࣊झ९ীਓશଇ్ಡೱൄඔ෨ุຊໜ༮ྀ࿒ဢၶჅᄙᅩᆼሎኲጂፕᎥᏹᑉᒜᓭᔾᖑᗢᘴᚅᛘᜨ៍ᡱᣁᤔᥥᦸᨈᩜ᪫᭐ᮠ᱃Ი᳨ᴺᶍᷜḰẁồἥὶΈ’₼ℐⅠ↴∃≗⊧⋹⍌⎜⏯⑀⒑ⓥ┴▉◗☭♺⛐✟❲⟃⠕⡧⢸⤋⥛⦯⧿⩑⪤⫴⭇⮘⯪ⰼⲍⳟⴰⶄⷓ⸧⻊⼜⽬⿀』つコㄆㅘㆩㇻ㉌㊟㋰㍂㎓㏥㐸㒇㓜㔫㕿㗏㘢㙳㛅㜗㝨㞻㠋㡞㢯㤁㥒㦦㧴㩊㪗㫮㬻㮑㯟㰳㲅㳔㴩㵹㷊㸟",44100],1,true],"name":"sampler"},{"id":3,"type":"Flange","x":688,"y":402,"inputs":[{"id":2,"index":0}],"params":[],"name":"flange"},{"id":4,"type":"Multiplier","x":817,"y":480,"inputs":[{"id":3,"index":0},{"id":5,"index":0}],"params":[],"name":"multiplier"},{"id":5,"type":"Number","x":673,"y":522,"inputs":[],"params":[0.2949999999999994,200],"name":"number"},{"id":6,"type":"Number","x":443,"y":452,"inputs":[],"params":[0.4599999999999995,200],"name":"number"}]
--------------------------------------------------------------------------------
/jams/linearad.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":993,"y":476,"inputs":[],"params":[],"name":"OUT"},{"id":2,"type":"LinearAD","x":829.5,"y":418.5,"inputs":[{"id":4,"index":4},{"id":7,"index":0},{"id":9,"index":0}],"params":[],"name":"linearad"},{"id":3,"type":"Clock","x":640,"y":310.5,"inputs":[],"params":[100],"name":"clock"},{"id":4,"type":"ClockDivider","x":688,"y":353.5,"inputs":[{"id":3,"index":1}],"params":[],"name":"cdivider"},{"id":5,"type":"SineOSC","x":845,"y":491,"inputs":[],"params":[],"name":"SINE OSC"},{"id":6,"type":"Multiplier","x":924.5,"y":473,"inputs":[{"id":2,"index":0},{"id":5,"index":0}],"params":[],"name":"multiplier"},{"id":7,"type":"Number","x":688,"y":446,"inputs":[],"params":[0.22999999999999932,200],"name":"number"},{"id":9,"type":"Number","x":680,"y":471,"inputs":[],"params":[0.12499999999999963,200],"name":"number"},{"id":10,"type":"TextDisplay","x":756.5,"y":532,"inputs":[],"params":["The LinearAD envelope accepts 3 inputs:\n1. Trigger\n2. Attack (in Seconds)\n3. Decay (in Seconds)",320,60],"name":"number"}]
--------------------------------------------------------------------------------
/jams/note2freq.json:
--------------------------------------------------------------------------------
1 | [{"id":20,"type":"Output","x":977,"y":482,"inputs":[],"params":[],"name":"OUT"},{"id":21,"type":"Number","x":642,"y":453.5,"inputs":[],"params":[69.00000000000021,200],"name":"number"},{"id":22,"type":"Note2Freq","x":731,"y":509.5,"inputs":[{"id":21,"index":0}],"params":[440,0,12],"name":"number"},{"id":23,"type":"Readout","x":824,"y":454.5,"inputs":[{"id":22,"index":0}],"params":[],"name":"Readout"},{"id":24,"type":"TextDisplay","x":734.5,"y":532.5,"inputs":[],"params":["↑right click for more",175,30],"name":"number"}]
--------------------------------------------------------------------------------
/jams/quad.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":962,"y":475,"inputs":[],"params":[],"name":"OUT"},{"id":2,"type":"QuadMixer","x":862,"y":475,"inputs":[{"id":3,"index":0},{"id":4,"index":0},{"id":5,"index":0}],"params":[],"name":"quadmixer"},{"id":3,"type":"SineOSC","x":734,"y":453,"inputs":[],"params":[],"name":"SINE OSC"},{"id":4,"type":"SineOSC","x":740,"y":500,"inputs":[{"id":6,"index":0}],"params":[],"name":"SINE OSC"},{"id":5,"type":"SineOSC","x":744,"y":548,"inputs":[{"id":10,"index":0}],"params":[],"name":"SINE OSC"},{"id":6,"type":"Multiplier","x":652,"y":488,"inputs":[{"id":8,"index":0},{"id":7,"index":0}],"params":[],"name":"multiplier"},{"id":7,"type":"Number","x":522,"y":506,"inputs":[],"params":[1.5,200],"name":"number"},{"id":8,"type":"Number","x":521,"y":452,"inputs":[],"params":[440,200],"name":"number"},{"id":9,"type":"Number","x":513,"y":555,"inputs":[],"params":[1.25,200],"name":"number"},{"id":10,"type":"Multiplier","x":653,"y":551,"inputs":[{"id":8,"index":0},{"id":9,"index":0}],"params":[],"name":"multiplier"},{"id":11,"type":"TextDisplay","x":827,"y":561,"inputs":[],"params":["QUAD is a mixer/sum",170,30],"name":"number"}]
--------------------------------------------------------------------------------
/jams/scopes.json:
--------------------------------------------------------------------------------
1 | [{"id":7,"type":"Output","x":960,"y":475,"inputs":[],"params":[],"name":"OUT"},{"id":8,"type":"Scope","x":858,"y":421,"inputs":[{"id":10,"index":0}],"params":[],"name":"scope"},{"id":9,"type":"XYScope","x":882,"y":498.5,"inputs":[{"id":12,"index":0}],"params":[],"name":"xyscope"},{"id":10,"type":"SineOSC","x":720.5,"y":437,"inputs":[{"id":14,"index":0},{"id":10,"index":0}],"params":[],"name":"SINE OSC"},{"id":11,"type":"SineOSC","x":726,"y":499,"inputs":[{"id":13,"index":0}],"params":[],"name":"SINE OSC"},{"id":12,"type":"MonoMerge","x":817,"y":498.5,"inputs":[{"id":10,"index":0},{"id":11,"index":0}],"params":[],"name":"multiplier"},{"id":13,"type":"Number","x":599.5,"y":485.5,"inputs":[],"params":[890.5399999999995,200],"name":"number"},{"id":14,"type":"Number","x":602.5,"y":435.5,"inputs":[],"params":[890.05,200],"name":"number"}]
--------------------------------------------------------------------------------
/jams/sineosc01.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":615,"y":475,"inputs":[],"params":[],"name":"OUT"},{"id":2,"type":"SineOSC","x":509,"y":475,"inputs":[],"params":[],"name":"SINE OSC"},{"id":3,"type":"Number","x":354,"y":437,"inputs":[],"params":[660,200],"name":"number"},{"id":4,"type":"TextDisplay","x":307,"y":527,"inputs":[],"params":["Connect SineOsc to OUT to hear it.\n\nConnect 660 to first input FREQ\nto change the oscillator's\nfrequency.\n\nHold Shift while dragging the\nNumber Module to change its value.\nRight click an input\nand choose Disconnect to disconnect.",288,108],"name":"number"}]
--------------------------------------------------------------------------------
/jams/sineosc02.json:
--------------------------------------------------------------------------------
1 | [{"id":1,"type":"Output","x":960,"y":475,"inputs":[],"params":[],"name":"OUT"},{"id":2,"type":"SineOSC","x":857.5,"y":475,"inputs":[],"params":[],"name":"SINE OSC"},{"id":3,"type":"SineOSC","x":755.5,"y":521.5,"inputs":[{"id":4,"index":0}],"params":[],"name":"SINE OSC"},{"id":4,"type":"Number","x":643,"y":521.5,"inputs":[],"params":[220,200],"name":"number"},{"id":5,"type":"TextDisplay","x":718,"y":556.5,"inputs":[],"params":["Oscillator 2",112,15],"name":"number"},{"id":6,"type":"TextDisplay","x":832.5,"y":513.5,"inputs":[],"params":["Oscillator 1",111,15],"name":"number"},{"id":7,"type":"TextDisplay","x":760,"y":601,"inputs":[],"params":["1. Connect OSC 1 to OUT.\n2. Connect OSC 2 to OSC 1's FASE.\n3. This is Phase Modulation\n Try Changing 220 to 12 instead!",280,60],"name":"number"}]
--------------------------------------------------------------------------------
/jams/switch.json:
--------------------------------------------------------------------------------
1 | [{"id":26,"type":"Output","x":960,"y":475,"inputs":[],"params":[],"name":"OUT"},{"id":27,"type":"Switch","x":779.5,"y":486,"inputs":[{"id":33,"index":0},{"id":30,"index":0},{"id":31,"index":0},{"id":32,"index":0}],"params":[3.7000000000000006],"name":"number"},{"id":28,"type":"SineOSC","x":866.5,"y":486,"inputs":[{"id":27,"index":0}],"params":[],"name":"SINE OSC"},{"id":29,"type":"Clock","x":622.5,"y":443,"inputs":[],"params":[120],"name":"clock"},{"id":30,"type":"Number","x":631.5,"y":497,"inputs":[],"params":[440,200],"name":"number"},{"id":31,"type":"Number","x":630,"y":523,"inputs":[],"params":[660,200],"name":"number"},{"id":32,"type":"Number","x":642,"y":553.5,"inputs":[],"params":[550,200],"name":"number"},{"id":33,"type":"Number","x":730.5,"y":415.5,"inputs":[],"params":[0.6049999999999996,200],"name":"number"},{"id":34,"type":"TextDisplay","x":735,"y":590.5,"inputs":[],"params":["Try changing the value\nof the first input!\n\nThen try connecting Clock's\nSecond instead.",250,60],"name":"number"}]
--------------------------------------------------------------------------------
/pages/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khoin/JAMS/6bf029815adaed545596952c06dad020066db1a5/pages/cover.jpg
--------------------------------------------------------------------------------
/pages/docs.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: JAMS Documentations
4 | permalink: /docs
5 | ---
6 |
7 | # This page is not ready yet. Sorry :(
--------------------------------------------------------------------------------
/pages/examples.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: JAMS - Examples
4 | permalink: /examples
5 | ---
6 |
7 | Below are examples for each modules and concept setups. The examples for modules will be specific for that module's usage. The examples for concepts will be listed from simplest to more complex setups.
8 |
9 | You should start with per modules first as there will be tutorials on how to use the application's interface as well.
10 |
11 | As always, remember to turn your volume down.
12 |
13 | # Per Modules
14 |
15 | * SineOscillator
16 | * Frequency Input [OPEN](./app#load#/jams/sineosc01.json)
17 | * Phase Input / Phase Modulation [OPEN](./app#load#/jams/sineosc02.json)
18 | * Arithmetic Operations [OPEN](./app#load#/jams/arithmetic.json)
19 | * Clock & Clock Divider & Sequencer16
20 | * Using absolute time T (in Seconds) [OPEN](./app#load#/jams/clock01.json)
21 | * Using pulses [OPEN](./app#load#/jams/clock02.json)
22 | * Note2Freq [OPEN](./app#load#/jams/note2freq.json)
23 | * Switch [OPEN](./app#load#/jams/switch.json)
24 | * LinearAD [OPEN](./app#load#/jams/linearad.json)
25 | * Sampler [OPEN](./app#load#/jams/sampler.json)
26 | * Flange [OPEN](./app#load#/jams/flange.json)
27 | * Scopes [OPEN](./app#load#/jams/scopes.json)
28 | * Quad [OPEN](./app#load#/jams/quad.json)
29 |
30 | # Per Concepts
31 |
32 | * Complex SineOsc Routing [OPEN](./app#load#/jams/concepts/complexRouting.json)
33 | * Kickdrum [OPEN](./app#load#/jams/concepts/kickDrum.json)
34 | * Bass Sequence [OPEN](./app#load#/jams/concepts/bassSequence.json)
35 | * Sample Chopping [OPEN](./app#load#/jams/concepts/sampleChopping.json)
36 | * Chord Arp [OPEN](./app#load#/jams/concepts/chordArp.json)
37 | * Delayed Melody [OPEN](./app#load#/jams/concepts/delayedMelody.json)
38 | * MIDI arpeggio [OPEN](./app#load#/jams/concepts/midiArpeggio.json)
39 |
40 | * Mixture 01 [OPEN](./app#load#/jams/concepts/mixture01.json)
41 | * DigitalCat [OPEN](./app#load#/jams/concepts/digicat.json)
--------------------------------------------------------------------------------
/pages/history.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: JAMS Development History
4 | permalink: /history
5 | ---
6 |
7 | This is my first big project in programming after [Pascal Draw](https://github.com/khoin/Pascal-Draw). It's been really fun developing every little thing for an application. From interfaces, context menus, file saving, WAV parsing, DSP to font designing, even! Unfortunately, some of those home-grown things had to go: my file saving couldn't handle big files when saving audio data came around, the 5x6 font was simply too small and limited to one language. Nonetheless, I've had great discussions with friends and had tons of help. I started posting short clips, pictures and videos of my developement since the beginning but I've only consilidated them here since Jul 29, 2017.
8 |
9 | * ___Project started with the name Emsys for "M-system".___
10 |
11 | * __2016, Aug 23__ First Desktop GUI prototype [[CLIP](https://www.instagram.com/p/BJdzOtZAd8D/)]
12 |
13 | * ___Discussion with Werner about phase modulation held somewhere around here.___
14 |
15 | * __Aug 24__ Number, Multiplier, SineOSC [[CLIP](https://www.instagram.com/p/BJhR4GcgpB6/)]
16 |
17 | * __Aug 25__ Creating modules through scripting (something I do less now) [[CLIP](https://www.instagram.com/p/BJj1yelA9A7/)]
18 |
19 | * __Aug 28__ Context Menu, Desktop Panning, Save/Load [[CLIP](https://www.instagram.com/p/BJriiq1A5fH/)]
20 |
21 | * __Aug 29__ 4Array, Modulus, Time [[CLIP](https://www.instagram.com/p/BJuTDu6AXtd/)]
22 |
23 | * __Aug 30__
24 | * A really cool patch (I forgot now :c) [[CLIP](https://www.instagram.com/p/BJvkk27AL_t/)]
25 | * Deleting Module, disconnecting connections possible via GUI [[PIC](https://www.instagram.com/p/BJxA-dRgUJu/)]
26 | * Nested Context Menu (using for-loop) [[PIC](https://www.instagram.com/p/BJy0rI_gX0X/)]
27 |
28 | * __Aug 31__
29 | * First prototypes of windows and window elements [[CLIP](https://www.instagram.com/p/BJzacxcA6f1/)]
30 |
31 | * __Sept 1__ WindowElements stacking and resizing [[PIC](https://www.instagram.com/p/BJ1SgNCgGse/)]
32 |
33 | * __Sept 6__ Fields for interface windows [[CLIP](https://www.instagram.com/p/BKCqz_5gjtB/)]
34 |
35 | * __Sept 8__ First MIDI integration [[CLIP](https://www.instagram.com/p/BKFgCZxg4F0/)]
36 |
37 | * __Sept 11__ Some touchscreen stuff, it got pulled later on [[CLIP](https://www.instagram.com/p/BKNZPoogHtD/)]
38 |
39 | * __Sept 14__ First big setup [[CLIP](https://www.instagram.com/p/BKXeTHegQKI/)]
40 |
41 | * __Sept 17__ MIDI setups [[VIDEO](https://www.youtube.com/watch?v=Xns9BYVU3wk)]
42 |
43 | * ___Started college, moving into dorm. A lot of shit happened between this part___
44 |
45 | * __Nov 4__
46 | * One Pole filter, source code taken from wavepot.com [[CLIP](https://www.instagram.com/p/BMYWbXoAxcn/)]
47 | * With proper chord [[CLIP](https://www.instagram.com/p/BMYYAqDgzU_/)]
48 |
49 | * __Dec 24__ More one-pole filter stuff [[CLIP](https://www.instagram.com/p/BOZOKYAgb1_/)]
50 |
51 | * __2017, Feb 18__ Side project that stems from PM synthesis [[CLIP](https://www.instagram.com/p/BQq_OQKDML9/)]
52 |
53 | * __March 7__ A quick patch between classes on a rainy day in Seattle at OUGL [[CLIP](https://www.instagram.com/p/BRWnZrXD_bo/)]
54 |
55 | * __March 10__ I demo'd JAMS at the MakerSpace [[PIC](https://www.instagram.com/p/BRem7kVDS5u/)]
56 | * Here's one of the patches I demo'd [[CLIP](https://www.instagram.com/p/BRc4F0EjgZk/)]
57 |
58 | * __March 31__ Usual ranting :) [[PIC](https://www.instagram.com/p/BSSzVxyDqh0/)]
59 |
60 | * __April 6__ I forgot the previous font size, but at this point, JAMS has 5x6 font [[PIC](https://www.instagram.com/p/BSkNWZqDfCK/)]
61 | * Also from this point, I rewrote the program in ES6, changed name to JAMS.
62 |
63 | * __April 8__ Rewriting interface windows [[CLIP](https://www.instagram.com/p/BSp5WxHD1cD/)]
64 |
65 | * __April 9__ jams.systems domain bought.
66 |
67 | * __April 10__ Rewriting WindowElements [[CLIP](https://www.instagram.com/p/BSsjPsajc0H/)]
68 |
69 | * __April 14__ Rewriting context menu. This time, recursion handles the flow [[PIC](https://www.instagram.com/p/BS28iB0DEZP/)]
70 |
71 | * __April 19__ Stereo first draft. [[PIC](https://www.instagram.com/p/BTFwDHEjGNT/)]
72 |
73 | * __April 21__
74 | * Sequencer, Clocks, Clock Divider, Quad [[VIDEO](https://www.youtube.com/watch?v=6nD33LSDCNQ)]
75 | * On a Mac [[CLIP](https://www.instagram.com/p/BTLE4PcjLtw/)]
76 |
77 | * __April 22__ Pseudo-random autonomous patch [[VIDEO](https://www.youtube.com/watch?v=BAedYl1Fm5s)]
78 |
79 | * ___Discussion with roommmate about stereo data structure happened around here___
80 |
81 | * __May 11__ Stereo fixed [[CLIP](https://www.instagram.com/p/BT8aAmojK-c/)]
82 |
83 | * __May 12__ Patch [[CLIP](https://www.instagram.com/p/BUBbrxDD-VE/)]
84 |
85 | * ___DSP for Flanger received from Werner. Discussion with roommate about WAV. Apparently, even though WAVE is a simple format, I thought they were stored in floating points at first. It made much more sense when he mentioned that it's stored as integers.___
86 |
87 | * __May 20__ Sampler implemented [[CLIP](https://www.instagram.com/p/BUTtfTaD67n/?taken-by=p07a)] [[VIDEO](https://www.youtube.com/watch?v=0pHITFYDOn8)]
88 |
89 | * __May 28__ Debugging sampler, glitch stuff [[CLIP](https://www.instagram.com/p/BUoLWhsjq7A/)]
90 |
91 | * __June 16__ A phrase in J.S.Bach's "Little" Fugue in G minor played by my voice sampled and pitched [[CLIP](https://www.instagram.com/p/BVbFTY0j3Gi/)]
92 |
93 | * __Stuff happened__
94 |
95 | * __July 7__ Flange DSP ready [[CLIP](https://www.instagram.com/p/BWRlab5je7k/)]
96 |
97 | * __July 20__ Saving sampler's audio data with project json. Delay. [[CLIP](https://www.instagram.com/p/BWwnLSZDOHV/)]
98 |
99 | * __July 21__ Waveform for Sampler. GUI for Flange. [[CLIP](https://www.instagram.com/p/BW3eyYSDaan/)]
100 |
101 | * __July 22__ Switch (Basically the old 4Array, but more dynamic) [[CLIP](https://www.instagram.com/p/BW4G5orDX2O/)]
102 |
103 | * __July 24__ Using Misaki 8x8 bitmap font instead. [[PIC](https://www.instagram.com/p/BW7Dm5djmXu/)]
104 |
105 | * __July 25__ JAMS now follows a color scheme because font is limited to certain colors. [[CLIP](https://www.instagram.com/p/BW9i1IcDLq9/)]
106 |
107 | * __July 27__ Fixing the last bits of Sampler [[CLIP](https://www.instagram.com/p/BXCYzikDCeP/)]
108 |
109 | * __July 28__ LinearDecay is now LinearAD (Attack-Decay) [[CLIP](https://www.instagram.com/p/BXFYbzHjTjT/)]
110 |
111 | * __July 29__ Did research on Jekyll. JAMS now have a homepage on GitHub Pages, along with examples. Consolidating development history.
--------------------------------------------------------------------------------
/src/ApplicationPreference.js:
--------------------------------------------------------------------------------
1 | class ApplicationPreference {
2 | constructor () {
3 | this.defaultPreference = {
4 | bufferSize: {
5 | name: "Audio Buffer Size (restart to take effect)",
6 | type: "listOptions",
7 | value: 2048,
8 | values: [256, 512, 1024, 2048, 4096, 8192]
9 | },
10 | fpsFactor : {
11 | name: "FPS Reduction Factor (for laggy audio)",
12 | type: "integerSlider",
13 | value: 1,
14 | max: 5,
15 | min: 1,
16 | step: 1
17 | },
18 | background : {
19 | name: "Desktop Background Color",
20 | type: "color",
21 | value: "#000"
22 | }
23 | }
24 |
25 | if (window.localStorage.getItem("JAMSPreference") == null)
26 | localStorage["JAMSPreference"] = JSON.stringify(this.defaultPreference);
27 | }
28 |
29 | getPreference () {
30 | return JSON.parse(localStorage.getItem("JAMSPreference"));
31 | }
32 |
33 | setItem (name, value) {
34 | let newPref = this.getPreference();
35 | newPref[name].value = value;
36 | localStorage["JAMSPreference"] = JSON.stringify(newPref);
37 | }
38 |
39 | getItem (name) {
40 | return JSON.parse(localStorage.getItem("JAMSPreference"))[name].value;
41 | }
42 |
43 | reset () {
44 | localStorage["JAMSPreference"] = JSON.stringify(this.defaultPreference);
45 | }
46 | }
--------------------------------------------------------------------------------
/src/AudioModule.js:
--------------------------------------------------------------------------------
1 | class AudioModule {
2 | constructor (con) {
3 | // meta & desktop
4 | this.id = con.id;
5 | this.x = con.x;
6 | this.y = con.y;
7 | this.numberOfInputs = 0;
8 | this.numberOfOutputs = 0;
9 | this.color = 0;
10 | this.width = 30;
11 | this.height = 30;
12 | this.name = "defaultName";
13 | this.helpText = "No Helptext Available";
14 |
15 | // dsp & midi
16 | this.inputs = [];
17 | this.params = [];
18 | this.prerun = false; // If true, module will be triggered to run BEFORE output.run is pulled.
19 | this.midiRequest = false;
20 | this.midiParam = 0; // the index of the param that midi will write over
21 |
22 | }
23 |
24 | interface (g, args) {}
25 |
26 | eDrag (e) {}
27 |
28 | eMouseDown (x, y) {}
29 |
30 | // index, time, calc or not, dV
31 | getInput (i, t, z, defaultValue = 0) {
32 | return (this.inputs[i])? this.inputs[i].module.run(t, z, this.inputs[i].index) : [defaultValue, defaultValue];
33 | }
34 |
35 | setParam (index, val) {
36 | this.params[index].value = val;
37 | if(this.params[index].onload) this.params[index].onload();
38 | return this;
39 | }
40 |
41 | connect (destinationNode, destinationIndex, originIndex) {
42 | destinationNode.inputs[destinationIndex] = {};
43 | let destinationInput = destinationNode.inputs[destinationIndex];
44 |
45 | destinationInput.id = this.id;
46 | destinationInput.module = this;
47 | destinationInput.index = originIndex; // origin output index
48 | destinationNode.eConnect();
49 | return this;
50 | }
51 |
52 | eConnect () {}
53 |
54 | unsetInput (index) {
55 | delete this.inputs[index];
56 | return this;
57 | }
58 |
59 | run (t, z, a) {
60 | // t : time
61 | // z : should the module return its previous calculated value? 1: yes, 0: no
62 | // a : output index
63 | }
64 |
65 | }
66 |
67 | let Modules = {};
68 |
--------------------------------------------------------------------------------
/src/AudioModules/Clock.js:
--------------------------------------------------------------------------------
1 | Modules.Clock = class Clock extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Clock";
6 |
7 | this.numberOfInputs = 0;
8 | this.numberOfOutputs= 2;
9 | this.color = 7;
10 | this.width = 60;
11 | this.height = 30;
12 | this.name = "clock";
13 | this.helpText =
14 | `---- Clock ----
15 | Return times in seconds or pulses
16 | `;
17 |
18 | this.params[0] = {
19 | name: "BPM",
20 | type: "number",
21 | value: 120
22 | }
23 | }
24 |
25 | interface (g, args) {
26 | g.text(5, 5 , "SECOND");
27 | g.text(5, 18, "PULSES");
28 | }
29 |
30 | run (t, z, a) {
31 | // 1/64
32 | return [t * (1-a) + ~~(t%(3.75/this.params[0].value) < 1/sampleRate) * a * 2, 0];
33 | }
34 | }
--------------------------------------------------------------------------------
/src/AudioModules/ClockDivider.js:
--------------------------------------------------------------------------------
1 | Modules.ClockDivider = class ClockDivider extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "ClockDivider";
6 | this.prerun = true;
7 |
8 | this.numberOfInputs = 1;
9 | this.numberOfOutputs= 5;
10 | this.color = 7;
11 | this.width = 40;
12 | this.height = 75;
13 | this.name = "cdivider";
14 | this.helpText =
15 | `---- ClockDivider ----
16 | ITS NOT WORKING
17 | `;
18 | this.position = 0;
19 | this.on = 0;
20 | }
21 |
22 | interface (g, args) {
23 | let portSize = args.portSize;
24 | let anchor = portSize/2 - 3;
25 | for( let i = 0; i < 5; i ++)
26 | g.text(4, anchor + portSize * i, "1/" + (64 >> i));
27 | }
28 |
29 | run (t, z, a) {
30 | if (z === 1) return [this.on * (this.position%Math.pow(2,a) == 0) * 1.5, 0];
31 |
32 | if(this.getInput(0, t, 1)[0] > 0.9) {
33 | this.position = (this.position+1)%64;
34 | this.on = 1;
35 | } else {
36 | this.on = 0;
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/AudioModules/Delay.js:
--------------------------------------------------------------------------------
1 | Modules.Delay = class Delay extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Delay";
6 | this.prerun = true;
7 |
8 | this.numberOfInputs = 2;
9 | this.numberOfOutputs= 1;
10 | this.color = 4;
11 | this.width = 60;
12 | this.height = 30;
13 | this.name = "Delay";
14 | this.helpText = "";
15 |
16 | // const
17 | this.BUFFER_LENGTH = sampleRate/2;
18 | // internals
19 | this.pointer = 0;
20 | this.distance = 1000;
21 |
22 | this.buffer = [new Float32Array(this.BUFFER_LENGTH), new Float32Array(this.BUFFER_LENGTH)];
23 |
24 | }
25 |
26 | interface (g, args) {
27 | g.text(3, 5, "INPUT");
28 | g.text(3, 18, "LENGTH");
29 | }
30 |
31 | run (t, z, a) {
32 | if (z == 1)
33 | return [
34 | this.buffer[0][(this.pointer + this.distance) % this.BUFFER_LENGTH],
35 | this.buffer[1][(this.pointer + this.distance) % this.BUFFER_LENGTH]
36 | ];
37 |
38 |
39 | let input = this.getInput(0, t, 1);
40 | this.distance = (this.getInput(1, t, 1))? ~~Math.max( 0 , Math.min(this.getInput(1, t, 1)[0], this.BUFFER_LENGTH) ) : 1000;
41 |
42 | this.buffer[0][this.pointer] = input[0];
43 | this.buffer[1][this.pointer] = input[1];
44 |
45 | if (--this.pointer < 0) this.pointer = this.BUFFER_LENGTH - 1;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/AudioModules/Flange.js:
--------------------------------------------------------------------------------
1 | // Thanks to Werner Van Belle from Bpmdj for the implementation of the flanger.
2 | // https://werner.yellowcouch.org
3 |
4 | Modules.Flange = class Flange extends AudioModule {
5 | constructor (con) {
6 | super(con);
7 |
8 | this.className = "Flange";
9 | this.prerun = true;
10 |
11 | this.numberOfInputs = 7;
12 | this.numberOfOutputs= 1;
13 | this.color = 4;
14 | this.width = 72;
15 | this.height = 105;
16 | this.name = "flange";
17 | this.helpText = "";
18 |
19 | this.feedback = 0.8;
20 | this.dry = 0.5;
21 | this.wet = 0.5;
22 |
23 | // const
24 | this.BUFFER_LENGTH = 2048;
25 | // internals
26 | this.isForward = true;
27 | this.step = 0.005;
28 | this.pointer= 0;
29 | this.D = 0;
30 | this.minD = 100;
31 | this.maxD = 512;
32 |
33 | this.buffer = [new Float32Array(this.BUFFER_LENGTH + 1), new Float32Array(this.BUFFER_LENGTH + 1)];
34 |
35 | }
36 |
37 | interface (g, args) {
38 | let portSize = args.portSize;
39 | let anchor = portSize/2 - 3;
40 | g.text(3, anchor, "INPUT");
41 | g.text(3, anchor + portSize, "MIN");
42 | g.text(3, anchor + portSize*2, "MAX");
43 | g.text(3, anchor + portSize*3, "SPEED");
44 | g.text(3, anchor + portSize*4, "FEEDBCK");
45 | g.text(3, anchor + portSize*5, "DRY");
46 | g.text(3, anchor + portSize*6, "WET");
47 | g.context.fillRect(64, 0, 1, 105);
48 | g.context.fillRect(64, ~~(105*this.minD/this.BUFFER_LENGTH), 8, 1);
49 | g.context.fillRect(64, ~~(105*this.maxD/this.BUFFER_LENGTH), 8, 1);
50 | g.context.fillRect(64, ~~(105*this.D/this.BUFFER_LENGTH), 8, 1);
51 | }
52 |
53 | run (t, z, a) {
54 | let input = this.getInput(0, t, 1);
55 | this.minD = this.getInput(1, t, 1)[0]? Math.min(this.maxD, Math.max(0, this.getInput(1, t, 1)[0])) : 100;
56 | this.maxD = this.getInput(2, t, 1)[0]? Math.min(2048, Math.max(this.minD, this.getInput(2, t, 1)[0])) : 512;
57 | this.step = this.getInput(3, t, 1)[0]? Math.max(0, this.getInput(3, t, 1)[0]/1000) : 0.005;
58 | this.feedback = this.getInput(4, t, 1)[0]? Math.min(0.99, Math.max(0, this.getInput(4, t, 1)[0])) : 0.8;
59 | this.dry = this.getInput(5, t, 1)[0]? Math.min(1, Math.max(0, this.getInput(5, t, 1)[0])) : 0.5;
60 | this.wet = this.getInput(6, t, 1)[0]? Math.min(1, Math.max(0, this.getInput(6, t, 1)[0])) : 0.5;
61 |
62 | if (this.isForward)
63 | this.D += this.step;
64 | else
65 | this.D -= this.step;
66 |
67 | this.isForward = this.D > this.maxD? false : this.D < this.minD? true : this.isForward;
68 |
69 | let d1 = parseInt(this.D),
70 | d2 = d1 + 1,
71 | v = this.D - d1;
72 |
73 | let oldLeft1 = this.buffer[0][(this.pointer + d1) % this.BUFFER_LENGTH],
74 | oldLeft = oldLeft1 + v * (this.buffer[0][(this.pointer + d2) % this.BUFFER_LENGTH] - oldLeft1);
75 | let oldRight1= this.buffer[1][(this.pointer + d1) % this.BUFFER_LENGTH],
76 | oldRight = oldRight1 + v * (this.buffer[1][(this.pointer + d2) % this.BUFFER_LENGTH] - oldRight1);
77 |
78 | let processed = [
79 | input[0] + this.feedback * oldLeft,
80 | input[1] + this.feedback * oldRight
81 | ];
82 |
83 | this.buffer[0][this.pointer] = processed[0];
84 | this.buffer[1][this.pointer] = processed[1];
85 | if (--this.pointer < 0) this.pointer = this.BUFFER_LENGTH;
86 |
87 | return [
88 | processed[0] * this.dry + oldLeft * this.wet,
89 | processed[1] * this.dry + oldRight * this.wet
90 | ];
91 | }
92 | }
--------------------------------------------------------------------------------
/src/AudioModules/LinearAD.js:
--------------------------------------------------------------------------------
1 | Modules.LinearAD = class LinearAD extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "LinearAD";
6 |
7 | this.numberOfInputs = 3;
8 | this.numberOfOutputs= 1;
9 | this.color = 6;
10 | this.width = 45;
11 | this.height = 45;
12 | this.name = "linearad";
13 |
14 | this.lastT = 0;
15 | this.lastV = 0;
16 | this.value = 0;
17 | this.phase = 0;
18 | }
19 |
20 | interface (g) {
21 | if (this.phase == 1) {
22 | g.point2(42 - 22*this.value, (1 - this.value) * 41 ,4);
23 | } else {
24 | g.point2(22*this.value, (1 - this.value) * 41 ,4);
25 | }
26 | g.line(0, 44, 22, 0);
27 | g.line(22, 0, 44, 44);
28 | }
29 |
30 | run (t, z, a) {
31 | let trig = this.getInput(0, t, 1)[0];
32 | this.lastT = (trig > 0.9 && this.lastV == 0)? t : this.lastT;
33 | const curr = t - this.lastT;
34 | let attack = this.getInput(1, t, 1)[0] !== 0? this.getInput(1, t, 1)[0] : 0.008;
35 | let decay = this.getInput(2, t, 1)[0] !== 0? this.getInput(2, t, 1)[0] : 0.400;
36 |
37 | if (curr <= attack) {
38 | this.value = (1/attack)*curr;
39 | this.phase = 0;
40 | } else {
41 | this.value = 1 - (curr-attack)/decay;
42 | this.phase = 1;
43 | }
44 | this.value = Math.max(0, Math.min(1, this.value));
45 | this.lastV = trig;
46 | return [this.value, this.value];
47 | }
48 | }
--------------------------------------------------------------------------------
/src/AudioModules/MidiKeyboard.js:
--------------------------------------------------------------------------------
1 | Modules.MidiKeyboard = class MidiKeyboard extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.midiRequest = true;
6 | this.midiParam = 0;
7 |
8 | this.className = "MidiKeyboard";
9 | this.numberOfInputs = 0;
10 | this.numberOfOutputs= 3;
11 | this.color = 2;
12 | this.width = 40;
13 | this.height = 45;
14 | this.name = "midiin";
15 |
16 | this.params[0] = {
17 | name: "mididata",
18 | type: "no-user-access",
19 | value: new Uint8Array([144,69,12])
20 | }
21 | this.params[1] = {
22 | name: "channel",
23 | type: "number",
24 | value: 1
25 | }
26 |
27 | this.runningValues = [0,0,0];
28 | }
29 |
30 | setParam (index, value) {
31 | this.params[index].value = value;
32 | if( index !== 0 ) return this;
33 | //on
34 | if(value[0] == (143 + this.params[1].value)) {
35 | this.runningValues[0] = 1;
36 | this.runningValues[1] = value[1];
37 | this.runningValues[2] = value[2]/127;
38 | }
39 | //off
40 | if(value[0] == (127 + this.params[1].value) && this.runningValues[1] == value[1])
41 | this.runningValues[0] = 0;
42 | return this;
43 | }
44 |
45 | interface (g, args) {
46 | g.text(4,5,"GATE");
47 | g.text(4,20,"NOTE");
48 | g.text(4,34,"VELO");
49 | }
50 |
51 | run (t, z, a) {
52 | var outputValues = this.runningValues.slice();
53 | return [outputValues[a],outputValues[a]];
54 | }
55 | }
--------------------------------------------------------------------------------
/src/AudioModules/MonoMerge.js:
--------------------------------------------------------------------------------
1 | Modules.MonoMerge = class MonoMerge extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "MonoMerge";
6 |
7 | this.numberOfInputs = 2;
8 | this.numberOfOutputs= 1;
9 | this.color = 70;
10 | this.width = 16;
11 | this.height = 30;
12 | this.name = "multiplier";
13 | this.helpText =
14 | `---- MonoMerge ----
15 | Merge Two Mono Signals together.
16 | Take Left channels.
17 | `;
18 | }
19 |
20 | interface (g) {
21 | g.text(5, 5, "L");
22 | g.text(5, 19, "R");
23 | }
24 |
25 | run (t, z, a) {
26 | const input1 = this.getInput(0, t, 1);
27 | const input2 = this.getInput(1, t, 1);
28 | return [input1[0], input2[0]];
29 | }
30 | }
--------------------------------------------------------------------------------
/src/AudioModules/Multiplier.js:
--------------------------------------------------------------------------------
1 | Modules.Multiplier = class Multiplier extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Multiplier";
6 |
7 | this.numberOfInputs = 2;
8 | this.numberOfOutputs= 1;
9 | this.color = 3;
10 | this.width = 30;
11 | this.height = 30;
12 | this.name = "multiplier";
13 | this.helpText =
14 | `---- Multiplier ----
15 | Multiplies two inputs together.
16 | Default signal for either input is zero.
17 | `;
18 | }
19 |
20 | interface (g) {
21 | g.text(12, 12, "×", 1);
22 | }
23 |
24 | run (t, z, a) {
25 | const input1 = this.getInput(0, t, 1);
26 | const input2 = this.getInput(1, t, 1);
27 | return [input1[0] * input2[0], input1[1] * input2[1]];
28 | }
29 | }
--------------------------------------------------------------------------------
/src/AudioModules/Note2Freq.js:
--------------------------------------------------------------------------------
1 | Modules.Note2Freq = class Note2Freq extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Note2Freq";
6 | this.numberOfInputs = 1;
7 | this.numberOfOutputs= 1;
8 | this.color = 3;
9 | this.width = 80;
10 | this.height = 15;
11 | this.name = "number";
12 |
13 | this.params[0] = {
14 | name : "Tuning",
15 | type : "number",
16 | value : 440
17 | }
18 | this.params[1] = {
19 | name : "transpose",
20 | type : "number",
21 | value : 0
22 | }
23 | this.params[2] = {
24 | name : "edo",
25 | type : "number",
26 | value : 12
27 | }
28 | }
29 |
30 | interface (g, args) {
31 | g.text(4, 4, "NOTE2FREQ" );
32 | }
33 |
34 | run (t, z, a) {
35 | if(this.inputs[0] == undefined) return [0,0];
36 | return [Math.pow(2, ( ~~this.getInput(0, t, 1)[0] + this.params[1].value - 69 ) / this.params[2].value) * this.params[0].value, 0];
37 | }
38 | }
--------------------------------------------------------------------------------
/src/AudioModules/Number.js:
--------------------------------------------------------------------------------
1 | Modules.Number = class Number extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Number";
6 | this.numberOfInputs = 0;
7 | this.numberOfOutputs= 1;
8 | this.color = 5;
9 | this.width = 72;
10 | this.height = 15;
11 | this.name = "number";
12 | this.helpText =
13 | `---- Number ----
14 | Returns the number displayed.
15 | Shift + Drag this number to change
16 | its value.
17 | The Sensitivity of the drag can be altered
18 | through the Parameters window.
19 | `;
20 |
21 | this.params[0] = {
22 | name: "Value",
23 | type: "number",
24 | _v: 1,
25 | text: "1",
26 | set value (x) {
27 | this._v = x;
28 | this.text = Math.round(this._v*10000)/10000;
29 | this.text = this.text.toString();
30 | if ( (this._v > 99999 || (this._v < 0.00001 && this._v > 0) ) && this.text.length > 5)
31 | this.text = Math.round(10000*this._v/(10**~~Math.log10(this._v)))/10000+"E"+~~Math.log10(this._v);
32 | },
33 | get value () { return this._v; }
34 | };
35 | this.params[1] = {
36 | name: "Drag Sensitivity",
37 | type: "number",
38 | value: 200
39 | }
40 |
41 | }
42 |
43 | eDrag (e) {
44 | this.params[0].value = this.params[0].value - e.movementY/this.params[1].value;
45 |
46 | }
47 |
48 | interface (g, args) {
49 | g.text(4, 4, this.params[0].text );
50 | }
51 |
52 | run (t, z, a) {
53 | return [this.params[0]._v, this.params[0]._v];
54 | }
55 | }
--------------------------------------------------------------------------------
/src/AudioModules/Output.js:
--------------------------------------------------------------------------------
1 | Modules.Output = class JAMSOutput extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Output";
6 |
7 | this.numberOfInputs = 1;
8 | this.numberOfOutputs = 0;
9 | this.color = 6;
10 | this.width = 32;
11 | this.height = 15;
12 | this.name = "OUT";
13 | }
14 |
15 | eConnect () {
16 |
17 | }
18 |
19 | interface (g, args) {
20 | g.text(4, 4, this.name);
21 | }
22 |
23 | run (t, z, a) {
24 | let out = this.getInput(0, t, 1);
25 | return [out[0], out[1]];
26 | }
27 | }
--------------------------------------------------------------------------------
/src/AudioModules/Plus.js:
--------------------------------------------------------------------------------
1 | Modules.Plus = class Plus extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Plus";
6 |
7 | this.numberOfInputs = 2;
8 | this.numberOfOutputs= 1;
9 | this.color = 3;
10 | this.width = 30;
11 | this.height = 30;
12 | this.name = "plus";
13 | this.helpText =
14 | `---- Plus ----
15 | Adds the two inputs together.
16 | Can act as a mixer.
17 | `;
18 | }
19 |
20 | interface (g) {
21 | g.text(12, 12, "+", 1);
22 | }
23 |
24 | run (t, z, a) {
25 | const input1 = this.getInput(0, t, 1);
26 | const input2 = this.getInput(1, t, 1);
27 | return [input1[0] + input2[0], input1[1] + input2[1]];
28 | }
29 | }
--------------------------------------------------------------------------------
/src/AudioModules/QuadMixer.js:
--------------------------------------------------------------------------------
1 | Modules.QuadMixer = class QuadMixer extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "QuadMixer";
6 | this.prerun = true;
7 |
8 | this.numberOfInputs = 4;
9 | this.numberOfOutputs= 1;
10 | this.color = 3;
11 | this.width = 32;
12 | this.height = 60;
13 | this.name = "quadmixer";
14 | this.helpText =
15 | `---- QuadMixer ----
16 | Mixes 4 inputs.
17 | Temporary Mixer that deals with that stereo bullshit.
18 | `;
19 | this.lchannels = new Float32Array(4);
20 | this.rchannels = new Float32Array(4);
21 | this.iter = true;
22 | }
23 |
24 | interface (g) {
25 | g.text(12, 13, "Q\nU\nA\nD",1);
26 | }
27 |
28 | run (t, z, a) {
29 | const c1 = this.getInput(0, t, 1);
30 | const c2 = this.getInput(1, t, 1);
31 | const c3 = this.getInput(2, t, 1);
32 | const c4 = this.getInput(3, t, 1);
33 |
34 | this.lchannels[0] = c1[0]/4;
35 | this.lchannels[1] = c2[0]/4;
36 | this.lchannels[2] = c3[0]/4;
37 | this.lchannels[3] = c4[0]/4;
38 |
39 | this.rchannels[0] = c1[1]/4;
40 | this.rchannels[1] = c2[1]/4;
41 | this.rchannels[2] = c3[1]/4;
42 | this.rchannels[3] = c4[1]/4;
43 |
44 | return [this.lchannels[0] + this.lchannels[1] + this.lchannels[2] + this.lchannels[3],
45 | this.rchannels[0] + this.rchannels[1] + this.rchannels[2] + this.rchannels[3]];
46 | }
47 | }
--------------------------------------------------------------------------------
/src/AudioModules/Rand.js:
--------------------------------------------------------------------------------
1 | Modules.Rand = class Rand extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Rand";
6 |
7 | this.numberOfInputs = 1;
8 | this.numberOfOutputs= 1;
9 | this.color = 3;
10 | this.width = 32;
11 | this.height = 15;
12 | this.name = "linear decay";
13 | this.helpText =
14 | `---- Rand ----
15 | Generates Random 0-1 by pulse
16 | `;
17 | this.sample = 0;
18 | }
19 |
20 | interface (g) {
21 | var pos = this.width * this.sample
22 | g.line(pos, 1, pos, this.height+1);
23 | }
24 |
25 | run (t, z, a) {
26 | if (!this.inputs[0]) return [this.sample, 0];
27 | this.sample = (this.getInput(0, t, 1)[0] > 0.9)? Math.random() : this.sample;
28 | return [this.sample, 0];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/AudioModules/Readout.js:
--------------------------------------------------------------------------------
1 | Modules.Readout = class Readout extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Readout";
6 | this.prerun = true;
7 |
8 | this.numberOfInputs = 1;
9 | this.numberOfOutputs= 0;
10 | this.color = 1;
11 | this.width = 72;
12 | this.height = 15;
13 | this.name = "Readout";
14 |
15 |
16 | this.value = 0;
17 | }
18 |
19 | interface (g, args) {
20 | g.text(4,4, this.value.toString().substr(0,7));
21 | }
22 |
23 | run (t, z, a) {
24 | if(!this.inputs[0]) return;
25 | this.value = this.getInput(0, t, 1)[0];
26 | }
27 | }
--------------------------------------------------------------------------------
/src/AudioModules/Remainder.js:
--------------------------------------------------------------------------------
1 | Modules.Remainder = class Remainder extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Remainder";
6 |
7 | this.numberOfInputs = 2;
8 | this.numberOfOutputs= 1;
9 | this.color = 3;
10 | this.width = 30;
11 | this.height = 30;
12 | this.name = "remainder";
13 | this.helpText =
14 | `---- Remainder ----
15 | Returns the remainder after dividing the
16 | first input by the second input.
17 |
18 | E.g: 10 % 4 = 2;
19 | `;
20 |
21 | }
22 |
23 | interface (g, args) {
24 | g.text(12, 12, "%", 1);
25 | }
26 |
27 | run (t, z, a) {
28 | const input1 = this.getInput(0, t, 1);
29 | const input2 = this.getInput(1, t, 1);
30 | return [input1[0] % input2[0], input1[1] % input2[1]];
31 | }
32 | }
--------------------------------------------------------------------------------
/src/AudioModules/Sampler.js:
--------------------------------------------------------------------------------
1 | Modules.Sampler = class Sampler extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Sampler";
6 | this.prerun = true;
7 |
8 | this.numberOfInputs = 4;
9 | this.numberOfOutputs= 1;
10 | this.color = 5;
11 | this.width = 80;
12 | this.height = 75;
13 | this.name = "sampler";
14 | this.helpText =
15 | `---- Sampler ----
16 | Basic WAV sampler.
17 | `;
18 | this.params[0] = {
19 | name: "WaveData",
20 | type: "wavefile",
21 | value: [new Float32Array(sampleRate), new Float32Array(sampleRate), sampleRate],
22 | onload: () => {
23 | this.adjustedRate = this.params[0].value[2] / sampleRate;
24 | this.position = this.realPosition = 0;
25 | // calc peaks
26 | this.rms = new Float32Array(this.width);
27 | this.len = this.params[0].value[0].length;
28 | let chunk = (this.len / (this.width * 32));
29 |
30 | for (let i = 0, j = 0; i < this.len; i += chunk, j++)
31 | this.rms[~~(j/32)] =
32 | Math.max(this.rms[~~(j/32)],
33 | ~~( 15 * Math.sqrt((1/Math.max(1,chunk)) *
34 | this.params[0].value[0]
35 | .slice(~~i, ~~(i+chunk))
36 | .map(x => x*x)
37 | .reduce((a,b) => a+b, 0))
38 | )
39 | );
40 |
41 |
42 | },
43 | paramSave: () => {
44 | return WaveParamSave(this.params[0].value);
45 | },
46 | paramLoad: d => {
47 | return WaveParamLoad(d);
48 | }
49 | };
50 |
51 | this.params[1] = {
52 | name: "speed",
53 | type: "number",
54 | value: 1
55 | }
56 | this.params[2] = {
57 | name: "loop",
58 | type: "boolean",
59 | value: true
60 | }
61 |
62 | this.rms = new Float32Array(this.width);
63 | this.len = sampleRate;
64 |
65 | this.position = 0;
66 | this.realPosition = 0;
67 | this.adjustedRate = this.params[0].value[2] / sampleRate;
68 | }
69 |
70 | eMouseDown (x, y) {
71 | if ( x > 40 && x < 80 && y < 15 )
72 | this.params[2].value = !this.params[2].value;
73 | }
74 |
75 | interface (g) {
76 | g.box(40, 0, 40, 15);
77 | g.text(4, 5, "RATE");
78 | g.text(4, 20,"BEGN");
79 | g.text(4, 35,"END");
80 | g.text(4, 50,"TRIG");
81 |
82 | // wave
83 | for (let i = 0; i < this.rms.length; i++)
84 | g.context.fillRect(i, 75-this.rms[i], 1, this.rms[i]);
85 |
86 | g.context.fillRect(~~(this.width*this.position/this.len), 60, 1, 15);
87 |
88 | if (this.params[2].value) {
89 | g.context.fillRect(40, 0, 40, 16);
90 | g.setColor(0)
91 | g.text(44, 5, "LOOP");
92 | } else {
93 | g.text(44, 5, "LOOP");
94 | }
95 | }
96 |
97 | clamp (x) {
98 | return Math.max(0, Math.min(x, 1));
99 | }
100 |
101 | run (t, z, a) {
102 | const data = this.params[0].value;
103 | const l = data[0].length;
104 |
105 | const delta = (this.realPosition - this.position);
106 | if (z == 1)
107 | return [
108 | data[0][this.position] + delta * (data[0][(this.position + 1) % l] - data[0][this.position]) ,
109 | data[1][this.position] + delta * (data[1][(this.position + 1) % l] - data[1][this.position])
110 | ];
111 |
112 | let speed = this.getInput(0, t, 1)[0] !== 0? this.getInput(0, t, 1)[0] : this.params[1].value;
113 | let start = this.getInput(1, t, 1)[0] !== 0? this.clamp( this.getInput(1, t, 1)[0] ) * l : 0;
114 | let end = this.getInput(2, t, 1)[0] !== 0? this.clamp( this.getInput(2, t, 1)[0] ) * l - 1 : l - 1;
115 | let trigger = this.getInput(3, t, 1)[0] > 0.95;
116 |
117 | // trigger
118 | if (trigger)
119 | return this.realPosition = this.position = start;
120 |
121 | // No Loop
122 | if (!this.params[2].value && ((speed > 0 && this.realPosition > end) || (speed < 0 && this.realPosition < start)) ) return;
123 |
124 | // Wrap
125 | if (this.realPosition < start) this.realPosition = end;
126 | if (this.realPosition > end) this.realPosition = start;
127 |
128 | this.realPosition = this.realPosition + this.adjustedRate * speed;
129 |
130 | this.position = ~~(this.realPosition) % l;
131 |
132 | }
133 | }
--------------------------------------------------------------------------------
/src/AudioModules/Scope.js:
--------------------------------------------------------------------------------
1 | Modules.Scope = class Scope extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Scope";
6 | this.prerun = true;
7 |
8 | this.numberOfInputs = 1;
9 | this.numberOfOutputs= 0;
10 | this.color = 1;
11 | this.width = 60;
12 | this.height = 30;
13 | this.name = "scope";
14 | this.helpText =
15 | `---- Scope ----
16 | Oscilloscope
17 | `;
18 | this.array = new Array(this.width)
19 |
20 | }
21 |
22 | interface (g, args) {
23 | for(var i=0; i < this.array.length; ++i)
24 | g.point(i, ~~(Math.min(1, this.array[i]) * this.height) );
25 | }
26 |
27 | run (t, z, a) {
28 | if(!this.inputs[0]) return;
29 | this.array.unshift( this.getInput(0, t, 1)[0]/2 + 0.5);
30 | this.array.pop();
31 | }
32 | }
--------------------------------------------------------------------------------
/src/AudioModules/Sequencer16.js:
--------------------------------------------------------------------------------
1 | Modules.Sequencer16 = class Sequencer16 extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Sequencer16";
6 | this.prerun = true;
7 |
8 | this.numberOfInputs = 2;
9 | this.numberOfOutputs= 2;
10 | this.color = 70;
11 | this.width = 60;
12 | this.height = 75;
13 | this.name = "seq16";
14 | this.helpText =
15 | `---- Sequencer16 ----
16 | A Sequencer16
17 | `;
18 | this.params[0] = {
19 | name: "Data",
20 | type: "array",
21 | value: new Float32Array(16)
22 | }
23 | this.position = 0;
24 | this.currentPosition= 0;
25 | }
26 |
27 | eMouseDown (x, y) {
28 | this.currentPosition= ~~(x/15) + 4 * ~~(y/15);
29 | }
30 |
31 | eDrag (e, x, y) {
32 | this.params[0].value[this.currentPosition] -= e.movementY / 7;
33 | }
34 |
35 | interface (g) {
36 | // numbers
37 | for (let i = 0; i < 16; i++)
38 | g.text(8 - (Graphics.textSize(""+(~~this.params[0].value[i]))[0]/2) + 15 * (i%4), 5 + 15 * ~~(i/4), ~~this.params[0].value[i])
39 | // lines
40 | for (let i = 1; i < 4; i++) {
41 | g.line(0, 15 * i, this.width, 15 * i);
42 | g.line(15 * i, 0, 15 * i, this.height - 15);
43 | }
44 | // and everything nice
45 | g.context.fillRect(this.position%4 * 15, ~~(this.position/4) * 15, 15, 15);
46 | g.line(0, 60, this.width, 60);
47 | g.text(5, this.height - 10, "SEQ16");
48 |
49 | g.setColor(0);
50 | g.text( 8 - (Graphics.textSize(""+(~~this.params[0].value[this.position]))[0]/2) + 15 * (this.position%4),
51 | 5 + 15 * ~~(this.position/4),
52 | ~~this.params[0].value[this.position])
53 | }
54 |
55 | run (t, z, a) {
56 | if (a == 1 && this.position == 0) return [1,1];
57 | if (!this.inputs[0]) return [this.params[0].value[0], 0];
58 | if (z == 1 && a == 0) return [~~this.params[0].value[this.position], 0];
59 |
60 | if (this.getInput(0, t, 1)[0] > 0.9) {
61 | this.position = (this.position + 1)%16;
62 | } else if (this.getInput(1, t, 1)[0] > 0.9) {
63 | this.position = 0;
64 | }
65 | return [0,0];
66 | }
67 | }
--------------------------------------------------------------------------------
/src/AudioModules/SineOSC.js:
--------------------------------------------------------------------------------
1 | Modules.SineOSC = class SineOSC extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "SineOSC";
6 | this.prerun = true;
7 |
8 | this.numberOfInputs = 2;
9 | this.numberOfOutputs = 1;
10 | this.color = 5;
11 | this.width = 40;
12 | this.height = 30;
13 | this.name = "SINE OSC";
14 | this.helpText =
15 | `---- SineGenerator ----
16 | * 1st Input : Frequency
17 | * 2nd Input : Phase
18 |
19 | ---- *** Tips *** ----
20 | Input another signal into the 2nd input
21 | to create Phase Modulation.
22 | `;
23 |
24 | this.lastSample = 0;
25 | this.phase = 0;
26 | }
27 |
28 | interface (g, args) {
29 | let portSize = args.portSize;
30 | let anchor = portSize/2 - 3;
31 | g.text(3, anchor, "FREQ");
32 | g.text(3, anchor + portSize, "FASE");
33 | }
34 |
35 | run (t, z, a) {
36 | if( z == 1 ) return [this.lastSample, this.lastSample];
37 | const pm = this.getInput(1, t, 1)[0];
38 | const f = this.getInput(0, t, 1, 440)[0];
39 | this.lastSample = Math.sin( Math.PI*2*(this.phase += f/sampleRate) + pm )
40 | return [this.lastSample, this.lastSample];
41 | }
42 | }
--------------------------------------------------------------------------------
/src/AudioModules/Switch.js:
--------------------------------------------------------------------------------
1 | Modules.Switch = class Switch extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "Switch";
6 | this.numberOfInputs = 2;
7 | this.numberOfOutputs= 1;
8 | this.color = 2;
9 | this.width = 48;
10 | this.height = 30;
11 | this.name = "number";
12 | this.helpText =
13 | `---- Switch ----
14 | First input dictates the input to be routed.
15 | `;
16 |
17 | this.params[0] = {
18 | name: "NumRoute",
19 | type: "number",
20 | _v: 1,
21 | text: "1",
22 | set value (x) {
23 | x = Math.max(1, x);
24 | this._v = x;
25 | },
26 | get value () { return this._v; }
27 | };
28 | this.currentInput = 0;
29 |
30 | }
31 |
32 | eDrag (e) {
33 | this.params[0].value = this.params[0].value - e.movementY/10;
34 | this.numberOfInputs = ~~this.params[0].value + 1;
35 | this.height = 15*this.numberOfInputs;
36 | for (let i = this.numberOfInputs; i < this.inputs.length; i++)
37 | this.unsetInput(i);
38 | }
39 |
40 | interface (g, args) {
41 | g.context.fillRect(0,15 * (this.currentInput+1), 15, 15);
42 | g.text(3, 4, "SW"+(this.numberOfInputs - 1)+"J");
43 |
44 | //this shouldn't be here... but...
45 | this.eDrag({movementY:0});
46 | }
47 |
48 | run (t, z, a) {
49 | this.currentInput = this.getInput(0, t, 1)[0]? ~~Math.max(0, this.getInput(0, t, 1)[0]%(this.numberOfInputs-1) ) : 0;
50 | let output = this.getInput(1 + this.currentInput, t, 1);
51 | return [output[0], output[1]];
52 | }
53 | }
--------------------------------------------------------------------------------
/src/AudioModules/TextDisplay.js:
--------------------------------------------------------------------------------
1 | Modules.TextDisplay = class TextDisplay extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "TextDisplay";
6 | this.numberOfInputs = 0;
7 | this.numberOfOutputs= 0;
8 | this.color = 1;
9 | this.width = 150;
10 | this.height = 30;
11 | this.name = "number";
12 |
13 | this.params[0] = {
14 | name: "Text",
15 | type: "text",
16 | value: ""
17 | };
18 | this.params[1] = {
19 | name: "Width",
20 | type: "number",
21 | value: 150,
22 |
23 | };
24 | this.params[2] = {
25 | name: "Height",
26 | type: "number",
27 | value: 30
28 | };
29 |
30 | }
31 |
32 | interface (g, args) {
33 | this.width = this.params[1].value;
34 | this.height = this.params[2].value;
35 | g.text(4, 4, this.params[0].value );
36 | }
37 |
38 | run (t, z, a) {
39 | return ;
40 | }
41 | }
--------------------------------------------------------------------------------
/src/AudioModules/XYScope.js:
--------------------------------------------------------------------------------
1 | Modules.XYScope = class XYScope extends AudioModule {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.className = "XYScope";
6 | this.prerun = true;
7 |
8 | this.numberOfInputs = 1;
9 | this.numberOfOutputs= 0;
10 | this.color = 1;
11 | this.width = 45;
12 | this.height = 45;
13 | this.name = "xyscope";
14 | this.helpText =
15 | `---- XYScope ----
16 | Parametric Oscilloscope
17 | Left channel, Right Channel
18 | `;
19 | this.larray = new Array(256);
20 | this.rarray = new Array(256);
21 | }
22 |
23 | interface (g, args) {
24 | for(var i=0; i < this.larray.length; ++i)
25 | g.point(~~(this.larray[i] * 22) + 22, ~~(this.rarray[i] * 22) + 22 );
26 | }
27 |
28 | run (t, z, a) {
29 | if(!this.inputs[0]) return;
30 | let i = this.getInput(0, t, 1);
31 | this.larray.unshift(i[0]);
32 | this.larray.pop();
33 | this.rarray.unshift(i[1]);
34 | this.rarray.pop();
35 | }
36 | }
--------------------------------------------------------------------------------
/src/Desktop.js:
--------------------------------------------------------------------------------
1 | class Desktop {
2 | constructor (Graphics, moduleList) {
3 | this.g = Graphics;
4 | this.mL = moduleList;
5 |
6 | // Constants (not technically)
7 | this.portSize = 15;
8 |
9 | // Display
10 | this.cX = 0;
11 | this.cY = 0;
12 | this.sXY = 1;
13 | // Mouse
14 | this.mX = 0;
15 | this.mY = 0;
16 | // Modules
17 | this.selectedModule = undefined;
18 | this.selectedInput = -1;
19 | this.selectedOutput = -1;
20 |
21 | this.caplocks = false;
22 | }
23 |
24 | scaleUp (e) {
25 | if ( this.sXY >= 3) return;
26 | this.cX -= ~~this.mouseMapX(e.clientX);
27 | this.cY -= ~~this.mouseMapY(e.clientY);
28 | this.sXY = ~~(this.sXY + 1);
29 | }
30 |
31 | scaleDown (e) {
32 | if ( this.sXY == 1 ) return;
33 | this.cX += ~~this.mouseMapX(e.clientX);
34 | this.cY += ~~this.mouseMapY(e.clientY);
35 | this.sXY = ~~(this.sXY - 1);
36 | }
37 |
38 | mouseMapX (x) {
39 | return (x - this.cX)/this.sXY;
40 | }
41 |
42 | mouseMapY (y) {
43 | return (y - this.cY)/this.sXY;
44 | }
45 |
46 | isOnModule (x, y) {
47 | x = this.mouseMapX(x);
48 | y = this.mouseMapY(y);
49 |
50 | var _m = undefined;
51 | this.mL.some(m => x > m.x - this.portSize && x < m.x + m.width + this.portSize && y > m.y && y < m.y + m.height && (_m = m));
52 | return _m;
53 | }
54 |
55 | isOnInput (m, x, y) {
56 | if (!m) return -1;
57 | x = this.mouseMapX(x);
58 | y = this.mouseMapY(y);
59 | return ( x < m.x && y < m.y + m.numberOfInputs * this.portSize )? ~~((y - m.y)/this.portSize) : -1;
60 | }
61 |
62 | isOnOutput (m, x, y) {
63 | if (!m) return -1;
64 | x = this.mouseMapX(x);
65 | y = this.mouseMapY(y);
66 | return ( x > m.x + m.width && y < m.y + m.numberOfOutputs * this.portSize )? ~~((y - m.y)/this.portSize) : -1;
67 | }
68 |
69 | eMouseDown (e, listener) {
70 | const x = e.clientX, y = e.clientY;
71 | const m = this.isOnModule(x, y);
72 |
73 | if (m) {
74 | this.selectedModule = m;
75 | this.selectedOutput = this.isOnOutput(m, x, y);
76 |
77 | if(this.selectedOutput !== -1) return;
78 |
79 | if (e.shiftKey || this.caplocks) {
80 | listener.turnOn("eModuleShiftDrag");
81 | this.selectedModule.eMouseDown(this.mouseMapX(x) - this.selectedModule.x, this.mouseMapY(y) - this.selectedModule.y);
82 | } else {
83 | listener.turnOn("eModuleDrag");
84 | }
85 |
86 | } else {
87 | listener.turnOn("eDrag");
88 | }
89 | }
90 |
91 | eMouseUp (e, listener) {
92 | const x = e.clientX, y = e.clientY;
93 | const m = this.isOnModule(x, y);
94 | let i = -1;
95 |
96 | if (m && (i = this.isOnInput(m, x, y)) !== -1)
97 | if(this.selectedOutput !== -1) {
98 | if( m.inputs[i] && m.inputs[i].index == this.selectedOutput ) m.unsetInput(i);
99 | this.selectedModule.connect(m, i, this.selectedOutput);
100 | }
101 |
102 | this.selectedOutput = this.selectedInput = -1;
103 |
104 | listener.turnOff("eModuleShiftDrag");
105 | listener.turnOff("eModuleDrag");
106 | listener.turnOff("eDrag");
107 | }
108 |
109 | eDrag (e) {
110 | this.cX += e.movementX;
111 | this.cY += e.movementY;
112 | }
113 |
114 | eMouseMove (e) {
115 | this.mX = this.mouseMapX(e.clientX);
116 | this.mY = this.mouseMapY(e.clientY);
117 | }
118 |
119 | eModuleDrag (e) {
120 | this.selectedModule.x += e.movementX / this.sXY;
121 | this.selectedModule.y += e.movementY / this.sXY;
122 | }
123 |
124 | eModuleShiftDrag(e) {
125 | let x = this.mouseMapX(e.clientX ) - this.selectedModule.x;
126 | let y = this.mouseMapY(e.clientY ) - this.selectedModule.y;
127 | this.selectedModule.eDrag(e, x, y);
128 | }
129 |
130 | eKeyDown (e) {
131 | if (e.key == "CapsLock")
132 | this.caplocks = !this.caplocks;
133 | }
134 |
135 | render () {
136 | const g = this.g;
137 | const portSize = this.portSize;
138 |
139 | g.setColor(1);
140 | if (this.caplocks)
141 | g.text(5, 5, "CAPLOCKS ON");
142 |
143 | g.context.save();
144 |
145 | g.context.translate(this.cX, this.cY);
146 | g.context.scale(this.sXY, this.sXY);
147 |
148 | if (this.selectedOutput !== -1) {
149 | const m = this.selectedModule;
150 | g.context.fillStyle = "#fff";
151 | g.line(
152 | m.x + m.width + portSize,
153 | m.y + portSize/2 + this.selectedOutput * portSize,
154 | this.mX,
155 | this.mY
156 | );
157 | }
158 |
159 | for ( let i = 0; i < this.mL.length; i++ ) {
160 | const m = this.mL[i];
161 |
162 | // Draw current module
163 | //g.context.fillStyle = g.context.strokeStyle = `hsl(${m.color}, 100%, ${(m == this.selectedModule)? 65 : 85}%)`;
164 | g.setColor(m.color);
165 | if (this.selectedModule == m)
166 | g.box(m.x, m.y + m.height, m.width, 1);
167 | g.box(m.x, m.y, m.width, m.height);
168 |
169 | // Draw inputs & outputs
170 | for (let j=0; j < m.numberOfInputs ; j++)
171 | g.box(m.x - portSize, m.y + j * portSize, portSize, portSize);
172 | for (let j=0; j < m.numberOfOutputs; j++)
173 | g.box(m.x + m.width, m.y + j * portSize, portSize, portSize);
174 |
175 | // Draw connections
176 | for (let j=0; j < m.inputs.length; j++) {
177 | // Skip empty ports
178 | if (!m.inputs[j]) continue;
179 |
180 | const sourceModule = m.inputs[j].module;
181 | g.line(
182 | m.x - portSize,
183 | m.y + portSize/2 + portSize * j,
184 | sourceModule.x + sourceModule.width + portSize,
185 | sourceModule.y + portSize/2 + portSize * m.inputs[j].index
186 | );
187 | }
188 |
189 | // Module local rendering
190 | g.context.save();
191 | g.context.translate(~~m.x, ~~m.y);
192 | m.interface(g, { portSize: portSize } );
193 | g.context.restore();
194 | }
195 |
196 | g.context.restore();
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/Graphics.js:
--------------------------------------------------------------------------------
1 | class Graphics {
2 | constructor (config) {
3 | this.config = config ||
4 | {
5 | width : 300,
6 | height: 300
7 | };
8 |
9 | this.DOMElement = document.createElement("canvas");
10 | this.DOMElement.width = config.width;
11 | this.DOMElement.height = config.height;
12 |
13 | this.context = this.DOMElement.getContext("2d");
14 |
15 | this.palette = ["#000", "#FFF", "#FF0", "#F0F", "#0FF", "#0F8", "#F80", "#F08", "#333"];
16 | this.currentColor = 1;
17 |
18 | this.font = config.font; //bitmap
19 | this.fontMap = " 、。,.·:;?!゛°'`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー--/\\~‖|…‥‘’“”()〔〕[]{}〈〉《》「」『』【】+-±×÷=≠<>≦≧∞∴♂♀゜′″℃¥$¢£%#%*@§☆★◆□■△▲▽▼※〒→←↑↓〓 ∈∋⊆⊇⊂⊃∩∪ ∧∨¬⇒⇔∀∃ ∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬ ʼn♯♭♪†‡¶ 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをん ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶ ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ αβγδεζηθικλμνξοπρςστυφχψω АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ абвгдеёжзийклмнопрстуфхцчшщъыьэюя ─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂";
20 | this.fontSet = [];
21 |
22 | this.loadFont();
23 | }
24 |
25 | loadFont () {
26 | // generate font set
27 | let dummyCanvas = document.createElement("canvas"); dummyCanvas.width = this.font.width; dummyCanvas.height = this.font.height;
28 | let dummyContxt = dummyCanvas.getContext("2d");
29 | let SIZE = 8*3; //8;
30 | let promises = [];
31 | for (let i = 0 ; i < this.palette.length; i++ ) {
32 | //to rgb 0-255
33 | let rgb = [0,0,0].map((x,k) => {
34 | return (parseInt(this.palette[i].substr(1,3),16) >> 4*(2-k) & 0xF)*17 ;
35 | });
36 | // draw bitmap somewhere to get image data
37 | dummyContxt.drawImage(this.font, 0, 0);
38 | let bitmap = dummyContxt.getImageData(0, 0, this.font.width, this.font.height);
39 | let data = bitmap.data;
40 | // coloring
41 | for (let j = 0; j < data.length; j += 4 ) {
42 | if (data[j+3] == 255) {
43 | data[j] = rgb[0];
44 | data[j+1] = rgb[1];
45 | data[j+2] = rgb[2];
46 | }
47 | }
48 | //create bitmaps
49 | let out = [];
50 | for (let j = 0; j < 720; j++) {
51 | out.push(createImageBitmap(bitmap,(j%90)*SIZE,~~(j/90)*SIZE, SIZE, SIZE));
52 | }
53 | let lilprom = Promise.all(out).then(bitmaps => {
54 | this.fontSet.push(bitmaps);
55 | });
56 | promises.push(lilprom);
57 | }
58 | Promise.all(promises).then( () => {
59 | // 2DContext.drawImage will throw an error, so we copy this method when all bitmap finishes resolving.
60 | this.text = this.text_;
61 | });
62 | }
63 |
64 | setColor (number) {
65 | this.context.fillStyle = this.context.strokeStyle = this.palette[number%this.palette.length];
66 | this.currentColor = number%this.palette.length;
67 | }
68 |
69 | appendTo (parent) {
70 | if ( parent instanceof Element ) parent.appendChild(this.DOMElement);
71 | }
72 |
73 | background (color) {
74 | this.context.fillStyle = color;
75 | this.context.fillRect(0,0, this.config.width, this.config.height);
76 | }
77 |
78 | point (x, y) {
79 | this.context.fillRect(x,y,1,1);
80 | }
81 |
82 | // point with variable size
83 | point2 (x, y, f) {
84 | this.context.fillRect(x,y,f||1, f||1);
85 | }
86 |
87 | // bresenham, taken from here: https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#JavaScript
88 | // this code is gplv3
89 | line (x0, y0, x1, y1) {
90 | x0 |= 0; x1 |= 0; y0 |= 0; y1 |= 0;
91 | const dx = Math.abs(x1-x0);
92 | const dy = Math.abs(y1-y0);
93 | let sx = x0 < x1 ? 1 : -1;
94 | let sy = y0 < y1 ? 1 : -1;
95 | let err = (dx > dy ? dx : -dy) /2;
96 | while ( !(x0 == x1 && y0 == y1) ) {
97 | this.point(x0, y0);
98 | let e2 = err;
99 | if (e2 > -dx) {
100 | err -= dy;
101 | x0 += sx;
102 | }
103 | if (e2 < dy) {
104 | err += dx;
105 | y0 += sy;
106 | }
107 | }
108 | }
109 |
110 | box (x, y, w, h) {
111 | x|=0; y|=0; w |=0; h|=0;
112 | this.context.strokeRect(x+.5, y+.5, w, h);
113 | }
114 | fillBox (x, y, w, h) {
115 | x|=0; y|=0; w |=0; h|=0;
116 | this.context.fillRect(x, y, w, h);
117 | }
118 |
119 | text () {}
120 | text_ (x, y, txt, f = 1) {
121 | x |= 0; y |= 0;
122 |
123 | let x0 = 0; let y0 = 0;
124 | [...txt.toString()].forEach(ltr => {
125 | if (ltr == '\n' && (y0 += 8))
126 | return x0 = 0;
127 |
128 | let index = this.fontMap.indexOf(ltr);
129 | if (index == -1) return x0+=7;
130 | this.context.drawImage(this.fontSet[this.currentColor][index], x + x0*f, y + y0*f, 8*f, 8*f);
131 | x0 += 8;
132 | });
133 |
134 | }
135 |
136 | // returns [width, height] in pixels
137 | static textSize (txt, f = 1) {
138 | let width = txt.length * 8 * f;
139 | let height = 8;
140 | for (let i = 0; i < txt.length; i++)
141 | height += txt.charAt(i) == "\n"? 8 : 0;
142 | return [width, height];
143 | }
144 | }
--------------------------------------------------------------------------------
/src/Interface.js:
--------------------------------------------------------------------------------
1 | class Interface {
2 | constructor (Graphics) {
3 | this.g = Graphics;
4 | this.elementList = [];
5 |
6 | this.sXY = 1;
7 | }
8 |
9 | add (element) {
10 | if (!(element instanceof InterfaceElement)) throw new Error("Not an InterfaceElement");
11 | this.elementList.unshift(element);
12 | element.parent = this;
13 | return this;
14 | }
15 |
16 | remove (element) {
17 | for ( let i = 0; i < this.elementList.length; i++ )
18 | if (element == this.elementList[i])
19 | this.elementList.splice(i, 1);
20 | return this;
21 | }
22 |
23 | removeByName (name) {
24 | for ( let i = 0; i < this.elementList.length; i++ )
25 | if (name == this.elementList[i].name)
26 | this.elementList.splice(i, 1);
27 | }
28 |
29 | isOnElement (el, x, y) {
30 | return x > el.x && x < el.x + el.w && y > el.y && y < el.y + el.h;
31 | }
32 |
33 | eMouseDown (e) {
34 | let x = e.clientX,
35 | y = e.clientY;
36 |
37 | let isOnElement = false;
38 |
39 | for ( let i = 0; i < this.elementList.length; i++ ) {
40 | const el = this.elementList[i];
41 | if ( isOnElement = this.isOnElement(el, x, y) && !el.bypassMouseDown) {
42 | // Put to index zero.
43 | this.elementList.unshift(this.elementList.splice(i, 1)[0]);
44 | el.eMouseDown(x - el.x, y - el.y);
45 | break;
46 | }
47 | el.eUnfocus();
48 | }
49 |
50 | return !isOnElement; //isBlocking == true
51 | }
52 |
53 | eMouseUp (e) {
54 | for ( let i = 0; i < this.elementList.length; i++ )
55 | this.elementList[i].eMouseUp(e);
56 | }
57 |
58 | eMouseMove (e) {
59 | const x = e.clientX;
60 | const y = e.clientY;
61 | this.g.DOMElement.style.cursor = "auto";
62 | for ( let i = 0; i < this.elementList.length; i++ )
63 | this.elementList[i].eMouseMove(e, x, y);
64 | }
65 |
66 | eKeyDown (e) {
67 | for ( let i = 0; i < this.elementList.length; i++ )
68 | this.elementList[i].eKeyDown(e);
69 | }
70 |
71 | render () {
72 | for ( let i = this.elementList.length - 1; i >= 0; i-- )
73 | this.elementList[i].render(this.g);
74 | }
75 | }
76 |
77 | class InterfaceElement {
78 | constructor (con = {}) {
79 | this.name = con.name || "SuperElement";
80 | this.parent = undefined;
81 | this.bypassMouseDown = false;
82 |
83 | this.x = con.x || 10;
84 | this.y = con.y || 10;
85 | this.w = con.w || 300;
86 | this.h = con.h || 300;
87 | }
88 |
89 | eMouseDown (x, y) {}
90 | eKeyDown (e) {}
91 | eUnfocus (e) {}
92 | eMouseUp (e) {}
93 | eMouseMove (e, x, y) {}
94 | render (g) {}
95 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/InterfaceContextMenu.js:
--------------------------------------------------------------------------------
1 | class InterfaceContextMenu extends InterfaceElement {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.padding = con.padding || 3;
6 | this.top = InterfaceContextMenu.preCalc(con.options, true, this.padding);
7 |
8 | this.w = this.top.width;
9 | this.h = this.top.height;
10 |
11 | // highlighter
12 | this.currentOption = undefined;
13 |
14 | return this;
15 | }
16 |
17 | static preCalc (input, visible = false, padding = 3) {
18 | let output = {
19 | visible: visible,
20 | width: 0,
21 | height: 0,
22 | children: []
23 | };
24 |
25 | for (let i = 0; i < input.length; i++) {
26 | let option = input[i];
27 | option.text = option.text.toUpperCase();
28 | let textSize = Graphics.textSize(option.text);
29 |
30 | output.width = Math.max(output.width, textSize[0] + padding*2);
31 | output.height += textSize[1] + padding*2;
32 |
33 | let child = {
34 | parent: output,
35 | text: option.text,
36 | callback: option.callback,
37 | children: (option.children)? this.preCalc(option.children, false, padding) : undefined
38 | };
39 |
40 | if (child.children) child.children.parent = child;
41 |
42 | output.children.push(child);
43 | }
44 |
45 | return output;
46 | }
47 |
48 | getOptionFromMouse (node, x, y, x0 = 0) {
49 | if (!node.visible || x < 0 || y < 0) return undefined;
50 |
51 | let boxHeight = this.padding * 2 + Graphics.textSize("A")[1];
52 | let index = Math.floor(y / boxHeight);
53 |
54 | if (x < node.width && node.children[index]) {
55 | return node.children[index];
56 | } else {
57 | let o = undefined;
58 | for (let i = 0; !o && i < node.children.length; i++)
59 | if (node.children[i].children)
60 | o = this.getOptionFromMouse(node.children[i].children, x - node.width, y - i * boxHeight, x0 + node.width);
61 | return o;
62 | }
63 | }
64 |
65 | // Calculate entire menus' total widths and height from an option
66 | menuSizeFromOption (node, width = 0, height = 0) {
67 | let boxHeight = this.padding * 2 + Graphics.textSize("A")[1];
68 | if (node !== this.top)
69 | return this.menuSizeFromOption(node.parent, width + ~~node.width, height + ~~node.height);
70 | else
71 | return [width + node.width, height + node.height];
72 | }
73 |
74 | eMouseDown (x, y) {
75 | this.currentOption = this.getOptionFromMouse(this.top, x, y);
76 |
77 | if(this.currentOption && this.currentOption.callback) {
78 | this.currentOption.callback();
79 | this.eUnfocus();
80 | }
81 |
82 | }
83 |
84 | eUnfocus () {
85 | this.parent.remove(this);
86 | }
87 |
88 | eMouseMove (e, x, y) {
89 | x = x - this.x,
90 | y = y - this.y;
91 |
92 | this.w = this.top.width;
93 | this.h = this.top.height;
94 |
95 | this.currentOption = this.getOptionFromMouse(this.top, x, y);
96 |
97 | if(this.currentOption) {
98 | const menuSize = this.menuSizeFromOption(this.currentOption);
99 | this.w = menuSize[0];
100 | this.h = menuSize[1];
101 |
102 | // clear lower level nodes that aren't hovered
103 | for(let option of this.currentOption.parent.children)
104 | if (option.children)
105 | option.children.visible = false;
106 |
107 | if (this.currentOption.children) {
108 | this.currentOption.children.visible = true;
109 | }
110 | }
111 | }
112 |
113 | renderNode (g, node, depth = 0) {
114 | const boxHeight = this.padding * 2 + Graphics.textSize("A")[1];
115 |
116 | g.context.fillStyle = `hsl(${depth*20}, 60%, 35%)`;
117 | g.context.fillRect(0, 0, node.width , node.height);
118 |
119 | let y = 0;
120 | for (let i = 0; i < node.children.length; i++) {
121 | let child = node.children[i];
122 |
123 | if (this.currentOption == child) {
124 | g.context.fillStyle = `hsl(${depth*20}, 30%, 50%)`;
125 | g.context.fillRect(0, i*boxHeight, node.width, boxHeight);
126 | }
127 |
128 | g.setColor(1);
129 | g.box(0, y, node.width, boxHeight);
130 | g.text(this.padding, y + this.padding, child.text);
131 |
132 | if (child.children) {
133 | g.line(node.width - 5, y + boxHeight, node.width , y + boxHeight - 5);
134 |
135 | if (child.children && child.children.visible) {
136 | g.context.save();
137 | g.context.translate(node.width, boxHeight*i);
138 | // recurse
139 | this.renderNode(g, child.children, depth + 1);
140 | g.context.restore();
141 | }
142 | }
143 |
144 | y += boxHeight;
145 | }
146 | }
147 |
148 | render (g) {
149 | g.context.save();
150 | g.context.translate(this.x, this.y);
151 | this.renderNode(g, this.top);
152 | g.context.restore();
153 | }
154 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/InterfaceMenuBar.js:
--------------------------------------------------------------------------------
1 | class InterfaceMenuBar extends InterfaceElement {
2 | constructor (con) {
3 | super(con);
4 | this.x = 0;
5 | this.y = 0;
6 | this.h = 24;
7 | this.w = innerWidth;
8 |
9 | this.menus = con.menus;
10 | this.app = con.app;
11 |
12 | let prevX = 0;
13 | this.menus = this.menus.map( x => {
14 | x.x = prevX;
15 | x.w = Graphics.textSize(x.name)[0] + 16
16 | prevX += x.w;
17 | return x;
18 | });
19 |
20 | this.currentIndex = -1;
21 | }
22 |
23 | eMouseDown () {
24 | if (this.currentIndex !== -1) this.menus[this.currentIndex].callback(this.parent, this.app, this.menus[this.currentIndex].x);
25 | }
26 |
27 | eMouseMove (e, x, y) {
28 | if (y > 24) return;
29 |
30 | for (let i = 0; i < this.menus.length ; i++) {
31 | if( this.menus[i].x + this.menus[i].w > x ) {
32 | this.currentIndex = i;
33 | break;
34 | }
35 | this.currentIndex = -1;
36 | }
37 |
38 | }
39 |
40 | render (g) {
41 | // Border
42 | g.setColor(8);
43 | g.fillBox(-1, -1, innerWidth+2, 24);
44 | g.setColor(1);
45 | g.box(-1, -1, innerWidth+2, 24);
46 |
47 | g.setColor(0);
48 | if (this.currentIndex !== -1) g.fillBox(this.menus[this.currentIndex].x , 0, this.menus[this.currentIndex].w, 23);
49 |
50 | g.setColor(1);
51 | for (let i = 0 ; i < this.menus.length; i++)
52 | g.text(8 + this.menus[i].x, 8, this.menus[i].name)
53 | }
54 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/InterfaceSprinkles.js:
--------------------------------------------------------------------------------
1 | class InterfaceSprinkles extends InterfaceElement {
2 | constructor (con) {
3 | super(con);
4 | this.x = this.y = 0;
5 | this.mx = this.my = 0;
6 |
7 | this.bucket = [];
8 | this.bypassMouseDown = true;
9 | this.i = 0;
10 | }
11 |
12 | eMouseMove (e) {
13 | if( this.i % 3 == 0)
14 | this.bucket.unshift(
15 | [
16 | ~~(e.clientX + (Math.random()-Math.random())*20),
17 | ~~(e.clientY + (Math.random()-Math.random())*20),
18 | Math.random()*360,
19 | 0
20 | ]);
21 | if (this.bucket.length > 70) this.bucket.pop();
22 | this.i++;
23 | }
24 |
25 | render (g) {
26 | for ( let j = 0; j < this.bucket.length; j++) {
27 | let p = this.bucket[j];
28 | let k = p[3] % 8;
29 | g.context.fillStyle = `hsl(${p[2]}, 100%, 90%)`;
30 |
31 | g.point2(p[0], p[1], 2);
32 | if (k < 4) {
33 | g.point2(p[0]-2, p[1], 2);
34 | g.point2(p[0]+2, p[1], 2);
35 | } else {
36 | g.point2(p[0], p[1]-2, 2);
37 | g.point2(p[0], p[1]+2, 2);
38 | }
39 |
40 | p[1] += 8;
41 | p[3]++;
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/InterfaceWindow.js:
--------------------------------------------------------------------------------
1 | class InterfaceWindow extends InterfaceElement {
2 | constructor (con) {
3 | super(con);
4 |
5 | this.title = con.title || "WINDOW TITLE";
6 | this.children = [];
7 |
8 | //properties
9 | this.isResizable = con.isResizable || false;
10 |
11 | // states
12 | this.onCloseBox = false;
13 | this.onResize = false;
14 | this.isDragging = false;
15 | this.isResizing = false;
16 |
17 | return this;
18 | }
19 |
20 | appendChild (component) {
21 | if(!(component instanceof InterfaceWindowComponent)) throw new TypeError("Must add only InterfaceWindowComponents");
22 | this.children.push(component);
23 | return this;
24 | }
25 |
26 | isOnComponent (com, x, y) {
27 | return x > com._absX && x < com._absX + com._absW && y > com._absY && y < com._absY + com._absH;
28 | }
29 |
30 | close () {
31 | this.parent.remove(this)
32 | }
33 |
34 | eMouseDown (x, y) {
35 | // titty bar
36 | if (y < 20) {
37 | // close btn
38 | if (x > this.w - 20) {
39 | this.parent.g.DOMElement.style.cursor = "auto";
40 | this.close();
41 | }
42 | this.isDragging = true;
43 | }
44 |
45 | if ( x > this.w - 15 && y > this.h - 15 && this.isResizable)
46 | this.isResizing = true;
47 |
48 | // components
49 | for (let i = 0; i < this.children.length; i++) {
50 | let el = this.children[i];
51 | if (this.isOnComponent(el, x, y))
52 | el.eMouseDown(x - el._absX, y - el._absY);
53 | else
54 | el.eUnfocus();
55 | }
56 |
57 | }
58 |
59 | eUnfocus () {
60 | }
61 |
62 | eMouseMove (e) {
63 | const x = e.clientX - this.x, y = e.clientY - this.y;
64 |
65 | this.onCloseBox = (x > this.w - 20 && x < this.w && y > 0 && y < 20);
66 | this.onResize = ( x > this.w - 15 && x < this.w + 3 && y > this.h - 15 && y < this.h + 3);
67 |
68 | if (this.onCloseBox)
69 | this.parent.g.DOMElement.style.cursor = "pointer";
70 | if (this.onResize && this.isResizable)
71 | this.parent.g.DOMElement.style.cursor = "se-resize";
72 |
73 | if (this.isResizing) {
74 | this.w += (this.w + e.movementX > 220) * e.movementX;
75 | this.h += (this.h + e.movementY > 200) * e.movementY;
76 | }
77 |
78 | if (this.isDragging) {
79 | this.x += e.movementX;
80 | this.y += e.movementY;
81 | }
82 |
83 | // components
84 | for (let i = 0; i < this.children.length; i++) {
85 | let el = this.children[i];
86 | if (this.isOnComponent(el, x, y))
87 | el.eMouseMove(x - el._absX, y - el._absY);
88 | else
89 | el.eMouseOut();
90 | }
91 | }
92 |
93 | eMouseUp (e) {
94 | this.isDragging = this.isResizing = false;
95 | for (let i = 0; i < this.children.length; i++)
96 | this.children[i].eMouseUp(e);
97 | }
98 |
99 | eKeyDown (e) {
100 | for (let i = 0; i < this.children.length; i++)
101 | this.children[i].eKeyDown(e);
102 | }
103 |
104 | render (g) {
105 |
106 | // switch to window's reference frame
107 | g.context.save();
108 | g.context.translate(this.x, this.y);
109 |
110 | g.context.fillStyle = "#000";
111 | g.context.fillRect(0, 0, this.w, this.h);
112 |
113 | g.setColor(1);
114 | g.box (0 , 0 , this.w , this.h);
115 | // Title Box
116 | g.context.lineWidth = 1;
117 | g.context.save();
118 | g.context.beginPath();
119 | g.context.rect(1, 1, this.w-1, 20);
120 | g.context.clip();
121 | g.box (0 , 0 , this.w , 20);
122 | g.text (7, 7, this.title, 1);
123 | g.context.restore();
124 | // Close Box
125 | g.context.fillStyle = (this.onCloseBox)? "#f44" : "#d33";
126 | g.context.fillRect (this.w - 20 , 0 , 20 , 20);
127 | g.context.fillStyle = "#fff";
128 | g.box (this.w - 20 , 0 , 20 , 20);
129 | // Resize
130 | if (this.isResizable)
131 | g.line ( this.w - 10, this.h, this.w , this.h - 10)
132 |
133 | //Rendering Children
134 | /***
135 | +--------------+-------+
136 | | | |
137 | | cw | |
138 | | +------+ | |
139 | | |child |ch |wh |
140 | | +------+ | |
141 | | wrapper | |
142 | +--------------+ |
143 | | child.ww |
144 | <--------------> |
145 | | |
146 | | window |
147 | +----------------------+
148 | this.w
149 | <---------------------->
150 | ***/
151 | g.context.save();
152 | g.context.beginPath();
153 | g.context.rect(0, 0, this.w-1, this.h-1);
154 | g.context.clip();
155 |
156 | let offsetX = 0,
157 | offsetY = 0,
158 | currentRowHeight = 0;
159 | for (let i = 0; i < this.children.length; i++) {
160 | const child = this.children[i];
161 | const cw = child.w,
162 | ch = child.h,
163 | ww = child.ww * this.w,
164 | wh = child.wh;
165 |
166 | if (offsetX + ww > this.w) {
167 | offsetX = 0;
168 | offsetY = offsetY + currentRowHeight;
169 | currentRowHeight = 0;
170 | }
171 | // Calculate (0, 0) position for components.
172 | // w,h <= 1 : assume it's a ratio; otherwise, pixel.
173 | let compXAnchor = (cw > 1)? (ww-cw)/2 : (ww * (1-cw))/2;
174 | let compYAnchor = (ch > 1)? (wh-ch)/2 : (wh * (1-ch))/2 ;
175 |
176 | child._absX = ~~(offsetX + compXAnchor);
177 | child._absY = ~~(20 + offsetY + compYAnchor);
178 | child._absW = (cw > 1)? cw : ww * cw;
179 | child._absH = (ch > 1)? ch : wh * ch;
180 | g.context.save();
181 | //g.box(0offsetX, 20 + offsetY, ww, wh);
182 | g.context.translate(child._absX, child._absY);
183 | child.render(g, child._absW, child._absH, ww);
184 | g.context.restore();
185 |
186 | currentRowHeight = Math.max(currentRowHeight, wh);
187 | offsetX += ww;
188 |
189 | }
190 |
191 | g.context.restore();
192 | g.context.restore();
193 | }
194 | }
195 |
196 | /**
197 | InterfaceWindowComponent:
198 | * InterfaceWindowComponents do not have relative position by default.
199 | * Components must provide dimensions (w, h).
200 | If the width or height is <= 1, it is assumed as ratio.
201 | * User instantiating Components must provide the ratio of the width of the
202 | component versus the width of the container/wrapper (sx) (e.g. InterfaceWindow).
203 | * User don't need to provide the ratio of heights.
204 | Usually, it does not make sense to have elements scaled to height ratios.
205 | They must, however, provide it in number of pixels.
206 | * InterfaceElements which are able to take InterfaceWindowComponents as children
207 | can refer to the user-provided ratio to render appropriately. That is,
208 | using Component's dimension to set (0, 0) to where Component renders itself.
209 | And if the user provided a relative position, the Element renders as said.
210 |
211 | * Subclasses should be named in the form Window[Name]. E.g.: WindowText
212 | **/
213 |
214 | class InterfaceWindowComponent {
215 | constructor (con = {}) {
216 | this.parent = undefined;
217 |
218 | this.w = 30;
219 | this.h = 10;
220 | this.ww = con.ww || 1;
221 | this.wh = con.wh || 10;
222 | }
223 |
224 | eMouseDown (x, y) {}
225 | eUnfocus (e) {}
226 | eMouseUp (e) {}
227 | eMouseMove (x, y) {}
228 | eMouseOut () {}
229 | eKeyDown (e) {}
230 | render (g, w, h) {}
231 | }
232 |
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowButton.js:
--------------------------------------------------------------------------------
1 | class WindowButton extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 | this.content = con.content || "";
5 | this.fontSize = con.fontSize || 1;
6 | this.fontColor = con.fontColor|| "#fff";
7 | this.callback = con.callback || (z => {});
8 |
9 | let dimensions = Graphics.textSize(this.content, this.fontSize);
10 | this.w = dimensions[0] + 5;
11 | this.h = dimensions[1] + 5;
12 |
13 | this.isHovered = false;
14 | }
15 |
16 | eMouseDown (x, y) {
17 | this.callback();
18 | }
19 |
20 | eMouseOut (e) {
21 | this.isHovered = false;
22 | }
23 |
24 | eMouseMove (e) {
25 | this.isHovered = true;
26 | }
27 |
28 | render (g, w, h) {
29 | if (this.isHovered) {
30 | g.context.fillStyle = "#fff";
31 | g.context.fillRect(0, 0, w, h);
32 | g.setColor(0);
33 | g.text(3, 3, this.content, this.fontSize);
34 | } else {
35 | g.setColor(1);
36 | g.text(3, 3, this.content, this.fontSize);
37 | g.box(0, 0, w, h);
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowCheckBox.js:
--------------------------------------------------------------------------------
1 | class WindowCheckBox extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 |
5 | this.w = 10;
6 | this.h = 10;
7 |
8 | this.getValue = con.getValue || (z => false);
9 | this.setValue = con.setValue || (z => {});
10 |
11 | this.value = this.getValue();
12 | }
13 |
14 | eMouseDown (x, y) {
15 | this.value = !this.value;
16 | this.setValue(this.value);
17 | }
18 |
19 | render (g, w, h) {
20 | g.context.fillStyle = g.context.strokeStyle = "#fff";
21 | g.box(0, 0, 10, 10);
22 | if(this.value) g.context.fillRect(0, 0, 10 , 10);
23 |
24 | }
25 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowColor.js:
--------------------------------------------------------------------------------
1 | class WindowColor extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 |
5 | this.w = 3/4;
6 | this.h = 10;
7 |
8 | this.getValue = con.getValue || (z => "");
9 | this.setValue = con.setValue || (z => {});
10 |
11 | this.isTyping = false;
12 | this.inputBuffer = this.getValue().toString();
13 | this.tempColor = this.inputBuffer;
14 | this.acceptedChars = "0123456789abcdefABCDEF#";
15 | this.validation = /^\#[0-9a-fA-F]{6}$/i;
16 | }
17 |
18 | eMouseDown (x, y) {
19 | this.isTyping = true;
20 | }
21 |
22 | eUnfocus () {
23 | if (this.isTyping) {
24 | this.isTyping = false;
25 | this.inputBuffer = (this.validation.test(this.inputBuffer))? this.inputBuffer.toUpperCase() : "#000000";
26 | this.setValue(this.inputBuffer);
27 | }
28 | }
29 |
30 | eKeyDown (e) {
31 | if (this.isTyping)
32 | switch(e.key) {
33 | case 'Enter':
34 | this.eUnfocus();
35 | break;
36 | case 'Backspace':
37 | this.inputBuffer = this.inputBuffer.toString();
38 | this.inputBuffer = this.inputBuffer.substr(0, this.inputBuffer.length - 1);
39 | break;
40 | default:
41 | if (this.inputBuffer.length > 6) return;
42 | if (this.acceptedChars.indexOf(e.key) !== -1)
43 | this.inputBuffer += e.key;
44 | }
45 |
46 | this.tempColor = (this.validation.test(this.inputBuffer))? this.inputBuffer : this.getValue().toString();
47 | }
48 |
49 | render (g, w, h) {
50 | g.setColor(1);
51 | g.box(0, 0, w, h);
52 |
53 | g.setColor(~~this.isTyping)
54 | g.fillBox(1, 1, w-1, h-1);
55 | g.context.fillStyle = this.tempColor;
56 | g.fillBox(1, 1, 48, h-1);
57 | g.setColor(1-~~this.isTyping)
58 | g.text(52, 2, this.inputBuffer.toString());
59 |
60 | }
61 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowFileUpload.js:
--------------------------------------------------------------------------------
1 | class WindowFileUpload extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 |
5 | this.w = 3/4;
6 | this.h = 10;
7 |
8 | this.getValue = con.getValue || (z => "");
9 | this.setValue = con.setValue || (z => {});
10 | this.ext = con.extension || "";
11 |
12 | this.fileName = "";
13 | this.isHovered = false;
14 | }
15 |
16 | eMouseDown (x, y) {
17 | var opener = document.createElement("input");
18 | opener.type = "file";
19 | opener.accept = this.ext;
20 | opener.addEventListener('change', e => {
21 | let file = e.target.files[0];
22 | var reader = new FileReader();
23 | reader.onload = (e) => {
24 | this.fileName = file.name;
25 | this.setValue(e.target.result);
26 | }
27 | reader.readAsBinaryString(file.slice(0, file.size));
28 | });
29 | opener.click();
30 | }
31 |
32 | eUnfocus () {
33 |
34 | }
35 |
36 | eKeyDown (e) {
37 |
38 | }
39 |
40 | eMouseOut (e) {
41 | this.isHovered = false;
42 | }
43 |
44 | eMouseMove (e) {
45 | this.isHovered = true;
46 | }
47 |
48 | render (g, w, h) {
49 | g.setColor(1);
50 | g.text(42, 2, this.fileName);
51 | g.box(0, 0, 36, h);
52 | if (this.isHovered) {
53 | g.context.fillRect(0, 0, 36, h);
54 | g.setColor(0)
55 | g.text(2, 2, "OPEN");
56 |
57 | } else {
58 | g.text(2, 2, "OPEN");
59 | }
60 |
61 | }
62 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowIntegerSlider.js:
--------------------------------------------------------------------------------
1 | class WindowIntegerSlider extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 |
5 | this.w = 3/4;
6 | this.h = 20;
7 |
8 | this.getValue = con.getValue || (z => "");
9 | this.setValue = con.setValue || (z => {});
10 | this.min = con.min || 1;
11 | this.max = con.max || 10;
12 | this.step = con.step || 1;
13 |
14 | this.displayedValue = Math.max(this.min, Math.min(this.max, parseInt(this.getValue())));
15 | this.absW = 10;
16 | this.isDragging = false;
17 | }
18 |
19 | eMouseDown (x, y) {
20 | this.isDragging = true;
21 | this.displayedValue = this.min+Math.round((this.max-this.min)*(1/this.step)*x/this.absW)/(1/this.step);
22 | this.setValue(parseInt(this.displayedValue));
23 | }
24 |
25 | eMouseUp (x, y) {
26 | this.isDragging = false;
27 | this.setValue(parseInt(this.displayedValue));
28 | }
29 |
30 | eMouseMove (x, y) {
31 | if (this.isDragging)
32 | this.displayedValue = this.min+Math.round((this.max-this.min)*(1/this.step)*x/this.absW)/(1/this.step);
33 | }
34 |
35 | render (g, w, h) {
36 | this.absW = w;
37 | g.setColor(1);
38 | g.box(0, 15, w, 1);
39 |
40 | let x = (this.displayedValue-this.min)/(this.max-this.min)*w;
41 | g.box(x, 10, 4, 10);
42 |
43 | g.text(x-1, 0, this.displayedValue);
44 | }
45 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowListOptions.js:
--------------------------------------------------------------------------------
1 | class WindowListOptions extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 |
5 | this.w = 3/4;
6 | this.h = 20;
7 |
8 | this.getValue = con.getValue || (z => "");
9 | this.setValue = con.setValue || (z => {});
10 | this.values = con.values || [];
11 |
12 | this.currentValue = this.values.indexOf(this.getValue());
13 | this.absW = 10;
14 | }
15 |
16 | eMouseDown (x, y) {
17 | this.setValue(this.values[Math.floor(this.values.length*x/this.absW)]);
18 | this.currentValue = this.values.indexOf(this.getValue());
19 | }
20 |
21 | render (g, w, h) {
22 | this.absW = w;
23 | g.setColor(1);
24 | g.box(0, 0, w, h);
25 |
26 | let width = w/this.values.length;
27 | for (let i = 0; i < this.values.length; i++) {
28 | let textW = Graphics.textSize(this.values[i].toString())[0];
29 | g.text((i*width)+(width-textW)/2, 6, this.values[i]);
30 | g.box(i*width, 0, width, h);
31 | }
32 | g.fillBox(this.currentValue*width, 0, width, h);
33 |
34 | g.setColor(0);
35 | let textW = Graphics.textSize(this.values[this.currentValue].toString())[0];
36 | g.text((this.currentValue*width)+(width-textW)/2, 6, this.values[this.currentValue]);
37 |
38 | }
39 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowNumber.js:
--------------------------------------------------------------------------------
1 | class WindowNumber extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 |
5 | this.w = 3/4;
6 | this.h = 10;
7 |
8 | this.getValue = con.getValue || (z => "");
9 | this.setValue = con.setValue || (z => {});
10 |
11 | this.isTyping = false;
12 | this.inputBuffer = this.getValue();
13 | this.acceptedChars = "0123456789.-";
14 | }
15 |
16 | eMouseDown (x, y) {
17 | this.isTyping = true;
18 | this.inputBuffer = "";
19 | }
20 |
21 | eUnfocus () {
22 | if (this.isTyping) {
23 | this.isTyping = false;
24 | this.setValue( this.inputBuffer = parseFloat(this.inputBuffer) || 0 );
25 | this.inputBuffer = this.inputBuffer.toString();
26 | }
27 | }
28 |
29 | eKeyDown (e) {
30 | if (this.isTyping)
31 | switch(e.key) {
32 | case 'Enter':
33 | this.eUnfocus();
34 | break;
35 | case 'Backspace':
36 | this.inputBuffer = this.inputBuffer.toString();
37 | this.inputBuffer = this.inputBuffer.substr(0, this.inputBuffer.length - 1);
38 | break;
39 | default:
40 | if (this.acceptedChars.indexOf(e.key) !== -1)
41 | this.inputBuffer += e.key;
42 | }
43 | }
44 |
45 | render (g, w, h) {
46 | g.setColor(1);
47 | g.box(0, 0, w, h);
48 |
49 | let charle = ~~(w/8);
50 |
51 | g.setColor(~~this.isTyping)
52 | g.fillBox(1, 1, w-1, h-1);
53 | g.setColor(1-~~this.isTyping)
54 | g.text(2, 2, (this.inputBuffer.length > charle)? "..." + this.inputBuffer.substr(-charle+3) : this.inputBuffer.toString().substr(0,charle));
55 |
56 | }
57 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowPalette.js:
--------------------------------------------------------------------------------
1 | class WindowPalette extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 |
5 | this.w = 1;
6 | this.h = 10;
7 |
8 | this.palette = con.palette instanceof Array? con.palette : ["#000"];
9 | }
10 |
11 | render (g, w, h) {
12 | let w_ = w / this.palette.length;
13 | for (let i = 0; i < this.palette.length; i++) {
14 | g.context.fillStyle = this.palette[i];
15 | g.context.fillRect(1+w_*i, 0, w_, h);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowText.js:
--------------------------------------------------------------------------------
1 | class WindowText extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 | this.content = con.content || "";
5 | this.fontSize = con.fontSize || 1;
6 | this.fontColor = con.fontColor|| 1;
7 |
8 | let dimensions = Graphics.textSize(this.content, this.fontSize);
9 | this.w = con.align=="left"? 0.9 : dimensions[0];
10 | this.h = dimensions[1];
11 |
12 | this.border = false;
13 | }
14 |
15 | render (g, w, h, ww) {
16 | g.setColor(this.fontColor);
17 | g.text(0, 0, this.content, this.fontSize);
18 | if (this.border)
19 | g.box(-2, -2, this.w + 2, this.h + 3);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/InterfaceElements/WindowComponents/WindowTextField.js:
--------------------------------------------------------------------------------
1 | class WindowTextField extends InterfaceWindowComponent {
2 | constructor (con = {}) {
3 | super(con);
4 |
5 | this.w = 3/4;
6 | this.h = Graphics.textSize("A")[1]+4;
7 |
8 | this.getValue = con.getValue || (z => {});
9 | this.setValue = con.setValue || (z => {});
10 |
11 | this.isTyping = false;
12 | this.inputBuffer = this.getValue() || "";
13 | }
14 |
15 | eMouseDown (x, y) {
16 | this.isTyping = true;
17 | }
18 |
19 | eUnfocus () {
20 | if (this.isTyping) {
21 | this.isTyping = false;
22 | this.setValue( this.inputBuffer );
23 | }
24 | }
25 |
26 | eKeyDown (e) {
27 | if (this.isTyping)
28 | switch(e.key) {
29 | case 'Enter':
30 | if (!e.shiftKey)
31 | this.eUnfocus();
32 | else
33 | this.inputBuffer += "\n";
34 | break;
35 | case 'Backspace':
36 | this.inputBuffer = this.inputBuffer.substr(0, this.inputBuffer.length - 1);
37 | break;
38 | case 'Shift':
39 | case 'Control':
40 | case 'Alt':
41 | case 'Meta':
42 | break;
43 | default:
44 | this.inputBuffer += e.key;
45 | }
46 | }
47 |
48 | render (g, w, h) {
49 | g.setColor(1);
50 | g.box(0, 0, w, h);
51 |
52 | let chunks = this.inputBuffer.split("\n");
53 |
54 | g.setColor(~~this.isTyping)
55 | g.fillBox(1, 1, w-1, h-1);
56 | g.setColor(1-~~this.isTyping)
57 | g.text(2, 2, chunks[chunks.length - 1].substr(0, ~~(w/8)));
58 |
59 | }
60 | }
--------------------------------------------------------------------------------
/src/JAMS.Interactions.js:
--------------------------------------------------------------------------------
1 | JAMS.Interactions = {};
2 |
3 | /**
4 | Collection of Interactions Windows/Menus/etc.
5 | **/
6 |
7 | // calls from src/InterfaceMenuBar.js
8 | JAMS.Interactions.menuBarMenus = [
9 | {
10 | name: "File",
11 | callback: (interface, app, x) => {
12 | interface.add(
13 | new InterfaceContextMenu({ x: x, y: 23, padding: 8,
14 | options: [{
15 | text: "New Setup",
16 | callback: () => {
17 | let win = new InterfaceWindow({ title: "Confirm", x: 100, y: 100, w: 200, h: 70, isResizable: false });
18 |
19 | win
20 | .appendChild(new WindowText({ content: "Confirm clearing setup?", wh: 25, ww: 1 }))
21 | .appendChild(new WindowButton({ content: "OK!" , callback: () => { app.newSetup(); win.close(); }, wh: 15 }))
22 |
23 | interface.add(win)
24 | }
25 | },{
26 | text: "Open Setup",
27 | callback: app.openSetup.bind(app)
28 | },{
29 | text: "Save Setup",
30 | callback: app.exportSetup.bind(app)
31 | },{
32 | text: "Preferences",
33 | callback: () => {
34 | let win = new InterfaceWindow({ title: "Preferences", x: 100, y: 100, w: 400, h: 400, isResizable: false });
35 |
36 | win
37 | .appendChild(new WindowText({ content: "◆ Persistent Preferences", fontSize: 1, wh: 40, ww: 1, align: "left" }));
38 |
39 | Object.keys(app.preferences.getPreference()).forEach( par => {
40 | entry = app.preferences.getPreference()[par];
41 |
42 | win.appendChild(new WindowText({
43 | ww: 1, wh: 30, align: "left",
44 | content: "-"+entry.name
45 | }));
46 |
47 | switch(entry.type) {
48 | case "listOptions":
49 | win.appendChild(new WindowListOptions({ ww: 1, wh: 30, values: entry.values,
50 | getValue: () => app.preferences.getItem(par),
51 | setValue: x => app.preferences.setItem(par, x)
52 | }));
53 | break;
54 | case "integerSlider":
55 | win.appendChild(new WindowIntegerSlider({ ww: 1, wh: 30, min: entry.min, max:entry.max, step: entry.step,
56 | getValue: () => app.preferences.getItem(par),
57 | setValue: x => app.preferences.setItem(par, x)
58 | }));
59 | break;
60 | case "color":
61 | win.appendChild(new WindowColor({ ww: 1, wh: 30,
62 | getValue: () => app.preferences.getItem(par),
63 | setValue: x => app.preferences.setItem(par, x)
64 | }));
65 | break;
66 | default:
67 | win.appendChild(new WindowText({ ww: 1, wh: 30,
68 | content: ""
69 | }));
70 | }
71 | });
72 |
73 | win
74 | .appendChild(new WindowText({ content: "◆ Reset", fontSize: 1, wh: 40, ww: 1, align: "left" }))
75 | .appendChild(new WindowButton({ content: "Reset Preferences", wh: 20, ww: 0.5, callback: () => app.preferences.reset() }))
76 | .appendChild(new WindowButton({ content: "Refresh Page", wh: 20, ww: 0.5, callback: () => location.reload() }))
77 |
78 | interface.add(win);
79 | }
80 | }]
81 | })
82 | )
83 | }
84 | },
85 | {
86 | name: "View",
87 | callback: (interface, app, x) => {
88 | let fakeE = {clientX: innerWidth/2, clientY: innerHeight/2};
89 | interface.add(
90 | new InterfaceContextMenu({ x: x, y: 23, padding: 8,
91 | options: [{
92 | text: "Zoom In (Ctrl + Scrollup)",
93 | callback: () => app.desktop.scaleUp(fakeE)
94 | },{
95 | text: "Zoom Out (Ctrl + Scrolldown)",
96 | callback: () => app.desktop.scaleDown(fakeE)
97 | }]
98 | })
99 | )
100 | }
101 | },
102 | {
103 | name: "Help",
104 | callback: (interface, app, x) => {
105 | interface.add(
106 | new InterfaceContextMenu({ x: x, y: 23, padding: 8,
107 | options: [{
108 | text: "Examples",
109 | callback: () => window.open("./examples")
110 | },{
111 | text: "Bug Track",
112 | callback: () => window.open("https://github.com/khoin/JAMS/issues")
113 | },{
114 | text: "About",
115 | callback: () => {
116 | let win = new InterfaceWindow({ title: "About", x: 100, y: 100, w: 400, h: 170, isResizable: false});
117 |
118 | win
119 | .appendChild(new WindowText({ content: "JAMS", fontSize: 3, wh: 50, ww: 1}))
120 | .appendChild(new WindowText({ content: "JAMS - A Modular System", fontSize: 2, wh: 20, ww: 1}))
121 | .appendChild(new WindowText({ content: "Fork me at github.com/khoin/JAMS ", fontSize: 1, wh: 40, ww: 1}))
122 | .appendChild(new WindowButton({ content: "Repository", wh: 20, ww: 0.5, callback: () => {window.open("https://github.com/khoin/JAMS")} }))
123 | .appendChild(new WindowButton({ content: "Acknowledgements", wh: 20, ww: 0.5, callback: () => {window.open("./#acknowledgements")} }))
124 | .appendChild(new WindowPalette({ palette: app.g.palette, wh: 20, ww: 1}));
125 |
126 | interface.add(win);
127 | }
128 | }]
129 | })
130 | )
131 | }
132 | }
133 | ]
134 |
135 | // calls from JAMS.js
136 | JAMS.Interactions.rightClickOnDesktop = (_this, e, x, y, currentModule) => {
137 | let pluginTree = _this.plugins.map( category => {
138 | return {
139 | text: category.name,
140 | children: category.children.map( child => {
141 | return {
142 | text: child,
143 | callback: () => _this.createModule(_this.desktop.mouseMapX(x), _this.desktop.mouseMapY(y), Modules[child])
144 | }})
145 | }
146 | });
147 |
148 | _this.interface.add(
149 | new InterfaceContextMenu({ x: x, y: y,
150 | options: [{
151 | text: "Add",
152 | children: pluginTree
153 | }]
154 | })
155 | )
156 | }
157 |
158 | JAMS.Interactions.rightClickOnModule = (_this, e, x, y, currentModule) => {
159 | _this.interface.add(
160 | new InterfaceContextMenu({ x: x, y: y,
161 | options: [{
162 | text: "Delete",
163 | callback: () => _this.deleteModule(currentModule)
164 | },{
165 | text: "Parameters",
166 | callback: () => {
167 | JAMS.Interactions.displayParametersWindow(_this, e, x, y, currentModule);
168 | }
169 | }]
170 | })
171 | )
172 | }
173 |
174 | JAMS.Interactions.displayParametersWindow = (_this, e, x, y, currentModule) => {
175 | let win = new InterfaceWindow({ title: `${currentModule.name} Parameters`, x: x, y: y, w: 400, h: 200, isResizable: true });
176 |
177 | win
178 | .appendChild(new WindowText({
179 | ww: 1/2, wh: 30, align: "left",
180 | content: "Module Name"
181 | }))
182 | .appendChild(new WindowTextField({
183 | ww: 1/2, wh: 30,
184 | getValue: () => currentModule.name,
185 | setValue: x => currentModule.name = x
186 | }))
187 |
188 | currentModule.params.forEach( par => {
189 | win.appendChild(new WindowText({
190 | ww: 1/2, wh: 30, align: "left",
191 | content: par.name
192 | }));
193 |
194 | switch(par.type) {
195 | case "text":
196 | win.appendChild(new WindowTextField({ ww: 1/2, wh: 30,
197 | getValue: () => par.value,
198 | setValue: x => par.value = x
199 | }))
200 | break;
201 | case "number":
202 | win.appendChild(new WindowNumber({ ww: 1/2, wh: 30,
203 | getValue: () => par.value,
204 | setValue: x => par.value = x
205 | }))
206 | break;
207 | case "boolean":
208 | win.appendChild(new WindowCheckBox({ ww: 1/2, wh: 30,
209 | getValue: () => par.value,
210 | setValue: x => par.value = x
211 | }))
212 | break;
213 | case "wavefile":
214 | win.appendChild(new WindowFileUpload({ ww: 1/2, wh: 30,
215 | extension: ".wav",
216 | getValue: () => x,
217 | setValue: x => {
218 | try {
219 | par.value = WaveReader(x);
220 | if (par.onload) par.onload();
221 | } catch(e) {
222 | console.error(e);
223 | _this.alert(e.message, "Error!");
224 | }
225 | }
226 | }))
227 | break;
228 | default:
229 | win.appendChild(new WindowText({ ww: 1, wh: 30,
230 | content: ""
231 | }));
232 | }
233 | })
234 |
235 | _this.interface.add(win);
236 | }
237 |
--------------------------------------------------------------------------------
/src/JAMS.js:
--------------------------------------------------------------------------------
1 | class JAMS {
2 | constructor (config) {
3 | // stuff
4 | this.moduleCounter = 0;
5 | this.modules = [];
6 | this.midiModules = [];
7 |
8 | this.preferences = new ApplicationPreference();
9 | this.g = new Graphics({width: config.width, height: config.height, font: font}); //font exists because
10 | this.interface = new Interface(this.g);
11 | this.desktop = new Desktop(this.g, this.modules);
12 | this.mouseListeners = new ListenerList(this.g.DOMElement, ["mousedown", "mouseup", "mousemove"]);
13 | this.keyListeners = new ListenerList(this.g.DOMElement, ["keydown"]);
14 |
15 |
16 | // Audio
17 | this.aC = (config.audioContext instanceof AudioContext)? config.audioContext : new AudioContext();
18 | window.sampleRate = this.aC.sampleRate;
19 | this.processor = this.aC.createScriptProcessor(this.preferences.getItem("bufferSize"), 0, 2);
20 | let splitter = this.aC.createChannelSplitter();
21 | let merger = this.aC.createChannelMerger();
22 |
23 | this.processor.connect(splitter);
24 | splitter.connect(merger, 0 , 0);
25 | splitter.connect(merger, 1 , 1);
26 | merger.connect(this.aC.destination);
27 |
28 | this.t = 0;
29 | this.frameCount = 0;
30 |
31 | this.outputModule = this.createModule(~~(innerWidth/2), ~~(innerHeight/2), Modules.Output);
32 | this.interface.add(new InterfaceMenuBar({menus : JAMS.Interactions.menuBarMenus, app: this}));
33 |
34 | //plugins
35 | this.plugins = [{
36 | name : "Oscillators",
37 | children: ["SineOSC"]
38 | },{
39 | name : "Math",
40 | children: ["Number", "Plus", "Multiplier", "Remainder", "Rand"]
41 | },{
42 | name : "Clock",
43 | children: ["Clock", "ClockDivider"]
44 | },{
45 | name: "MIDI/Signals",
46 | children: ["MidiKeyboard"]
47 | },{
48 | name : "Harmony",
49 | children: ["Note2Freq"]
50 | },{
51 | name: "Sequencers",
52 | children: ["Sequencer16", "Switch"]
53 | },{
54 | name: "Envelopes",
55 | children: ["LinearAD"]
56 | },{
57 | name: "Sampling",
58 | children: ["Sampler"]
59 | },{
60 | name: "Effects",
61 | children: ["Flange", "Delay"]
62 | },{
63 | name: "Misc",
64 | children: ["QuadMixer", "Scope", "XYScope", "Readout", "TextDisplay", "MonoMerge"]
65 | }];
66 | }
67 |
68 | appendTo (element) {
69 | this.g.appendTo(element);
70 | return this;
71 | }
72 |
73 | init () {
74 | let _ = this;
75 |
76 | this.processor.onaudioprocess = function(e) {
77 | let obuffer = e.outputBuffer;
78 | let incr = obuffer.length/_.aC.sampleRate;
79 |
80 | let ldata = obuffer.getChannelData(0);
81 | let rdata = obuffer.getChannelData(1);
82 |
83 | for (let i = 0; i < obuffer.length; i++) {
84 | let o = _.audioLoop(_.t + (i/_.aC.sampleRate));
85 | ldata[i] = o[0];
86 | rdata[i] = o[1];
87 | }
88 |
89 | _.t += incr;
90 | }
91 |
92 | // MIDI Error Window
93 | let mid = new MIDI(error => {
94 | this.alert("Your browser does not support MIDI");
95 | });
96 |
97 | mid.on('any', (a,e) => {
98 | this.midiModules.forEach( mod => {
99 | mod.setParam(mod.midiParam, e.data);
100 | })
101 | })
102 |
103 | // make DOM focus-able
104 | this.g.DOMElement.tabIndex = 1; // oh gosh (what does this do?)
105 | // okay i spent 30 minutes trying to figure out why keydown doesn't work. so this line is the thing.
106 |
107 | // zooming
108 | this.g.DOMElement.addEventListener("wheel", e => { e.preventDefault();
109 | if(e.ctrlKey) {
110 | if(e.deltaY < 0)
111 | this.desktop.scaleUp(e)
112 | else
113 | this.desktop.scaleDown(e);
114 | }
115 | })
116 |
117 | this.mouseListeners.add("mousedown" , this.interface.eMouseDown , this.interface);
118 | this.mouseListeners.add("mousemove" , this.interface.eMouseMove , this.interface);
119 | this.mouseListeners.add("mouseup" , this.interface.eMouseUp , this.interface);
120 | this.mouseListeners.add("mousedown" , this.desktop.eMouseDown , this.desktop);
121 | this.mouseListeners.add("mousemove" , this.desktop.eDrag , this.desktop, false);
122 | this.mouseListeners.add("mousemove" , this.desktop.eMouseMove , this.desktop);
123 | this.mouseListeners.add("mousemove" , this.desktop.eModuleDrag , this.desktop, false);
124 | this.mouseListeners.add("mousemove" , this.desktop.eModuleShiftDrag , this.desktop, false);
125 | this.mouseListeners.add("mouseup" , this.desktop.eMouseUp , this.desktop);
126 |
127 | this.keyListeners.add("keydown" , this.interface.eKeyDown , this.interface);
128 | this.keyListeners.add("keydown" , this.desktop.eKeyDown , this.desktop);
129 |
130 | // rightclick
131 | this.g.DOMElement.addEventListener("contextmenu", this.eContextMenu.bind(this));
132 |
133 | // fire rendering loop
134 | this.render();
135 | }
136 |
137 | eContextMenu(e) {
138 | e.preventDefault();
139 | const x = e.clientX;
140 | const y = e.clientY;
141 |
142 | let currentModule = this.desktop.isOnModule(x, y);
143 |
144 | if (currentModule) {
145 |
146 | let currentInput = this.desktop.isOnInput(currentModule, x, y);
147 |
148 | if (currentInput !== -1) {
149 | this.interface.add(
150 | new InterfaceContextMenu({ x: x, y: y,
151 | options: [{
152 | text: "Disconnect",
153 | callback: () => currentModule.unsetInput(currentInput)
154 | }]
155 | })
156 | )
157 | } else {
158 | // for some reason, passing `this` doesn't work, so we pass it as an argument
159 | JAMS.Interactions.rightClickOnModule.call(null, this, e, x, y, currentModule);
160 | }
161 | } else {
162 | JAMS.Interactions.rightClickOnDesktop.call(null, this, e, x, y);
163 | }
164 | }
165 |
166 | render () {
167 | if (this.frameCount % ~~this.preferences.getItem("fpsFactor") == 0) {
168 | this.g.background(this.preferences.getItem("background"));
169 | this.desktop.render();
170 | this.interface.render();
171 | }
172 | this.frameCount = (this.frameCount + 1) % 9999;
173 | requestAnimationFrame(this.render.bind(this));
174 | }
175 |
176 | audioLoop (t) {
177 | this.modules.forEach( module => {
178 | if(module.prerun == true) module.run(t);
179 | });
180 |
181 | return this.outputModule.run(t, 1, 0);
182 | }
183 |
184 | createModule(x, y, module, id) {
185 | var mod = new module({
186 | id: (id)? id : ++this.moduleCounter,
187 | x: x,
188 | y: y
189 | });
190 |
191 | if(mod.midiRequest == true) this.midiModules.push(mod);
192 | this.modules.push(mod);
193 | return mod;
194 | }
195 |
196 | deleteModule(deletingModule) {
197 | let i = 0, _ = this;
198 |
199 | this.midiModules.some( (module, index) => {
200 | if(module == deletingModule) {
201 | _.midiModules.splice(index, 1);
202 | return true;
203 | }
204 | })
205 |
206 | this.modules.forEach( (module, id) => {
207 | if( module == deletingModule ) i = id;
208 | module.inputs.forEach( (input, index) => {
209 | if( !input ) return;
210 | if( input.module == deletingModule ) module.unsetInput(index);
211 | });
212 | });
213 | this.modules.splice(i, 1);
214 | }
215 |
216 | getModuleFromId (id) {
217 | var mod; this.modules.some( m => (mod = m).id == id );
218 | if(mod.id !== id) return undefined;
219 | return mod;
220 | }
221 |
222 | newSetup() {
223 | this.modules.splice(0,this.modules.length);
224 | this.outputModule = this.createModule(innerWidth/2, innerHeight/2, Modules.Output);
225 | }
226 |
227 | exportSetup () {
228 | let fileName = "";
229 | this.interface
230 | .add(
231 | (new InterfaceWindow({
232 | title: "Save Setup", x: ~~(innerWidth/2), y: ~~(innerHeight/2), w: 200, h: 100,
233 | isResizable: false
234 | }))
235 | .appendChild(
236 | new WindowText({ content: "FILENAME", ww: 1/2, wh: 50 })
237 | )
238 | .appendChild(
239 | new WindowTextField({ ww: 1/2, wh: 50,
240 | setValue: x => { fileName = x },
241 | getValue: () => fileName
242 | })
243 | )
244 | .appendChild(
245 | new WindowButton({ ww: 1 , wh: 20,
246 | content: "SAVE",
247 | callback: () => {
248 | saveAs(new File([this.saveSetupToJSON()], "JAMS-"+fileName+".json", {type: "data:application/json;charset=utf-8"}));
249 | }
250 | })
251 | )
252 | )
253 | }
254 |
255 | openSetup() {
256 | setTimeout(() => { /**remove canvas drag listener**/ },100);
257 | var opener = document.createElement("input");
258 | opener.type = "file";
259 | opener.accept = ".json";
260 | opener.addEventListener('change', (e) => {
261 | if(!e.target.files[0] || e.target.files[0].name.substr(-5).toLowerCase() !== ".json") return;
262 | var reader = new FileReader();
263 | reader.onload = (e) => {
264 | this.loadSetup(e.target.result);
265 | }
266 | reader.readAsText(e.target.files[0]);
267 |
268 | });
269 | opener.click();
270 | }
271 |
272 | openSetupFromUrl (url) {
273 | let win = this.alert("Loading Setup...");
274 | fetch(url).then(response => {
275 | if (response.ok)
276 | return response.text()
277 | this.alert("Failed loading setup from URL");
278 | }).then(data => {
279 | this.loadSetup(data);
280 | win.close();
281 | });
282 | }
283 |
284 | alert (message, title = "Alert", callback) { //this is synonymous to window.alert of the DOM
285 | let win = new InterfaceWindow({
286 | x: ~~(innerWidth/2), y: ~~(innerHeight/2),
287 | w: 370, h: 100,
288 | title: title
289 | });
290 |
291 | win
292 | .appendChild(new WindowText({
293 | ww: 1, wh: 30,
294 | content: message,
295 | fontSize: 1
296 | }))
297 | .appendChild(new WindowButton({
298 | ww: 1, wh: 30,
299 | content: "OK!",
300 | fontSize: 1,
301 | callback: () => {
302 | if (callback !== undefined) callback();
303 | win.close();
304 | }
305 | }));
306 |
307 | this.interface.add(win);
308 | return win;
309 | }
310 |
311 | saveSetupToJSON() {
312 | var setup = [];
313 | this.modules.forEach( module => {
314 | var inputsArr = [];
315 | module.inputs.forEach( (input,ind) => {
316 | inputsArr[ind] = {
317 | id : input.id,
318 | index : input.index
319 | }
320 | });
321 |
322 | let params = module.params.map( p => {
323 | if (!p.paramSave) {
324 | return p.value;
325 | } else {
326 | return p.paramSave(p.value);
327 | }
328 | });
329 |
330 | setup.push({
331 | id: module.id,
332 | type: module.className,
333 | x: module.x,
334 | y: module.y,
335 | inputs: inputsArr,
336 | params: params,
337 | name: module.name
338 | })
339 | });
340 | return JSON.stringify(setup);
341 | }
342 |
343 | loadSetup (json) {
344 | let _ = this;
345 | this.newSetup();
346 | let setup = JSON.parse(json);
347 | try {
348 | setup.forEach( module => {
349 | _.moduleCounter = Math.max(_.moduleCounter, module.id);
350 | try {
351 | var mod = _.createModule(module.x, module.y, Modules[module.type], module.id);
352 | } catch(e) {
353 | console.warn(e);
354 | module.invalid = true;
355 | console.warn("Possible that " + module.type + " is not a valid module");
356 | return;
357 | }
358 | mod.name = module.name || "";
359 | module.params.forEach( (val, ind) => {
360 | if(!mod.params[ind].paramLoad)
361 | mod.setParam(ind, val);
362 | else
363 | mod.setParam(ind, mod.params[ind].paramLoad(val));
364 | });
365 |
366 | if(module.type == "Output") {
367 | _.deleteModule(_.outputModule);
368 | _.outputModule = mod;
369 | }
370 | })
371 | setup.forEach( module => {
372 | let targetModule = _.getModuleFromId(module.id);
373 | if(targetModule == undefined) return;
374 | module.inputs.forEach( (input,index) => {
375 | if( input == null ) return;
376 | let sourceModule = _.getModuleFromId(input.id);
377 | if( sourceModule == undefined ) return;
378 | sourceModule.connect(targetModule, index, input.index);
379 | })
380 | });
381 | } catch(e) {
382 | console.error("Corrupted Setup File: ", e); return;
383 | }
384 | }
385 |
386 | }
--------------------------------------------------------------------------------
/src/ListenerList.js:
--------------------------------------------------------------------------------
1 | class ListenerList {
2 | constructor (object, events) {
3 | if(!(object instanceof Element)) throw new TypeError("Object is not a DOM Element");
4 | this.executors = [];
5 | // Should consider Sorted Map for these reasons:
6 | // * No name conflicts
7 | // * Prioritizing listeners
8 | // Sticking with this for now.
9 |
10 | for (let event of events)
11 | object.addEventListener(event, this.run.bind(this));
12 |
13 | return this;
14 | }
15 |
16 | add (type, func, binder, status = true) {
17 | if(!(func instanceof Function)) throw new TypeError("listener is not a function");
18 |
19 | this.executors.push({
20 | s : status,
21 | t : type,
22 | f : func,
23 | _ : binder
24 | });
25 | }
26 |
27 | turnOn (name) {
28 | for (let exe of this.executors)
29 | if ( exe.f.name == name ) {
30 | exe.s = true;
31 | break;
32 | }
33 | }
34 |
35 | turnOff (name) {
36 | for (let exe of this.executors)
37 | if ( exe.f.name == name ) {
38 | exe.s = false;
39 | break;
40 | }
41 | }
42 |
43 | run (e) {
44 | for (let i = 0, a = true; a !== false && i < this.executors.length; i++) {
45 | const exe = this.executors[i];
46 | if(exe.s && e.type == exe.t)
47 | a = exe.f.call(exe._, e, this);
48 | }
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/src/MIDI.js:
--------------------------------------------------------------------------------
1 | //@author github.com/mog
2 | var MIDI = function(error) {
3 |
4 | "use strict";
5 |
6 | var _inputs = [],
7 | _outputs = [],
8 |
9 | _eventList = {};
10 |
11 | function handleInput(event){
12 |
13 | var data = event.data,
14 | id = data[1],
15 | val = data[2];
16 |
17 | if (data[0] == 254 || data[0] == 248)
18 | return;
19 |
20 | if(_eventList[ id ])
21 | for(var i = 0, len = _eventList[ id ].length; i < len; i++ )
22 | _eventList[ id ][i]( val, event );
23 |
24 | if(_eventList[ 'any' ])
25 | for(i = 0, len = _eventList[ 'any' ].length; i < len; i++ )
26 | _eventList[ 'any' ][i]( val, event );
27 | }
28 |
29 | (function init(){
30 | try {
31 | navigator.requestMIDIAccess().then(function(midi) {
32 |
33 | for (var i = midi.inputs.values(), o = i.next(); !o.done; o = i.next()) {
34 | _inputs.push(o.value);
35 | }
36 |
37 | for (i = midi.outputs.values(), o = i.next(); !o.done; o = i.next()) {
38 | _outputs.push(o.value);
39 | }
40 |
41 | _inputs.forEach(function(input) {
42 | input.onmidimessage = handleInput;
43 | });
44 | });
45 | } catch(e) {
46 | if(error) error(e);
47 | }
48 | })();
49 |
50 | function bind(key, fn){
51 |
52 | if(!_eventList[key])
53 | _eventList[key] = [];
54 |
55 | _eventList[key].push(fn);
56 | }
57 |
58 | function unbind(key, fn){
59 |
60 | if(_eventList[ key ]){
61 | for(var i = 0, len = _eventList[ id ].length; i < len; i++ ){
62 | if(fn === _eventList[ id ][i])
63 | _eventList[ id ].splice(i, 1);
64 | break;
65 | }
66 |
67 | if(_eventList[ id ].length === 0)
68 | delete _eventList[ id ];
69 | }
70 | }
71 |
72 | return {
73 | on:bind,
74 | off:unbind,
75 | inputs: _inputs
76 | };
77 | };
--------------------------------------------------------------------------------
/src/Utils/FileSaver.min.js:
--------------------------------------------------------------------------------
1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
2 | var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})}
3 |
--------------------------------------------------------------------------------
/src/Utils/WaveReader.js:
--------------------------------------------------------------------------------
1 | // x binary from FileReader
2 | // returns [leftChannel as Float32Array, rightChannel as F32A, sampleRate]
3 | // only support 16-bit pcm
4 |
5 | function WaveReader(x) {
6 | if (x.substr(8,8) !== "WAVEfmt ")
7 | throw new Error("Not a WAV File")
8 | if (x.charCodeAt(20) !== 0x01)
9 | throw new Error("Unsupported WAV format");
10 | if (x.charCodeAt(34) !== 0x10)
11 | throw new Error("Unsupported Bit-Depth. 16-bit please.")
12 |
13 | const numChan = x.charCodeAt(22);
14 | const sampleR8 = x.charCodeAt(24) + (x.charCodeAt(25) << 8);
15 | const bitRate = x.charCodeAt(34) + (x.charCodeAt(35) << 8);
16 | const startPoint = x.indexOf("data") + 8;
17 | const chunkLength = x.substr(startPoint-4, 4).split("").reduce( (a,n,i) => a + (n.charCodeAt(0) << (i*8)), 0);
18 | const chanLength = Math.min(180 * sampleRate, chunkLength/(bitRate/8)/numChan);
19 |
20 | const leftChan = new Float32Array(chanLength);
21 | const rightChan = new Float32Array(chanLength);
22 |
23 | /**
24 | console.log(`
25 | numChan: ${numChan}
26 | sampleRate: ${sampleR8}
27 | bitRate: ${bitRate}
28 | duration: ${chanLength/sampleRate} seconds
29 | `);
30 | **/
31 | const range = 0x1 << (bitRate - 1);
32 |
33 | for (let i = 0; i < chanLength; i++) {
34 | let sampIncr = bitRate/8;
35 | let blocOffs = sampIncr * numChan * i;
36 | leftChan[i] = x .substr(startPoint + blocOffs, sampIncr)
37 | .split("")
38 | .reduce((a,n,j) => a + (n.charCodeAt(0) << (j*8)), 0);
39 | leftChan[i] = leftChan[i] & 0x8000? leftChan[i]/range - 2 : leftChan[i] / range;
40 |
41 | if (numChan == 2) {
42 | rightChan[i] = (x.substr(startPoint + blocOffs + sampIncr, sampIncr)
43 | .split("")
44 | .reduce((a,n,j) => a + (n.charCodeAt(0) << (j*8)), 0) ) ;
45 | rightChan[i] = rightChan[i] & 0x8000? rightChan[i]/range - 2 : rightChan[i] / range;
46 | } else {
47 | rightChan[i] = leftChan[i];
48 | }
49 |
50 | }
51 | return [leftChan, rightChan, sampleR8];
52 | }
53 |
54 | // 15-bit 16384
55 | function WaveParamSave(data) {
56 | let left = "";
57 | let right = "";
58 |
59 | for (let i = 0; i < data[0].length; i++) {
60 | left += String.fromCharCode( ~~((data[0][i] + 1) * 16384) );
61 | right += String.fromCharCode( ~~((data[1][i] + 1) * 16384) );
62 | }
63 |
64 | return [left, right, data[2]];
65 | }
66 |
67 | function WaveParamLoad(data) {
68 | let left = new Float32Array(data[0].length);
69 | let right = new Float32Array(left.length);
70 |
71 | for (let i = 0; i < data[0].length; i++) {
72 | left[i] = (data[0].charCodeAt(i)-16384)/16384;
73 | right[i]= (data[1].charCodeAt(i)-16384)/16384;
74 | }
75 |
76 | return [left, right, data[2]];
77 | }
78 |
--------------------------------------------------------------------------------
/src/jams.plugins.js:
--------------------------------------------------------------------------------
1 | // Super Class ---
2 | // t,z,a : time, recursive? (1: no, 0: sure), outputIndex
3 |
4 | JAMS.Module = function(config) {
5 | if( !(this instanceof JAMS.Module)) return new JAMS.Module(config);
6 |
7 | // ID, UI stuff
8 | this.id = config.id;
9 | this.x = config.x;
10 | this.y = config.y;
11 | this.numberOfInputs = 0;
12 | this.numberOfOutputs = 0;
13 | this.color = 0;
14 | this.width = 50
15 | this.height = 20;
16 | this.name = "defaultName";
17 |
18 | this.prerun = false;
19 | this.helpText = "No HelpText Available";
20 | this.inputs = [];
21 | this.params = [];
22 |
23 | }
24 |
25 | JAMS.Module.prototype.interface = function() {}
26 | JAMS.Module.prototype.handleDrag = function() {}
27 | JAMS.Module.prototype.run = function() {}
28 | JAMS.Module.prototype.setParam = function(index, value) {
29 | this.params[index].value = value;
30 | return this;
31 | }
32 | JAMS.Module.prototype.setInput = function(node, index, originIndex, id) {
33 | this.inputs[index] = {};
34 | this.inputs[index].id = id;
35 | this.inputs[index].module = node;
36 | this.inputs[index].index = originIndex;
37 | return this;
38 | }
39 | JAMS.Module.prototype.unsetInput = function(index) {
40 | delete this.inputs[index];
41 | return this;
42 | }
43 | JAMS.Module.prototype.connect = function(node, index, originIndex) {
44 | node.setInput(this, index, originIndex, this.id);
45 | return this;
46 | }
47 |
48 | //--------------
49 | // one input.
50 | var JAMSOutput = function(config) {
51 | JAMS.Module.call(this, config);
52 |
53 | this.className = "JAMSOutput";
54 |
55 | this.numberOfInputs = 1;
56 | this.numberOfOutputs= 0;
57 | this.color = 25;
58 | this.width = 40;
59 | this.height = 20;
60 | this.name = "OUTPUT";
61 |
62 | }
63 | JAMSOutput.prototype = Object.create(JAMS.Module.prototype);
64 |
65 | JAMSOutput.prototype.interface = function(ui, uiVars) {
66 | ui.text( 8, 7, this.name);
67 | }
68 | JAMSOutput.prototype.run = function(t, z) {
69 | return (this.inputs[0])? this.inputs[0].module.run(t, z, this.inputs.index) : 0 ;
70 | }
71 |
72 | //----------
73 | // input0: frequency
74 | // input1: phase
75 |
76 | var JAMSSineGenerator = function(config) {
77 | JAMS.Module.call(this, config);
78 |
79 | this.className = "JAMSSineGenerator";
80 |
81 | this.prerun = true;
82 | this.numberOfInputs = 2;
83 | this.numberOfOutputs= 1;
84 | this.color = 100;
85 | this.width = 100;
86 | this.height = 40;
87 | this.name = "SINE OSC";
88 | this.helpText =
89 | `---- SineGenerator ----
90 | * 1st Input : Frequency
91 | * 2nd Input : Phase
92 |
93 | ---- *** Tips *** ----
94 | Input another signal into the 2nd input
95 | to create Phase Modulation.
96 | `;
97 |
98 | // need dis
99 | this.lastSample = 0;
100 | }
101 |
102 | JAMSSineGenerator.prototype = Object.create(JAMS.Module.prototype);
103 | JAMSSineGenerator.prototype.interface = function(ui, uiVars) {
104 | ui.text( 2, 2, this.name);
105 | }
106 | JAMSSineGenerator.prototype.run = function(t, z, outputIndex) {
107 | if( z == 1 ) return this.lastSample;
108 | var pm = (this.inputs[1])? this.inputs[1].module.run(t, 1, this.inputs[1].index) : 0;
109 | var f = (this.inputs[0])? this.inputs[0].module.run(t, 1, this.inputs[0].index) : 440;
110 | return this.lastSample = Math.sin( Math.PI*2*f*t + pm );
111 | };
112 |
113 | //---------
114 | // Product of the two inputs is the output.
115 | var JAMSMultiplier = function(config) {
116 | JAMS.Module.call(this, config);
117 |
118 | this.className = "JAMSMultiplier";
119 |
120 | this.numberOfInputs = 2;
121 | this.numberOfOutputs= 1;
122 | this.color = 80;
123 | this.width = 40;
124 | this.height = 40;
125 | this.name = "multiplier";
126 | this.helpText =
127 | `---- Multiplier ----
128 | Multiplies two inputs together.
129 | Default signal for either input is zero.
130 |
131 | ---- *** Tips *** ----
132 | To change a volume of a signal, multiple
133 | that signal with a number.
134 | If that number = 1, full volume.
135 | If that number = 0, it is muted.
136 |
137 | Without the existence of a Pow() module
138 | You can multiple the same signal to itself
139 | to generate a quadratic curve from linear.
140 | `;
141 | }
142 |
143 | JAMSMultiplier.prototype = Object.create(JAMS.Module.prototype);
144 | JAMSMultiplier.prototype.interface = function(ui, uiVars) {
145 | ui.text( 17, 17, "*",2);
146 | }
147 | JAMSMultiplier.prototype.run = function (t, z) {
148 | var input1 = (this.inputs[0])? this.inputs[0].module.run(t, 1, this.inputs[0].index) : 0;
149 | var input2 = (this.inputs[1])? this.inputs[1].module.run(t, 1, this.inputs[1].index) : 0;
150 | return input1 * input2;
151 | }
152 | // ----
153 | // Returns a number.
154 | // Param0 = number itself
155 | // Param1 = Sensitivity of the dragging
156 | var JAMSNumber = function(id) {
157 | JAMS.Module.call(this, id);
158 |
159 | this.className = "JAMSNumber";
160 |
161 | this.numberOfInputs = 0;
162 | this.numberOfOutputs= 1;
163 | this.color = 170;
164 | this.width = 50;
165 | this.height = 20;
166 | this.name = "number";
167 | this.helpText =
168 | `---- Number ----
169 | Returns the number displayed.
170 | Shift + Drag this number to change
171 | is value.
172 | The Sensitivity of the drag can be altered
173 | through the Parameters window.
174 | `;
175 |
176 | this.params[0] = {};
177 | this.params[0].name = "Value";
178 | this.params[0].type = "number";
179 | this.params[0].value = 1;
180 | this.params[1] = {};
181 | this.params[1].name = "Drag Sensitivity";
182 | this.params[1].type = "number";
183 | this.params[1].value = 5;
184 | }
185 | JAMSNumber.prototype = Object.create(JAMS.Module.prototype);
186 | JAMSNumber.prototype.interface = function(ui, uiVars) {
187 | var displayingText = Math.round(this.params[0].value*10000)/10000;
188 | displayingText = displayingText.toString();
189 | ui.text( 6, 7, (displayingText.length > 8)? ".."+displayingText.substr(0,8) : displayingText );
190 | }
191 | JAMSNumber.prototype.run = function (t,z) {
192 | return this.params[0].value;
193 | }
194 | JAMSNumber.prototype.handleDrag = function(e) {
195 | this.params[0].value -= e.movementY/this.params[1].value;
196 | }
197 | // ----
198 | // Time t. Returns t, basically.
199 | var JAMSt = function(id) {
200 | JAMS.Module.call(this, id);
201 |
202 | this.className = "JAMSt";
203 |
204 | this.numberOfInputs = 0;
205 | this.numberOfOutputs= 1;
206 | this.color = 40;
207 | this.width = 30;
208 | this.height = 20;
209 | this.name = "time";
210 | this.helpText =
211 | `---- Time ----
212 | Returns current time in seconds.
213 | `;
214 |
215 | }
216 | JAMSt.prototype = Object.create(JAMS.Module.prototype);
217 | JAMSt.prototype.interface = function(ui, uiVars) {
218 | ui.text( 14, 7, "t" ) ;
219 | }
220 | JAMSt.prototype.run = function (t,z) {
221 | return t;
222 | }
223 | // ---
224 | // Remainder Modulo
225 | var JAMSRemainder = function(id) {
226 | JAMS.Module.call(this, id);
227 |
228 | this.className = "JAMSRemainder";
229 |
230 | this.numberOfInputs = 2;
231 | this.numberOfOutputs= 1;
232 | this.color = 180;
233 | this.width = 40;
234 | this.height = 40;
235 | this.name = "remainder";
236 | this.helpText =
237 | `---- Remainder ----
238 | Returns the remainder after dividing the
239 | first input by the second input.
240 |
241 | E.g: 10 % 4 = 2;
242 | `;
243 |
244 | }
245 | JAMSRemainder.prototype = Object.create(JAMS.Module.prototype);
246 | JAMSRemainder.prototype.interface = function(ui, uiVars) {
247 | ui.text( 17, 15, "%", 2);
248 | }
249 | JAMSRemainder.prototype.run = function (t,z) {
250 | var input1 = (this.inputs[0])? this.inputs[0].module.run(t, 1, this.inputs[0].index) : 0;
251 | var input2 = (this.inputs[1])? this.inputs[1].module.run(t, 1, this.inputs[1].index) : 0;
252 | return input1 % input2;
253 | }
254 | // ----
255 | // Pluser
256 | var JAMSPlus = function(id) {
257 | JAMS.Module.call(this, id);
258 |
259 | this.className = "JAMSPlus";
260 |
261 | this.numberOfInputs = 2;
262 | this.numberOfOutputs= 1;
263 | this.color = 380;
264 | this.width = 40;
265 | this.height = 40;
266 | this.name = "plus";
267 | this.helpText =
268 | `---- Plus ----
269 | Adds the two inputs together.
270 | Acts as a mixer.
271 | Remember, signals exceeding [1;-1]
272 | will be clipped.
273 | `;
274 |
275 | }
276 | JAMSPlus.prototype = Object.create(JAMS.Module.prototype);
277 | JAMSPlus.prototype.interface = function(ui, uiVars) {
278 | ui.text( 17, 15, "+", 2);
279 | }
280 | JAMSPlus.prototype.run = function (t,z) {
281 | var input1 = (this.inputs[0])? this.inputs[0].module.run(t, 1, this.inputs[0].index) : 0;
282 | var input2 = (this.inputs[1])? this.inputs[1].module.run(t, 1, this.inputs[1].index) : 0;
283 | return input1 + input2;
284 | }
285 | // ---
286 | // 4Array. Takes in 5 input. First is index, 4 others are values. Returns the value of the index given and floors it.
287 | var JAMS4Array = function(id) {
288 | JAMS.Module.call(this, id);
289 |
290 | this.className = "JAMS4Array";
291 |
292 | this.numberOfInputs = 5;
293 | this.numberOfOutputs= 1;
294 | this.color = 280;
295 | this.width = 20;
296 | this.height = 100;
297 | this.name = "4array";
298 | this.helpText =
299 | `---- 4Array ----
300 | The first input determines which input from 2-5
301 | to return. The first input is rounded to the
302 | lower integer and modulo if it exceeds 5.
303 | E.g: Input 1 = 7.8;
304 | Output = Input[ 1 + floor(7.8%4) ] =
305 | = Input[ 1 + 3 ] = Input 4
306 |
307 | ---- *** Tips *** ----
308 | Very useful for simple melodies. You can
309 | nest these together to create a song.
310 | `;
311 |
312 | this.curIndex = 1;
313 |
314 | }
315 | JAMS4Array.prototype = Object.create(JAMS.Module.prototype);
316 | JAMS4Array.prototype.interface = function(ui, uiVars) {
317 | ui.context.fillRect( -uiVars.ioWidth , uiVars.ioHeight*this.curIndex, uiVars.ioWidth, uiVars.ioHeight );
318 | }
319 | JAMS4Array.prototype.run = function (t,z) {
320 | if (this.inputs[0] == undefined) return;
321 | var index = this.curIndex = (this.inputs[0])? Math.floor(Math.abs(this.inputs[0].module.run(t, 1,this.inputs[0].index)%4+1)) : 0;
322 | var output = (this.inputs[index])? this.inputs[index].module.run(t, 1, this.inputs[index].index) : 0;
323 | return output;
324 | }
325 | // ----
326 | var JAMSmidiNote = function(id) {
327 | JAMS.Module.call(this, id);
328 |
329 | this.className = "JAMSmidiNote";
330 |
331 | this.numberOfInputs = 0;
332 | this.numberOfOutputs = 4;
333 | this.color = 340;
334 | this.width = 50;
335 | this.height = 80;
336 | this.name = "NoteInput";
337 | this.helpText =
338 | `---- midiNote ----
339 | 1st Output: Sends a pulse when noteOn is received
340 | 2nd Output: Sends a pulse when noteOff is received
341 | 3rd Output: Returns the last note value
342 | 4rd Output: Returns the last velocity value
343 |
344 | ---- *** Tips *** ----
345 | Use the 3rd Output and route it through a
346 | Note2Freq module to convert it to Hertz.
347 | Then, route it to a SineGenerator.
348 | `;
349 |
350 | this.midiRequest = true;
351 | this.midiParamIndex = 0;
352 |
353 | this.params[0] = {};
354 | this.params[0].name = "midi data";
355 | this.params[0].type = "Array";
356 | this.params[0].value = new Uint8Array([144,69,12]);
357 | this.params[1] = {};
358 | this.params[1].name = "channel";
359 | this.params[1].type = "number";
360 | this.params[1].value = 1;
361 |
362 | //on, off, note, vel
363 | this.runningValues = [0,0,0,0];
364 | }
365 | JAMSmidiNote.prototype = Object.create(JAMS.Module.prototype);
366 |
367 | JAMS.Module.prototype.setParam = function(index, value) {
368 | this.params[index].value = value;
369 | if( index !== 0 ) return this;
370 | if(value[0] == (143 + this.params[1].value)) { this.runningValues[0] = 1; this.runningValues[2] = value[1]; this.runningValues[3] = value[2]; }
371 | if(value[0] == (127 + this.params[1].value) && this.runningValues[2] == value[1]) this.runningValues[1] = 1;
372 | return this;
373 | }
374 | JAMSmidiNote.prototype.interface = function(ui, uiVars) {
375 | var firstOffset = uiVars.ioHeight/2 - 2.5;
376 | ["on trigger", "off trigger", "note", "velocity"].forEach( (label, index) => {
377 | ui.text(4, firstOffset + uiVars.ioHeight*index, label);
378 | })
379 | }
380 | JAMSmidiNote.prototype.run = function(t, z, a) {
381 | var outputValues = this.runningValues.slice();
382 | if( (a == 1 || a == 0) && z !== 1 ) this.runningValues[a] = 0;
383 | return outputValues[a];
384 | }
385 | //---
386 | var JAMSNote2Freq = function(id) {
387 | JAMS.Module.call(this, id);
388 |
389 | this.className = "JAMSNote2Freq";
390 |
391 | this.numberOfInputs = 1;
392 | this.numberOfOutputs= 1;
393 | this.color = 130;
394 | this.width = 50;
395 | this.height = 20;
396 | this.name = "note2freq";
397 |
398 | this.params[0] = {};
399 | this.params[0].name = "Tuning";
400 | this.params[0].type = "number";
401 | this.params[0].value = 440;
402 | this.params[1] = {};
403 | this.params[1].name = "transpose";
404 | this.params[1].type = "number";
405 | this.params[1].value = 0;
406 | }
407 | JAMSNote2Freq.prototype = Object.create(JAMS.Module.prototype);
408 | JAMSNote2Freq.prototype.interface = function(ui, uiVars) {
409 | ui.text( 6, 7, "NOTE->FREQ" );
410 | }
411 | JAMSNote2Freq.prototype.run = function (t, z, a) {
412 | if(this.inputs[0] == undefined) return;
413 | return Math.pow(2, ( this.inputs[0].module.run(t, 1, this.inputs[0].index) + this.params[1].value - 69 ) / 12) * this.params[0].value;
414 | }
415 | // ----
416 | // first input on triggger/ second is off trigger. outputs 1 and 0
417 | var JAMSOnOffEnvelope = function(id) {
418 | JAMS.Module.call(this, id);
419 |
420 | this.className = "JAMSOnOffEnvelope";
421 |
422 | this.numberOfInputs = 2;
423 | this.numberOfOutputs= 1;
424 | this.color = 10;
425 | this.width = 20;
426 | this.height = 40;
427 | this.name = "on/off envelope";
428 | this.helpText =
429 | `---- OnOffEnvelope ----
430 | 1st Input: Receives NoteOn trigger
431 | 2nd Input: Receives NoteOff trigger
432 |
433 | When a trigger (a signal with its current value being
434 | 1 higher than its last value) is received,
435 | the envelope either turns on or off.
436 | `;
437 |
438 | this.output = 0;
439 | this.lastOn = 0;
440 | this.lastOff = 0;
441 | }
442 | JAMSOnOffEnvelope.prototype = Object.create(JAMS.Module.prototype);
443 | JAMSOnOffEnvelope.prototype.interface = function(ui, uiVars) {
444 | if(this.output == 1) ui.context.fillRect(0,0,20,40);
445 | }
446 | JAMSOnOffEnvelope.prototype.run = function (t, z, a) {
447 | if(!this.inputs[0] || !this.inputs[1]) return;
448 | var newOn = this.inputs[0].module.run(t, 0, this.inputs[0].index),
449 | newOff = this.inputs[1].module.run(t, 0, this.inputs[1].index);
450 | if ( newOn -this.lastOn >= 1) this.output = 1;
451 | if ( newOff-this.lastOff >= 1) this.output = 0;
452 | this.lastOn = newOn;
453 | this.lastOff = newOff;
454 | return this.output;
455 | }
456 | // ----
457 | var JAMSSimpleDecay = function(id) {
458 | JAMS.Module.call(this, id);
459 |
460 | this.className = "JAMSSimpleDecay";
461 |
462 | this.numberOfInputs = 2;
463 | this.numberOfOutputs = 1;
464 | this.color = 30;
465 | this.width = 20;
466 | this.height = 40;
467 | this.name = "simple decay";
468 | this.helpText =
469 | `---- SimpleDecay ----
470 | 1st Input: Receives NoteOn trigger
471 | 2nd Input: The value being deducted every execution
472 |
473 | It basically listens to only NoteOn. When it catches one,
474 | it fires, keep deducting the value being fed to the second
475 | input until it hits zero.
476 | ---- *** Tips *** ----
477 | Use the OneOverSRate module with this one. That module basically
478 | shows the millisecond and returns the value the envelope needs
479 | to deduct per sample call.
480 | `;
481 |
482 | this.output = 0;
483 | this.attack = false;
484 | this.lastTrig = 0;
485 | }
486 | JAMSSimpleDecay.prototype = Object.create(JAMS.Module.prototype);
487 | JAMSSimpleDecay.prototype.interface = function(ui, uiVars) {
488 | ui.context.fillRect(0,this.height-(this.output*this.height),20,1);
489 | }
490 | JAMSSimpleDecay.prototype.run = function (t, z, a) {
491 | if(!this.inputs[0] ) return;
492 | var newTrig = this.inputs[0].module.run(t, 0, this.inputs[0].index);
493 | if( newTrig - this.lastTrig >= 1) { this.attack = true; }
494 | if(this.output >= 1) { this.attack = false; }
495 | this.lastTrig = newTrig;
496 | if(this.attack) return this.output = Math.min(this.output + 0.09, 1);
497 | this.output -= (this.inputs[1])? ( this.inputs[1].module.run(t, 0, this.inputs[0].index) ) : 0.00001;
498 | this.output = Math.max(0, this.output);
499 | return this.output;
500 | }
501 | // ------
502 | // basically returns 1/window.sampleRate;
503 | var JAMSMilliseconds = function(id) {
504 | JAMS.Module.call(this, id);
505 |
506 | this.className = "JAMSMilliseconds";
507 |
508 | this.numberOfInputs = 0;
509 | this.numberOfOutputs= 1;
510 | this.color = 0;
511 | this.width = 50;
512 | this.height = 20;
513 | this.name = "milliseconds";
514 | this.helpText =
515 | `---- Milliseconds ----
516 | It basically shows
517 | the millisecond, and returns the amount of deduction
518 | to reach from 1 to 0 (zero) within the millisecond
519 | displayed.
520 |
521 | ---- *** Tips *** ----
522 | Use with the SimpleDecay envelope.
523 | E.g: 500 will return 1000/500 * 1/sampleRate
524 | = 2 * 1/48000
525 |
526 | If you were to deduct this amount from 1 every sample,
527 | then you'd reach zero after 500 milliseconds,
528 | given that the app environment is in 48KHz.
529 | `;
530 |
531 | this.params[0] = {};
532 | this.params[0].name = "Value";
533 | this.params[0].type = "number";
534 | this.params[0].value = 1;
535 | this.params[1] = {};
536 | this.params[1].name = "Drag Sensitivity";
537 | this.params[1].type = "number";
538 | this.params[1].value = 5;
539 |
540 | this.con = 1/sampleRate;
541 | }
542 | JAMSMilliseconds.prototype = Object.create(JAMS.Module.prototype);
543 | JAMSMilliseconds.prototype.interface = function(ui, uiVars) {
544 | var displayingText = Math.round(this.params[0].value*10000)/10000;
545 | displayingText = displayingText.toString();
546 | ui.text( 6, 7, (displayingText.length > 8)? ".."+displayingText.substr(0,8) : displayingText );
547 | }
548 | JAMSMilliseconds.prototype.run = function (t,z) {
549 | return 1000/this.params[0].value * this.con;
550 | }
551 | JAMSMilliseconds.prototype.handleDrag = function(e) {
552 | this.params[0].value -= e.movementY/this.params[1].value;
553 | }
554 | // --- debugger
555 | var JAMSDebugger = function(id) {
556 | JAMS.Module.call(this,id);
557 |
558 | this.className = "JAMSDebugger";
559 |
560 | this.prerun = true;
561 |
562 | this.numberOfInputs = 1;
563 | this.numberOfOutputs = 0;
564 | this.color = 90;
565 | this.width = 80;
566 | this.height= 40;
567 | this.name = "debugger";
568 | this.helpText =
569 | `---- Debugger ----
570 | Displays the waveform.
571 | `;
572 |
573 | this.array = new Array(this.width);
574 | this.array.fill(0);
575 | }
576 | JAMSDebugger.prototype = Object.create(JAMS.Module.prototype);
577 | JAMSDebugger.prototype.interface = function(ui, uiVars) {
578 | var _ = this;
579 | for(var i=0; i<_.array.length; ++i) {
580 | ui.point(i, Math.round(Math.min(1,_.array[i])*_.height) );
581 | }
582 | }
583 | JAMSDebugger.prototype.run = function(t,z,a) {
584 | if(!this.inputs[0]) return;
585 | this.array.unshift( this.inputs[0].module.run(t,1, this.inputs[0].index)/2 + 0.5);
586 | this.array.pop();
587 | }
588 |
589 | //---
590 | // One pole filter
591 | // Please see: https://github.com/opendsp/filter/blob/master/index.js
592 | // Authors: stagas, Will Pirkle, et al.
593 | // input, cut, typ
594 |
595 | var JAMSOnePole = function(id) {
596 | JAMS.Module.call(this, id);
597 |
598 | this.className = "JAMSOnePole";
599 |
600 | this.prerun = false;
601 |
602 | this.numberOfInputs = 3;
603 | this.numberOfOutputs = 1;
604 | this.color = 50;
605 | this.width = 80;
606 | this.height= 60;
607 | this.name = "filter";
608 |
609 | this.type = 0;
610 | this.out = [];
611 | this.fc = 200;
612 | this.a = 1;
613 | this.b = 1;
614 | this.z1 = 0;
615 | this.reset();
616 | this.update();
617 | }
618 |
619 | JAMSOnePole.prototype = Object.create(JAMS.Module.prototype);
620 |
621 | JAMSOnePole.prototype.interface = function(ui, uiVars) {
622 | var labels = ["input", "cutoff", "type"];
623 | for (var i = 0; i < labels.length; i++)
624 | ui.text(5, 5 + i * 20, labels[i]);
625 | }
626 |
627 | JAMSOnePole.prototype.prewarp = function(f){
628 | return Math.tan(Math.PI * f / sampleRate);
629 | }
630 |
631 | JAMSOnePole.prototype.reset = function(){
632 | this.z1 = 0;
633 | }
634 |
635 | JAMSOnePole.prototype.update = function(){
636 | var wa = this.prewarp(this.fc);
637 | this.a = wa / (1.0 + wa);
638 | }
639 |
640 | JAMSOnePole.prototype.run = function(t, z, a) {
641 | if(!this.inputs[0]) return;
642 | if(isNaN(this.out[0])) this.reset();
643 | var out = this.out;
644 | this.fc = (!this.inputs[1])? 300 : Math.abs(this.inputs[1].module.run(t, 1, this.inputs[1].index));
645 | this.update();
646 | var type = (!this.inputs[2])? 0 : this.inputs[2].module.run(t, 1, this.inputs[2].index);
647 | var xn = this.inputs[0].module.run(t, 1, this.inputs[0].index);
648 | var vn = (xn - this.z1) * this.a;
649 | out[0] = vn + this.z1;
650 | this.z1 = vn + out[0];
651 | out[1] = xn - out[0];
652 | out[2] = out[0] - out[1];
653 | return out[~~type];
654 | }
655 |
656 | //---
657 | // Sawtooth Wave (yes, finally)
658 |
659 | var JAMSSawtoothGenerator = function(config) {
660 | JAMS.Module.call(this, config);
661 |
662 | this.className = "JAMSSawtoothGenerator";
663 |
664 | this.prerun = true;
665 | this.numberOfInputs = 2;
666 | this.numberOfOutputs= 1;
667 | this.color = 300;
668 | this.width = 80;
669 | this.height = 40;
670 | this.name = "saw osc";
671 | this.helpText =
672 | `---- SawtoothGenerator ----
673 | * 1st Input : Frequency
674 | * 2nd Input : Phase
675 | ---- *** Tips *** ----
676 | Input another signal into the 2nd input
677 | to create Phase Modulation.
678 | `;
679 |
680 | // need dis
681 | this.lastSample = 0;
682 | }
683 |
684 | JAMSSawtoothGenerator.prototype = Object.create(JAMS.Module.prototype);
685 | JAMSSawtoothGenerator.prototype.interface = function(ui, uiVars) {
686 | ui.text( 2, 2, this.name);
687 | }
688 | JAMSSawtoothGenerator.prototype.run = function(t, z, outputIndex) {
689 | if( z == 1 ) return this.lastSample;
690 | var pm = (this.inputs[1])? this.inputs[1].module.run(t, 1, this.inputs[1].index) : 0;
691 | var f = (this.inputs[0])? this.inputs[0].module.run(t, 1, this.inputs[0].index) : 440;
692 | return this.lastSample = (1 - 2 * ((t + pm) % (1 / f)) * f);
693 | };
694 |
--------------------------------------------------------------------------------
/src/misaki.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khoin/JAMS/6bf029815adaed545596952c06dad020066db1a5/src/misaki.png
--------------------------------------------------------------------------------
/src/misaki3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khoin/JAMS/6bf029815adaed545596952c06dad020066db1a5/src/misaki3x.png
--------------------------------------------------------------------------------