index.html 18.2 KB
Newer Older
1
2
<!DOCTYPE html>
<html>
3
4
5
	<head>
		<meta charset="utf-8">
		<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
6
		<title>Purr Data</title>
Prakhar Agarwal's avatar
Prakhar Agarwal committed
7
		<link rel="shortcut icon" type="image/jpg" href="./purr.png"/>
8
9
		<meta name="description" content="Purr Data compiled for web with Emscripten" />
		<meta name="keywords" content="purr-data" />
10
		<meta name="generator" content="emsripten" />
Prakhar Agarwal's avatar
Prakhar Agarwal committed
11
		
12
		<link rel="stylesheet" type="text/css" href="./css/dejavu.css">
13
		<link id="page_style" rel="stylesheet" type="text/css" href="./css/default.css">
14
		<link rel="stylesheet" type="text/css" href="./css/webapp/webapp.css">
15
		<link rel="stylesheet" href="libs/bootstrap/bootstrap.min.css">
16
17
	</head>

Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
18
19
20
21
22
23
	<body id="container">
		<!-- Modal -->
		<div class="modal fade" id="loading-modal" tabindex="-1" role="dialog" aria-hidden="true">
			<div class="modal-dialog modal-dialog-centered" role="document">
				<div class="modal-content">
					<div class="modal-body text-center">
24
						<div class="spinner-border text-primary mt-2" style="width: 3rem; height: 3rem;" role="status">
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
25
26
							<span class="sr-only">Loading...</span>
						  </div>
27
						  <h3 class="mt-4">Loading Purr Data...</h3>
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
28
29
30
31
32
					</div>
				</div>
			</div>
		</div>

Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
33
		<div id="container-app" class="container-fluid">
34
			<!-- Menu -->
35
			<nav id="menu"></nav>
36
37

			<!-- Content -->
38
			<div class="d-flex content-webapp" id="content">
39
40

			<!-- Sidebar -->
41
			
42
			<div class="card" id="sidebar">
43
				<div class="card-body text-center" id="sidebar-body">
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
44
					<div id="pd-info">
Prakhar Agarwal's avatar
Prakhar Agarwal committed
45
						<img src="https://agraef.github.io/purr-data-intro/purr.png" alt="purr-data-logo">
Zack Lee's avatar
Zack Lee committed
46
						<h4>Purr Data</h4>
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
47
48
49
					</div>
					<hr>
					<div id="sidebar-body-dialog"></div>
50
					<hr>
Akash Negi's avatar
Akash Negi committed
51
					<div class="d-flex ml-3">
52
53
54
55
56
57
58
						<i class="fa fa fa-volume-up text-primary" aria-hidden="true"></i>
						<h5>DSP</h5>
						<label class="dsp_toggle">
							<input type="checkbox" id="dsp_control" name="dsp_control" value="on" />
							<span></span>
						</label>

59
					</div>
60
61
					<hr>
					<div>
Akash Negi's avatar
Akash Negi committed
62
						<div class="d-flex ml-3">
63
64
							<i class="fa fa-folder-open-o text-primary" aria-hidden="true"></i>
							<h5>Files</h5>
Prakhar Agarwal's avatar
Prakhar Agarwal committed
65
							<i class="fa fa-refresh ml-auto mt-1 text-primary" id="reload-i" aria-hidden="true"
66
67
68
							onclick="pdbundle.pdgui.update_file_ls()"></i>

						</div>
69

70
						<div class="text-left" id="sidebar-files">
71
							<ul id="file_ls"></ul>
72
73
74
75
							<div class="text-secondary text-center" id="file_ls_empty">
								<p>No files found yet</p>
							</div>

76
						</div>
Zack Lee's avatar
Zack Lee committed
77
					</div>
78
79
80
81
82
				</div>
			</div>

			<!-- Canvas/Pd Windows -->
			<div class="d-flex flex-column flex-fill" id="windows">
Zack Lee's avatar
Zack Lee committed
83

84
				<!-- Canvas Container -->
85
86
				<div class="flex-grow-1 card" id="canvas-content">
					<div class="d-flex" id="canvas-container">
87
88
89
90
91
					</div>
				</div>

				<!-- Console window -->
				<div class="card" id="console-window">
