musicbox wip

This commit is contained in:
minjaesong
2024-04-15 20:57:27 +09:00
parent 96954983f0
commit c19184a55f
9 changed files with 220 additions and 34 deletions

View File

@@ -52,6 +52,7 @@ id;classname;tags
51;net.torvald.terrarum.modulebasegame.gameitems.ItemElectricWorkbench;FIXTURE,CRAFTING
52;net.torvald.terrarum.modulebasegame.gameitems.ItemLogicSignalSevenSeg;FIXTURE,SIGNAL
53;net.torvald.terrarum.modulebasegame.gameitems.ItemEngravingWorkbench;FIXTURE,CRAFTING
54;net.torvald.terrarum.modulebasegame.gameitems.ItemMechanicalTines;FIXTURE,MUSIC,SIGNAL
# ingots
112;net.torvald.terrarum.modulebasegame.gameitems.IngotCopper;INGOT
1 id classname tags
52 52 net.torvald.terrarum.modulebasegame.gameitems.ItemLogicSignalSevenSeg FIXTURE,SIGNAL
53 53 net.torvald.terrarum.modulebasegame.gameitems.ItemEngravingWorkbench FIXTURE,CRAFTING
54 # ingots 54 net.torvald.terrarum.modulebasegame.gameitems.ItemMechanicalTines FIXTURE,MUSIC,SIGNAL
55 # ingots
56 112 net.torvald.terrarum.modulebasegame.gameitems.IngotCopper INGOT
57 113 net.torvald.terrarum.modulebasegame.gameitems.IngotIron INGOT
58 114 net.torvald.terrarum.modulebasegame.gameitems.ItemCoalCoke COMBUSTIBLE

Binary file not shown.

View File

@@ -7,6 +7,8 @@ import com.badlogic.gdx.utils.Disposable
*/
abstract class AudioBank : Disposable {
open val notCopyable: Boolean = false
protected val hash = System.nanoTime()
abstract fun makeCopy(): AudioBank

View File

@@ -8,8 +8,10 @@ class MusicCache(val trackName: String) : Disposable {
private val cache = HashMap<String, AudioBank>()
fun getOrPut(music: AudioBank?): AudioBank? {
if (music != null)
if (music != null && !music.notCopyable)
return cache.getOrPut(music.name) { music.makeCopy() }
else if (music != null)
return music
return null
}

View File

@@ -1,6 +1,7 @@
package net.torvald.terrarum.modulebasegame.audio.audiobank
import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.INGAME
import net.torvald.terrarum.audio.AudioBank
@@ -9,14 +10,21 @@ import net.torvald.terrarum.audio.AudioBank
*/
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 notCopyable = true
// TODO don't store samples (1MB each!), store numbers instead and synthesize on readSamples()
internal 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 fun toString(): String {
return "Msg(tick=$tick, samplesDispatched=$samplesDispatched)"
}
}
override val name = "spieluhr"
override val samplingRate = 48000 // 122880 // use 122880 to make each tick is 2048 samples
override val channels = 1
private val getSample = // usage: getSample(noteNum 0..60)
InstrumentLoader.load("spieluhr", "basegame", "audio/effects/notes/spieluhr.ogg", 29)
InstrumentLoader.load("spieluhr", "basegame", "audio/effects/notes/spieluhr.ogg", 41)
private val SAMPLES_PER_TICK = samplingRate / App.TICK_SPEED // should be 800 on default setting
@@ -34,16 +42,26 @@ class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {})
return result
}
init {
printdbg(this, "Note 0 length: ${getSample(0).first.size}")
printdbg(this, "Note 12 length: ${getSample(12).first.size}")
printdbg(this, "Note 24 length: ${getSample(24).first.size}")
printdbg(this, "Note 48 length: ${getSample(48).first.size}")
}
/**
* Queues the notes such that they are played on the next tick
*/
override fun sendMessage(noteBits: Long) {
queue(INGAME.WORLD_UPDATE_TIMER + 1, noteBits)
}
private fun queue(tick: Long, 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)
val buf = FloatArray(getSample(0).first.size)
// combine all those samples
notes.forEach { note ->
@@ -53,7 +71,8 @@ class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {})
}
// actually queue it
messageQueue.add(Msg(tick, buf, buf))
val msg = Msg(tick, buf, buf)
messageQueue.add(messageQueue.size, msg)
}
override fun currentPositionInSamples() = 0L
@@ -62,6 +81,9 @@ class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {})
val tickCount = INGAME.WORLD_UPDATE_TIMER
val bufferSize = bufferL.size
bufferL.fill(0f)
bufferR.fill(0f)
// only copy over the past and current messages
messageQueue.filter { it.tick <= tickCount }.forEach {
// copy over the samples
@@ -73,44 +95,33 @@ class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {})
it.samplesDispatched += bufferSize
}
// dequeue the finished messages
val messagesToKill = ArrayList<Msg>(messageQueue.filter { it.samplesDispatched >= it.samplesL.size })
if (messagesToKill.isNotEmpty()) messagesToKill.forEach {
messageQueue.remove(it)
var rc = 0
while (rc < messageQueue.size) {
val it = messageQueue[rc]
if (it.samplesDispatched >= it.samplesL.size) {
messageQueue.removeAt(rc)
rc -= 1
}
rc += 1
}
printdbg(this, "Queuelen: ${messageQueue.size}")
return bufferSize
}
override fun reset() {
TODO("Not yet implemented")
messageQueue.clear()
}
override fun makeCopy(): AudioBank {
TODO("Not yet implemented")
throw UnsupportedOperationException()
}
override fun dispose() {
TODO("Not yet implemented")
}
private fun prel(n1: Int, n2: Int, n3: Int, n4: Int, n5: Int): List<Int> {
return listOf(
n1, n2, n3, n4, n5, n3, n4, n5,
n1, n2, n3, n4, n5, n3, n4, n5
)
}
private val testNotes = prel(0,0,0,0,0)+
prel(24,28,31,36,40) +
prel(24, 26,33,28,41) +
prel(23,26,31,38,41) +
prel(24,28,31,36,40) +
prel(24,28,33,40,45) +
prel(24,26,30,33,38) +
prel(23,26,31,38,43) +
prel(23,24,28,31,36) +
prel(21,24,28,31,36)
}

