Skip to content

Instantly share code, notes, and snippets.

@Hadyn
Created March 25, 2016 19:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Hadyn/2a5bcc3084d463f8423e to your computer and use it in GitHub Desktop.
Save Hadyn/2a5bcc3084d463f8423e to your computer and use it in GitHub Desktop.
package com.jagex;
import com.jagex.io.Buffer;
/**
* Created by hadyn on 5/1/2015.
*/
public class MidiCodec {
private static final int EVENT_NOTE_ON = 0x0;
private static final int EVENT_NOTE_OFF = 0x1;
private static final int EVENT_CONTROL_CHANGE = 0x2;
private static final int EVENT_PITCH_BEND = 0x3;
private static final int EVENT_CHANNEL_PRESSURE = 0x4;
private static final int EVENT_KEY_PRESSURE = 0x5;
private static final int EVENT_PROGRAM_CHANGE = 0x6;
private static final int EVENT_END_OF_TRACK = 0x7;
private static final int EVENT_SET_TEMPO = 0x07 | 0x10;
private static final int CONTROLLER_BANK_SELECT = 0;
private static final int CONTROLLER_BANK_SELECT_LSB = 32;
private static final int CONTROLLER_MODULATION_WHEEL = 1;
private static final int CONTROLLER_MODULATION_WHEEL_LSB = 33;
private static final int CONTROLLER_CHANNEL_VOLUME = 7;
private static final int CONTROLLER_CHANNEL_VOLUME_LSB = 39;
private static final int CONTROLLER_PAN = 10;
private static final int CONTROLLER_PAN_LSB = 42;
private static final int CONTROLLER_NRPN = 99;
private static final int CONTROLLER_NRPN_LSB = 98;
private static final int CONTROLLER_RPN = 101;
private static final int CONTROLLER_RPN_LSB = 100;
private static final int CONTROLLER_DAMPER_PEDAL = 64;
private static final int CONTROLLER_PORTAMENDO = 65;
private static final int CONTROLLER_ALL_SOUND_OFF = 120;
private static final int CONTROLLER_RESET_ALL_CONTROLLERS = 121;
private static final int CONTROLLER_ALL_NOTES_OFF = 123;
private static final int TYPE_END_OF_TRACK = 0x2f;
private static final int TYPE_SET_TEMPO = 0x51;
byte[] data;
public MidiCodec() {}
void decode(Buffer input, boolean strict) {
// Read the footer which contains the amount
// of tracks and time division
input.position(input.capacity() - 3);
int tracks = input.getUInt8();
int division = input.getUInt16();
int length = 14 + tracks * 10;
input.position(0);
int tempoCount = 0;
int controlChangeCount = 0;
int noteOnCount = 0;
int noteOffCount = 0;
int pitchBendCount = 0;
int channelPressureCount = 0;
int keyPressureCount = 0;
int programChangeCount = 0;
// Parse and count each of the event opcodes
int i;
int j;
int track = 0;
while(track < tracks) {
i = -1;
while(true) {
j = input.getUInt8();
if(strict || i != j) {
++length;
}
i = j & 0xf;
if(j == EVENT_END_OF_TRACK) {
++track;
break;
}
if(j == EVENT_SET_TEMPO) {
++tempoCount;
} else if(i == EVENT_NOTE_ON) {
++noteOnCount;
} else if(i == EVENT_NOTE_OFF) {
++noteOffCount;
} else if(i == EVENT_CONTROL_CHANGE) {
++controlChangeCount;
} else if(i == EVENT_PITCH_BEND) {
++pitchBendCount;
} else if(i == EVENT_CHANNEL_PRESSURE) {
++channelPressureCount;
} else if(i == EVENT_KEY_PRESSURE) {
++keyPressureCount;
} else {
if(i != EVENT_PROGRAM_CHANGE) {
throw new RuntimeException();
}
++programChangeCount;
}
}
}
// Append the amount of data for each event
length += 5 * tempoCount;
length += 2 * (noteOnCount + noteOffCount + controlChangeCount + pitchBendCount + keyPressureCount);
length += channelPressureCount + programChangeCount;
// TODO: Eventually remove this shit
int eventLengthOffset = input.position();
// Parse the delta time for each event
int deltaTimeStart = input.position();
int eventCount = tracks + tempoCount + controlChangeCount + noteOnCount +
noteOffCount + pitchBendCount + channelPressureCount +
keyPressureCount + programChangeCount;
for(int l = 0; l < eventCount; l++) {
input.getVariableLength();
}
// Append the data length of the event delta times
// to the length of the decompressed output
length += input.position() - deltaTimeStart;
// Mark where the controller data begins
int controllerOffset = input.position();
// Count each of the controller changes
int modulationWheelCount = 0;
int modulationWheelMsbCount = 0;
int channelVolumeCount = 0;
int channelVolumeLsbCount = 0;
int panCount = 0;
int panLsbCount = 0;
int nrpnCount = 0;
int nrpnLsbCount = 0;
int rpnCount = 0;
int rpnLsbCount = 0;
int var26 = 0;
int toggleCount = 0;
int controller = 0;
for(int k = 0; k < controlChangeCount; k++) {
controller = controller + input.getUInt8() & 127;
if(controller != CONTROLLER_BANK_SELECT && controller != CONTROLLER_BANK_SELECT_LSB) {
if(controller == CONTROLLER_MODULATION_WHEEL) {
++modulationWheelCount;
} else if(controller == CONTROLLER_MODULATION_WHEEL_LSB) {
++modulationWheelMsbCount;
} else if(controller == CONTROLLER_CHANNEL_VOLUME) {
++channelVolumeCount;
} else if(controller == CONTROLLER_CHANNEL_VOLUME_LSB) {
++channelVolumeLsbCount;
} else if(controller == CONTROLLER_PAN) {
++panCount;
} else if(controller == CONTROLLER_PAN_LSB) {
++panLsbCount;
} else if(controller == CONTROLLER_NRPN) {
++nrpnCount;
} else if(controller == CONTROLLER_NRPN_LSB) {
++nrpnLsbCount;
} else if(controller == CONTROLLER_RPN) {
++rpnCount;
} else if(controller == CONTROLLER_RPN_LSB) {
++rpnLsbCount;
} else if(controller != CONTROLLER_DAMPER_PEDAL && controller != CONTROLLER_PORTAMENDO &&
controller != CONTROLLER_ALL_SOUND_OFF && controller != CONTROLLER_RESET_ALL_CONTROLLERS &&
controller != CONTROLLER_ALL_NOTES_OFF) {
++toggleCount;
} else {
++var26;
}
} else {
++programChangeCount;
}
}
// Calculate each of the offsets for all of the data to encode
// the midi file with.
int opcodeOffset = 0;
int toggleOffset = input.position();
input.skip(var26);
int keyPressureOffset = input.position();
input.skip(keyPressureCount);
int channelPressureOffset = input.position();
input.skip(channelPressureCount);
int var33 = input.position();
input.skip(pitchBendCount);
int modulationWheelOffset = input.position();
input.skip(modulationWheelCount);
int channelVolumeOffset = input.position();
input.skip(channelVolumeCount);
int panOffset = input.position();
input.skip(panCount);
int keyOffset = input.position();
input.skip(noteOnCount + noteOffCount + keyPressureCount);
int onVelocityOffset = input.position();
input.skip(noteOnCount);
int effectOffset = input.position();
input.skip(toggleCount);
int offVelocityOffset = input.position();
input.skip(noteOffCount);
int modulationWheelLsbOffset = input.position();
input.skip(modulationWheelMsbCount);
int channelVolumeLsbOffset = input.position();
input.skip(channelVolumeLsbCount);
int panLsbOffset = input.position();
input.skip(panLsbCount);
int programChangeOffset = input.position();
input.skip(programChangeCount);
int var45 = input.position();
input.skip(pitchBendCount);
int nrpnOffset = input.position();
input.skip(nrpnCount);
int nrpnLsbOffset = input.position();
input.skip(nrpnLsbCount);
int rpnOffset = input.position();
input.skip(rpnCount);
int rpnLsbOffset = input.position();
input.skip(rpnLsbCount);
int tempoSetOffset = input.position();
input.skip(tempoCount * 3);
// Create a buffer to put the encoded midi file
data = new byte[length];
Buffer output = new Buffer(data);
// Encode the header chunk to the output buffer, big endian
// 0: int32 -> 'MThd'
// 4: int32 -> chunk_len
// 8: int16 -> format
// 10: int16 -> tracks
// 12: int16 -> division
//
// format notes
// ------------------------------------------------------------
// format 0 -> Single track
// format 1 -> One or more simultaneous tracks
// format 2 -> One or more independent tracks
//
// division notes
// ------------------------------------------------------------
// For the division value the MSB determines what the division
// value defines, if the MSB
output.putInt32(0x4d546864);
output.putInt32(6);
output.putInt16(tracks > 1 ? 1 : 0);
output.putInt16(tracks);
output.putInt16(division);
// Position the buffer to read the event lengths
input.position(eventLengthOffset);
// Encode each of the track chunks to the output buffer
// int32 -> 'MTtk'
// varlen -> delta_time
int[] controllers = new int[128];
controller = 0;
int channel = 0;
int key = 0;
int onVelocity = 0;
int offVelocity = 0;
int pitch = 0;
int channelPressure = 0;
int keyPressure = 0;
// Write each of the tracks
track = 0;
while(track < tracks) {
output.putInt32(0x4d54726b);
output.skip(4); // Skip writing the length until the end
int chunkStart = output.position();
int type = -1;
// Write each of the events
while(true) {
int deltaTimeOffset = input.getVariableLength();
output.putVariableLength(deltaTimeOffset);
int opcode = input.getUInt8(opcodeOffset++);
boolean updated = strict || type != opcode;
type = opcode & 0xf;
// Meta events
// --------------------------------------------------------------------------------
// Both 'end_of_track' and 'set_tempo' are meta events and are denoted with the first
// nibble of their opcode being 0b0111, however the set tempo is annotated with
// the 0x80 flag to differentiate the opcodes so that the meta event byte header
// isn't written on change.
//
// Format:
//
// 0xff <meta_type : int8> <length : varlen> <data : int8[length]>
//
//
// System exclusive events
// -------------------------------------------------------------------------------
// In this implementation system exclusive events are not encoded.
//
// Midi event
// -------------------------------------------------------------------------------
// Note on, note off, control change, pitch bend, channel pressure, key pressure,
// and program change are midi events. The LS nibble of the encoded opcode represents
// the event type while opcode may have encoded the 0x10 flag which represents a change
// in the target channel when encoding the event.
//
// When encoding events unless in strict mode the codec will not encode the event type, this
// will only occur if the event type changes or target channel is updated.
//
// Opcode format:
//
// xxxctttt
//
// - x is unused
// - c is the channel control, if it is set to one it flips the channel (0 or 1) currently being used.
// - t is the event type.
//
// Encode an end of track event
if(opcode == EVENT_END_OF_TRACK) {
if(updated) {
output.putInt8(0xff);
}
output.putInt8(TYPE_END_OF_TRACK); // Event type
output.putInt8(0); // Length
// Encode the length of the track to the buffer and stop parsing the
// current track
output.putIntLength(output.position() - chunkStart);
track++;
break;
}
if(opcode == EVENT_SET_TEMPO) {
if(updated) {
output.putInt8(0xff);
}
output.putInt8(TYPE_SET_TEMPO); // Type
output.putInt8(3); // Length
output.putInt8(input.getUInt8(tempoSetOffset++));
output.putInt8(input.getUInt8(tempoSetOffset++));
output.putInt8(input.getUInt8(tempoSetOffset++));
} else {
channel ^= opcode >> 4;
if(type == EVENT_NOTE_ON) {
// Note on
if(updated) {
output.putInt8(0b10010000 + channel);
}
key += input.getInt8(keyOffset++);
onVelocity += input.getInt8(onVelocityOffset++);
output.putInt8(key & 127);
output.putInt8(onVelocity & 127);
} else if(type == EVENT_NOTE_OFF) {
// Note off
if(updated) {
output.putInt8(0b10000000 + channel);
}
key += input.getInt8(keyOffset++);
offVelocity += input.getInt8(offVelocityOffset++);
output.putInt8(key & 127);
output.putInt8(offVelocity & 127);
} else if(type == EVENT_CONTROL_CHANGE) {
// Controller change
if(updated) {
output.putInt8(0b10110000 + channel);
}
controller = controller + input.getInt8(controllerOffset++) & 127;
output.putInt8(controller);
byte delta;
if(controller != CONTROLLER_BANK_SELECT && controller != CONTROLLER_BANK_SELECT_LSB) {
if(controller == CONTROLLER_MODULATION_WHEEL) {
delta = input.getInt8(modulationWheelOffset++);
} else if(controller == CONTROLLER_MODULATION_WHEEL_LSB) {
delta = input.getInt8(modulationWheelLsbOffset++);
} else if(controller == CONTROLLER_CHANNEL_VOLUME) {
delta = input.getInt8(channelVolumeOffset++);
} else if(controller == CONTROLLER_CHANNEL_VOLUME_LSB) {
delta = input.getInt8(channelVolumeLsbOffset++);
} else if(controller == CONTROLLER_PAN) {
delta = input.getInt8(panOffset++);
} else if(controller == CONTROLLER_PAN_LSB) {
delta = input.getInt8(panLsbOffset++);
} else if(controller == CONTROLLER_NRPN) {
delta = input.getInt8(nrpnOffset++);
} else if(controller == CONTROLLER_NRPN_LSB) {
delta = input.getInt8(nrpnLsbOffset++);
} else if(controller == CONTROLLER_RPN) {
delta = input.getInt8(rpnOffset++);
} else if(controller == CONTROLLER_RPN_LSB) {
delta = input.getInt8(rpnLsbOffset++);
} else if(controller != CONTROLLER_DAMPER_PEDAL && controller != CONTROLLER_PORTAMENDO &&
controller != CONTROLLER_ALL_SOUND_OFF && controller != CONTROLLER_RESET_ALL_CONTROLLERS &&
controller != CONTROLLER_ALL_NOTES_OFF) {
delta = input.getInt8(effectOffset++);
} else {
delta = input.getInt8(toggleOffset++);
}
} else {
delta = input.getInt8(programChangeOffset++);
}
int value = delta + controllers[controller];
controllers[controller] = value;
output.putInt8(value & 127);
} else if(type == EVENT_PITCH_BEND) {
// Pitch bend change
if(updated) {
output.putInt8(0b11100000 + channel);
}
pitch += input.getInt8(var45++);
pitch += input.getInt8(var33++) << 7;
output.putInt8(pitch & 127);
output.putInt8(pitch >> 7 & 127);
} else if(type == EVENT_CHANNEL_PRESSURE) {
// Channel pressure (after touch)
if(updated) {
output.putInt8(0b11010000 + channel);
}
channelPressure += input.getInt8(channelPressureOffset++);
output.putInt8(channelPressure & 127);
} else if(type == EVENT_KEY_PRESSURE) {
// Key pressure
if(updated) {
output.putInt8(0b10100000 + channel);
}
key += input.getInt8(keyOffset++);
keyPressure += input.getInt8(keyPressureOffset++);
output.putInt8(key & 127);
output.putInt8(keyPressure & 127);
} else {
if(type != EVENT_PROGRAM_CHANGE) {
throw new RuntimeException("Unknown event type");
}
// Program change
if(updated) {
output.putInt8(0b11000000 + channel);
}
output.putInt8(input.getInt8(programChangeOffset++));
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment