diff --git a/modules/db-read/database-public-loki.js b/modules/db-read/database-public-loki.js index 7cee318..10add8b 100644 --- a/modules/db-read/database-public-loki.js +++ b/modules/db-read/database-public-loki.js @@ -25,6 +25,7 @@ const mapFunDefault = function(left, right) { 'album-art': left.aaFile, filepath: left.filepath, rating: right.rating, + "replaygain-track-db": left.replaygainTrackDb, vpath: left.vpath }; }; @@ -137,7 +138,8 @@ exports.setup = function (mstream, program) { "title": result[0].title ? result[0].title : null, "year": result[0].year ? result[0].year : null, "album-art": result[0].aaFile ? result[0].aaFile : null, - "rating": result[0].rating ? result[0].rating : null + "rating": result[0].rating ? result[0].rating : null, + "replaygain-track-db": result[0]['replaygain-track-db'] ? result[0]['replaygain-track-db'] : null } }); }); @@ -279,7 +281,8 @@ exports.setup = function (mstream, program) { "title": result[0].title ? result[0].title : null, "year": result[0].year ? result[0].year : null, "album-art": result[0].aaFile ? result[0].aaFile : null, - "rating": result[0].rating ? result[0].rating : null + "rating": result[0].rating ? result[0].rating : null, + "replaygain-track-db": result[0]['replaygain-track-db'] ? result[0]['replaygain-track-db'] : null }; } } @@ -448,7 +451,8 @@ exports.setup = function (mstream, program) { "year": row.year ? row.year : null, "album-art": row.aaFile ? row.aaFile : null, "filename": fe.basename(row.filepath), - "rating": row.rating ? row.rating : null + "rating": row.rating ? row.rating : null, + "replaygain-track-db": row['replaygain-track-db'] ? row['replaygain-track-db'] : null } }); } @@ -567,7 +571,8 @@ exports.setup = function (mstream, program) { "title": randomSong.title ? randomSong.title : null, "year": randomSong.year ? randomSong.year : null, "album-art": randomSong.aaFile ? randomSong.aaFile : null, - "rating": randomSong.rating ? randomSong.rating : null + "rating": randomSong.rating ? randomSong.rating : null, + "replaygain-track-db": randomSong['replaygain-track-db'] ? randomSong['replaygain-track-db'] : null } }); @@ -669,6 +674,7 @@ exports.setup = function (mstream, program) { 'album-art': right.aaFile, filepath: right.filepath, rating: left.rating, + "replaygain-track-db": right.replaygainTrackDb, vpath: right.vpath }; }; @@ -700,7 +706,8 @@ exports.setup = function (mstream, program) { "year": row.year ? row.year : null, "album-art": row.aaFile ? row.aaFile : null, "filename": fe.basename(row.filepath), - "rating": row.rating ? row.rating : null + "rating": row.rating ? row.rating : null, + "replaygain-track-db": row['replaygain-track-db'] ? row['replaygain-track-db'] : null } }); } @@ -752,7 +759,8 @@ exports.setup = function (mstream, program) { "year": row.year ? row.year : null, "album-art": row.aaFile ? row.aaFile : null, "filename": fe.basename(row.filepath), - "rating": row.rating ? row.rating : null + "rating": row.rating ? row.rating : null, + "replaygain-track": row.replaygainTrack ? row.replaygainTrack : null } }); } diff --git a/modules/db-write/database-default-loki.js b/modules/db-write/database-default-loki.js index e28e509..5959f3d 100644 --- a/modules/db-write/database-default-loki.js +++ b/modules/db-write/database-default-loki.js @@ -75,7 +75,8 @@ exports.insertEntries = function (arrayOfSongs, vpath) { "hash": song.hash, "aaFile": song.aaFile ? song.aaFile : null, "vpath": vpath, - "ts": Math.floor(Date.now() / 1000) + "ts": Math.floor(Date.now() / 1000), + "replaygainTrackDb": song.replaygain_track_gain ? song.replaygain_track_gain.dB : null }); saveCounter++; diff --git a/public/css/mstream-player.css b/public/css/mstream-player.css index 3233f9a..95856ef 100644 --- a/public/css/mstream-player.css +++ b/public/css/mstream-player.css @@ -464,3 +464,19 @@ input[type=range]:focus::-ms-fill-lower { input[type=range]:focus::-ms-fill-upper { background: #5c5c5c; } + +#rg-pregain-info { + color: rgb(102, 132, 178); + font-weight: 800; + font-size: 13px; + font-family: 'Jura', sans-serif; + width: 34px; + padding: 13px 0; + opacity: 0; + transition: opacity 0.25s; + text-align: right; +} + +#rg-status { + transition: opacity 0.25s; +} diff --git a/public/js/mstream.player.js b/public/js/mstream.player.js index b6f88c5..e40b439 100644 --- a/public/js/mstream.player.js +++ b/public/js/mstream.player.js @@ -5,6 +5,8 @@ var MSTREAMPLAYER = (function () { mstreamModule.positionCache = { val: -1 }; mstreamModule.playlist = []; var cacheTimeout = 30000; + + var currentReplayGainAmp = 1.0; mstreamModule.editSongMetadata = function (key, value, songIndex) { for (var i = 0, len = mstreamModule.playlist.length; i < len; i++) { @@ -22,13 +24,14 @@ var MSTREAMPLAYER = (function () { var localPlayerObject = getCurrentPlayer(); var otherPlayerObject = getOtherPlayer(); + const rgainAdjustedVolume = newVolume / 100 * currentReplayGainAmp if (localPlayerObject && localPlayerObject.playerObject) { - localPlayerObject.playerObject.volume(newVolume / 100); + localPlayerObject.playerObject.volume(rgainAdjustedVolume); } if (otherPlayerObject && otherPlayerObject.playerObject) { - otherPlayerObject.playerObject.volume(newVolume / 100); + otherPlayerObject.playerObject.volume(rgainAdjustedVolume); } } @@ -495,6 +498,8 @@ var MSTREAMPLAYER = (function () { } + // Should be called whenever the "metadata" field of the current song is changed, or + // the current song is changed. mstreamModule.resetCurrentMetadata = function () { var lPlayer = getCurrentPlayer(); var curSong = lPlayer.songObject; @@ -505,9 +510,40 @@ var MSTREAMPLAYER = (function () { mstreamModule.playerStats.metadata.title = curSong.metadata.title; mstreamModule.playerStats.metadata.year = curSong.metadata.year; mstreamModule.playerStats.metadata['album-art'] = curSong.metadata['album-art']; + mstreamModule.playerStats.metadata['replaygain-track-db'] = curSong.metadata['replaygain-track-db']; } + + mstreamModule.updateReplayGainFromSong(curSong); } + // Update ReplayGain state from given song, if required. + mstreamModule.updateReplayGainFromSong = function (song) { + console.assert(song); + var newRgAmpValue = undefined; + + if (mstreamModule.playerStats.replayGain) { + if (song.metadata) { + const rgainDb = song.metadata['replaygain-track-db']; + if (rgainDb) { + // Note: the music-metadata package has a similar calculation in its Utils class, and that's used to + // calculate a returned 'ratio' value. However, the calculation used there is actually calculating the power + // ratio and not the amplitude ratio as required. As power is amplitude squared, that results in a volume + // reduction that's too small (i.e. 0.25**2 = 0.00625). + newRgAmpValue = Math.pow(10, (rgainDb + mstreamModule.playerStats.replayGainPreGainDb) / 20) + } + } + + if (newRgAmpValue === undefined) { + currentReplayGainAmp = 0.316; // -10 db for songs without ReplayGain info. + } else { + currentReplayGainAmp = newRgAmpValue; + } + } else { + currentReplayGainAmp = 1.0; + } + + mstreamModule.changeVolume(mstreamModule.playerStats.volume); + } mstreamModule.resetPositionCache = function () { var len; @@ -610,7 +646,9 @@ var MSTREAMPLAYER = (function () { "year": false, "album-art": false, "filepath": false, - } + }, + replayGain: false, + replayGainPreGainDb: 0 } var playerA = { @@ -889,6 +927,21 @@ var MSTREAMPLAYER = (function () { return mstreamModule.playerStats.autoDJ; } + // ReplayGain + mstreamModule.setReplayGainActive = function (isActive) { + mstreamModule.playerStats.replayGain = isActive; + if (getCurrentPlayer() && getCurrentPlayer().songObject) { + mstreamModule.updateReplayGainFromSong(getCurrentPlayer().songObject); + } + } + + mstreamModule.setReplayGainPreGainDb = function (db) { + mstreamModule.playerStats.replayGainPreGainDb = db; + if (getCurrentPlayer() && getCurrentPlayer().songObject) { + mstreamModule.updateReplayGainFromSong(getCurrentPlayer().songObject); + } + } + // Return an object that is assigned to Module return mstreamModule; }()); diff --git a/public/js/mstream.vue-player-controls.js b/public/js/mstream.vue-player-controls.js index efb6932..7ec87ed 100644 --- a/public/js/mstream.vue-player-controls.js +++ b/public/js/mstream.vue-player-controls.js @@ -2,6 +2,14 @@ var VUEPLAYER = (function () { const mstreamModule = {}; mstreamModule.playlists = []; + const replayGainPreGainSettings = [ + -15.0, + -10.0, + -6.0, + 0.0 + ]; + var replayGainInfoTimeout; + var currentPopperSongIndex2; var currentPopperSongIndex; var currentPopperSong; @@ -218,6 +226,10 @@ var VUEPLAYER = (function () { created: function () { if (typeof(Storage) !== "undefined") { this.curVol = localStorage.getItem("volume"); + MSTREAMPLAYER.setReplayGainActive(localStorage.getItem("replayGain") == "true"); + + const rgPregain = Number(localStorage.getItem("replayGainPreGainDb")); + MSTREAMPLAYER.setReplayGainPreGainDb(rgPregain === NaN ? 0 : rgPregain); } MSTREAMPLAYER.changeVolume(parseInt(this.curVol)); }, @@ -296,6 +308,43 @@ var VUEPLAYER = (function () { toggleAutoDJ: function () { MSTREAMPLAYER.toggleAutoDJ(); }, + toggleReplayGain: function () { + // With a series of clicks, allow the user to first activate ReplayGain, then progress through a list of + // settings for the desired level of pre-gain, and then finally disable ReplayGain again. + if (replayGainInfoTimeout) { clearTimeout(replayGainInfoTimeout); } + + var pregainInfoElement = document.getElementById('rg-pregain-info') + var rgStatusElement = document.getElementById('rg-status') + + if (!this.playerStats.replayGain) { + MSTREAMPLAYER.setReplayGainPreGainDb(replayGainPreGainSettings[0]); + MSTREAMPLAYER.setReplayGainActive(true); + } else { + const settingsIdx = replayGainPreGainSettings.indexOf(this.playerStats.replayGainPreGainDb); + if (settingsIdx == -1 || settingsIdx >= replayGainPreGainSettings.length - 1) { + MSTREAMPLAYER.setReplayGainActive(false); + pregainInfoElement.style.opacity = "0.0"; + rgStatusElement.style.opacity = "1.0"; + } else { + MSTREAMPLAYER.setReplayGainPreGainDb(replayGainPreGainSettings[settingsIdx + 1]); + } + } + + if (this.playerStats.replayGain) { + pregainInfoElement.style.opacity = "1.0"; + rgStatusElement.style.opacity = "0.0"; + + replayGainInfoTimeout = setTimeout(function () { + pregainInfoElement.style.opacity = "0.0"; + rgStatusElement.style.opacity = "1.0"; + }, 1000); + } + + if (typeof(Storage) !== "undefined") { + localStorage.setItem("replayGain", this.playerStats.replayGain); + localStorage.setItem("replayGainPreGainDb", this.playerStats.replayGainPreGainDb); + } + }, fadeOverlay: function () { if ($('#main-overlay').is(':visible')) { $('#main-overlay').fadeOut("slow"); diff --git a/public/mstream.html b/public/mstream.html index 6e594e7..89fec6b 100755 --- a/public/mstream.html +++ b/public/mstream.html @@ -425,6 +425,7 @@
+ @@ -474,6 +475,10 @@ +