View File

@@ -6,8 +6,12 @@ import net.torvald.terrarum.audio.AudioProcessBuf
import net.torvald.terrarum.audio.audiobank.MusicContainer
import net.torvald.terrarum.ceilToInt
import net.torvald.terrarum.floorToInt
import org.dyn4j.Epsilon
import java.lang.Math.pow
import kotlin.math.PI
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
import kotlin.math.sin
/**
* Creates 61-note (C1 to C5) pack of samples from a single file. Tuning system is 12-note Equal Temperament.
@@ -17,7 +21,7 @@ import kotlin.math.roundToInt
object InstrumentLoader {
// 0 is C0
private fun getStretch(noteNum: Int) = pow(2.0, (noteNum - 29) / 12.0)
private fun getRate(noteNum: Int) = pow(2.0, noteNum / 12.0)
/**
@@ -51,8 +55,8 @@ object InstrumentLoader {
for (j in 0 until 61) {
val i = j - initialNote
val rate = getStretch(i)
val sampleCount = (masterFile.totalSizeInSamples * rate).roundToInt()
val rate = getRate(i)
val sampleCount = (masterFile.totalSizeInSamples * (1.0 / rate)).roundToInt()
val samplesL = FloatArray(sampleCount)
val samplesR = if (isDualMono) samplesL else FloatArray(sampleCount)
@@ -92,7 +96,7 @@ object InstrumentLoader {
var weightedSum = 0.0
for (j in leftBound..rightBound) {
val w = AudioProcessBuf.L(t - j.toDouble())
val w = L(t - j.toDouble())
akkuL += input[j] * w
weightedSum += w
}
@@ -101,4 +105,14 @@ object InstrumentLoader {
}
}
fun L(x: Double): Double {
return if (x.absoluteValue < Epsilon.E)
1.0
else if (-TAPS <= x && x < TAPS)
(TAPS * sin(PI * x) * sin(PI * x / TAPS)) / (PI * PI * x * x)
else
0.0
}
}

View File

@@ -0,0 +1,132 @@
package net.torvald.terrarum.modulebasegame.gameactors
import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.INGAME
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.langpack.Lang
import net.torvald.terrarum.modulebasegame.audio.audiobank.AudioBankMusicBox
import net.torvald.terrarum.modulebasegame.gameitems.FixtureItemBase
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
/**
* Created by minjaesong on 2024-04-15.
*/
class FixtureMechanicalTines : Electric {
constructor() : super(
BlockBox(BlockBox.NO_COLLISION, 2, 2),
nameFun = { Lang["ITEM_MECHANICAL_TINES"] }
)
@Transient private val audioBank = AudioBankMusicBox()
@Transient private val track = App.audioMixer.getFreeTrackNoMatterWhat()
init {
track.trackingTarget = this
track.currentTrack = audioBank
track.play()
val itemImage = FixtureItemBase.getItemImageFromSingleImage("basegame", "sprites/fixtures/mechanical_tines.tga")
density = 1400.0
setHitboxDimension(TILE_SIZE * 2, TILE_SIZE * 2, 0, 0)
makeNewSprite(TextureRegionPack(itemImage.texture, TILE_SIZE * 2, TILE_SIZE * 2)).let {
it.setRowsAndFrames(1,1)
}
actorValue[AVKey.BASEMASS] = 20.0
setWireSinkAt(0, 1, "digital_bit")
setWireSinkAt(1, 1, "network")
despawnHook = {
track.stop()
track.currentTrack = null
track.trackingTarget = null
audioBank.dispose()
}
}
@Transient private var testRollCursor = 0
@Transient private val TICK_DIVISOR = 12
override fun updateSignal() {
// TODO update using network port
if (isSignalHigh(0, 1)) {
if (INGAME.WORLD_UPDATE_TIMER % TICK_DIVISOR == 0L) {
audioBank.sendMessage(testNotes[testRollCursor])
testRollCursor = (testRollCursor + 1) % testNotes.size
}
}
}
companion object {
@Transient val testNotes =
prel(24,28,31,36,40) +
prel(24,26,33,38,41) +
prel(23,26,31,38,41) +
prel(24,28,31,36,40) +
prel(24,28,33,40,45) +
prel(24,26,30,33,38) +
prel(23,26,31,38,43) +
prel(23,24,28,31,36) +
prel(21,24,28,31,36) +
prel(14,21,26,30,36) +
prel(19,23,26,31,35) +
prel(19,22,28,31,37) +
prel(17,21,26,33,38) +
prel(17,20,26,29,35) +
prel(16,19,24,31,36) +
prel(16,17,21,24,29) +
prel(14,17,21,24,29) +
prel( 7,14,19,23,29) +
prel(12,16,19,24,28) +
prel(12,19,22,24,28) +
prel( 5,17,21,24,28) +
prel( 6,12,21,24,27) +
prel( 8,17,23,24,26) +
prel( 7,17,19,23,26) +
prel( 7,16,19,24,28) +
prel( 7,14,19,24,29) +
prel( 7,14,19,23,29) +
prel( 7,15,21,24,30) +
prel( 7,16,19,24,31) +
prel( 7,14,19,24,29) +
prel( 7,14,19,23,29) +
prel( 0,12,19,22,28) +
end1( 0,12,17,21,24,29,21,17,14) +
end2( 0,11,31,35,38,41,26,29,28) +
end3( 0,12,28,31,36)
private fun prel(n1: Int, n2: Int, n3: Int, n4: Int, n5: Int): List<Long> {
return listOf(
1L shl n1, 1L shl n2, 1L shl n3, 1L shl n4, 1L shl n5, 1L shl n3, 1L shl n4, 1L shl n5,
1L shl n1, 1L shl n2, 1L shl n3, 1L shl n4, 1L shl n5, 1L shl n3, 1L shl n4, 1L shl n5)
}
private fun end1(n1: Int, n2: Int, n3: Int, n4: Int, n5: Int, n6: Int, n7: Int, n8: Int, n9: Int): List<Long> {
return listOf(
1L shl n1, 1L shl n2, 1L shl n3, 1L shl n4, 1L shl n5, 1L shl n6, 1L shl n5, 1L shl n4,
1L shl n5, 1L shl n7, 1L shl n8, 1L shl n7, 1L shl n8, 1L shl n9, 1L shl n8, 1L shl n9)
}
private fun end2(n1: Int, n2: Int, n3: Int, n4: Int, n5: Int, n6: Int, n7: Int, n8: Int, n9: Int): List<Long> {
return listOf(
1L shl n1, 1L shl n2, 1L shl n3, 1L shl n4, 1L shl n5, 1L shl n6, 1L shl n5, 1L shl n4,
1L shl n5, 1L shl n4, 1L shl n3, 1L shl n4, 1L shl n7, 1L shl n8, 1L shl n9, 1L shl n7)
}
private fun end3(vararg ns: Int): List<Long> {
return listOf(ns.map { 1L shl it }.fold(0L) { acc, note -> acc or note }) + List(15) { 0L }
}
}
}

View File

@@ -36,4 +36,22 @@ class ItemMusicalTurntable(originalID: ItemID) : FixtureItemBase(originalID, "ne
override var baseToolSize: Double? = baseMass
override var originalName = "ITEM_TURNTABLE"
}
/**
* Created by minjaesong on 2024-04-15.
*/
class ItemMechanicalTines(originalID: ItemID) : FixtureItemBase(originalID, "net.torvald.terrarum.modulebasegame.gameactors.FixtureMechanicalTines") {
override var baseMass = 20.0
override val canBeDynamic = false
override val materialId = ""
override val itemImage: TextureRegion
get() = FixtureItemBase.getItemImageFromSingleImage("basegame", "sprites/fixtures/mechanical_tines.tga")
override var baseToolSize: Double? = baseMass
override var originalName = "ITEM_MECHANICAL_TINES"
}

Binary file not shown.