footsteps using dynamic track wip

This commit is contained in:
minjaesong
2023-12-02 22:45:26 +09:00
parent 6da28b943a
commit 18f8fa881f
41 changed files with 225 additions and 17 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -261,7 +261,20 @@ $BULLET Ambient sound recordings:
- crickets_02.ogg - crickets_02.ogg
Copyright (C) 2012, 2013, 2015, 2016, 2017 Klankbeeld Copyright (C) 2012, 2013, 2015, 2016, 2017 Klankbeeld
Sound from <https://www.freesound.org/people/klankbeeld/> Sound from <https://www.freesound.org/people/klankbeeld>
$BULLET Footstep sound recordings:
- GRSS_01..06.ogg
- GRVL_01..07.ogg
- ROCK_01..09.ogg
- SAND_01..04.ogg
- WOOD_01..03.ogg
Copyright (C) 2020, 2021, 2022 Nox Sound
Sound from <https://freesound.org/people/Nox_Sound>

View File

@@ -217,7 +217,7 @@ open class IngameInstance(val batch: FlippingSpriteBatch, val isMultiplayer: Boo
actorContainerInactive.forEach { it.dispose() } actorContainerInactive.forEach { it.dispose() }
world.dispose() world.dispose()
disposables.forEach(Consumer { it.tryDispose() }) disposables.forEach { it.tryDispose() }
} }
//////////// ////////////

View File

@@ -6,6 +6,7 @@ import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.utils.JsonValue import com.badlogic.gdx.utils.JsonValue
import net.torvald.terrarum.App.* import net.torvald.terrarum.App.*
import net.torvald.terrarum.App.setToGameConfig import net.torvald.terrarum.App.setToGameConfig
import net.torvald.terrarum.audio.AudioCodex
import net.torvald.terrarum.blockproperties.BlockCodex import net.torvald.terrarum.blockproperties.BlockCodex
import net.torvald.terrarum.blockproperties.OreCodex import net.torvald.terrarum.blockproperties.OreCodex
import net.torvald.terrarum.blockproperties.WireCodex import net.torvald.terrarum.blockproperties.WireCodex
@@ -464,6 +465,16 @@ object ModMgr {
return dir.listFiles() return dir.listFiles()
} }
} }
fun getGdxFiles(module: String, path: String): Array<FileHandle> {
checkExistence(module)
val dir = getGdxFile(module, path)
if (!dir.isDirectory) {
throw FileNotFoundException("The path is not a directory")
}
else {
return dir.list()
}
}
/** Get a common file (literal file or directory) from all the installed mods. Files are guaranteed to exist. If a mod does not /** Get a common file (literal file or directory) from all the installed mods. Files are guaranteed to exist. If a mod does not
* contain the file, the mod will be skipped. * contain the file, the mod will be skipped.
@@ -668,6 +679,35 @@ object ModMgr {
} }
} }
object GameAudioLoader {
val audioPath = listOf(
"audio/music",
"audio/effects",
"audio/ambient",
)
init {
Terrarum.audioCodex = AudioCodex()
}
private fun loadAudio(basename: String, file: FileHandle) {
if (file.isDirectory)
file.list().forEach { loadAudio("$basename.${it.nameWithoutExtension()}", it) }
else {
val id = basename
val materialID = file.nameWithoutExtension().substringBefore('_')
Terrarum.audioCodex.addToFootstepPool(materialID, file)
printdbg(this, "Registering music $id")
}
}
@JvmStatic operator fun invoke(module: String) {
audioPath.forEach {
getGdxFiles(module, it).forEach { file -> loadAudio("audio.${file.name()}", file) }
}
}
}
/** /**
* A sugar-library for easy texture pack creation * A sugar-library for easy texture pack creation
*/ */

View File

