fragment-tools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +101 -0
  3. package/bin/index.js +19 -0
  4. package/docs/README.md +18 -0
  5. package/docs/api/CLI.md +44 -0
  6. package/docs/api/renderers.md +80 -0
  7. package/docs/api/sketch.md +216 -0
  8. package/docs/api/triggers.md +101 -0
  9. package/docs/guide/about.md +16 -0
  10. package/docs/guide/exports.md +86 -0
  11. package/docs/guide/external-dependencies.md +22 -0
  12. package/docs/guide/getting-started.md +113 -0
  13. package/docs/guide/hot-shader-reloading.md +20 -0
  14. package/docs/guide/shortcuts.md +12 -0
  15. package/docs/guide/triggers.png +0 -0
  16. package/docs/guide/using-triggers.md +39 -0
  17. package/examples/cube-three.js +34 -0
  18. package/examples/ellipse-p5.js +26 -0
  19. package/examples/icon.fs +96 -0
  20. package/examples/icon.js +63 -0
  21. package/examples/icon.png +0 -0
  22. package/examples/icon.transparent.png +0 -0
  23. package/examples/package-lock.json +40 -0
  24. package/examples/package.json +15 -0
  25. package/examples/shape-2d.js +45 -0
  26. package/examples/shape-three.js +49 -0
  27. package/examples/shape-tree.fs +3 -0
  28. package/package.json +37 -0
  29. package/screenshot.png +0 -0
  30. package/src/cli/db.js +17 -0
  31. package/src/cli/index.js +198 -0
  32. package/src/cli/log.js +26 -0
  33. package/src/cli/plugins/check-dependencies.js +77 -0
  34. package/src/cli/plugins/db.js +12 -0
  35. package/src/cli/plugins/hot-shader-reload.js +86 -0
  36. package/src/cli/plugins/hot-sketch-reload.js +39 -0
  37. package/src/cli/plugins/screenshot.js +31 -0
  38. package/src/cli/server.js +140 -0
  39. package/src/cli/templates/2d.js +15 -0
  40. package/src/cli/templates/fragment.fs +10 -0
  41. package/src/cli/templates/fragment.js +18 -0
  42. package/src/cli/templates/index.js +24 -0
  43. package/src/cli/templates/p5.js +13 -0
  44. package/src/cli/templates/three-fragment.js +53 -0
  45. package/src/cli/templates/three-orthographic.js +23 -0
  46. package/src/cli/templates/three-perspective.js +20 -0
  47. package/src/cli/ws.js +92 -0
  48. package/src/client/app/App.svelte +8 -0
  49. package/src/client/app/client.js +68 -0
  50. package/src/client/app/components/IconCross.svelte +29 -0
  51. package/src/client/app/components/Init.svelte +13 -0
  52. package/src/client/app/components/KeyBinding.svelte +32 -0
  53. package/src/client/app/inputs/Input.js +15 -0
  54. package/src/client/app/inputs/Keyboard.js +21 -0
  55. package/src/client/app/inputs/MIDI.js +144 -0
  56. package/src/client/app/inputs/Mouse.js +5 -0
  57. package/src/client/app/inputs/Webcam.js +98 -0
  58. package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +88 -0
  59. package/src/client/app/lib/canvas-recorder/FFMPEGRecorder.js +56 -0
  60. package/src/client/app/lib/canvas-recorder/FrameRecorder.js +40 -0
  61. package/src/client/app/lib/canvas-recorder/GIFRecorder.js +52 -0
  62. package/src/client/app/lib/canvas-recorder/MP4Recorder.js +46 -0
  63. package/src/client/app/lib/canvas-recorder/WebMRecorder.js +30 -0
  64. package/src/client/app/lib/canvas-recorder/mp4.js +20 -0
  65. package/src/client/app/lib/canvas-recorder/mp4.wasm +0 -0
  66. package/src/client/app/lib/canvas-recorder/utils.js +22 -0
  67. package/src/client/app/lib/gl/Geometry.js +39 -0
  68. package/src/client/app/lib/gl/Program.js +130 -0
  69. package/src/client/app/lib/gl/Renderer.js +148 -0
  70. package/src/client/app/lib/gl/Texture.js +114 -0
  71. package/src/client/app/lib/gl/index.js +109 -0
  72. package/src/client/app/lib/gl/utils.js +5 -0
  73. package/src/client/app/lib/helpers/frameDebounce.js +40 -0
  74. package/src/client/app/lib/loader/index.js +20 -0
  75. package/src/client/app/lib/loader/loadImage.js +19 -0
  76. package/src/client/app/lib/loader/loadScript.js +14 -0
  77. package/src/client/app/lib/paper-sizes.js +104 -0
  78. package/src/client/app/lib/presets.js +12 -0
  79. package/src/client/app/lib/tempo/Analyser.js +165 -0
  80. package/src/client/app/lib/tempo/Range.js +97 -0
  81. package/src/client/app/lib/tempo/index.js +138 -0
  82. package/src/client/app/modules/AudioAnalyser/Range.svelte +93 -0
  83. package/src/client/app/modules/AudioAnalyser/Spectrum.svelte +31 -0
  84. package/src/client/app/modules/AudioAnalyser.svelte +70 -0
  85. package/src/client/app/modules/Console/ConsoleLine.svelte +254 -0
  86. package/src/client/app/modules/Console.svelte +82 -0
  87. package/src/client/app/modules/Exports.svelte +105 -0
  88. package/src/client/app/modules/MidiPanel.svelte +106 -0
  89. package/src/client/app/modules/Monitor.svelte +62 -0
  90. package/src/client/app/modules/Params.svelte +112 -0
  91. package/src/client/app/renderers/2DRenderer.js +5 -0
  92. package/src/client/app/renderers/FragmentRenderer.js +62 -0
  93. package/src/client/app/renderers/OGLRenderer.js +0 -0
  94. package/src/client/app/renderers/P5Renderer.js +39 -0
  95. package/src/client/app/renderers/THREERenderer.js +128 -0
  96. package/src/client/app/stores/audioAnalysis.js +10 -0
  97. package/src/client/app/stores/console.js +76 -0
  98. package/src/client/app/stores/errors.js +25 -0
  99. package/src/client/app/stores/exports.js +28 -0
  100. package/src/client/app/stores/index.js +2 -0
  101. package/src/client/app/stores/layout.js +187 -0
  102. package/src/client/app/stores/multisampling.js +16 -0
  103. package/src/client/app/stores/props.js +44 -0
  104. package/src/client/app/stores/renderers.js +60 -0
  105. package/src/client/app/stores/rendering.js +111 -0
  106. package/src/client/app/stores/sketches.js +40 -0
  107. package/src/client/app/stores/time.js +27 -0
  108. package/src/client/app/stores/utils.js +66 -0
  109. package/src/client/app/transitions/fade.js +17 -0
  110. package/src/client/app/transitions/index.js +12 -0
  111. package/src/client/app/transitions/splitX.js +16 -0
  112. package/src/client/app/transitions/splitY.js +16 -0
  113. package/src/client/app/triggers/Keyboard.js +95 -0
  114. package/src/client/app/triggers/MIDI.js +122 -0
  115. package/src/client/app/triggers/Mouse.js +96 -0
  116. package/src/client/app/triggers/Trigger.js +71 -0
  117. package/src/client/app/triggers/index.js +19 -0
  118. package/src/client/app/triggers/shared.js +37 -0
  119. package/src/client/app/ui/Build.svelte +96 -0
  120. package/src/client/app/ui/ErrorOverlay.svelte +130 -0
  121. package/src/client/app/ui/Field.svelte +262 -0
  122. package/src/client/app/ui/FieldGroup.svelte +103 -0
  123. package/src/client/app/ui/FieldSection.svelte +123 -0
  124. package/src/client/app/ui/FieldSpace.svelte +37 -0
  125. package/src/client/app/ui/FieldTrigger.svelte +263 -0
  126. package/src/client/app/ui/FieldTriggers.svelte +58 -0
  127. package/src/client/app/ui/FloatingParams.svelte +49 -0
  128. package/src/client/app/ui/Layout.svelte +50 -0
  129. package/src/client/app/ui/LayoutColumn.svelte +9 -0
  130. package/src/client/app/ui/LayoutComponent.svelte +279 -0
  131. package/src/client/app/ui/LayoutResizer.svelte +218 -0
  132. package/src/client/app/ui/LayoutRoot.svelte +11 -0
  133. package/src/client/app/ui/LayoutRow.svelte +9 -0
  134. package/src/client/app/ui/LayoutToolbar.svelte +264 -0
  135. package/src/client/app/ui/Module.svelte +154 -0
  136. package/src/client/app/ui/ModuleHeaderAction.svelte +87 -0
  137. package/src/client/app/ui/ModuleHeaderButton.svelte +21 -0
  138. package/src/client/app/ui/ModuleHeaderSelect.svelte +50 -0
  139. package/src/client/app/ui/ModuleRenderer.svelte +38 -0
  140. package/src/client/app/ui/OutputRenderer.svelte +149 -0
  141. package/src/client/app/ui/ParamsMultisampling.svelte +109 -0
  142. package/src/client/app/ui/ParamsOutput.svelte +139 -0
  143. package/src/client/app/ui/Preview.svelte +15 -0
  144. package/src/client/app/ui/SelectChevrons.svelte +25 -0
  145. package/src/client/app/ui/SketchRenderer.svelte +672 -0
  146. package/src/client/app/ui/SketchSelect.svelte +49 -0
  147. package/src/client/app/ui/fields/ButtonInput.svelte +54 -0
  148. package/src/client/app/ui/fields/CheckboxInput.svelte +70 -0
  149. package/src/client/app/ui/fields/ColorInput.svelte +187 -0
  150. package/src/client/app/ui/fields/FieldInputRow.svelte +13 -0
  151. package/src/client/app/ui/fields/ImageInput.svelte +145 -0
  152. package/src/client/app/ui/fields/Input.svelte +120 -0
  153. package/src/client/app/ui/fields/ListInput.svelte +106 -0
  154. package/src/client/app/ui/fields/NumberInput.svelte +114 -0
  155. package/src/client/app/ui/fields/ProgressInput.svelte +90 -0
  156. package/src/client/app/ui/fields/Select.svelte +116 -0
  157. package/src/client/app/ui/fields/TextInput.svelte +18 -0
  158. package/src/client/app/ui/fields/Vec2Input.svelte +5 -0
  159. package/src/client/app/ui/fields/Vec3Input.svelte +6 -0
  160. package/src/client/app/ui/fields/VectorInput.svelte +102 -0
  161. package/src/client/app/utils/canvas.utils.js +229 -0
  162. package/src/client/app/utils/color.utils.js +427 -0
  163. package/src/client/app/utils/file.utils.js +77 -0
  164. package/src/client/app/utils/glsl.utils.js +14 -0
  165. package/src/client/app/utils/glslErrors.js +154 -0
  166. package/src/client/app/utils/index.js +39 -0
  167. package/src/client/app/utils/math.utils.js +23 -0
  168. package/src/client/app/utils/props.utils.js +53 -0
  169. package/src/client/index.html +18 -0
  170. package/src/client/main.js +9 -0
  171. package/src/client/public/css/global.css +115 -0
  172. package/src/client/public/favicon.ico +0 -0
  173. package/src/client/public/fonts/Inter-Bold.woff2 +0 -0
  174. package/src/client/public/fonts/Inter-Italic.woff2 +0 -0
  175. package/src/client/public/fonts/Inter-Regular.woff2 +0 -0
  176. package/src/client/public/fonts/Inter-SemiBold.woff2 +0 -0
  177. package/src/client/public/fonts/JetBrainsMono-Regular.woff2 +0 -0
  178. package/src/client/public/icons/chevron-bottom.svg +3 -0
  179. package/src/client/public/icons/chevron-right.svg +3 -0
  180. package/src/client/public/icons/chevron-top.svg +3 -0
  181. package/src/client/public/icons/columns-horizontal.svg +4 -0
  182. package/src/client/public/icons/columns-vertical.svg +4 -0
  183. package/src/client/public/icons/folder-plus.svg +6 -0
  184. package/src/client/public/icons/lock.svg +4 -0
  185. package/src/client/public/icons/picture-in-picture.svg +4 -0
  186. package/src/client/public/icons/trash.svg +5 -0
  187. package/src/client/public/icons/trigger.svg +8 -0
  188. package/src/client/public/icons/unlock.svg +4 -0
  189. package/src/client/public/js/ffmpeg.min.js +2 -0
  190. package/src/client/public/js/ffmpeg.min.js.map +1 -0
  191. package/src/client/public/js/gif.js +2 -0
  192. package/src/client/public/js/gif.worker.js +2 -0
@@ -0,0 +1,18 @@
1
+ import fragmentShader from "./fragment.fs";
2
+
3
+ let uniforms = {
4
+ uTime: { value: 0, type: "float" },
5
+ };
6
+
7
+ export let init = ({ frag }) => {
8
+ frag.uniforms = uniforms;
9
+ frag.shader = fragmentShader;
10
+ };
11
+
12
+ export let update = ({ frag, deltaTime }) => {
13
+ uniforms.uTime.value += deltaTime;
14
+
15
+ frag.render();
16
+ };
17
+
18
+ export let rendering = "fragment";
@@ -0,0 +1,24 @@
1
+ const templates = {
2
+ "2d": [
3
+ "./templates/2d.js"
4
+ ],
5
+ "fragment": [
6
+ "./templates/fragment.js",
7
+ "./templates/fragment.fs"
8
+ ],
9
+ "three/orthographic": [
10
+ "./templates/three-orthographic.js"
11
+ ],
12
+ "three/fragment": [
13
+ "./templates/three-fragment.js",
14
+ "./templates/fragment.fs"
15
+ ],
16
+ "three/perspective": [
17
+ "./templates/three-perspective.js"
18
+ ],
19
+ "p5": [
20
+ "./templates/p5.js"
21
+ ]
22
+ };
23
+
24
+ export default templates;
@@ -0,0 +1,13 @@
1
+ import p5 from "p5";
2
+
3
+ export let props = {};
4
+
5
+ export let setup = ({ p, width, height }) => {
6
+
7
+ };
8
+
9
+ export let draw = ({ p }) => {
10
+ p.background(255, 0, 0);
11
+ };
12
+
13
+ export let rendering = "p5";
@@ -0,0 +1,53 @@
1
+ import * as THREE from "three";
2
+ import fragmentShader from "./fragment.fs";
3
+
4
+ let camera;
5
+ let uniforms = {
6
+ uResolution: { value: new THREE.Vector2() },
7
+ uTime: { value: 0 },
8
+ };
9
+
10
+ export let init = ({ scene, width, height }) => {
11
+ camera = new THREE.OrthographicCamera(1, 1, 1, 1, 1, 1000);
12
+
13
+ let geometry = new THREE.BufferGeometry();
14
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute([-1, 3, 0, -1, -1, 0, 3, -1, 0], 3));
15
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute([0, 2, 0, 0, 2, 0], 2));
16
+
17
+ let mesh = new THREE.Mesh(geometry, new THREE.RawShaderMaterial({
18
+ vertexShader: `
19
+ attribute vec3 position;
20
+ attribute vec2 uv;
21
+
22
+ varying vec2 vUv;
23
+
24
+ void main() {
25
+ vUv = uv;
26
+ gl_Position = vec4(position, 1.);
27
+ }
28
+ `,
29
+ fragmentShader,
30
+ uniforms,
31
+ }));
32
+
33
+ scene.add(mesh);
34
+ };
35
+
36
+ export let update = ({ renderer, scene, time, deltaTime }) => {
37
+ uniforms.uTime.value = time;
38
+ renderer.render(scene, camera);
39
+ };
40
+
41
+ export let resize = ({ width, height }) => {
42
+ uniforms.uResolution.value.x = width;
43
+ uniforms.uResolution.value.y = height;
44
+
45
+ camera.left = -width * 0.5;
46
+ camera.right = width * 0.5;
47
+ camera.top = height * 0.5;
48
+ camera.bottom = -height * 0.5;
49
+
50
+ camera.updateProjectionMatrix();
51
+ };
52
+
53
+ export let rendering = "three";
@@ -0,0 +1,23 @@
1
+ import * as THREE from "three";
2
+
3
+ let camera;
4
+
5
+ export let init = ({ scene, width, height }) => {
6
+ camera = new THREE.OrthographicCamera(1, 1, 1, 1, 1, 1000);
7
+ camera.position.z = 1;
8
+ };
9
+
10
+ export let update = ({ renderer, scene, time, deltaTime }) => {
11
+ renderer.render(scene, camera);
12
+ };
13
+
14
+ export let resize = ({ width, height }) => {
15
+ camera.left = -width * 0.5;
16
+ camera.right = width * 0.5;
17
+ camera.top = height * 0.5;
18
+ camera.bottom = -height * 0.5;
19
+
20
+ camera.updateProjectionMatrix();
21
+ };
22
+
23
+ export let rendering = "three";
@@ -0,0 +1,20 @@
1
+ import * as THREE from "three";
2
+
3
+ let camera;
4
+
5
+ export let init = ({ scene, width, height }) => {
6
+ camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
7
+ camera.position.z = 10;
8
+ camera.lookAt(new THREE.Vector3());
9
+ };
10
+
11
+ export let update = ({ renderer, scene, time, deltaTime }) => {
12
+ renderer.render(scene, camera);
13
+ };
14
+
15
+ export let resize = ({ width, height }) => {
16
+ camera.aspect = width / height;
17
+ camera.updateProjectionMatrix();
18
+ };
19
+
20
+ export let rendering = "three";
package/src/cli/ws.js ADDED
@@ -0,0 +1,92 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import getPort from 'get-port';
4
+ import log from "./log.js";
5
+ import WebSocket, { WebSocketServer } from 'ws';
6
+ import db from "./db.js";
7
+
8
+ export async function start({
9
+ port = 1234,
10
+ cwd = "",
11
+ } = {}) {
12
+ port = await getPort({ port });
13
+
14
+ let wss = new WebSocketServer({ port });
15
+
16
+ wss.on('connection', (socket) => {
17
+ socket.on('message', (message) => {
18
+ const json = JSON.parse(message);
19
+ const { event, data } = json;
20
+
21
+ if (event === "save") {
22
+ const { key, value } = data;
23
+ db.save(key, value);
24
+ }
25
+
26
+ send(json, {
27
+ sender: socket
28
+ });
29
+ });
30
+
31
+ send({
32
+ event: 'start',
33
+ data: {
34
+ clientCount: wss.clients.size - 1,
35
+ },
36
+ }, {
37
+ include: socket
38
+ });
39
+
40
+ send({
41
+ event: 'client-connect',
42
+ data: {
43
+ clientCount: wss.clients.size - 1
44
+ }
45
+ }, {
46
+ exclude: socket,
47
+ });
48
+
49
+ socket.on('close', () => {
50
+ send({
51
+ event: 'client-disconnect',
52
+ data: {
53
+ clientCount: wss.clients.size - 1
54
+ }
55
+ }, {
56
+ exclude: socket,
57
+ });
58
+ });
59
+ });
60
+
61
+ wss.on('error', (e) => {
62
+ if (e.code !== 'EADDRINUSE') {
63
+ console.error(`WebSocket server error:\n${e.stack || e.message}`);
64
+ }
65
+ });
66
+
67
+ function send(payload, { transform = true, exclude = null, include = null } = {}) {
68
+ const stringified = transform ? JSON.stringify(payload) : payload;
69
+
70
+ wss.clients.forEach((client) => {
71
+ if (client.readyState === WebSocket.OPEN && (client !== exclude || client === include)) {
72
+ client.send(stringified);
73
+ }
74
+ });
75
+ }
76
+
77
+ return {
78
+ port,
79
+ send,
80
+ close: () => {
81
+ return new Promise((resolve, reject) => {
82
+ wss.close((err) => {
83
+ if (err) {
84
+ reject(err)
85
+ } else {
86
+ resolve()
87
+ }
88
+ });
89
+ });
90
+ }
91
+ };
92
+ }
@@ -0,0 +1,8 @@
1
+ <script>
2
+ import Layout from "./ui/Layout.svelte";
3
+ import Init from "./components/Init.svelte";
4
+ import "./client";
5
+ </script>
6
+
7
+ <Init />
8
+ <Layout />
@@ -0,0 +1,68 @@
1
+ const socketProtocol = (location.protocol === 'https:' ? 'wss' : 'ws');
2
+ const socketHost = `${location.hostname}:${__FRAGMENT_PORT__}`;
3
+
4
+ let socket, listeners = {};
5
+
6
+ function handleMessage(payload) {
7
+ const { event, data = {} } = payload;
8
+ const callbacks = listeners[event];
9
+
10
+ if (callbacks && callbacks.length) {
11
+ callbacks.forEach((cb) => cb(data));
12
+ }
13
+ }
14
+
15
+ function on(event, cb) {
16
+ if (!listeners[event]) {
17
+ listeners[event] = [];
18
+ }
19
+
20
+ listeners[event].push(cb);
21
+
22
+ return () => {
23
+ off(event, cb);
24
+ };
25
+ }
26
+
27
+ function off(event, cb) {
28
+ const callbacks = listeners[event];
29
+
30
+ if (callbacks && callbacks.length) {
31
+ const filtered = callbacks.filter((callback) => callback !== cb);
32
+
33
+ listeners[event] = filtered;
34
+ }
35
+ }
36
+
37
+ let opened = false;
38
+ function emit(event, data) {
39
+ if (opened) {
40
+ socket.send(JSON.stringify({
41
+ event,
42
+ data,
43
+ }));
44
+ }
45
+ }
46
+
47
+ if (import.meta.hot) {
48
+ console.log("[fragment] connecting...");
49
+
50
+ socket = new WebSocket(`${socketProtocol}://${socketHost}`);
51
+
52
+ socket.addEventListener('message', async (message) => {
53
+ const { data } = message;
54
+
55
+ handleMessage(JSON.parse(data));
56
+ });
57
+
58
+ socket.addEventListener("open", () => {
59
+ console.log("[fragment] connected.");
60
+ opened = true;
61
+ });
62
+
63
+ import.meta.hot.on('sketch-update', (data) => {
64
+ console.log(`[fragment] hmr update /${data.filepath}`);
65
+ })
66
+ }
67
+
68
+ export const client = { on, off, emit };
@@ -0,0 +1,29 @@
1
+ <div class="icon-cross">
2
+ <div class="icon-cross-side"></div>
3
+ <div class="icon-cross-side"></div>
4
+ </div>
5
+
6
+ <style>
7
+ .icon-cross {
8
+ position: relative;
9
+ width: 100%;
10
+ height: 100%;
11
+ }
12
+
13
+ .icon-cross-side {
14
+ --size: 10px;
15
+ position: absolute;
16
+ top: calc(50% - 1px);
17
+ left: calc(50% - var(--size) * 0.5);
18
+ width: var(--size);
19
+ height: 2px;
20
+
21
+ transform: rotate(45deg);
22
+
23
+ background-color: #551717;
24
+ }
25
+
26
+ .icon-cross-side:last-child {
27
+ transform: rotate(-45deg);
28
+ }
29
+ </style>
@@ -0,0 +1,13 @@
1
+ <script>
2
+ import { assignSketchFiles } from "../triggers/shared.js";
3
+ import { sketchesKeys } from "../stores/sketches.js";
4
+ import "../utils/glslErrors.js";
5
+
6
+ sketchesKeys.subscribe((keys) => {
7
+ if (keys.length > 0) {
8
+ assignSketchFiles(keys);
9
+ }
10
+ })
11
+
12
+
13
+ </script>
@@ -0,0 +1,32 @@
1
+ <script>
2
+ import { createEventDispatcher, onDestroy, onMount } from "svelte";
3
+ import { onKeyPress, onKeyDown, onKeyUp } from "../triggers";
4
+
5
+ export let key;
6
+ export let type = "press";
7
+
8
+ const dispatch = createEventDispatcher();
9
+
10
+ const triggers = {
11
+ "press": onKeyPress,
12
+ "down": onKeyDown,
13
+ "up": onKeyUp,
14
+ };
15
+
16
+ const triggerType = triggers[type];
17
+
18
+ let trigger;
19
+
20
+ onMount(() => {
21
+ trigger = triggerType(key, (event) => {
22
+ dispatch('trigger', event);
23
+ });
24
+ })
25
+
26
+ onDestroy(() => {
27
+ if (trigger) {
28
+ trigger.destroy();
29
+ trigger = null;
30
+ }
31
+ });
32
+ </script>
@@ -0,0 +1,15 @@
1
+ class Input {
2
+ constructor() {
3
+ this.enabled = true;
4
+ }
5
+
6
+ enable() {
7
+ this.enabled = true;
8
+ }
9
+
10
+ disable() {
11
+ this.enabled = false;
12
+ }
13
+ }
14
+
15
+ export default Input;
@@ -0,0 +1,21 @@
1
+ import Input from "./Input";
2
+
3
+ class Keyboard extends Input {
4
+
5
+ /**
6
+ *
7
+ * @param {KeyboardEvent} event
8
+ */
9
+ getStepFromEvent(event) {
10
+ if (event.shiftKey) {
11
+ return 10;
12
+ } else if (event.altKey) {
13
+ return 0.1;
14
+ }
15
+
16
+ return 1;
17
+ }
18
+
19
+ }
20
+
21
+ export default new Keyboard();
@@ -0,0 +1,144 @@
1
+ import Input from "./Input";
2
+
3
+ const commands = {
4
+ 0x8: "noteoff",
5
+ 0x9: "noteon",
6
+ 0xB: "controlchange",
7
+ };
8
+
9
+ const notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
10
+
11
+ const LOCAL_STORAGE_KEY = "midi.requested";
12
+
13
+ class MIDI extends Input {
14
+
15
+ constructor() {
16
+ super();
17
+
18
+ this.access = null;
19
+ this.requesting = false;
20
+ this.enabled = false;
21
+
22
+ this.listeners = new Map();
23
+ this.selectedInputID = null;
24
+ this.selectedOutputID = null;
25
+
26
+ if (localStorage.getItem(LOCAL_STORAGE_KEY)) {
27
+ this.request();
28
+ }
29
+ }
30
+
31
+ get inputs() {
32
+ return this.access ? this.access.inputs : new Map();
33
+ }
34
+
35
+ get outputs() {
36
+ return this.access ? this.access.outputs : new Map();
37
+ }
38
+
39
+ start() {
40
+ this.access.onstatechange = (event) => this.onStateChange(event);
41
+ this.attachListeners();
42
+
43
+ if (this.inputs.size === 1) {
44
+ const [entry] = this.inputs.values();
45
+ const { id } = entry;
46
+
47
+ this.selectedInputID = id;
48
+ }
49
+
50
+ if (this.outputs.size === 1) {
51
+ const [entry] = this.outputs.values();
52
+ const { id } = entry;
53
+
54
+ this.selectedOutputID = id;
55
+ }
56
+ }
57
+
58
+ attachListeners() {
59
+ this.inputs.forEach(entry => {
60
+ entry.onmidimessage = (event) => {
61
+ this.onMessage(event);
62
+ };
63
+ })
64
+ }
65
+
66
+ addEventListener(eventName, fn) {
67
+ if (!this.listeners.has(eventName)) {
68
+ this.listeners.set(eventName, []);
69
+ }
70
+
71
+ this.listeners.set(eventName, [...this.listeners.get(eventName), fn]);
72
+ }
73
+
74
+ onMessage(event) {
75
+ let command = event.data[0] >> 4;
76
+ let type = commands[command];
77
+
78
+ let channel = (event.data[0] & 0xf) + 1;
79
+ let data1 = event.data[1]
80
+ let data2 = event.data[2];
81
+
82
+ let note = {
83
+ number: data1,
84
+ name: notes[data1 % 12],
85
+ };
86
+
87
+ let velocity = data2 / 127;
88
+ let rawVelocity = data2;
89
+
90
+ let controller = event.target;
91
+
92
+ let data = {
93
+ type,
94
+ note,
95
+ channel,
96
+ velocity,
97
+ rawVelocity,
98
+ value: velocity,
99
+ rawValue: rawVelocity,
100
+ currentTarget: controller,
101
+ target: controller,
102
+ srcElement: controller,
103
+ };
104
+
105
+ this.emit('message', data);
106
+ this.emit(type, data);
107
+ }
108
+
109
+ onStateChange(e) {
110
+ const event = e.port.state;
111
+
112
+ this.emit(event, e);
113
+ this.attachListeners();
114
+ }
115
+
116
+ emit(eventName, data) {
117
+ if (this.listeners.has(eventName)) {
118
+ const listeners = this.listeners.get(eventName);
119
+
120
+ listeners.forEach(listener => listener(data));
121
+ }
122
+ }
123
+
124
+ handleError(error) {
125
+ console.error(error);
126
+ }
127
+
128
+ async request() {
129
+ try {
130
+ if (!this.requesting && !this.access) {
131
+ localStorage.setItem(LOCAL_STORAGE_KEY, true);
132
+ this.requesting = true;
133
+ this.access = await navigator.requestMIDIAccess();
134
+ this.enabled = true;
135
+ this.requesting = false;
136
+ this.start();
137
+ }
138
+ } catch(error) {
139
+ this.handleError(error);
140
+ }
141
+ }
142
+ }
143
+
144
+ export default new MIDI();
@@ -0,0 +1,5 @@
1
+ import Input from "./Input";
2
+
3
+ class Mouse extends Input {}
4
+
5
+ export default new Mouse();
@@ -0,0 +1,98 @@
1
+ import Input from "./Input";
2
+
3
+ class Webcam extends Input {
4
+
5
+ constructor() {
6
+ super();
7
+
8
+ this._useCanvas = false;
9
+ this.stream = null;
10
+ this.requested = false;
11
+
12
+ this.video = document.createElement('video');
13
+ this.canvas = document.createElement('canvas');
14
+ this.context = null;
15
+ }
16
+
17
+ /**
18
+ @param {Boolean} value
19
+ */
20
+ set useCanvas(value) {
21
+ const prev = this.useCanvas;
22
+
23
+ this._useCanvas = value;
24
+
25
+ if (!prev && this._useCanvas) {
26
+ this.prepareCanvas();
27
+ } else if (prev && this._useCanvas) {
28
+ cancelAnimationFrame(this._raf);
29
+ this._raf = null;
30
+ }
31
+ }
32
+
33
+ get useCanvas() {
34
+ return this._useCanvas;
35
+ }
36
+
37
+ enable() {
38
+ if (!this.requested) {
39
+ this.request();
40
+ }
41
+ }
42
+
43
+ disable() {
44
+ this.enabled = false;
45
+
46
+ if (this.stream) {
47
+ if (this.useCanvas && this._raf) {
48
+ cancelAnimationFrame(this._raf);
49
+ this._raf = null;
50
+ }
51
+
52
+ this.stream.getTracks().forEach(track => {
53
+ track.stop()
54
+ });
55
+
56
+ this.stream = null;
57
+ }
58
+ }
59
+
60
+ prepareCanvas() {
61
+ this.canvas.width = video.videoWidth;
62
+ this.canvas.height = video.videoHeight;
63
+
64
+ this.context = this.canvas.getContext("2d");
65
+ }
66
+
67
+ drawToCanvas() {
68
+ this.context.drawImage(video, 0, 0, canvas.width, canvas.height);
69
+
70
+ this._raf = requestAnimationFrame(() => {
71
+ this.drawToCanvas()
72
+ });
73
+ }
74
+
75
+ async request() {
76
+ try {
77
+ this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
78
+ this.requested = true;
79
+ this.enabled = true;
80
+
81
+ this.video.addEventListener('canplay', () => {
82
+ this.prepareCanvas();
83
+
84
+ if (this.useCanvas) {
85
+ this.drawToCanvas();
86
+ }
87
+ });
88
+ this.video.srcObject = this.stream;
89
+ this.video.play();
90
+ } catch(error) {
91
+ console.error(`Error while trying to access webcam.`);
92
+ console.log(error);
93
+ }
94
+ }
95
+
96
+ }
97
+
98
+ export default new Webcam();