feat: composer

This commit is contained in:
xtex 2023-07-30 09:57:56 +08:00
parent 314957eeb5
commit bbef6cb342
Signed by: xtex
GPG Key ID: B918086ED8045B91
3 changed files with 82 additions and 13 deletions

View File

@ -1,20 +1,75 @@
package quaedam.projection.music
import kotlin.math.abs
import kotlin.random.Random
import kotlin.random.nextInt
object Composer {
/**
* The composer for music.
* rhythmRandom is used for a better rhythm sync between different instruments.
*/
class Composer(val noteRandom: Random, val rhythmRandom: Random) {
data class Note(val note: Int, val volume: Float, val time: Int)
fun composeMusic(random: Random) = listOf<Note>(
Note(0, 1.0f, 3),
Note(1, 1.0f, 3),
Note(2, 1.0f, 3),
Note(3, 1.0f, 3),
Note(4, 1.0f, 3),
Note(5, 1.0f, 3),
Note(6, 1.0f, 3),
Note(7, 1.0f, 3),
val baseTime = arrayOf(5, 5, 3, 3, 4, 4, 2, 2, 10).random(rhythmRandom)
val baseNote = noteRandom.nextInt(5..19)
fun composeMusic() = decorate(
(0..rhythmRandom.nextInt(5)).flatMap { composeSection() }
)
fun decorate(notes: List<Note>) = notes.map {
if (noteRandom.nextInt(4) == 0) {
doDecorate(it)
} else {
it
}
}
fun doDecorate(note: Note): Note {
var noteVal = note.note
if (noteRandom.nextInt(4) == 0) {
if (noteRandom.nextBoolean()) {
noteVal += 1
} else {
noteVal -= 1
}
}
var volume = note.volume
if (noteRandom.nextInt(4) == 0) {
volume *= noteRandom.nextFloat() * 0.8f + 0.6f
}
return Note(noteVal, volume, note.time)
}
fun composeSection(depth: Int = 0): List<Note> {
if (depth < 3 && rhythmRandom.nextBoolean()) {
val notes = (0..rhythmRandom.nextInt(3 - depth)).flatMap { composeSection(depth + 1) }
if (depth == 2) {
return (0..rhythmRandom.nextInt(3)).flatMap { notes }
} else {
return notes
}
} else {
var notePointer = baseNote + noteRandom.nextInt(-3..3)
var direction = -1
var directionCounter = 0
return (0..rhythmRandom.nextInt(4..16)).map {
if (directionCounter == 0) {
// start new direction
directionCounter = rhythmRandom.nextInt(2..6)
direction = if (directionCounter % 2 == 0) {
rhythmRandom.nextInt(-2..2)
} else {
noteRandom.nextInt(-3..3)
}
}
notePointer = abs(notePointer + direction) % 25
directionCounter--
Note(notePointer, 1.0f, baseTime + rhythmRandom.nextInt(-1..1))
}
}
}
}

View File

@ -27,6 +27,7 @@ import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import quaedam.Quaedam
import quaedam.projector.Projector
import quaedam.utils.getChunksNearby
import quaedam.utils.sendBlockUpdated
object CyberInstrument {
@ -173,11 +174,24 @@ class CyberInstrumentBlockEntity(pos: BlockPos, state: BlockState) :
private fun checkProjections() =
Projector.findNearbyProjections(level!!, blockPos, MusicProjection.effect.get()).isNotEmpty()
fun startMusic() {
if (player == null && !level!!.isClientSide && checkProjections()) {
fun startMusic(force: Boolean = false, synced: Boolean = false) {
if ((player == null || force) && !level!!.isClientSide && checkProjections()) {
player = MusicPlayer(level!!.random.nextLong(), level!!, blockPos)
setChanged()
sendBlockUpdated()
if (!synced) {
// sync start to other instruments
level!!.getChunksNearby(blockPos, 1)
.flatMap {
it.blockEntities
.filterValues { entity -> entity is CyberInstrumentBlockEntity }
.filterKeys { pos -> pos.distSqr(blockPos) < 100 }
.values
}
.filterNot { it == this }
.filterIsInstance<CyberInstrumentBlockEntity>()
.forEach { it.startMusic(force = true, synced = true) }
}
}
}

View File

@ -31,7 +31,7 @@ class MusicPlayer(val seed: Long, val level: Level, val pos: BlockPos, val start
tag.getLong(TAG_STARTED_AT)
)
var notes = Composer.composeMusic(Random(seed)).toMutableList()
var notes = Composer(Random(seed), Random(startedAt / 20 * 15)).composeMusic().toMutableList()
val totalTime = notes.sumOf { it.time }.toLong()
var remainingTime = totalTime
val isEnd get() = remainingTime <= 0 || notes.isEmpty()