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 <gpe@openrs2.org>
bzip2
Graham 3 years ago
parent 739d5faea7
commit c79ce805df
  1. 538
      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<ByteBuf>()
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)
}
}
}
}
}
Loading…
Cancel
Save