mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-10 02:24:05 +09:00
music track resampling
This commit is contained in:
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user