@@ -16,6 +16,7 @@ import net.torvald.random.HQRNG
import net.torvald.terrarum.App.* import net.torvald.terrarum.App.*
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED
import net.torvald.terrarum.audio.AudioCodex
import net.torvald.terrarum.blockproperties.BlockCodex import net.torvald.terrarum.blockproperties.BlockCodex
import net.torvald.terrarum.blockproperties.FluidCodex import net.torvald.terrarum.blockproperties.FluidCodex
import net.torvald.terrarum.blockproperties.OreCodex import net.torvald.terrarum.blockproperties.OreCodex
@@ -78,6 +79,7 @@ object Terrarum : Disposable {
var apocryphas = HashMap<String, Any>(); internal set var apocryphas = HashMap<String, Any>(); internal set
var fluidCodex = FluidCodex(); internal set var fluidCodex = FluidCodex(); internal set
var oreCodex = OreCodex(); internal set var oreCodex = OreCodex(); internal set
var audioCodex = AudioCodex(); internal set
////////////////////////////// //////////////////////////////

View File

@@ -0,0 +1,34 @@
package net.torvald.terrarum.audio
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.audio.Music
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Disposable
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.modulebasegame.MusicContainer
import net.torvald.terrarum.tryDispose
typealias MaterialID = String
/**
* Created by minjaesong on 2023-12-02.
*/
class AudioCodex: Disposable {
@Transient val footsteps = HashMap<MaterialID, HashSet<MusicContainer>>()
internal constructor()
fun addToFootstepPool(materialID: MaterialID, music: FileHandle) {
if (footsteps[materialID] == null) footsteps[materialID] = HashSet()
footsteps[materialID]!!.add(
MusicContainer(music.nameWithoutExtension(), music.file(), Gdx.audio.newMusic(music)) {}
)
}
fun getRandomFootstep(materialID: MaterialID) = footsteps[materialID]?.random()
override fun dispose() {
footsteps.values.forEach { it.forEach { it.gdxMusic.tryDispose() } }
}
}

View File

@@ -101,18 +101,14 @@ object AudioMixer: Disposable {
* Return oldest dynamic track, even if the track is currently playing * Return oldest dynamic track, even if the track is currently playing
*/ */
fun getFreeTrackNoMatterWhat(): TerrarumAudioMixerTrack { fun getFreeTrackNoMatterWhat(): TerrarumAudioMixerTrack {
return dynamicTracks.minBy { it.playStartedTime } return getFreeTrack() ?: dynamicTracks.minBy { it.playStartedTime }
} }
/** /**
* Return oldest dynamic track that is not playing * Return oldest dynamic track that is not playing
*/ */
fun getFreeTrack(): TerrarumAudioMixerTrack? { fun getFreeTrack(): TerrarumAudioMixerTrack? {
val oldestTrack = dynamicTracks.minBy { it.playStartedTime } return dynamicTracks.filter { it.trackingTarget == null && !it.isPlaying }.minByOrNull { it.playStartedTime }
return if (oldestTrack.isPlaying)
null
else
oldestTrack
} }
private val processingExecutor = ThreadExecutor() private val processingExecutor = ThreadExecutor()

View File

@@ -2,6 +2,7 @@ package net.torvald.terrarum.audio
import com.badlogic.gdx.utils.Queue import com.badlogic.gdx.utils.Queue
import net.torvald.reflection.forceInvoke import net.torvald.reflection.forceInvoke
import net.torvald.terrarum.App
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
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
@@ -41,7 +42,7 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
private val distFalloff = 2048.0 private val distFalloff = 2048.0
private fun printdbg(msg: Any) { private fun printdbg(msg: Any) {
if (true) println("[AudioAdapter ${track.name}] $msg") if (true) App.printdbg("AudioAdapter ${track.name}", msg)
} }
override fun run() { override fun run() {
// while (running) { // uncomment to multithread // while (running) { // uncomment to multithread
@@ -80,8 +81,8 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
track.volume = track.maxVolume track.volume = track.maxVolume
(track.filters[0] as BinoPan).pan = 0f (track.filters[0] as BinoPan).pan = 0f
} }
else { else if (track.trackingTarget is ActorWithBody) {
val relativeXpos = relativeXposition(AudioMixer.actorNowPlaying!!, track.trackingTarget!!) val relativeXpos = relativeXposition(AudioMixer.actorNowPlaying!!, track.trackingTarget as ActorWithBody)
track.volume = track.maxVolume * (1.0 - relativeXpos.absoluteValue.pow(0.5) / distFalloff) track.volume = track.maxVolume * (1.0 - relativeXpos.absoluteValue.pow(0.5) / distFalloff)
(track.filters[0] as BinoPan).pan = ((2*asin(relativeXpos / distFalloff)) / Math.PI).toFloat() (track.filters[0] as BinoPan).pan = ((2*asin(relativeXpos / distFalloff)) / Math.PI).toFloat()
} }
@@ -91,6 +92,10 @@ class MixerTrackProcessor(val bufferSize: Int, val rate: Int, val track: Terraru
// fetch deviceBufferSize amount of sample from the disk // fetch deviceBufferSize amount of sample from the disk
if (track.trackType != TrackType.MASTER && track.trackType != TrackType.BUS && track.streamPlaying) { if (track.trackType != TrackType.MASTER && track.trackType != TrackType.BUS && track.streamPlaying) {
if (track.trackType == TrackType.DYNAMIC_SOURCE) {
printdbg("${track.name} streaming")
}
streamBuf.fetchBytes { streamBuf.fetchBytes {
val bytesRead = track.currentTrack?.gdxMusic?.forceInvoke<Int>("read", arrayOf(it)) val bytesRead = track.currentTrack?.gdxMusic?.forceInvoke<Int>("read", arrayOf(it))
if (bytesRead == null || bytesRead <= 0) { // some class (namely Mp3) may return 0 instead of negative value if (bytesRead == null || bytesRead <= 0) { // some class (namely Mp3) may return 0 instead of negative value

View File

@@ -9,6 +9,7 @@ import net.torvald.reflection.forceInvoke
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.audio.dsp.NullFilter import net.torvald.terrarum.audio.dsp.NullFilter
import net.torvald.terrarum.audio.dsp.TerrarumAudioFilter import net.torvald.terrarum.audio.dsp.TerrarumAudioFilter
import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.gameactors.ActorWithBody import net.torvald.terrarum.gameactors.ActorWithBody
import net.torvald.terrarum.getHashStr import net.torvald.terrarum.getHashStr
import net.torvald.terrarum.hashStrMap import net.torvald.terrarum.hashStrMap
@@ -22,7 +23,7 @@ enum class TrackType {
STATIC_SOURCE, DYNAMIC_SOURCE, BUS, MASTER STATIC_SOURCE, DYNAMIC_SOURCE, BUS, MASTER
} }
class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, private val maxVolumeFun: () -> Double = {1.0}): Disposable { class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, var maxVolumeFun: () -> Double = {1.0}): Disposable {
companion object { companion object {
const val SAMPLING_RATE = 48000 const val SAMPLING_RATE = 48000
@@ -57,7 +58,7 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, privat
val filters: Array<TerrarumAudioFilter> = Array(4) { NullFilter } val filters: Array<TerrarumAudioFilter> = Array(4) { NullFilter }
var trackingTarget: ActorWithBody? = null var trackingTarget: Actor? = null
var playStartedTime = 0L; private set var playStartedTime = 0L; private set
@@ -140,9 +141,16 @@ class TerrarumAudioMixerTrack(val name: String, val trackType: TrackType, privat
fun stop() { fun stop() {
currentTrack?.gdxMusic?.forceInvoke<Int>("reset", arrayOf()) currentTrack?.gdxMusic?.forceInvoke<Int>("reset", arrayOf())
streamPlaying = false streamPlaying = false
playStartedTime = 0L // playStartedTime = 0L
if (trackingTarget != null && currentTrack != null) {
trackingTarget!!.onAudioInterrupt(currentTrack!!)
}
fireSongFinishHook() fireSongFinishHook()
// fireSoundFinishHook() // fireSoundFinishHook()
trackingTarget = null
} }
fun fireSongFinishHook() { fun fireSongFinishHook() {

View File

@@ -3,11 +3,13 @@ package net.torvald.terrarum.gameactors
import com.badlogic.gdx.audio.Music import com.badlogic.gdx.audio.Music
import com.badlogic.gdx.audio.Sound import com.badlogic.gdx.audio.Sound
import net.torvald.random.HQRNG import net.torvald.random.HQRNG
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.INGAME import net.torvald.terrarum.INGAME
import net.torvald.terrarum.ReferencingRanges import net.torvald.terrarum.ReferencingRanges
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.audio.AudioMixer import net.torvald.terrarum.audio.AudioMixer
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack import net.torvald.terrarum.audio.TerrarumAudioMixerTrack
import net.torvald.terrarum.audio.TrackVolume
import net.torvald.terrarum.modulebasegame.MusicContainer import net.torvald.terrarum.modulebasegame.MusicContainer
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
import net.torvald.terrarum.modulebasegame.gameactors.Pocketed import net.torvald.terrarum.modulebasegame.gameactors.Pocketed
@@ -120,13 +122,17 @@ abstract class Actor : Comparable<Actor>, Runnable {
} }
} }
printdbg(this, "Dynamic Source ${track?.name}")
return track return track
} }
open fun startAudio(music: MusicContainer) { open fun startAudio(music: MusicContainer, volume: TrackVolume = 1.0) {
getTrackByAudio(music)?.let { getTrackByAudio(music)?.let {
it.trackingTarget = if (this is ActorWithBody) this else null it.stop()
it.trackingTarget = this
it.currentTrack = music it.currentTrack = music
it.maxVolumeFun = { volume }
it.play() it.play()
} }
} }

View File

@@ -11,6 +11,7 @@ import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF
import net.torvald.terrarum.audio.AudioCodex
import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.blockproperties.BlockProp import net.torvald.terrarum.blockproperties.BlockProp
import net.torvald.terrarum.gamecontroller.KeyToggler import net.torvald.terrarum.gamecontroller.KeyToggler
@@ -2041,6 +2042,20 @@ open class ActorWithBody : Actor {
makeDust0(tile, px, py, particleCount, collisionDamage, vecSum) makeDust0(tile, px, py, particleCount, collisionDamage, vecSum)
} }
} }
if (particleCount >= 1.0) {
val volumeMax = (particleCount.pow(0.75) / 8.0).coerceIn(0.0, 2.0)
val feetTileMats = feetTiles.map { BlockCodex[it.second].material }
val feetTileCnt = feetTileMats.size.toDouble()
val materialStats = feetTileMats.distinct().map { mat -> mat to feetTileMats.count { it == mat } }
materialStats.forEach { (mat, cnt) ->
Terrarum.audioCodex.getRandomFootstep(mat)?.let {
val vol = volumeMax * (cnt / feetTileCnt)
startAudio(it, vol)
printdbg(this, "Playing footstep $mat (vol: $vol, file: ${it.file.name})")
}
}
}
} }
} }