92
93
94
95
					<div class="card-header d-flex justify-content-between">
						<h5>
							<a data-toggle="collapse" data-parent="#windows" href="#console_bottom">Console</a>
						</h5>
Zack Lee's avatar
Zack Lee committed
96
97

						<div class="console-find-webapp" id="console_find" style="display:none;">
98
							<div class="d-flex justify-content-end">
Zack Lee's avatar
Zack Lee committed
99
								<label><input type="text" id="console_find_text" name="console_find_text"
Prakhar Agarwal's avatar
Prakhar Agarwal committed
100
									defaultValue="Search in Console" style="width:10em;" placeholder="Search in Console"/>
Zack Lee's avatar
Zack Lee committed
101
								</label>
102
								<label class="highlight-find">Highlight All
Zack Lee's avatar
Zack Lee committed
103
104
105
106
									<input type="checkbox" id="console_find_highlight" name="console_find_highlight"
										onchange="console_find_highlight_all(this);" />
								</label>
							</div>
107
						</div>
108

109
110
					</div>

111
112
113
			  
					<div class="collapse show" id="console_bottom">
					  <div class="card-block">
Zack Lee's avatar
Zack Lee committed
114
115
116
						<div id="printout">
							<pre id="p1" style="white-space: pre-wrap;"></pre>
						</div>
117
					  </div>
118
119
120
121
					</div>
				</div>
			</div>
		</div>
Zack Lee's avatar
Zack Lee committed
122
123
	</div>
	</div>
124

Zack Lee's avatar
Zack Lee committed
125
	<script>
126

Zack Lee's avatar
Zack Lee committed
127
128
129
130
131
132
		//--------------------- emscripten ----------------------------
		var Module
			= {
			preRun: []
			, postRun: []
			, print: function (e) {
133
134
				1 < arguments.length && (e = Array.prototype.slice.call(arguments).join(" "));
				console.log(e);
Zack Lee's avatar
Zack Lee committed
135
136
			}
			, printErr: function (e) {
137
138
				1 < arguments.length && (e = Array.prototype.slice.call(arguments).join(" "));
				console.error(e)
Zack Lee's avatar
Zack Lee committed
139
140
141
			}
			, pd: {} // make pd object accessible from outside of the scope
			, mainInit: function () { // called after Module is ready
142
				Module.pd = new Module.Pd(); // instantiate Pd object
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
143
144
145
146
147
				// Remove modal after everything loaded
				$(document).ready(function(){
					$("#loading-modal").modal('hide');
				});

148
				if (typeof Module.pd != "object") {
149
					alert("Pd: failed to instantiate pd object");
Zack Lee's avatar
Zack Lee committed
150
151
152
					console.error("Pd: failed to instantiate pd object");
					Module.mainExit();
					return;
153
154
155
				}
				var pd = Module.pd;
				pd.setNoGui(false); // set to true if you don't use the pd's gui
156
157
158
				// initializing webapp
				initialize_webapp()

159
160
161
162
163

				// create an AudioContext
				var isWebAudioSupported = false;
				var audioContextList = [];
				(function () {
Zack Lee's avatar
Zack Lee committed
164
165
166
167
168
169
170
171
172
173
174
					var AudioContext = self.AudioContext || self.webkitAudioContext || false;
					if (AudioContext) {
						isWebAudioSupported = true;
						self.AudioContext = new Proxy(AudioContext, {
							construct(target, args) {
								var result = new target(...args);
								audioContextList.push(result);
								return result;
							}
						});
					}
175
176
				})();
				if (isWebAudioSupported) {
Zack Lee's avatar
Zack Lee committed
177
					console.log("Audio: successfully enabled");
178
179
				}
				else {
Zack Lee's avatar
Zack Lee committed
180
181
182
183
					alert("The Web Audio API is not supported in this browser.");
					console.error("Audio: failed to use the web audio");
					Module.mainExit();
					return;
184
				}
Zack Lee's avatar
Zack Lee committed
185

186
187
188
				// check if the web midi library exists and is supported
				var isWebMidiSupported = false;
				if (typeof WebMidi != "object") {
189
190
					alert("Midi: failed to find the 'WebMidi' object");
					console.error("Midi: failed to find the 'WebMidi' object");
Zack Lee's avatar
Zack Lee committed
191
192
					Module.mainExit();
					return;
193
				}
Zack Lee's avatar
Zack Lee committed
194

195
196
197
				// array of enabled midi device ids (without duplicates)
				var midiInIds = [];
				var midiOutIds = [];
Zack Lee's avatar
Zack Lee committed
198

199
200
201
				// 10 input, 10 output device numbers to send with "pd midi-dialog"
				// 0: no device, 1: first available device, 2: second available device...
				var midiarr = [];
Zack Lee's avatar
Zack Lee committed
202

203
204
				// enable midi
				WebMidi.enable(function (err) {
Zack Lee's avatar
Zack Lee committed
205
206
					if (err) {
						// if the browser doesn't support web midi, one can still use pd without it
207
						alert("The Web MIDI API is not supported in this browser.\nPlease check: https://github.com/djipco/webmidi#browser-support");
Zack Lee's avatar
Zack Lee committed
208
						console.error("Midi: failed to enable midi", err);
209
					}
Zack Lee's avatar
Zack Lee committed
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
					else {
						isWebMidiSupported = true;
						console.log("Midi: successfully enabled");

						// select all available input/output devices as default
						midiInIds = [];
						midiOutIds = [];
						for (var i = 0; i < WebMidi.inputs.length; i++) {
							midiInIds.push(WebMidi.inputs[i].id);
						}
						for (var i = 0; i < WebMidi.outputs.length; i++) {
							midiOutIds.push(WebMidi.outputs[i].id);
						}
						midiarr = [];
						for (var i = 0; i < 10; i++) {
							var devno = i < midiInIds.length ? i + 1 : 0;
							midiarr.push(devno);
						}
						for (var i = 0; i < 10; i++) {
							var devno = i < midiOutIds.length ? i + 1 : 0;
							midiarr.push(devno);
						}
						// called whenever input/output devices connection status changes
						function onConnectionChanged() {
							console.log("Midi: connection status changed");
							pdbundle.pdgui.pdsend("pd midi-dialog", midiarr.join(" ")); // send message to pd
						}
						// make sure we get only one callback at a time
						var timerId;
						WebMidi.addListener("connected", function (e) {
							clearTimeout(timerId);
							timerId = setTimeout(() => onConnectionChanged(), 100);
						});
						WebMidi.addListener("disconnected", function (e) {
							clearTimeout(timerId);
							timerId = setTimeout(() => onConnectionChanged(), 100);
						});
247
248
					}
				}, false); // not use sysex
Zack Lee's avatar
Zack Lee committed
249

250
251
				// reinit pd (called by "pd audio-dialog" message)
				Module.Pd.reinit = function (newinchan, newoutchan, newrate) {
Zack Lee's avatar
Zack Lee committed
252
253
254
255
256
257
258
259
260
261
262
					if (pd.init(newinchan, newoutchan, newrate, pd.getTicksPerBuffer())) {

						// print obtained settings
						console.log("Pd: successfully reinitialized");
						console.log("Pd: audio input channels: " + pd.getNumInChannels());
						console.log("Pd: audio output channels: " + pd.getNumOutChannels());
						console.log("Pd: audio sample rate: " + pd.getSampleRate());
						console.log("Pd: audio ticks per buffer: " + pd.getTicksPerBuffer());
					}
					else {
						// failed to reinit pd
263
						alert("Pd: failed to reinitialize pd");
Zack Lee's avatar
Zack Lee committed
264
265
266
						console.error("Pd: failed to reinitialize pd");
						Module.mainExit();
					}
267
				}
Zack Lee's avatar
Zack Lee committed
268

269
270
271
272
				// open midi (called by "pd midi-dialog" message)
				// receives input/output arrays of only selected devices
				// 0: first available device, 1: second available device...
				Module.Pd.openMidi = function (midiinarr, midioutarr) {
Zack Lee's avatar
Zack Lee committed
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
					if (!isWebMidiSupported)
						return;

					// if the selected device doesn't exist, use first available device instead
					midiinarr = midiinarr.map(item => item >= WebMidi.inputs.length || item < 0 ? 0 : item);
					midioutarr = midioutarr.map(item => item >= WebMidi.outputs.length || item < 0 ? 0 : item);

					// save this settings so we can check again later when connection status changes 
					midiarr = [];
					for (var i = 0; i < 10; i++) {
						var devno = i < midiinarr.length ? midiinarr[i] + 1 : 0;
						midiarr.push(devno);
					}
					for (var i = 0; i < 10; i++) {
						var devno = i < midioutarr.length ? midioutarr[i] + 1 : 0;
						midiarr.push(devno);
					}
					// remove duplicates and convert device numbers to ids
					midiinarr = Array.from(new Set(midiinarr));
					midioutarr = Array.from(new Set(midioutarr));
					midiInIds = midiinarr.map(item => WebMidi.inputs[item].id);
					midiOutIds = midioutarr.map(item => WebMidi.outputs[item].id);

					// print all selected devices to the console
					for (var i = 0; i < midiInIds.length; i++) {
						var input = WebMidi.getInputById(midiInIds[i]);
						console.log("Midi: input" + (i + 1) + ": " + input.name);
					}
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						console.log("Midi: output" + (i + 1) + ": " + output.name);
					}
					// receive midi messages from WebMidi and forward them to pd input
					function receiveNoteOn(e) {
						pd.sendNoteOn(e.channel, e.note.number, e.rawVelocity);
					}

					function receiveNoteOff(e) {
						pd.sendNoteOn(e.channel, e.note.number, 0);
					}

					function receiveControlChange(e) {
						pd.sendControlChange(e.channel, e.controller.number, e.value);
					}

					function receiveProgramChange(e) {
						pd.sendProgramChange(e.channel, e.value + 1);
					}

					function receivePitchBend(e) {
						// [bendin] takes 0 - 16383 while [bendout] returns -8192 - 8192
						pd.sendPitchBend(e.channel, e.value * 8192 + 8192);
					}

					function receiveAftertouch(e) {
						pd.sendAftertouch(e.channel, e.value * 127);
					}

					function receivePolyAftertouch(e) {
						pd.sendPolyAftertouch(e.channel, e.note.number, e.value * 127);
					}

					for (var i = 0; i < midiInIds.length; i++) {
						var input = WebMidi.getInputById(midiInIds[i]);
						if (input) {
							input.removeListener(); // remove all added listeners
							input.addListener("noteon", "all", receiveNoteOn);
							input.addListener("noteoff", "all", receiveNoteOff);
							input.addListener("controlchange", "all", receiveControlChange);
							input.addListener("programchange", "all", receiveProgramChange);
							input.addListener("pitchbend", "all", receivePitchBend);
							input.addListener("channelaftertouch", "all", receiveAftertouch);
							input.addListener("keyaftertouch", "all", receivePolyAftertouch);
						}
347
348
					}
				}
Zack Lee's avatar
Zack Lee committed
349

350
351
				// get midi in device name
				Module.Pd.getMidiInDeviceName = function (devno) {
Zack Lee's avatar
Zack Lee committed
352
353
354
355
356
357
358
359
360
361
					if (!isWebMidiSupported)
						return;
					if (devno >= WebMidi.inputs.length || devno < 0) {
						devno = 0;
					}
					var name = WebMidi.inputs[devno].name;
					var lengthBytes = lengthBytesUTF8(name) + 1;
					var stringOnWasmHeap = _malloc(lengthBytes);
					stringToUTF8(name, stringOnWasmHeap, lengthBytes);
					return stringOnWasmHeap;
362
				}
Zack Lee's avatar
Zack Lee committed
363

364
365
				// get midi out device name
				Module.Pd.getMidiOutDeviceName = function (devno) {
Zack Lee's avatar
Zack Lee committed
366
367
368
369
370
371
372
373
374
375
					if (!isWebMidiSupported)
						return;
					if (devno >= WebMidi.inputs.length || devno < 0) {
						devno = 0;
					}
					var name = WebMidi.outputs[devno].name;
					var lengthBytes = lengthBytesUTF8(name) + 1;
					var stringOnWasmHeap = _malloc(lengthBytes);
					stringToUTF8(name, stringOnWasmHeap, lengthBytes);
					return stringOnWasmHeap;
376
				}
Zack Lee's avatar
Zack Lee committed
377

378
379
				// receive gui commands (only called in gui mode)
				Module.Pd.receiveCommandBuffer = function (data) {
Zack Lee's avatar
Zack Lee committed
380
381
382
					var command_buffer = {
						next_command: ""
					};
383
					pdbundle.pdgui.perfect_parser(data, command_buffer);
384
				}
Zack Lee's avatar
Zack Lee committed
385

386
387
				// receive print messages (only called in no gui mode)
				Module.Pd.receivePrint = function (s) {
Zack Lee's avatar
Zack Lee committed
388
					console.log(s);
389
				}
Zack Lee's avatar
Zack Lee committed
390

391
392
393
				// receive from pd's subscribed sources
				Module.Pd.receiveBang = function (source) {
				}
Zack Lee's avatar
Zack Lee committed
394

395
396
				Module.Pd.receiveFloat = function (source, value) {
				}
Zack Lee's avatar
Zack Lee committed
397

398
399
				Module.Pd.receiveSymbol = function (source, symbol) {
				}
Zack Lee's avatar
Zack Lee committed
400

401
402
				Module.Pd.receiveList = function (source, list) {
				}
Zack Lee's avatar
Zack Lee committed
403

404
405
				Module.Pd.receiveMessage = function (source, symbol, list) {
				}
Zack Lee's avatar
Zack Lee committed
406

407
408
				// receive midi messages from pd and forward them to WebMidi output
				Module.Pd.receiveNoteOn = function (channel, pitch, velocity) {
Zack Lee's avatar
Zack Lee committed
409
410
411
412
413
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.playNote(pitch, channel, { rawVelocity: true, velocity: velocity });
						}
414
415
					}
				}
Zack Lee's avatar
Zack Lee committed
416

417
				Module.Pd.receiveControlChange = function (channel, controller, value) {
Zack Lee's avatar
Zack Lee committed
418
419
420
421
422
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.sendControlChange(controller, value, channel);
						}
423
424
					}
				}
Zack Lee's avatar
Zack Lee committed
425

426
				Module.Pd.receiveProgramChange = function (channel, value) {
Zack Lee's avatar
Zack Lee committed
427
428
429
430
431
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.sendProgramChange(value, channel);
						}
432
433
					}
				}
Zack Lee's avatar
Zack Lee committed
434

435
				Module.Pd.receivePitchBend = function (channel, value) {
Zack Lee's avatar
Zack Lee committed
436
437
438
439
440
441
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							// [bendin] takes 0 - 16383 while [bendout] returns -8192 - 8192
							output.sendPitchBend(value / 8192, channel);
						}
442
443
					}
				}
Zack Lee's avatar
Zack Lee committed
444

445
				Module.Pd.receiveAftertouch = function (channel, value) {
Zack Lee's avatar
Zack Lee committed
446
447
448
449
450
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.sendChannelAftertouch(value / 127, channel);
						}
451
452
					}
				}
Zack Lee's avatar
Zack Lee committed
453

454
				Module.Pd.receivePolyAftertouch = function (channel, pitch, value) {
Zack Lee's avatar
Zack Lee committed
455
456
457
458
459
					for (var i = 0; i < midiOutIds.length; i++) {
						var output = WebMidi.getOutputById(midiOutIds[i]);
						if (output) {
							output.sendKeyAftertouch(pitch, channel, value / 127);
						}
460
461
					}
				}
