music track resampling

This commit is contained in:
minjaesong
2023-12-11 03:35:03 +09:00
parent cc7f7b11d8
commit 949376b26a
6 changed files with 133 additions and 7 deletions

View File

@@ -309,10 +309,11 @@ object AudioMixer: Disposable {
// stop streaming if fadeBus is muted // stop streaming if fadeBus is muted
if (req.fadeTarget == 0.0 && track == fadeBus) { if (req.fadeTarget == 0.0 && track == fadeBus) {
musicTrack.stop()
musicTrack.currentTrack = null musicTrack.currentTrack = null
musicTrack.streamPlaying = false
ambientTrack.stop()
ambientTrack.currentTrack = null ambientTrack.currentTrack = null
ambientTrack.streamPlaying = false
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package net.torvald.terrarum.audio package net.torvald.terrarum.audio
import com.jme3.math.FastMath 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.BUFFER_SIZE
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE
import net.torvald.terrarum.ceilToInt 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 samplesIn = inputSamplingRate / gcd // 147 for 44100
private val samplesOut = SAMPLING_RATE / gcd // 160 for 48000 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) { private fun resampleBlock(inn: FloatArray, out: FloatArray) {
@@ -75,7 +76,11 @@ class AudioProcessBuf(inputSamplingRate: Int, val audioReadFun: (ByteArray) -> I
try { try {
val bytesRead = audioReadFun(readBuf) val bytesRead = audioReadFun(readBuf)
if (bytesRead == null || bytesRead <= 0) onAudioFinished() if (bytesRead == null || bytesRead <= 0) {
printdbg(this, "Music finished; bytesRead = $bytesRead")
onAudioFinished()
}
else { else {
for(c in 0 until readCount) { for(c in 0 until readCount) {
val sl = (getFromReadBuf(4 * c + 0, bytesRead) or getFromReadBuf(4 * c + 1, bytesRead).shl(8)).toShort() 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 // shift bytes in the fout
System.arraycopy(foutL, BS, foutL, 0, validSamplesInBuf - BS) System.arraycopy(foutL, BS, foutL, 0, validSamplesInBuf - BS)
System.arraycopy(foutR, BS, foutR, 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 // decrement necessary variables
validSamplesInBuf -= BS validSamplesInBuf -= BS

View File

@@ -1,11 +1,16 @@
package net.torvald.terrarum.audio package net.torvald.terrarum.audio
import com.badlogic.gdx.backends.lwjgl3.audio.Mp3
import com.badlogic.gdx.utils.Queue 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.reflection.forceInvoke
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.audio.dsp.BinoPan import net.torvald.terrarum.audio.dsp.BinoPan
import net.torvald.terrarum.audio.dsp.NullFilter import net.torvald.terrarum.audio.dsp.NullFilter
import net.torvald.terrarum.gameactors.ActorWithBody import net.torvald.terrarum.gameactors.ActorWithBody
import net.torvald.terrarum.printStackTrace
import net.torvald.terrarum.relativeXposition import net.torvald.terrarum.relativeXposition
import net.torvald.terrarum.sqr import net.torvald.terrarum.sqr
import kotlin.math.* import kotlin.math.*
@@ -44,11 +49,17 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
} }
private fun allocateStreamBuf(track: TerrarumAudioMixerTrack) { private fun allocateStreamBuf(track: TerrarumAudioMixerTrack) {
printdbg("Allocating a StreamBuf with rate ${track.currentTrack!!.samplingRate}")
streamBuf = AudioProcessBuf(track.currentTrack!!.samplingRate, { streamBuf = AudioProcessBuf(track.currentTrack!!.samplingRate, {
track.currentTrack?.gdxMusic?.forceInvoke<Int>("read", arrayOf(it)) val l = track.currentTrack?.gdxMusic?.forceInvoke<Int>("read", arrayOf(it))
track.currentTrack?.let {
it.samplesRead += l!! / 4
}
l
}, { }, {
track.stop() track.stop()
this.streamBuf = null this.streamBuf = null
printdbg("StreamBuf is now null")
}) })
} }

View File

@@ -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 override fun equals(other: Any?) = this.hash == (other as TerrarumAudioMixerTrack).hash
fun stop() { fun stop() {
currentTrack?.samplesRead = 0L
currentTrack?.gdxMusic?.forceInvoke<Int>("reset", arrayOf()) currentTrack?.gdxMusic?.forceInvoke<Int>("reset", arrayOf())
streamPlaying = false streamPlaying = false
// playStartedTime = 0L // playStartedTime = 0L
@@ -151,6 +152,7 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, var ma
// fireSoundFinishHook() // fireSoundFinishHook()
trackingTarget = null trackingTarget = null
processor.streamBuf = null
} }
fun fireSongFinishHook() { fun fireSongFinishHook() {

View File

@@ -2,12 +2,22 @@ package net.torvald.terrarum.modulebasegame
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.audio.Music 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.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.*
import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.audio.AudioMixer import net.torvald.terrarum.audio.AudioMixer
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE
import net.torvald.unicode.EMDASH import net.torvald.unicode.EMDASH
import java.io.File import java.io.File
import javax.sound.sampled.AudioSystem
data class MusicContainer( data class MusicContainer(
val name: String, val name: String,
@@ -15,11 +25,77 @@ data class MusicContainer(
val gdxMusic: Music, val gdxMusic: Music,
val songFinishedHook: (Music) -> Unit val songFinishedHook: (Music) -> Unit
) { ) {
val samplingRate: Int
val codec: String
var samplesRead = 0L; internal set
val samplesTotal: Long
init { init {
gdxMusic.setOnCompletionListener(songFinishedHook) gdxMusic.setOnCompletionListener(songFinishedHook)
samplingRate = when (gdxMusic) {
is Wav.Music -> {
val rate = gdxMusic.extortField<WavInputStream>("input")!!.sampleRate
printdbg(this, "music $name is WAV; rate = $rate")
rate
}
is Ogg.Music -> {
val rate = gdxMusic.extortField<OggInputStream>("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>("bitstream")!!
val header = bitstream.readFrame()
val rate = header.sampleRate
tempMusic.dispose()
// val bitstream = gdxMusic.extortField<Bitstream>("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 override fun toString() = if (name.isEmpty()) file.nameWithoutExtension else name
} }

View File

@@ -541,7 +541,34 @@ class BasicDebugInfoWindow : UICanvas() {
val faderY = y + stripFilterHeight * numberOfFilters val faderY = y + stripFilterHeight * numberOfFilters
// receives (opposite of "sends") // 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) -> track.sidechainInputs.reversed().forEachIndexed { i, (side, mix) ->
val mixDb = fullscaleToDecibels(mix) val mixDb = fullscaleToDecibels(mix)
val perc = ((mixDb + 24.0).coerceAtLeast(0.0) / 24.0).toFloat() val perc = ((mixDb + 24.0).coerceAtLeast(0.0) / 24.0).toFloat()