├── .gitignore
├── README.md
├── css
├── reset.css
└── style.css
├── fonts
└── roboto_regular.typeface.json
├── gitcommandon.txt
├── images
├── glow.png
└── glow_test.png
├── index.html
├── js
├── animator.js
├── camerahelper.js
├── classes
│ ├── class.note.js
│ ├── class.stars.js
│ └── class.vector.js
├── colors.js
├── midi
│ ├── midifile.js
│ ├── midiloader.js
│ └── stream.js
├── midirenderer.js
├── physics.js
├── three.min.js
├── three.r77.min.js
└── visualize.js
└── midi
├── deb_clai.mid
├── furelise.mid
├── furelise.mp3
├── got.mid
├── got.mp3
├── pokemon.mid
├── pokemon.mp3
├── pokemon2.mid
├── river.mid
├── river.mp3
├── river2.mid
├── river2.mp3
├── test.mid
├── test2.mid
├── test3.mid
├── test4.mid
└── test4.mp3
/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/.gitignore
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DMA - 3D MIDI visualizer
2 | 3D MIDI visualization for web using Three.js and WebGL.
3 | Built using [jasmid](https://github.com/gasman/jasmid) by [gasman](https://github.com/gasman).
4 |
5 | Developed by [miii](https://github.com/miii), [rcedermalm](https://github.com/rcedermalm), [erikssonjohan](https://github.com/erikssonjohan), [felixgronborg](https://github.com/felixgronborg) & [veromq](https://github.com/veromq).
6 |
7 | Screenshots:
8 |
9 |
10 |
11 | ---
12 | 
13 |
--------------------------------------------------------------------------------
/css/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | overflow: hidden;
3 | }
--------------------------------------------------------------------------------
/gitcommandon.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/gitcommandon.txt
--------------------------------------------------------------------------------
/images/glow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/images/glow.png
--------------------------------------------------------------------------------
/images/glow_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/images/glow_test.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 3D visualization
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/js/animator.js:
--------------------------------------------------------------------------------
1 | var Animator = function() {
2 | var container;
3 | var camera, scene, renderer;
4 | var light;
5 |
6 | var mouseX = 0, mouseY = 0;
7 | var mouseXnorm = 0, mouseYnorm = 0;
8 | var cameraRotX = 0, cameraRotY = 0;
9 | var cameraHelper = null;
10 |
11 | var windowHalfX = window.innerWidth / 2;
12 | var windowHalfY = window.innerHeight / 2;
13 |
14 | // Object3D ("Group") nodes and Mesh nodes
15 | var sceneRoot = new THREE.Group();
16 | var viewRotation = new THREE.Group();
17 | var translationOrbit = new THREE.Group();
18 | var objectMesh;
19 | var textMesh;
20 |
21 | var mouseDown = false;
22 |
23 | /////////////////////////////
24 |
25 | var sensitivity = 3;
26 | var nearestDistance = 3;
27 |
28 | var cameraFOV = 110;
29 |
30 | /////////////////////////////
31 |
32 | var objects = [];
33 |
34 | /////////////////////////////
35 |
36 | var midiRenderActive = true;
37 |
38 | /////////////////////////////
39 |
40 | function _onWindowResize() {
41 | windowHalfX = window.innerWidth / 2;
42 | windowHalfY = window.innerHeight / 2;
43 |
44 | camera.aspect = window.innerWidth / window.innerHeight;
45 | camera.updateProjectionMatrix();
46 |
47 | renderer.setSize( window.innerWidth, window.innerHeight );
48 | renderer.render(scene, camera);
49 | }
50 |
51 | function _onMouseDown(event) {
52 | mouseDown = true;
53 |
54 | cameraRotX = viewRotation.rotation.x;
55 | cameraRotY = viewRotation.rotation.y;
56 |
57 | var mousePos = _getMousePos(event);
58 | mouseXnorm = mousePos.x;
59 | mouseYnorm = mousePos.y;
60 | }
61 |
62 | function _onMouseUp() {
63 | mouseDown = false;
64 |
65 | cameraRotX = viewRotation.rotation.x;
66 | cameraRotY = viewRotation.rotation.y;
67 | }
68 |
69 | function _onMouseMove(event) {
70 | var mousePos = _getMousePos(event);
71 | mouseX = mousePos.x;
72 | mouseY = mousePos.y;
73 | }
74 |
75 | function _onScroll(event) {
76 | if (event.wheelDeltaY > 0) {
77 | // User scrolled up
78 | if (camera.position.z > nearestDistance)
79 | camera.position.z -= 1;
80 | } else {
81 | // User scrolled down
82 | camera.position.z += 1;
83 | }
84 | }
85 |
86 | function _getMousePos(event) {
87 | // mouseX, mouseY are in the range [-1, 1]
88 | return {
89 | x: (event.clientX - windowHalfX) / windowHalfX,
90 | y: (event.clientY - windowHalfY) / windowHalfY
91 | }
92 | }
93 |
94 |
95 | function _onKeyDown(event){
96 |
97 | switch(event.keyCode){
98 | case 38: {
99 | translationOrbit.position.z+=0.3;
100 | break;
101 | }
102 | case 40: {
103 | translationOrbit.position.z-=0.3;
104 | break;
105 | }
106 | case 49: {
107 | cameraHelper.animateTo({
108 | x: 0,
109 | y: 0,
110 | z: 0,
111 | zoom: 35,
112 | });
113 | break;
114 | }
115 | case 50: {
116 | cameraHelper.animateTo({
117 | x: 0,
118 | y: -Math.PI/2,
119 | z: 20,
120 | zoom: 40,
121 | });
122 | break;
123 | }
124 | case 51: {
125 | cameraHelper.animateTo({
126 | x: Math.PI/4,
127 | y: -Math.PI/4,
128 | z: 0,
129 | zoom: 40,
130 | });
131 | break;
132 | }
133 |
134 | case 32:
135 | viewRotation.remove(textMesh);
136 | break;
137 | }
138 |
139 | }
140 |
141 | function onNoteAdded(note) {
142 | note.spawn();
143 | translationOrbit.add(note.getMesh());
144 | objects.push(note);
145 | window.Physics.resetSlowdown();
146 | }
147 |
148 | function onMidiRendererCompleted() {
149 | midiRenderActive = false;
150 | }
151 |
152 | ///////////////////////////
153 |
154 | function init() {
155 |
156 | container = document.getElementById('container');
157 |
158 | camera = new THREE.PerspectiveCamera(70, window.innerWidth/window.innerHeight, 0.1, 10000);
159 |
160 | scene = new THREE.Scene();
161 |
162 | //scene.fog = new THREE.FogExp2(0x000000, 0.02);
163 | var amb = new THREE.AmbientLight(0xFFFFFF);
164 | scene.add(amb);
165 |
166 | // Mesh
167 | var geometryBox = new THREE.BoxGeometry(1, 1, 1);
168 | var materialBox = new THREE.MeshBasicMaterial();
169 | materialBox.wireframe = true;
170 | objectMesh = new THREE.Mesh(geometryBox, materialBox);
171 |
172 | // Top-level node
173 | scene.add(sceneRoot);
174 |
175 | // Sun branch
176 | sceneRoot.add(viewRotation);
177 | viewRotation.add(translationOrbit);
178 | //viewRotation.add(objectMesh);
179 |
180 | light = new THREE.DirectionalLight(0xffffff, 1);
181 | light.position.set(0, 0, 1);
182 | light.target.position.set(0, 0, 0);
183 | light.intensity = 1.5;
184 | viewRotation.add(light);
185 |
186 | //var stars = new Stars().create().getParticleSystem();
187 | //viewRotation.add(stars);
188 |
189 | cameraHelper = new CameraHelper();
190 | cameraHelper.setOrbit(viewRotation).setTransOrbit(translationOrbit).setCamera(camera);
191 |
192 | var radius = 20;
193 | segments = 64;
194 | material = new THREE.LineBasicMaterial({color: 0x333333});
195 | geometry = new THREE.CircleGeometry( radius, segments );
196 |
197 | // Remove center vertex
198 | geometry.vertices.shift();
199 |
200 | translationOrbit.add( new THREE.Line( geometry, material ) );
201 |
202 | var loader = new THREE.FontLoader();
203 | loader.load('fonts/roboto_regular.typeface.json', function (font) {
204 | var textMaterial = new THREE.MeshLambertMaterial({
205 | color: 0x888888
206 | });
207 | var textGeometry = new THREE.TextGeometry("Press space to start", {
208 | font: font,
209 | size: 2,
210 | height: 0.1
211 | });
212 | textMesh = new THREE.Mesh(textGeometry, textMaterial);
213 | textMesh.position.x = -12;
214 | viewRotation.add(textMesh);
215 | });
216 |
217 | renderer = new THREE.WebGLRenderer();
218 | renderer.setClearColor(0x000000);
219 | renderer.setPixelRatio(window.devicePixelRatio);
220 | renderer.setSize( window.innerWidth, window.innerHeight);
221 | container.appendChild(renderer.domElement);
222 |
223 | window.addEventListener('resize', _onWindowResize, false);
224 | window.addEventListener('mousedown', _onMouseDown, false);
225 | window.addEventListener('mouseup', _onMouseUp, false);
226 | window.addEventListener('mousemove', _onMouseMove, false);
227 | window.addEventListener('mousewheel', _onScroll, false);
228 | window.addEventListener('keydown', _onKeyDown, false);
229 |
230 | // Set up the camera
231 | camera.position.x = 0;
232 | camera.position.y = 0;
233 | camera.position.z = 30;
234 | camera.lookAt( scene.position );
235 |
236 | return this;
237 | }
238 |
239 | function render(frame) {
240 |
241 | // Perform animations
242 | if (mouseDown) {
243 | viewRotation.rotation.x = cameraRotX + (mouseY - mouseYnorm) * sensitivity;
244 | viewRotation.rotation.y = cameraRotY + (mouseX - mouseXnorm) * sensitivity;
245 | }
246 |
247 | for (i = 0; i < objects.length; i++) {
248 | objects[i].animate(frame);
249 |
250 | if (midiRenderActive)
251 | objects[i].getMesh().position.z -= 1/15;
252 | }
253 |
254 | // Render the scene
255 | renderer.render(scene, camera);
256 | }
257 |
258 | function renderOnce() {
259 | renderer.render(scene, camera);
260 | }
261 |
262 | return {
263 | init: init,
264 | render: render,
265 | onNoteAdded: onNoteAdded,
266 | onMidiRendererCompleted: onMidiRendererCompleted,
267 | renderOnce: renderOnce
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/js/camerahelper.js:
--------------------------------------------------------------------------------
1 | var CameraHelper = function() {
2 | var camera = null;
3 | var orbit = null;
4 | var transOrbit = null;
5 |
6 | function setOrbit(orb) {
7 | orbit = orb;
8 |
9 | return this;
10 | }
11 |
12 | function setCamera(cam) {
13 | camera = cam;
14 |
15 | return this;
16 | }
17 |
18 | function setTransOrbit(orb) {
19 | transOrbit = orb;
20 |
21 | return this;
22 | }
23 |
24 | function animateTo(options) {
25 | x = options.x != null ? options.x : orbit.rotation.x;
26 | y = options.y != null ? options.y : orbit.rotation.y;
27 | camz = options.zoom != null ? options.zoom : camera.position.z;
28 | z = options.z != null ? options.z : transOrbit.position.z;
29 |
30 | var deltaOrbitRotation = {
31 | x: x - orbit.rotation.x,
32 | y: y - orbit.rotation.y,
33 | z: z - transOrbit.position.z
34 | };
35 | var deltaCamPosition = {
36 | z: camz - camera.position.z
37 | }
38 |
39 | var timevar = 200;
40 | var steps = {
41 | x: deltaOrbitRotation.x / timevar,
42 | y: deltaOrbitRotation.y / timevar,
43 | z: deltaOrbitRotation.z / timevar,
44 | camz: deltaCamPosition.z / timevar
45 | }
46 |
47 | _animate({x: x, y: y, z: z}, {z: camz}, steps);
48 | }
49 |
50 | function _animate(orbitDest, camDest, steps) {
51 |
52 | orbit.rotation.x += steps.x;
53 | orbit.rotation.y += steps.y;
54 | transOrbit.position.z += steps.z;
55 | camera.position.z += steps.camz;
56 |
57 | var done = {
58 | x: false,
59 | y: false,
60 | z: false,
61 | camz: false
62 | }
63 |
64 | if (Math.abs(orbit.rotation.x - orbitDest.x) <= 2 * Math.abs(steps.x)) {
65 | orbit.rotation.x = orbitDest.x;
66 | done.x = true;
67 | }
68 |
69 | if (Math.abs(orbit.rotation.y - orbitDest.y) <= 2 * Math.abs(steps.y)) {
70 | orbit.rotation.y = orbitDest.y;
71 | done.y = true;
72 | }
73 |
74 | if (Math.abs(camera.position.z - camDest.z) <= 2 * Math.abs(steps.camz)) {
75 | camera.position.z = camDest.z;
76 | done.camz = true;
77 | }
78 |
79 | if (Math.abs(transOrbit.position.z - orbitDest.z) <= 2 * Math.abs(steps.z)) {
80 | transOrbit.position.z = orbitDest.z;
81 | done.z = true;
82 | }
83 |
84 | if (!done.x || !done.y || !done.camz || !done.z)
85 | setTimeout(function() {
86 | _animate(orbitDest, camDest, steps);
87 | }, (1/60));
88 | }
89 |
90 | return {
91 | setCamera: setCamera,
92 | setOrbit: setOrbit,
93 | setTransOrbit: setTransOrbit,
94 | animateTo: animateTo
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/js/classes/class.note.js:
--------------------------------------------------------------------------------
1 | var Note = function() {
2 |
3 | var mesh;
4 |
5 | var startFrame = null;
6 | var noteID = null;
7 | var noteLength = null;
8 |
9 | var mass = null;
10 | var velocity = null;
11 |
12 | function spawn() {
13 | //console.log('Note spawned', noteID);
14 | //console.log('Note length', noteLength);
15 |
16 | // Create sphere, colors are defined here
17 | var sphere = new THREE.SphereGeometry(0.5, 24);
18 |
19 | var ColorOfSphere = "#" + note_colours[noteID];
20 | //var ColorOfSphere = window.Colors.getColorByNoteID(noteID);
21 | var color = new THREE.Color(ColorOfSphere);
22 | var material = new THREE.MeshLambertMaterial({color: color.getHex()});
23 |
24 | //material.wireframe = true;
25 |
26 | mesh = new THREE.Mesh(sphere, material);
27 |
28 | //GLOW
29 | var glowMaterial = new THREE.SpriteMaterial( {
30 | map: window.map,
31 | color: color.getHex(),
32 | transparent: false,
33 | blending: THREE.AdditiveBlending
34 | } );
35 |
36 | var sprite = new THREE.Sprite( glowMaterial );
37 | sprite.scale.set(2.5, 2.5, 1);
38 | mesh.add(sprite);
39 | /////
40 |
41 | var x = 1 + 2 * noteLength / 8192 + Math.random();
42 | var x = 10;
43 |
44 | // Object mass (atm: random values between 0-5)
45 | mass = x;
46 |
47 | // Initial velocity
48 | velocity = new Vector().create(20, 20);
49 |
50 | // Object start position
51 | /*var posID = noteID%12;
52 | mesh.position.x = note_positions[posID][0];
53 | mesh.position.y = note_positions[posID][1];*/
54 |
55 | var period = 4;
56 | var radius = 20;
57 | mesh.position.x = radius * Math.cos(noteID / 127 * (period * 2 * Math.PI));
58 | mesh.position.y = radius * Math.sin(noteID / 127 * (period * 2 * Math.PI));
59 |
60 | return this;
61 | }
62 |
63 |
64 |
65 |
66 | // Used by midirender.js
67 | function setNoteID(id) {
68 | noteID = id;
69 |
70 | return this;
71 | }
72 |
73 | // Used by midirender.js
74 | function setNoteLength(length) {
75 | noteLength = length;
76 |
77 | return this;
78 | }
79 |
80 | // Used by animator.js
81 | function getMesh() {
82 | return mesh;
83 | }
84 |
85 | // Used by physics.js
86 | function getPosition() {
87 | return mesh.position;
88 | }
89 |
90 | // Used by physics.js
91 | function getVelocity() {
92 | return velocity;
93 | }
94 |
95 | // Used by physics.js
96 | function getMass() {
97 | return mass;
98 | }
99 |
100 | // Used by animator.js
101 | function animate(frame) {
102 |
103 | window.Physics.updateSlowdownCoefficients(frame);
104 |
105 | // Calculate acceleration and new velocity
106 | window.Physics.affect(this);
107 |
108 | // Translate the object
109 | window.Physics.render(this);
110 |
111 | }
112 |
113 | return {
114 | spawn: spawn,
115 | setNoteID: setNoteID,
116 | setNoteLength: setNoteLength,
117 | getMesh: getMesh,
118 | getPosition: getPosition,
119 | getVelocity: getVelocity,
120 | getMass: getMass,
121 | animate: animate
122 | };
123 |
124 | };
125 |
126 |
127 | window.note_colours = ['5e0200', '5a1900', '8e5102', '8f5103', 'e59b03', '424e00', '0e3d00', '1a502a', '003f4f', '281151', '4d0151', '51002c',
128 | '5e0200', '5a1900', '8e5102', '8f5103', 'e59b03', '424e00', '0e3d00', '1a502a', '003f4f', '281151', '4d0151', '51002c',
129 | '5e0200', '5a1900', '8e5102', '8f5103', 'e59b03', '424e00', '0e3d00', '1a502a', '003f4f', '281151', '4d0151', '51002c',
130 | '5e0200', '5a1900', '8e5102', '8f5103', 'e59b03', '424e00', '0e3d00', '1a502a', '003f4f', '281151', '4d0151', '51002c',
131 | '6a0201', '6d1e01', '8d2c00', 'a05e0a', 'e8a310', '5c6d01', '145801', '256c3b', '005b6a', '351a6c', '6e0173', '770144',
132 | '690401', '8a2a01', 'a13a00', 'bf7b14', 'ecb428', '7e9101', '248302', '3d9758', '018191', '502b94', '910196', 'a20265',
133 | '910301', 'b14003', 'c55900', 'd9982a', 'f1c851', 'adc002', '3eb307', '63c383', '03b8c4', '8052c7', 'c105c5', 'c8068a',
134 | 'dd0c07', 'd6640b', 'e88400', 'edb342', 'fad364', 'c9db0f', '61d614', '8de2ab', '65d6df', 'a678e2', 'd911de', 'e112ac',
135 | 'f21c03', 'e9820f', 'fbb002', 'f6c458', 'fcdc7f', 'e1ef18', '8ef034', 'acf1c6', '18ebf1', 'c098f1', 'efb2f1', 'f115c6',
136 | 'fd361c', 'f79d1e', 'ffbe21', 'fbd06d', 'fce090', 'edf826', 'abfa48', 'bef9d5', '00f5fa', 'd1aefa', 'f73bfa', 'fa2ad7',
137 | 'ff8061', 'fda924', 'ffc447', 'fcd575', 'fde396', 'f3fd2a', 'a5ff44', 'c2fedb', '15fafe', 'd7b3fe', 'fc33fe', 'fe29d8'];
138 |
139 |
140 |
141 | window.note_positions = [[3,3], [0,3], [3,0], [3,-3], [-3,3], [-3,-3], [0, -3], [-3, 0], [4, 4], [4,0], [0,4], [-4,-4]];
142 |
--------------------------------------------------------------------------------
/js/classes/class.stars.js:
--------------------------------------------------------------------------------
1 | var Stars = function(){
2 |
3 | var particleSystem = null;
4 |
5 | function create() {
6 | var particleCount = 1800;
7 | var particleMap = new THREE.TextureLoader().load("images/particle.png");
8 |
9 | particles = new THREE.Geometry();
10 | pMaterial = new THREE.PointsMaterial({
11 | map: particleMap,
12 | color: 0xFFFFFF,
13 | size: 20,
14 | blending: THREE.AdditiveBlending,
15 | transparent: true
16 | });
17 |
18 | // now create the individual particles
19 | for(var p = 0; p < particleCount; p++) {
20 |
21 | // create a particle with random
22 | // position values, -250 -> 250
23 | var pX = Math.random() * 10 - 5;
24 | pY = Math.random() * 10 - 5;
25 | pZ = Math.random() * 10 - 5;
26 | particle = new THREE.Vertex(
27 | new THREE.Vector3(pX, pY, pZ)
28 | );
29 | // create a velocity vector
30 | particle.velocity = new THREE.Vector3(
31 | 0, // x
32 | -Math.random(), // y
33 | 0); // z
34 |
35 | // add it to the geometry
36 | particles.vertices.push(particle);
37 |
38 | }
39 |
40 | // create the particle system
41 | particleSystem = new THREE.Points(particles, pMaterial);
42 | particleSystem.sortParticles = true;
43 |
44 | return this;
45 | }
46 |
47 | function getParticleSystem(){
48 | return particleSystem;
49 | }
50 |
51 | function animate(frame) {
52 | // add some rotation to the system
53 | particleSystem.rotation.y += 0.001;
54 |
55 | var pCount = particleCount;
56 | while(pCount--) {
57 | // get the particle
58 | var particle = particles.vertices[pCount];
59 |
60 | // check if we need to reset
61 | if(particle.position.y < -200) {
62 | particle.position.y = 200;
63 | particle.velocity.y = 0;
64 | }
65 |
66 | // update the velocity
67 | particle.velocity.y += Math.random() * .0001;
68 |
69 | // and the position
70 | particle.position.addSelf(
71 | particle.velocity);
72 | }
73 |
74 | }
75 |
76 | return {
77 | getParticleSystem: getParticleSystem,
78 | create: create,
79 | animate: animate,
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/js/classes/class.vector.js:
--------------------------------------------------------------------------------
1 | var Vector = function() {
2 | this.x = 0;
3 | this.y = 0;
4 |
5 | function create(xx, yy) {
6 | this.x = xx;
7 | this.y = yy;
8 |
9 | return this;
10 | }
11 |
12 | return {
13 | create: create,
14 | x: this.x,
15 | y: this.y
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/js/colors.js:
--------------------------------------------------------------------------------
1 | var Colors = function() {
2 |
3 | var hasLooped = false;
4 |
5 | function getColorByNoteID(noteID) {
6 | // Note ID is a number 0-127
7 | // Most of them are between 60-95
8 |
9 | var periods = 5;
10 | var freq = periods * 2 * Math.PI / 127;
11 |
12 | var w0 = 0;
13 | var phase = Math.PI / 2;
14 |
15 | if (!hasLooped) {
16 | for (i = 0; i < 128; i++) {
17 | var r = Math.round(Math.sin(w0 + i * freq) * 127 + 128);
18 | var g = Math.round(Math.sin(w0 + i * freq + phase) * 127 + 128);
19 | var b = Math.round(Math.sin(w0 + i * freq + 2 * phase) * 127 + 128);
20 | console.log('%c rgb(' + r + ', ' + g + ', ' + b + ')', 'color: rgb(' + r + ', ' + g + ', ' + b + ')');
21 | }
22 | hasLooped = true;
23 | }
24 |
25 | var r = Math.round(Math.sin(w0 + noteID * freq) * 127 + 128);
26 | var g = Math.round(Math.sin(w0 + noteID * freq + phase) * 127 + 128);
27 | var b = Math.round(Math.sin(w0 + noteID * freq + 2 * phase) * 127 + 128);
28 |
29 | var rgbString = 'rgb('+r+', '+g+', '+b+')';
30 | return rgbString;
31 | }
32 |
33 | return {
34 | getColorByNoteID: getColorByNoteID
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/js/midi/midifile.js:
--------------------------------------------------------------------------------
1 | /*
2 | class to parse the .mid file format
3 | (depends on stream.js)
4 | */
5 | function MidiFile(data) {
6 | function readChunk(stream) {
7 | var id = stream.read(4);
8 | var length = stream.readInt32();
9 | return {
10 | 'id': id,
11 | 'length': length,
12 | 'data': stream.read(length)
13 | };
14 | }
15 |
16 | var lastEventTypeByte;
17 |
18 | function readEvent(stream) {
19 | var event = {};
20 | event.deltaTime = stream.readVarInt();
21 | var eventTypeByte = stream.readInt8();
22 | if ((eventTypeByte & 0xf0) == 0xf0) {
23 | /* system / meta event */
24 | if (eventTypeByte == 0xff) {
25 | /* meta event */
26 | event.type = 'meta';
27 | var subtypeByte = stream.readInt8();
28 | var length = stream.readVarInt();
29 | switch(subtypeByte) {
30 | case 0x00:
31 | event.subtype = 'sequenceNumber';
32 | if (length != 2) throw "Expected length for sequenceNumber event is 2, got " + length;
33 | event.number = stream.readInt16();
34 | return event;
35 | case 0x01:
36 | event.subtype = 'text';
37 | event.text = stream.read(length);
38 | return event;
39 | case 0x02:
40 | event.subtype = 'copyrightNotice';
41 | event.text = stream.read(length);
42 | return event;
43 | case 0x03:
44 | event.subtype = 'trackName';
45 | event.text = stream.read(length);
46 | return event;
47 | case 0x04:
48 | event.subtype = 'instrumentName';
49 | event.text = stream.read(length);
50 | return event;
51 | case 0x05:
52 | event.subtype = 'lyrics';
53 | event.text = stream.read(length);
54 | return event;
55 | case 0x06:
56 | event.subtype = 'marker';
57 | event.text = stream.read(length);
58 | return event;
59 | case 0x07:
60 | event.subtype = 'cuePoint';
61 | event.text = stream.read(length);
62 | return event;
63 | case 0x20:
64 | event.subtype = 'midiChannelPrefix';
65 | if (length != 1) throw "Expected length for midiChannelPrefix event is 1, got " + length;
66 | event.channel = stream.readInt8();
67 | return event;
68 | case 0x2f:
69 | event.subtype = 'endOfTrack';
70 | if (length != 0) throw "Expected length for endOfTrack event is 0, got " + length;
71 | return event;
72 | case 0x51:
73 | event.subtype = 'setTempo';
74 | if (length != 3) throw "Expected length for setTempo event is 3, got " + length;
75 | event.microsecondsPerBeat = (
76 | (stream.readInt8() << 16)
77 | + (stream.readInt8() << 8)
78 | + stream.readInt8()
79 | )
80 | return event;
81 | case 0x54:
82 | event.subtype = 'smpteOffset';
83 | if (length != 5) throw "Expected length for smpteOffset event is 5, got " + length;
84 | var hourByte = stream.readInt8();
85 | event.frameRate = {
86 | 0x00: 24, 0x20: 25, 0x40: 29, 0x60: 30
87 | }[hourByte & 0x60];
88 | event.hour = hourByte & 0x1f;
89 | event.min = stream.readInt8();
90 | event.sec = stream.readInt8();
91 | event.frame = stream.readInt8();
92 | event.subframe = stream.readInt8();
93 | return event;
94 | case 0x58:
95 | event.subtype = 'timeSignature';
96 | if (length != 4) throw "Expected length for timeSignature event is 4, got " + length;
97 | event.numerator = stream.readInt8();
98 | event.denominator = Math.pow(2, stream.readInt8());
99 | event.metronome = stream.readInt8();
100 | event.thirtyseconds = stream.readInt8();
101 | return event;
102 | case 0x59:
103 | event.subtype = 'keySignature';
104 | if (length != 2) throw "Expected length for keySignature event is 2, got " + length;
105 | event.key = stream.readInt8(true);
106 | event.scale = stream.readInt8();
107 | return event;
108 | case 0x7f:
109 | event.subtype = 'sequencerSpecific';
110 | event.data = stream.read(length);
111 | return event;
112 | default:
113 | // console.log("Unrecognised meta event subtype: " + subtypeByte);
114 | event.subtype = 'unknown'
115 | event.data = stream.read(length);
116 | return event;
117 | }
118 | event.data = stream.read(length);
119 | return event;
120 | } else if (eventTypeByte == 0xf0) {
121 | event.type = 'sysEx';
122 | var length = stream.readVarInt();
123 | event.data = stream.read(length);
124 | return event;
125 | } else if (eventTypeByte == 0xf7) {
126 | event.type = 'dividedSysEx';
127 | var length = stream.readVarInt();
128 | event.data = stream.read(length);
129 | return event;
130 | } else {
131 | throw "Unrecognised MIDI event type byte: " + eventTypeByte;
132 | }
133 | } else {
134 | /* channel event */
135 | var param1;
136 | if ((eventTypeByte & 0x80) == 0) {
137 | /* running status - reuse lastEventTypeByte as the event type.
138 | eventTypeByte is actually the first parameter
139 | */
140 | param1 = eventTypeByte;
141 | eventTypeByte = lastEventTypeByte;
142 | } else {
143 | param1 = stream.readInt8();
144 | lastEventTypeByte = eventTypeByte;
145 | }
146 | var eventType = eventTypeByte >> 4;
147 | event.channel = eventTypeByte & 0x0f;
148 | event.type = 'channel';
149 | switch (eventType) {
150 | case 0x08:
151 | event.subtype = 'noteOff';
152 | event.noteNumber = param1;
153 | event.velocity = stream.readInt8();
154 | return event;
155 | case 0x09:
156 | event.noteNumber = param1;
157 | event.velocity = stream.readInt8();
158 | if (event.velocity == 0) {
159 | event.subtype = 'noteOff';
160 | } else {
161 | event.subtype = 'noteOn';
162 | }
163 | return event;
164 | case 0x0a:
165 | event.subtype = 'noteAftertouch';
166 | event.noteNumber = param1;
167 | event.amount = stream.readInt8();
168 | return event;
169 | case 0x0b:
170 | event.subtype = 'controller';
171 | event.controllerType = param1;
172 | event.value = stream.readInt8();
173 | return event;
174 | case 0x0c:
175 | event.subtype = 'programChange';
176 | event.programNumber = param1;
177 | return event;
178 | case 0x0d:
179 | event.subtype = 'channelAftertouch';
180 | event.amount = param1;
181 | return event;
182 | case 0x0e:
183 | event.subtype = 'pitchBend';
184 | event.value = param1 + (stream.readInt8() << 7);
185 | return event;
186 | default:
187 | throw "Unrecognised MIDI event type: " + eventType
188 | /*
189 | console.log("Unrecognised MIDI event type: " + eventType);
190 | stream.readInt8();
191 | event.subtype = 'unknown';
192 | return event;
193 | */
194 | }
195 | }
196 | }
197 |
198 | stream = Stream(data);
199 | var headerChunk = readChunk(stream);
200 | if (headerChunk.id != 'MThd' || headerChunk.length != 6) {
201 | throw "Bad .mid file - header not found";
202 | }
203 | var headerStream = Stream(headerChunk.data);
204 | var formatType = headerStream.readInt16();
205 | var trackCount = headerStream.readInt16();
206 | var timeDivision = headerStream.readInt16();
207 |
208 | if (timeDivision & 0x8000) {
209 | throw "Expressing time division in SMTPE frames is not supported yet"
210 | } else {
211 | ticksPerBeat = timeDivision;
212 | }
213 |
214 | var header = {
215 | 'formatType': formatType,
216 | 'trackCount': trackCount,
217 | 'ticksPerBeat': ticksPerBeat
218 | }
219 | var tracks = [];
220 | for (var i = 0; i < header.trackCount; i++) {
221 | tracks[i] = [];
222 | var trackChunk = readChunk(stream);
223 | if (trackChunk.id != 'MTrk') {
224 | throw "Unexpected chunk - expected MTrk, got "+ trackChunk.id;
225 | }
226 | var trackStream = Stream(trackChunk.data);
227 | while (!trackStream.eof()) {
228 | var event = readEvent(trackStream);
229 | tracks[i].push(event);
230 | //console.log(event);
231 | }
232 | }
233 |
234 | return {
235 | 'header': header,
236 | 'tracks': tracks
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/js/midi/midiloader.js:
--------------------------------------------------------------------------------
1 | function MidiLoader() {
2 |
3 | var midi = null;
4 | var bpm = null;
5 |
6 | function _fetch(url) {
7 | var req = new XMLHttpRequest();
8 | req.open('GET', url, true);
9 | req.overrideMimeType('text\/plain; charset=x-user-defined');
10 | req.onload = function() {
11 | _onFetch(req.response);
12 | }
13 | req.send(null);
14 | }
15 |
16 | function _onFetch(filestream) {
17 | var t = filestream || "" ;
18 | var ff = [];
19 | var mx = t.length;
20 | var scc= String.fromCharCode;
21 |
22 | for (var z = 0; z < mx; z++) {
23 | ff[z] = scc(t.charCodeAt(z) & 255);
24 | }
25 |
26 | var data = ff.join("");
27 | midi = MidiFile(data);
28 |
29 | if (window.onMidiLoaded != undefined)
30 | window.onMidiLoaded();
31 | }
32 |
33 | function _findLastEndedNote(notes, currentEndedNote) {
34 | if (notes.length > currentEndedNote && notes[currentEndedNote].end != null)
35 | _findLastEndedNote(currentEndedNote++);
36 | else
37 | return;
38 | }
39 |
40 | ////////////////////////////
41 |
42 | function load(url) {
43 | _fetch(url);
44 | return this;
45 | }
46 |
47 | function getMidiData() {
48 | return midi;
49 | }
50 |
51 | function getTracks() {
52 |
53 | var tracks = [];
54 | for (k = 0; k < midi.tracks.length; k++) {
55 | var source = midi.tracks[k];
56 |
57 | var time = 0;
58 | var trackname = '';
59 | var notes = [];
60 | var lastEndedNote = 0;
61 | var nextPatch = 0;
62 |
63 | for (i = 0; i < source.length; i++) {
64 |
65 | time += source[i].deltaTime;
66 |
67 | if (source[0].microsecondsPerBeat)
68 | bpm = Math.round(60 / (source[0].microsecondsPerBeat / 1000000));
69 |
70 | if (source[i].subtype == 'programChange')
71 | nextPatch = source[i].programNumber + 1;
72 |
73 | if (source[i].subtype == 'noteOn') {
74 | var note = {
75 | start: time,
76 | end: null,
77 | note: source[i].noteNumber,
78 | velocity: source[i].velocity
79 | }
80 | notes.push(note);
81 | }
82 |
83 | if (source[i].subtype == 'noteOff')
84 | for (j = lastEndedNote; j < notes.length; j++) {
85 | if (source[i].noteNumber == notes[j].note) {
86 | notes[j].end = time;
87 | _findLastEndedNote(notes, lastEndedNote);
88 | }
89 | }
90 |
91 | if (source[i].subtype == 'trackName')
92 | trackname = source[i].text;
93 |
94 | }
95 |
96 | if (notes.length > 0) {
97 | tracks.push({
98 | patchId: nextPatch,
99 | name: trackname,
100 | notes: notes
101 | });
102 | }
103 | }
104 |
105 | return tracks;
106 | }
107 |
108 | function getTicksPerBeat() {
109 | return midi.header.ticksPerBeat;
110 | }
111 |
112 | function getBPM() {
113 | return bpm;
114 | }
115 |
116 | function getPatch(patch) {
117 | var patches = ["-", "Acoustic Grand Piano","Bright Acoustic Piano","Electric Grand Piano","Honky-tonk Piano","Electric Piano 1","Electric Piano 2","Harpsichord","Clavi","Celesta","Glockenspiel","Music Box","Vibraphone","Marimba","Xylophone","Tubular Bells","Dulcimer","Drawbar Organ","Percussive Organ","Rock Organ","Church Organ","Reed Organ","Accordion","Harmonica","Tango Accordion","Guitar (nylon)","Acoustic Guitar (steel)","Electric Guitar (jazz)","Electric Guitar (clean)","Electric Guitar (muted)","Overdriven Guitar","Distortion Guitar","Guitar harmonics","Acoustic Bass","Electric Bass (finger)","Electric Bass (pick)","Fretless Bass","Slap Bass 1","Slap Bass 2","Synth Bass 1","Synth Bass 2","Violin","Viola","Cello","Contrabass","Tremolo Strings","Pizzicato Strings","Orchestral Harp","Timpani","String Ensemble 1","String Ensemble 2","SynthStrings 1","SynthStrings 2","Choir Aahs","Voice Oohs","Synth Voice","Orchestra Hit","Trumpet","Trombone","Tuba","Muted Trumpet","French Horn","Brass Section","SynthBrass 1","SynthBrass 2","Soprano Sax","Alto Sax","Tenor Sax","Baritone Sax","Oboe","English Horn","Bassoon","Clarinet","Piccolo","Flute","Recorder","Pan Flute","Blown Bottle","Shakuhachi","Whistle","Ocarina","Lead 1 (square)","Lead 2 (sawtooth)","Lead 3 (calliope)","Lead 4 (chiff)","Lead 5 (charang)","Lead 6 (voice)","Lead 7 (fifths)","Lead 8 (bass+lead)","Pad 1 (new age)","Pad 2 (warm)","Pad 3 (polysynth)","Pad 4 (choir)","Pad 5 (bowed)","Pad 6 (metallic)","Pad 7 (halo)","Pad 8 (sweep)","FX 1 (rain)","FX 2 (soundtrack)","FX 3 (crystal)","FX 4 (atmosphere)","FX 5 (brightness)","FX 6 (goblins)","FX 7 (echoes)","FX 8 (sci-fi)","Sitar","Banjo","Shamisen","Koto","Kalimba","Bag pipe","Fiddle","Shanai","Tinkle Bell","Agogo","Steel Drums","Woodblock","Taiko drum","Melodic Tom","Synth Drum","Reverse Cymbal","Guitar Fret Noise","Breath Noise","Seashore","Bird Tweet","Telephone Ring","Helicopter","Applause","Gunshot"];
118 | return patches[patch];
119 | }
120 |
121 | return {
122 | 'load': load,
123 | 'getMidiData': getMidiData,
124 | 'getTracks': getTracks,
125 | 'getTicksPerBeat': getTicksPerBeat,
126 | 'getBPM': getBPM,
127 | 'getPatch': getPatch
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/js/midi/stream.js:
--------------------------------------------------------------------------------
1 | /* Wrapper for accessing strings through sequential reads */
2 | function Stream(str) {
3 | var position = 0;
4 |
5 | function read(length) {
6 | var result = str.substr(position, length);
7 | position += length;
8 | return result;
9 | }
10 |
11 | /* read a big-endian 32-bit integer */
12 | function readInt32() {
13 | var result = (
14 | (str.charCodeAt(position) << 24)
15 | + (str.charCodeAt(position + 1) << 16)
16 | + (str.charCodeAt(position + 2) << 8)
17 | + str.charCodeAt(position + 3));
18 | position += 4;
19 | return result;
20 | }
21 |
22 | /* read a big-endian 16-bit integer */
23 | function readInt16() {
24 | var result = (
25 | (str.charCodeAt(position) << 8)
26 | + str.charCodeAt(position + 1));
27 | position += 2;
28 | return result;
29 | }
30 |
31 | /* read an 8-bit integer */
32 | function readInt8(signed) {
33 | var result = str.charCodeAt(position);
34 | if (signed && result > 127) result -= 256;
35 | position += 1;
36 | return result;
37 | }
38 |
39 | function eof() {
40 | return position >= str.length;
41 | }
42 |
43 | /* read a MIDI-style variable-length integer
44 | (big-endian value in groups of 7 bits,
45 | with top bit set to signify that another byte follows)
46 | */
47 | function readVarInt() {
48 | var result = 0;
49 | while (true) {
50 | var b = readInt8();
51 | if (b & 0x80) {
52 | result += (b & 0x7f);
53 | result <<= 7;
54 | } else {
55 | /* b is the last byte */
56 | return result + b;
57 | }
58 | }
59 | }
60 |
61 | return {
62 | 'eof': eof,
63 | 'read': read,
64 | 'readInt32': readInt32,
65 | 'readInt16': readInt16,
66 | 'readInt8': readInt8,
67 | 'readVarInt': readVarInt
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/js/midirenderer.js:
--------------------------------------------------------------------------------
1 | var MidiRenderer = function() {
2 |
3 | var animator = null;
4 | var midi;
5 | var notes;
6 |
7 | var offsetDelay = 0; // add delay to fix animation bug (ms)
8 |
9 | // Method to add note to scene
10 | function _addNote(noteID, noteLength) {
11 | var note = new Note().setNoteID(noteID).setNoteLength(noteLength);
12 | animator.onNoteAdded(note);
13 | }
14 |
15 | // Convert elapsed time to midi ticks
16 | function _timeToTicks(time) {
17 | // Time is given i ms
18 | var sec = (time / 1000);
19 | // Get beats per second
20 | var bps = midi.getBPM() / 60;
21 | var currentBeat = bps * sec;
22 |
23 | var tick = midi.getTicksPerBeat() * currentBeat;
24 |
25 | return tick;
26 | }
27 |
28 | function render(time) {
29 | // Get current tick
30 | var tick = _timeToTicks(time);
31 |
32 | var nArray = notes;
33 |
34 | if (nArray.length == 0)
35 | animator.onMidiRendererCompleted();
36 |
37 | // Loop through all non-played notes
38 | for (i = 0; i < nArray.length; i++) {
39 | // If note should not be played, break the loop since array i sorted by time
40 | if (nArray[i].start + offsetDelay > tick)
41 | break;
42 |
43 | // If note should be played, but has not been added to the scene, add it
44 | _addNote(nArray[i].note, (nArray[i].end - nArray[i].start));
45 | // Remove the note from the array
46 | notes.splice(i, 1);
47 | }
48 | }
49 |
50 | function init(midiObject, anim) {
51 | midi = midiObject;
52 | animator = anim;
53 |
54 | var tracks = midiObject.getTracks();
55 | // Get first track only
56 | notes = tracks[0].notes;
57 |
58 | return this;
59 | }
60 |
61 | function play(player, animateFunc) {
62 | player.onplaying = function() {
63 | animateFunc();
64 | }
65 |
66 | player.play();
67 | }
68 |
69 | return {
70 | init: init,
71 | render: render,
72 | play: play
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/js/physics.js:
--------------------------------------------------------------------------------
1 | var Physics = function() {
2 | // The size of the vector field in Three.js coordinates on each axis
3 | // No spheres should be spawned outside this area
4 | // Default: 15
5 | var vectorFieldSize = 15;
6 | // Determine how many vector fields to be created for each Three.js coordinate
7 | // Default: 3
8 | var precision = 3;
9 | // Sphere gravity
10 | // Default: 3
11 | var gravity = 20;
12 | // Animation speed
13 | // Default: 3
14 | var speed = 5;
15 | // When calculating new velocity, old velocity should be multiplied with this factor
16 | // Use values between 0.9-1
17 | // Default: 1 (this will not affect the velocity at all)
18 | var velocityScale = 1;
19 | // Velocity distance compensation
20 | // Larger value means less compensation, set to 0 to disable
21 | // Default: 200
22 | var velCompCoefficient = 250;
23 | // Spiral force effect
24 | // Negative values will change direction, set to 0 to disable
25 | // Default: 0
26 | var spiralForce = 5;
27 |
28 | // Center of the vector field
29 | var center;
30 | // Variable to store the vector field
31 | var vectors = [];
32 |
33 | ///////////////////////////////////
34 |
35 | // Do not touch
36 | var sceneFrame = 0;
37 | var speedCoefficient = 1;
38 | var vectorFieldCoefficient = 1;
39 | var slowdownFired = 0;
40 |
41 | var slowdownEnabled = true;
42 |
43 | ///////////////////////////////////
44 |
45 | function _createVectorField() {
46 | // Get vector field size (field will be grids * grids large)
47 | var grids = vectorFieldSize * 2 + 1;
48 | // Total amount of vectors in vector field
49 | var nVectors = Math.pow(grids * precision, 2);
50 | // Find the center of the vector field
51 | // Since the vector field is a four-square the center coordinate is the same for both axis
52 | center = (grids * precision - 1) / 2;
53 |
54 | // Create all vectors
55 | for (i = 0; i < nVectors; i++) {
56 | // Find the x coordinate of the current vector
57 | x = ((i % (grids * precision)) - center) / precision;
58 | // Find the y coordinate of the current vector
59 | y = (center - Math.floor(i / (grids * precision))) / precision;
60 |
61 | // Arbitrary coefficient based on scientific evidence
62 | A = gravity / 40;
63 | B = spiralForce * 1 / 20;
64 |
65 | // Calculate acceleration
66 | xAcceleration = A * -x * Math.abs(x) - B * y;
67 | yAcceleration = A * -y * Math.abs(y) + B * x;
68 |
69 | // Create current vector
70 | vectors[i] = new Vector().create(xAcceleration, yAcceleration);
71 | }
72 | }
73 |
74 | function _getNearestVector(pos) {
75 | // Get vector field size (field will be grids * grids large)
76 | var grids = vectorFieldSize * 2 + 1;
77 | // Find vector field coordinate for nearest vector
78 | vx = Math.round(pos.x * precision);
79 | vy = Math.round(pos.y * precision);
80 |
81 | // If outside vector field, still use nearest vector (x-axis)
82 | if (Math.abs(vx) > vectorFieldSize * precision){
83 | vx = (vx > 0 ? 1 : -1) * vectorFieldSize * precision;
84 | }
85 |
86 | // If outside vector field, still use nearest vector (y-axis)
87 | if (Math.abs(vy) > vectorFieldSize * precision){
88 | vy = (vy > 0 ? 1 : -1) * vectorFieldSize * precision;
89 | }
90 |
91 | // Find the index of the vector in the vector field array
92 | return (vx + center) + (center - vy) * grids * precision;
93 | }
94 |
95 | // Method run once
96 | function init() {
97 | console.log('Physics init');
98 |
99 | _createVectorField();
100 |
101 | return this;
102 | }
103 |
104 | function updateSlowdownCoefficients(frame) {
105 | if (!slowdownEnabled){
106 | return;
107 | }
108 |
109 | sceneFrame = frame;
110 |
111 | var deltaFrame = frame - slowdownFired;
112 |
113 | //speedCoefficient = Math.pow(0.999, Math.pow(deltaFrame, 2) / 1500);
114 | vectorFieldCoefficient = Math.exp(-1 * deltaFrame / 60);
115 | }
116 |
117 | function resetSlowdown() {
118 | slowdownFired = sceneFrame;
119 | }
120 |
121 | // Metod to call when object should be affected by vector field
122 | function affect(note) {
123 |
124 | var velocity = note.getVelocity();
125 | var pos = note.getPosition();
126 | var vec = _getNearestVector(pos);
127 |
128 | if (!vec){
129 | console.warn('Nearest vector not found');
130 | }
131 |
132 | // Mass should affect the velocity of the object
133 | var density = 1 / note.getMass();
134 |
135 | var distance = Math.sqrt(Math.pow(vectors[vec].x, 2) + Math.pow(vectors[vec].y, 2));
136 | var denominator = velCompCoefficient === 0 ? 0 : (distance / velCompCoefficient);
137 | var compensation = 1 / (1 + denominator);
138 |
139 | // Use acceleration (from the nearest vector) to affect the velocity
140 | velocity.x = compensation * speedCoefficient * velocityScale * velocity.x + vectorFieldCoefficient * vectors[vec].x * density;
141 | velocity.y = compensation * speedCoefficient * velocityScale * velocity.y + vectorFieldCoefficient * vectors[vec].y * density;
142 | }
143 |
144 | function render(note) {
145 | var pos = note.getPosition();
146 | var velocity = note.getVelocity();
147 |
148 | // Arbitrary coefficient based on scientific evidence
149 | var A = speed * 1 / 240;
150 |
151 | // Translate the object with the new velocity
152 | pos.x += A * velocity.x;
153 | pos.y += A * velocity.y;
154 | }
155 |
156 | return {
157 | init: init,
158 | updateSlowdownCoefficients: updateSlowdownCoefficients,
159 | resetSlowdown: resetSlowdown,
160 | affect: affect,
161 | render: render
162 | };
163 | };
164 |
--------------------------------------------------------------------------------
/js/visualize.js:
--------------------------------------------------------------------------------
1 | var midiObject = new MidiLoader().load('midi/river.mid');
2 | var animator;
3 | var midiRenderer;
4 |
5 | var animationStartTime = null;
6 | var playing = false;
7 |
8 | window.onload = function() {
9 |
10 | window.map = new THREE.TextureLoader().load('images/glow.png');
11 |
12 | var player = document.createElement('audio');
13 | player.preload = true;
14 | player.src = "midi/river2.mp3";
15 |
16 | window.onMidiLoaded = function() {
17 | // Midi data
18 | console.log('Tracks: ', midiObject.getTracks());
19 | console.log('Ticks per beat: ', midiObject.getTicksPerBeat());
20 | console.log('BPM: ', midiObject.getBPM());
21 |
22 | // Initialize animator, physics and midirenderer
23 | animator = new Animator().init();
24 | window.Physics = new Physics().init();
25 | window.Colors = new Colors();
26 | midiRenderer = new MidiRenderer().init(midiObject, animator);
27 |
28 | // Start rendering
29 | animator.renderOnce();
30 |
31 | player.oncanplaythrough = function() {
32 | window.addEventListener('keydown', function(event) {
33 | if (event.keyCode != 32)
34 | return;
35 |
36 | midiRenderer.play(player, function() {
37 | playing = true;
38 | });
39 | }, false);
40 | }
41 |
42 | animate(null);
43 |
44 | }
45 |
46 | var frame = 0;
47 | var deltaFrame;
48 |
49 | function animate(time) {
50 | if (time == null) {
51 | requestAnimationFrame(animate);
52 | return;
53 | }
54 |
55 | // Request to be called again for next frame
56 | requestAnimationFrame(animate);
57 |
58 | // Render midi file and Three.js scene
59 | if (playing) {
60 | if (animationStartTime == null)
61 | animationStartTime = time;
62 | deltaTime = time - animationStartTime;
63 |
64 | midiRenderer.render(deltaTime);
65 | }
66 |
67 | animator.render(frame);
68 |
69 | frame++;
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/midi/deb_clai.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/deb_clai.mid
--------------------------------------------------------------------------------
/midi/furelise.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/furelise.mid
--------------------------------------------------------------------------------
/midi/furelise.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/furelise.mp3
--------------------------------------------------------------------------------
/midi/got.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/got.mid
--------------------------------------------------------------------------------
/midi/got.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/got.mp3
--------------------------------------------------------------------------------
/midi/pokemon.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/pokemon.mid
--------------------------------------------------------------------------------
/midi/pokemon.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/pokemon.mp3
--------------------------------------------------------------------------------
/midi/pokemon2.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/pokemon2.mid
--------------------------------------------------------------------------------
/midi/river.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/river.mid
--------------------------------------------------------------------------------
/midi/river.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/river.mp3
--------------------------------------------------------------------------------
/midi/river2.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/river2.mid
--------------------------------------------------------------------------------
/midi/river2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/river2.mp3
--------------------------------------------------------------------------------
/midi/test.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/test.mid
--------------------------------------------------------------------------------
/midi/test2.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/test2.mid
--------------------------------------------------------------------------------
/midi/test3.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/test3.mid
--------------------------------------------------------------------------------
/midi/test4.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/test4.mid
--------------------------------------------------------------------------------
/midi/test4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miii/liu-tnm061-3D-MIDI-visualizer/215ef7884d002f5eae153a9b210f30e45ba59b28/midi/test4.mp3
--------------------------------------------------------------------------------