Zack Lee's avatar
Zack Lee committed
462

463
464
				Module.Pd.receiveMidiByte = function (port, byte) {
				}
Zack Lee's avatar
Zack Lee committed
465

466
467
468
469
470
				// default audio settings
				var numInChannels = 0; // supported values: 0, 1, 2
				var numOutChannels = 2; // supported values: 1, 2
				var sampleRate = 44100; // might change depending on browser/system
				var ticksPerBuffer = 32; // supported values: 4, 8, 16, 32, 64, 128, 256
Zack Lee's avatar
Zack Lee committed
471

472
473
				// open audio devices, init pd
				if (pd.init(numInChannels, numOutChannels, sampleRate, ticksPerBuffer)) {
Zack Lee's avatar
Zack Lee committed
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495

					// print obtained settings
					console.log("Pd: successfully initialized");
					console.log("Pd: audio input channels:", pd.getNumInChannels());
					console.log("Pd: audio output channels:", pd.getNumOutChannels());
					console.log("Pd: audio sample rate:", pd.getSampleRate());
					console.log("Pd: audio ticks per buffer:", pd.getTicksPerBuffer());

					// add internals/externals help/search paths
					var helpPath = "purr-data/doc/5.reference";
					var extPath = "purr-data/extra";
					pd.addToHelpPath(helpPath);
					pd.addToSearchPath(extPath);
					pd.addToHelpPath(extPath);
					var dir = FS.readdir(extPath);
					for (var i = 0; i < dir.length; i++) {
						var item = dir[i];
						if (item.charAt(0) != ".") {
							var path = extPath + "/" + item;
							pd.addToSearchPath(path); // externals can be created without path prefix
							pd.addToHelpPath(path);
						}
496
497
498
					}
				}
				else { // failed to init pd
499
					alert("Pd: failed to initialize pd");
Zack Lee's avatar
Zack Lee committed
500
501
					console.error("Pd: failed to initialize pd");
					Module.mainExit();
502
				}
