package net.taehui.twilight import com.fasterxml.jackson.databind.ObjectMapper import net.taehui.twilight.awilight.DefaultCompute import net.taehui.twilight.note.InputNote import net.taehui.twilight.note.LongNote import net.taehui.twilight.note.MeterNote import net.taehui.twilight.note.TrapNote import org.apache.commons.lang3.ArrayUtils import java.nio.charset.Charset import java.util.* import kotlin.math.abs class BMSONCompiler(noteFileContents: ByteArray, format: String) : BaseCompiler(noteFileContents, format) { private val bmsonPositionLogicalYMap = TreeMap<Long, Double>() private val bmsonPositionBPMMap = TreeMap<Long, Double>() private lateinit var text: JSON.Bmson private var res = 0L override fun handleCompile(targetComputing: Computing) { text = ObjectMapper().readValue(String(noteFileContents, Charset.forName(format)), JSON.Bmson::class.java) val title = text.info.title val titleAssister0 = text.info.subtitle val titleAssister1 = text.info.chart_name targetComputing.title = "$title${if (titleAssister0.isEmpty() || title.endsWith(titleAssister0)) "" else " $titleAssister0"}${ if (titleAssister1.isEmpty() || title.endsWith( titleAssister1 ) ) "" else " $titleAssister1" }" targetComputing.artist = mutableListOf(text.info.artist, *text.info.subartists.toTypedArray()).joinToString(" / ") targetComputing.genre = text.info.genre val levelTextValue = abs(text.info.level) if (levelTextValue < 100) { targetComputing.levelText = "LV. $levelTextValue" } else { val levelText = levelTextValue.toString() targetComputing.levelText = "LV. ${levelText.substring(0.coerceAtLeast(levelText.length - 2), levelText.length)}" } val longNoteVariety = text.info.ln_type targetComputing.isAutoLongNote = longNoteVariety != 2L && longNoteVariety != 3L when (text.info.mode_hint) { "generic-4keys" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_4 "generic-5keys", "popn-5k" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_5 "generic-6keys" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_6 "generic-8keys" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_8 "generic-9keys", "popn-9k" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_9 "generic-10keys" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_10 "beat-5k" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_5_1 "beat-7k" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_7_1 "beat-10k" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_10_2 "beat-14k" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_14_2 "keyboard-24k" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_24_2 "keyboard-24k-double" -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_48_4 else -> targetComputing.inputMode = Component.InputMode.INPUT_MODE_7 } targetComputing.judgmentStage = 70 / (0.03 * text.info.judge_rank + 7) targetComputing.hitPointsValue = text.info.total / 10000 val bmsonPositionSet = TreeSet<Long>() bmsonPositionSet.add(0L) var audioCount = 0 for (audioChannel in text.sound_channels) { if (audioChannel.name.isNotEmpty()) { ++audioCount } for (note in audioChannel.notes) { var bmsonPosition = note.y val input = getBMSONInput(note.x.toInt(), targetComputing.inputMode) val isInput = input > 0 val isLongInput = note.l > 0L bmsonPositionSet.add(bmsonPosition) if (isInput) { positionStandNoteCountMap[bmsonPosition.toDouble()] = positionStandNoteCountMap.getOrDefault(bmsonPosition.toDouble(), 0) + 1 val isAutoableInput = ArrayUtils.indexOf(Component.AUTOABLE_INPUTS[targetComputing.inputMode.value], input) != -1 targetComputing.totalNotes += 1 if (isAutoableInput) { targetComputing.autoableNotes += 1 } if (isLongInput) { targetComputing.longNotes += 1 } } if (isLongInput) { bmsonPosition += note.l bmsonPositionSet.add(bmsonPosition) } highestPosition = highestPosition.coerceAtLeast(bmsonPosition.toDouble()) } } if (targetComputing.totalNotes == 0) { targetComputing.inputMode = Component.InputMode.INPUT_MODE_5_1 } for (trapChannel in text.sound_channels) { for (note in trapChannel.notes) { val bmsonPosition = note.y val input = getBMSONInput(note.x.toInt(), targetComputing.inputMode) val isInput = input > 0 bmsonPositionSet.add(bmsonPosition) if (isInput) { ++targetComputing.trapNotes } highestPosition = highestPosition.coerceAtLeast(bmsonPosition.toDouble()) } } text.bga.bga_events.forEach { bmsonPositionSet.add(it.y) } text.bga.layer_events.forEach { bmsonPositionSet.add(it.y) } text.bga.poor_events.forEach { bmsonPositionSet.add(it.y) } text.lines.forEach { val bmsonPosition = it.y bmsonPositionSet.add(bmsonPosition) highestPosition = highestPosition.coerceAtLeast(bmsonPosition.toDouble()) } text.bpm_events.forEach { val bmsonPosition = it.y bpmValues[bmsonPosition.toDouble()] = it.bpm bmsonPositionSet.add(bmsonPosition) } val bmsonPositionStopMap = TreeMap<Long, Long>() text.stop_events.forEach { val bmsonPosition = it.y bmsonPositionStopMap[bmsonPosition] = bmsonPositionStopMap.getOrDefault(bmsonPosition, 0L) + it.duration bmsonPositionSet.add(bmsonPosition) } targetComputing.levyingBPM = text.info.init_bpm valueComponent = Component(targetComputing.levyingBPM) res = text.info.resolution * 4L var lastBMSONPosition = 0L var lastWait = 0.0 var lastBPM = targetComputing.levyingBPM for (bmsonPosition in bmsonPositionSet) { bmsonPositionLogicalYMap[bmsonPosition] = bmsonPosition.toDouble() * -valueComponent.logicalYMeter / res.toDouble() lastWait += valueComponent.millisMeter * (bmsonPosition - lastBMSONPosition).toDouble() / res.toDouble() bpmValues[bmsonPosition.toDouble()]?.let { lastBPM = it valueComponent.setBPM(it) } waitValues[bmsonPosition.toDouble()] = lastWait lastBMSONPosition = bmsonPosition bmsonPositionStopMap[bmsonPosition]?.let { lastWait += valueComponent.millisMeter * it.toDouble() / res.toDouble() bmsonPositionBPMMap[bmsonPosition] = lastBPM } } targetComputing.isBanned = audioCount < 2 || targetComputing.totalNotes == 0 targetComputing.noteVariety = Component.NoteVariety.BMSON } override fun handleCompile(defaultComputer: DefaultCompute) { handleCompile(defaultComputer as Computing) text.lines.forEach { val bmsonPosition = it.y val logicalY = valueComponent.levyingHeight + bmsonPositionLogicalYMap[bmsonPosition]!! val wait = waitValues[bmsonPosition.toDouble()]!! notes.add(MeterNote(logicalY, wait)) } text.sound_channels.forEach { it.notes.sortedBy { note -> note.y }.forEach { note -> val bmsonPosition = note.y val logicalY = valueComponent.levyingHeight + bmsonPositionLogicalYMap[bmsonPosition]!! val wait = waitValues[bmsonPosition.toDouble()]!! val input = getBMSONInput(note.x.toInt(), defaultComputer.inputMode) if (input > 0) { if (note.l > 0L) { notes.add( LongNote( logicalY, wait, input, waitValues[(bmsonPosition + note.l).toDouble()]!! - wait, logicalY - (valueComponent.levyingHeight + bmsonPositionLogicalYMap[bmsonPosition + note.l]!!) ) ) } else { notes.add(InputNote(logicalY, wait, input)) } } } } text.mine_channels.forEach { it.notes.sortedBy { note -> note.y }.forEach { note -> val input = getBMSONInput(note.x.toInt(), defaultComputer.inputMode) if (input > 0) { val bmsonPosition = note.y val logicalY = valueComponent.levyingHeight + bmsonPositionLogicalYMap[bmsonPosition]!! val wait = waitValues[bmsonPosition.toDouble()]!! notes.add(TrapNote(logicalY, wait, input)) } } } bpmValues.forEach { waitBPMMap[waitValues[it.key]!!] = it.value } text.stop_events.forEach { val bmsonPosition = it.y bmsonPositionBPMMap[bmsonPosition]?.let { bpm -> val wait = waitValues[bmsonPosition.toDouble()]!! valueComponent.setBPM(bpm) waitStopMap[wait] = 0.0 waitStopMap[wait + valueComponent.millisMeter * it.duration.toDouble() / res.toDouble()] = bpm } } } private fun getBMSONInput(x: Int, inputMode: Component.InputMode): Int { when (inputMode) { Component.InputMode.INPUT_MODE_4 -> { when (x) { 1, 2, 3, 4 -> { return x } } } Component.InputMode.INPUT_MODE_5 -> { when (x) { 1, 2, 3, 4, 5 -> { return x } } } Component.InputMode.INPUT_MODE_6 -> { when (x) { 1, 2, 3, 4, 5, 6 -> { return x } } } Component.InputMode.INPUT_MODE_7 -> { when (x) { 1, 2, 3, 4, 5, 6, 7 -> { return x } } } Component.InputMode.INPUT_MODE_8 -> { when (x) { 1, 2, 3, 4, 5, 6, 7, 8 -> { return x } } } Component.InputMode.INPUT_MODE_9 -> { when (x) { 1, 2, 3, 4, 5, 6, 7, 8, 9 -> { return x } } } Component.InputMode.INPUT_MODE_10 -> { when (x) { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 -> { return x } } } Component.InputMode.INPUT_MODE_5_1 -> { when (x) { 8 -> { return 1 } 1, 2, 3, 4, 5 -> { return x + 1 } } } Component.InputMode.INPUT_MODE_7_1 -> { when (x) { 8 -> { return 1 } 1, 2, 3, 4, 5, 6, 7 -> { return x + 1 } } } Component.InputMode.INPUT_MODE_10_2 -> { when (x) { 8 -> { return 1 } 16 -> { return 12 } 1, 2, 3, 4, 5 -> { return x + 1 } 9, 10, 11, 12, 13 -> { return x - 2 } } } Component.InputMode.INPUT_MODE_14_2 -> { when (x) { 8 -> { return 1 } 1, 2, 3, 4, 5, 6, 7 -> { return x + 1 } 9, 10, 11, 12, 13, 14, 15, 16 -> { return x } } } Component.InputMode.INPUT_MODE_24_2 -> { when (x) { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 -> { return x + 1 } 25 -> { return 1 } 26 -> { return 26 } } } Component.InputMode.INPUT_MODE_48_4 -> { when (x) { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 -> { return x + 2 } 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 -> { return x } 25 -> { return 1 } 26 -> { return 2 } 51 -> { return 51 } 52 -> { return 52 } } } } return 0 } }