From 3d71b5c6190fc08f265f37d2d4290c5bf471bb74 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Fri, 12 Jan 2024 16:32:03 +0900 Subject: [PATCH] binopan adjustments, get actor head size from the sprite --- .../AssembledSpriteAnimation.kt | 28 ++++++++++++++++--- src/net/torvald/terrarum/audio/AudioMixer.kt | 16 +++++++++++ .../terrarum/audio/MixerTrackProcessor.kt | 14 +++++----- src/net/torvald/terrarum/audio/dsp/BinoPan.kt | 26 +++++++++++------ .../terrarum/audio/dsp/TerrarumAudioFilter.kt | 9 ++++-- .../spriteassembler/AssembleSheetPixmap.kt | 18 ++++++------ 6 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/net/torvald/spriteanimation/AssembledSpriteAnimation.kt b/src/net/torvald/spriteanimation/AssembledSpriteAnimation.kt index 393e652a0..0eb5373d4 100644 --- a/src/net/torvald/spriteanimation/AssembledSpriteAnimation.kt +++ b/src/net/torvald/spriteanimation/AssembledSpriteAnimation.kt @@ -5,10 +5,9 @@ import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.utils.GdxRuntimeException -import net.torvald.terrarum.ItemCodex -import net.torvald.terrarum.Second -import net.torvald.terrarum.floorToFloat +import net.torvald.terrarum.* import net.torvald.terrarum.gameactors.ActorWithBody +import net.torvald.terrarum.gameactors.ActorWithBody.Companion.METER import net.torvald.terrarum.gameitems.GameItem import net.torvald.terrarum.modulebasegame.gameactors.Pocketed import net.torvald.terrarum.savegame.ByteArray64Reader @@ -19,7 +18,6 @@ import net.torvald.terrarum.spriteassembler.ADProperties import net.torvald.terrarum.spriteassembler.ADPropertyObject import net.torvald.terrarum.spriteassembler.AssembleFrameBase import net.torvald.terrarum.spriteassembler.AssembleSheetPixmap -import net.torvald.terrarum.tryDispose import java.io.InputStream import java.util.* @@ -61,6 +59,9 @@ class AssembledSpriteAnimation( @Transient private val res = HashMap() + @Transient var headSprite: TextureRegion? = null + @Transient var headSizeInMeter: Float? = null + init { // init = true @@ -73,6 +74,25 @@ class AssembledSpriteAnimation( else AssembleSheetPixmap.getAssetsDirFileGetter(adp) adp.bodyparts.forEach { res[it] = getPartTexture(fileGetter, it) } + + val (mugPixmap, headSprite0) = if (disk != null) + AssembleSheetPixmap.getMugshotFromVirtualDisk(disk, -1025L, adp) + else + AssembleSheetPixmap.getMugshotFromAssetsDir(adp) + + headSprite = headSprite0 + + mugPixmap?.let { pixmap -> + // measure width + val m = (0 until pixmap.height).map { y -> + (0 until pixmap.width).fold(0) { acc, x -> + acc + (pixmap.getPixel(x, y).and(255) > 128).toInt() + } + }.filter { it > 0 }.average() / METER + if (m > 0) headSizeInMeter = m.toFloat() * 0.36f + } + + mugPixmap?.dispose() } private var delta = 0f diff --git a/src/net/torvald/terrarum/audio/AudioMixer.kt b/src/net/torvald/terrarum/audio/AudioMixer.kt index 1cb3a3e75..1f38c3dd7 100644 --- a/src/net/torvald/terrarum/audio/AudioMixer.kt +++ b/src/net/torvald/terrarum/audio/AudioMixer.kt @@ -5,6 +5,7 @@ import com.badlogic.gdx.Input import com.badlogic.gdx.backends.lwjgl3.audio.Lwjgl3Audio import com.badlogic.gdx.utils.Disposable import com.jme3.math.FastMath +import net.torvald.spriteanimation.AssembledSpriteAnimation import net.torvald.terrarum.* import net.torvald.terrarum.App.THREAD_COUNT import net.torvald.terrarum.App.printdbg @@ -13,11 +14,13 @@ import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RAT import net.torvald.terrarum.audio.dsp.* import net.torvald.terrarum.concurrent.ThreadExecutor import net.torvald.terrarum.concurrent.sliceEvenly +import net.torvald.terrarum.gameactors.ActorWithBody import net.torvald.terrarum.modulebasegame.BuildingMaker import net.torvald.terrarum.modulebasegame.MusicContainer import java.lang.Thread.MAX_PRIORITY import java.util.* import java.util.concurrent.Callable +import java.util.concurrent.atomic.AtomicReference import kotlin.math.* /** @@ -114,11 +117,23 @@ object AudioMixer: Disposable { return dynamicTracks.filter { it.trackingTarget == null && !it.isPlaying }.minByOrNull { it.playStartedTime } } + var listenerHeadSize = BinoPan.EARDIST_DEFAULT; private set + + private fun setHeadSize(actor: ActorWithBody?): Float { + val scale = actor?.scale?.toFloat() ?: 1f + val headSize0 = if (actor?.sprite is AssembledSpriteAnimation) + (actor.sprite as AssembledSpriteAnimation).headSizeInMeter + else null + + return (headSize0 ?: 0f).times(scale).coerceAtLeast(BinoPan.EARDIST_DEFAULT) + } + private val processingExecutor = ThreadExecutor() val processingThread = Thread { // serial precessing while (processing) { actorNowPlaying = Terrarum.ingame?.actorNowPlaying + listenerHeadSize = setHeadSize(actorNowPlaying) // process dynamicTracks.forEach { @@ -143,6 +158,7 @@ object AudioMixer: Disposable { // parallel processing, it seems even on the 7950X this is less efficient than serial processing... /*while (processing) { actorNowPlaying = Terrarum.ingame?.actorNowPlaying + listenerHeadSize = setHeadSize(actorNowPlaying) for (tracks in parallelProcessingSchedule) { if (!processing) break diff --git a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt index d411cdbb9..516c40c5e 100644 --- a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt +++ b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt @@ -3,6 +3,7 @@ package net.torvald.terrarum.audio import com.badlogic.gdx.utils.Queue import com.jme3.math.FastMath import net.torvald.reflection.forceInvoke +import net.torvald.spriteanimation.AssembledSpriteAnimation import net.torvald.terrarum.App import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATED @@ -55,7 +56,7 @@ class MixerTrackProcessor(val buffertaille: Int, val rate: Int, val track: Terra private val distFalloff = 1600.0 private fun printdbg(msg: Any) { - if (true) App.printdbg("AudioAdapter ${track.name}", msg) + if (true) App.printdbg("MixerTrackProcessor ${track.name}", msg) } private fun allocateStreamBuf(track: TerrarumAudioMixerTrack) { @@ -116,6 +117,8 @@ class MixerTrackProcessor(val buffertaille: Int, val rate: Int, val track: Terra // update panning and shits if (track.trackType == TrackType.DYNAMIC_SOURCE && track.isPlaying) { + (track.filters[0] as BinoPan).earDist = AudioMixer.listenerHeadSize + if (AudioMixer.actorNowPlaying != null) { if (track.trackingTarget == null || track.trackingTarget == AudioMixer.actorNowPlaying) { // "reset" the track @@ -128,10 +131,7 @@ class MixerTrackProcessor(val buffertaille: Int, val rate: Int, val track: Terra val distFromActor = distBetweenActors(AudioMixer.actorNowPlaying!!, track.trackingTarget as ActorWithBody) val vol = track.maxVolume * getVolFun(distFromActor / distFalloff).coerceAtLeast(0.0) track.volume = vol - (track.filters[0] as BinoPan).pan = - if (relativeXpos <= -distFalloff) -1f - else if (relativeXpos >= distFalloff) 1f - else ((2*asin(relativeXpos / distFalloff)) / Math.PI).toFloat() + (track.filters[0] as BinoPan).pan = tanh(1.5 * relativeXpos / distFalloff).toFloat() (track.filters[1] as Lowpass).setCutoff( (SAMPLING_RATED*0.5) / (24.0 * (distFromActor / distFalloff).sqr() + 1.0) ) @@ -268,13 +268,13 @@ class MixerTrackProcessor(val buffertaille: Int, val rate: Int, val track: Terra // val x2 = x.pow(q(x)) // method 1. - //https://www.desmos.com/calculator/uzbjw10lna + // https://www.desmos.com/calculator/uzbjw10lna val K = 512.0 return K.pow(-sqrt(1.0+x.sqr())) * K // method 2. - // https://www.desmos.com/calculator/xxp5ipp85w + // https://www.desmos.com/calculator/3xsac66rsp } private fun FloatArray.applyVolume(volume: Float) = FloatArray(this.size) { (this[it] * volume) } diff --git a/src/net/torvald/terrarum/audio/dsp/BinoPan.kt b/src/net/torvald/terrarum/audio/dsp/BinoPan.kt index 7b5e099b8..1f455ced9 100644 --- a/src/net/torvald/terrarum/audio/dsp/BinoPan.kt +++ b/src/net/torvald/terrarum/audio/dsp/BinoPan.kt @@ -7,7 +7,9 @@ import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.audio.AudioMixer import net.torvald.terrarum.audio.TerrarumAudioMixerTrack import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.AUDIO_BUFFER_SIZE +import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF import net.torvald.terrarum.audio.decibelsToFullscale +import net.torvald.terrarum.ceilToInt import net.torvald.terrarum.ui.BasicDebugInfoWindow.Companion.COL_METER_GRAD import net.torvald.terrarum.ui.BasicDebugInfoWindow.Companion.COL_METER_GRAD2 import net.torvald.terrarum.ui.BasicDebugInfoWindow.Companion.FILTER_NAME_ACTIVE @@ -20,12 +22,14 @@ import kotlin.math.roundToInt /** * @param pan -1 for far-left, 0 for centre, 1 for far-right * @param soundSpeed speed of the sound in meters per seconds - * @param earDist distance between ears in meters + * @param earDist distance between ears in meters. Maximum: 16.0 */ -class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter() { +class BinoPan(var pan: Float, var earDist: Float = EARDIST_DEFAULT): TerrarumAudioFilter() { - private val delayLineL = FloatArray(AUDIO_BUFFER_SIZE) - private val delayLineR = FloatArray(AUDIO_BUFFER_SIZE) + private val MAX_DELAY = (EARDIST_MAX / AudioMixer.SPEED_OF_SOUND * SAMPLING_RATEF).ceilToInt() + + private val delayLineL = FloatArray(MAX_DELAY) + private val delayLineR = FloatArray(MAX_DELAY) private fun getFrom(index: Float, buf0: FloatArray, buf1: FloatArray): Float { @@ -42,6 +46,9 @@ class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter() companion object { + const val EARDIST_DEFAULT = 0.18f + const val EARDIST_MAX = 16f + private val PANNING_CONST = 6.0 private const val L = 0 private const val R = 1 @@ -50,11 +57,14 @@ class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter() } + /** + * @param angleOffset must be smaller than ±90 deg + */ private fun thru(sym: String, angleOffset: Float, inbuf: FloatArray, sumbuf: Array, delayLine: FloatArray) { - val pan = (pan + angleOffset / 90f).coerceIn(-1f, 1f) + val pan = (pan + angleOffset / (90f - angleOffset.absoluteValue)).coerceIn(-1f, 1f) + val timeDiffMax = earDist.coerceAtMost(EARDIST_MAX) / AudioMixer.SPEED_OF_SOUND * SAMPLING_RATEF val angle = pan * HALF_PI - val timeDiffMax = earDist / AudioMixer.SPEED_OF_SOUND * TerrarumAudioMixerTrack.SAMPLING_RATEF val delayInSamples = (timeDiffMax * FastMath.sin(angle)).absoluteValue val volMultDbThis = PANNING_CONST * pan.absoluteValue val volMultFsThis = decibelsToFullscale(volMultDbThis).toFloat() @@ -87,8 +97,8 @@ class BinoPan(var pan: Float, var earDist: Float = 0.18f): TerrarumAudioFilter() // printdbg(this, "$sym\tpan=$pan, mults=${mults[L]}\t${mults[R]}") } override fun thru(inbuf: List, outbuf: List) { - thru("L", -90f, inbuf[L], outLs, delayLineL) - thru("R", +90f, inbuf[R], outRs, delayLineR) + thru("L", -30f, inbuf[L], outLs, delayLineL) + thru("R", +30f, inbuf[R], outRs, delayLineR) for (i in 0 until AUDIO_BUFFER_SIZE) { val outL = (outLs[L][i] + outRs[L][i]) / 2f diff --git a/src/net/torvald/terrarum/audio/dsp/TerrarumAudioFilter.kt b/src/net/torvald/terrarum/audio/dsp/TerrarumAudioFilter.kt index 353ddfd27..097ec6800 100644 --- a/src/net/torvald/terrarum/audio/dsp/TerrarumAudioFilter.kt +++ b/src/net/torvald/terrarum/audio/dsp/TerrarumAudioFilter.kt @@ -19,6 +19,11 @@ abstract class TerrarumAudioFilter { fun FloatArray.applyGain(gain: Float = 1f) = this.map { it * gain }.toFloatArray() fun push(samples: FloatArray, buf: FloatArray) { - System.arraycopy(buf, samples.size, buf, 0, buf.size - samples.size) - System.arraycopy(samples, 0, buf, buf.size - samples.size, samples.size) + if (samples.size >= buf.size) { + System.arraycopy(samples, samples.size - buf.size, buf, 0, buf.size) + } + else { + System.arraycopy(buf, samples.size, buf, 0, buf.size - samples.size) + System.arraycopy(samples, 0, buf, buf.size - samples.size, samples.size) + } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/spriteassembler/AssembleSheetPixmap.kt b/src/net/torvald/terrarum/spriteassembler/AssembleSheetPixmap.kt index 9322f9f5f..7895225c0 100644 --- a/src/net/torvald/terrarum/spriteassembler/AssembleSheetPixmap.kt +++ b/src/net/torvald/terrarum/spriteassembler/AssembleSheetPixmap.kt @@ -80,19 +80,19 @@ object AssembleSheetPixmap { return null } - fun getMugshotFromAssetsDir(properties: ADProperties): TextureRegion? { - // TODO assemble from HAIR_FORE (optional), HAIR (optional) then HEAD (mandatory) + fun getMugshotFromAssetsDir(properties: ADProperties): Pair { + // assemble from HAIR_FORE (optional), HAIR (optional) then HEAD (mandatory) val getter = getAssetsDirFileGetter(properties) val headPixmap = getPartPixmap(getter, "HEAD") val hairPixmap = getPartPixmap(getter, "HAIR") val hair2Pixmap = getPartPixmap(getter, "HAIR_FORE") - if (headPixmap == null) throw FileNotFoundException("Bodyparts file of HEAD is not found!") - return composeMugshot(properties, headPixmap, hairPixmap, hair2Pixmap) + if (headPixmap == null) return null to null//throw FileNotFoundException("Bodyparts file of HEAD is not found!") + return headPixmap to composeMugshot(properties, headPixmap, hairPixmap, hair2Pixmap) } - fun getMugshotFromVirtualDisk(disk: SimpleFileSystem, entrynum: Long, properties: ADProperties): TextureRegion? { - // TODO assemble from HAIR_FORE (optional), HAIR (optional) then HEAD (mandatory) + fun getMugshotFromVirtualDisk(disk: SimpleFileSystem, entrynum: Long, properties: ADProperties): Pair { + // assemble from HAIR_FORE (optional), HAIR (optional) then HEAD (mandatory) val bodypartMapping = Properties() bodypartMapping.load(ByteArray64Reader(disk.getFile(entrynum)!!.bytes, Common.CHARSET)) val getter = getVirtualDiskFileGetter(bodypartMapping, disk) @@ -100,8 +100,8 @@ object AssembleSheetPixmap { val hairPixmap = getPartPixmap(getter, "HAIR") val hair2Pixmap = getPartPixmap(getter, "HAIR_FORE") - if (headPixmap == null) throw FileNotFoundException("Bodyparts file of HEAD is not found!") - return composeMugshot(properties, headPixmap, hairPixmap, hair2Pixmap) + if (headPixmap == null) return null to null//throw FileNotFoundException("Bodyparts file of HEAD is not found!") + return headPixmap to composeMugshot(properties, headPixmap, hairPixmap, hair2Pixmap) } private fun composeMugshot(properties: ADProperties, head: Pixmap, hair: Pixmap?, hair2: Pixmap?): TextureRegion { @@ -125,7 +125,7 @@ object AssembleSheetPixmap { val tr = TextureRegion(Texture(canvas)) canvas.dispose() - head.dispose() +// head.dispose() hair?.dispose() hair2?.dispose()