From 3c64cfce4a387cba695be37f3214845d1139c7b7 Mon Sep 17 00:00:00 2001 From: Aaron Scherer Date: Tue, 9 Feb 2016 15:12:21 -0800 Subject: [PATCH] Adding volume wrapper --- lib/Util/TokenCacher-shim.js | 24 ++++++------- lib/Voice/AudioEncoder.js | 63 ++++++++++++++++++++------------ lib/Voice/VoiceConnection.js | 44 ++++++++++++++++++----- lib/Voice/VolumeTransformer.js | 66 ++++++++++++++++++++++++++++++++++ src/Voice/AudioEncoder.js | 59 ++++++++++++++++-------------- src/Voice/VoiceConnection.js | 45 +++++++++++++++++------ src/Voice/VolumeTransformer.js | 45 +++++++++++++++++++++++ 7 files changed, 266 insertions(+), 80 deletions(-) create mode 100644 lib/Voice/VolumeTransformer.js create mode 100644 src/Voice/VolumeTransformer.js diff --git a/lib/Util/TokenCacher-shim.js b/lib/Util/TokenCacher-shim.js index 53412d365..f483e9575 100644 --- a/lib/Util/TokenCacher-shim.js +++ b/lib/Util/TokenCacher-shim.js @@ -6,23 +6,23 @@ exports.__esModule = true; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var TokenCacher = (function () { - function TokenCacher() { - _classCallCheck(this, TokenCacher); - } + function TokenCacher() { + _classCallCheck(this, TokenCacher); + } - TokenCacher.prototype.setToken = function setToken() {}; + TokenCacher.prototype.setToken = function setToken() {}; - TokenCacher.prototype.save = function save() {}; + TokenCacher.prototype.save = function save() {}; - TokenCacher.prototype.getToken = function getToken() { - return null; - }; + TokenCacher.prototype.getToken = function getToken() { + return null; + }; - TokenCacher.prototype.init = function init(ind) { - this.done = true; - }; + TokenCacher.prototype.init = function init(ind) { + this.done = true; + }; - return TokenCacher; + return TokenCacher; })(); exports["default"] = TokenCacher; diff --git a/lib/Voice/AudioEncoder.js b/lib/Voice/AudioEncoder.js index 3bb45e95b..5bc79116d 100644 --- a/lib/Voice/AudioEncoder.js +++ b/lib/Voice/AudioEncoder.js @@ -10,12 +10,16 @@ var _child_process = require("child_process"); var _child_process2 = _interopRequireDefault(_child_process); +// no opus! + +var _VolumeTransformer = require("./VolumeTransformer"); + +var _VolumeTransformer2 = _interopRequireDefault(_VolumeTransformer); + var opus; try { opus = require("node-opus"); -} catch (e) { - // no opus! -} +} catch (e) {} var AudioEncoder = (function () { function AudioEncoder() { @@ -49,7 +53,6 @@ var AudioEncoder = (function () { }; AudioEncoder.prototype.getCommand = function getCommand(force) { - if (this.choice && force) return choice; var choices = ["avconv", "ffmpeg"]; @@ -79,74 +82,88 @@ var AudioEncoder = (function () { }; AudioEncoder.prototype.encodeStream = function encodeStream(stream, options) { - var self = this; + var _this = this; + return new Promise(function (resolve, reject) { - var enc = _child_process2["default"].spawn(self.getCommand(), ['-loglevel', '0', '-i', '-', '-f', 's16le', '-ar', '48000', '-af', 'volume=' + (options.volume || 1), '-ac', 2, 'pipe:1'], { stdio: ['pipe', 'pipe', 'ignore'] }); + _this.volume = new _VolumeTransformer2["default"](options.volume || 1); + + var enc = _child_process2["default"].spawn(_this.getCommand(), ['-loglevel', '0', '-i', '-', '-f', 's16le', '-ar', '48000', '-ac', 2, 'pipe:1'], { stdio: ['pipe', 'pipe', 'ignore'] }); stream.pipe(enc.stdin); + enc.stdout.pipe(_this.volume); - enc.stdout.once("readable", function () { + _this.volume.once("readable", function () { resolve({ proc: enc, - stream: enc.stdout, + stream: _this.volume, instream: stream, channels: 2 }); }); - enc.stdout.on("end", function () { + _this.volume.on("end", function () { reject("end"); }); - enc.stdout.on("close", function () { + _this.volume.on("close", function () { reject("close"); }); }); }; AudioEncoder.prototype.encodeFile = function encodeFile(file, options) { - var self = this; - return new Promise(function (resolve, reject) { - var enc = _child_process2["default"].spawn(self.getCommand(), ['-loglevel', '0', '-i', file, '-f', 's16le', '-ar', '48000', '-af', 'volume=' + (options.volume || 1), '-ac', 2, 'pipe:1'], { stdio: ['pipe', 'pipe', 'ignore'] }); + var _this2 = this; - enc.stdout.once("readable", function () { + return new Promise(function (resolve, reject) { + _this2.volume = new _VolumeTransformer2["default"](options.volume || 1); + + var enc = _child_process2["default"].spawn(_this2.getCommand(), ['-loglevel', '0', '-i', file, '-f', 's16le', '-ar', '48000', '-ac', 2, 'pipe:1'], { stdio: ['pipe', 'pipe', 'ignore'] }); + + enc.stdout.pipe(_this2.volume); + + _this2.volume.once("readable", function () { resolve({ proc: enc, - stream: enc.stdout, + stream: _this2.volume, channels: 2 }); }); - enc.stdout.on("end", function () { + _this2.volume.on("end", function () { reject("end"); }); - enc.stdout.on("close", function () { + _this2.volume.on("close", function () { reject("close"); }); }); }; AudioEncoder.prototype.encodeArbitraryFFmpeg = function encodeArbitraryFFmpeg(ffmpegOptions) { - var self = this; + var _this3 = this; + return new Promise(function (resolve, reject) { + _this3.volume = new _VolumeTransformer2["default"](1); + // add options discord.js needs var options = ffmpegOptions.concat(['-loglevel', '0', '-f', 's16le', '-ar', '48000', '-ac', 2, 'pipe:1']); - var enc = _child_process2["default"].spawn(self.getCommand(), options, { stdio: ['pipe', 'pipe', 'ignore'] }); + var enc = _child_process2["default"].spawn(_this3.getCommand(), options, { stdio: ['pipe', 'pipe', 'ignore'] }); - enc.stdout.once("readable", function () { + enc.stdout.pipe(_this3.volume); + + _this3.volume.once("readable", function () { resolve({ proc: enc, - stream: enc.stdout, + stream: _this3.volume, channels: 2 }); }); - enc.stdout.on("end", function () { + _this3.volume.on("end", function () { reject("end"); }); - enc.stdout.on("close", function () { + _this3.volume.on("close", function () { reject("close"); }); }); diff --git a/lib/Voice/VoiceConnection.js b/lib/Voice/VoiceConnection.js index b891709c0..8b2140336 100644 --- a/lib/Voice/VoiceConnection.js +++ b/lib/Voice/VoiceConnection.js @@ -117,17 +117,19 @@ var VoiceConnection = (function (_EventEmitter) { VoiceConnection.prototype.playStream = function playStream(stream) { var channels = arguments.length <= 1 || arguments[1] === undefined ? 2 : arguments[1]; - var self = this; + var self = this, + startTime = Date.now(), + count = 0, + length = 20, + retStream = new _StreamIntent2["default"](), + onWarning = false, + lastVolume = this.volume !== undefined ? this.volume.get() : 1; - var startTime = Date.now(); - var count = 0; + this.volume = stream; + this.playing = true; + this.playingIntent = retStream; - var length = 20; - - self.playing = true; - var retStream = new _StreamIntent2["default"](); - var onWarning = false; - self.playingIntent = retStream; + this.setVolume(lastVolume); function send() { if (!self.playingIntent || !self.playing) { @@ -409,6 +411,30 @@ var VoiceConnection = (function (_EventEmitter) { }); }; + VoiceConnection.prototype.wrapVolume = function wrapVolume(stream) { + stream.pipe(this.volume); + + return this.volume; + }; + + VoiceConnection.prototype.setVolume = function setVolume(volume) { + this.volume.set(volume); + }; + + VoiceConnection.prototype.getVolume = function getVolume() { + return this.volume.get(); + }; + + VoiceConnection.prototype.mute = function mute() { + this.lastVolume = this.volume.get(); + this.setVolume(0); + }; + + VoiceConnection.prototype.unmute = function unmute() { + this.setVolume(this.lastVolume); + this.lastVolume = undefined; + }; + return VoiceConnection; })(_events2["default"]); diff --git a/lib/Voice/VolumeTransformer.js b/lib/Voice/VolumeTransformer.js new file mode 100644 index 000000000..576db754a --- /dev/null +++ b/lib/Voice/VolumeTransformer.js @@ -0,0 +1,66 @@ +'use strict'; + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var Transform = require('stream').Transform; + +var Volume = (function (_Transform) { + _inherits(Volume, _Transform); + + function Volume() { + _classCallCheck(this, Volume); + + _Transform.apply(this, arguments); + } + + Volume.prototype.get = function get() { + return this.volume; + }; + + Volume.prototype.set = function set(volume) { + this.volume = volume; + }; + + Volume.prototype._transform = function _transform(buffer, encoding, callback) { + var out = new Buffer(buffer.length); + + for (var i = 0; i < buffer.length; i += 2) { + // Read Int16, multiple with multiplier and round down + //console.log(this.volume, this.multiplier, buffer.readInt16LE(i)); + var uint = Math.floor(this.multiplier * buffer.readInt16LE(i)); + + // Ensure value stays within 16bit + uint = Math.min(32767, uint); + uint = Math.max(-32767, uint); + + // Write 2 new bytes into other buffer; + out.writeInt16LE(uint, i); + } + + this.push(out); + callback(); + }; + + _createClass(Volume, [{ + key: 'volume', + get: function get() { + return this._volume === undefined ? 1 : this._volume; + }, + set: function set(value) { + this._volume = value; + } + }, { + key: 'multiplier', + get: function get() { + return Math.tan(this.volume); + } + }]); + + return Volume; +})(Transform); + +module.exports = Volume; diff --git a/src/Voice/AudioEncoder.js b/src/Voice/AudioEncoder.js index 6c74d7423..3ac16eafd 100644 --- a/src/Voice/AudioEncoder.js +++ b/src/Voice/AudioEncoder.js @@ -9,6 +9,8 @@ try { // no opus! } +import VolumeTransformer from "./VolumeTransformer"; + export default class AudioEncoder { constructor() { if (opus) { @@ -40,8 +42,7 @@ export default class AudioEncoder { } getCommand(force) { - - if (this.choice && force) + if(this.choice && force) return choice; var choices = ["avconv", "ffmpeg"]; @@ -58,73 +59,77 @@ export default class AudioEncoder { } encodeStream(stream, options) { - var self = this; return new Promise((resolve, reject) => { - var enc = cpoc.spawn(self.getCommand(), [ + this.volume = new VolumeTransformer(options.volume || 1); + + var enc = cpoc.spawn(this.getCommand(), [ '-loglevel', '0', '-i', '-', '-f', 's16le', '-ar', '48000', - '-af', 'volume=' + (options.volume || 1), '-ac', 2, 'pipe:1' ], {stdio: ['pipe', 'pipe', 'ignore']}); stream.pipe(enc.stdin); + enc.stdout.pipe(this.volume); - enc.stdout.once("readable", function () { + this.volume.once("readable", () => { resolve({ proc: enc, - stream: enc.stdout, + stream: this.volume, instream: stream, - channels : 2 + channels: 2 }); }); - enc.stdout.on("end", function () { + this.volume.on("end", () => { reject("end"); }); - enc.stdout.on("close", function () { + this.volume.on("close", () => { reject("close"); }); }); } encodeFile(file, options) { - var self = this; return new Promise((resolve, reject) => { - var enc = cpoc.spawn(self.getCommand(), [ + this.volume = new VolumeTransformer(options.volume || 1); + + var enc = cpoc.spawn(this.getCommand(), [ '-loglevel', '0', '-i', file, '-f', 's16le', '-ar', '48000', - '-af', 'volume=' + (options.volume || 1), '-ac', 2, 'pipe:1' - ], { stdio: ['pipe', 'pipe', 'ignore'] }); + ], {stdio: ['pipe', 'pipe', 'ignore']}); - enc.stdout.once("readable", function () { + enc.stdout.pipe(this.volume); + + this.volume.once("readable", () => { resolve({ proc: enc, - stream: enc.stdout, - channels : 2 + stream: this.volume, + channels: 2 }); }); - enc.stdout.on("end", function () { + this.volume.on("end", () => { reject("end"); }); - enc.stdout.on("close", function () { + this.volume.on("close", () => { reject("close"); }); }); } encodeArbitraryFFmpeg(ffmpegOptions) { - var self = this; return new Promise((resolve, reject) => { + this.volume = new VolumeTransformer(1); + // add options discord.js needs var options = ffmpegOptions.concat([ '-loglevel', '0', @@ -133,21 +138,23 @@ export default class AudioEncoder { '-ac', 2, 'pipe:1' ]); - var enc = cpoc.spawn(self.getCommand(), options, { stdio: ['pipe', 'pipe', 'ignore'] }); + var enc = cpoc.spawn(this.getCommand(), options, {stdio: ['pipe', 'pipe', 'ignore']}); - enc.stdout.once("readable", function () { + enc.stdout.pipe(this.volume); + + this.volume.once("readable", () => { resolve({ proc: enc, - stream: enc.stdout, - channels : 2 + stream: this.volume, + channels: 2 }); }); - enc.stdout.on("end", function () { + this.volume.on("end", () => { reject("end"); }); - enc.stdout.on("close", function () { + this.volume.on("close", () => { reject("close"); }); }); diff --git a/src/Voice/VoiceConnection.js b/src/Voice/VoiceConnection.js index 4c8283c40..b72b2ae50 100644 --- a/src/Voice/VoiceConnection.js +++ b/src/Voice/VoiceConnection.js @@ -83,18 +83,19 @@ export default class VoiceConnection extends EventEmitter { } playStream(stream, channels=2) { + var self = this, + startTime = Date.now(), + count = 0, + length = 20, + retStream = new StreamIntent(), + onWarning = false, + lastVolume = this.volume !== undefined ? this.volume.get() : 1; - var self = this; + this.volume = stream; + this.playing = true; + this.playingIntent = retStream; - var startTime = Date.now(); - var count = 0; - - var length = 20; - - self.playing = true; - var retStream = new StreamIntent(); - var onWarning = false; - self.playingIntent = retStream; + this.setVolume(lastVolume); function send() { if (!self.playingIntent || !self.playing) { @@ -371,4 +372,28 @@ export default class VoiceConnection extends EventEmitter { }); } + + wrapVolume(stream) { + stream.pipe(this.volume); + + return this.volume; + } + + setVolume(volume) { + this.volume.set(volume); + } + + getVolume() { + return this.volume.get(); + } + + mute() { + this.lastVolume = this.volume.get(); + this.setVolume(0); + } + + unmute() { + this.setVolume(this.lastVolume); + this.lastVolume = undefined; + } } diff --git a/src/Voice/VolumeTransformer.js b/src/Voice/VolumeTransformer.js new file mode 100644 index 000000000..f8506fb70 --- /dev/null +++ b/src/Voice/VolumeTransformer.js @@ -0,0 +1,45 @@ +const Transform = require('stream').Transform; + +class Volume extends Transform { + get volume() { + return this._volume === undefined ? 1 : this._volume; + } + + set volume(value) { + this._volume = value; + } + + get multiplier() { + return Math.tan(this.volume); + } + + get() { + return this.volume; + } + + set(volume) { + this.volume = volume; + } + + _transform(buffer, encoding, callback) { + let out = new Buffer(buffer.length); + + for (let i = 0; i < buffer.length; i += 2) { + // Read Int16, multiple with multiplier and round down + //console.log(this.volume, this.multiplier, buffer.readInt16LE(i)); + let uint = Math.floor(this.multiplier * buffer.readInt16LE(i)); + + // Ensure value stays within 16bit + uint = Math.min(32767, uint); + uint = Math.max(-32767, uint); + + // Write 2 new bytes into other buffer; + out.writeInt16LE(uint, i); + } + + this.push(out); + callback(); + } +} + +module.exports = Volume;