VolumeTransformer.js (3906B)
1 // Based on discord.js' old volume system 2 3 const { Transform } = require('stream'); 4 5 /** 6 * Transforms a stream of PCM volume. 7 * @memberof core 8 * @extends TransformStream 9 */ 10 class VolumeTransformer extends Transform { 11 /** 12 * @memberof core 13 * @param {Object} options Any optional TransformStream options plus some extra: 14 * @param {string} options.type The type of transformer: s16le (signed 16-bit little-endian), s16be, s32le, s32be 15 * @param {number} [options.volume=1] The output volume of the stream 16 * @example 17 * // Half the volume of a signed 16-bit little-endian PCM stream 18 * input 19 * .pipe(new prism.VolumeTransformer({ type: 's16le', volume: 0.5 })) 20 * .pipe(writeStream); 21 */ 22 constructor(options = {}) { 23 super(options); 24 switch (options.type) { 25 case 's16le': 26 this._readInt = (buffer, index) => buffer.readInt16LE(index); 27 this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index); 28 this._bits = 16; 29 break; 30 case 's16be': 31 this._readInt = (buffer, index) => buffer.readInt16BE(index); 32 this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index); 33 this._bits = 16; 34 break; 35 case 's32le': 36 this._readInt = (buffer, index) => buffer.readInt32LE(index); 37 this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index); 38 this._bits = 32; 39 break; 40 case 's32be': 41 this._readInt = (buffer, index) => buffer.readInt32BE(index); 42 this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index); 43 this._bits = 32; 44 break; 45 default: 46 throw new Error('VolumeTransformer type should be one of s16le, s16be, s32le, s32be'); 47 } 48 this._bytes = this._bits / 8; 49 this._extremum = Math.pow(2, this._bits - 1); 50 this.volume = typeof options.volume === 'undefined' ? 1 : options.volume; 51 this._chunk = Buffer.alloc(0); 52 } 53 54 _readInt(buffer, index) { return index; } 55 _writeInt(buffer, int, index) { return index; } 56 57 _transform(chunk, encoding, done) { 58 // If the volume is 1, act like a passthrough stream 59 if (this.volume === 1) { 60 this.push(chunk); 61 return done(); 62 } 63 64 const { _bytes, _extremum } = this; 65 66 chunk = this._chunk = Buffer.concat([this._chunk, chunk]); 67 if (chunk.length < _bytes) return done(); 68 69 const transformed = Buffer.allocUnsafe(chunk.length); 70 const complete = Math.floor(chunk.length / _bytes) * _bytes; 71 72 for (let i = 0; i < complete; i += _bytes) { 73 const int = Math.min(_extremum - 1, Math.max(-_extremum, Math.floor(this.volume * this._readInt(chunk, i)))); 74 this._writeInt(transformed, int, i); 75 } 76 77 this._chunk = chunk.slice(complete); 78 this.push(transformed); 79 return done(); 80 } 81 82 _destroy(err, cb) { 83 super._destroy(err, cb); 84 this._chunk = null; 85 } 86 87 /** 88 * Sets the volume relative to the input stream - i.e. 1 is normal, 0.5 is half, 2 is double. 89 * @param {number} volume The volume that you want to set 90 */ 91 setVolume(volume) { 92 this.volume = volume; 93 } 94 95 /** 96 * Sets the volume in decibels. 97 * @param {number} db The decibels 98 */ 99 setVolumeDecibels(db) { 100 this.setVolume(Math.pow(10, db / 20)); 101 } 102 103 /** 104 * Sets the volume so that a perceived value of 0.5 is half the perceived volume etc. 105 * @param {number} value The value for the volume 106 */ 107 setVolumeLogarithmic(value) { 108 this.setVolume(Math.pow(value, 1.660964)); 109 } 110 111 /** 112 * The current volume of the stream in decibels 113 * @readonly 114 * @type {number} 115 */ 116 get volumeDecibels() { 117 return Math.log10(this._volume) * 20; 118 } 119 /** 120 * The current volume of the stream from a logarithmic scale 121 * @readonly 122 * @type {number} 123 */ 124 get volumeLogarithmic() { 125 return Math.pow(this._volume, 1 / 1.660964); 126 } 127 } 128 129 module.exports = VolumeTransformer;