From 21d6f0854b270eae68b946ec62140f52e0ddf1ec Mon Sep 17 00:00:00 2001 From: MeLike2D Date: Thu, 25 Feb 2021 18:35:14 -0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20the=20final=20filters,=20fini?= =?UTF-8?q?sh=20the=20track=20related=20endpoints.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/build.gradle | 16 +- .../main/kotlin/obsidian/server/Obsidian.kt | 13 +- .../kotlin/obsidian/server/io/Dispatch.kt | 2 +- .../kotlin/obsidian/server/io/MagmaClient.kt | 5 + .../src/main/kotlin/obsidian/server/io/Op.kt | 3 +- .../kotlin/obsidian/server/io/Operation.kt | 26 ++- .../obsidian/server/io/controllers/Tracks.kt | 170 +++++++++++++----- .../kotlin/obsidian/server/player/Link.kt | 4 +- .../server/player/filter/FilterChain.kt | 28 ++- .../player/filter/impl/ChannelMixFilter.kt | 45 +++++ .../player/filter/impl/KaraokeFilter.kt | 44 +++++ .../player/filter/impl/LowPassFilter.kt | 37 ++++ .../player/filter/impl/RotationFilter.kt | 37 ++++ .../player/filter/impl/TimescaleFilter.kt | 7 +- .../player/filter/impl/TremoloFilter.kt | 7 +- .../player/filter/impl/VibratoFilter.kt | 53 ++++++ .../server/player/filter/impl/VolumeFilter.kt | 11 +- Server/src/test/resources/tracks.http | 20 +++ 18 files changed, 441 insertions(+), 87 deletions(-) create mode 100644 Server/src/main/kotlin/obsidian/server/player/filter/impl/ChannelMixFilter.kt create mode 100644 Server/src/main/kotlin/obsidian/server/player/filter/impl/KaraokeFilter.kt create mode 100644 Server/src/main/kotlin/obsidian/server/player/filter/impl/LowPassFilter.kt create mode 100644 Server/src/main/kotlin/obsidian/server/player/filter/impl/RotationFilter.kt create mode 100644 Server/src/main/kotlin/obsidian/server/player/filter/impl/VibratoFilter.kt create mode 100644 Server/src/test/resources/tracks.http diff --git a/Server/build.gradle b/Server/build.gradle index d452c2c..12c8c28 100644 --- a/Server/build.gradle +++ b/Server/build.gradle @@ -45,17 +45,20 @@ dependencies { /* ktor */ - // metrics + // Serialization + implementation "io.ktor:ktor-serialization:$ktorVersion" + + // Metrics implementation "io.ktor:ktor-metrics-micrometer:$ktorVersion" implementation "io.micrometer:micrometer-registry-prometheus:latest.release" - // server shit + // Server implementation "io.ktor:ktor-server-core:$ktorVersion" implementation "io.ktor:ktor-websockets:$ktorVersion" implementation "io.ktor:ktor-server-cio:$ktorVersion" implementation "io.ktor:ktor-locations:$ktorVersion" - // client shit + // Client implementation "io.ktor:ktor-client-core:$ktorVersion" implementation "io.ktor:ktor-client-websockets:$ktorVersion" implementation "io.ktor:ktor-client-okhttp:$ktorVersion" @@ -79,13 +82,14 @@ jar { compileJava.options.encoding = "UTF-8" compileKotlin { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_13 + targetCompatibility = JavaVersion.VERSION_13 kotlinOptions { - jvmTarget = "11" + jvmTarget = "13" freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalStdlibApi" freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ObsoleteCoroutinesApi" + freeCompilerArgs += "-Xopt-in=io.ktor.locations.KtorExperimentalLocationsAPI" freeCompilerArgs += "-Xinline-classes" } } diff --git a/Server/src/main/kotlin/obsidian/server/Obsidian.kt b/Server/src/main/kotlin/obsidian/server/Obsidian.kt index 3da82de..b4bb538 100644 --- a/Server/src/main/kotlin/obsidian/server/Obsidian.kt +++ b/Server/src/main/kotlin/obsidian/server/Obsidian.kt @@ -27,9 +27,11 @@ import ch.qos.logback.core.rolling.TimeBasedRollingPolicy import com.uchuhimo.konf.Config import com.uchuhimo.konf.source.yaml import io.ktor.application.* +import io.ktor.features.* import io.ktor.locations.* import io.ktor.metrics.micrometer.* import io.ktor.routing.* +import io.ktor.serialization.* import io.ktor.server.cio.* import io.ktor.server.engine.* import io.ktor.websocket.* @@ -78,8 +80,17 @@ object Obsidian { val server = embeddedServer(CIO, host = config[ObsidianConfig.Host], port = config[ObsidianConfig.Port]) { install(Locations) + install(WebSockets) - install(MicrometerMetrics) { registry = metricRegistry } + + install(MicrometerMetrics) { + registry = metricRegistry + } + + @Suppress() + install(ContentNegotiation) { + json() + } routing { magma.use(this) diff --git a/Server/src/main/kotlin/obsidian/server/io/Dispatch.kt b/Server/src/main/kotlin/obsidian/server/io/Dispatch.kt index 86e2cd4..8aa96a8 100644 --- a/Server/src/main/kotlin/obsidian/server/io/Dispatch.kt +++ b/Server/src/main/kotlin/obsidian/server/io/Dispatch.kt @@ -105,7 +105,7 @@ data class CurrentTrack( @Serializable data class Frames( - val nulled: Int, + val lost: Int, val sent: Int ) diff --git a/Server/src/main/kotlin/obsidian/server/io/MagmaClient.kt b/Server/src/main/kotlin/obsidian/server/io/MagmaClient.kt index dbf5d8d..20256f8 100644 --- a/Server/src/main/kotlin/obsidian/server/io/MagmaClient.kt +++ b/Server/src/main/kotlin/obsidian/server/io/MagmaClient.kt @@ -111,6 +111,11 @@ class MagmaClient( link.seekTo(position) } + on { + links[guildId]?.player?.stopTrack() + bedrock.destroyConnection(guildId) + } + on { val link = links.computeIfAbsent(guildId) { Link(this@MagmaClient, guildId) diff --git a/Server/src/main/kotlin/obsidian/server/io/Op.kt b/Server/src/main/kotlin/obsidian/server/io/Op.kt index 9404ac8..1718f84 100644 --- a/Server/src/main/kotlin/obsidian/server/io/Op.kt +++ b/Server/src/main/kotlin/obsidian/server/io/Op.kt @@ -43,7 +43,8 @@ enum class Op(val code: Int) { StopTrack(5), Pause(6), Filters(7), - Seek(8); + Seek(8), + Destroy(9); companion object Serializer : KSerializer { /** diff --git a/Server/src/main/kotlin/obsidian/server/io/Operation.kt b/Server/src/main/kotlin/obsidian/server/io/Operation.kt index 2cac27a..ce17823 100644 --- a/Server/src/main/kotlin/obsidian/server/io/Operation.kt +++ b/Server/src/main/kotlin/obsidian/server/io/Operation.kt @@ -19,7 +19,6 @@ package obsidian.server.io import kotlinx.serialization.* -import kotlinx.serialization.builtins.LongAsStringSerializer import kotlinx.serialization.builtins.nullable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor @@ -27,9 +26,7 @@ import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject -import obsidian.server.player.filter.impl.EqualizerFilter -import obsidian.server.player.filter.impl.TimescaleFilter -import obsidian.server.player.filter.impl.TremoloFilter +import obsidian.server.player.filter.impl.* sealed class Operation { companion object : DeserializationStrategy { @@ -63,6 +60,7 @@ sealed class Operation { Op.Pause -> decode(Pause.serializer()) Op.Filters -> decode(Filters.serializer()) Op.Seek -> decode(Seek.serializer()) + Op.Destroy -> decode(Destroy.serializer()) else -> if (data == null) { val element = decodeNullableSerializableElement(descriptor, idx, JsonElement.serializer().nullable) @@ -86,7 +84,6 @@ sealed class Operation { data class PlayTrack( val track: String, - @Serializable(with = LongAsStringSerializer::class) @SerialName("guild_id") val guildId: Long, @@ -101,18 +98,13 @@ data class PlayTrack( ) : Operation() @Serializable -data class StopTrack( - @Serializable(with = LongAsStringSerializer::class) - val guildId: Long - -) : Operation() +data class StopTrack(@SerialName("guild_id") val guildId: Long) : Operation() @Serializable data class SubmitVoiceUpdate( val endpoint: String, val token: String, - @Serializable(with = LongAsStringSerializer::class) @SerialName("guild_id") val guildId: Long, @@ -122,7 +114,6 @@ data class SubmitVoiceUpdate( @Serializable data class Pause( - @Serializable(with = LongAsStringSerializer::class) @SerialName("guild_id") val guildId: Long, val state: Boolean = true @@ -130,14 +121,18 @@ data class Pause( @Serializable data class Filters( - @Serializable(with = LongAsStringSerializer::class) @SerialName("guild_id") val guildId: Long, val volume: Float? = null, val tremolo: TremoloFilter? = null, val equalizer: EqualizerFilter? = null, - val timescale: TimescaleFilter? = null + val timescale: TimescaleFilter? = null, + val karaoke: KaraokeFilter? = null, + val channelMix: ChannelMixFilter? = null, + val vibrato: VibratoFilter? = null, + val rotation: RotationFilter? = null, + val lowPass: LowPassFilter? = null ) : Operation() @Serializable @@ -146,3 +141,6 @@ data class Seek( val guildId: Long, val position: Long ) : Operation() + +@Serializable +data class Destroy(@SerialName("guild_id") val guildId: Long) : Operation() diff --git a/Server/src/main/kotlin/obsidian/server/io/controllers/Tracks.kt b/Server/src/main/kotlin/obsidian/server/io/controllers/Tracks.kt index 6dfcd86..7ffa070 100644 --- a/Server/src/main/kotlin/obsidian/server/io/controllers/Tracks.kt +++ b/Server/src/main/kotlin/obsidian/server/io/controllers/Tracks.kt @@ -18,79 +18,165 @@ package obsidian.server.io.controllers +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException import com.sedmelluq.discord.lavaplayer.track.AudioTrack +import io.ktor.application.* import io.ktor.locations.* +import io.ktor.request.* +import io.ktor.response.* import io.ktor.routing.* import kotlinx.coroutines.future.await +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonTransformingSerializer import obsidian.server.Obsidian +import obsidian.server.io.AudioTrackSerializer import obsidian.server.io.search.AudioLoader import obsidian.server.io.search.LoadType import obsidian.server.util.TrackUtil -import obsidian.server.util.buildJson -import obsidian.server.util.respondJson -import org.json.JSONArray -import org.json.JSONObject import org.slf4j.Logger import org.slf4j.LoggerFactory class Tracks(routing: Routing) { init { routing { - get { data -> - context.respondJson(loadTracks(data.identifier)) + @Location("/loadtracks") + data class LoadTracks(val identifier: String) + + get { + val response = loadTracks(it.identifier) + context.respond(response) + } + + @Location("/decodetrack") + data class DecodeTrack(val track: String) + + get { + val track = TrackUtil.decode(it.track) + context.respond(getTrackInfo(track)) + } + + post("/decodetracks") { + val body = call.receive() + context.respond(body.tracks.map(::getTrackInfo)) } } } - private suspend fun loadTracks(identifier: String): JSONObject { + private suspend fun loadTracks(identifier: String): LoadTracksResponse { val result = AudioLoader(Obsidian.playerManager) .load(identifier) .await() - return buildJson { - put("tracks", buildJson { - for (track in result.tracks.map(::encodeTrack)) { - put(track) - } - }) + if (result.exception != null) { + logger.error("Track loading failed", result.exception) + } - put("playlist_info", buildJson { - put("name", result.playlistName) - put("selected_track", result.selectedTrack) - }) + return LoadTracksResponse( + tracks = result.tracks.map(::getTrack), - put("load_type", result.loadResultType) + type = result.loadResultType, - if (result.loadResultType == LoadType.LOAD_FAILED && result.exception != null) { - put("exception", buildJson { - put("message", result.exception!!.localizedMessage) - put("severity", result.exception!!.severity.toString()) - }) + playlistInfo = result.playlistName?.let { + LoadTracksResponse.PlaylistInfo( + name = it, + selectedTrack = result.selectedTrack + ) + }, - logger.error("Track loading failed", result.exception) + exception = if (result.loadResultType == LoadType.LOAD_FAILED && result.exception != null) { + LoadTracksResponse.Exception( + message = result.exception!!.localizedMessage, + severity = result.exception!!.severity + ) + } else { + null } - } + ) + } + + private fun getTrack(audioTrack: AudioTrack): Track = + Track(track = audioTrack, info = getTrackInfo(audioTrack)) + + private fun getTrackInfo(audioTrack: AudioTrack): Track.Info = + Track.Info( + title = audioTrack.info.title, + uri = audioTrack.info.uri, + identifier = audioTrack.info.identifier, + author = audioTrack.info.author, + length = audioTrack.duration, + isSeekable = audioTrack.isSeekable, + isStream = audioTrack.info.isStream, + position = audioTrack.position + ) + + @Serializable + data class DecodeTracksBody(@Serializable(with = AudioTrackListSerializer::class) val tracks: List) + + @Serializable + data class LoadTracksResponse( + @SerialName("load_type") + val type: LoadType, + + @SerialName("playlist_info") + val playlistInfo: PlaylistInfo?, + + val tracks: List, + + val exception: Exception? + ) { + @Serializable + data class Exception( + val message: String, + val severity: FriendlyException.Severity + ) + + @Serializable + data class PlaylistInfo( + val name: String, + + @SerialName("selected_track") + val selectedTrack: Int? + ) } - @Location("/loadtracks") - data class LoadTracks(val identifier: String) + @Serializable + data class Track( + @Serializable(with = AudioTrackSerializer::class) + val track: AudioTrack, + val info: Info + ) { + @Serializable + data class Info( + val title: String, + val author: String, + val uri: String, + val identifier: String, + val length: Long, + val position: Long, + + @SerialName("is_stream") + val isStream: Boolean, + + @SerialName("is_seekable") + val isSeekable: Boolean, + ) + } + + // taken from docs lmao + object AudioTrackListSerializer : JsonTransformingSerializer>(ListSerializer(AudioTrackSerializer)) { + override fun transformDeserialize(element: JsonElement): JsonElement = + if (element !is JsonArray) { + JsonArray(listOf(element)) + } else { + element + } + } companion object { private val logger: Logger = LoggerFactory.getLogger(Tracks::class.java) - - fun encodeTrack(track: AudioTrack): JSONObject = buildJson { - put("track", TrackUtil.encode(track)) - put("info", buildJson { - put("title", track.info.title) - put("source", track.sourceManager.sourceName) - put("author", track.info.author) - put("length", track.info.length) - put("identifier", track.info.identifier) - put("uri", track.info.uri) - put("is_stream", track.info.isStream) - put("is_seekable", track.isSeekable) - put("position", track.position) - }) - } } } diff --git a/Server/src/main/kotlin/obsidian/server/player/Link.kt b/Server/src/main/kotlin/obsidian/server/player/Link.kt index 263a421..ba4a946 100644 --- a/Server/src/main/kotlin/obsidian/server/player/Link.kt +++ b/Server/src/main/kotlin/obsidian/server/player/Link.kt @@ -40,8 +40,6 @@ import obsidian.server.io.PlayerUpdate import obsidian.server.player.filter.FilterChain import obsidian.server.util.TrackUtil import obsidian.server.util.config.ObsidianConfig -import org.slf4j.Logger -import org.slf4j.LoggerFactory import java.nio.ByteBuffer class Link( @@ -109,7 +107,7 @@ class Link( val frames = Frames( sent = frameCounter.lastSuccess, - nulled = frameCounter.lastLoss, + lost = frameCounter.lastLoss, ) client.send( diff --git a/Server/src/main/kotlin/obsidian/server/player/filter/FilterChain.kt b/Server/src/main/kotlin/obsidian/server/player/filter/FilterChain.kt index 0fbfce6..50672b1 100644 --- a/Server/src/main/kotlin/obsidian/server/player/filter/FilterChain.kt +++ b/Server/src/main/kotlin/obsidian/server/player/filter/FilterChain.kt @@ -27,22 +27,25 @@ import com.sedmelluq.discord.lavaplayer.format.AudioDataFormat import com.sedmelluq.discord.lavaplayer.track.AudioTrack import obsidian.server.io.Filters import obsidian.server.player.Link -import obsidian.server.player.filter.impl.EqualizerFilter -import obsidian.server.player.filter.impl.TimescaleFilter -import obsidian.server.player.filter.impl.TremoloFilter -import obsidian.server.player.filter.impl.VolumeFilter +import obsidian.server.player.filter.impl.* class FilterChain(val link: Link) { + var channelMix: ChannelMixFilter? = null var equalizer: EqualizerFilter? = null - var volume: VolumeFilter? = null + var karaoke: KaraokeFilter? = null + var lowPass: LowPassFilter? = null + var rotation: RotationFilter? = null var timescale: TimescaleFilter? = null var tremolo: TremoloFilter? = null + var vibrato: VibratoFilter? = null + var volume: VolumeFilter? = null + /** * All enabled filters. */ val enabled: List - get() = listOfNotNull(equalizer, volume, timescale, tremolo) + get() = listOfNotNull(channelMix, equalizer, karaoke, lowPass, rotation, timescale, tremolo, vibrato, volume) /** * Get the filter factory. @@ -95,9 +98,18 @@ class FilterChain(val link: Link) { fun from(link: Link, filters: Filters): FilterChain { return FilterChain(link).apply { - filters.volume?.let { volume = VolumeFilter(it) } - timescale = filters.timescale + channelMix = filters.channelMix equalizer = filters.equalizer + karaoke = filters.karaoke + lowPass = filters.lowPass + rotation = filters.rotation + timescale = filters.timescale + tremolo = filters.tremolo + vibrato = filters.vibrato + + filters.volume?.let { + volume = VolumeFilter(it) + } } } } diff --git a/Server/src/main/kotlin/obsidian/server/player/filter/impl/ChannelMixFilter.kt b/Server/src/main/kotlin/obsidian/server/player/filter/impl/ChannelMixFilter.kt new file mode 100644 index 0000000..880e839 --- /dev/null +++ b/Server/src/main/kotlin/obsidian/server/player/filter/impl/ChannelMixFilter.kt @@ -0,0 +1,45 @@ +/* + * Obsidian + * Copyright (C) 2021 Mixtape-Bot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package obsidian.server.player.filter.impl + +import com.github.natanbc.lavadsp.channelmix.ChannelMixPcmAudioFilter +import com.sedmelluq.discord.lavaplayer.filter.FloatPcmAudioFilter +import com.sedmelluq.discord.lavaplayer.format.AudioDataFormat +import kotlinx.serialization.Serializable +import obsidian.server.player.filter.Filter + +@Serializable +data class ChannelMixFilter( + val leftToLeft: Float = 1f, + val leftToRight: Float = 0f, + val rightToRight: Float = 0f, + val rightToLeft: Float = 1f, +) : Filter { + override val enabled: Boolean + get() = Filter.isSet(leftToLeft, 1.0f) || Filter.isSet(leftToRight, 0.0f) || + Filter.isSet(rightToLeft, 0.0f) || Filter.isSet(rightToRight, 1.0f); + + override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter = + ChannelMixPcmAudioFilter(downstream).also { + it.leftToLeft = leftToLeft + it.leftToRight = leftToRight + it.rightToRight = rightToRight + it.rightToLeft = rightToLeft + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/obsidian/server/player/filter/impl/KaraokeFilter.kt b/Server/src/main/kotlin/obsidian/server/player/filter/impl/KaraokeFilter.kt new file mode 100644 index 0000000..6ea767f --- /dev/null +++ b/Server/src/main/kotlin/obsidian/server/player/filter/impl/KaraokeFilter.kt @@ -0,0 +1,44 @@ +/* + * Obsidian + * Copyright (C) 2021 Mixtape-Bot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package obsidian.server.player.filter.impl + +import com.github.natanbc.lavadsp.karaoke.KaraokePcmAudioFilter +import com.sedmelluq.discord.lavaplayer.filter.FloatPcmAudioFilter +import com.sedmelluq.discord.lavaplayer.format.AudioDataFormat +import kotlinx.serialization.Serializable +import obsidian.server.player.filter.Filter + +@Serializable +data class KaraokeFilter( + val level: Float, + val monoLevel: Float, + val filterBand: Float, + val filterWidth: Float, +) : Filter { + override val enabled: Boolean + get() = Filter.isSet(level, 1f) || Filter.isSet(monoLevel, 1f) + || Filter.isSet(filterBand, 220f) || Filter.isSet(filterWidth, 100f) + + override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter = + KaraokePcmAudioFilter(downstream, format.channelCount, format.sampleRate) + .setLevel(level) + .setMonoLevel(monoLevel) + .setFilterBand(filterBand) + .setFilterWidth(filterWidth) +} \ No newline at end of file diff --git a/Server/src/main/kotlin/obsidian/server/player/filter/impl/LowPassFilter.kt b/Server/src/main/kotlin/obsidian/server/player/filter/impl/LowPassFilter.kt new file mode 100644 index 0000000..0f518f6 --- /dev/null +++ b/Server/src/main/kotlin/obsidian/server/player/filter/impl/LowPassFilter.kt @@ -0,0 +1,37 @@ +/* + * Obsidian + * Copyright (C) 2021 Mixtape-Bot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package obsidian.server.player.filter.impl + +import com.github.natanbc.lavadsp.lowpass.LowPassPcmAudioFilter +import com.sedmelluq.discord.lavaplayer.filter.FloatPcmAudioFilter +import com.sedmelluq.discord.lavaplayer.format.AudioDataFormat +import kotlinx.serialization.Serializable +import obsidian.server.player.filter.Filter + +@Serializable +data class LowPassFilter( + val smoothing: Float = 20f +) : Filter { + override val enabled: Boolean + get() = Filter.isSet(smoothing, 20f) + + override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter = + LowPassPcmAudioFilter(downstream, format.channelCount, 0) + .setSmoothing(smoothing) +} \ No newline at end of file diff --git a/Server/src/main/kotlin/obsidian/server/player/filter/impl/RotationFilter.kt b/Server/src/main/kotlin/obsidian/server/player/filter/impl/RotationFilter.kt new file mode 100644 index 0000000..2c1d85c --- /dev/null +++ b/Server/src/main/kotlin/obsidian/server/player/filter/impl/RotationFilter.kt @@ -0,0 +1,37 @@ +/* + * Obsidian + * Copyright (C) 2021 Mixtape-Bot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package obsidian.server.player.filter.impl + +import com.github.natanbc.lavadsp.rotation.RotationPcmAudioFilter +import com.sedmelluq.discord.lavaplayer.filter.FloatPcmAudioFilter +import com.sedmelluq.discord.lavaplayer.format.AudioDataFormat +import kotlinx.serialization.Serializable +import obsidian.server.player.filter.Filter + +@Serializable +data class RotationFilter( + val rotationHz: Float = 5f +) : Filter { + override val enabled: Boolean + get() = Filter.isSet(rotationHz, 5f) + + override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter = + RotationPcmAudioFilter(downstream, format.sampleRate) + .setRotationSpeed(rotationHz.toDouble() /* seems like a bad idea idk. */) +} \ No newline at end of file diff --git a/Server/src/main/kotlin/obsidian/server/player/filter/impl/TimescaleFilter.kt b/Server/src/main/kotlin/obsidian/server/player/filter/impl/TimescaleFilter.kt index 54212bd..298a10e 100644 --- a/Server/src/main/kotlin/obsidian/server/player/filter/impl/TimescaleFilter.kt +++ b/Server/src/main/kotlin/obsidian/server/player/filter/impl/TimescaleFilter.kt @@ -39,7 +39,7 @@ data class TimescaleFilter( || isSet(speed, 1f) || isSet(rate, 1f)) - override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter { + init { require(speed > 0) { "'speed' must be greater than 0" } @@ -51,11 +51,12 @@ data class TimescaleFilter( require(pitch > 0) { "'pitch' must be greater than 0" } + } - return TimescalePcmAudioFilter(downstream, format.channelCount, format.sampleRate) + override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter = + TimescalePcmAudioFilter(downstream, format.channelCount, format.sampleRate) .setPitch(pitch.toDouble()) .setRate(rate.toDouble()) .setSpeed(speed.toDouble()) - } } diff --git a/Server/src/main/kotlin/obsidian/server/player/filter/impl/TremoloFilter.kt b/Server/src/main/kotlin/obsidian/server/player/filter/impl/TremoloFilter.kt index bfafaaf..3b2e160 100644 --- a/Server/src/main/kotlin/obsidian/server/player/filter/impl/TremoloFilter.kt +++ b/Server/src/main/kotlin/obsidian/server/player/filter/impl/TremoloFilter.kt @@ -33,7 +33,7 @@ data class TremoloFilter( override val enabled: Boolean get() = isSet(frequency, 2f) || isSet(depth, 0.5f); - override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter { + init { require(depth <= 1 && depth > 0) { "'depth' must be greater than 0 and less than 1" } @@ -41,9 +41,10 @@ data class TremoloFilter( require(frequency > 0) { "'frequency' must be greater than 0" } + } - return TremoloPcmAudioFilter(downstream, format.channelCount, format.sampleRate) + override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter = + TremoloPcmAudioFilter(downstream, format.channelCount, format.sampleRate) .setDepth(depth) .setFrequency(frequency) - } } diff --git a/Server/src/main/kotlin/obsidian/server/player/filter/impl/VibratoFilter.kt b/Server/src/main/kotlin/obsidian/server/player/filter/impl/VibratoFilter.kt new file mode 100644 index 0000000..c4ec497 --- /dev/null +++ b/Server/src/main/kotlin/obsidian/server/player/filter/impl/VibratoFilter.kt @@ -0,0 +1,53 @@ +/* + * Obsidian + * Copyright (C) 2021 Mixtape-Bot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package obsidian.server.player.filter.impl + +import com.github.natanbc.lavadsp.vibrato.VibratoPcmAudioFilter +import com.sedmelluq.discord.lavaplayer.filter.FloatPcmAudioFilter +import com.sedmelluq.discord.lavaplayer.format.AudioDataFormat +import kotlinx.serialization.Serializable +import obsidian.server.player.filter.Filter + +@Serializable +data class VibratoFilter( + val frequency: Float = 2f, + val depth: Float = .5f +) : Filter { + override val enabled: Boolean + get() = Filter.isSet(frequency, 2f) || Filter.isSet(depth, 0.5f) + + init { + require(depth > 0 && depth < 1) { + "'depth' must be greater than 0 and less than 1." + } + + require(frequency > 0 && frequency < VIBRATO_FREQUENCY_MAX_HZ) { + "'frequency' must be greater than 0 and less than $VIBRATO_FREQUENCY_MAX_HZ" + } + } + + override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter = + VibratoPcmAudioFilter(downstream, format.channelCount, format.sampleRate) + .setFrequency(frequency) + .setDepth(depth) + + companion object { + private const val VIBRATO_FREQUENCY_MAX_HZ = 14f + } +} \ No newline at end of file diff --git a/Server/src/main/kotlin/obsidian/server/player/filter/impl/VolumeFilter.kt b/Server/src/main/kotlin/obsidian/server/player/filter/impl/VolumeFilter.kt index e3ad730..6610914 100644 --- a/Server/src/main/kotlin/obsidian/server/player/filter/impl/VolumeFilter.kt +++ b/Server/src/main/kotlin/obsidian/server/player/filter/impl/VolumeFilter.kt @@ -32,12 +32,13 @@ data class VolumeFilter( override val enabled: Boolean get() = isSet(volume, 1f) - override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter { - require(volume > 0 && volume <= 5) { - "'volume' must be greater than 0 and less than or equal to 5." + init { + require(volume in 0.0..5.0) { + "'volume' must be >= 0 and <= 5." } + } - return VolumePcmAudioFilter(downstream) + override fun build(format: AudioDataFormat, downstream: FloatPcmAudioFilter): FloatPcmAudioFilter = + VolumePcmAudioFilter(downstream) .setVolume(volume) - } } \ No newline at end of file diff --git a/Server/src/test/resources/tracks.http b/Server/src/test/resources/tracks.http new file mode 100644 index 0000000..9b1e2ca --- /dev/null +++ b/Server/src/test/resources/tracks.http @@ -0,0 +1,20 @@ +### /decodetracks multiple + +POST http://localhost:3030/decodetracks +Content-Type: application/json + +{ + "tracks": [ + "QAAAigIAKkhhbHNleSwgRG9taW5pYyBGaWtlIC0gRG9taW5pYydzIEludGVybHVkZQAGSGFsc2V5AAAAAAABZ2AAC1BiS05sOHBPVlRFAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9UGJLTmw4cE9WVEUAB3lvdXR1YmUAAAAAAAAAAA==", + "QAAAagIABGRhcmsADGJsb29keSB3aGl0ZQAAAAAAAW8wAAthSjFOS0NST1pDcwABACtodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWFKMU5LQ1JPWkNzAAd5b3V0dWJlAAAAAAAAAAA=" + ] +} + +### /decodetracks single + +POST http://localhost:3030/decodetracks +Content-Type: application/json + +{ + "tracks": "QAAAagIABGRhcmsADGJsb29keSB3aGl0ZQAAAAAAAW8wAAthSjFOS0NST1pDcwABACtodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWFKMU5LQ1JPWkNzAAd5b3V0dWJlAAAAAAAAAAA=" +}