Skip to content
This repository has been archived by the owner on Nov 11, 2024. It is now read-only.

Commit

Permalink
feat: add audio filter feature (#1105)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mednoob authored Dec 31, 2022
1 parent ddafb68 commit e1cb51b
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 14 deletions.
13 changes: 13 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@
"slashRoleDescription": "View or change the DJ role",
"slashRoleNewRoleOption": "New role for the DJ"
},
"filter": {
"availableFilters": "Available Filters",
"currentlyUsedFilters": "Currently Used Filters",
"currentState": "`{filter}` is currently `{state}`",
"description": "Configure song filter",
"embedFooter": "To {opstate} the filter, use {prefix}filter {opstate} {filter}",
"filterSet": "`{filter}` filter is now `{state}`",
"slashStateDescription": "{state, select, enable {Enable} other {Disable}} a song filter",
"slashStateFilterDescription": "The song filter that will be {state, select, enable {enabled} other {disabled}}",
"slashStatusDescription": "Check the current status of song filters",
"slashStatusFilterDescription": "The song filter that you want to check",
"specifyFilter": "Please specify the song filter"
},
"lyrics": {
"apiError": "The API could not find the song **{song}**, because: `{message}`",
"description": "Show the lyrics from the song",
Expand Down
13 changes: 13 additions & 0 deletions lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@
"slashRoleDescription": "Mira o cambia la configuración del rol DJ",
"slashRoleNewRoleOption": "Nuevo rol DJ"
},
"filter": {
"availableFilters": "Available Filters",
"currentlyUsedFilters": "Currently Used Filters",
"currentState": "`{filter}` is currently `{state}`",
"description": "Configure song filter",
"embedFooter": "To {opstate, select, enable {enable} other {disable}} the filter, use {prefix}filter {opstate} {filter}",
"filterSet": "`{filter}` filter is now `{state}`",
"slashStateDescription": "{state, select, enable {Enable} other {Disable}} a song filter",
"slashStateFilterDescription": "The song filter that will be {state, select, enable {enabled} other {disabled}}",
"slashStatusDescription": "Check the current status of song filters",
"slashStatusFilterDescription": "The song filter that you want to check",
"specifyFilter": "Please specify the song filter"
},
"lyrics": {
"apiError": "El API no pudo encontrar la letra de la canción **{song}**, razón: {message}",
"description": "Muestra la letra de una canción",
Expand Down
13 changes: 13 additions & 0 deletions lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@
"slashRoleDescription": "Afficher ou modifier le rôle du DJ",
"slashRoleNewRoleOption": "Un nouveau rôle pour le DJ"
},
"filter": {
"availableFilters": "Available Filters",
"currentlyUsedFilters": "Currently Used Filters",
"currentState": "`{filter}` is currently `{state}`",
"description": "Configure song filter",
"embedFooter": "To {opstate, select, enable {enable} other {disable}} the filter, use {prefix}filter {opstate} {filter}",
"filterSet": "`{filter}` filter is now `{state}`",
"slashStateDescription": "{state, select, enable {Enable} other {Disable}} a song filter",
"slashStateFilterDescription": "The song filter that will be {state, select, enable {enabled} other {disabled}}",
"slashStatusDescription": "Check the current status of song filters",
"slashStatusFilterDescription": "The song filter that you want to check",
"specifyFilter": "Please specify the song filter"
},
"lyrics": {
"apiError": "L'API n'a pas pu trouver la chanson **{song}**, parce que : `{message}`",
"description": "Montrez les paroles de la chanson",
Expand Down
13 changes: 13 additions & 0 deletions lang/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,19 @@
"slashRoleDescription": "Lihat atau ubah role DJ",
"slashRoleNewRoleOption": "Role baru untuk DJ"
},
"filter": {
"availableFilters": "Filter yang tersedia",
"currentlyUsedFilters": "Filter yang sedang digunakan",
"currentState": "Filter `{filter}` dalam status `{state}`",
"description": "Menyetel filter lagu",
"embedFooter": "Untuk {opstate, select, enable {menyalakan} other {mematikan}} filter tersebut, gunakan {prefix}filter {opstate} {filter}",
"filterSet": "filter `{filter}` sekarang `{state}`",
"slashStateDescription": "{state, select, enable {Menyalakan} other {Mematikan}} filter lagu",
"slashStateFilterDescription": "Filter lagu yang akan di{state, select, enable {nyalakan} other {matikan}}",
"slashStatusDescription": "Cek status filter lagu",
"slashStatusFilterDescription": "Filter yang ingin di cek",
"specifyFilter": "Mohon berikan filter lagu"
},
"lyrics": {
"apiError": "Tidak dapat menemukan lagu **{song}**, dikarenakan: `{message}`",
"description": "Menampilkan lirik lagu",
Expand Down
155 changes: 155 additions & 0 deletions src/commands/music/FilterCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { inVC, sameVC, validVC } from "../../utils/decorators/MusicUtil";
import { CommandContext } from "../../structures/CommandContext";
import { createEmbed } from "../../utils/functions/createEmbed";
import { BaseCommand } from "../../structures/BaseCommand";
import { Command } from "../../utils/decorators/Command";
import { filterArgs } from "../../utils/functions/ffmpegArgs";
import i18n from "../../config";
import { ApplicationCommandOptionType, Message } from "discord.js";

type FilterSubCmd = "disable" | "enable" | "status";

const slashFilterChoices = Object.keys(filterArgs).map(x => ({ name: x, value: x }));

@Command({
aliases: [],
description: i18n.__("commands.music.filter.description"),
name: "filter",
slash: {
options: [
{
description: i18n.__mf("commands.music.filter.slashStateDescription", {
state: "enable"
}),
name: "enable",
options: [
{
choices: slashFilterChoices,
description: i18n.__mf("commands.music.filter.slashStateFilterDescription", {
state: "enable"
}),
name: "filter",
required: true,
type: ApplicationCommandOptionType.String
}
],
type: ApplicationCommandOptionType.Subcommand
},
{
description: i18n.__mf("commands.music.filter.slashStateDescription", {
state: "disable"
}),
name: "disable",
options: [
{
choices: slashFilterChoices,
description: i18n.__("commands.music.filter.slashStateFilterDescription", {
state: "disable"
}),
name: "filter",
required: true,
type: ApplicationCommandOptionType.String
}
],
type: ApplicationCommandOptionType.Subcommand
},
{
description: i18n.__("commands.music.filter.slashStatusDescription"),
name: "status",
options: [
{
choices: slashFilterChoices,
description: i18n.__("commands.music.filter.slashStatusFilterDescription"),
name: "filter",
required: false,
type: ApplicationCommandOptionType.String
}
],
type: ApplicationCommandOptionType.Subcommand
}
]
},
usage: "{prefix}filter"
})
export class FilterCommand extends BaseCommand {
@inVC
@validVC
@sameVC
public execute(ctx: CommandContext): Promise<Message> {
const mode: Record<string, FilterSubCmd> = {
on: "enable",
off: "disable",
enable: "enable",
disable: "disable",
stats: "status",
status: "status"
}
const subcmd = mode[
(
ctx.options?.getSubcommand() ??
ctx.args[0] as string | undefined
)?.toLowerCase() as unknown as string
] as FilterSubCmd | undefined;
const filter = (ctx.options?.getString("filter") ?? ctx.args[subcmd ? 1 : 0] as string | undefined)?.toLowerCase() as keyof typeof filterArgs;
if (subcmd === "enable" || subcmd === "disable") {
if (!filterArgs[filter]) {
return ctx.reply({
embeds: [createEmbed("error", i18n.__("commands.music.filter.specifyFilter"))]
});
}

ctx.guild?.queue?.setFilter(filter, subcmd === "enable");
return ctx.reply({
embeds: [
createEmbed("info", i18n.__mf("commands.music.filter.filterSet", {
filter,
state: subcmd === "enable" ? "ENABLED" : "DISABLED"
}))
]
});
}

if (filterArgs[filter]) {
return ctx.reply({
embeds: [
createEmbed("info", i18n.__mf("commands.music.filter.currentState", {
filter,
state: ctx.guild?.queue?.filters[filter] ? "ENABLED" : "DISABLED"
}))
.setFooter({
text: i18n.__mf("commands.music.filter.embedFooter", {
filter,
opstate: ctx.guild?.queue?.filters[filter] ? "disable" : "enable",
prefix: ctx.isCommand() ? "/" : this.client.config.mainPrefix
})
})
]
});
}

const keys = Object.keys(filterArgs) as (keyof typeof filterArgs)[]
return ctx.reply({
embeds: [
createEmbed("info")
.addFields(
{
name: i18n.__("commands.music.filter.availableFilters"),
value: keys
.filter(x => !ctx.guild?.queue?.filters[x])
.map(x => `\`${x}\``)
.join("\n") || "-",
inline: true
},
{
name: i18n.__("commands.music.filter.currentlyUsedFilters"),
value: keys
.filter(x => ctx.guild?.queue?.filters[x])
.map(x => `\`${x}\``)
.join("\n") || "-",
inline: true
}
)
]
})
}
}
30 changes: 18 additions & 12 deletions src/structures/ServerQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { SongManager } from "../utils/structures/SongManager";
import { createEmbed } from "../utils/functions/createEmbed";
import { play } from "../utils/handlers/GeneralUtil";
import { LoopMode, QueueSong } from "../typings";
import { filterArgs } from "../utils/functions/ffmpegArgs";
import { Rawon } from "./Rawon";
import i18n from "../config";
import { AudioPlayer, AudioPlayerPlayingState, AudioPlayerStatus, AudioResource, createAudioPlayer, VoiceConnection } from "@discordjs/voice";
import { TextChannel, Snowflake } from "discord.js";

const nonEnum = { enumerable: false };

export class ServerQueue {
public stayInVC = this.textChannel.client.config.stayInVCAfterFinished;
public readonly player: AudioPlayer = createAudioPlayer();
Expand All @@ -17,6 +20,7 @@ export class ServerQueue {
public readonly songs = new SongManager(this.client, this.textChannel.guild);
public loopMode: LoopMode = "OFF";
public shuffle = false;
public filters: Partial<Record<keyof typeof filterArgs, boolean>> = {};

private _lastVSUpdateMsg: Snowflake | null = null;
private _lastMusicMsg: Snowflake | null = null;
Expand All @@ -25,18 +29,10 @@ export class ServerQueue {

public constructor(public readonly textChannel: TextChannel) {
Object.defineProperties(this, {
_skipVoters: {
enumerable: false
},
_lastMusicMsg: {
enumerable: false
},
_lastVSUpdateMsg: {
enumerable: false
},
_volume: {
enumerable: false
}
_skipVoters: nonEnum,
_lastMusicMsg: nonEnum,
_lastVSUpdateMsg: nonEnum,
_volume: nonEnum
});

this.player
Expand Down Expand Up @@ -124,6 +120,16 @@ export class ServerQueue {
});
}

public setFilter(filter: keyof typeof filterArgs, state: boolean): void {
const before = this.filters[filter];
this.filters[filter] = state;

if (before !== state && this.player.state.status === AudioPlayerStatus.Playing) {
this.playing = false;
void play(this.textChannel.guild, (this.player.state.resource as AudioResource<QueueSong>).metadata.key, true);
}
}

public stop(): void {
this.songs.clear();
this.player.stop(true);
Expand Down
39 changes: 39 additions & 0 deletions src/utils/functions/ffmpegArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export const filterArgs = {
bassboost: "bass=g=7.5",
nightcore: "aresample=48000,asetrate=48000*1.25",
vaporwave: "aresample=48000,asetrate=48000*0.8",
treble: "treble=g=5",
"8d": "apulsator=hz=0.08",
reverse: "areverse",
surround: "surround",
haas: "haas",
phaser: "aphaser=in_gain=0.4",
gate: "agate",
mcompand: "mcompand",
flanger: "flanger",
tremolo: "tremolo",
karaoke: "stereotools=mlev=0.1",
vibrato: "vibrato=f=6.5",
echo: "aecho=0.8:0.9:1000:0.3"
}

export function ffmpegArgs(filters: Record<keyof typeof filterArgs, boolean>): string[] {
const keys = Object.keys(filters) as (keyof typeof filterArgs)[];
return [
"-loglevel", "0",
"-ar", "48000",
"-ac", "2",
"-f", "opus",
"-acodec", "libopus",
...(
keys.some(x => filters[x])
? [
"-af",
keys.reduce<string[]>((p, c) => {
if (filters[c]) p.push(filterArgs[c]);
return p;
}, []).join(",")
] : []
)
]
}
11 changes: 9 additions & 2 deletions src/utils/handlers/general/play.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { createEmbed } from "../../functions/createEmbed";
import { getStream } from "../YTDLUtil";
import { ffmpegArgs } from "../../functions/ffmpegArgs";
import i18n from "../../../config";
import { AudioPlayerError, createAudioResource, entersState, VoiceConnectionStatus } from "@discordjs/voice";
import { AudioPlayerError, createAudioResource, entersState, StreamType, VoiceConnectionStatus } from "@discordjs/voice";
import { ChannelType, Guild } from "discord.js";
import prism from "prism-media";

export async function play(guild: Guild, nextSong?: string, wasIdle?: boolean): Promise<void> {
const queue = guild.queue;
Expand Down Expand Up @@ -40,7 +42,12 @@ export async function play(guild: Guild, nextSong?: string, wasIdle?: boolean):
return;
}

const resource = createAudioResource(await getStream(song.song.url), { inlineVolume: true, metadata: song });
const stream = new prism.FFmpeg({
args: ffmpegArgs(queue.filters)
});
(await getStream(song.song.url)).pipe(stream);

const resource = createAudioResource(stream, { inlineVolume: true, inputType: StreamType.OggOpus, metadata: song });

queue.client.debugLog.logData("info", "PLAY_HANDLER", `Created audio resource for ${guild.name}(${guild.id})`);

Expand Down

0 comments on commit e1cb51b

Please sign in to comment.