View File

@@ -43,6 +43,7 @@ class EntryPoint : ModuleEntryPoint() {
ModMgr.GameOreLoader.invoke(moduleName) ModMgr.GameOreLoader.invoke(moduleName)
ModMgr.GameLanguageLoader.invoke(moduleName) ModMgr.GameLanguageLoader.invoke(moduleName)
ModMgr.GameCraftingRecipeLoader.invoke(moduleName) ModMgr.GameCraftingRecipeLoader.invoke(moduleName)
ModMgr.GameAudioLoader.invoke(moduleName)
if (App.IS_DEVELOPMENT_BUILD) { if (App.IS_DEVELOPMENT_BUILD) {
println("[EntryPoint] Crafting Recipes: ") println("[EntryPoint] Crafting Recipes: ")

View File

@@ -8,6 +8,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.glutils.ShapeRenderer import com.badlogic.gdx.graphics.glutils.ShapeRenderer
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.App.* import net.torvald.terrarum.App.*
import net.torvald.terrarum.Terrarum.audioCodex
import net.torvald.terrarum.Terrarum.getPlayerSaveFiledesc import net.torvald.terrarum.Terrarum.getPlayerSaveFiledesc
import net.torvald.terrarum.Terrarum.getWorldSaveFiledesc import net.torvald.terrarum.Terrarum.getWorldSaveFiledesc
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
@@ -1586,7 +1587,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
} }
musicGovernor.dispose() musicGovernor.dispose()
audioCodex.dispose()
super.dispose() super.dispose()
} }
} }