├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json └── test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "commonjs": true, 6 | "es6": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "rules": { 10 | "strict": 2, 11 | "indent": 0, 12 | "linebreak-style": 0, 13 | "quotes": 0, 14 | "semi": 0, 15 | "no-cond-assign": 1, 16 | "no-constant-condition": 1, 17 | "no-duplicate-case": 1, 18 | "no-empty": 1, 19 | "no-ex-assign": 1, 20 | "no-extra-boolean-cast": 1, 21 | "no-extra-semi": 1, 22 | "no-fallthrough": 1, 23 | "no-func-assign": 1, 24 | "no-global-assign": 1, 25 | "no-implicit-globals": 2, 26 | "no-inner-declarations": ["error", "functions"], 27 | "no-irregular-whitespace": 2, 28 | "no-loop-func": 1, 29 | "no-magic-numbers": ["warn", { "ignore": [1, 0, -1], "ignoreArrayIndexes": true}], 30 | "no-multi-str": 1, 31 | "no-mixed-spaces-and-tabs": 1, 32 | "no-proto": 1, 33 | "no-sequences": 1, 34 | "no-throw-literal": 1, 35 | "no-unmodified-loop-condition": 1, 36 | "no-useless-call": 1, 37 | "no-void": 1, 38 | "no-with": 2, 39 | "wrap-iife": 1, 40 | "no-redeclare": 1, 41 | "no-unused-vars": ["error", { "vars": "all", "args": "none" }], 42 | "no-sparse-arrays": 1 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "6" 5 | - "5" 6 | - "4" 7 | - "0.12" 8 | - "0.10" 9 | matrix: 10 | fast_finish: true 11 | allow_failures: 12 | - node_js: "0.10" 13 | - node_js: "0.12" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2018 Dmitry Yvanov 3 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 21 | OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # audio-buffer-utils [![Build Status](https://travis-ci.org/audiojs/audio-buffer-utils.svg?branch=master)](https://travis-ci.org/audiojs/audio-buffer-utils) [![unstable](https://img.shields.io/badge/stability-unstable-green.svg)](http://github.com/badges/stability-badges) [![Greenkeeper badge](https://badges.greenkeeper.io/audiojs/audio-buffer-utils.svg)](https://greenkeeper.io/) 2 | 3 | Utility functions for [_AudioBuffers_](https://github.com/audiojs/audio-buffer) in web-audio and node. Optimized for performance. 4 | 5 | * [util.create(src, ch|opts?)](https://github.com/audiojs/audio-buffer-utils#utilcreatedatalength-optionschannels1-samplerate44100) 6 | * [util.shallow(buf)](#utilshallowbuffer) 7 | * [util.clone(buf)](#utilclonebuffer) 8 | * [util.copy(buf, dst, start?)](#utilcopyfrombuffer-tobuffer-offset0) 9 | * [util.slice(buf, start?, end?)](#utilslicebuffer-start0-end-0) 10 | * [util.subbuffer(buf, start?, end?, ch?)](#utilsubbufferbuffer-start0-end-0-channels) 11 | * [util.concat(a, b, ...)](#utilconcatbuffer1-buffer2-buffer3-buffern-) 12 | * [util.repeat(buf, n)](#utilrepeatbuffer-times) 13 | * [util.reverse(src, dst?, start?, end?)](#utilreversebuffer-target-start0-end-0) 14 | * [util.invert(src, dst?, start?, end?)](#utilinvertbuffer-target-start0-end-0) 15 | * [util.zero(buf)](#utilzerobuffer) 16 | * [util.noise(buf)](#utilnoisebuffer) 17 | * [util.equal(a, b, ...)](#utilequalbuffera-bufferb-) 18 | * [util.fill(buf, dst?, val, start?, end?)](#utilfillbuffer-target-valuevalue-i-channelvalue-start0-end-0) 19 | * [util.resize(buf, len)](#utilresizebuffer-length) 20 | * [util.pad(buf, len, val?)](#utilpadbufferlength-lengthbuffer-value0) 21 | * [util.shift(buf, off)](#utilshiftbuffer-offset) 22 | * [util.rotate(buf, off)](#utilrotatebuffer-offset) 23 | * [util.normalize(buf, dst?, start?, end?)](#utilnormalizebuffer-target-start0-end-0) 24 | * [util.removeStatic(buf, dst?, start?, end?)](#utilremovestaticbuffer-target-start0-end-0) 25 | * [util.trim(buf, lvl)](#utiltrimbuffer-threshold0) 26 | * [util.mix(a, b, amt?, off?)](#utilmixbuffera-bufferb-ratiovala-valb-i-channelval-offset0) 27 | * [util.size(buf)](#utilsizebuffer) 28 | * [util.data(buf, dst?)](#utildatabuffer-data) 29 | 30 | ## Usage 31 | 32 | [![npm install audio-buffer-utils](https://nodei.co/npm/audio-buffer-utils.png?mini=true)](https://npmjs.org/package/audio-buffer-utils/) 33 | 34 | ### `util.create(data|length, options|channels=1, sampleRate=44100)` 35 | 36 | Create a new buffer from any argument. 37 | Data can be a length, an array with channels' data, an other buffer or plain array. See [audio-buffer-from](https://github.com/audiojs/audio-buffer-from) module. 38 | 39 | ```js 40 | //mono buffer with 100 samples 41 | let a = util.create(100) 42 | 43 | //stereo buffer with predefined channels data 44 | let b = util.create([Array(100).fill(0.5), Array(100).fill(0.4)]) 45 | 46 | //minimal length buffer (1 sample, 1 channel) 47 | let c = util.create() 48 | 49 | //create 2 seconds buffer with reduced sample rate 50 | let rate = 22050 51 | let d = util.create(2 * rate, 2, rate) 52 | ``` 53 | 54 | ### `util.shallow(buffer)` 55 | Create a new buffer with the same characteristics as `buffer`, contents are undefined. 56 | 57 | ```js 58 | //create buffer with the same shape as `a` 59 | let b = util.shallow(a) 60 | 61 | util.equal(a, b) //false 62 | ``` 63 | 64 | ### `util.clone(buffer)` 65 | Create a new buffer with the same characteristics as `buffer`, fill it with a copy of `buffer`'s data, and return it. 66 | 67 | ```js 68 | //clone buffer `a` 69 | let b = util.clone(a) 70 | 71 | util.equal(a, b) //true 72 | ``` 73 | 74 | ### `util.copy(fromBuffer, toBuffer, offset=0)` 75 | Copy the data from one buffer to another, with optional offset. If length of `fromBuffer` exceeds `offset + toBuffer.length`, an error will be thrown. 76 | 77 | ### `util.slice(buffer, start=0, end=-0)` 78 | Create a new buffer by slicing the current one. 79 | 80 | ### `util.subbuffer(buffer, start=0, end=-0, channels?)` 81 | Create a new buffer by subreferencing the current one. The new buffer represents a handle for the source buffer, working on it's data. Note that it is null-context buffer, meaning that it is not bound to web audio API. To convert it to real _AudioBuffer_, use `util.slice` or `util.create`. 82 | 83 | `channels` array may apply channels mapping to pick only indicated channels from the initial buffer. See also [audio-buffer-remix](https://github.com/audiojs/audio-buffer-remix). 84 | 85 | ```js 86 | var a = util.create(100, 2) 87 | var b = util.subbuffer(10, 90) 88 | 89 | //b references a 90 | b.getChannelData(0)[0] = 1 91 | a.getChannelData(0)[10] // 1 92 | 93 | //convert b to web-audio-api buffer 94 | b = util.slice(b) 95 | 96 | //create mono-buffer from a 97 | var c = util.subbuffer(a, [1]) 98 | ``` 99 | 100 | ### `util.concat(buffer1, [buffer2, buffer3], bufferN, ...)` 101 | Create a new buffer by concatting buffers or list. 102 | Channels are extended to the buffer with maximum number. 103 | 104 | ### `util.repeat(buffer, times)` 105 | Return a new buffer with contents of the initial one repeated defined number of times. 106 | 107 | ### `util.reverse(buffer, target?, start=0, end=-0)` 108 | Reverse `buffer`. Place data to `target` buffer, if any, otherwise modify `buffer` in-place. 109 | 110 | ### `util.invert(buffer, target?, start=0, end=-0)` 111 | Invert `buffer`. Place data to `target` buffer, if any, otherwise modify `buffer` in-place. 112 | 113 | ### `util.zero(buffer)` 114 | Zero all of `buffer`'s channel data. `buffer` is modified in-place. 115 | 116 | ### `util.noise(buffer)` 117 | Fill `buffer` with random data. `buffer` is modified in-place. 118 | 119 | ### `util.equal(bufferA, bufferB, ...)` 120 | Test whether the content of N buffers is the same. 121 | 122 | ```js 123 | let a = util.create(1024, 2) 124 | util.noise(a) 125 | let b = util.clone(a) 126 | let c = util.shallow(a) 127 | util.copy(a, c) 128 | 129 | if (util.equal(a, b, c)) { 130 | //true 131 | } 132 | ``` 133 | 134 | ### `util.fill(buffer, target?, value|(value, i, channel)=>value, start=0, end=-0)` 135 | Fill `buffer` with provided function or value. 136 | Place data to `target` buffer, if any, otherwise modify `buffer` in-place (that covers _map_ functionality). 137 | Pass optional `start` and `end` indexes. 138 | 139 | ```js 140 | let frequency = 440, rate = 44100 141 | 142 | //create 2 seconds buffer 143 | let a = util.create(2 * rate) 144 | 145 | //populate with 440hz sine wave 146 | util.fill(a, (value, i, channel)=>Math.sin(Math.PI * 2 * frequency * i / rate)) 147 | ``` 148 | 149 | ### `util.resize(buffer, length)` 150 | Return new buffer based on the passed one, with shortened/extended length. 151 | Initial data is whether sliced or filled with zeros. Combines `util.pad` and `util.slice`. 152 | 153 | ```js 154 | //change duration to 2s 155 | let b = util.resize(a, 2 * a.sampleRate) 156 | ``` 157 | 158 | ### `util.pad(buffer|length, length|buffer, value=0)` 159 | ### `util.padLeft(buffer, length, value=0)` 160 | ### `util.padRight(buffer, length, value=0)` 161 | Right/left-pad buffer to the length, filling with value. 162 | 163 | ```js 164 | let buf = util.create(3, 1) 165 | util.fill(buf, .2) 166 | 167 | util.pad(buf, 5) // [.2,.2,.2, 0,0] 168 | util.pad(5, buf) // [0,0, .2,.2,.2] 169 | util.pad(buf, 5, .1) // [.2,.2,.2, .1,.1] 170 | util.pad(5, buf, .1) // [.1,.1, .2,.2,.2] 171 | ``` 172 | 173 | ### `util.shift(buffer, offset)` 174 | Shift signal in the time domain by `offset` samples, filling with zeros. 175 | Modify `buffer` in-place. 176 | 177 | ### `util.rotate(buffer, offset)` 178 | Shift signal in the time domain by `offset` samples, in circular fashion. 179 | Modify `buffer` in-place. 180 | 181 | ### `util.normalize(buffer, target?, start=0, end=-0)` 182 | Normalize buffer by the amplitude, bring to -1..+1 range. Channel amplitudes ratio will be preserved. You may want to remove static level beforehead, because normalization preserves zero static level. Note that it is not the same as [array-normalize](https://github.com/dy/array-noramalize). 183 | Places data to `target` buffer, if any, otherwise modifies `buffer` in-place. 184 | 185 | ```js 186 | const AudioBuffer = require('audio-buffer') 187 | const util = require('audio-buffer-utils') 188 | 189 | let buf = AudioBuffer(1, [0, 0.2, 0, -0.4]); 190 | util.normalize(buf); 191 | buf.getChannelData(0) // [0, .5, 0, -1] 192 | ``` 193 | 194 | ### `util.removeStatic(buffer, target?, start=0, end=-0)` 195 | Remove DC (Direct Current) offset from the signal, i.e. remove static level, that is bring mean to zero. DC offset will be reduced for every channel independently. 196 | 197 | ```js 198 | var a = AudioBuffer(2, [.5,.7,.3,.5]) 199 | 200 | util.removeStatic(a) 201 | 202 | a.getChannelData(0) // [-.1, .1] 203 | a.getChannelData(1) // [-.1, .1] 204 | ``` 205 | 206 | ### `util.trim(buffer, threshold=0)` 207 | ### `util.trimLeft(buffer, threshold=0)` 208 | ### `util.trimRight(buffer, threshold=0)` 209 | 210 | Create buffer with trimmed zeros from the start and/or end, by the threshold amplitude. 211 | 212 | ### `util.mix(bufferA, bufferB, ratio|(valA, valB, i, channel)=>val?, offset=0)` 213 | Mix second buffer into the first one. Pass optional weight value or mixing function. 214 | 215 | ### `util.size(buffer)` 216 | Return buffer size, in bytes. Use [pretty-bytes](https://npmjs.org/package/pretty-bytes) package to format bytes to a string, if needed. 217 | 218 | ### `util.data(buffer, data?)` 219 | Get channels' data in array. Pass existing array to transfer the data to it. 220 | Useful in audio-workers to transfer buffer to output. 221 | 222 | ```js 223 | let a = util.create(3, 2) 224 | 225 | let audioData = util.data(a) // [[0,0,0], [0,0,0]] 226 | ``` 227 | 228 | ## Related 229 | 230 | > [audio-buffer](https://github.com/audiojs/audio-buffer) — audio data container, both for node/browser.
231 | > [audio-buffer-list](https://github.com/audiojs/audio-buffer-list) — linked audio buffers sequence structure
232 | > [audio](https://github.com/audiojs/audio) — class for high-level audio manipulations, comprising the functionality of above mentioned. 233 | > [ciseaux](https://github.com/mohayonao/ciseaux) 234 | 235 | ## Credits 236 | 237 | Thanks to [**@jaz303**](https://github.com/jaz303/) for [the initial idea](https://github.com/jaz303/audio-buffer-utils) and collaboration. 238 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module audio-buffer-utils 3 | */ 4 | 5 | 'use strict' 6 | 7 | var AudioBuffer = require('audio-buffer') 8 | var isAudioBuffer = require('is-audio-buffer') 9 | var isBrowser = require('is-browser') 10 | var clamp = require('clamp') 11 | var AudioContext = require('audio-context') 12 | var isBuffer = require('is-buffer') 13 | var createBuffer = require('audio-buffer-from') 14 | 15 | var isNeg = function (number) { 16 | return number === 0 && (1 / number) === -Infinity; 17 | }; 18 | 19 | var nidx = function negIdx (idx, length) { 20 | return idx == null ? 0 : isNeg(idx) ? length : idx <= -length ? 0 : idx < 0 ? (length + (idx % length)) : Math.min(length, idx); 21 | } 22 | 23 | var context 24 | 25 | var utils = { 26 | create: create, 27 | copy: copy, 28 | shallow: shallow, 29 | clone: clone, 30 | reverse: reverse, 31 | invert: invert, 32 | zero: zero, 33 | noise: noise, 34 | equal: equal, 35 | fill: fill, 36 | slice: slice, 37 | concat: concat, 38 | resize: resize, 39 | pad: pad, 40 | padLeft: padLeft, 41 | padRight: padRight, 42 | rotate: rotate, 43 | shift: shift, 44 | normalize: normalize, 45 | removeStatic: removeStatic, 46 | trim: trim, 47 | trimLeft: trimLeft, 48 | trimRight: trimRight, 49 | mix: mix, 50 | size: size, 51 | data: data, 52 | subbuffer: subbuffer, 53 | repeat: repeat 54 | } 55 | 56 | Object.defineProperty(utils, 'context', { 57 | get: function () { 58 | if (!context) context = AudioContext() 59 | return context 60 | } 61 | }) 62 | 63 | module.exports = utils 64 | 65 | /** 66 | * Create buffer from any argument. 67 | * Better constructor than audio-buffer. 68 | */ 69 | function create (src, options, sampleRate) { 70 | var length, data 71 | 72 | if (typeof options === 'number') { 73 | options = {channels: options} 74 | } 75 | else if (typeof options === 'string') { 76 | options = {format: options} 77 | } 78 | else if (!options) { 79 | options = {} 80 | } 81 | if (sampleRate) { 82 | options.sampleRate = sampleRate 83 | } 84 | options.context = utils.context 85 | 86 | return createBuffer(src, options) 87 | } 88 | 89 | 90 | /** 91 | * Copy data from buffer A to buffer B 92 | */ 93 | function copy (from, to, offset) { 94 | validate(from); 95 | validate(to); 96 | 97 | offset = offset || 0; 98 | 99 | for (var channel = 0, l = Math.min(from.numberOfChannels, to.numberOfChannels); channel < l; channel++) { 100 | to.getChannelData(channel).set(from.getChannelData(channel), offset); 101 | } 102 | 103 | return to; 104 | } 105 | 106 | 107 | /** 108 | * Assert argument is AudioBuffer, throw error otherwise. 109 | */ 110 | function validate (buffer) { 111 | if (!isAudioBuffer(buffer)) throw new Error('Argument should be an AudioBuffer instance.'); 112 | } 113 | 114 | 115 | 116 | /** 117 | * Create a buffer with the same characteristics as inBuffer, without copying 118 | * the data. Contents of resulting buffer are undefined. 119 | */ 120 | function shallow (buffer) { 121 | validate(buffer); 122 | 123 | //workaround for faster browser creation 124 | //avoid extra checks & copying inside of AudioBuffer class 125 | if (isBrowser) { 126 | return utils.context.createBuffer(buffer.numberOfChannels, buffer.length, buffer.sampleRate); 127 | } 128 | 129 | return create(buffer.length, buffer.numberOfChannels, buffer.sampleRate); 130 | } 131 | 132 | 133 | /** 134 | * Create clone of a buffer 135 | */ 136 | function clone (buffer) { 137 | return copy(buffer, shallow(buffer)); 138 | } 139 | 140 | 141 | /** 142 | * Reverse samples in each channel 143 | */ 144 | function reverse (buffer, target, start, end) { 145 | validate(buffer); 146 | 147 | //if target buffer is passed 148 | if (!isAudioBuffer(target) && target != null) { 149 | end = start; 150 | start = target; 151 | target = null; 152 | } 153 | 154 | if (target) { 155 | validate(target); 156 | copy(buffer, target); 157 | } 158 | else { 159 | target = buffer; 160 | } 161 | 162 | start = start == null ? 0 : nidx(start, buffer.length); 163 | end = end == null ? buffer.length : nidx(end, buffer.length); 164 | 165 | for (var i = 0, c = target.numberOfChannels; i < c; ++i) { 166 | target.getChannelData(i).subarray(start, end).reverse(); 167 | } 168 | 169 | return target; 170 | } 171 | 172 | 173 | /** 174 | * Invert amplitude of samples in each channel 175 | */ 176 | function invert (buffer, target, start, end) { 177 | //if target buffer is passed 178 | if (!isAudioBuffer(target) && target != null) { 179 | end = start; 180 | start = target; 181 | target = null; 182 | } 183 | 184 | return fill(buffer, target, function (sample) { return -sample; }, start, end); 185 | } 186 | 187 | 188 | /** 189 | * Fill with zeros 190 | */ 191 | function zero (buffer, target, start, end) { 192 | return fill(buffer, target, 0, start, end); 193 | } 194 | 195 | 196 | /** 197 | * Fill with white noise 198 | */ 199 | function noise (buffer, target, start, end) { 200 | return fill(buffer, target, function (sample) { return Math.random() * 2 - 1; }, start, end); 201 | } 202 | 203 | 204 | /** 205 | * Test whether two buffers are the same 206 | */ 207 | function equal (bufferA, bufferB) { 208 | //walk by all the arguments 209 | if (arguments.length > 2) { 210 | for (var i = 0, l = arguments.length - 1; i < l; i++) { 211 | if (!equal(arguments[i], arguments[i + 1])) return false; 212 | } 213 | return true; 214 | } 215 | 216 | validate(bufferA); 217 | validate(bufferB); 218 | 219 | if (bufferA.length !== bufferB.length || bufferA.numberOfChannels !== bufferB.numberOfChannels) return false; 220 | 221 | for (var channel = 0; channel < bufferA.numberOfChannels; channel++) { 222 | var dataA = bufferA.getChannelData(channel); 223 | var dataB = bufferB.getChannelData(channel); 224 | 225 | for (var i = 0; i < dataA.length; i++) { 226 | if (dataA[i] !== dataB[i]) return false; 227 | } 228 | } 229 | 230 | return true; 231 | } 232 | 233 | 234 | 235 | /** 236 | * Generic in-place fill/transform 237 | */ 238 | function fill (buffer, target, value, start, end) { 239 | validate(buffer); 240 | 241 | //if target buffer is passed 242 | if (!isAudioBuffer(target) && target != null) { 243 | //target is bad argument 244 | if (typeof value == 'function') { 245 | target = null; 246 | } 247 | else { 248 | end = start; 249 | start = value; 250 | value = target; 251 | target = null; 252 | } 253 | } 254 | 255 | if (target) { 256 | validate(target); 257 | } 258 | else { 259 | target = buffer; 260 | } 261 | 262 | //resolve optional start/end args 263 | start = start == null ? 0 : nidx(start, buffer.length); 264 | end = end == null ? buffer.length : nidx(end, buffer.length); 265 | //resolve type of value 266 | if (!(value instanceof Function)) { 267 | for (var channel = 0, c = buffer.numberOfChannels; channel < c; channel++) { 268 | var targetData = target.getChannelData(channel); 269 | for (var i = start; i < end; i++) { 270 | targetData[i] = value 271 | } 272 | } 273 | } 274 | else { 275 | for (var channel = 0, c = buffer.numberOfChannels; channel < c; channel++) { 276 | var data = buffer.getChannelData(channel), 277 | targetData = target.getChannelData(channel); 278 | for (var i = start; i < end; i++) { 279 | targetData[i] = value.call(buffer, data[i], i, channel, data); 280 | } 281 | } 282 | } 283 | 284 | return target; 285 | } 286 | 287 | /** 288 | * Repeat buffer 289 | */ 290 | function repeat (buffer, times) { 291 | validate(buffer); 292 | 293 | if (!times || times < 0) return new AudioBuffer(null, {length: 0, numberOfChannels: buffer.numberOfChannels, sampleRate: buffer.sampleRate}) 294 | 295 | if (times === 1) return buffer 296 | 297 | var bufs = [] 298 | for (var i = 0; i < times; i++) { 299 | bufs.push(buffer) 300 | } 301 | 302 | return concat(bufs) 303 | } 304 | 305 | /** 306 | * Return sliced buffer 307 | */ 308 | function slice (buffer, start, end) { 309 | validate(buffer); 310 | 311 | start = start == null ? 0 : nidx(start, buffer.length); 312 | end = end == null ? buffer.length : nidx(end, buffer.length); 313 | 314 | var data = []; 315 | for (var channel = 0; channel < buffer.numberOfChannels; channel++) { 316 | var channelData = buffer.getChannelData(channel) 317 | data.push(channelData.slice(start, end)); 318 | } 319 | return create(data, buffer.numberOfChannels, buffer.sampleRate); 320 | } 321 | 322 | /** 323 | * Create handle for a buffer from subarrays 324 | */ 325 | function subbuffer (buffer, start, end, channels) { 326 | validate(buffer); 327 | 328 | if (Array.isArray(start)) { 329 | channels = start 330 | start = 0; 331 | end = -0; 332 | } 333 | else if (Array.isArray(end)) { 334 | channels = end 335 | end = -0; 336 | } 337 | 338 | if (!Array.isArray(channels)) { 339 | channels = Array(buffer.numberOfChannels) 340 | for (var c = 0; c < buffer.numberOfChannels; c++) { 341 | channels[c] = c 342 | } 343 | } 344 | 345 | start = start == null ? 0 : nidx(start, buffer.length); 346 | end = end == null ? buffer.length : nidx(end, buffer.length); 347 | 348 | var data = []; 349 | for (var i = 0; i < channels.length; i++) { 350 | var channel = channels[i] 351 | var channelData = buffer.getChannelData(channel) 352 | data.push(channelData.subarray(start, end)); 353 | } 354 | 355 | //null-context buffer covers web-audio-api buffer functions 356 | var buf = new AudioBuffer(null, {length: 0, sampleRate: buffer.sampleRate, numberOfChannels: buffer.numberOfChannels}) 357 | 358 | //FIXME: not reliable hack to replace data. Mb use audio-buffer-list? 359 | buf.length = data[0].length 360 | buf._data = null 361 | buf._channelData = data 362 | buf.duration = buf.length / buf.sampleRate 363 | 364 | return buf 365 | } 366 | 367 | /** 368 | * Concat buffer with other buffer(s) 369 | */ 370 | function concat () { 371 | var list = [] 372 | 373 | for (var i = 0, l = arguments.length; i < l; i++) { 374 | var arg = arguments[i] 375 | if (Array.isArray(arg)) { 376 | for (var j = 0; j < arg.length; j++) { 377 | list.push(arg[j]) 378 | } 379 | } 380 | else { 381 | list.push(arg) 382 | } 383 | } 384 | 385 | var channels = 1; 386 | var length = 0; 387 | //FIXME: there might be required more thoughtful resampling, but now I'm lazy sry :( 388 | var sampleRate = 0; 389 | 390 | for (var i = 0; i < list.length; i++) { 391 | var buf = list[i] 392 | validate(buf) 393 | length += buf.length 394 | channels = Math.max(buf.numberOfChannels, channels) 395 | sampleRate = Math.max(buf.sampleRate, sampleRate) 396 | } 397 | 398 | var data = []; 399 | for (var channel = 0; channel < channels; channel++) { 400 | var channelData = new Float32Array(length), offset = 0 401 | 402 | for (var i = 0; i < list.length; i++) { 403 | var buf = list[i] 404 | if (channel < buf.numberOfChannels) { 405 | channelData.set(buf.getChannelData(channel), offset); 406 | } 407 | offset += buf.length 408 | } 409 | 410 | data.push(channelData); 411 | } 412 | 413 | return create(data, channels, sampleRate); 414 | } 415 | 416 | 417 | /** 418 | * Change the length of the buffer, by trimming or filling with zeros 419 | */ 420 | function resize (buffer, length) { 421 | validate(buffer); 422 | 423 | if (length < buffer.length) return slice(buffer, 0, length); 424 | 425 | return concat(buffer, create(length - buffer.length, buffer.numberOfChannels)); 426 | } 427 | 428 | 429 | /** 430 | * Pad buffer to required size 431 | */ 432 | function pad (a, b, value) { 433 | var buffer, length; 434 | 435 | if (typeof a === 'number') { 436 | buffer = b; 437 | length = a; 438 | } else { 439 | buffer = a; 440 | length = b; 441 | } 442 | 443 | value = value || 0; 444 | 445 | validate(buffer); 446 | 447 | //no need to pad 448 | if (length < buffer.length) return buffer; 449 | 450 | //left-pad 451 | if (buffer === b) { 452 | return concat(fill(create(length - buffer.length, buffer.numberOfChannels), value), buffer); 453 | } 454 | 455 | //right-pad 456 | return concat(buffer, fill(create(length - buffer.length, buffer.numberOfChannels), value)); 457 | } 458 | function padLeft (data, len, value) { 459 | return pad(len, data, value) 460 | } 461 | function padRight (data, len, value) { 462 | return pad(data, len, value) 463 | } 464 | 465 | 466 | 467 | /** 468 | * Shift content of the buffer in circular fashion 469 | */ 470 | function rotate (buffer, offset) { 471 | validate(buffer); 472 | 473 | for (var channel = 0; channel < buffer.numberOfChannels; channel++) { 474 | var cData = buffer.getChannelData(channel); 475 | var srcData = cData.slice(); 476 | for (var i = 0, l = cData.length, idx; i < l; i++) { 477 | idx = (offset + (offset + i < 0 ? l + i : i )) % l; 478 | cData[idx] = srcData[i]; 479 | } 480 | } 481 | 482 | return buffer; 483 | } 484 | 485 | 486 | /** 487 | * Shift content of the buffer 488 | */ 489 | function shift (buffer, offset) { 490 | validate(buffer); 491 | 492 | for (var channel = 0; channel < buffer.numberOfChannels; channel++) { 493 | var cData = buffer.getChannelData(channel); 494 | if (offset > 0) { 495 | for (var i = cData.length - offset; i--;) { 496 | cData[i + offset] = cData[i]; 497 | } 498 | } 499 | else { 500 | for (var i = -offset, l = cData.length - offset; i < l; i++) { 501 | cData[i + offset] = cData[i] || 0; 502 | } 503 | } 504 | } 505 | 506 | return buffer; 507 | } 508 | 509 | 510 | /** 511 | * Normalize buffer by the maximum value, 512 | * limit values by the -1..1 range 513 | */ 514 | function normalize (buffer, target, start, end) { 515 | //resolve optional target arg 516 | if (!isAudioBuffer(target)) { 517 | end = start; 518 | start = target; 519 | target = null; 520 | } 521 | 522 | start = start == null ? 0 : nidx(start, buffer.length); 523 | end = end == null ? buffer.length : nidx(end, buffer.length); 524 | 525 | //for every channel bring it to max-min amplitude range 526 | var max = 0 527 | 528 | for (var c = 0; c < buffer.numberOfChannels; c++) { 529 | var data = buffer.getChannelData(c) 530 | for (var i = start; i < end; i++) { 531 | max = Math.max(Math.abs(data[i]), max) 532 | } 533 | } 534 | 535 | var amp = Math.max(1 / max, 1) 536 | 537 | return fill(buffer, target, function (value, i, ch) { 538 | return clamp(value * amp, -1, 1) 539 | }, start, end); 540 | } 541 | 542 | /** 543 | * remove DC offset 544 | */ 545 | function removeStatic (buffer, target, start, end) { 546 | var means = mean(buffer, start, end) 547 | 548 | return fill(buffer, target, function (value, i, ch) { 549 | return value - means[ch]; 550 | }, start, end); 551 | } 552 | 553 | /** 554 | * Get average level per-channel 555 | */ 556 | function mean (buffer, start, end) { 557 | validate(buffer) 558 | 559 | start = start == null ? 0 : nidx(start, buffer.length); 560 | end = end == null ? buffer.length : nidx(end, buffer.length); 561 | 562 | if (end - start < 1) return [] 563 | 564 | var result = [] 565 | 566 | for (var c = 0; c < buffer.numberOfChannels; c++) { 567 | var sum = 0 568 | var data = buffer.getChannelData(c) 569 | for (var i = start; i < end; i++) { 570 | sum += data[i] 571 | } 572 | result.push(sum / (end - start)) 573 | } 574 | 575 | return result 576 | } 577 | 578 | 579 | /** 580 | * Trim sound (remove zeros from the beginning and the end) 581 | */ 582 | function trim (buffer, level) { 583 | return trimInternal(buffer, level, true, true); 584 | } 585 | 586 | function trimLeft (buffer, level) { 587 | return trimInternal(buffer, level, true, false); 588 | } 589 | 590 | function trimRight (buffer, level) { 591 | return trimInternal(buffer, level, false, true); 592 | } 593 | 594 | function trimInternal(buffer, level, trimLeft, trimRight) { 595 | validate(buffer); 596 | 597 | level = (level == null) ? 0 : Math.abs(level); 598 | 599 | var start, end; 600 | 601 | if (trimLeft) { 602 | start = buffer.length; 603 | //FIXME: replace with indexOF 604 | for (var channel = 0, c = buffer.numberOfChannels; channel < c; channel++) { 605 | var data = buffer.getChannelData(channel); 606 | for (var i = 0; i < data.length; i++) { 607 | if (i > start) break; 608 | if (Math.abs(data[i]) > level) { 609 | start = i; 610 | break; 611 | } 612 | } 613 | } 614 | } else { 615 | start = 0; 616 | } 617 | 618 | if (trimRight) { 619 | end = 0; 620 | //FIXME: replace with lastIndexOf 621 | for (var channel = 0, c = buffer.numberOfChannels; channel < c; channel++) { 622 | var data = buffer.getChannelData(channel); 623 | for (var i = data.length - 1; i >= 0; i--) { 624 | if (i < end) break; 625 | if (Math.abs(data[i]) > level) { 626 | end = i + 1; 627 | break; 628 | } 629 | } 630 | } 631 | } else { 632 | end = buffer.length; 633 | } 634 | 635 | return slice(buffer, start, end); 636 | } 637 | 638 | 639 | /** 640 | * Mix current buffer with the other one. 641 | * The reason to modify bufferA instead of returning the new buffer 642 | * is reduced amount of calculations and flexibility. 643 | * If required, the cloning can be done before mixing, which will be the same. 644 | */ 645 | function mix (bufferA, bufferB, ratio, offset) { 646 | validate(bufferA); 647 | validate(bufferB); 648 | 649 | if (ratio == null) ratio = 0.5; 650 | var fn = ratio instanceof Function ? ratio : function (a, b) { 651 | return a * (1 - ratio) + b * ratio; 652 | }; 653 | 654 | if (offset == null) offset = 0; 655 | else if (offset < 0) offset += bufferA.length; 656 | 657 | for (var channel = 0; channel < bufferA.numberOfChannels; channel++) { 658 | var aData = bufferA.getChannelData(channel); 659 | var bData = bufferB.getChannelData(channel); 660 | 661 | for (var i = offset, j = 0; i < bufferA.length && j < bufferB.length; i++, j++) { 662 | aData[i] = fn.call(bufferA, aData[i], bData[j], j, channel); 663 | } 664 | } 665 | 666 | return bufferA; 667 | } 668 | 669 | 670 | /** 671 | * Size of a buffer, in bytes 672 | */ 673 | function size (buffer) { 674 | validate(buffer); 675 | 676 | return buffer.numberOfChannels * buffer.getChannelData(0).byteLength; 677 | } 678 | 679 | 680 | /** 681 | * Return array with buffer’s per-channel data 682 | */ 683 | function data (buffer, data) { 684 | validate(buffer); 685 | 686 | //ensure output data array, if not defined 687 | data = data || []; 688 | 689 | //transfer data per-channel 690 | for (var channel = 0; channel < buffer.numberOfChannels; channel++) { 691 | if (ArrayBuffer.isView(data[channel])) { 692 | data[channel].set(buffer.getChannelData(channel)); 693 | } 694 | else { 695 | data[channel] = buffer.getChannelData(channel); 696 | } 697 | } 698 | 699 | return data; 700 | } 701 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "audio-buffer-utils", 3 | "version": "5.1.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "almost-equal": { 8 | "version": "1.1.0", 9 | "resolved": "https://registry.npmjs.org/almost-equal/-/almost-equal-1.1.0.tgz", 10 | "integrity": "sha1-+FHGMROHV5lCdqou++jfowZszN0=", 11 | "dev": true 12 | }, 13 | "atob-lite": { 14 | "version": "2.0.0", 15 | "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", 16 | "integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY=" 17 | }, 18 | "audio-buffer": { 19 | "version": "4.0.4", 20 | "resolved": "https://registry.npmjs.org/audio-buffer/-/audio-buffer-4.0.4.tgz", 21 | "integrity": "sha512-phH+MR3G+N/PO5ZKKxx7HlU6vJwAJFa0+FCaTjr/4lUZU/RCjUTqlk3nMJTRy5+b+6cbx8m//EtwZOVI5Ht9+w==", 22 | "requires": { 23 | "audio-context": "^1.0.0" 24 | } 25 | }, 26 | "audio-buffer-from": { 27 | "version": "1.1.1", 28 | "resolved": "https://registry.npmjs.org/audio-buffer-from/-/audio-buffer-from-1.1.1.tgz", 29 | "integrity": "sha512-8Wcira24z+26GXDFe7ZFRF1bJm1iWrz8O+XL8iNZxZjxqAPXIoK1IrJiOqStv35ASPbRjG57ZK/T0WO92MDUSg==", 30 | "requires": { 31 | "audio-buffer": "^4.0.4", 32 | "audio-context": "^1.0.1", 33 | "audio-format": "^2.0.0", 34 | "is-audio-buffer": "^1.0.11", 35 | "is-plain-obj": "^1.1.0", 36 | "pcm-convert": "^1.6.0", 37 | "pick-by-alias": "^1.2.0", 38 | "string-to-arraybuffer": "^1.0.0" 39 | } 40 | }, 41 | "audio-context": { 42 | "version": "1.0.3", 43 | "resolved": "https://registry.npmjs.org/audio-context/-/audio-context-1.0.3.tgz", 44 | "integrity": "sha512-RH3/rM74f2ITlohhjgC7oYZVS97wtv/SEjXLCzEinnrIPIDxc39m2aFc6wmdkM0NYRKo1DMleYPMAIbnTRW0eA==" 45 | }, 46 | "audio-format": { 47 | "version": "2.3.2", 48 | "resolved": "https://registry.npmjs.org/audio-format/-/audio-format-2.3.2.tgz", 49 | "integrity": "sha512-5IA2grZhaVhpGxX6lbJm8VVh/SKQULMXXrFxuiodi0zhzDPRB8BJfieo89AclEQv4bDxZRH4lv06qNnxqkFhKQ==", 50 | "requires": { 51 | "is-audio-buffer": "^1.0.11", 52 | "is-buffer": "^1.1.5", 53 | "is-plain-obj": "^1.1.0", 54 | "pick-by-alias": "^1.2.0", 55 | "sample-rate": "^2.0.0" 56 | }, 57 | "dependencies": { 58 | "is-buffer": { 59 | "version": "1.1.6", 60 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 61 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 62 | } 63 | } 64 | }, 65 | "balanced-match": { 66 | "version": "1.0.0", 67 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 68 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 69 | "dev": true 70 | }, 71 | "brace-expansion": { 72 | "version": "1.1.11", 73 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 74 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 75 | "dev": true, 76 | "requires": { 77 | "balanced-match": "^1.0.0", 78 | "concat-map": "0.0.1" 79 | } 80 | }, 81 | "clamp": { 82 | "version": "1.0.1", 83 | "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz", 84 | "integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=" 85 | }, 86 | "concat-map": { 87 | "version": "0.0.1", 88 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 89 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 90 | "dev": true 91 | }, 92 | "deep-equal": { 93 | "version": "1.0.1", 94 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 95 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 96 | "dev": true 97 | }, 98 | "define-properties": { 99 | "version": "1.1.3", 100 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 101 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 102 | "dev": true, 103 | "requires": { 104 | "object-keys": "^1.0.12" 105 | } 106 | }, 107 | "defined": { 108 | "version": "1.0.0", 109 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 110 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 111 | "dev": true 112 | }, 113 | "es-abstract": { 114 | "version": "1.13.0", 115 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", 116 | "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", 117 | "dev": true, 118 | "requires": { 119 | "es-to-primitive": "^1.2.0", 120 | "function-bind": "^1.1.1", 121 | "has": "^1.0.3", 122 | "is-callable": "^1.1.4", 123 | "is-regex": "^1.0.4", 124 | "object-keys": "^1.0.12" 125 | } 126 | }, 127 | "es-to-primitive": { 128 | "version": "1.2.0", 129 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", 130 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", 131 | "dev": true, 132 | "requires": { 133 | "is-callable": "^1.1.4", 134 | "is-date-object": "^1.0.1", 135 | "is-symbol": "^1.0.2" 136 | } 137 | }, 138 | "for-each": { 139 | "version": "0.3.3", 140 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 141 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 142 | "dev": true, 143 | "requires": { 144 | "is-callable": "^1.1.3" 145 | } 146 | }, 147 | "fs.realpath": { 148 | "version": "1.0.0", 149 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 150 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 151 | "dev": true 152 | }, 153 | "function-bind": { 154 | "version": "1.1.1", 155 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 156 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 157 | "dev": true 158 | }, 159 | "glob": { 160 | "version": "7.1.3", 161 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 162 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 163 | "dev": true, 164 | "requires": { 165 | "fs.realpath": "^1.0.0", 166 | "inflight": "^1.0.4", 167 | "inherits": "2", 168 | "minimatch": "^3.0.4", 169 | "once": "^1.3.0", 170 | "path-is-absolute": "^1.0.0" 171 | } 172 | }, 173 | "has": { 174 | "version": "1.0.3", 175 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 176 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 177 | "dev": true, 178 | "requires": { 179 | "function-bind": "^1.1.1" 180 | } 181 | }, 182 | "has-symbols": { 183 | "version": "1.0.0", 184 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 185 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 186 | "dev": true 187 | }, 188 | "inflight": { 189 | "version": "1.0.6", 190 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 191 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 192 | "dev": true, 193 | "requires": { 194 | "once": "^1.3.0", 195 | "wrappy": "1" 196 | } 197 | }, 198 | "inherits": { 199 | "version": "2.0.3", 200 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 201 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 202 | "dev": true 203 | }, 204 | "iota-array": { 205 | "version": "1.0.0", 206 | "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", 207 | "integrity": "sha1-ge9X/l0FgUzVjCSDYyqZwwoOgIc=", 208 | "dev": true 209 | }, 210 | "is-audio-buffer": { 211 | "version": "1.1.0", 212 | "resolved": "https://registry.npmjs.org/is-audio-buffer/-/is-audio-buffer-1.1.0.tgz", 213 | "integrity": "sha512-fmPC/dizJmP4ITCsW5oTQGMJ9wZVE+A/zAe6FQo3XwgERxmXHmm3ON5XkWDAxmyxvsrDmWx3NArpSgamp/59AA==" 214 | }, 215 | "is-base64": { 216 | "version": "0.1.0", 217 | "resolved": "https://registry.npmjs.org/is-base64/-/is-base64-0.1.0.tgz", 218 | "integrity": "sha512-WRRyllsGXJM7ZN7gPTCCQ/6wNPTRDwiWdPK66l5sJzcU/oOzcIcRRf0Rux8bkpox/1yjt0F6VJRsQOIG2qz5sg==" 219 | }, 220 | "is-browser": { 221 | "version": "2.1.0", 222 | "resolved": "https://registry.npmjs.org/is-browser/-/is-browser-2.1.0.tgz", 223 | "integrity": "sha512-F5rTJxDQ2sW81fcfOR1GnCXT6sVJC104fCyfj+mjpwNEwaPYSn5fte5jiHmBg3DHsIoL/l8Kvw5VN5SsTRcRFQ==" 224 | }, 225 | "is-buffer": { 226 | "version": "2.0.3", 227 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", 228 | "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" 229 | }, 230 | "is-callable": { 231 | "version": "1.1.4", 232 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 233 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 234 | "dev": true 235 | }, 236 | "is-date-object": { 237 | "version": "1.0.1", 238 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 239 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 240 | "dev": true 241 | }, 242 | "is-plain-obj": { 243 | "version": "1.1.0", 244 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", 245 | "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" 246 | }, 247 | "is-regex": { 248 | "version": "1.0.4", 249 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 250 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 251 | "dev": true, 252 | "requires": { 253 | "has": "^1.0.1" 254 | } 255 | }, 256 | "is-symbol": { 257 | "version": "1.0.2", 258 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", 259 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", 260 | "dev": true, 261 | "requires": { 262 | "has-symbols": "^1.0.0" 263 | } 264 | }, 265 | "minimatch": { 266 | "version": "3.0.4", 267 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 268 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 269 | "dev": true, 270 | "requires": { 271 | "brace-expansion": "^1.1.7" 272 | } 273 | }, 274 | "minimist": { 275 | "version": "1.2.0", 276 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 277 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 278 | "dev": true 279 | }, 280 | "ndarray": { 281 | "version": "1.0.18", 282 | "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.18.tgz", 283 | "integrity": "sha1-tg06cyJOxVXQ+qeXEeUCRI/T95M=", 284 | "dev": true, 285 | "requires": { 286 | "iota-array": "^1.0.0", 287 | "is-buffer": "^1.0.2" 288 | }, 289 | "dependencies": { 290 | "is-buffer": { 291 | "version": "1.1.6", 292 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 293 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", 294 | "dev": true 295 | } 296 | } 297 | }, 298 | "negative-index": { 299 | "version": "1.0.3", 300 | "resolved": "https://registry.npmjs.org/negative-index/-/negative-index-1.0.3.tgz", 301 | "integrity": "sha512-SzyzUmUYmerjD9gOEqizFRcDakY+Cf/59rOfqMi4bT212PN6g+jkUOjr/Sx9oOUOxi7qf6dsRwE0PPeRIq4DRw==", 302 | "requires": { 303 | "negative-zero": "^2.0.0" 304 | } 305 | }, 306 | "negative-zero": { 307 | "version": "2.0.0", 308 | "resolved": "https://registry.npmjs.org/negative-zero/-/negative-zero-2.0.0.tgz", 309 | "integrity": "sha1-7jAaowFLQBgJFqGeN3kqN1ZwhVc=" 310 | }, 311 | "object-assign": { 312 | "version": "4.1.1", 313 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 314 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 315 | }, 316 | "object-inspect": { 317 | "version": "1.6.0", 318 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", 319 | "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", 320 | "dev": true 321 | }, 322 | "object-keys": { 323 | "version": "1.1.0", 324 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", 325 | "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", 326 | "dev": true 327 | }, 328 | "once": { 329 | "version": "1.4.0", 330 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 331 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 332 | "dev": true, 333 | "requires": { 334 | "wrappy": "1" 335 | } 336 | }, 337 | "path-is-absolute": { 338 | "version": "1.0.1", 339 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 340 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 341 | "dev": true 342 | }, 343 | "path-parse": { 344 | "version": "1.0.6", 345 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 346 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 347 | "dev": true 348 | }, 349 | "pcm-convert": { 350 | "version": "1.6.5", 351 | "resolved": "https://registry.npmjs.org/pcm-convert/-/pcm-convert-1.6.5.tgz", 352 | "integrity": "sha512-5CEspU4j8aEQ80AhNbcLfpT0apc93E6endFxahWd4sV70I6PN7LPdz8GoYm/1qr400K9bUVsVA+KxNgbFROZPw==", 353 | "requires": { 354 | "audio-format": "^2.3.2", 355 | "is-audio-buffer": "^1.0.11", 356 | "is-buffer": "^1.1.5", 357 | "object-assign": "^4.1.1" 358 | }, 359 | "dependencies": { 360 | "is-buffer": { 361 | "version": "1.1.6", 362 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 363 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 364 | } 365 | } 366 | }, 367 | "pick-by-alias": { 368 | "version": "1.2.0", 369 | "resolved": "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz", 370 | "integrity": "sha1-X3yysfIabh6ISgyHhVqko3NhEHs=" 371 | }, 372 | "resolve": { 373 | "version": "1.10.0", 374 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", 375 | "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", 376 | "dev": true, 377 | "requires": { 378 | "path-parse": "^1.0.6" 379 | } 380 | }, 381 | "resumer": { 382 | "version": "0.0.0", 383 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 384 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 385 | "dev": true, 386 | "requires": { 387 | "through": "~2.3.4" 388 | } 389 | }, 390 | "sample-rate": { 391 | "version": "2.0.1", 392 | "resolved": "https://registry.npmjs.org/sample-rate/-/sample-rate-2.0.1.tgz", 393 | "integrity": "sha512-AIK0vVBiAEObmpJOxQu/WCyklnWGqzTSDII4O7nBo+SJHmfgBUiYhgV/Y3Ohz76gfSlU6R5CIAKggj+nAOLSvg==" 394 | }, 395 | "string-to-arraybuffer": { 396 | "version": "1.0.2", 397 | "resolved": "https://registry.npmjs.org/string-to-arraybuffer/-/string-to-arraybuffer-1.0.2.tgz", 398 | "integrity": "sha512-DaGZidzi93dwjQen5I2osxR9ERS/R7B1PFyufNMnzhj+fmlDQAc1DSDIJVJhgI8Oq221efIMbABUBdPHDRt43Q==", 399 | "requires": { 400 | "atob-lite": "^2.0.0", 401 | "is-base64": "^0.1.0" 402 | } 403 | }, 404 | "string.prototype.trim": { 405 | "version": "1.1.2", 406 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", 407 | "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", 408 | "dev": true, 409 | "requires": { 410 | "define-properties": "^1.1.2", 411 | "es-abstract": "^1.5.0", 412 | "function-bind": "^1.0.2" 413 | } 414 | }, 415 | "tape": { 416 | "version": "4.10.1", 417 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.10.1.tgz", 418 | "integrity": "sha512-G0DywYV1jQeY3axeYnXUOt6ktnxS9OPJh97FGR3nrua8lhWi1zPflLxcAHavZ7Jf3qUfY7cxcVIVFa4mY2IY1w==", 419 | "dev": true, 420 | "requires": { 421 | "deep-equal": "~1.0.1", 422 | "defined": "~1.0.0", 423 | "for-each": "~0.3.3", 424 | "function-bind": "~1.1.1", 425 | "glob": "~7.1.3", 426 | "has": "~1.0.3", 427 | "inherits": "~2.0.3", 428 | "minimist": "~1.2.0", 429 | "object-inspect": "~1.6.0", 430 | "resolve": "~1.10.0", 431 | "resumer": "~0.0.0", 432 | "string.prototype.trim": "~1.1.2", 433 | "through": "~2.3.8" 434 | } 435 | }, 436 | "through": { 437 | "version": "2.3.8", 438 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 439 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 440 | "dev": true 441 | }, 442 | "wrappy": { 443 | "version": "1.0.2", 444 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 445 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 446 | "dev": true 447 | } 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "audio-buffer-utils", 3 | "version": "5.1.2", 4 | "description": "Utility functions for AudioBuffers", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js", 8 | "test:browser": "budo test.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/audiojs/audio-buffer-utils.git" 13 | }, 14 | "keywords": [ 15 | "web", 16 | "audio", 17 | "audiojs", 18 | "api", 19 | "buffer", 20 | "web audio", 21 | "audio-buffer" 22 | ], 23 | "author": "Jason Frame (http://jasonframe.co.uk)", 24 | "contributors": [ 25 | "Dmitry Yv " 26 | ], 27 | "license": "ISC", 28 | "bugs": { 29 | "url": "https://github.com/audiojs/audio-buffer-utils/issues" 30 | }, 31 | "homepage": "https://github.com/audiojs/audio-buffer-utils", 32 | "devDependencies": { 33 | "almost-equal": "^1.1.0", 34 | "is-browser": "^2.0.1", 35 | "ndarray": "^1.0.18", 36 | "tape": "^4.6.3" 37 | }, 38 | "dependencies": { 39 | "audio-buffer": "^4.0.0", 40 | "audio-buffer-from": "^1.0.0", 41 | "audio-context": "^1.0.0", 42 | "clamp": "^1.0.1", 43 | "is-audio-buffer": "^1.0.8", 44 | "is-browser": "^2.0.1", 45 | "is-buffer": "^2.0.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var util = require('./'); 2 | var AudioBuffer = require('audio-buffer'); 3 | var isBrowser = require('is-browser'); 4 | var test = require('tape') 5 | var almost = require('almost-equal') 6 | var NDArray = require('ndarray') 7 | 8 | 9 | function almostEqual (x, y) { 10 | if (x.length && y.length) return x.every(function (x, i) { 11 | return almostEqual(x, y[i]); 12 | }); 13 | 14 | var EPSILON = 1e-5; 15 | if (!almost(x, y, EPSILON)) t.fail(x, y, 16 | `${x} ≈ ${y}`, '≈'); 17 | 18 | return true; 19 | }; 20 | 21 | test('zero constructor', function (t) { 22 | var buffer = util.create() 23 | 24 | t.equal(buffer.length, 1) 25 | t.equal(buffer.numberOfChannels, 1) 26 | 27 | t.end() 28 | }); 29 | 30 | test('from Array', function (t) { 31 | var buffer = util.create([ 32 | 0, 1, 0, 1, 0, 1 33 | ], 2); 34 | 35 | t.deepEqual(buffer.getChannelData(0), [0, 1, 0]); 36 | t.deepEqual(buffer.getChannelData(1), [1, 0, 1]); 37 | t.end() 38 | }); 39 | 40 | test('from Number', function (t) { 41 | var buffer = util.create(2, 2) 42 | 43 | t.equal(buffer.length, 2) 44 | 45 | t.end() 46 | }) 47 | 48 | test('from Float32Array', function (t) { 49 | var buffer = util.create(new Float32Array([ 50 | 0, 1, 0, 1, 0, 1, 0, 1, 0 51 | ]), 3); 52 | 53 | t.deepEqual(buffer.getChannelData(0), [0, 1, 0]); 54 | t.deepEqual(buffer.getChannelData(1), [1, 0, 1]); 55 | t.deepEqual(buffer.getChannelData(2), [0, 1, 0]); 56 | t.end() 57 | }); 58 | 59 | test('from Buffer', function (t) { 60 | var data = new Buffer(8*3); 61 | data.writeFloatLE(1.0, 0); 62 | data.writeFloatLE(-1.0, 4); 63 | data.writeFloatLE(0.5, 8); 64 | data.writeFloatLE(-0.5, 12); 65 | data.writeFloatLE(-1, 16); 66 | data.writeFloatLE(0.5, 20); 67 | 68 | var buffer = util.create(data, 'float32 3-channel') 69 | 70 | t.deepEqual(buffer.getChannelData(0), [1, -1.0]); 71 | t.deepEqual(buffer.getChannelData(1), [0.5, -0.5]); 72 | t.deepEqual(buffer.getChannelData(2), [-1, 0.5]); 73 | t.end() 74 | }); 75 | 76 | test('from AudioBuffer', function (t) { 77 | var a1 = util.create([1,-1,0.5,-0.5], 2); 78 | var a2 = util.create(a1); 79 | var a3 = util.create(a1); 80 | 81 | t.notEqual(a1, a2); 82 | t.notEqual(a1, a3); 83 | t.deepEqual(a3.getChannelData(1), [0.5,-0.5]); 84 | 85 | a1.getChannelData(0)[0] = 0; 86 | t.deepEqual(a1.getChannelData(0), [0,-1]); 87 | t.deepEqual(a2.getChannelData(0), [1,-1]); 88 | t.end() 89 | }); 90 | 91 | test('from ArrayBuffer', function (t) { 92 | var a = util.create( (new Float32Array([1,-1,0.5,-0.5])).buffer, 'float32 stereo'); 93 | t.deepEqual(a.getChannelData(1), [0.5,-0.5]); 94 | t.deepEqual(a.getChannelData(0), [1,-1]); 95 | t.end() 96 | }); 97 | 98 | test('from NDArray', function (t) { 99 | var a = util.create( new NDArray(new Float32Array([1,-1,0.5,-0.5]), [2,2])); 100 | t.deepEqual(a.getChannelData(1), [0.5,-0.5]); 101 | t.deepEqual(a.getChannelData(0), [1,-1]); 102 | 103 | //FIXME: there might need more tests, like detection of ndarray dimensions etc 104 | t.end() 105 | }); 106 | 107 | test('from Array of Arrays', function (t) { 108 | var a = util.create([ [1, -1], [0.5,-0.5], [-1, 0.5] ]); 109 | t.deepEqual(a.getChannelData(1), [0.5,-0.5]); 110 | t.deepEqual(a.getChannelData(0), [1,-1]); 111 | 112 | var a = util.create([ [1, -1], [0.5,-0.5], [-1, 0.5] ] ); 113 | t.deepEqual(a.getChannelData(1), [0.5,-0.5]); 114 | t.deepEqual(a.getChannelData(0), [1,-1]); 115 | t.deepEqual(a.getChannelData(2), [-1,0.5]); 116 | 117 | t.notEqual(Array.isArray(a.getChannelData(0))) 118 | 119 | t.end() 120 | }); 121 | 122 | if (isBrowser) test('from WAABuffer', function (t) { 123 | var buf = util.context.createBuffer(3, 2, 44100); 124 | buf.getChannelData(0).fill(1); 125 | buf.getChannelData(1).fill(-1); 126 | buf.getChannelData(2).fill(0); 127 | 128 | var a = util.create( buf, 3 ); 129 | t.deepEqual(a.getChannelData(2), [0,0]); 130 | t.deepEqual(a.getChannelData(1), [-1,-1]); 131 | t.deepEqual(a.getChannelData(0), [1,1]); 132 | 133 | t.throws(function () { 134 | util.create(0, 2) 135 | }) 136 | //test that data is bound 137 | //NOTE: it seems that is shouldn’t - we can gracefully clone the buffer 138 | // buf.getChannelData(2).fill(0.5); 139 | // t.deepEqual(a.getChannelData(2), buf.getChannelData(2)); 140 | 141 | t.end() 142 | }); 143 | 144 | test('length', function (t) { 145 | var buffer = util.create(Array(12), 1); 146 | t.equal(buffer.length, 12); 147 | var buffer = util.create(Array(12), 2); 148 | t.equal(buffer.length, 6); 149 | var buffer = util.create(Array(12), 3); 150 | t.equal(buffer.length, 4); 151 | var buffer = util.create(Array(12), 4); 152 | t.equal(buffer.length, 3); 153 | var buffer = util.create(Array(12), 6); 154 | t.equal(buffer.length, 2); 155 | 156 | t.end() 157 | }); 158 | 159 | 160 | test('clone', function (t) { 161 | var a = util.create(10, 3, 3000); 162 | var b = util.create(a); 163 | var c = util.create(a, 2, 4000); 164 | 165 | t.notEqual(a, b); 166 | t.deepEqual(a.getChannelData(0), b.getChannelData(0)); 167 | t.deepEqual(a.getChannelData(2), b.getChannelData(2)); 168 | t.equal(b.numberOfChannels, 3); 169 | t.equal(b.sampleRate, 3000); 170 | t.equal(c.sampleRate, 4000); 171 | t.equal(c.numberOfChannels, 2); 172 | t.deepEqual(a.getChannelData(0), c.getChannelData(0)); 173 | t.deepEqual(a.getChannelData(1), c.getChannelData(1)); 174 | 175 | if (isBrowser) { 176 | var a = util.context.createBuffer(2,10,44100); 177 | var b = util.create(a); 178 | 179 | t.notEqual(a, b); 180 | t.notEqual(a.getChannelData(0), b.getChannelData(0)); 181 | t.deepEqual(a.getChannelData(0), b.getChannelData(0)); 182 | } 183 | t.end() 184 | }); 185 | 186 | 187 | 188 | test('create', function (t) { 189 | var buf1 = util.create(); 190 | t.equal(buf1.length, 1); 191 | t.equal(buf1.numberOfChannels, 1); 192 | 193 | var buf2 = util.create([[0,1], [0,1], [1,0]]); 194 | t.deepEqual(buf2.getChannelData(2), [1, 0]); 195 | t.equal(buf2.numberOfChannels, 3) 196 | 197 | var buf3 = util.create([new Float32Array([0,1]), new Float32Array([0,1]), new Float32Array([1,0])]); 198 | t.deepEqual(buf3.getChannelData(2), [1, 0]); 199 | 200 | var buf4 = util.create(5, 2, 44100); 201 | t.deepEqual(buf4.getChannelData(0), [0,0,0,0,0]); 202 | t.equal(buf4.numberOfChannels, 2) 203 | 204 | var buf5 = util.create(buf4); 205 | t.notEqual(buf4, buf5); 206 | t.notEqual(buf4.getChannelData(0), buf5.getChannelData(0)); 207 | t.deepEqual(buf5.getChannelData(0), [0,0,0,0,0]); 208 | t.equal(buf5.numberOfChannels, 2) 209 | 210 | var buf6 = util.create([1,0,0,1], 2); 211 | t.deepEqual(buf6.getChannelData(1), [0,1]); 212 | t.equal(buf5.numberOfChannels, 2) 213 | 214 | var buf7 = util.create([1,0,0,1], 1); 215 | t.deepEqual(buf7.getChannelData(0), [1,0,0,1]); 216 | t.equal(buf7.numberOfChannels, 1) 217 | t.end() 218 | }); 219 | 220 | test('equal', function (t) { 221 | var buf1 = util.create([1, 0, -1, 0], 2); 222 | var buf2 = util.create([1, 0, -1, 0], 2); 223 | var buf3 = util.create([1, 0, 1, 0], 2); 224 | var buf4 = util.create([1, 0, 1, 0, 1], 2); //the last sample is lost 225 | var buf5 = util.create([1, 0, 1, 0], 2); 226 | 227 | t.ok(util.equal(buf1, buf2)); 228 | t.ok(!util.equal(buf1, buf3)); 229 | t.ok(!util.equal(buf1, buf4)); 230 | t.ok(util.equal(buf3, buf4)); 231 | t.ok(util.equal(buf3, buf4, buf5)); 232 | t.ok(!util.equal(buf4, buf5, buf3, buf1)); 233 | 234 | t.throws(function () { 235 | util.equal(buf1, new Float32Array(1)); 236 | }); 237 | t.end() 238 | }); 239 | 240 | test('shallow', function (t) { 241 | var buf1 = util.create([0, 1, 2, 3]); 242 | var buf2 = util.shallow(buf1); 243 | 244 | t.equal(buf1.length, buf2.length); 245 | t.equal(buf1.numberOfChannels, buf2.numberOfChannels); 246 | t.equal(buf1.sampleRate, buf2.sampleRate); 247 | 248 | t.throws(function () { 249 | util.shallow(new Float32Array(1)); 250 | }); 251 | t.end() 252 | }); 253 | 254 | 255 | test('clone', function (t) { 256 | var buf1 = util.create([1, 0, -1, 0], 2); 257 | var buf2 = util.clone(buf1); 258 | 259 | t.ok(util.equal(buf1, buf2)); 260 | t.notEqual(buf1, buf2); 261 | 262 | buf2.getChannelData(0)[1] = 0.5; 263 | t.deepEqual(buf1.getChannelData(0), [1, 0]); 264 | t.deepEqual(buf2.getChannelData(0), [1, 0.5]); 265 | 266 | t.throws(function () { 267 | util.clone({}); 268 | }); 269 | t.end() 270 | }); 271 | 272 | 273 | test('copy', function (t) { 274 | var buf1 = util.create([1, 0, -1, 0], 2); 275 | var buf2 = util.shallow(buf1); 276 | 277 | util.copy(buf1, buf2); 278 | 279 | t.ok(util.equal(buf1, buf2)); 280 | t.notEqual(buf1, buf2); 281 | 282 | buf2.getChannelData(0)[1] = 0.5; 283 | t.deepEqual(buf1.getChannelData(0), [1, 0]); 284 | t.deepEqual(buf2.getChannelData(0), [1, 0.5]); 285 | 286 | var buf3 = util.create(8); 287 | util.copy(buf2, buf3, 4); 288 | t.deepEqual(buf3.getChannelData(0), [0,0,0,0, 1, 0.5, 0, 0]); 289 | 290 | // util.copy(buf3, buf2); 291 | t.end() 292 | }); 293 | 294 | 295 | test('clone - backing arrays are not shared between buffers', function (t) { 296 | var buf1 = util.create([0, 1, 2, 3, 4]); 297 | var buf2 = util.clone(buf1); 298 | 299 | buf2.getChannelData(0)[0] = 100; 300 | t.equal(0, buf1.getChannelData(0)[0]); 301 | t.end() 302 | }); 303 | 304 | 305 | test('reverse', function (t) { 306 | var buf1 = util.create([1, 0, -1, 0], 2); 307 | util.reverse(buf1); 308 | 309 | t.deepEqual(buf1.getChannelData(0), [0, 1]); 310 | t.deepEqual(buf1.getChannelData(1), [0, -1]); 311 | 312 | t.throws(function () { 313 | util.reverse([1,2,3]); 314 | }); 315 | 316 | var buf2 = util.shallow(buf1); 317 | util.reverse(buf1, buf2); 318 | 319 | t.deepEqual(buf2.getChannelData(1), [-1, 0]); 320 | 321 | var buf3 = util.create([0,.1,.2,.3,.4,.5]) 322 | util.reverse(buf3, 1,3) 323 | t.deepEqual(buf3.getChannelData(0), new Float32Array([0,.2,.1,.3,.4,.5])) 324 | 325 | t.end() 326 | }); 327 | 328 | 329 | test('invert', function (t) { 330 | var buf1 = util.create([1, 0.5, -1, 0], 2); 331 | util.invert(buf1); 332 | 333 | t.deepEqual(buf1.getChannelData(0), [-1, -0.5]); 334 | t.deepEqual(buf1.getChannelData(1), [1, 0]); 335 | 336 | t.throws(function () { 337 | util.invert(new Float32Array([1,2,3])); 338 | }); 339 | 340 | var buf2 = util.shallow(buf1); 341 | util.invert(buf1, buf2); 342 | 343 | t.deepEqual(buf2.getChannelData(1), [-1, 0]); 344 | 345 | 346 | var buf3 = util.create([0,.1,.2,.3,.4,.5], 1) 347 | util.invert(buf3, 1,3) 348 | t.deepEqual(buf3.getChannelData(0), new Float32Array([0,-.1,-.2,.3,.4,.5])) 349 | 350 | t.end() 351 | }); 352 | 353 | 354 | test('zero', function (t) { 355 | var buf1 = util.create([1, 0.5, -1, 0], 2); 356 | util.zero(buf1); 357 | 358 | t.deepEqual(buf1.getChannelData(0), [0, 0]); 359 | t.deepEqual(buf1.getChannelData(1), [0, 0]); 360 | 361 | t.throws(function () { 362 | util.invert(buf1.getChannelData(0)); 363 | }); 364 | t.end() 365 | }); 366 | 367 | 368 | test('noise', function (t) { 369 | var buf1 = util.create(4, 2); 370 | util.noise(buf1); 371 | 372 | t.notDeepEqual(buf1.getChannelData(0), [0, 0]); 373 | t.notDeepEqual(buf1.getChannelData(1), [0, 0]); 374 | 375 | t.throws(function () { 376 | util.noise(buf1.getChannelData(0)); 377 | }); 378 | t.end() 379 | }); 380 | 381 | 382 | test('fill with function', function (t) { 383 | var a = util.create([1,2,3,4], 2); 384 | util.fill(a, function (sample, channel, offset) { return channel + offset }); 385 | 386 | t.deepEqual(a.getChannelData(0), [0,1]); 387 | t.deepEqual(a.getChannelData(1), [1,2]); 388 | 389 | t.throws(function () { 390 | util.fill([1,2,3], function () {}); 391 | }); 392 | t.end() 393 | }); 394 | 395 | 396 | test('fill with value', function (t) { 397 | var a = util.create([1,2,3,4], 2); 398 | util.fill(a, 1, 1, 3); 399 | 400 | t.deepEqual(a.getChannelData(0), [1,1]); 401 | t.deepEqual(a.getChannelData(1), [3,1]); 402 | 403 | t.throws(function () { 404 | util.fill(a.getChannelData(1), 1); 405 | }); 406 | t.end() 407 | }); 408 | 409 | test('fill to another buffer', function (t) { 410 | var a = util.create([1,2,3,4], 2); 411 | var b = util.shallow(a); 412 | util.fill(a, b, 1, 1, 3); 413 | 414 | t.deepEqual(a.getChannelData(0), [1,2]); 415 | t.deepEqual(a.getChannelData(1), [3,4]); 416 | 417 | t.deepEqual(b.getChannelData(0), [0,1]); 418 | t.deepEqual(b.getChannelData(1), [0,1]); 419 | t.end() 420 | }); 421 | 422 | test('fill callback argument', function (t) { 423 | var a = util.create([1,2,3,4], 2); 424 | 425 | //NOTE: such arguments are possible in case of `Through(util.noise)` etc. 426 | util.fill(a, function () {}, function () { return 1; }); 427 | 428 | t.deepEqual(a.getChannelData(0), [1,1]); 429 | t.deepEqual(a.getChannelData(1), [1,1]); 430 | t.end() 431 | }); 432 | 433 | test('fill negative offsets', function (t) { 434 | var a = util.create(10) 435 | 436 | util.fill(a, .1, -2) 437 | t.deepEqual(a.getChannelData(0), new Float32Array([0,0,0,0,0,0,0,0,.1,.1])) 438 | 439 | util.fill(a, .2, 0, -7) 440 | t.deepEqual(a.getChannelData(0), new Float32Array([.2,.2,.2,0,0,0,0,0,.1,.1])) 441 | 442 | t.end() 443 | }) 444 | 445 | test('slice', function (t) { 446 | var a = util.create([1,2,3,4,5,6,7,8,9], 3); 447 | 448 | var b = util.slice(a, 1); 449 | t.deepEqual(b.getChannelData(0), [2,3]); 450 | t.deepEqual(b.getChannelData(1), [5,6]); 451 | 452 | var c = util.slice(a, 1, 2); 453 | t.deepEqual(c.getChannelData(0), [2]); 454 | t.deepEqual(c.getChannelData(1), [5]); 455 | t.deepEqual(c.numberOfChannels, 3); 456 | 457 | b.getChannelData(0)[0] = 1; 458 | t.deepEqual(b.getChannelData(0), [1,3]); 459 | t.deepEqual(a.getChannelData(0), [1, 2, 3]); 460 | 461 | t.throws(function () { 462 | util.slice([1,2,3,4], 1, 2); 463 | }); 464 | t.end() 465 | }); 466 | 467 | test('repeat', function (t) { 468 | var a = util.create([0,.5,1]) 469 | 470 | var a0 = util.repeat(a, 0) 471 | t.equal(a0.length, 0) 472 | 473 | var a1 = util.repeat(a, 1) 474 | t.equal(a1.length, 3) 475 | t.deepEqual(a1, a) 476 | 477 | var a2 = util.repeat(a, 2) 478 | t.deepEqual(a2.getChannelData(0), [0,.5,1,0,.5,1]) 479 | 480 | var a3 = util.repeat(a, 3) 481 | t.deepEqual(a3.getChannelData(0), [0,.5,1,0,.5,1,0,.5,1]) 482 | 483 | t.end() 484 | }) 485 | 486 | test('subbuffer', function (t) { 487 | // var a = util.create([0, .1, .2, .3]) 488 | // var b = util.create([a.getChannelData(0).subarray(1,2)]) 489 | // b.getChannelData(0)[0] = .4 490 | // t.deepEqual(a.getChannelData(0), new Float32Array([0, .4, .2, .3])) 491 | 492 | var a = util.create([1,2,3,4,5,6,7,8,9], 3); 493 | 494 | var b = util.subbuffer(a, 1); 495 | b.getChannelData(0)[0] += .5 496 | t.deepEqual(a.getChannelData(0), [1,2.5,3]); 497 | t.deepEqual(b.getChannelData(0), [2.5,3]); 498 | t.deepEqual(b.getChannelData(1), [5,6]); 499 | 500 | var c = util.subbuffer(a, 1, 2); 501 | c.getChannelData(1)[0] += .5 502 | t.deepEqual(a.getChannelData(0), [1,2.5,3]); 503 | t.deepEqual(a.getChannelData(1), [4,5.5,6]); 504 | t.deepEqual(c.getChannelData(0), [2.5]); 505 | t.deepEqual(c.getChannelData(1), [5.5]); 506 | t.deepEqual(c.numberOfChannels, 3); 507 | 508 | b.getChannelData(2)[0] = 1; 509 | t.deepEqual(b.getChannelData(2), [1, 9]); 510 | t.deepEqual(a.getChannelData(2), [7, 1, 9]); 511 | t.deepEqual(c.getChannelData(2), [1]); 512 | 513 | if (isBrowser) { 514 | var s = util.context.createBufferSource() 515 | t.throws(function () { 516 | s.buffer = c 517 | }) 518 | s.buffer = util.slice(c) 519 | } 520 | 521 | var d = util.subbuffer(a, [1,2]) 522 | t.deepEqual(d.getChannelData(0), [4,5.5,6]) 523 | t.deepEqual(d.getChannelData(1), [7,1,9]) 524 | t.ok(d.duration) 525 | 526 | t.end() 527 | }); 528 | 529 | 530 | test('map', function (t) { 531 | var a = util.create([1, 1, 1, 1, 1, 1], 3); 532 | var b = util.shallow(a) 533 | 534 | var b = util.fill(a, b, function (sample, channel, offset) { 535 | return sample + channel + offset 536 | }); 537 | 538 | t.notEqual(a, b); 539 | t.ok(!util.equal(a, b)); 540 | t.deepEqual(b.getChannelData(0), [1,2]); 541 | t.deepEqual(b.getChannelData(1), [2,3]); 542 | t.deepEqual(b.numberOfChannels, 3); 543 | 544 | b.getChannelData(0)[0] = 0; 545 | t.deepEqual(a.getChannelData(0), [1,1]); 546 | t.deepEqual(b.getChannelData(0), [0,2]); 547 | 548 | t.throws(function () { 549 | util.fill([1,2,3,4], function () {}); 550 | }); 551 | t.end() 552 | }); 553 | 554 | 555 | test('concat', function (t) { 556 | var a = util.create([1,1,1,1], 2); 557 | var b = util.create(2, 3); 558 | var c = util.create([-1, -1], 1, 22050); //handle this! 559 | 560 | var d = util.concat(a, c); 561 | t.deepEqual(d.getChannelData(0), [1,1,-1,-1]); 562 | t.deepEqual(d.getChannelData(1), [1,1,0,0]); 563 | 564 | var d = util.concat(c, a); 565 | t.deepEqual(d.getChannelData(0), [-1,-1,1,1]); 566 | t.deepEqual(d.getChannelData(1), [0,0,1,1]); 567 | 568 | var d = util.concat(a, b, c); 569 | 570 | t.deepEqual(d.getChannelData(0), [1,1,0,0,-1,-1]); 571 | t.deepEqual(d.getChannelData(1), [1,1,0,0,0,0]); 572 | t.deepEqual(d.getChannelData(2), [0,0,0,0,0,0]); 573 | 574 | var d = util.concat([a, b, c]); 575 | 576 | t.deepEqual(d.getChannelData(0), [1,1,0,0,-1,-1]); 577 | t.deepEqual(d.getChannelData(1), [1,1,0,0,0,0]); 578 | t.deepEqual(d.getChannelData(2), [0,0,0,0,0,0]); 579 | 580 | t.throws(function () { 581 | util.concat([1,2,3,4], [5,6]); 582 | }); 583 | 584 | var e = util.concat(util.create(4), util.create(1),util.create(1),util.create(1),util.create(1),util.create(1),util.create(1)) 585 | t.equal(e.length, 10) 586 | 587 | t.end() 588 | }); 589 | 590 | 591 | test('resize', function (t) { 592 | var a = util.create([1,1,1,1,1], 1, 44100); 593 | 594 | //set too big 595 | a = util.resize(a, 10); 596 | t.deepEqual(a.getChannelData(0), [1,1,1,1,1,0,0,0,0,0]); 597 | 598 | //set too small 599 | a = util.resize(a, 2); 600 | t.deepEqual(a.getChannelData(0), [1,1]); 601 | 602 | t.throws(function () { 603 | util.resize('123', 2); 604 | }); 605 | t.end() 606 | }); 607 | 608 | 609 | test('rotate (+ve)', function (t) { 610 | var a = util.create([0,0,1,1,0,0,-1,-1]); 611 | util.rotate(a, 2); 612 | t.deepEqual(a.getChannelData(0), [-1,-1,0,0,1,1,0,0]); 613 | 614 | t.throws(function () { 615 | util.rotate([1,2,3], 2); 616 | }); 617 | t.end() 618 | }); 619 | 620 | test('rotate (-ve)', function(t) { 621 | var a = util.create([0,0,1,1,0,0,-1,-1]); 622 | util.rotate(a, -3); 623 | t.deepEqual(a.getChannelData(0), [1,0,0,-1,-1,0,0,1]); 624 | 625 | t.throws(function () { 626 | util.rotate([1,2,3], -2); 627 | }); 628 | t.end() 629 | }); 630 | 631 | 632 | test('shift (+ve)', function (t) { 633 | var a = util.create([0,0,1,1,0,0,-1,-1]); 634 | util.shift(a, 2); 635 | t.deepEqual(a.getChannelData(0), [0,0,0,0,1,1,0,0]); 636 | 637 | t.throws(function () { 638 | util.shift([1,2,3], 2); 639 | }); 640 | t.end() 641 | }); 642 | 643 | test('shift (-ve)', function (t) { 644 | var a = util.create([0,0,1,1,0,0,-1,-1]); 645 | util.shift(a, -3); 646 | t.deepEqual(a.getChannelData(0), [1,0,0,-1,-1,0,0,0]); 647 | 648 | t.throws(function () { 649 | util.shift([1,2,3], -2); 650 | }); 651 | t.end() 652 | }); 653 | 654 | 655 | test('normalize', function (t) { 656 | var a = util.create([0, 0.2, 0, -0.4]); 657 | util.normalize(a); 658 | t.deepEqual(a.getChannelData(0), [0, .5, 0, -1]); 659 | 660 | var b = util.create([0, 1, 0, -1]); 661 | util.normalize(b); 662 | t.deepEqual(b.getChannelData(0), [0, 1, 0, -1]); 663 | 664 | var c = util.create([0, 5, 0, -5]); 665 | util.normalize(c); 666 | t.deepEqual(c.getChannelData(0), [0, 1, 0, -1]); 667 | 668 | //channels static 669 | var c = util.create([0, .25, 0, -.5], 2); 670 | util.normalize(c); 671 | t.deepEqual(c.getChannelData(0), [0, .5]); 672 | t.deepEqual(c.getChannelData(1), [0, -1]); 673 | 674 | //too big value 675 | //FIXME: too large values are interpreted as 1, but maybe we need deamplifying instead 676 | //for example, biquad-filters may return values > 1, then we do not want to clip values 677 | var a = util.create([0, 0.1, 0, -0.5, 999, 2], 2); 678 | 679 | util.normalize(a); 680 | 681 | t.deepEqual(a.getChannelData(1), [-0.5, 1, 1]); 682 | 683 | t.throws(function () { 684 | util.normalize(new Float32Array([0, 0.1, 0.2])); 685 | }); 686 | t.end() 687 | }); 688 | 689 | test('removeStatic', function (t) { 690 | var a = util.create([.5,.7,.3,.5], 2) 691 | 692 | util.removeStatic(a) 693 | 694 | t.ok(almostEqual, a.getChannelData(0), [-.1, .1]) 695 | t.ok(almostEqual, a.getChannelData(1), [-.1, .1]) 696 | t.end() 697 | }); 698 | 699 | 700 | test('trim', function (t) { 701 | //trim single 702 | var a = util.create([0,0,1,0,0], 1) 703 | var b = util.trim(a) 704 | t.deepEqual(b.getChannelData(0), [1]) 705 | 706 | //trim both 707 | var a = util.create([0,0,1,0,0,2,3,0], 2); 708 | var b = util.trim(a); 709 | 710 | t.deepEqual(b.getChannelData(0), [0,1]); 711 | t.deepEqual(b.getChannelData(1), [2,3]); 712 | 713 | //no trim 714 | var a = util.create([1,0,1,0,0,2,3,1], 2); 715 | var b = util.trim(a); 716 | 717 | t.deepEqual(b.getChannelData(0), [1,0,1,0]); 718 | t.deepEqual(b.getChannelData(1), [0,2,3,1]); 719 | 720 | t.throws(function () { 721 | util.trim(new Float32Array([0, 0.1, 0.2])); 722 | }); 723 | t.end() 724 | }); 725 | 726 | 727 | test('pad', function (t) { 728 | //pad right 729 | var a = util.create([0,1,2,3,4,5], 2); 730 | var b = util.padRight(a, 4); 731 | 732 | t.deepEqual(b.getChannelData(0), [0,1,2,0]); 733 | t.deepEqual(b.getChannelData(1), [3,4,5,0]); 734 | 735 | //pad left 736 | var a = util.create([0,1,2,3,4,5], 2); 737 | var b = util.padLeft(a, 4); 738 | 739 | t.deepEqual(b.getChannelData(0), [0,0,1,2]); 740 | t.deepEqual(b.getChannelData(1), [0,3,4,5]); 741 | 742 | //pad value 743 | var a = util.create([0,1,2,3,4,5], 2); 744 | var b = util.pad(4, a, 0.5); 745 | 746 | t.deepEqual(b.getChannelData(0), [0.5,0,1,2]); 747 | t.deepEqual(b.getChannelData(1), [0.5,3,4,5]); 748 | 749 | t.throws(function () { 750 | util.pad(new Float32Array([0, 0.1, 0.2])); 751 | }); 752 | 753 | //pad conversion 754 | var a = util.create(1, 1) 755 | var b = util.pad(a, 10); 756 | t.equal(b.numberOfChannels, 1); 757 | 758 | t.end() 759 | }); 760 | 761 | 762 | test('size', function (t) { 763 | var a = util.create(200, 2); 764 | t.equal(util.size(a), 200 * 2 * 4); 765 | 766 | t.throws(function () { 767 | util.size(); 768 | }); 769 | t.end() 770 | }); 771 | 772 | 773 | test('mix', function (t) { 774 | var a = util.create([0,1,0,1], 2); 775 | var b = util.create([0.5, 0.5, -0.5, -0.5], 2); 776 | 777 | //simple mix 778 | util.mix(a, b); 779 | t.deepEqual(a.getChannelData(0), [0.25, 0.75]); 780 | t.deepEqual(a.getChannelData(1), [-0.25, 0.25]); 781 | 782 | //fn mix 783 | var a = util.create([0, 1, 0, 1, 0, 1], 2); 784 | var b = util.create([1, 1, 1, 1], 2); 785 | util.mix(a, b, function (v1, v2) { 786 | return v1 + v2; 787 | }, 1); 788 | 789 | t.deepEqual(a.getChannelData(0), [0, 2, 1]); 790 | t.deepEqual(a.getChannelData(1), [1, 1, 2]); 791 | 792 | t.throws(function () { 793 | util.mix([1,2,3], [4,5,6], 0.1); 794 | }); 795 | t.end() 796 | }); 797 | 798 | 799 | test('data', function (t) { 800 | var b = util.create([1,-1, 0.5, -1, 0, -0.5], 3); 801 | 802 | var data = util.data(b); 803 | 804 | t.deepEqual(data[0], [1, -1]); 805 | t.deepEqual(data[1], [0.5, -1]); 806 | t.deepEqual(data[2], [0, -0.5]); 807 | 808 | var src = [new Float32Array(2), new Float32Array(2), new Float32Array(2)]; 809 | var data = util.data(b, src); 810 | 811 | t.deepEqual(src[0], [1, -1]); 812 | t.deepEqual(src[1], [0.5, -1]); 813 | t.deepEqual(src[2], [0, -0.5]); 814 | t.end() 815 | }); 816 | --------------------------------------------------------------------------------