diff --git a/README.md b/README.md index 766e6f02..a54b397f 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ Read this in other languages: [🇰🇷](./docs/readme/README-ko.md), [🇮🇸] - **Downloader**: downloads MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl) +- **Equalizer**: add filters to boost or cut specific range of frequencies (e.g. bass booster) + - **Exponential Volume**: Makes the volume slider [exponential](https://greasyfork.org/en/scripts/397686-youtube-music-fix-volume-ratio/) so it's easier to select lower volumes diff --git a/src/i18n/resources/en.json b/src/i18n/resources/en.json index 018b528e..c53105db 100644 --- a/src/i18n/resources/en.json +++ b/src/i18n/resources/en.json @@ -808,6 +808,18 @@ "visualizer-type": "Visualizer Type" }, "name": "Visualizer" + }, + "equalizer": { + "description": "Adds an equalizer to the player", + "name": "Equalizer", + "menu": { + "presets": { + "label": "Presets", + "list": { + "bass-booster": "Bass booster" + } + } + } } } } diff --git a/src/plugins/equalizer/index.ts b/src/plugins/equalizer/index.ts new file mode 100644 index 00000000..262816fd --- /dev/null +++ b/src/plugins/equalizer/index.ts @@ -0,0 +1,80 @@ +import { createPlugin } from '@/utils'; +import { t } from '@/i18n'; +import { MenuContext } from '@/types/contexts'; +import { MenuTemplate } from '@/menu'; +import { defaultPresets, presetConfigs, Preset, FilterConfig } from './presets'; + +export type EqualizerPluginConfig = { + enabled: boolean; + filters: FilterConfig[]; + presets: { [preset in Preset]: boolean }; +}; + +let appliedFilters: BiquadFilterNode[] = []; + +export default createPlugin({ + name: () => t('plugins.equalizer.name'), + description: () => t('plugins.equalizer.description'), + restartNeeded: false, + config: { + enabled: false, + filters: [], + presets: { 'bass-booster': false }, + } as EqualizerPluginConfig, + menu: async ({ + getConfig, + setConfig, + }: MenuContext): Promise => { + const config = await getConfig(); + + return [ + { + label: t('plugins.equalizer.menu.presets.label'), + type: 'submenu', + submenu: defaultPresets.map((preset) => ({ + label: t(`plugins.equalizer.menu.presets.list.${preset}`), + type: 'radio', + checked: config.presets[preset], + click() { + setConfig({ + presets: { ...config.presets, [preset]: !config.presets[preset] }, + }); + }, + })), + }, + ]; + }, + renderer: { + async start({ getConfig }) { + const config = await getConfig(); + + document.addEventListener( + 'ytmd:audio-can-play', + ({ detail: { audioSource, audioContext } }) => { + const filtersToApply = config.filters.concat( + defaultPresets + .filter((preset) => config.presets[preset]) + .map((preset) => presetConfigs[preset]), + ); + filtersToApply.forEach((filter) => { + const biquadFilter = audioContext.createBiquadFilter(); + biquadFilter.type = filter.type; + biquadFilter.frequency.value = filter.frequency; // filter frequency in Hz + biquadFilter.Q.value = filter.Q; + biquadFilter.gain.value = filter.gain; // filter gain in dB + + audioSource.connect(biquadFilter); + biquadFilter.connect(audioContext.destination); + + appliedFilters.push(biquadFilter); + }); + }, + { once: true, passive: true }, + ); + }, + stop() { + appliedFilters.forEach((filter) => filter.disconnect()); + appliedFilters = []; + }, + }, +}); diff --git a/src/plugins/equalizer/presets.ts b/src/plugins/equalizer/presets.ts new file mode 100644 index 00000000..5e2afc46 --- /dev/null +++ b/src/plugins/equalizer/presets.ts @@ -0,0 +1,18 @@ +export const defaultPresets = ['bass-booster'] as const; +export type Preset = (typeof defaultPresets)[number]; + +export type FilterConfig = { + type: BiquadFilterType; + frequency: number; + Q: number; + gain: number; +}; + +export const presetConfigs: Record = { + 'bass-booster': { + type: 'lowshelf', + frequency: 80, + Q: 100, + gain: 12.0, + }, +};