| OLD | NEW |
| 1 // For the record, these distance formulas were taken from the OpenAL | 1 // For the record, these distance formulas were taken from the OpenAL |
| 2 // spec | 2 // spec |
| 3 // (http://connect.creativelabs.com/openal/Documentation/OpenAL%201.1%20Specific
ation.pdf), | 3 // (http://connect.creativelabs.com/openal/Documentation/OpenAL%201.1%20Specific
ation.pdf), |
| 4 // not the code. The Web Audio spec follows the OpenAL formulas. | 4 // not the code. The Web Audio spec follows the OpenAL formulas. |
| 5 | 5 |
| 6 function linearDistance(panner, x, y, z) { | 6 function linearDistance(panner, x, y, z) { |
| 7 var distance = Math.sqrt(x * x + y * y + z * z); | 7 let distance = Math.sqrt(x * x + y * y + z * z); |
| 8 var dref = Math.min(panner.refDistance, panner.maxDistance); | 8 let dref = Math.min(panner.refDistance, panner.maxDistance); |
| 9 var dmax = Math.max(panner.refDistance, panner.maxDistance); | 9 let dmax = Math.max(panner.refDistance, panner.maxDistance); |
| 10 distance = Math.max(Math.min(distance, dmax), dref); | 10 distance = Math.max(Math.min(distance, dmax), dref); |
| 11 var rolloff = Math.max(Math.min(panner.rolloffFactor, 1), 0); | 11 let rolloff = Math.max(Math.min(panner.rolloffFactor, 1), 0); |
| 12 if (dref === dmax) | 12 if (dref === dmax) |
| 13 return 1 - rolloff; | 13 return 1 - rolloff; |
| 14 | 14 |
| 15 var gain = (1 - rolloff * (distance - dref) / (dmax - dref)); | 15 let gain = (1 - rolloff * (distance - dref) / (dmax - dref)); |
| 16 | 16 |
| 17 return gain; | 17 return gain; |
| 18 } | 18 } |
| 19 | 19 |
| 20 function inverseDistance(panner, x, y, z) { | 20 function inverseDistance(panner, x, y, z) { |
| 21 var distance = Math.sqrt(x * x + y * y + z * z); | 21 let distance = Math.sqrt(x * x + y * y + z * z); |
| 22 distance = Math.max(distance, panner.refDistance); | 22 distance = Math.max(distance, panner.refDistance); |
| 23 var rolloff = panner.rolloffFactor; | 23 let rolloff = panner.rolloffFactor; |
| 24 var gain = panner.refDistance / (panner.refDistance + rolloff * (Math.max(di
stance, panner.refDistance) - panner.refDistance)); | 24 let gain = panner.refDistance / |
| 25 (panner.refDistance + |
| 26 rolloff * (Math.max(distance, panner.refDistance) - panner.refDistance)); |
| 25 | 27 |
| 26 return gain; | 28 return gain; |
| 27 } | 29 } |
| 28 | 30 |
| 29 function exponentialDistance(panner, x, y, z) { | 31 function exponentialDistance(panner, x, y, z) { |
| 30 var distance = Math.sqrt(x * x + y * y + z * z); | 32 let distance = Math.sqrt(x * x + y * y + z * z); |
| 31 distance = Math.max(distance, panner.refDistance); | 33 distance = Math.max(distance, panner.refDistance); |
| 32 var rolloff = panner.rolloffFactor; | 34 let rolloff = panner.rolloffFactor; |
| 33 var gain = Math.pow(distance / panner.refDistance, -rolloff); | 35 let gain = Math.pow(distance / panner.refDistance, -rolloff); |
| 34 | 36 |
| 35 return gain; | 37 return gain; |
| 36 } | 38 } |
| 37 | 39 |
| 38 // Simple implementations of 3D vectors implemented as a 3-element array. | 40 // Simple implementations of 3D vectors implemented as a 3-element array. |
| 39 | 41 |
| 40 // x - y | 42 // x - y |
| 41 function vec3Sub(x, y) { | 43 function vec3Sub(x, y) { |
| 42 var z = new Float32Array(3); | 44 let z = new Float32Array(3); |
| 43 z[0] = x[0] - y[0]; | 45 z[0] = x[0] - y[0]; |
| 44 z[1] = x[1] - y[1]; | 46 z[1] = x[1] - y[1]; |
| 45 z[2] = x[2] - y[2]; | 47 z[2] = x[2] - y[2]; |
| 46 | 48 |
| 47 return z; | 49 return z; |
| 48 } | 50 } |
| 49 | 51 |
| 50 // x/|x| | 52 // x/|x| |
| 51 function vec3Normalize(x) { | 53 function vec3Normalize(x) { |
| 52 var mag = Math.hypot(...x); | 54 let mag = Math.hypot(...x); |
| 53 return x.map(function (c) { return c / mag; }); | 55 return x.map(function(c) { |
| 56 return c / mag; |
| 57 }); |
| 54 } | 58 } |
| 55 | 59 |
| 56 // x == 0? | 60 // x == 0? |
| 57 function vec3IsZero(x) { | 61 function vec3IsZero(x) { |
| 58 return x[0] === 0 && x[1] === 0 && x[2] === 0; | 62 return x[0] === 0 && x[1] === 0 && x[2] === 0; |
| 59 } | 63 } |
| 60 | 64 |
| 61 // Vector cross product | 65 // Vector cross product |
| 62 function vec3Cross(u, v) { | 66 function vec3Cross(u, v) { |
| 63 var cross = new Float32Array(3); | 67 let cross = new Float32Array(3); |
| 64 cross[0] = u[1] * v[2] - u[2] * v[1]; | 68 cross[0] = u[1] * v[2] - u[2] * v[1]; |
| 65 cross[1] = u[2] * v[0] - u[0] * v[2]; | 69 cross[1] = u[2] * v[0] - u[0] * v[2]; |
| 66 cross[2] = u[0] * v[1] - u[1] * v[0]; | 70 cross[2] = u[0] * v[1] - u[1] * v[0]; |
| 67 return cross; | 71 return cross; |
| 68 } | 72 } |
| 69 | 73 |
| 70 // Dot product | 74 // Dot product |
| 71 function vec3Dot(x, y) { | 75 function vec3Dot(x, y) { |
| 72 return x[0] * y[0] + x[1] * y[1] + x[2] * y[2]; | 76 return x[0] * y[0] + x[1] * y[1] + x[2] * y[2]; |
| 73 } | 77 } |
| 74 | 78 |
| 75 // a*x, for scalar a | 79 // a*x, for scalar a |
| 76 function vec3Scale(a, x) { | 80 function vec3Scale(a, x) { |
| 77 return x.map(function (c) { return a * c; }); | 81 return x.map(function(c) { |
| 82 return a * c; |
| 83 }); |
| 78 } | 84 } |
| 79 | 85 |
| 80 function calculateAzimuth(source, listener, listenerForward, listenerUp) { | 86 function calculateAzimuth(source, listener, listenerForward, listenerUp) { |
| 81 var sourceListener = vec3Sub(source, listener); | 87 let sourceListener = vec3Sub(source, listener); |
| 82 | 88 |
| 83 if (vec3IsZero(sourceListener)) | 89 if (vec3IsZero(sourceListener)) |
| 84 return 0; | 90 return 0; |
| 85 | 91 |
| 86 sourceListener = vec3Normalize(sourceListener); | 92 sourceListener = vec3Normalize(sourceListener); |
| 87 | 93 |
| 88 var listenerRight = vec3Normalize(vec3Cross(listenerForward, listenerUp)); | 94 let listenerRight = vec3Normalize(vec3Cross(listenerForward, listenerUp)); |
| 89 var listenerForwardNorm = vec3Normalize(listenerForward); | 95 let listenerForwardNorm = vec3Normalize(listenerForward); |
| 90 | 96 |
| 91 var up = vec3Cross(listenerRight, listenerForwardNorm); | 97 let up = vec3Cross(listenerRight, listenerForwardNorm); |
| 92 var upProjection = vec3Dot(sourceListener, up); | 98 let upProjection = vec3Dot(sourceListener, up); |
| 93 | 99 |
| 94 var projectedSource = vec3Normalize(vec3Sub(sourceListener, vec3Scale(upProj
ection, up))); | 100 let projectedSource = |
| 101 vec3Normalize(vec3Sub(sourceListener, vec3Scale(upProjection, up))); |
| 95 | 102 |
| 96 var azimuth = 180 / Math.PI * Math.acos(vec3Dot(projectedSource, listenerRig
ht)); | 103 let azimuth = |
| 104 180 / Math.PI * Math.acos(vec3Dot(projectedSource, listenerRight)); |
| 97 | 105 |
| 98 // Source in front or behind the listener | 106 // Source in front or behind the listener |
| 99 var frontBack = vec3Dot(projectedSource, listenerForwardNorm); | 107 let frontBack = vec3Dot(projectedSource, listenerForwardNorm); |
| 100 if (frontBack < 0) | 108 if (frontBack < 0) |
| 101 azimuth = 360 - azimuth; | 109 azimuth = 360 - azimuth; |
| 102 | 110 |
| 103 // Make azimuth relative to "front" and not "right" listener vector. | 111 // Make azimuth relative to "front" and not "right" listener vector. |
| 104 if (azimuth >= 0 && azimuth <= 270) | 112 if (azimuth >= 0 && azimuth <= 270) |
| 105 azimuth = 90 - azimuth; | 113 azimuth = 90 - azimuth; |
| 106 else | 114 else |
| 107 azimuth = 450 - azimuth; | 115 azimuth = 450 - azimuth; |
| 108 | 116 |
| 109 // We don't need elevation, so we're skipping that computation. | 117 // We don't need elevation, so we're skipping that computation. |
| 110 return azimuth; | 118 return azimuth; |
| 111 } | 119 } |
| 112 | 120 |
| 113 // Map our position angle to the azimuth angle (in degrees). | 121 // Map our position angle to the azimuth angle (in degrees). |
| 114 // | 122 // |
| 115 // An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg. | 123 // An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg. |
| 116 function angleToAzimuth(angle) { | 124 function angleToAzimuth(angle) { |
| 117 return 90 - angle * 180 / Math.PI; | 125 return 90 - angle * 180 / Math.PI; |
| 118 } | 126 } |
| 119 | 127 |
| 120 // The gain caused by the EQUALPOWER panning model | 128 // The gain caused by the EQUALPOWER panning model |
| 121 function equalPowerGain(azimuth, numberOfChannels) { | 129 function equalPowerGain(azimuth, numberOfChannels) { |
| 122 var halfPi = Math.PI / 2; | 130 let halfPi = Math.PI / 2; |
| 123 | 131 |
| 124 if (azimuth < -90) | 132 if (azimuth < -90) |
| 125 azimuth = -180 - azimuth; | 133 azimuth = -180 - azimuth; |
| 126 else | 134 else |
| 127 azimuth = 180 - azimuth; | 135 azimuth = 180 - azimuth; |
| 128 | 136 |
| 129 if (numberOfChannels == 1) { | 137 if (numberOfChannels == 1) { |
| 130 var panPosition = (azimuth + 90) / 180; | 138 let panPosition = (azimuth + 90) / 180; |
| 131 | 139 |
| 132 var gainL = Math.cos(halfPi * panPosition); | 140 let gainL = Math.cos(halfPi * panPosition); |
| 133 var gainR = Math.sin(halfPi * panPosition); | 141 let gainR = Math.sin(halfPi * panPosition); |
| 134 | 142 |
| 135 return { left : gainL, right : gainR }; | 143 return {left: gainL, right: gainR}; |
| 144 } else { |
| 145 if (azimuth <= 0) { |
| 146 let panPosition = (azimuth + 90) / 90; |
| 147 |
| 148 let gainL = Math.cos(halfPi * panPosition); |
| 149 let gainR = Math.sin(halfPi * panPosition); |
| 150 |
| 151 return {left: gainL, right: gainR}; |
| 136 } else { | 152 } else { |
| 137 if (azimuth <= 0) { | 153 let panPosition = azimuth / 90; |
| 138 var panPosition = (azimuth + 90) / 90; | |
| 139 | 154 |
| 140 var gainL = Math.cos(halfPi * panPosition); | 155 let gainL = Math.cos(halfPi * panPosition); |
| 141 var gainR = Math.sin(halfPi * panPosition); | 156 let gainR = Math.sin(halfPi * panPosition); |
| 142 | 157 |
| 143 return { left : gainL, right : gainR }; | 158 return {left: gainL, right: gainR}; |
| 144 } else { | |
| 145 var panPosition = azimuth / 90; | |
| 146 | |
| 147 var gainL = Math.cos(halfPi * panPosition); | |
| 148 var gainR = Math.sin(halfPi * panPosition); | |
| 149 | |
| 150 return { left : gainL, right : gainR }; | |
| 151 } | |
| 152 } | 159 } |
| 160 } |
| 153 } | 161 } |
| 154 | 162 |
| 155 function applyPanner(azimuth, srcL, srcR, numberOfChannels) { | 163 function applyPanner(azimuth, srcL, srcR, numberOfChannels) { |
| 156 var length = srcL.length; | 164 let length = srcL.length; |
| 157 var outL = new Float32Array(length); | 165 let outL = new Float32Array(length); |
| 158 var outR = new Float32Array(length); | 166 let outR = new Float32Array(length); |
| 159 | 167 |
| 160 if (numberOfChannels == 1) { | 168 if (numberOfChannels == 1) { |
| 161 for (var k = 0; k < length; ++k) { | 169 for (let k = 0; k < length; ++k) { |
| 162 var gains = equalPowerGain(azimuth[k], numberOfChannels); | 170 let gains = equalPowerGain(azimuth[k], numberOfChannels); |
| 163 | 171 |
| 164 outL[k] = srcL[k] * gains.left; | 172 outL[k] = srcL[k] * gains.left; |
| 165 outR[k] = srcR[k] * gains.right; | 173 outR[k] = srcR[k] * gains.right; |
| 166 } | 174 } |
| 167 } else { | 175 } else { |
| 168 for (var k = 0; k < length; ++k) { | 176 for (let k = 0; k < length; ++k) { |
| 169 var gains = equalPowerGain(azimuth[k], numberOfChannels); | 177 let gains = equalPowerGain(azimuth[k], numberOfChannels); |
| 170 | 178 |
| 171 if (azimuth[k] <= 0) { | 179 if (azimuth[k] <= 0) { |
| 172 outL[k] = srcL[k] + srcR[k] * gains.left; | 180 outL[k] = srcL[k] + srcR[k] * gains.left; |
| 173 outR[k] = srcR[k] * gains.right; | 181 outR[k] = srcR[k] * gains.right; |
| 174 } else { | 182 } else { |
| 175 outL[k] = srcL[k] * gains.left; | 183 outL[k] = srcL[k] * gains.left; |
| 176 outR[k] = srcR[k] + srcL[k] * gains.right; | 184 outR[k] = srcR[k] + srcL[k] * gains.right; |
| 177 } | 185 } |
| 178 } | |
| 179 } | 186 } |
| 187 } |
| 180 | 188 |
| 181 return { left: outL, right: outR }; | 189 return {left: outL, right: outR}; |
| 182 } | 190 } |
| OLD | NEW |