mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
test code might shelf later lol
This commit is contained in:
23
.idea/libraries/jetbrains_kotlinx_coroutines_core.xml
generated
Normal file
23
.idea/libraries/jetbrains_kotlinx_coroutines_core.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.coroutines.core" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" />
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlinx-coroutines-core-1.7.3.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlinx-coroutines-core-jvm-1.7.3.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/annotations-23.0.0.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlin-stdlib-common-1.8.20.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlin-stdlib-jdk8-1.8.20.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlin-stdlib-1.8.20.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlin-stdlib-jdk7-1.8.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlinx-coroutines-core-jvm-1.7.3-javadoc.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/annotations-23.0.0-javadoc.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlin-stdlib-common-1.8.20-javadoc.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlin-stdlib-jdk8-1.8.20-javadoc.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlin-stdlib-1.8.20-javadoc.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/lib/kotlin-stdlib-jdk7-1.8.20-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
@@ -920,6 +920,8 @@ public class App implements ApplicationListener {
|
||||
audioManagerThread.interrupt();
|
||||
}
|
||||
|
||||
AudioMixer.INSTANCE.dispose();
|
||||
|
||||
if (currentScreen != null) {
|
||||
currentScreen.hide();
|
||||
currentScreen.dispose();
|
||||
|
||||
@@ -2,15 +2,17 @@ package net.torvald.terrarum.audio
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.backends.lwjgl3.audio.Lwjgl3Audio
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import net.torvald.terrarum.App
|
||||
import net.torvald.terrarum.modulebasegame.MusicContainer
|
||||
import net.torvald.terrarum.tryDispose
|
||||
|
||||
/**
|
||||
* Any audio reference fed into this manager will get lost; you must manually store and dispose of them on your own.
|
||||
*
|
||||
* Created by minjaesong on 2023-11-07.
|
||||
*/
|
||||
object AudioMixer {
|
||||
object AudioMixer: Disposable {
|
||||
const val DEFAULT_FADEOUT_LEN = 2.4
|
||||
|
||||
/** Returns a master volume */
|
||||
@@ -28,8 +30,8 @@ object AudioMixer {
|
||||
|
||||
private val tracks = Array(10) { TerrarumAudioMixerTracks() }
|
||||
|
||||
private val masterTrack = TerrarumAudioMixerTracks().also { master ->
|
||||
tracks.forEach { master.sidechainInputs.add(it to 1.0) }
|
||||
private val masterTrack = TerrarumAudioMixerTracks(true).also { master ->
|
||||
tracks.forEach { master.addSidechainInput(it, 1.0) }
|
||||
}
|
||||
|
||||
private val musicTrack: TerrarumAudioMixerTracks
|
||||
@@ -122,4 +124,9 @@ object AudioMixer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun dispose() {
|
||||
tracks.forEach { it.tryDispose() }
|
||||
masterTrack.tryDispose()
|
||||
}
|
||||
}
|
||||
36
src/net/torvald/terrarum/audio/AudioProcessBuf.kt
Normal file
36
src/net/torvald/terrarum/audio/AudioProcessBuf.kt
Normal file
@@ -0,0 +1,36 @@
|
||||
package net.torvald.terrarum.audio
|
||||
|
||||
import net.torvald.terrarum.serialise.toUint
|
||||
|
||||
/**
|
||||
* Audio is assumed to be 16 bits
|
||||
*
|
||||
* Created by minjaesong on 2023-11-17.
|
||||
*/
|
||||
class AudioProcessBuf(val size: Int) {
|
||||
|
||||
var buf0 = ByteArray(size); private set
|
||||
var buf1 = ByteArray(size); private set
|
||||
|
||||
var fbuf0 = FloatArray(size / 2); private set
|
||||
var fbuf1 = FloatArray(size / 2); private set
|
||||
|
||||
fun shift(): ByteArray {
|
||||
buf0 = buf1
|
||||
buf1 = ByteArray(size)
|
||||
|
||||
fbuf0 = fbuf1
|
||||
fbuf1 = FloatArray(size / 2) {
|
||||
val i16 = (buf1[4*it].toUint() or buf1[4*it+1].toUint().shl(8)).toShort()
|
||||
i16 / 32767f
|
||||
}
|
||||
|
||||
return buf1
|
||||
}
|
||||
|
||||
fun getL0() = FloatArray(size / 4) { fbuf0[2*it] }
|
||||
fun getR0() = FloatArray(size / 4) { fbuf0[2*it+1] }
|
||||
fun getL1() = FloatArray(size / 4) { fbuf1[2*it] }
|
||||
fun getR1() = FloatArray(size / 4) { fbuf1[2*it+1] }
|
||||
|
||||
}
|
||||
255
src/net/torvald/terrarum/audio/OpenALBufferedAudioDevice.kt
Normal file
255
src/net/torvald/terrarum/audio/OpenALBufferedAudioDevice.kt
Normal file
@@ -0,0 +1,255 @@
|
||||
package net.torvald.terrarum.audio
|
||||
|
||||
import com.badlogic.gdx.audio.AudioDevice
|
||||
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
|
||||
import com.badlogic.gdx.math.MathUtils
|
||||
import com.badlogic.gdx.utils.GdxRuntimeException
|
||||
import org.lwjgl.BufferUtils
|
||||
import org.lwjgl.openal.AL10
|
||||
import org.lwjgl.openal.AL11
|
||||
import java.nio.Buffer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.IntBuffer
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2023-01-01.
|
||||
*/
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 See AUTHORS file.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/** @author Nathan Sweet
|
||||
*/
|
||||
class OpenALBufferedAudioDevice(
|
||||
private val audio: OpenALLwjgl3Audio,
|
||||
val rate: Int,
|
||||
isMono: Boolean,
|
||||
val bufferSize: Int,
|
||||
val bufferCount: Int,
|
||||
private val fillBufferCallback: () -> Unit
|
||||
) : AudioDevice {
|
||||
private val channels: Int
|
||||
private var buffers: IntBuffer? = null
|
||||
private var sourceID = -1
|
||||
private val format: Int
|
||||
private var isPlaying = false
|
||||
private var volume = 1f
|
||||
private var renderedSeconds = 0f
|
||||
private val secondsPerBuffer: Float
|
||||
private var bytes: ByteArray? = null
|
||||
private var bytesLength = 2
|
||||
private val tempBuffer: ByteBuffer
|
||||
|
||||
/**
|
||||
* Invoked whenever a buffer is emptied after writing samples
|
||||
*
|
||||
* Preferably you write 2-3 buffers worth of samples at the beginning of the playback
|
||||
*/
|
||||
|
||||
init {
|
||||
channels = if (isMono) 1 else 2
|
||||
format = if (channels > 1) AL10.AL_FORMAT_STEREO16 else AL10.AL_FORMAT_MONO16
|
||||
secondsPerBuffer = bufferSize.toFloat() / bytesPerSample / channels / rate
|
||||
tempBuffer = BufferUtils.createByteBuffer(bufferSize)
|
||||
}
|
||||
|
||||
override fun writeSamples(samples: ShortArray, offset: Int, numSamples: Int) {
|
||||
if (bytes == null || bytes!!.size < numSamples * 2) bytes = ByteArray(numSamples * 2)
|
||||
val end = Math.min(offset + numSamples, samples.size)
|
||||
var i = offset
|
||||
var ii = 0
|
||||
while (i < end) {
|
||||
val sample = samples[i]
|
||||
bytes!![ii++] = (sample.toInt() and 0xFF).toByte()
|
||||
bytes!![ii++] = (sample.toInt() shr 8 and 0xFF).toByte()
|
||||
i++
|
||||
}
|
||||
bytesLength = ii
|
||||
writeSamples(bytes!!, 0, numSamples * 2)
|
||||
}
|
||||
|
||||
override fun writeSamples(samples: FloatArray, offset: Int, numSamples: Int) {
|
||||
if (bytes == null || bytes!!.size < numSamples * 2) bytes = ByteArray(numSamples * 2)
|
||||
val end = Math.min(offset + numSamples, samples.size)
|
||||
var i = offset
|
||||
var ii = 0
|
||||
while (i < end) {
|
||||
var floatSample = samples[i]
|
||||
floatSample = MathUtils.clamp(floatSample, -1f, 1f)
|
||||
val intSample = (floatSample * 32767).toInt()
|
||||
bytes!![ii++] = (intSample and 0xFF).toByte()
|
||||
bytes!![ii++] = (intSample shr 8 and 0xFF).toByte()
|
||||
i++
|
||||
}
|
||||
bytesLength = ii
|
||||
writeSamples(bytes!!, 0, numSamples * 2)
|
||||
}
|
||||
|
||||
private fun audioObtainSource(isMusic: Boolean): Int {
|
||||
val obtainSourceMethod = OpenALLwjgl3Audio::class.java.getDeclaredMethod("obtainSource", java.lang.Boolean.TYPE)
|
||||
obtainSourceMethod.isAccessible = true
|
||||
return obtainSourceMethod.invoke(audio, isMusic) as Int
|
||||
}
|
||||
private fun audioFreeSource(sourceID: Int) {
|
||||
val freeSourceMethod = OpenALLwjgl3Audio::class.java.getDeclaredMethod("freeSource", java.lang.Integer.TYPE)
|
||||
freeSourceMethod.isAccessible = true
|
||||
freeSourceMethod.invoke(audio, sourceID)
|
||||
}
|
||||
|
||||
private val alErrors = hashMapOf(
|
||||
AL10.AL_INVALID_NAME to "AL_INVALID_NAME",
|
||||
AL10.AL_INVALID_ENUM to "AL_INVALID_ENUM",
|
||||
AL10.AL_INVALID_VALUE to "AL_INVALID_VALUE",
|
||||
AL10.AL_INVALID_OPERATION to "AL_INVALID_OPERATION",
|
||||
AL10.AL_OUT_OF_MEMORY to "AL_OUT_OF_MEMORY"
|
||||
)
|
||||
|
||||
fun writeSamples(data: ByteArray, offset: Int, length: Int) {
|
||||
var offset = offset
|
||||
var length = length
|
||||
require(length >= 0) { "length cannot be < 0." }
|
||||
if (sourceID == -1) {
|
||||
sourceID = audioObtainSource(true)
|
||||
if (sourceID == -1) return
|
||||
if (buffers == null) {
|
||||
buffers = BufferUtils.createIntBuffer(bufferCount)
|
||||
AL10.alGetError()
|
||||
AL10.alGenBuffers(buffers)
|
||||
AL10.alGetError().let {
|
||||
if (it != AL10.AL_NO_ERROR) throw GdxRuntimeException("Unabe to allocate audio buffers: ${alErrors[it]}")
|
||||
}
|
||||
}
|
||||
AL10.alSourcei(sourceID, AL10.AL_LOOPING, AL10.AL_FALSE)
|
||||
AL10.alSourcef(sourceID, AL10.AL_GAIN, volume)
|
||||
// Fill initial buffers.
|
||||
var queuedBuffers = 0
|
||||
for (i in 0 until bufferCount) {
|
||||
val bufferID = buffers!![i]
|
||||
val written = Math.min(bufferSize, length)
|
||||
(tempBuffer as Buffer).clear()
|
||||
(tempBuffer.put(data, offset, written) as Buffer).flip()
|
||||
AL10.alBufferData(bufferID, format, tempBuffer, rate)
|
||||
AL10.alSourceQueueBuffers(sourceID, bufferID)
|
||||
length -= written
|
||||
offset += written
|
||||
queuedBuffers++
|
||||
}
|
||||
// Queue rest of buffers, empty.
|
||||
(tempBuffer as Buffer).clear().flip()
|
||||
for (i in queuedBuffers until bufferCount) {
|
||||
val bufferID = buffers!![i]
|
||||
AL10.alBufferData(bufferID, format, tempBuffer, rate)
|
||||
AL10.alSourceQueueBuffers(sourceID, bufferID)
|
||||
}
|
||||
AL10.alSourcePlay(sourceID)
|
||||
isPlaying = true
|
||||
}
|
||||
while (length > 0) {
|
||||
val written = fillBuffer(data, offset, length)
|
||||
length -= written
|
||||
offset += written
|
||||
}
|
||||
}
|
||||
|
||||
/** Blocks until some of the data could be buffered. */
|
||||
private fun fillBuffer(data: ByteArray, offset: Int, length: Int): Int {
|
||||
val written = Math.min(bufferSize, length)
|
||||
outer@ while (true) {
|
||||
var buffers = AL10.alGetSourcei(sourceID, AL10.AL_BUFFERS_PROCESSED)
|
||||
while (buffers-- > 0) {
|
||||
val bufferID = AL10.alSourceUnqueueBuffers(sourceID)
|
||||
if (bufferID == AL10.AL_INVALID_VALUE) break
|
||||
renderedSeconds += secondsPerBuffer
|
||||
(tempBuffer as Buffer).clear()
|
||||
(tempBuffer.put(data, offset, written) as Buffer).flip()
|
||||
AL10.alBufferData(bufferID, format, tempBuffer, rate)
|
||||
AL10.alSourceQueueBuffers(sourceID, bufferID)
|
||||
break@outer
|
||||
}
|
||||
// Wait for buffer to be free.
|
||||
try {
|
||||
Thread.sleep((1000 * secondsPerBuffer).toLong())
|
||||
fillBufferCallback()
|
||||
}
|
||||
catch (ignored: InterruptedException) {
|
||||
}
|
||||
}
|
||||
|
||||
// A buffer underflow will cause the source to stop.
|
||||
if (!isPlaying || AL10.alGetSourcei(sourceID, AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING) {
|
||||
AL10.alSourcePlay(sourceID)
|
||||
isPlaying = true
|
||||
}
|
||||
return written
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (sourceID == -1) return
|
||||
audioFreeSource(sourceID)
|
||||
sourceID = -1
|
||||
renderedSeconds = 0f
|
||||
isPlaying = false
|
||||
}
|
||||
|
||||
fun isPlaying(): Boolean {
|
||||
return if (sourceID == -1) false else isPlaying
|
||||
}
|
||||
|
||||
override fun setVolume(volume: Float) {
|
||||
this.volume = volume
|
||||
if (sourceID != -1) AL10.alSourcef(sourceID, AL10.AL_GAIN, volume)
|
||||
}
|
||||
|
||||
var position: Float
|
||||
get() = if (sourceID == -1) 0f else renderedSeconds + AL10.alGetSourcef(sourceID, AL11.AL_SEC_OFFSET)
|
||||
set(position) {
|
||||
renderedSeconds = position
|
||||
}
|
||||
|
||||
fun getChannels(): Int {
|
||||
return if (format == AL10.AL_FORMAT_STEREO16) 2 else 1
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
if (buffers == null) return
|
||||
if (sourceID != -1) {
|
||||
audioFreeSource(sourceID)
|
||||
sourceID = -1
|
||||
}
|
||||
AL10.alDeleteBuffers(buffers)
|
||||
buffers = null
|
||||
}
|
||||
|
||||
override fun isMono(): Boolean {
|
||||
return channels == 1
|
||||
}
|
||||
|
||||
override fun getLatency(): Int {
|
||||
return (secondsPerBuffer * bufferCount * 1000).toInt()
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
// A buffer underflow will cause the source to stop.
|
||||
}
|
||||
|
||||
override fun resume() {
|
||||
// Automatically resumes when samples are written
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val bytesPerSample = 2
|
||||
private val ui8toI16Hi = ByteArray(256) { (128 + it).toByte() }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,41 @@
|
||||
package net.torvald.terrarum.audio
|
||||
|
||||
import com.jme3.math.FastMath
|
||||
|
||||
interface TerrarumAudioFilters {
|
||||
fun thru(inbufL: FloatArray, inbufR: FloatArray, outbufL: FloatArray, outbufR: FloatArray)
|
||||
fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>)
|
||||
}
|
||||
|
||||
object NullFilter: TerrarumAudioFilters {
|
||||
override fun thru(inbufL: FloatArray, inbufR: FloatArray, outbufL: FloatArray, outbufR: FloatArray) {
|
||||
System.arraycopy(inbufL, 0, outbufL, 0, inbufL.size)
|
||||
System.arraycopy(inbufR, 0, outbufR, 0, inbufL.size)
|
||||
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) {
|
||||
outbuf1.forEachIndexed { index, outTrack ->
|
||||
System.arraycopy(inbuf1[index], 0, outTrack, 0, outTrack.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Lowpass(cutoff: Int, rate: Int): TerrarumAudioFilters {
|
||||
|
||||
val alpha: Float
|
||||
init {
|
||||
val RC: Float = 1f / (cutoff.toFloat() * FastMath.TWO_PI)
|
||||
val dt: Float = 1f / rate
|
||||
alpha = dt / (RC + dt)
|
||||
}
|
||||
|
||||
override fun thru(inbuf0: List<FloatArray>, inbuf1: List<FloatArray>, outbuf0: List<FloatArray>, outbuf1: List<FloatArray>) {
|
||||
for (ch in outbuf1.indices) {
|
||||
val out = outbuf1[ch]
|
||||
val inn = inbuf1[ch]
|
||||
// System.arraycopy(inn, 0, out, 0, out.size)
|
||||
|
||||
out[0] = outbuf0[ch].last()
|
||||
|
||||
for (i in 1 until outbuf1[ch].size) {
|
||||
out[i] = out[i-1] + (alpha * (inn[i] - out[i-1]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,25 @@
|
||||
package net.torvald.terrarum.audio
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import kotlinx.coroutines.*
|
||||
import net.torvald.reflection.forceInvoke
|
||||
import net.torvald.terrarum.getHashStr
|
||||
import net.torvald.terrarum.modulebasegame.MusicContainer
|
||||
import net.torvald.terrarum.utils.PasswordBase32
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.pow
|
||||
|
||||
typealias TrackVolume = Double
|
||||
|
||||
class TerrarumAudioMixerTracks {
|
||||
class TerrarumAudioMixerTracks(val isMaster: Boolean = false): Disposable {
|
||||
|
||||
companion object {
|
||||
const val SAMPLING_RATE = 48000
|
||||
}
|
||||
|
||||
val hash = getHashStr()
|
||||
|
||||
@@ -28,8 +39,50 @@ class TerrarumAudioMixerTracks {
|
||||
get() = fullscaleToDecibels(volume)
|
||||
set(value) { volume = decibelsToFullscale(value) }
|
||||
|
||||
val filters = arrayListOf<TerrarumAudioFilters>()
|
||||
val sidechainInputs = arrayListOf<Pair<TerrarumAudioMixerTracks, TrackVolume>>()
|
||||
val filters = Array(4) { NullFilter }
|
||||
|
||||
private val sidechainInputs = Array<Pair<TerrarumAudioMixerTracks, TrackVolume>?>(16) { null }
|
||||
internal fun getSidechains(): List<TerrarumAudioMixerTracks?> = sidechainInputs.map { it?.first }
|
||||
fun addSidechainInput(input: TerrarumAudioMixerTracks, inputVolume: TrackVolume) {
|
||||
if (input.isMaster)
|
||||
throw IllegalArgumentException("Cannot add master track as a sidechain")
|
||||
|
||||
if (sidechainInputs.map { it?.first }.any { it?.hash == input.hash })
|
||||
throw IllegalArgumentException("The track '${input.hash}' already exists")
|
||||
|
||||
if (getSidechains().any { mySidechain ->
|
||||
val theirSidechains = mySidechain?.getSidechains()
|
||||
theirSidechains?.any { theirSidechain -> theirSidechain?.hash == this.hash } == true
|
||||
})
|
||||
throw IllegalArgumentException("The track '${input.hash}' contains current track (${this.hash}) as its sidechain")
|
||||
|
||||
val emptySpot = sidechainInputs.indexOf(null)
|
||||
if (emptySpot != -1) {
|
||||
sidechainInputs[emptySpot] = (input to inputVolume)
|
||||
}
|
||||
else {
|
||||
throw IllegalStateException("Sidechain is full (${sidechainInputs.size})!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// in bytes
|
||||
private val deviceBufferSize = Gdx.audio.javaClass.getDeclaredField("deviceBufferSize").let {
|
||||
it.isAccessible = true
|
||||
it.get(Gdx.audio) as Int
|
||||
}
|
||||
private val deviceBufferCount = Gdx.audio.javaClass.getDeclaredField("deviceBufferCount").let {
|
||||
it.isAccessible = true
|
||||
it.get(Gdx.audio) as Int
|
||||
}
|
||||
private val adev = OpenALBufferedAudioDevice(
|
||||
Gdx.audio as OpenALLwjgl3Audio,
|
||||
SAMPLING_RATE,
|
||||
false,
|
||||
deviceBufferSize,
|
||||
deviceBufferCount
|
||||
) {}
|
||||
|
||||
|
||||
/**
|
||||
* assign nextTrack to currentTrack, then assign nextNext to nextTrack.
|
||||
@@ -40,15 +93,77 @@ class TerrarumAudioMixerTracks {
|
||||
nextTrack = nextNext
|
||||
}
|
||||
|
||||
|
||||
private var streamPlaying = false
|
||||
fun play() {
|
||||
currentTrack?.gdxMusic?.play()
|
||||
streamPlaying = true
|
||||
// currentTrack?.gdxMusic?.play()
|
||||
}
|
||||
|
||||
val isPlaying: Boolean?
|
||||
get() = currentTrack?.gdxMusic?.isPlaying
|
||||
|
||||
override fun dispose() {
|
||||
adev.dispose()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = this.hash == (other as TerrarumAudioMixerTracks).hash
|
||||
|
||||
|
||||
// 1st ring of the hell: the THREADING HELL //
|
||||
|
||||
private val processJob: Job
|
||||
private var processContinuation: Continuation<Unit>? = null
|
||||
|
||||
|
||||
|
||||
private val streamBuf = AudioProcessBuf(deviceBufferSize)
|
||||
private val sideChainBufs = Array(sidechainInputs.size) { AudioProcessBuf(deviceBufferSize) }
|
||||
private val outBufL0 = FloatArray(deviceBufferSize / 4)
|
||||
private val outBufR0 = FloatArray(deviceBufferSize / 4)
|
||||
private val outBufL1 = FloatArray(deviceBufferSize / 4)
|
||||
private val outBufR1 = FloatArray(deviceBufferSize / 4)
|
||||
|
||||
init {
|
||||
processJob = GlobalScope.launch { // calling 'launch' literally launches the coroutine right awya
|
||||
// fetch deviceBufferSize amount of sample from the disk
|
||||
if (streamPlaying) {
|
||||
currentTrack?.gdxMusic?.forceInvoke<Unit>("read", arrayOf(streamBuf.shift()))
|
||||
}
|
||||
|
||||
// also fetch samples from sidechainInputs
|
||||
// TODO
|
||||
|
||||
// combine all the inputs
|
||||
// TODO this code just uses streamBuf
|
||||
|
||||
val samplesL0 = streamBuf.getL0()
|
||||
val samplesR0 = streamBuf.getR0()
|
||||
val samplesL1 = streamBuf.getL1()
|
||||
val samplesR1 = streamBuf.getR1()
|
||||
|
||||
// run the input through the stack of filters
|
||||
// TODO skipped lol
|
||||
|
||||
// final writeout
|
||||
System.arraycopy(samplesL0, 0, outBufL0, 0, outBufL0.size)
|
||||
System.arraycopy(samplesR0, 0, outBufR0, 0, outBufR0.size)
|
||||
System.arraycopy(samplesL1, 0, outBufL1, 0, outBufL1.size)
|
||||
System.arraycopy(samplesR1, 0, outBufR1, 0, outBufR1.size)
|
||||
|
||||
// by this time, the output buffer is filled with processed results, pause the execution
|
||||
if (!isMaster) {
|
||||
suspendCoroutine<Unit> {
|
||||
processContinuation = it
|
||||
}
|
||||
}
|
||||
else {
|
||||
getSidechains().forEach {
|
||||
it?.processContinuation?.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fullscaleToDecibels(fs: Double) = 10.0 * log10(fs)
|
||||
|
||||
Reference in New Issue
Block a user