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,165 @@
1
+ import Range from "./Range.js";
2
+
3
+ const defaultRanges = [0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125];
4
+
5
+ class Analyser {
6
+
7
+ /**
8
+ * @param {Object} params
9
+ * @param {AudioContext} [params.context]
10
+ * @param {Number[]} [params.ranges]
11
+ * @param {Number} [params.smoothingTimeConstant = 0.3]
12
+ * @param {Number} [params.fftSize=512]
13
+ */
14
+ constructor({
15
+ context = new AudioContext(),
16
+ ranges = defaultRanges,
17
+ smoothingTimeConstant = 0.3,
18
+ fftSize = 512,
19
+ } = []) {
20
+ this.context = context;
21
+
22
+ this.ranges = [];
23
+
24
+ this._analyser = this.context.createAnalyser()
25
+ this._analyser.smoothingTimeConstant = smoothingTimeConstant;
26
+ this._analyser.fftSize = fftSize;
27
+
28
+ this.master = this.context.createGain();
29
+ this.master.connect(this._analyser);
30
+
31
+ const { frequencyBinCount } = this._analyser;
32
+
33
+ this.frequencyBinCount = frequencyBinCount;
34
+ this.freqByteData = new Uint8Array(frequencyBinCount);
35
+ this.timeByteData = new Uint8Array(frequencyBinCount);
36
+
37
+ this.setRanges(ranges);
38
+ }
39
+
40
+ /**
41
+ * Set the weight of the ranges used for the analysis
42
+ * @param {Number[]} [ranges]
43
+ */
44
+ setRanges(ranges = defaultRanges) {
45
+ if (this.ranges.length > 0) {
46
+ for (let i = 0; i < this.ranges.length; i++) {
47
+ this.ranges[i].dispose();
48
+ this.ranges[i] = null;
49
+ }
50
+
51
+ this.ranges = [];
52
+ }
53
+
54
+ this.rangeCount = ranges.length;
55
+
56
+ const sum = ranges.reduce((total, v) => {
57
+ return total + v;
58
+ }, 0);
59
+
60
+ if (sum > 0 && sum !== 1) {
61
+ console.error(`Tempo.Analyser :: sum of ranges is not equal to 1.`);
62
+ return;
63
+ }
64
+
65
+ let start = 0;
66
+ let end = this.frequencyBinCount;
67
+
68
+ for (let i = 0; i < ranges.length; i++) {
69
+ const step = ranges[i] * end;
70
+
71
+ const range = new Range(start, start + step);
72
+
73
+ this.ranges.push(range);
74
+
75
+ start += step;
76
+ }
77
+
78
+ // global
79
+ this.ranges.push(new Range(0, end));
80
+ }
81
+
82
+ /**
83
+ * Run the analysis of the current source
84
+ * @param {Number} deltaTime - Time elapsed since last call in milliseconds
85
+ */
86
+ update(deltaTime) {
87
+ this._analyser.getByteFrequencyData(this.freqByteData);
88
+
89
+ for (let i = 0; i < this.ranges.length; i++) {
90
+ this.ranges[i].update(deltaTime, this.freqByteData);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Disconnect the current source of the analyser
96
+ */
97
+ disconnect() {
98
+ this.source.disconnect(this.master);
99
+ }
100
+
101
+ /**
102
+ * Connect a MediaStream to the analyser
103
+ * @param {MediaStream} stream
104
+ */
105
+ connectMediaStream(stream) {
106
+ if (this.source) {
107
+ this.disconnect();
108
+ }
109
+
110
+ this.source = this.context.createMediaStreamSource(stream);
111
+ this.source.connect(this.master);
112
+ }
113
+
114
+ /**
115
+ * Connect a HTMLMediaElement to the analyser
116
+ * @param {MediaElement} audio
117
+ */
118
+ connectMediaElement(element) {
119
+ if (this.source) {
120
+ this.disconnect();
121
+ }
122
+
123
+ this.source = this.context.createMediaElementSource(element)
124
+ this.source.connect(this.master);
125
+ }
126
+
127
+ getRange(index) {
128
+ if (index >= this.rangeCount) {
129
+ console.log(`Range ${index} is not available. RangeCount: ${this.rangeCount}`);
130
+ return;
131
+ }
132
+
133
+ return this.ranges[index];
134
+ }
135
+
136
+ getMaster() {
137
+ return this.ranges[this.rangeCount];
138
+ }
139
+
140
+ dispose() {
141
+ if (this.source) {
142
+ this.disconnect();
143
+ this.source = null;
144
+ }
145
+
146
+ for (let i = 0; i < this.ranges.length; i++) {
147
+ this.ranges[i].dispose();
148
+ this.ranges[i] = null;
149
+ }
150
+
151
+ this.ranges = null;
152
+
153
+ this.master.disconnect(this._analyser);
154
+ this.master = null;
155
+ this._analyser = null;
156
+
157
+ this.timeByteData = null;
158
+ this.freqByteData = null;
159
+ this.rangeCount = null;
160
+ this.frequencyBinCount = null;
161
+ this.context = null;
162
+ }
163
+ }
164
+
165
+ export default Analyser;
@@ -0,0 +1,97 @@
1
+ class Range {
2
+
3
+ constructor(start, end) {
4
+ this.start = start;
5
+ this.end = end;
6
+
7
+ this.BEAT_HOLD_TIME = 300; // between 0 and 1000
8
+ this.BEAT_DECAY_RATE = 0.992; // between 0 and 1
9
+ this.BEAT_MIN = 0.1; // between 0 and 1
10
+
11
+ this._volume = 0;
12
+ this.beatCutOff = 0;
13
+ this.beatTime = 0;
14
+
15
+ this.listeners = [];
16
+ }
17
+
18
+ get volume() {
19
+ return this._volume;
20
+ }
21
+
22
+ get decay() {
23
+ return (this.BEAT_DECAY_RATE - 0.95) / 0.0499;
24
+ }
25
+
26
+ set decay(value) {
27
+ this.BEAT_DECAY_RATE = 0.95 + value * 0.0499;
28
+ }
29
+
30
+ get min() {
31
+ return this.BEAT_MIN;
32
+ }
33
+
34
+ set min(value) {
35
+ this.BEAT_MIN = value;
36
+ }
37
+
38
+ get hold() {
39
+ return this.BEAT_HOLD_TIME / 1000;
40
+ }
41
+
42
+ set hold(value) {
43
+ this.BEAT_HOLD_TIME = value * 1000;
44
+ }
45
+
46
+ onBeat(listener) {
47
+ this.listeners.push(listener);
48
+
49
+ return () => {
50
+ const index = this.listeners.indexOf(listener);
51
+
52
+ this.listeners.splice(index, 1);
53
+ }
54
+ }
55
+
56
+ update(deltaTime, freqByteData) {
57
+ this._volume = 0;
58
+
59
+ for (let i = this.start; i < this.end; i++) {
60
+ this._volume += freqByteData[i];
61
+ }
62
+
63
+ this._volume /= (this.end - this.start) * 256;
64
+
65
+ // detect beat
66
+ if (this.beatTime >= this.BEAT_HOLD_TIME && this._volume > this.beatCutOff && this._volume > this.BEAT_MIN) {
67
+ for (let i = 0; i < this.listeners.length; i++) {
68
+ this.listeners[i]();
69
+ }
70
+
71
+ this.beatCutOff = this._volume * 1.15;
72
+ this.beatTime = 0;
73
+ } else {
74
+ if (this.beatTime <= this.BEAT_HOLD_TIME) {
75
+ this.beatTime += deltaTime * 1000;
76
+ } else {
77
+ this.beatCutOff *= this.BEAT_DECAY_RATE;
78
+ this.beatCutOff = Math.max(this.beatCutOff, this.BEAT_MIN);
79
+ }
80
+ }
81
+ }
82
+
83
+ dispose() {
84
+ this.start = null;
85
+ this.end = null;
86
+
87
+ this.BEAT_HOLD_TIME = null;
88
+ this.BEAT_DECAY_RATE = null;
89
+ this.BEAT_MIN = null;
90
+
91
+ this._volume = null;
92
+ this.beatCutOff = null;
93
+ this.beatTime = null;
94
+ }
95
+ }
96
+
97
+ export default Range;
@@ -0,0 +1,138 @@
1
+ import Analyser from "./Analyser.js";
2
+
3
+ class Tempo {
4
+
5
+ constructor({
6
+ context = new AudioContext(),
7
+ ranges,
8
+ autoUpdate = true,
9
+ } = {}) {
10
+ this.context = context;
11
+ this.autoUpdate = autoUpdate;
12
+ this.analyser = new Analyser({
13
+ context: this.context,
14
+ ranges,
15
+ });
16
+
17
+ this.running = false;
18
+
19
+ this.listeners = [];
20
+
21
+ document.addEventListener("touchend", unlock, true);
22
+ document.addEventListener("mousedown", unlock, true);
23
+ document.addEventListener("click", unlock, true);
24
+
25
+ document.addEventListener("touchend", unlock, true);
26
+ document.addEventListener("mousedown", unlock, true);
27
+ document.addEventListener("click", unlock, true);
28
+
29
+ function unlock() {
30
+ if (context.state === "suspended" || context.state === "interrupted") {
31
+ context.resume();
32
+
33
+ document.removeEventListener("touchend", unlock, true);
34
+ document.removeEventListener("mousedown", unlock, true);
35
+ document.removeEventListener("click", unlock, true);
36
+ }
37
+
38
+ return;
39
+
40
+ // if (typeof context.resume === "function") {
41
+ // return context.resume().then(startFakeBuffer);
42
+ // }
43
+ }
44
+ }
45
+
46
+ onUpdate(listener) {
47
+ this.listeners.push(listener);
48
+
49
+ return () => {
50
+ this.listeners.splice(this.listeners.indexOf(listener), 1);
51
+ };
52
+ }
53
+
54
+ get master() {
55
+ return this.analyser.getMaster();
56
+ }
57
+
58
+ getRange(index) {
59
+ return this.analyser.getRange(index);
60
+ }
61
+
62
+ getRanges() {
63
+ const rangesWithoutMaster = [...this.analyser.ranges]
64
+ rangesWithoutMaster.splice(this.analyser.ranges.length - 1, 1);
65
+
66
+ return rangesWithoutMaster;
67
+ }
68
+
69
+ setRanges(ranges) {
70
+ this.analyser.setRanges(ranges);
71
+ }
72
+
73
+ update(deltaTime) {
74
+ this.analyser.update(deltaTime);
75
+
76
+ for (let i = 0; i < this.listeners.length; i++) {
77
+ this.listeners[i]();
78
+ }
79
+ }
80
+
81
+ listen() {
82
+ navigator.getUserMedia({
83
+ audio: true,
84
+ video: false
85
+ }, (stream) => {
86
+ this.analyser.connectMediaStream(stream);
87
+
88
+ if (this.autoUpdate) {
89
+ this.start();
90
+ }
91
+ }, (error) => {
92
+ console.error(`Tempo :: cannot access microphone. Make sure to allow usage of the microphone.`);
93
+ });
94
+ }
95
+
96
+ start() {
97
+ this.running = true;
98
+
99
+ let lastTime = performance.now();
100
+
101
+ const run = (time = 0) => {
102
+ let dt = time - lastTime;
103
+ lastTime = time;
104
+
105
+ this.update(dt);
106
+ this._raf = requestAnimationFrame(run);
107
+ };
108
+
109
+ run();
110
+ }
111
+
112
+ stop() {
113
+ if (this._raf) {
114
+ cancelAnimationFrame(this._raf);
115
+ this._raf = null;
116
+ }
117
+
118
+ this.running = false;
119
+ }
120
+
121
+ dispose() {
122
+ if (this.running) {
123
+ this.stop();
124
+ }
125
+
126
+ this.analyser.dispose();
127
+ this.analyser = null;
128
+
129
+ this.context.close();
130
+ this.context = null;
131
+ this.running = null;
132
+
133
+ console.log("Tempo :: dispose", this);
134
+ }
135
+
136
+ }
137
+
138
+ export default Tempo;
@@ -0,0 +1,93 @@
1
+ <script>
2
+ import { onDestroy, onMount } from "svelte";
3
+
4
+ import { audioAnalysis } from "../../stores/audioAnalysis";
5
+
6
+
7
+
8
+ export let rowCount = 10;
9
+ export let index;
10
+
11
+ let rows = Array(rowCount).fill({ node: null });
12
+ let range = audioAnalysis.getRange(index);
13
+
14
+ let offUpdate;
15
+
16
+ onMount(() => {
17
+ offUpdate = audioAnalysis.onUpdate(() => {
18
+ const { volume } = range;
19
+ const count = rowCount * volume;
20
+
21
+ const full = Math.floor(count);
22
+ const temp = count - full;
23
+
24
+ for(let i = rows.length - 1; i >= 0; i--) {
25
+ const index = rows.length - i;
26
+ const node = rows[i];
27
+
28
+ if (index < full) {
29
+ node.style.setProperty('--opacity', 1);
30
+ } else {
31
+ node.style.setProperty('--opacity', 0);
32
+ }
33
+ }
34
+ });
35
+ });
36
+
37
+ onDestroy(() => {
38
+ if (offUpdate) {
39
+ offUpdate();
40
+ offUpdate = null;
41
+ }
42
+ });
43
+
44
+ </script>
45
+
46
+ <div class="range" style={`grid-template-rows: repeat(${rows.length}, 1fr)`}>
47
+ {#each rows as row, i }
48
+ <div class="row" bind:this={rows[i]}></div>
49
+ {/each}
50
+ </div>
51
+
52
+ <style>
53
+ .range {
54
+ position: relative;
55
+
56
+ display: grid;
57
+ row-gap: 2px;
58
+ padding: 3px 0;
59
+ }
60
+
61
+ .range:not(:last-child):after {
62
+ content: '';
63
+
64
+ position: absolute;
65
+ top: 0;
66
+ right: -2px;
67
+ bottom: 0;
68
+ width: 1px;
69
+
70
+ background-color: var(--color-border-input);
71
+ }
72
+
73
+ .row {
74
+ position: relative;
75
+ background: rgba(0, 0, 0, 0.3);
76
+ border-radius: 2px;
77
+ }
78
+
79
+ .row:before {
80
+ content: "";
81
+
82
+ position: absolute;
83
+ top: 0;
84
+ left: 0;
85
+
86
+ width: 100%;
87
+ height: 100%;
88
+
89
+ background-color: var(--color-active);
90
+ opacity: var(--opacity, 0);
91
+ border-radius: 2px;
92
+ }
93
+ </style>
@@ -0,0 +1,31 @@
1
+ <script>
2
+ import Range from "./Range.svelte";
3
+ import { audioAnalysis } from "../../stores/audioAnalysis";
4
+
5
+ $: ranges = audioAnalysis.getRanges();
6
+
7
+ </script>
8
+
9
+ <div class="spectrum" style={`grid-template-columns: repeat(${ranges.length}, 1fr)`}>
10
+ {#each ranges as r, rangeIndex }
11
+ <Range rowCount={12} index={rangeIndex} />
12
+ {/each}
13
+ </div>
14
+
15
+ <style>
16
+ .spectrum {
17
+ position: relative;
18
+
19
+ display: grid;
20
+ column-gap: 3px;
21
+ width: 100%;
22
+ aspect-ratio: 3 / 1;
23
+ grid-template-columns: repeat(10, 1fr);
24
+ padding: 0 3px;
25
+
26
+ border-radius: var(--border-radius-input);
27
+ background-color: var(--color-background-input);
28
+ box-shadow: inset 0 0 0 1px var(--color-border-input);
29
+ }
30
+
31
+ </style>
@@ -0,0 +1,70 @@
1
+ <script>
2
+ import Field from "../ui/Field.svelte";
3
+ import FieldGroup from "../ui/FieldGroup.svelte";
4
+ import Module from "../ui/Module.svelte";
5
+ import Spectrum from "./AudioAnalyser/Spectrum.svelte";
6
+ import { audioAnalysis } from "../stores/audioAnalysis.js";
7
+ import { onDestroy, onMount } from "svelte";
8
+
9
+ onMount(() => {
10
+ audioAnalysis.listen();
11
+ });
12
+
13
+ onDestroy(() => {
14
+ audioAnalysis.stop();
15
+ })
16
+
17
+
18
+ function handleChangeDecay(event) {
19
+ audioAnalysis.master.decay = event.detail;
20
+ }
21
+
22
+ function handleChangeHold(event) {
23
+ audioAnalysis.master.hold = event.detail;
24
+ }
25
+
26
+ function handleChangeMin(event) {
27
+ audioAnalysis.master.min = event.detail;
28
+ }
29
+
30
+ </script>
31
+
32
+ <Module name="Audio Analyser">
33
+ <Field
34
+ key="source"
35
+ value="microphone"
36
+ params={{ options: ["microphone", "audioplayer"]}}
37
+ />
38
+ <Field key="spectrum">
39
+ <Spectrum />
40
+ </Field>
41
+ <FieldGroup name="global">
42
+ <Field key="decay" value={audioAnalysis.master.decay} params={{min: 0, max: 1, step: 0.01}} on:change={handleChangeDecay}/>
43
+ <Field key="hold" value={audioAnalysis.master.hold} params={{min: 0, max: 1, step: 0.01}} on:change={handleChangeHold}/>
44
+ <Field key="min" value={audioAnalysis.master.min} params={{min: 0, max: 1, step: 0.01}} on:change={handleChangeMin}/>
45
+ </FieldGroup>
46
+ {#if audioAnalysis.getRanges().length > 0}
47
+ {#each audioAnalysis.getRanges() as range, rangeIndex }
48
+ <FieldGroup name={`range ${range.start}-${range.end}`} collapsed key={rangeIndex}>
49
+ <Field
50
+ key="decay"
51
+ value={range.decay}
52
+ params={{min: 0, max: 1, step: 0.01}}
53
+ on:change={(e) => range.decay = e.detail}
54
+ />
55
+ <Field
56
+ key="hold"
57
+ value={range.hold}
58
+ params={{min: 0, max: 1, step: 0.01}}
59
+ on:change={(e) => range.hold = e.detail}
60
+ />
61
+ <Field
62
+ key="min"
63
+ value={range.min}
64
+ params={{min: 0, max: 1, step: 0.01}}
65
+ on:change={(e) => range.min = e.detail}
66
+ />
67
+ </FieldGroup>
68
+ {/each}
69
+ {/if}
70
+ </Module>