mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 03:24:06 +09:00
seemingly working but thread is not gracefully dying
This commit is contained in:
@@ -32,7 +32,7 @@ object AudioMixer: Disposable {
|
|||||||
|
|
||||||
private val masterTrack = TerrarumAudioMixerTrack("Master", true).also { master ->
|
private val masterTrack = TerrarumAudioMixerTrack("Master", true).also { master ->
|
||||||
tracks.forEach { master.addSidechainInput(it, 1.0) }
|
tracks.forEach { master.addSidechainInput(it, 1.0) }
|
||||||
// master.filters[0] = Lowpass(240, TerrarumAudioMixerTrack.SAMPLING_RATE)
|
master.filters[0] = Lowpass(240, TerrarumAudioMixerTrack.SAMPLING_RATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val musicTrack: TerrarumAudioMixerTrack
|
private val musicTrack: TerrarumAudioMixerTrack
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE
|
import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE
|
||||||
|
|
||||||
@@ -7,21 +8,19 @@ import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RAT
|
|||||||
* Created by minjaesong on 2023-11-17.
|
* Created by minjaesong on 2023-11-17.
|
||||||
*/
|
*/
|
||||||
class MixerTrackProcessor(val bufferSize: Int, val track: TerrarumAudioMixerTrack): Runnable {
|
class MixerTrackProcessor(val bufferSize: Int, val track: TerrarumAudioMixerTrack): Runnable {
|
||||||
@Volatile
|
@Volatile private var running = true
|
||||||
private var running = true
|
@Volatile private var paused = false
|
||||||
|
|
||||||
@Volatile
|
|
||||||
private var paused = false
|
|
||||||
private val pauseLock = java.lang.Object()
|
private val pauseLock = java.lang.Object()
|
||||||
|
|
||||||
|
|
||||||
|
private val emptyBuf = FloatArray(bufferSize / 4)
|
||||||
|
|
||||||
|
|
||||||
internal val streamBuf = AudioProcessBuf(bufferSize)
|
internal val streamBuf = AudioProcessBuf(bufferSize)
|
||||||
internal val sideChainBufs = Array(track.sidechainInputs.size) { AudioProcessBuf(bufferSize) }
|
internal val sideChainBufs = Array(track.sidechainInputs.size) { AudioProcessBuf(bufferSize) }
|
||||||
|
|
||||||
private var fout0 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
private var fout0 = listOf(emptyBuf, emptyBuf)
|
||||||
private var fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
private var fout1 = listOf(emptyBuf, emptyBuf)
|
||||||
|
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
@@ -69,17 +68,49 @@ class MixerTrackProcessor(val bufferSize: Int, val track: TerrarumAudioMixerTrac
|
|||||||
// TODO this code just uses streamBuf
|
// TODO this code just uses streamBuf
|
||||||
|
|
||||||
|
|
||||||
var samplesL0: FloatArray
|
var samplesL0: FloatArray? = null
|
||||||
var samplesR0: FloatArray
|
var samplesR0: FloatArray? = null
|
||||||
var samplesL1: FloatArray
|
var samplesL1: FloatArray? = null
|
||||||
var samplesR1: FloatArray
|
var samplesR1: FloatArray? = null
|
||||||
|
|
||||||
|
var bufEmpty = false
|
||||||
|
|
||||||
if (track.isMaster) {
|
if (track.isMaster) {
|
||||||
// TEST CODE must combine all the inputs
|
// TEST CODE must combine all the inputs
|
||||||
samplesL0 = track.sidechainInputs[0]!!.first.processor.fout0[0]
|
track.sidechainInputs[0]?.let {
|
||||||
samplesR0 = track.sidechainInputs[0]!!.first.processor.fout0[1]
|
samplesL0 = it.first.processor.fout0[0]
|
||||||
samplesL1 = track.sidechainInputs[0]!!.first.processor.fout1[0]
|
samplesR0 = it.first.processor.fout0[1]
|
||||||
samplesR1 = track.sidechainInputs[0]!!.first.processor.fout1[1]
|
samplesL1 = it.first.processor.fout1[0]
|
||||||
|
samplesR1 = it.first.processor.fout1[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*track.sidechainInputs[0].let {
|
||||||
|
if (it != null) {
|
||||||
|
val f0 = it.first.pcmQueue.removeFirstOrElse {
|
||||||
|
bufEmpty = true
|
||||||
|
listOf(emptyBuf, emptyBuf)
|
||||||
|
}
|
||||||
|
samplesL0 = f0[0]
|
||||||
|
samplesR0 = f0[1]
|
||||||
|
|
||||||
|
val f1 = it.first.pcmQueue.removeFirstOrElse {
|
||||||
|
bufEmpty = true
|
||||||
|
listOf(emptyBuf, emptyBuf)
|
||||||
|
}
|
||||||
|
samplesL1 = f1[0]
|
||||||
|
samplesR1 = f1[1]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
samplesL0 = emptyBuf
|
||||||
|
samplesR0 = emptyBuf
|
||||||
|
samplesL1 = emptyBuf
|
||||||
|
samplesR1 = emptyBuf
|
||||||
|
|
||||||
|
bufEmpty = true
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
samplesL0 = streamBuf.getL0(track.volume)
|
samplesL0 = streamBuf.getL0(track.volume)
|
||||||
@@ -89,25 +120,27 @@ class MixerTrackProcessor(val bufferSize: Int, val track: TerrarumAudioMixerTrac
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// run the input through the stack of filters
|
if (samplesL0 != null && samplesL1 != null && samplesR0 != null && samplesR1 != null) {
|
||||||
val filterStack = track.filters.filter { !it.bypass && it !is NullFilter }
|
// run the input through the stack of filters
|
||||||
|
val filterStack = track.filters.filter { !it.bypass && it !is NullFilter }
|
||||||
|
|
||||||
if (filterStack.isEmpty()) {
|
if (filterStack.isEmpty()) {
|
||||||
fout1 = listOf(samplesL1, samplesR1)
|
fout1 = listOf(samplesL1!!, samplesR1!!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var fin0 = listOf(samplesL0, samplesR0)
|
var fin0 = listOf(samplesL0!!, samplesR0!!)
|
||||||
var fin1 = listOf(samplesL1, samplesR1)
|
var fin1 = listOf(samplesL1!!, samplesR1!!)
|
||||||
fout0 = fout1
|
fout0 = fout1
|
||||||
fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
||||||
|
|
||||||
filterStack.forEachIndexed { index, it ->
|
filterStack.forEachIndexed { index, it ->
|
||||||
it(fin0, fin1, fout0, fout1)
|
it(fin0, fin1, fout0, fout1)
|
||||||
fin0 = fout0
|
fin0 = fout0
|
||||||
fin1 = fout1
|
fin1 = fout1
|
||||||
if (index < filterStack.lastIndex) {
|
if (index < filterStack.lastIndex) {
|
||||||
fout0 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
fout0 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
||||||
fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
fout1 = listOf(FloatArray(bufferSize / 4), FloatArray(bufferSize / 4))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,11 +151,21 @@ class MixerTrackProcessor(val bufferSize: Int, val track: TerrarumAudioMixerTrac
|
|||||||
this.pause()
|
this.pause()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
track.adev!!.setVolume(AudioMixer.masterVolume.toFloat())
|
if (samplesL0 != null && samplesL1 != null && samplesR0 != null && samplesR1 != null) {
|
||||||
val samples = interleave(fout1[0], fout1[1])
|
|
||||||
track.adev!!.writeSamples(samples, 0, samples.size)
|
|
||||||
Thread.sleep(1)
|
|
||||||
|
|
||||||
|
// spin until queue is sufficiently empty
|
||||||
|
while (track.pcmQueue.size > 3) {
|
||||||
|
Thread.sleep(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
println("[AudioAdapter ${track.name}] Pushing to queue (queue size: ${track.pcmQueue.size})")
|
||||||
|
track.pcmQueue.addLast(fout1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// spin
|
||||||
|
Thread.sleep(12)
|
||||||
|
|
||||||
|
// wake sidechain processors
|
||||||
track.getSidechains().forEach {
|
track.getSidechains().forEach {
|
||||||
it?.processor?.resume()
|
it?.processor?.resume()
|
||||||
}
|
}
|
||||||
@@ -130,6 +173,8 @@ class MixerTrackProcessor(val bufferSize: Int, val track: TerrarumAudioMixerTrac
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println("[AudioAdapter ${track.name}] MixerTrackProcessor EXIT")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun interleave(f1: FloatArray, f2: FloatArray) = FloatArray(f1.size + f2.size) {
|
private fun interleave(f1: FloatArray, f2: FloatArray) = FloatArray(f1.size + f2.size) {
|
||||||
@@ -155,4 +200,62 @@ class MixerTrackProcessor(val bufferSize: Int, val track: TerrarumAudioMixerTrac
|
|||||||
pauseLock.notifyAll() // Unblocks thread
|
pauseLock.notifyAll() // Unblocks thread
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <T> Queue<T>.removeFirstOrElse(function: () -> T): T {
|
||||||
|
return if (this.isEmpty) {
|
||||||
|
this.removeFirst()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
function()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FeedSamplesToAdev(val track: TerrarumAudioMixerTrack) : Runnable {
|
||||||
|
init {
|
||||||
|
if (!track.isMaster) throw IllegalArgumentException("Track is not master")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printdbg(msg: Any) {
|
||||||
|
if (true) println("[AudioAdapter ${track.name}] $msg")
|
||||||
|
}
|
||||||
|
@Volatile private var exit = false
|
||||||
|
override fun run() {
|
||||||
|
while (!exit) {
|
||||||
|
|
||||||
|
val writeQueue = track.pcmQueue
|
||||||
|
|
||||||
|
if (writeQueue.notEmpty()) {
|
||||||
|
|
||||||
|
printdbg("Taking samples from queue (queue size: ${writeQueue.size})")
|
||||||
|
|
||||||
|
val samples = writeQueue.removeFirst()
|
||||||
|
// playhead.position = writeQueue.size
|
||||||
|
|
||||||
|
// printdbg("P${playhead.index+1} Vol ${playhead.masterVolume}; LpP ${playhead.pcmUploadLength}; start playback...")
|
||||||
|
// printdbg(""+(0..42).joinToString { String.format("%.2f", samples[it]) })
|
||||||
|
|
||||||
|
track.adev!!.writeSamples(samples)
|
||||||
|
|
||||||
|
// printdbg("P${playhead.index+1} go back to spinning")
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (writeQueue.isEmpty) {
|
||||||
|
// printdbg("!! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED !! QUEUE EXHAUSTED ")
|
||||||
|
|
||||||
|
// TODO: wait for 1-2 seconds then finally stop the device
|
||||||
|
// playhead.audioDevice.stop()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Thread.sleep(1)
|
||||||
|
}
|
||||||
|
printdbg("FeedSamplesToAdev EXIT")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
exit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -96,6 +96,20 @@ class OpenALBufferedAudioDevice(
|
|||||||
writeSamples(bytes!!, 0, numSamples * 2)
|
writeSamples(bytes!!, 0, numSamples * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun interleave(f1: FloatArray, f2: FloatArray) = FloatArray(f1.size + f2.size) {
|
||||||
|
if (it % 2 == 0) f1[it / 2] else f2[it / 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param samples multitrack
|
||||||
|
* @param numSamples number of samples per channel
|
||||||
|
*/
|
||||||
|
fun writeSamples(samples: List<FloatArray>) {
|
||||||
|
interleave(samples[0], samples[1]).let {
|
||||||
|
writeSamples(it, 0, it.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun audioObtainSource(isMusic: Boolean): Int {
|
private fun audioObtainSource(isMusic: Boolean): Int {
|
||||||
val obtainSourceMethod = OpenALLwjgl3Audio::class.java.getDeclaredMethod("obtainSource", java.lang.Boolean.TYPE)
|
val obtainSourceMethod = OpenALLwjgl3Audio::class.java.getDeclaredMethod("obtainSource", java.lang.Boolean.TYPE)
|
||||||
obtainSourceMethod.isAccessible = true
|
obtainSourceMethod.isAccessible = true
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package net.torvald.terrarum.audio
|
|||||||
import com.badlogic.gdx.Gdx
|
import com.badlogic.gdx.Gdx
|
||||||
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
|
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
|
||||||
import com.badlogic.gdx.utils.Disposable
|
import com.badlogic.gdx.utils.Disposable
|
||||||
|
import com.badlogic.gdx.utils.Queue
|
||||||
import net.torvald.reflection.forceInvoke
|
import net.torvald.reflection.forceInvoke
|
||||||
import net.torvald.terrarum.getHashStr
|
import net.torvald.terrarum.getHashStr
|
||||||
import net.torvald.terrarum.modulebasegame.MusicContainer
|
import net.torvald.terrarum.modulebasegame.MusicContainer
|
||||||
@@ -104,6 +105,10 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false):
|
|||||||
get() = currentTrack?.gdxMusic?.isPlaying
|
get() = currentTrack?.gdxMusic?.isPlaying
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
|
if (isMaster) {
|
||||||
|
queueDispatcher.stop()
|
||||||
|
queueDispatcherThread.join()
|
||||||
|
}
|
||||||
processor.stop()
|
processor.stop()
|
||||||
processorThread.join()
|
processorThread.join()
|
||||||
adev?.dispose()
|
adev?.dispose()
|
||||||
@@ -114,15 +119,29 @@ class TerrarumAudioMixerTrack(val name: String, val isMaster: Boolean = false):
|
|||||||
|
|
||||||
// 1st ring of the hell: the THREADING HELL //
|
// 1st ring of the hell: the THREADING HELL //
|
||||||
|
|
||||||
internal var processor = MixerTrackProcessor(4096, this)
|
val BUFFER_SIZE = 4096
|
||||||
|
|
||||||
|
internal var processor = MixerTrackProcessor(BUFFER_SIZE, this)
|
||||||
private val processorThread = Thread(processor).also {
|
private val processorThread = Thread(processor).also {
|
||||||
it.start()
|
it.start()
|
||||||
}
|
}
|
||||||
|
internal var pcmQueue = Queue<List<FloatArray>>()
|
||||||
|
private lateinit var queueDispatcher: FeedSamplesToAdev
|
||||||
|
private lateinit var queueDispatcherThread: Thread
|
||||||
|
|
||||||
private fun interleave(f1: FloatArray, f2: FloatArray) = FloatArray(f1.size + f2.size) {
|
init {
|
||||||
if (it % 2 == 0) f1[it / 2] else f2[it / 2]
|
pcmQueue.addLast(listOf(FloatArray(BUFFER_SIZE / 4), FloatArray(BUFFER_SIZE / 4)))
|
||||||
|
pcmQueue.addLast(listOf(FloatArray(BUFFER_SIZE / 4), FloatArray(BUFFER_SIZE / 4)))
|
||||||
|
|
||||||
|
if (isMaster) {
|
||||||
|
queueDispatcher = FeedSamplesToAdev(this)
|
||||||
|
queueDispatcherThread = Thread(queueDispatcher).also {
|
||||||
|
it.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fullscaleToDecibels(fs: Double) = 10.0 * log10(fs)
|
fun fullscaleToDecibels(fs: Double) = 20.0 * log10(fs)
|
||||||
fun decibelsToFullscale(db: Double) = 10.0.pow(db / 10.0)
|
fun decibelsToFullscale(db: Double) = 10.0.pow(db / 20.0)
|
||||||
Reference in New Issue
Block a user