more audio codes

This commit is contained in:
minjaesong
2024-04-14 23:45:34 +09:00
parent 4fba0f70c9
commit 9762f38868
7 changed files with 206 additions and 54 deletions

View File

@@ -0,0 +1,81 @@
package net.torvald.terrarum.modulebasegame.audio.audiobank
import com.badlogic.gdx.utils.Queue
import net.torvald.terrarum.App
import net.torvald.terrarum.INGAME
import net.torvald.terrarum.audio.AudioBank
/**
* Created by minjaesong on 2024-04-12.
*/
class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {}) : AudioBank() {
private data class Msg(val tick: Long, val samplesL: FloatArray, val samplesR: FloatArray, var samplesDispatched: Int = 0) // in many cases, samplesL and samplesR will point to the same object
override val name = "spieluhr"
override val samplingRate = 48000
override val channels = 1
private val getSample = // usage: getSample(noteNum 0..60)
InstrumentLoader.load("spieluhr", "basegame", "audio/effects/notes/spieluhr.ogg", 29)
private val SAMEPLES_PER_TICK = samplingRate / App.TICK_SPEED // should be 800 on default setting
override val totalSizeInSamples = getSample(0).first.size.toLong() // length of lowest-pitch note
private val messageQueue = Queue<Msg>()
private fun findSetBits(num: Long): List<Int> {
val result = mutableListOf<Int>()
for (i in 0 until 61) {
if (num and (1L shl i) != 0L) {
result.add(i)
}
}
return result
}
/**
* Queues the notes such that they are played on the next tick
*/
fun queuePlay(noteBits: Long) {
if (noteBits == 0L) return
val tick = INGAME.WORLD_UPDATE_TIMER + 1
val notes = findSetBits(noteBits)
val buf = FloatArray(getSample(notes.first()).first.size)
// combine all those samples
notes.forEach { note ->
getSample(note).first.forEachIndexed { index, fl ->
buf[index] += fl
}
}
// actually queue it
messageQueue.addLast(Msg(tick, buf, buf))
}
override fun currentPositionInSamples(): Long {
TODO("Not yet implemented")
}
override fun readSamples(bufferL: FloatArray, bufferR: FloatArray): Int {
val tickCount = INGAME.WORLD_UPDATE_TIMER
TODO("Not yet implemented")
}
override fun reset() {
TODO("Not yet implemented")
}
override fun makeCopy(): AudioBank {
TODO("Not yet implemented")
}
override fun dispose() {
TODO("Not yet implemented")
}
}

View File

@@ -0,0 +1,101 @@
package net.torvald.terrarum.modulebasegame.audio.audiobank
import net.torvald.terrarum.CommonResourcePool
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.audio.AudioProcessBuf
import net.torvald.terrarum.audio.audiobank.MusicContainer
import net.torvald.terrarum.ceilToInt
import net.torvald.terrarum.floorToInt
import java.lang.Math.pow
import kotlin.math.roundToInt
/**
* Creates 61-note (C1 to C5) pack of samples from a single file. Tuning system is 12-note Equal Temperament.
*
* Created by minjaesong on 2024-04-14.
*/
object InstrumentLoader {
// 0 is C0
private fun getStretch(noteNum: Int) = pow(2.0, (noteNum - 29) / 12.0)
/**
* Will read the sample and create 61 copies of them. Rendered samples will be stored on the CommonResourcePool
* with the naming rule of `"${idBase}_${noteNumber}"`, with format of Pair<FloatArray, FloatArray>
*
* If `isDualMono` option is set, two values of a pair will point to the same FloatArray.
*
* @param idBase Base ID string
* @param module Which module the path must refer to
* @param path path to the audio
* @param initialNote Initial note of the given sample. Ranged from 0 to 60. C1 is 0, F3 is 29
* @param isDualMono if the input sample is in dual mono
*/
fun load(idBase: String, module: String, path: String, initialNote: Int, isDualMono: Boolean = true): (Int) -> Pair<FloatArray, FloatArray> {
if (initialNote !in 0..60) throw IllegalArgumentException("Initial note too low or high ($initialNote not in range of 0..60)")
val baseResourceName = "inst$$idBase"
if (CommonResourcePool.resourceExists("${baseResourceName}_$initialNote")) return { it ->
CommonResourcePool.getAs<Pair<FloatArray, FloatArray>>("${baseResourceName}_$it")
}
val masterFile = MusicContainer("${idBase}_${initialNote}", ModMgr.getFile(module, path))
val masterSamplesL = FloatArray(masterFile.totalSizeInSamples.toInt())
val masterSamplesR = FloatArray(masterFile.totalSizeInSamples.toInt())
masterFile.readSamples(masterSamplesL, masterSamplesR)
val renderedSamples = Array<Pair<FloatArray?, FloatArray?>>(61) { null to null }
for (j in 0 until 61) {
val i = j - initialNote
val rate = getStretch(i)
val sampleCount = (masterFile.totalSizeInSamples * rate).roundToInt()
val samplesL = FloatArray(sampleCount)
val samplesR = if (isDualMono) samplesL else FloatArray(sampleCount)
renderedSamples[j] = samplesL to samplesR
// do resampling
resample(masterSamplesL, samplesL, rate)
if (!isDualMono)
resample(masterSamplesR, samplesR, rate)
CommonResourcePool.addToLoadingList("${baseResourceName}_$j") {
samplesL to samplesR
}
}
CommonResourcePool.loadAll()
masterFile.dispose()
return { it ->
CommonResourcePool.getAs<Pair<FloatArray, FloatArray>>("${baseResourceName}_$it")
}
}
private val TAPS = 8
private fun resample(input: FloatArray, output: FloatArray, rate: Double) {
for (sampleIdx in 0 until output.size) {
val t = sampleIdx.toDouble() * rate
val leftBound = maxOf(0, (t - TAPS + 1).floorToInt())
val rightBound = minOf(input.size - 1, (t + TAPS).ceilToInt())
var akkuL = 0.0
var weightedSum = 0.0
for (j in leftBound..rightBound) {
val w = AudioProcessBuf.L(t - j.toDouble())
akkuL += input[j] * w
weightedSum += w
}
output[sampleIdx] = (akkuL / weightedSum).toFloat()
}
}
}