mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-09 01:54:04 +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
|
51;net.torvald.terrarum.modulebasegame.gameitems.ItemElectricWorkbench;FIXTURE,CRAFTING
|
||||||
52;net.torvald.terrarum.modulebasegame.gameitems.ItemLogicSignalSevenSeg;FIXTURE,SIGNAL
|
52;net.torvald.terrarum.modulebasegame.gameitems.ItemLogicSignalSevenSeg;FIXTURE,SIGNAL
|
||||||
53;net.torvald.terrarum.modulebasegame.gameitems.ItemEngravingWorkbench;FIXTURE,CRAFTING
|
53;net.torvald.terrarum.modulebasegame.gameitems.ItemEngravingWorkbench;FIXTURE,CRAFTING
|
||||||
|
54;net.torvald.terrarum.modulebasegame.gameitems.ItemMechanicalTines;FIXTURE,MUSIC,SIGNAL
|
||||||
|
|
||||||
# ingots
|
# ingots
|
||||||
112;net.torvald.terrarum.modulebasegame.gameitems.IngotCopper;INGOT
|
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 {
|
abstract class AudioBank : Disposable {
|
||||||
|
|
||||||
|
open val notCopyable: Boolean = false
|
||||||
|
|
||||||
protected val hash = System.nanoTime()
|
protected val hash = System.nanoTime()
|
||||||
|
|
||||||
abstract fun makeCopy(): AudioBank
|
abstract fun makeCopy(): AudioBank
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ class MusicCache(val trackName: String) : Disposable {
|
|||||||
private val cache = HashMap<String, AudioBank>()
|
private val cache = HashMap<String, AudioBank>()
|
||||||
|
|
||||||
fun getOrPut(music: AudioBank?): AudioBank? {
|
fun getOrPut(music: AudioBank?): AudioBank? {
|
||||||
if (music != null)
|
if (music != null && !music.notCopyable)
|
||||||
return cache.getOrPut(music.name) { music.makeCopy() }
|
return cache.getOrPut(music.name) { music.makeCopy() }
|
||||||
|
else if (music != null)
|
||||||
|
return music
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package net.torvald.terrarum.modulebasegame.audio.audiobank
|
package net.torvald.terrarum.modulebasegame.audio.audiobank
|
||||||
|
|
||||||
import net.torvald.terrarum.App
|
import net.torvald.terrarum.App
|
||||||
|
import net.torvald.terrarum.App.printdbg
|
||||||
import net.torvald.terrarum.INGAME
|
import net.torvald.terrarum.INGAME
|
||||||
import net.torvald.terrarum.audio.AudioBank
|
import net.torvald.terrarum.audio.AudioBank
|
||||||
|
|
||||||
@@ -9,14 +10,21 @@ import net.torvald.terrarum.audio.AudioBank
|
|||||||
*/
|
*/
|
||||||
class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {}) : 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 name = "spieluhr"
|
||||||
override val samplingRate = 48000 // 122880 // use 122880 to make each tick is 2048 samples
|
override val samplingRate = 48000 // 122880 // use 122880 to make each tick is 2048 samples
|
||||||
override val channels = 1
|
override val channels = 1
|
||||||
|
|
||||||
private val getSample = // usage: getSample(noteNum 0..60)
|
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
|
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
|
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
|
* Queues the notes such that they are played on the next tick
|
||||||
*/
|
*/
|
||||||
override fun sendMessage(noteBits: Long) {
|
override fun sendMessage(noteBits: Long) {
|
||||||
|
queue(INGAME.WORLD_UPDATE_TIMER + 1, noteBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun queue(tick: Long, noteBits: Long) {
|
||||||
if (noteBits == 0L) return
|
if (noteBits == 0L) return
|
||||||
|
|
||||||
val tick = INGAME.WORLD_UPDATE_TIMER + 1
|
|
||||||
val notes = findSetBits(noteBits)
|
val notes = findSetBits(noteBits)
|
||||||
|
|
||||||
val buf = FloatArray(getSample(notes.first()).first.size)
|
val buf = FloatArray(getSample(0).first.size)
|
||||||
|
|
||||||
// combine all those samples
|
// combine all those samples
|
||||||
notes.forEach { note ->
|
notes.forEach { note ->
|
||||||
@@ -53,7 +71,8 @@ class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {})
|
|||||||
}
|
}
|
||||||
|
|
||||||
// actually queue it
|
// actually queue it
|
||||||
messageQueue.add(Msg(tick, buf, buf))
|
val msg = Msg(tick, buf, buf)
|
||||||
|
messageQueue.add(messageQueue.size, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun currentPositionInSamples() = 0L
|
override fun currentPositionInSamples() = 0L
|
||||||
@@ -62,6 +81,9 @@ class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {})
|
|||||||
val tickCount = INGAME.WORLD_UPDATE_TIMER
|
val tickCount = INGAME.WORLD_UPDATE_TIMER
|
||||||
val bufferSize = bufferL.size
|
val bufferSize = bufferL.size
|
||||||
|
|
||||||
|
bufferL.fill(0f)
|
||||||
|
bufferR.fill(0f)
|
||||||
|
|
||||||
// only copy over the past and current messages
|
// only copy over the past and current messages
|
||||||
messageQueue.filter { it.tick <= tickCount }.forEach {
|
messageQueue.filter { it.tick <= tickCount }.forEach {
|
||||||
// copy over the samples
|
// copy over the samples
|
||||||
@@ -73,44 +95,33 @@ class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {})
|
|||||||
it.samplesDispatched += bufferSize
|
it.samplesDispatched += bufferSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// dequeue the finished messages
|
// dequeue the finished messages
|
||||||
val messagesToKill = ArrayList<Msg>(messageQueue.filter { it.samplesDispatched >= it.samplesL.size })
|
var rc = 0
|
||||||
if (messagesToKill.isNotEmpty()) messagesToKill.forEach {
|
while (rc < messageQueue.size) {
|
||||||
messageQueue.remove(it)
|
val it = messageQueue[rc]
|
||||||
|
if (it.samplesDispatched >= it.samplesL.size) {
|
||||||
|
messageQueue.removeAt(rc)
|
||||||
|
rc -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rc += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printdbg(this, "Queuelen: ${messageQueue.size}")
|
||||||
|
|
||||||
return bufferSize
|
return bufferSize
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reset() {
|
override fun reset() {
|
||||||
TODO("Not yet implemented")
|
messageQueue.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun makeCopy(): AudioBank {
|
override fun makeCopy(): AudioBank {
|
||||||
TODO("Not yet implemented")
|
throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
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.audio.audiobank.MusicContainer
|
||||||
import net.torvald.terrarum.ceilToInt
|
import net.torvald.terrarum.ceilToInt
|
||||||
import net.torvald.terrarum.floorToInt
|
import net.torvald.terrarum.floorToInt
|
||||||
|
import org.dyn4j.Epsilon
|
||||||
import java.lang.Math.pow
|
import java.lang.Math.pow
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.roundToInt
|
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.
|
* 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 {
|
object InstrumentLoader {
|
||||||
|
|
||||||
// 0 is C0
|
// 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) {
|
for (j in 0 until 61) {
|
||||||
val i = j - initialNote
|
val i = j - initialNote
|
||||||
val rate = getStretch(i)
|
val rate = getRate(i)
|
||||||
val sampleCount = (masterFile.totalSizeInSamples * rate).roundToInt()
|
val sampleCount = (masterFile.totalSizeInSamples * (1.0 / rate)).roundToInt()
|
||||||
|
|
||||||
val samplesL = FloatArray(sampleCount)
|
val samplesL = FloatArray(sampleCount)
|
||||||
val samplesR = if (isDualMono) samplesL else FloatArray(sampleCount)
|
val samplesR = if (isDualMono) samplesL else FloatArray(sampleCount)
|
||||||
@@ -92,7 +96,7 @@ object InstrumentLoader {
|
|||||||
var weightedSum = 0.0
|
var weightedSum = 0.0
|
||||||
|
|
||||||
for (j in leftBound..rightBound) {
|
for (j in leftBound..rightBound) {
|
||||||
val w = AudioProcessBuf.L(t - j.toDouble())
|
val w = L(t - j.toDouble())
|
||||||
akkuL += input[j] * w
|
akkuL += input[j] * w
|
||||||
weightedSum += 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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,3 +37,21 @@ class ItemMusicalTurntable(originalID: ItemID) : FixtureItemBase(originalID, "ne
|
|||||||
override var originalName = "ITEM_TURNTABLE"
|
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