mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-16 21:44:05 +09:00
more audio codes
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user