Zack Lee's avatar
Zack Lee committed
503
504
505
506
			}
			, mainLoop: function () { // called every frame (use for whatever)
			}
			, mainExit: function () { // this won't be called from emscripten
507
508
				console.error("quiting emscripten...");
				if (typeof Module.pd == "object") {
Zack Lee's avatar
Zack Lee committed
509
510
511
					Module.pd.clear(); // clear pd, close audio devices
					Module.pd.unsubscribeAll(); // unsubscribe all subscribed sources
					Module.pd.delete(); // quit SDL, emscripten
512
513
				}
				if (typeof WebMidi == "object") {
Zack Lee's avatar
Zack Lee committed
514
					WebMidi.disable(); // disable all midi devices
515
516
				}
			}
Zack Lee's avatar
Zack Lee committed
517
		};
518
519
520


			// Add pd module to pd
Zack Lee's avatar
Zack Lee committed
521
522
523
524
525
526
527
528
	</script>

	<!-- Pd Bundle -->
	<script src="dist/bundle.js"></script>

	<script src="index.js"></script>

	<script src="/utils/console_search.js"></script>
529

Zack Lee's avatar
Zack Lee committed
530
531
	<!-- Emscripten backend -->
	<script src="main.js"></script>
532

Zack Lee's avatar
Zack Lee committed
533
534
535
536
	<!-- Utils -->
	<script src="utils/actions.js"></script>
	<!-- <script src="utils/audio_ctx.js"></script>  -->
	<script src="utils/console_find.js"></script>
537

Zack Lee's avatar
Zack Lee committed
538
539
540
	<!-- Components -->
	<script src="components/menu/menu.js"></script>
	<script src="components/canvas/canvas.js"></script>
541
542


Zack Lee's avatar
Zack Lee committed
543
544
545
	<!-- Libs -->
	<!-- Webmidi -->
	<script src="libs/webmidi/webmidi.min.js"></script>
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
546

Zack Lee's avatar
Zack Lee committed
547
	<!-- jQuery -->
Prakhar Agarwal's avatar
Prakhar Agarwal committed
548
	<script src="libs/jquery/jquery-3.6.0.min.js"></script>
549

Zack Lee's avatar
Zack Lee committed
550
551
	<!-- jQuery UI -->
	<script src="libs/jquery/jquery-ui.js"></script>
552

Zack Lee's avatar
Zack Lee committed
553
554
	<!-- Fontawesome -->
	<script src="libs/fa/fontawesome_1b8a796d74.js"></script>
555
556


Zack Lee's avatar
Zack Lee committed
557
	<!-- Bootstrap -->
Prakhar Agarwal's avatar
Prakhar Agarwal committed
558
	<script src="libs/bootstrap/popper.min.js"></script>
559

Prakhar Agarwal's avatar
Prakhar Agarwal committed
560
	<script src="libs/bootstrap/bootstrap.min.js"></script>
561

Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
562
563
564
	<!-- Show modal on load -->
	<script>
		$(document).ready(function(){
565
			$("#loading-modal").modal('show', {backdrop: 'static', keyboard: false},);
Hugo Neves de Carvalho's avatar
Hugo Neves de Carvalho committed
566
567
		});
	</script>
568
569
</body>

Akash Negi's avatar
Akash Negi committed
570
</html>