mirror of
https://github.com/WerWolv/ImHex-Patterns.git
synced 2026-03-27 23:37:04 -05:00
pattern/midi: Support multiple tracks (#124)
* pattern/midi: Support multiple tracks * pattern/midi: Replace custom type used to index array
This commit is contained in:
@@ -1,72 +1,278 @@
|
||||
#include <std/core.pat>
|
||||
|
||||
#pragma MIME audio/midi
|
||||
#pragma endian big
|
||||
|
||||
using Delta = u8;
|
||||
using NoteValue = u8;
|
||||
using Velocity = u8;
|
||||
using EOF = u8;
|
||||
|
||||
// this is just for debugging midi file generation
|
||||
// I'm testing a known good file against a bad one
|
||||
// the file is hard coded in file format 0, and if
|
||||
// you're expecting meta events anywhere add a specific
|
||||
// call for those
|
||||
// Only supports format 0
|
||||
// https://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html
|
||||
|
||||
enum NoteEvent : u8 {
|
||||
NoteOn = 0x90,
|
||||
NoteOff = 0x80
|
||||
bool g_has_more_messages = true;
|
||||
bool g_has_more_tracks = true;
|
||||
|
||||
fn signed14(s16 n) {
|
||||
// Converts to 14-bit in range 0..16383. Note that MIDI data bytes
|
||||
// have their first (most significant) bit clear (0), thus each byte
|
||||
// is in range 0..127
|
||||
n = ((n & 0x7f) << 7) | ((n >> 8) & 0x7f);
|
||||
if (n >= 0) {
|
||||
n -= 0x2000;
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
enum MetaFlag : u16 {
|
||||
Footer = 0xFF2F,
|
||||
KeySigEvent = 0xFF59,
|
||||
TimeSigEvent = 0xFF58,
|
||||
TempoEvent = 0xFF51,
|
||||
TrackNameEvent = 0xFF03
|
||||
fn u32ify(u32 vlq) {
|
||||
// Converts from variable-length quantity to u32. These numbers are
|
||||
// represented 7 bits per byte, most significant bits first. All bytes
|
||||
// except the last have bit 7 set, and the last byte has bit 7 clear.
|
||||
// If the number is in range 0..127, it is thus represented exactly
|
||||
// as one byte.
|
||||
u32 n = vlq & 0x7f;
|
||||
if (vlq & 0x8000 == 0x8000) {
|
||||
n += ((vlq & 0x7f00) >> 8) * 0x80;
|
||||
}
|
||||
if (vlq & 0x800000 == 0x800000) {
|
||||
n += ((vlq & 0x7f0000) >> 8 * 2) * 0x80 * 0x80;
|
||||
}
|
||||
if (vlq & 0x80000000 == 0x80000000) {
|
||||
n += ((vlq & 0x7f000000) >> 8 * 3) * 0x80 * 0x80 * 0x80;
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
enum Status : u8 {
|
||||
NoteOff = 0x80 ... 0x8F,
|
||||
NoteOn = 0x90 ... 0x9F,
|
||||
PolyphonicAfterTouch = 0xA0 ... 0xAF,
|
||||
ControlChange = 0xB0 ... 0xBF,
|
||||
ProgramChange = 0xC0 ... 0xCF,
|
||||
ChannelAfterTouch = 0xD0 ... 0xDF,
|
||||
PitchWheel = 0xE0 ... 0xEF,
|
||||
SysEx = 0xF0,
|
||||
TimeCodeQtrFrame = 0xF1,
|
||||
SongPositionPointer = 0xF2,
|
||||
SongSelect = 0xF3,
|
||||
Undefined = 0xF4 ... 0xF5,
|
||||
TuneRequest = 0xF6,
|
||||
EndOfSysEx = 0xF7,
|
||||
TimingClock = 0xF8,
|
||||
Undefined = 0xF9,
|
||||
Start = 0xFA,
|
||||
Continue = 0xFB,
|
||||
Stop = 0xFC,
|
||||
Undefined = 0xFD,
|
||||
ActiveSensing = 0xFE,
|
||||
MetaEvent = 0xFF,
|
||||
};
|
||||
|
||||
Status g_next_status;
|
||||
|
||||
enum MetaType : u8 {
|
||||
SequenceNumber = 0x00,
|
||||
Text = 0x01,
|
||||
CopyrightNotice = 0x02,
|
||||
TrackName = 0x03,
|
||||
InstrumentName = 0x04,
|
||||
Lyric = 0x05,
|
||||
Marker = 0x06,
|
||||
CuePoint = 0x07,
|
||||
ChannelPrefix = 0x20,
|
||||
EndOfTrack = 0x2F,
|
||||
SetTempo = 0x51,
|
||||
SMPTEOffset = 0x54,
|
||||
TimeSignature = 0x58,
|
||||
KeySignature = 0x59,
|
||||
SequencerSpecific = 0x7F
|
||||
};
|
||||
|
||||
enum ControlChangeType : u8 {
|
||||
Bank_Select_MSB = 0,
|
||||
Modulation_Wheel_MSB = 1,
|
||||
Breath_Controller_MSB = 2,
|
||||
Undefined_MSB = 3,
|
||||
Foot_Pedal_MSB = 4,
|
||||
Portamento_Time_MSB = 5,
|
||||
Data_Entry_MSB = 6,
|
||||
Volume_MSB = 7,
|
||||
Balance_MSB = 8,
|
||||
Undefined_MSB = 9,
|
||||
Pan_MSB = 10,
|
||||
Expression_MSB = 11,
|
||||
Effect_Controller_1_MSB = 12,
|
||||
Effect_Controller_2_MSB = 13,
|
||||
Undefined_MSB = 14,
|
||||
Undefined_MSB = 15,
|
||||
General_Purpose_MSB = 16 ... 19,
|
||||
Undefined_MSB = 20 ... 31,
|
||||
Bank_Select_LSB = 32,
|
||||
Modulation_Wheel_LSB = 33,
|
||||
Breath_Controller_LSB = 34,
|
||||
Undefined_LSB = 35,
|
||||
Foot_Pedal_LSB = 36,
|
||||
Portamento_Time_LSB = 37,
|
||||
Data_Entry_LSB = 38,
|
||||
Volume_LSB = 39,
|
||||
Balance_LSB = 40,
|
||||
Undefined_LSB = 41,
|
||||
Pan_LSB = 42,
|
||||
Expression_LSB = 43,
|
||||
Effect_Controller_1_LSB = 44,
|
||||
Effect_Controller_2_LSB = 45,
|
||||
Undefined_LSB = 46,
|
||||
Undefined_LSB = 47,
|
||||
General_Purpose_LSB = 48 ... 51,
|
||||
Undefined_LSB = 52 ... 63,
|
||||
Damper_Pedal = 64,
|
||||
Portamento = 65,
|
||||
Sostenuto_Pedal = 66,
|
||||
Soft_Pedal = 67,
|
||||
Legato_FootSwitch = 68,
|
||||
Hold_2 = 69,
|
||||
Sound_Controller_1 = 70,
|
||||
Sound_Controller_2 = 71,
|
||||
Sound_Controller_3 = 72,
|
||||
Sound_Controller_4 = 73,
|
||||
Sound_Controller_6 = 75,
|
||||
Sound_Controller_7 = 76,
|
||||
Sound_Controller_8 = 77,
|
||||
Sound_Controller_9 = 78,
|
||||
Sound_Controller_10 = 79,
|
||||
General_Purpose_CC_Control = 80 ... 83,
|
||||
Portamento_CC_Control = 84,
|
||||
Undefined = 85 ... 87,
|
||||
High_Resolution_Velocity_Prefix = 88,
|
||||
Effect_2_Depth = 92,
|
||||
Effect_3_Depth = 93,
|
||||
Effect_4_Depth = 94,
|
||||
Effect_5_Depth = 95,
|
||||
Data_Increment = 96,
|
||||
Data_Decrement = 97,
|
||||
Non_Registered_Parameter_Number_LSB = 98,
|
||||
Non_Registered_Parameter_Number_MSB = 99,
|
||||
Registered_Parameter_Number_LSB = 100,
|
||||
Registered_Parameter_Number_MSB = 101,
|
||||
Undefined = 102 ... 119,
|
||||
All_Sound_Off = 120,
|
||||
Reset_All_Controllers = 121,
|
||||
Local_Switch = 122,
|
||||
All_Notes_Off = 123,
|
||||
Omni_Mode_Off = 124,
|
||||
Omni_Mode_On = 125,
|
||||
Mono_Mode = 126,
|
||||
Poly_Mode = 127,
|
||||
};
|
||||
|
||||
enum HeaderFlag : u32 {
|
||||
MThd = 0x4D546864
|
||||
};
|
||||
|
||||
enum TrackChunk : u32 {
|
||||
enum TrackFlag : u32 {
|
||||
MTrk = 0x4D54726B
|
||||
};
|
||||
|
||||
struct TimeSigEvent {
|
||||
Delta delta;
|
||||
MetaFlag flag;
|
||||
u16 numerator;
|
||||
u8 denominator;
|
||||
u8 ticks_per_click; // not used
|
||||
u8 thirty_second_notes_per_crotchet;
|
||||
struct VariableLengthQuantity<auto name> {
|
||||
if (std::mem::read_unsigned($, 1) & 0x80 == 0x80) {
|
||||
if (std::mem::read_unsigned($ + 1, 1) & 0x80 == 0x80) {
|
||||
if (std::mem::read_unsigned($ + 2, 1) & 0x80 == 0x80) {
|
||||
u32 value [[format("u32ify"), name(name)]];
|
||||
} else {
|
||||
u24 value [[format("u32ify"), name(name)]];
|
||||
}
|
||||
} else {
|
||||
u16 value [[format("u32ify"), name(name)]];
|
||||
}
|
||||
} else {
|
||||
u8 value [[format("u32ify"), name(name)]];
|
||||
}
|
||||
};
|
||||
|
||||
struct KeySigEvent {
|
||||
Delta delta;
|
||||
MetaFlag flag;
|
||||
u16 key;
|
||||
u8 mode;
|
||||
struct MessageData {
|
||||
match (u8(g_next_status) & 0xf0) {
|
||||
(Status::NoteOff | Status::NoteOn): {
|
||||
u8 note_number;
|
||||
u8 velocity;
|
||||
}
|
||||
(Status::PolyphonicAfterTouch): {
|
||||
u8 note_number;
|
||||
u8 amount;
|
||||
}
|
||||
(Status::ControlChange): {
|
||||
ControlChangeType cc_number;
|
||||
u8 value;
|
||||
}
|
||||
(Status::ProgramChange): {
|
||||
u8 program_number;
|
||||
}
|
||||
(Status::ChannelAfterTouch): {
|
||||
u8 amount;
|
||||
}
|
||||
(Status::PitchWheel): {
|
||||
s16 value [[format("signed14")]];
|
||||
}
|
||||
}
|
||||
match (g_next_status) {
|
||||
(Status::SysEx | Status::EndOfSysEx): {
|
||||
// "EndOfSysEx" can appear as the last data byte in a
|
||||
// system exclusive message, or as the starting byte in
|
||||
// multi-packet messages
|
||||
VariableLengthQuantity<"sysex_length"> sysex_length [[inline]];
|
||||
u8 sysex_data[sysex_length.value];
|
||||
}
|
||||
(Status::SongPositionPointer): {
|
||||
u8 lsb;
|
||||
u8 msb;
|
||||
}
|
||||
(Status::SongSelect): {
|
||||
u8 song_number;
|
||||
}
|
||||
(Status::MetaEvent): {
|
||||
MetaType meta_type;
|
||||
VariableLengthQuantity<"meta_length"> meta_length [[inline]];
|
||||
match (meta_type) {
|
||||
(MetaType::EndOfTrack): {
|
||||
g_has_more_messages = false;
|
||||
g_has_more_tracks = std::mem::read_unsigned($, 4) == le u32(TrackFlag::MTrk);
|
||||
}
|
||||
(MetaType::Text
|
||||
| MetaType::CopyrightNotice
|
||||
| MetaType::TrackName
|
||||
| MetaType::InstrumentName
|
||||
| MetaType::Lyric
|
||||
| MetaType::Marker
|
||||
| MetaType::CuePoint): {
|
||||
char text[meta_length.value];
|
||||
}
|
||||
(MetaType::TimeSignature): {
|
||||
u8 numerator;
|
||||
u8 denominator;
|
||||
u8 ticks_per_click;
|
||||
u8 thirty_second_notes_per_crotchet;
|
||||
}
|
||||
(MetaType::KeySignature): {
|
||||
u8 key;
|
||||
u8 mode;
|
||||
}
|
||||
(MetaType::SetTempo): {
|
||||
u24 micro_seconds_per_click; // default 1 million
|
||||
}
|
||||
(_): {
|
||||
u8 meta_data[meta_length.value];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct TempoEvent {
|
||||
Delta delta;
|
||||
MetaFlag flag;
|
||||
u32 micro_seconds_per_click; // default 1 million
|
||||
};
|
||||
struct Message {
|
||||
VariableLengthQuantity<"delta"> delta [[inline]];
|
||||
|
||||
struct TrackNameEvent {
|
||||
Delta delta;
|
||||
MetaFlag flag;
|
||||
u8 length;
|
||||
u8 text;
|
||||
};
|
||||
// Status bytes of MIDI channel messages may be omitted if the
|
||||
// preceding event is a MIDI channel message with the same status
|
||||
if (std::mem::read_unsigned($, 1) > 0x7f) {
|
||||
Status status;
|
||||
g_next_status = status;
|
||||
}
|
||||
|
||||
struct Note {
|
||||
Delta delta;
|
||||
NoteEvent ne;
|
||||
NoteValue note;
|
||||
Velocity vel;
|
||||
MessageData data [[inline]];
|
||||
};
|
||||
|
||||
struct HeaderChunk {
|
||||
@@ -75,21 +281,18 @@ struct HeaderChunk {
|
||||
u16 mode;
|
||||
u16 num_tracks;
|
||||
u16 ticks_per_quarter;
|
||||
TrackChunk chunk;
|
||||
u32 track_length;
|
||||
};
|
||||
|
||||
struct Footer {
|
||||
Delta d;
|
||||
MetaFlag m;
|
||||
EOF eof;
|
||||
struct TrackChunk {
|
||||
TrackFlag flag;
|
||||
u32 length;
|
||||
g_has_more_messages = true;
|
||||
Message messages[while(g_has_more_messages)];
|
||||
};
|
||||
|
||||
struct MidiFile {
|
||||
HeaderChunk header;
|
||||
// whatever meta flags can be in here
|
||||
Note notes[12]; //however many notes you're looking at
|
||||
Footer f;
|
||||
TrackChunk tracks[while(g_has_more_tracks)];
|
||||
};
|
||||
|
||||
MidiFile midi_file @ 0x00;
|
||||
|
||||
BIN
tests/patterns/test_data/midi.hexpat.mid
Normal file
BIN
tests/patterns/test_data/midi.hexpat.mid
Normal file
Binary file not shown.
Reference in New Issue
Block a user