binopan adjustments, get actor head size from the sprite

This commit is contained in:
minjaesong
2024-01-12 16:32:03 +09:00
parent a2f61a2be7
commit 3d71b5c619
6 changed files with 81 additions and 30 deletions

View File

@@ -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<String, TextureRegion?>()
@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

View File

@@ -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

View File

@@ -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) }

View File

@@ -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<FloatArray>, 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<FloatArray>, outbuf: List<FloatArray>) {
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

View File

@@ -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)
}
}

View File

@@ -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<Pixmap?, TextureRegion?> {
// 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<Pixmap?, TextureRegion?> {
// 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()