buddy

node MVC discord bot
Log | Files | Refs | README

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;