diff --git a/src/i18n/en_US.jsonc b/src/i18n/en_US.jsonc index 45c9a4e2..1832b3da 100644 --- a/src/i18n/en_US.jsonc +++ b/src/i18n/en_US.jsonc @@ -143,7 +143,11 @@ "term.recordLabels": "Record Labels", "term.videoExtras": "Video Extras", "term.top": "Top", - + "term.lossless": "Lossless", + "term.lossless.description": "(up to 24-bit/48 kHz)", + "term.hireslossless": "Hi-Res Lossless", + "term.hireslossless.description": "(up to 24-bit/192 kHz)", + // Home "home.title": "Home", "home.recentlyPlayed": "Recently Played", @@ -232,12 +236,18 @@ "settings.header.audio": "Audio", "settings.header.audio.description": "Adjust the audio settings for Cider.", "settings.option.audio.quality": "Audio Quality", // Dropdown + "settings.header.audio.quality.hireslossless": "Hi-Res Lossless", + "settings.header.audio.quality.hireslossless.description": "(up to 24-bit/192 kHz)", + "settings.header.audio.quality.lossless": "Lossless", + "settings.header.audio.quality.lossless.description": "(up to 24-bit/48 kHz)", "settings.header.audio.quality.high": "High", "settings.header.audio.quality.low": "Low", "settings.header.audio.quality.auto": "Auto", "settings.option.audio.seamlessTransition": "Seamless Audio Transition", // Toggle "settings.option.audio.enableAdvancedFunctionality": "Enable Advanced Functionality", // Toggle - "settings.option.audio.enableAdvancedFunctionality.description": "Enabling AudioContext functionality will allow for extended audio features like Audio Normalization, Equalizers and Visualizers, however on some systems this may cause stuttering in audio tracks.", + "settings.option.audio.enableAdvancedFunctionality.description": "Enabling AudioContext functionality will allow for extended audio features like Audio Normalization , Equalizers and Visualizers, however on some systems this may cause stuttering in audio tracks.", + "settings.option.audio.enableAdvancedFunctionality.decryptLLPW": "Decrypt Lossless Playback Workflow", // Toggle + "settings.option.audio.enableAdvancedFunctionality.decryptLLPW.description": "Enables the ability for Cider to decrypt Lossless Audio Files. A minor performance hit will occur.", "settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Audio Normalization", // Toggle "settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalizes peak volume for individual tracks to create a more uniform listening experience.", "settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Audio Spatialization", // Toggle diff --git a/src/i18n/ja_JP.jsonc b/src/i18n/ja_JP.jsonc index 8c178de1..485cf2cc 100644 --- a/src/i18n/ja_JP.jsonc +++ b/src/i18n/ja_JP.jsonc @@ -189,17 +189,26 @@ "action.tray.quit": "終了", "action.tray.show": "表示", "action.update": "アップデート", + "action.copy": "コピー", + "action.newpreset": "ニュープリセット", // Equalizer Preset + "action.deletepreset": "プリセットを削除", // Equalizer Preset // Settings - Audio "settings.header.audio": "オーディオ", "settings.header.audio.description": "Ciderのオーディオ設定", "settings.option.audio.quality": "音質", // Dropdown + "settings.header.audio.quality.hireslossless": "ハイレゾロスレス", + "settings.header.audio.quality.hireslossless.description": "(最大解像度 24 ビット/192 kHz)", + "settings.header.audio.quality.lossless": "ロスレス", + "settings.header.audio.quality.lossless.description": "(最大解像度 24 ビット/48 kHz)", "settings.header.audio.quality.high": "高品質", "settings.header.audio.quality.low": "高効率", "settings.header.audio.quality.auto": "自動", "settings.option.audio.seamlessTransition": "曲間なしで再生", // Toggle "settings.option.audio.enableAdvancedFunctionality": "先進的な機能", // Toggle "settings.option.audio.enableAdvancedFunctionality.description": "AudioContext 機能を有効にすると、オーディオノーマライズ、空間オーディオ、イコライザーなどの機能を使用できますが、音が途切れるかもしれません。", // Toggle + "settings.option.audio.enableAdvancedFunctionality.decryptLLPW": "ロスレスオーディオ", // Toggle + "settings.option.audio.enableAdvancedFunctionality.decryptLLPW.description": "ロスレスオーディオを有効にする", // Toggle "settings.option.audio.enableAdvancedFunctionality.audioNormalization": "オーディオノーマライズ", // Toggle "settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "さまざまな曲の音量を均一にし、より整った音を楽しめるようにする機能です。", "settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "オーディオ空間化", // Toggle diff --git a/src/i18n/zh_CN.jsonc b/src/i18n/zh_CN.jsonc index 370c226e..c0e9c0cc 100644 --- a/src/i18n/zh_CN.jsonc +++ b/src/i18n/zh_CN.jsonc @@ -197,6 +197,10 @@ "settings.header.audio": "音频", "settings.header.audio.description": "调整 Cider 的音频设置", "settings.option.audio.quality": "音质", // Dropdown + "settings.header.audio.quality.hireslossless": "高解析度无损", + "settings.header.audio.quality.hireslossless.description": "(最高 24 位/192 kHz)", + "settings.header.audio.quality.lossless": "无损", + "settings.header.audio.quality.lossless.description": "(最高 24 位/48 kHz)", "settings.header.audio.quality.high": "高音质", "settings.header.audio.quality.low": "高效率", "settings.header.audio.quality.auto": "自动", diff --git a/src/i18n/zh_TW.jsonc b/src/i18n/zh_TW.jsonc index e009d896..3a06689c 100644 --- a/src/i18n/zh_TW.jsonc +++ b/src/i18n/zh_TW.jsonc @@ -189,18 +189,27 @@ "action.tray.quit": "退出", "action.tray.show": "顯示", "action.update": "更新", + "action.copy": "複製", + "action.newpreset": "新預設", // Equalizer Preset + "action.deletepreset": "刪除預設", // Equalizer Preset // Settings - Audio "settings.header.audio": "音訊", "settings.header.audio.description": "調整Cider的音訊設定", "settings.option.audio.quality": "音訊音質", // Dropdown + "settings.header.audio.quality.hireslossless": "高解析度保真壓縮", + "settings.header.audio.quality.hireslossless.description": "(最高 24 位元/192 kHz)", + "settings.header.audio.quality.lossless": "保真壓縮", + "settings.header.audio.quality.lossless.description": "(最高 24 位元/48 kHz)", "settings.header.audio.quality.high": "高品質", "settings.header.audio.quality.low": "高效率", "settings.header.audio.quality.auto": "自動", "settings.option.audio.seamlessTransition": "無間斷播放", // Toggle "settings.option.audio.enableAdvancedFunctionality": "進階機能", // Toggle "settings.option.audio.enableAdvancedFunctionality.description": "啟用 AudioContext 將解鎖類似音訊標準化和等化器的進階機能。但是會在一些電腦造成音樂卡頓。", + "settings.option.audio.enableAdvancedFunctionality.decryptLLPW": "保真壓縮解碼", // Toggle + "settings.option.audio.enableAdvancedFunctionality.decryptLLPW.description": "給予 Cider 解碼保真壓縮檔案的能力。相對的,這將會增加運算工作量。", "settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音訊標準化", // Toggle "settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "將平衡輕柔和響亮的歌曲,建立更統一的聆聽體驗。", "settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "音訊空間化", // Toggle diff --git a/src/main/base/store.ts b/src/main/base/store.ts index 8156799c..266cc7f4 100644 --- a/src/main/base/store.ts +++ b/src/main/base/store.ts @@ -31,6 +31,7 @@ export class Store { "quality": "256", "seamless_audio": true, "normalization": false, + "decryptLLPW": false, "spatial": false, "maxVolume": 1, "volumePrecision": 0.1, diff --git a/src/renderer/audio/audio.js b/src/renderer/audio/audio.js index 00d90c54..fe1f6024 100644 --- a/src/renderer/audio/audio.js +++ b/src/renderer/audio/audio.js @@ -8,6 +8,8 @@ var CiderAudio = { audioBands : null, preampNode : null, vibrantbassNode: null, + llpw: null, + llpwEnabled: null }, init: function (cb = function () { }) { //AudioOutputs.fInit = true; @@ -29,6 +31,9 @@ var CiderAudio = { try{ CiderAudio.audioNodes.spatialNode.disconnect();} catch(e){} try{ CiderAudio.audioNodes.preampNode.disconnect(); + for (var i of CiderAudio.audioNodes.llpw){ + i.disconnect(); + } for (var i of CiderAudio.audioNodes.vibrantbassNode){ i.disconnect(); } @@ -121,7 +126,11 @@ var CiderAudio = { let VIBRANTBASSBANDS = app.cfg.audio.vibrantBass.frequencies; let VIBRANTBASSGAIN = app.cfg.audio.vibrantBass.gain; let VIBRANTBASSQ = app.cfg.audio.vibrantBass.Q; - CiderAudio.audioNodes.audioBands = []; CiderAudio.audioNodes.vibrantbassNode = []; + LLPW_Q = [5, 1, 3.536, 1.25, 8.409, 1.25, 14.14, 7.071, 5, 0.625, 16.82, 20, 20, 20, 28.28, 28.28, 28.28, 20, 33.64, 33.64, 10, 28.28, 7.071, 3.856]; + LLPW_GAIN = [0.38, -1.81, -0.23, -0.51, 0.4, 0.84, 0.36, -0.34, 0.27, -1.2, -0.42, -0.67, 0.81, 1.31, -0.71, 0.68, -1.04, 0.79, -0.73, -1.33, 1.17, 0.57, 0.35, 6.33]; + LLPW_FREQUENCIES = [16.452, 24.636, 37.134, 74.483, 159.54, 308.18, 670.21, 915.81, 1200.7, 2766.4, 2930.6, 4050.6, 4409.1, 5395.2, 5901.6, 6455.5, 7164.1, 7724.1, 8449, 10573, 12368, 14198, 17910, 18916]; + CiderAudio.audioNodes.audioBands = []; CiderAudio.audioNodes.vibrantbassNode = []; + CiderAudio.audioNodes.llpw = []; CiderAudio.audioNodes.llpwEnabled = 0; for (i = 0; i < BANDS.length; i++) { CiderAudio.audioNodes.audioBands[i] = CiderAudio.context.createBiquadFilter(); @@ -130,6 +139,14 @@ var CiderAudio = { CiderAudio.audioNodes.audioBands[i].Q.value = Q[i]; CiderAudio.audioNodes.audioBands[i].gain.value = GAIN[i] * app.cfg.audio.equalizer.mix; } + + for (i = 0; i < LLPW_FREQUENCIES.length; i++) { + CiderAudio.audioNodes.llpw[i] = CiderAudio.context.createBiquadFilter(); + CiderAudio.audioNodes.llpw[i].type = 'peaking'; // 'peaking'; + CiderAudio.audioNodes.llpw[i].frequency.value = LLPW_FREQUENCIES[i]; + CiderAudio.audioNodes.llpw[i].Q.value = LLPW_Q[i]; + CiderAudio.audioNodes.llpw[i].gain.value = LLPW_GAIN[i] * 0.5 * CiderAudio.audioNodes.llpwEnabled; + } CiderAudio.audioNodes.preampNode = CiderAudio.context.createBiquadFilter(); CiderAudio.audioNodes.preampNode.type = 'highshelf'; @@ -147,13 +164,18 @@ var CiderAudio = { if (app.cfg.audio.spatial) { try{ CiderAudio.audioNodes.spatialNode.output.disconnect(CiderAudio.context.destination); } catch(e){} - CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.audioNodes.preampNode); + CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.audioNodes.llpw[0]); } else { try{ CiderAudio.audioNodes.gainNode.disconnect(CiderAudio.context.destination);} catch(e){} - CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.preampNode); + CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.llpw[0]); } + for (i = 1; i < LLPW_FREQUENCIES.length; i ++) { + CiderAudio.audioNodes.llpw[i-1].connect(CiderAudio.audioNodes.llpw[i]); + } + CiderAudio.audioNodes.llpw[LLPW_FREQUENCIES.length-1].connect(CiderAudio.audioNodes.preampNode); + CiderAudio.audioNodes.preampNode.connect(CiderAudio.audioNodes.vibrantbassNode[0]); for (i = 1; i < VIBRANTBASSBANDS.length; i ++) { diff --git a/src/renderer/index.js b/src/renderer/index.js index 9fc54862..1bec5f04 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -748,12 +748,13 @@ const app = new Vue({ } }) - ipcRenderer.on('play', function (_event, mode, id) { - if (mode !== 'url') { - self.mk.setQueue({[mode]: id}).then(() => { - app.mk.play() - }) - } else { + ipcRenderer.on('play', function(_event, mode, id) { + if (mode !== 'url'){ + self.mk.setQueue({[mode]: id}).then(() => { + app.mk.play() + }) + + } else { app.openAppleMusicURL(id) } }); @@ -775,7 +776,7 @@ const app = new Vue({ self.$refs.queue.updateQueue(); } this.currentSongInfo = a - + if (app.cfg.audio.normalization) { // get unencrypted audio previews to get SoundCheck's normalization tag @@ -799,6 +800,7 @@ const app = new Vue({ } catch (e) { } } + try { a = a.item.attributes; @@ -820,6 +822,7 @@ const app = new Vue({ // app.getNowPlayingArtwork(42); app.getNowPlayingArtworkBG(32); app.loadLyrics(); + app.losslessBadge(); // Playback Notifications if (this.cfg.general.playbackNotifications && !document.hasFocus() && a.artistName && a.artwork && a.name) { @@ -2302,7 +2305,7 @@ const app = new Vue({ showSearch() { this.page = "search" }, - loadLyrics() { + loadLyrics() { const musicType = (MusicKit.getInstance().nowPlayingItem != null) ? MusicKit.getInstance().nowPlayingItem["type"] ?? '' : ''; console.log("mt", musicType) if (musicType === "musicVideo") { @@ -2345,6 +2348,22 @@ const app = new Vue({ }) notyf.success('Removed from library.') }, + async losslessBadge() { + const songID = (this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem["_songId"] ?? -1 : -1; + if (app.cfg.audio.quality == 2304 && app.cfg.advanced.decryptLLPW && songID != -1) { + let extendedAssets = await app.mk.api.song(songID, {extend : 'extendedAssetUrls'}) + if (extendedAssets.attributes.audioTraits.includes('lossless')) { + app.mk.nowPlayingItem['attributes']['lossless'] = true + CiderAudio.audioNodes.llpwEnabled = 1} + else { + CiderAudio.audioNodes.llpwEnabled = 0 + } + } + + else { + CiderAudio.audioNodes.llpwEnabled = 0 + } + }, async loadYTLyrics() { const track = (this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.title ?? '' : ''; const artist = (this.mk.nowPlayingItem != null) ? this.mk.nowPlayingItem.artistName ?? '' : ''; @@ -2959,7 +2978,7 @@ const app = new Vue({ if (type.slice(-1) != "s") { type += "s" } - type = type.replace("library-", "") + type = type.replace("library-", "") let id = item.attributes.playParams.catalogId ?? item.id let index = types.findIndex(function (type) { diff --git a/src/renderer/views/main.ejs b/src/renderer/views/main.ejs index a4710132..cb23950d 100644 --- a/src/renderer/views/main.ejs +++ b/src/renderer/views/main.ejs @@ -37,8 +37,555 @@
{{ convertToMins(getSongProgress()) }}
+{{ convertToMins(mk.currentPlaybackDuration) }} +
+