From 949376b26a8d7ff220bcce9435cf91a3017c4fc3 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 11 Dec 2023 03:35:03 +0900 Subject: [PATCH] music track resampling --- src/net/torvald/terrarum/audio/AudioMixer.kt | 5 +- .../torvald/terrarum/audio/AudioProcessBuf.kt | 13 +++- .../terrarum/audio/MixerTrackProcessor.kt | 13 +++- .../terrarum/audio/TerrarumAudioMixerTrack.kt | 2 + .../modulebasegame/TerrarumMusicGovernor.kt | 78 ++++++++++++++++++- .../terrarum/ui/BasicDebugInfoWindow.kt | 29 ++++++- 6 files changed, 133 insertions(+), 7 deletions(-) diff --git a/src/net/torvald/terrarum/audio/AudioMixer.kt b/src/net/torvald/terrarum/audio/AudioMixer.kt index 7301ba8ed..00fd53ebd 100644 --- a/src/net/torvald/terrarum/audio/AudioMixer.kt +++ b/src/net/torvald/terrarum/audio/AudioMixer.kt @@ -309,10 +309,11 @@ object AudioMixer: Disposable { // stop streaming if fadeBus is muted if (req.fadeTarget == 0.0 && track == fadeBus) { + musicTrack.stop() musicTrack.currentTrack = null - musicTrack.streamPlaying = false + + ambientTrack.stop() ambientTrack.currentTrack = null - ambientTrack.streamPlaying = false } } } diff --git a/src/net/torvald/terrarum/audio/AudioProcessBuf.kt b/src/net/torvald/terrarum/audio/AudioProcessBuf.kt index a9d2e336f..0050719f7 100644 --- a/src/net/torvald/terrarum/audio/AudioProcessBuf.kt +++ b/src/net/torvald/terrarum/audio/AudioProcessBuf.kt @@ -1,6 +1,7 @@ package net.torvald.terrarum.audio import com.jme3.math.FastMath +import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.BUFFER_SIZE import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE import net.torvald.terrarum.ceilToInt @@ -40,7 +41,7 @@ class AudioProcessBuf(inputSamplingRate: Int, val audioReadFun: (ByteArray) -> I private val samplesIn = inputSamplingRate / gcd // 147 for 44100 private val samplesOut = SAMPLING_RATE / gcd // 160 for 48000 - private val internalBufferSize = samplesOut * ((BS.toFloat()) / samplesIn).ceilToInt() // (512 / 160) -> 640 for 44100, 48000 + private val internalBufferSize = if (doResample) (BS.toFloat() / samplesOut).ceilToInt().plus(1) * samplesOut else BS // (512 / 160) -> 640 for 44100, 48000 private fun resampleBlock(inn: FloatArray, out: FloatArray) { @@ -75,7 +76,11 @@ class AudioProcessBuf(inputSamplingRate: Int, val audioReadFun: (ByteArray) -> I try { val bytesRead = audioReadFun(readBuf) - if (bytesRead == null || bytesRead <= 0) onAudioFinished() + if (bytesRead == null || bytesRead <= 0) { + printdbg(this, "Music finished; bytesRead = $bytesRead") + + onAudioFinished() + } else { for(c in 0 until readCount) { val sl = (getFromReadBuf(4 * c + 0, bytesRead) or getFromReadBuf(4 * c + 1, bytesRead).shl(8)).toShort() @@ -119,6 +124,10 @@ class AudioProcessBuf(inputSamplingRate: Int, val audioReadFun: (ByteArray) -> I // shift bytes in the fout System.arraycopy(foutL, BS, foutL, 0, validSamplesInBuf - BS) System.arraycopy(foutR, BS, foutR, 0, validSamplesInBuf - BS) + for (i in validSamplesInBuf until BS) { + foutL[i] = 0f + foutR[i] = 0f + } // decrement necessary variables validSamplesInBuf -= BS diff --git a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt index a297c3fff..995fce26f 100644 --- a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt +++ b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt @@ -1,11 +1,16 @@ package net.torvald.terrarum.audio +import com.badlogic.gdx.backends.lwjgl3.audio.Mp3 import com.badlogic.gdx.utils.Queue +import javazoom.jl.decoder.Bitstream +import javazoom.jl.decoder.MP3Decoder +import net.torvald.reflection.extortField import net.torvald.reflection.forceInvoke import net.torvald.terrarum.App import net.torvald.terrarum.audio.dsp.BinoPan import net.torvald.terrarum.audio.dsp.NullFilter import net.torvald.terrarum.gameactors.ActorWithBody +import net.torvald.terrarum.printStackTrace import net.torvald.terrarum.relativeXposition import net.torvald.terrarum.sqr import kotlin.math.* @@ -44,11 +49,17 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru } private fun allocateStreamBuf(track: TerrarumAudioMixerTrack) { + printdbg("Allocating a StreamBuf with rate ${track.currentTrack!!.samplingRate}") streamBuf = AudioProcessBuf(track.currentTrack!!.samplingRate, { - track.currentTrack?.gdxMusic?.forceInvoke("read", arrayOf(it)) + val l = track.currentTrack?.gdxMusic?.forceInvoke("read", arrayOf(it)) + track.currentTrack?.let { + it.samplesRead += l!! / 4 + } + l }, { track.stop() this.streamBuf = null + printdbg("StreamBuf is now null") }) } diff --git a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt index 58512d725..30d29f8b6 100644 --- a/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt +++ b/src/net/torvald/terrarum/audio/TerrarumAudioMixerTrack.kt @@ -139,6 +139,7 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, var ma override fun equals(other: Any?) = this.hash == (other as TerrarumAudioMixerTrack).hash fun stop() { + currentTrack?.samplesRead = 0L currentTrack?.gdxMusic?.forceInvoke("reset", arrayOf()) streamPlaying = false // playStartedTime = 0L @@ -151,6 +152,7 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, var ma // fireSoundFinishHook() trackingTarget = null + processor.streamBuf = null } fun fireSongFinishHook() { diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt index dfcc85801..4581ee759 100644 --- a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt @@ -2,12 +2,22 @@ package net.torvald.terrarum.modulebasegame import com.badlogic.gdx.Gdx import com.badlogic.gdx.audio.Music +import com.badlogic.gdx.backends.lwjgl3.audio.Mp3 +import com.badlogic.gdx.backends.lwjgl3.audio.Ogg +import com.badlogic.gdx.backends.lwjgl3.audio.OggInputStream +import com.badlogic.gdx.backends.lwjgl3.audio.Wav +import com.badlogic.gdx.backends.lwjgl3.audio.Wav.WavInputStream import com.badlogic.gdx.utils.GdxRuntimeException +import com.jcraft.jorbis.VorbisFile +import javazoom.jl.decoder.Bitstream +import net.torvald.reflection.extortField import net.torvald.terrarum.* import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.audio.AudioMixer +import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE import net.torvald.unicode.EMDASH import java.io.File +import javax.sound.sampled.AudioSystem data class MusicContainer( val name: String, @@ -15,11 +25,77 @@ data class MusicContainer( val gdxMusic: Music, val songFinishedHook: (Music) -> Unit ) { + val samplingRate: Int + val codec: String + + var samplesRead = 0L; internal set + val samplesTotal: Long + init { gdxMusic.setOnCompletionListener(songFinishedHook) + + samplingRate = when (gdxMusic) { + is Wav.Music -> { + val rate = gdxMusic.extortField("input")!!.sampleRate + + printdbg(this, "music $name is WAV; rate = $rate") + rate + } + is Ogg.Music -> { + val rate = gdxMusic.extortField("input")!!.sampleRate + + printdbg(this, "music $name is OGG; rate = $rate") + rate + } + is Mp3.Music -> { + val tempMusic = Gdx.audio.newMusic(Gdx.files.absolute(file.absolutePath)) + val bitstream = tempMusic.extortField("bitstream")!! + val header = bitstream.readFrame() + val rate = header.sampleRate + tempMusic.dispose() + +// val bitstream = gdxMusic.extortField("bitstream")!! +// val header = bitstream.readFrame() +// val rate = header.sampleRate +// gdxMusic.reset() + + printdbg(this, "music $name is MP3; rate = $rate") + rate + } + else -> { + printdbg(this, "music $name is ${gdxMusic::class.qualifiedName}; rate = default") + SAMPLING_RATE + } + } + + codec = gdxMusic::class.qualifiedName!!.split('.').let { + if (it.last() == "Music") it.dropLast(1).last() else it.last() + } + + samplesTotal = when (gdxMusic) { + is Wav.Music -> getWavFileSampleCount(file) + is Ogg.Music -> getOggFileSampleCount(file) + else -> Long.MAX_VALUE + } + } - val samplingRate: Int = 44100 // TODO + private fun getWavFileSampleCount(file: File): Long { + val ais = AudioSystem.getAudioInputStream(file) + val r = ais.frameLength + ais.close() + return r + } + + private fun getOggFileSampleCount(file: File): Long { + try { + val vorbisFile = VorbisFile(file.absolutePath) + return vorbisFile.pcm_total(0) + } + catch (e: Throwable) { + return Long.MAX_VALUE + } + } override fun toString() = if (name.isEmpty()) file.nameWithoutExtension else name } diff --git a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt index 6dd5d0fe5..38f8987f4 100644 --- a/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt +++ b/src/net/torvald/terrarum/ui/BasicDebugInfoWindow.kt @@ -541,7 +541,34 @@ class BasicDebugInfoWindow : UICanvas() { val faderY = y + stripFilterHeight * numberOfFilters // receives (opposite of "sends") - if (track != AudioMixer.sfxSumBus) { + if (track.trackType == TrackType.STATIC_SOURCE && track.sidechainInputs.isEmpty()) { + // show sample rate and codec info instead + listOf( + (track.currentTrack?.name ?: " -----").let { + if (it.length > 7) it.replace(" ", "").let { it.substring(0 until minOf(it.length, 7)) } + else it + }, + "C:${track.currentTrack?.codec ?: ""}", + "R:${track.currentTrack?.samplingRate ?: ""}", + ).forEachIndexed { i, s -> + // gauge background + batch.color = COL_METER_TROUGH + Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, stripW.toFloat(), 14f) + + // fill the song title line with a progress bar + if (i == 0 && track.currentTrack != null) { + val perc = (track.currentTrack!!.samplesRead.toFloat() / track.currentTrack!!.samplesTotal).coerceAtMost(1f) + batch.color = COL_SENDS_GRAD2 + Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f, stripW * perc, 14f) + batch.color = COL_SENDS_GRAD + Toolkit.fillArea(batch, x.toFloat(), faderY - (i + 1) * 16f + 14f, stripW * perc, 2f) + } + + batch.color = FILTER_NAME_ACTIVE + App.fontSmallNumbers.draw(batch, s, x + 3f, faderY - (i + 1) * 16f + 1f) + } + } + else if (track != AudioMixer.sfxSumBus) { track.sidechainInputs.reversed().forEachIndexed { i, (side, mix) -> val mixDb = fullscaleToDecibels(mix) val perc = ((mixDb + 24.0).coerceAtLeast(0.0) / 24.0).toFloat()