sound on memory wip

This commit is contained in:
minjaesong
2024-04-02 00:29:59 +09:00
parent a1a70274dd
commit 53f54a450d
6 changed files with 100 additions and 29 deletions

View File

@@ -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_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("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_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("haptic_bup", Gdx.files.internal("./assets/audio/effects/haptic_bup.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("haptic_bip", Gdx.files.internal("./assets/audio/effects/haptic_bip.ogg").file(), false, (Music m) -> { highPrioritySoundPlaying = false; 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 // make loading list
CommonResourcePool.INSTANCE.loadAll(); CommonResourcePool.INSTANCE.loadAll();
@@ -1971,13 +1971,15 @@ public class App implements ApplicationListener {
public static void playGUIsound(MusicContainer sound, double volume, float pan) { public static void playGUIsound(MusicContainer sound, double volume, float pan) {
if (!highPrioritySoundPlaying) { if (!highPrioritySoundPlaying) {
var it = audioMixer.getGuiTrack(); var it = audioMixer.getFreeGuiTrackNoMatterWhat();
it.stop(); if (it != null) {
it.setCurrentTrack(sound); it.stop();
it.setMaxVolumeFun(() -> volume); it.setCurrentTrack(sound);
it.setVolume(volume); it.setMaxVolumeFun(() -> volume);
((BinoPan) it.getFilters()[1]).setPan(pan); it.setVolume(volume);
it.play(); ((BinoPan) it.getFilters()[0]).setPan(pan);
it.play();
}
} }
} }
public static void playGUIsound(MusicContainer sound, double volume) { playGUIsound(sound, volume, 0.0f); } 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) { 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 // 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; highPrioritySoundPlaying = true;
it.stop(); it.stop();
it.setCurrentTrack(sound); it.setCurrentTrack(sound);
it.setMaxVolumeFun(() -> volume); it.setMaxVolumeFun(() -> volume);
it.setVolume(volume); it.setVolume(volume);
((BinoPan) it.getFilters()[1]).setPan(pan); ((BinoPan) it.getFilters()[0]).setPan(pan);
it.play(); it.play();
} }
public static void playGUIsoundHigh(MusicContainer sound, double volume) { playGUIsoundHigh(sound, volume, 0.0f); } public static void playGUIsoundHigh(MusicContainer sound, double volume) { playGUIsoundHigh(sound, volume, 0.0f); }

View File

@@ -66,7 +66,7 @@ class AudioMixer : Disposable {
else if (it == 9) "\u00D9Open\u00D9" // convolution1 else if (it == 9) "\u00D9Open\u00D9" // convolution1
else if (it == 10) "\u00D9Cave\u00D9" // convolution2 else if (it == 10) "\u00D9Cave\u00D9" // convolution2
else if (it == 11) "\u00F0 \u00DA \u00F0" // fade 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) { when (it) {
0 -> { musicVolume } 0 -> { musicVolume }
4 -> { ambientVolume } 4 -> { ambientVolume }
@@ -115,10 +115,29 @@ class AudioMixer : Disposable {
ambientTrack1, ambientTrack2, ambientTrack3, ambientTrack4 ambientTrack1, ambientTrack2, ambientTrack3, ambientTrack4
) )
val guiTracks = Array(4) { TerrarumAudioMixerTrack("GUI${it+1}", TrackType.STATIC_SOURCE) }
var processing = false var processing = false
var actorNowPlaying = Terrarum.ingame?.actorNowPlaying; private set 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 * Return oldest dynamic track, even if the track is currently playing
*/ */
@@ -169,6 +188,12 @@ class AudioMixer : Disposable {
catch (e: Throwable) { e.printStackTrace() } catch (e: Throwable) { e.printStackTrace() }
} }
} }
guiTracks.forEach {
if (!it.processor.paused) {
try { it.processor.run() }
catch (e: Throwable) { e.printStackTrace() }
}
}
tracks.forEach { tracks.forEach {
if (!it.processor.paused) { if (!it.processor.paused) {
try { it.processor.run() } try { it.processor.run() }
@@ -230,7 +255,10 @@ class AudioMixer : Disposable {
it.filters[0] = Gain(1f) 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[0] = SoftClp
masterTrack.filters[1] = Buffer masterTrack.filters[1] = Buffer
@@ -285,6 +313,7 @@ class AudioMixer : Disposable {
/*parallelProcessingSchedule = /*parallelProcessingSchedule =
arrayOf(musicTrack, ambientTrack, guiTrack).sliceEvenly(THREAD_COUNT / 2).toTypedArray() + arrayOf(musicTrack, ambientTrack, guiTrack).sliceEvenly(THREAD_COUNT / 2).toTypedArray() +
dynamicTracks.sliceEvenly(THREAD_COUNT / 2).toTypedArray() + dynamicTracks.sliceEvenly(THREAD_COUNT / 2).toTypedArray() +
guiTracks +
arrayOf(sfxSumBus, sumBus, convolveBusOpen, convolveBusCave).sliceEvenly(THREAD_COUNT / 2).toTypedArray() + arrayOf(sfxSumBus, sumBus, convolveBusOpen, convolveBusCave).sliceEvenly(THREAD_COUNT / 2).toTypedArray() +
arrayOf(fadeBus) + arrayOf(fadeBus) +
arrayOf(masterTrack)*/ arrayOf(masterTrack)*/
@@ -552,6 +581,7 @@ class AudioMixer : Disposable {
fun reset() { fun reset() {
ambientStopped = true ambientStopped = true
dynamicTracks.forEach { it.stop() } dynamicTracks.forEach { it.stop() }
guiTracks.forEach { it.stop() }
tracks.filter { it.trackType == TrackType.STATIC_SOURCE }.forEach { it.stop() } tracks.filter { it.trackType == TrackType.STATIC_SOURCE }.forEach { it.stop() }
tracks.forEach { tracks.forEach {
it.processor.purgeBuffer() it.processor.purgeBuffer()
@@ -574,6 +604,7 @@ class AudioMixer : Disposable {
// feedingThread.join() // feedingThread.join()
tracks.forEach { it.tryDispose() } tracks.forEach { it.tryDispose() }
dynamicTracks.forEach { it.tryDispose() } dynamicTracks.forEach { it.tryDispose() }
guiTracks.forEach { it.tryDispose() }
masterTrack.tryDispose() masterTrack.tryDispose()
} }
} }

View File

@@ -1,6 +1,5 @@
package net.torvald.terrarum.audio package net.torvald.terrarum.audio
import com.badlogic.gdx.utils.Queue
import net.torvald.reflection.forceInvoke import net.torvald.reflection.forceInvoke
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.audio.AudioMixer.Companion.DS_FLTIDX_LOW 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) bytesRead += read0(buffer, bytesRead)
} }
// if isLooping=true, do gapless sampleRead but reads from itself // 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() track.currentTrack?.reset()
bytesRead += read0(buffer, bytesRead) bytesRead += read0(buffer, bytesRead)

View File

@@ -12,28 +12,32 @@ import com.jcraft.jorbis.VorbisFile
import javazoom.jl.decoder.Bitstream import javazoom.jl.decoder.Bitstream
import net.torvald.reflection.extortField import net.torvald.reflection.extortField
import net.torvald.reflection.forceInvoke import net.torvald.reflection.forceInvoke
import net.torvald.terrarum.App import net.torvald.unsafe.UnsafeHelper
import net.torvald.terrarum.tryDispose import net.torvald.unsafe.UnsafePtr
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import javax.sound.sampled.AudioSystem import javax.sound.sampled.AudioSystem
data class MusicContainer( data class MusicContainer(
val toRAM: Boolean = false,
val name: String, val name: String,
val file: File, val file: File,
val loop: Boolean = false, val looping: Boolean = false,
internal var songFinishedHook: (Music) -> Unit = {} internal var songFinishedHook: (Music) -> Unit = {}
): Disposable { ): Disposable {
val samplingRate: Int val samplingRate: Int
val codec: String val codec: String
var samplesRead = 0L; internal set var samplesReadCount = 0L; internal set
val samplesTotal: Long 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 { init {
gdxMusic.isLooping = loop gdxMusic.isLooping = looping
gdxMusic.setOnCompletionListener(songFinishedHook) gdxMusic.setOnCompletionListener(songFinishedHook)
@@ -82,6 +86,45 @@ data class MusicContainer(
else -> Long.MAX_VALUE 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<Int>("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<Int>("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<Int>("reset", arrayOf())
} }
private fun getWavFileSampleCount(file: File): Long { private fun getWavFileSampleCount(file: File): Long {
@@ -133,14 +176,10 @@ data class MusicContainer(
override fun toString() = if (name.isEmpty()) file.nameWithoutExtension else name override fun toString() = if (name.isEmpty()) file.nameWithoutExtension else name
fun reset() {
samplesRead = 0L
gdxMusic.forceInvoke<Int>("reset", arrayOf())
}
override fun equals(other: Any?) = this.file.path == (other as MusicContainer).file.path override fun equals(other: Any?) = this.file.path == (other as MusicContainer).file.path
override fun dispose() { override fun dispose() {
gdxMusic.dispose() gdxMusic.dispose()
soundBuf?.destroy()
} }
} }

View File

@@ -1,6 +1,5 @@
package net.torvald.terrarum.modulebasegame package net.torvald.terrarum.modulebasegame
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.utils.GdxRuntimeException import com.badlogic.gdx.utils.GdxRuntimeException
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.terrarum.* import net.torvald.terrarum.*
@@ -148,7 +147,7 @@ class TerrarumMusicGovernor : MusicGovernor() {
MusicContainer( MusicContainer(
fileHandle.nameWithoutExtension().replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" "), fileHandle.nameWithoutExtension().replace('_', ' ').split(" ").map { it.capitalize() }.joinToString(" "),
fileHandle.file(), fileHandle.file(),
loop = true, looping = true,
) )
} }
catch (e: GdxRuntimeException) { catch (e: GdxRuntimeException) {

View File

@@ -284,6 +284,7 @@ abstract class UIItem(var parentUI: UICanvas, val initialX: Int, val initialY: I
} }
object UIItemAccessibilityUtil { 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() { fun playHapticCursorHovered() {
App.playGUIsound(CommonResourcePool.getAs("sound:haptic_bup"), 0.1666666) App.playGUIsound(CommonResourcePool.getAs("sound:haptic_bup"), 0.1666666)
} }