├── .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 --------------------------------------------------------------------------------