From 53f54a450d5b7f71013afdfc557b2a397bae1d14 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 2 Apr 2024 00:29:59 +0900 Subject: [PATCH] sound on memory wip --- src/net/torvald/terrarum/App.java | 26 ++++---- src/net/torvald/terrarum/audio/AudioMixer.kt | 35 ++++++++++- .../terrarum/audio/MixerTrackProcessor.kt | 3 +- .../torvald/terrarum/audio/MusicContainer.kt | 61 +++++++++++++++---- .../modulebasegame/TerrarumMusicGovernor.kt | 3 +- src/net/torvald/terrarum/ui/UIItem.kt | 1 + 6 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/net/torvald/terrarum/App.java b/src/net/torvald/terrarum/App.java index 12657dc91..56deacc54 100644 --- a/src/net/torvald/terrarum/App.java +++ b/src/net/torvald/terrarum/App.java @@ -554,9 +554,9 @@ public class App implements ApplicationListener { CommonResourcePool.INSTANCE.addToLoadingList("title_health1", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_take_a_break.tga"))); CommonResourcePool.INSTANCE.addToLoadingList("title_health2", () -> new Texture(Gdx.files.internal("./assets/graphics/gui/health_distance.tga"))); - CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bop", () -> new MusicContainer("haptic_bop", Gdx.files.internal("./assets/audio/effects/haptic_bop.ogg").file(), false, (Music m) -> { return null; })); - CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bup", () -> new MusicContainer("haptic_bup", Gdx.files.internal("./assets/audio/effects/haptic_bup.ogg").file(), false, (Music m) -> { return null; })); - CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bip", () -> new MusicContainer("haptic_bip", Gdx.files.internal("./assets/audio/effects/haptic_bip.ogg").file(), false, (Music m) -> { highPrioritySoundPlaying = false; return null; })); + CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bop", () -> new MusicContainer(true, "haptic_bop", Gdx.files.internal("./assets/audio/effects/haptic_bop.ogg").file(), false, (Music m) -> { return null; })); + CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bup", () -> new MusicContainer(true, "haptic_bup", Gdx.files.internal("./assets/audio/effects/haptic_bup.ogg").file(), false, (Music m) -> { return null; })); + CommonResourcePool.INSTANCE.addToLoadingList("sound:haptic_bip", () -> new MusicContainer(true, "haptic_bip", Gdx.files.internal("./assets/audio/effects/haptic_bip.ogg").file(), false, (Music m) -> { highPrioritySoundPlaying = false; return null; })); // make loading list CommonResourcePool.INSTANCE.loadAll(); @@ -1971,13 +1971,15 @@ public class App implements ApplicationListener { public static void playGUIsound(MusicContainer sound, double volume, float pan) { if (!highPrioritySoundPlaying) { - var it = audioMixer.getGuiTrack(); - it.stop(); - it.setCurrentTrack(sound); - it.setMaxVolumeFun(() -> volume); - it.setVolume(volume); - ((BinoPan) it.getFilters()[1]).setPan(pan); - it.play(); + var it = audioMixer.getFreeGuiTrackNoMatterWhat(); + if (it != null) { + it.stop(); + it.setCurrentTrack(sound); + it.setMaxVolumeFun(() -> volume); + it.setVolume(volume); + ((BinoPan) it.getFilters()[0]).setPan(pan); + it.play(); + } } } public static void playGUIsound(MusicContainer sound, double volume) { playGUIsound(sound, volume, 0.0f); } @@ -1985,13 +1987,13 @@ public class App implements ApplicationListener { public static void playGUIsoundHigh(MusicContainer sound, double volume, float pan) { // TODO when a sound is played thru this function, other sound play calls thru playGUIsound are ignored until this sound finishes playing - var it = audioMixer.getGuiTrack(); + var it = audioMixer.getFreeGuiTrackNoMatterWhat(); highPrioritySoundPlaying = true; it.stop(); it.setCurrentTrack(sound); it.setMaxVolumeFun(() -> volume); it.setVolume(volume); - ((BinoPan) it.getFilters()[1]).setPan(pan); + ((BinoPan) it.getFilters()[0]).setPan(pan); it.play(); } public static void playGUIsoundHigh(MusicContainer sound, double volume) { playGUIsoundHigh(sound, volume, 0.0f); } diff --git a/src/net/torvald/terrarum/audio/AudioMixer.kt b/src/net/torvald/terrarum/audio/AudioMixer.kt index d00f689fb..654f616e4 100644 --- a/src/net/torvald/terrarum/audio/AudioMixer.kt +++ b/src/net/torvald/terrarum/audio/AudioMixer.kt @@ -66,7 +66,7 @@ class AudioMixer : Disposable { else if (it == 9) "\u00D9Open\u00D9" // convolution1 else if (it == 10) "\u00D9Cave\u00D9" // convolution2 else if (it == 11) "\u00F0 \u00DA \u00F0" // fade - else "Trk${it+1}", trackType = if (it >= 7 || it == 3) TrackType.BUS else TrackType.STATIC_SOURCE, maxVolumeFun = { + else "Trk${it+1}", trackType = if (it >= 6 || it == 3) TrackType.BUS else TrackType.STATIC_SOURCE, maxVolumeFun = { when (it) { 0 -> { musicVolume } 4 -> { ambientVolume } @@ -115,10 +115,29 @@ class AudioMixer : Disposable { ambientTrack1, ambientTrack2, ambientTrack3, ambientTrack4 ) + val guiTracks = Array(4) { TerrarumAudioMixerTrack("GUI${it+1}", TrackType.STATIC_SOURCE) } + var processing = false var actorNowPlaying = Terrarum.ingame?.actorNowPlaying; private set + fun getFreeGuiTrackNoMatterWhat(): TerrarumAudioMixerTrack { + synchronized(this) { + val it = getFreeGuiTrack() ?: guiTracks.minBy { it.playStartedTime }.also { it.checkedOutTime = System.nanoTime() } + println("GuiTrack ${it.name}") + return it + } + } + + fun getFreeGuiTrack(): TerrarumAudioMixerTrack? { + synchronized(this) { + return guiTracks.minByOrNull { maxOf(it.checkedOutTime, it.playStartedTime) } + .also { + it?.checkedOutTime = System.nanoTime() + } + } + } + /** * Return oldest dynamic track, even if the track is currently playing */ @@ -169,6 +188,12 @@ class AudioMixer : Disposable { catch (e: Throwable) { e.printStackTrace() } } } + guiTracks.forEach { + if (!it.processor.paused) { + try { it.processor.run() } + catch (e: Throwable) { e.printStackTrace() } + } + } tracks.forEach { if (!it.processor.paused) { try { it.processor.run() } @@ -230,7 +255,10 @@ class AudioMixer : Disposable { it.filters[0] = Gain(1f) } - guiTrack.filters[1] = BinoPan(0f) + guiTracks.forEach { + guiTrack.addSidechainInput(it, 1.0) + it.filters[0] = BinoPan(0f) + } masterTrack.filters[0] = SoftClp masterTrack.filters[1] = Buffer @@ -285,6 +313,7 @@ class AudioMixer : Disposable { /*parallelProcessingSchedule = arrayOf(musicTrack, ambientTrack, guiTrack).sliceEvenly(THREAD_COUNT / 2).toTypedArray() + dynamicTracks.sliceEvenly(THREAD_COUNT / 2).toTypedArray() + + guiTracks + arrayOf(sfxSumBus, sumBus, convolveBusOpen, convolveBusCave).sliceEvenly(THREAD_COUNT / 2).toTypedArray() + arrayOf(fadeBus) + arrayOf(masterTrack)*/ @@ -552,6 +581,7 @@ class AudioMixer : Disposable { fun reset() { ambientStopped = true dynamicTracks.forEach { it.stop() } + guiTracks.forEach { it.stop() } tracks.filter { it.trackType == TrackType.STATIC_SOURCE }.forEach { it.stop() } tracks.forEach { it.processor.purgeBuffer() @@ -574,6 +604,7 @@ class AudioMixer : Disposable { // feedingThread.join() tracks.forEach { it.tryDispose() } dynamicTracks.forEach { it.tryDispose() } + guiTracks.forEach { it.tryDispose() } masterTrack.tryDispose() } } diff --git a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt index 7f28787d1..85178539f 100644 --- a/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt +++ b/src/net/torvald/terrarum/audio/MixerTrackProcessor.kt @@ -1,6 +1,5 @@ package net.torvald.terrarum.audio -import com.badlogic.gdx.utils.Queue import net.torvald.reflection.forceInvoke import net.torvald.terrarum.* import net.torvald.terrarum.audio.AudioMixer.Companion.DS_FLTIDX_LOW @@ -102,7 +101,7 @@ class MixerTrackProcessor(bufferSize: Int, val rate: Int, val track: TerrarumAud bytesRead += read0(buffer, bytesRead) } // if isLooping=true, do gapless sampleRead but reads from itself - else if (track.currentTrack?.gdxMusic?.isLooping == true && bytesRead < buffer.size) { + else if (track.currentTrack?.looping == true && bytesRead < buffer.size) { track.currentTrack?.reset() bytesRead += read0(buffer, bytesRead) diff --git a/src/net/torvald/terrarum/audio/MusicContainer.kt b/src/net/torvald/terrarum/audio/MusicContainer.kt index 686c59f9e..1f4b5caeb 100644 --- a/src/net/torvald/terrarum/audio/MusicContainer.kt +++ b/src/net/torvald/terrarum/audio/MusicContainer.kt @@ -12,28 +12,32 @@ import com.jcraft.jorbis.VorbisFile import javazoom.jl.decoder.Bitstream import net.torvald.reflection.extortField import net.torvald.reflection.forceInvoke -import net.torvald.terrarum.App -import net.torvald.terrarum.tryDispose +import net.torvald.unsafe.UnsafeHelper +import net.torvald.unsafe.UnsafePtr import java.io.File import java.io.FileInputStream import javax.sound.sampled.AudioSystem data class MusicContainer( + val toRAM: Boolean = false, val name: String, val file: File, - val loop: Boolean = false, + val looping: Boolean = false, internal var songFinishedHook: (Music) -> Unit = {} ): Disposable { val samplingRate: Int val codec: String - var samplesRead = 0L; internal set + var samplesReadCount = 0L; internal set val samplesTotal: Long - val gdxMusic = Gdx.audio.newMusic(FileHandle(file)) + private val gdxMusic: Music = Gdx.audio.newMusic(FileHandle(file)) + + private var soundBuf: UnsafePtr? = null; private set + init { - gdxMusic.isLooping = loop + gdxMusic.isLooping = looping gdxMusic.setOnCompletionListener(songFinishedHook) @@ -82,6 +86,45 @@ data class MusicContainer( else -> Long.MAX_VALUE } + + if (toRAM) { + if (samplesTotal == Long.MAX_VALUE) throw IllegalStateException("Could not read sample count") + + val readSize = 8192 + var readCount = 0L + val readBuf = ByteArray(readSize) + + soundBuf = UnsafeHelper.allocate(4L * samplesTotal) + + while (readCount < samplesTotal) { + val read = gdxMusic.forceInvoke("read", arrayOf(readBuf))!!.toLong() + + UnsafeHelper.memcpyRaw(readBuf, UnsafeHelper.getArrayOffset(readBuf), null, soundBuf!!.ptr + readCount, read) + + readCount += read + } + } + } + + fun readBytes(buffer: ByteArray): Int { + if (soundBuf == null) { + val bytesRead = gdxMusic.forceInvoke("read", arrayOf(buffer)) ?: 0 + samplesReadCount += bytesRead / 4 + return bytesRead + } + else { + val bytesToRead = minOf(buffer.size.toLong(), 4 * (samplesTotal - samplesReadCount)) + + UnsafeHelper.memcpyRaw(null, soundBuf!!.ptr, buffer, UnsafeHelper.getArrayOffset(buffer), bytesToRead) + + samplesReadCount += bytesToRead / 4 + return bytesToRead.toInt() + } + } + + fun reset() { + samplesReadCount = 0L + gdxMusic.forceInvoke("reset", arrayOf()) } private fun getWavFileSampleCount(file: File): Long { @@ -133,14 +176,10 @@ data class MusicContainer( override fun toString() = if (name.isEmpty()) file.nameWithoutExtension else name - fun reset() { - samplesRead = 0L - gdxMusic.forceInvoke("reset", arrayOf()) - } - override fun equals(other: Any?) = this.file.path == (other as MusicContainer).file.path override fun dispose() { gdxMusic.dispose() + soundBuf?.destroy() } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt index 8b05b9aba..57b494c8b 100644 --- a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt @@ -1,6 +1,5 @@ package net.torvald.terrarum.modulebasegame -import com.badlogic.gdx.Gdx import com.badlogic.gdx.utils.GdxRuntimeException import com.jme3.math.FastMath import net.torvald.terrarum.* @@ -148,7 +147,7 @@ class TerrarumMusicGovernor : MusicGovernor() { MusicContainer( fileHandle.nameWithoutExtension().replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" "), fileHandle.file(), - loop = true, + looping = true, ) } catch (e: GdxRuntimeException) { diff --git a/src/net/torvald/terrarum/ui/UIItem.kt b/src/net/torvald/terrarum/ui/UIItem.kt index 1ede8691a..dd1fb3a84 100644 --- a/src/net/torvald/terrarum/ui/UIItem.kt +++ b/src/net/torvald/terrarum/ui/UIItem.kt @@ -284,6 +284,7 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I } object UIItemAccessibilityUtil { + // TODO have multiple bop instances (num of copies equal to guiTracks), then play the track with its index according to getFreeGuiTrack() fun playHapticCursorHovered() { App.playGUIsound(CommonResourcePool.getAs("sound:haptic_bup"), 0.1666666) }