From c79ce805df9c7f320be2bbbc8ab2f496f13156d9 Mon Sep 17 00:00:00 2001 From: Graham Date: Tue, 20 Jul 2021 23:17:06 +0100 Subject: [PATCH] Add implementation of the client's song format It's basically a simplified MIDI file (lacks support for SMPTE and most meta/system messages) encoded in a smarter way. This implementation can decode anything in the 550 cache, convert it to MIDI format and then re-encode it bit-for-bit identically to the original. Signed-off-by: Graham --- .../kotlin/org/openrs2/cache/midi/Song.kt | 538 ++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100644 cache-550/src/main/kotlin/org/openrs2/cache/midi/Song.kt diff --git a/cache-550/src/main/kotlin/org/openrs2/cache/midi/Song.kt b/cache-550/src/main/kotlin/org/openrs2/cache/midi/Song.kt new file mode 100644 index 00000000..9968008f --- /dev/null +++ b/cache-550/src/main/kotlin/org/openrs2/cache/midi/Song.kt @@ -0,0 +1,538 @@ +package org.openrs2.cache.midi + +import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufAllocator +import org.openrs2.buffer.readVarInt +import org.openrs2.buffer.writeVarInt +import javax.sound.midi.MetaMessage +import javax.sound.midi.MidiEvent +import javax.sound.midi.Sequence +import javax.sound.midi.ShortMessage + +public object Song { + private const val NOTE_ON = 0 + private const val NOTE_OFF = 1 + private const val CONTROL_CHANGE = 2 + private const val PITCH_WHEEL_CHANGE = 3 + private const val CHANNEL_PRESSURE_CHANGE = 4 + private const val KEY_PRESSURE_CHANGE = 5 + private const val PROGRAM_CHANGE = 6 + private const val END_OF_TRACK = 7 + private const val SET_TEMPO = 23 + + private const val BANK_SELECT_MSB = 0 + private const val BANK_SELECT_LSB = 32 + private const val MODULATION_WHEEL_MSB = 1 + private const val MODULATION_WHEEL_LSB = 33 + private const val CHANNEL_VOLUME_MSB = 7 + private const val CHANNEL_VOLUME_LSB = 39 + private const val PAN_MSB = 10 + private const val PAN_LSB = 42 + private const val NON_REGISTERED_MSB = 99 + private const val NON_REGISTERED_LSB = 98 + private const val REGISTERED_MSB = 101 + private const val REGISTERED_LSB = 100 + private const val DAMPER = 64 + private const val PORTAMENTO = 65 + private const val ALL_SOUND_OFF = 120 + private const val RESET_CONTROLLERS = 121 + private const val ALL_NOTES_OFF = 123 + + private const val META_END_OF_TRACK = 47 + private const val META_SET_TEMPO = 81 + + private const val CONTROLLERS = 128 + + public fun read(buf: ByteBuf): Sequence { + buf.markReaderIndex() + + val tracks = buf.getUnsignedByte(buf.writerIndex() - 3).toInt() + val division = buf.getUnsignedShort(buf.writerIndex() - 2) + + if (division and 0x8000 != 0) { + throw IllegalArgumentException("SMPTE unsupported") + } + + val sequence = Sequence(Sequence.PPQ, division and 0x7FFF) + + var events = 0 + var tempoChanges = 0 + var noteOnEvents = 0 + var noteOffEvents = 0 + var controllerEvents = 0 + var pitchWheelEvents = 0 + var channelPressureEvents = 0 + var keyPressureEvents = 0 + var bankSelectEvents = 0 + + for (i in 0 until tracks) { + while (true) { + events++ + + val statusAndChannel = buf.readUnsignedByte().toInt() + if (statusAndChannel == END_OF_TRACK) { + break + } else if (statusAndChannel == SET_TEMPO) { + tempoChanges++ + continue + } + + when (val status = statusAndChannel and 0xF) { + NOTE_ON -> noteOnEvents++ + NOTE_OFF -> noteOffEvents++ + CONTROL_CHANGE -> controllerEvents++ + PITCH_WHEEL_CHANGE -> pitchWheelEvents++ + CHANNEL_PRESSURE_CHANGE -> channelPressureEvents++ + KEY_PRESSURE_CHANGE -> keyPressureEvents++ + PROGRAM_CHANGE -> bankSelectEvents++ + else -> throw IllegalArgumentException("Unsupported status: $status") + } + } + } + + val deltaTimeBuf = buf.slice() + for (i in 0 until events) { + buf.readVarInt() + } + + var modulationWheelMsbEvents = 0 + var modulationWheelLsbEvents = 0 + var channelVolumeMsbEvents = 0 + var channelVolumeLsbEvents = 0 + var panMsbEvents = 0 + var panLsbEvents = 0 + var nonRegisteredMsbEvents = 0 + var nonRegisteredLsbEvents = 0 + var registeredMsbEvents = 0 + var registeredLsbEvents = 0 + var otherKnownControllerEvents = 0 + var unknownControllerEvents = 0 + + val controllerBuf = buf.slice() + + var controller = 0 + for (i in 0 until controllerEvents) { + controller = (controller + buf.readUnsignedByte()) and 0x7F + + when (controller) { + BANK_SELECT_MSB, BANK_SELECT_LSB -> bankSelectEvents++ + MODULATION_WHEEL_MSB -> modulationWheelMsbEvents++ + MODULATION_WHEEL_LSB -> modulationWheelLsbEvents++ + CHANNEL_VOLUME_MSB -> channelVolumeMsbEvents++ + CHANNEL_VOLUME_LSB -> channelVolumeLsbEvents++ + PAN_MSB -> panMsbEvents++ + PAN_LSB -> panLsbEvents++ + NON_REGISTERED_MSB -> nonRegisteredMsbEvents++ + NON_REGISTERED_LSB -> nonRegisteredLsbEvents++ + REGISTERED_MSB -> registeredMsbEvents++ + REGISTERED_LSB -> registeredLsbEvents++ + DAMPER, PORTAMENTO, ALL_SOUND_OFF, RESET_CONTROLLERS, ALL_NOTES_OFF -> otherKnownControllerEvents++ + else -> unknownControllerEvents++ + } + } + + val otherKnownControllerBuf = buf.readSlice(otherKnownControllerEvents) + val keyPressureBuf = buf.readSlice(keyPressureEvents) + val channelPressureBuf = buf.readSlice(channelPressureEvents) + val pitchWheelMsbBuf = buf.readSlice(pitchWheelEvents) + val modulationWheelMsbBuf = buf.readSlice(modulationWheelMsbEvents) + val channelVolumeMsbBuf = buf.readSlice(channelVolumeMsbEvents) + val panMsbBuf = buf.readSlice(panMsbEvents) + val keyBuf = buf.readSlice(noteOnEvents + noteOffEvents + keyPressureEvents) + val onVelocityBuf = buf.readSlice(noteOnEvents) + val unknownControllerBuf = buf.readSlice(unknownControllerEvents) + val offVelocityBuf = buf.readSlice(noteOffEvents) + val modulationWheelLsbBuf = buf.readSlice(modulationWheelLsbEvents) + val channelVolumeLsbBuf = buf.readSlice(channelVolumeLsbEvents) + val panLsbBuf = buf.readSlice(panLsbEvents) + val bankSelectBuf = buf.readSlice(bankSelectEvents) + val pitchWheelLsbBuf = buf.readSlice(pitchWheelEvents) + val nonRegisteredMsbBuf = buf.readSlice(nonRegisteredMsbEvents) + val nonRegisteredLsbBuf = buf.readSlice(nonRegisteredLsbEvents) + val registeredMsbBuf = buf.readSlice(registeredMsbEvents) + val registeredLsbBuf = buf.readSlice(registeredLsbEvents) + val tempoBuf = buf.readSlice(tempoChanges * 3) + + // check only the three trailer bytes are remaining + assert(buf.readableBytes() == 3) + + buf.resetReaderIndex() + + var channel = 0 + var key = 0 + var onVelocity = 0 + var offVelocity = 0 + controller = 0 + val values = IntArray(CONTROLLERS) + var pitchWheel = 0 + var channelPressure = 0 + var keyPressure = 0 + + for (i in 0 until tracks) { + val track = sequence.createTrack() + + var time = 0L + + while (true) { + time += deltaTimeBuf.readVarInt() + + val statusAndChannel = buf.readUnsignedByte().toInt() + if (statusAndChannel == END_OF_TRACK) { + track.add(MidiEvent(MetaMessage(META_END_OF_TRACK, null, 0), time)) + break + } else if (statusAndChannel == SET_TEMPO) { + val tempo = ByteArray(3) + tempoBuf.readBytes(tempo) + track.add(MidiEvent(MetaMessage(META_SET_TEMPO, tempo, tempo.size), time)) + continue + } + + val status = statusAndChannel and 0xF + channel = channel xor (statusAndChannel shr 4) + + val message = when (status) { + NOTE_ON -> { + key = (key + keyBuf.readUnsignedByte().toInt()) and 0x7F + onVelocity = (onVelocity + onVelocityBuf.readUnsignedByte().toInt()) and 0x7F + ShortMessage(ShortMessage.NOTE_ON, channel, key, onVelocity) + } + NOTE_OFF -> { + key = (key + keyBuf.readUnsignedByte().toInt()) and 0x7F + offVelocity = (offVelocity + offVelocityBuf.readUnsignedByte().toInt()) and 0x7F + ShortMessage(ShortMessage.NOTE_OFF, channel, key, offVelocity) + } + CONTROL_CHANGE -> { + controller = (controller + controllerBuf.readUnsignedByte()) and 0x7F + + val valueDelta = when (controller) { + BANK_SELECT_MSB, BANK_SELECT_LSB -> bankSelectBuf.readUnsignedByte().toInt() + MODULATION_WHEEL_MSB -> modulationWheelMsbBuf.readUnsignedByte().toInt() + MODULATION_WHEEL_LSB -> modulationWheelLsbBuf.readUnsignedByte().toInt() + CHANNEL_VOLUME_MSB -> channelVolumeMsbBuf.readUnsignedByte().toInt() + CHANNEL_VOLUME_LSB -> channelVolumeLsbBuf.readUnsignedByte().toInt() + PAN_MSB -> panMsbBuf.readUnsignedByte().toInt() + PAN_LSB -> panLsbBuf.readUnsignedByte().toInt() + NON_REGISTERED_MSB -> nonRegisteredMsbBuf.readUnsignedByte().toInt() + NON_REGISTERED_LSB -> nonRegisteredLsbBuf.readUnsignedByte().toInt() + REGISTERED_MSB -> registeredMsbBuf.readUnsignedByte().toInt() + REGISTERED_LSB -> registeredLsbBuf.readUnsignedByte().toInt() + DAMPER, PORTAMENTO, ALL_SOUND_OFF, RESET_CONTROLLERS, ALL_NOTES_OFF -> + otherKnownControllerBuf.readUnsignedByte().toInt() + else -> unknownControllerBuf.readUnsignedByte().toInt() + } + + val value = values[controller] + valueDelta + values[controller] = value + ShortMessage(ShortMessage.CONTROL_CHANGE, channel, controller, value and 0x7F) + } + PITCH_WHEEL_CHANGE -> { + pitchWheel += pitchWheelLsbBuf.readUnsignedByte().toInt() + pitchWheel += (pitchWheelMsbBuf.readUnsignedByte().toInt() shl 7) + pitchWheel = pitchWheel and 0x3FFF + ShortMessage(ShortMessage.PITCH_BEND, channel, pitchWheel and 0x7F, pitchWheel shr 7) + } + CHANNEL_PRESSURE_CHANGE -> { + channelPressure = (channelPressure + channelPressureBuf.readUnsignedByte().toInt()) and 0x7F + ShortMessage(ShortMessage.CHANNEL_PRESSURE, channel, channelPressure, 0) + } + KEY_PRESSURE_CHANGE -> { + key = (key + keyBuf.readUnsignedByte().toInt()) and 0x7F + keyPressure = (keyPressure + keyPressureBuf.readUnsignedByte().toInt()) and 0x7F + ShortMessage(ShortMessage.POLY_PRESSURE, channel, key, keyPressure) + } + PROGRAM_CHANGE -> { + val bankSelect = bankSelectBuf.readUnsignedByte().toInt() + ShortMessage(ShortMessage.PROGRAM_CHANGE, channel, bankSelect, 0) + } + else -> throw IllegalStateException() + } + + track.add(MidiEvent(message, time)) + } + } + + // we've consumed the entire buf (checked by an assertion above) + buf.readerIndex(buf.writerIndex()) + + return sequence + } + + public fun write(sequence: Sequence, buf: ByteBuf) { + if (sequence.divisionType != Sequence.PPQ) { + throw IllegalArgumentException("SMPTE unsupported") + } + + val buffers = Buffers.alloc(buf.alloc()) + try { + val deltaTimeBuf = buffers.deltaTimeBuf + val controllerBuf = buffers.controllerBuf + val otherKnownControllerBuf = buffers.otherKnownControllerBuf + val keyPressureBuf = buffers.keyPressureBuf + val channelPressureBuf = buffers.channelPressureBuf + val pitchWheelMsbBuf = buffers.pitchWheelMsbBuf + val modulationWheelMsbBuf = buffers.modulationWheelMsbBuf + val channelVolumeMsbBuf = buffers.channelVolumeMsbBuf + val panMsbBuf = buffers.panMsbBuf + val keyBuf = buffers.keyBuf + val onVelocityBuf = buffers.onVelocityBuf + val unknownControllerBuf = buffers.unknownControllerBuf + val offVelocityBuf = buffers.offVelocityBuf + val modulationWheelLsbBuf = buffers.modulationWheelLsbBuf + val channelVolumeLsbBuf = buffers.channelVolumeLsbBuf + val panLsbBuf = buffers.panLsbBuf + val bankSelectBuf = buffers.bankSelectBuf + val pitchWheelLsbBuf = buffers.pitchWheelLsbBuf + val nonRegisteredMsbBuf = buffers.nonRegisteredMsbBuf + val nonRegisteredLsbBuf = buffers.nonRegisteredLsbBuf + val registeredMsbBuf = buffers.nonRegisteredMsbBuf + val registeredLsbBuf = buffers.nonRegisteredLsbBuf + val tempoBuf = buffers.tempoBuf + + var prevChannel = 0 + var prevKey = 0 + var prevOnVelocity = 0 + var prevOffVelocity = 0 + var prevController = 0 + var prevPitchWheel = 0 + var prevChannelPressure = 0 + var prevKeyPressure = 0 + val prevValues = IntArray(CONTROLLERS) + + for (track in sequence.tracks) { + var prevTime = 0L + + for (i in 0 until track.size()) { + val event = track[i] + + val time = event.tick + deltaTimeBuf.writeVarInt((time - prevTime).toInt()) + prevTime = time + + when (val message = event.message) { + is MetaMessage -> { + when (val type = message.type) { + META_END_OF_TRACK -> buf.writeByte(END_OF_TRACK) + META_SET_TEMPO -> { + buf.writeByte(SET_TEMPO) + require(message.data.size == 3) + tempoBuf.writeBytes(message.data) + } + else -> throw IllegalArgumentException("Unsupported meta type: $type") + } + } + is ShortMessage -> { + val command = message.status and 0xF0 + val channel = message.status and 0xF + + val status = when (command) { + ShortMessage.NOTE_ON -> NOTE_ON + ShortMessage.NOTE_OFF -> NOTE_OFF + ShortMessage.CONTROL_CHANGE -> CONTROL_CHANGE + ShortMessage.PITCH_BEND -> PITCH_WHEEL_CHANGE + ShortMessage.CHANNEL_PRESSURE -> CHANNEL_PRESSURE_CHANGE + ShortMessage.POLY_PRESSURE -> KEY_PRESSURE_CHANGE + ShortMessage.PROGRAM_CHANGE -> PROGRAM_CHANGE + else -> throw IllegalArgumentException("Unsupported command: $command") + } + + buf.writeByte(((channel xor prevChannel) shl 4) or status) + prevChannel = channel + + if ( + command == ShortMessage.NOTE_ON || + command == ShortMessage.NOTE_OFF || + command == ShortMessage.POLY_PRESSURE + ) { + val key = message.data1 + keyBuf.writeByte((key - prevKey) and 0x7F) + prevKey = key + } + + when (command) { + ShortMessage.NOTE_ON -> { + val onVelocity = message.data2 + onVelocityBuf.writeByte((onVelocity - prevOnVelocity) and 0x7F) + prevOnVelocity = onVelocity + } + ShortMessage.NOTE_OFF -> { + val offVelocity = message.data2 + offVelocityBuf.writeByte((offVelocity - prevOffVelocity) and 0x7F) + prevOffVelocity = offVelocity + } + ShortMessage.CONTROL_CHANGE -> { + val controller = message.data1 + controllerBuf.writeByte((controller - prevController) and 0x7F) + prevController = controller + + val value = message.data2 + val valueDelta = (value - prevValues[controller]) and 0x7F + + when (controller) { + BANK_SELECT_MSB, BANK_SELECT_LSB -> bankSelectBuf.writeByte(valueDelta) + MODULATION_WHEEL_MSB -> modulationWheelMsbBuf.writeByte(valueDelta) + MODULATION_WHEEL_LSB -> modulationWheelLsbBuf.writeByte(valueDelta) + CHANNEL_VOLUME_MSB -> channelVolumeMsbBuf.writeByte(valueDelta) + CHANNEL_VOLUME_LSB -> channelVolumeLsbBuf.writeByte(valueDelta) + PAN_MSB -> panMsbBuf.writeByte(valueDelta) + PAN_LSB -> panLsbBuf.writeByte(valueDelta) + NON_REGISTERED_MSB -> nonRegisteredMsbBuf.writeByte(valueDelta) + NON_REGISTERED_LSB -> nonRegisteredLsbBuf.writeByte(valueDelta) + REGISTERED_MSB -> registeredMsbBuf.writeByte(valueDelta) + REGISTERED_LSB -> registeredLsbBuf.writeByte(valueDelta) + DAMPER, PORTAMENTO, ALL_SOUND_OFF, RESET_CONTROLLERS, ALL_NOTES_OFF -> + otherKnownControllerBuf.writeByte(valueDelta) + else -> unknownControllerBuf.writeByte(valueDelta) + } + + prevValues[controller] = value + } + ShortMessage.PITCH_BEND -> { + val pitchWheel = message.data1 or (message.data2 shl 7) + val pitchWheelDelta = (pitchWheel - prevPitchWheel) and 0x3FFF + pitchWheelLsbBuf.writeByte(pitchWheelDelta and 0x7F) + pitchWheelMsbBuf.writeByte(pitchWheelDelta shr 7) + prevPitchWheel = pitchWheel + } + ShortMessage.CHANNEL_PRESSURE -> { + val channelPressure = message.data1 + channelPressureBuf.writeByte((channelPressure - prevChannelPressure) and 0x7F) + prevChannelPressure = channelPressure + } + ShortMessage.POLY_PRESSURE -> { + val keyPressure = message.data2 + keyPressureBuf.writeByte((keyPressure - prevKeyPressure) and 0x7F) + prevKeyPressure = keyPressure + } + ShortMessage.PROGRAM_CHANGE -> { + bankSelectBuf.writeByte(message.data1) + } + else -> throw IllegalStateException() + } + } + else -> throw IllegalArgumentException("Unsupported message type: ${message.javaClass.name}") + } + } + } + + buf.writeBytes(deltaTimeBuf) + buf.writeBytes(controllerBuf) + buf.writeBytes(otherKnownControllerBuf) + buf.writeBytes(keyPressureBuf) + buf.writeBytes(channelPressureBuf) + buf.writeBytes(pitchWheelMsbBuf) + buf.writeBytes(modulationWheelMsbBuf) + buf.writeBytes(channelVolumeMsbBuf) + buf.writeBytes(panMsbBuf) + buf.writeBytes(keyBuf) + buf.writeBytes(onVelocityBuf) + buf.writeBytes(unknownControllerBuf) + buf.writeBytes(offVelocityBuf) + buf.writeBytes(modulationWheelLsbBuf) + buf.writeBytes(channelVolumeLsbBuf) + buf.writeBytes(panLsbBuf) + buf.writeBytes(bankSelectBuf) + buf.writeBytes(pitchWheelLsbBuf) + buf.writeBytes(nonRegisteredMsbBuf) + buf.writeBytes(nonRegisteredLsbBuf) + buf.writeBytes(registeredMsbBuf) + buf.writeBytes(registeredLsbBuf) + buf.writeBytes(tempoBuf) + } finally { + buffers.release() + } + + buf.writeByte(sequence.tracks.size) + buf.writeShort(sequence.resolution and 0x7FFF) + } + + private class Buffers private constructor( + val deltaTimeBuf: ByteBuf, + val controllerBuf: ByteBuf, + val otherKnownControllerBuf: ByteBuf, + val keyPressureBuf: ByteBuf, + val channelPressureBuf: ByteBuf, + val pitchWheelMsbBuf: ByteBuf, + val modulationWheelMsbBuf: ByteBuf, + val channelVolumeMsbBuf: ByteBuf, + val panMsbBuf: ByteBuf, + val keyBuf: ByteBuf, + val onVelocityBuf: ByteBuf, + val unknownControllerBuf: ByteBuf, + val offVelocityBuf: ByteBuf, + val modulationWheelLsbBuf: ByteBuf, + val channelVolumeLsbBuf: ByteBuf, + val panLsbBuf: ByteBuf, + val bankSelectBuf: ByteBuf, + val pitchWheelLsbBuf: ByteBuf, + val nonRegisteredMsbBuf: ByteBuf, + val nonRegisteredLsbBuf: ByteBuf, + val registeredMsbBuf: ByteBuf, + val registeredLsbBuf: ByteBuf, + val tempoBuf: ByteBuf + ) { + fun release() { + deltaTimeBuf.release() + controllerBuf.release() + otherKnownControllerBuf.release() + keyPressureBuf.release() + channelPressureBuf.release() + pitchWheelMsbBuf.release() + modulationWheelMsbBuf.release() + channelVolumeMsbBuf.release() + panMsbBuf.release() + keyBuf.release() + onVelocityBuf.release() + unknownControllerBuf.release() + offVelocityBuf.release() + modulationWheelLsbBuf.release() + channelVolumeLsbBuf.release() + panLsbBuf.release() + bankSelectBuf.release() + pitchWheelLsbBuf.release() + nonRegisteredMsbBuf.release() + nonRegisteredLsbBuf.release() + registeredMsbBuf.release() + registeredLsbBuf.release() + tempoBuf.release() + } + + companion object { + fun alloc(alloc: ByteBufAllocator): Buffers { + val bufs = mutableListOf() + try { + for (i in 0..22) { + bufs += alloc.buffer() + } + + return Buffers( + bufs[0].retain(), + bufs[1].retain(), + bufs[2].retain(), + bufs[3].retain(), + bufs[4].retain(), + bufs[5].retain(), + bufs[6].retain(), + bufs[7].retain(), + bufs[8].retain(), + bufs[9].retain(), + bufs[10].retain(), + bufs[11].retain(), + bufs[12].retain(), + bufs[13].retain(), + bufs[14].retain(), + bufs[15].retain(), + bufs[16].retain(), + bufs[17].retain(), + bufs[18].retain(), + bufs[19].retain(), + bufs[20].retain(), + bufs[21].retain(), + bufs[22].retain() + ) + } finally { + bufs.forEach(ByteBuf::release) + } + } + } + } +}