mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
musicbox wip
This commit is contained in:
@@ -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
|
||||
|
||||
|
BIN
assets/mods/basegame/sprites/fixtures/mechanical_tines.tga
LFS
Normal file
BIN
assets/mods/basegame/sprites/fixtures/mechanical_tines.tga
LFS
Normal file
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
BIN
work_files/graphics/sprites/fixtures/mechanical_tines.kra
LFS
Normal file
BIN
work_files/graphics/sprites/fixtures/mechanical_tines.kra
LFS
Normal file
Binary file not shown.
Reference in New Issue
Block a user