From 032f3c7c013c117dca5b684af88f81b73e1fd0d4 Mon Sep 17 00:00:00 2001 From: qufb <93520295+qufb@users.noreply.github.com> Date: Sun, 11 Jun 2023 12:09:23 +0100 Subject: [PATCH] pattern/midi: Support multiple tracks (#124) * pattern/midi: Support multiple tracks * pattern/midi: Replace custom type used to index array --- patterns/midi.hexpat | 317 +++++++++++++++++++---- tests/patterns/test_data/midi.hexpat.mid | Bin 0 -> 7590 bytes 2 files changed, 260 insertions(+), 57 deletions(-) create mode 100644 tests/patterns/test_data/midi.hexpat.mid diff --git a/patterns/midi.hexpat b/patterns/midi.hexpat index f96a565..d654763 100644 --- a/patterns/midi.hexpat +++ b/patterns/midi.hexpat @@ -1,72 +1,278 @@ +#include + #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 { + 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; diff --git a/tests/patterns/test_data/midi.hexpat.mid b/tests/patterns/test_data/midi.hexpat.mid new file mode 100644 index 0000000000000000000000000000000000000000..ac41827233e29b237dc55acc98a646e251ed1ff5 GIT binary patch literal 7590 zcmeI1TTfd@5XYC&yHY8tl-J0-1f+yZ5eRJ1i3t}Qu(^{!t|x&?tr9_nsH(mds3KMB zgU>_1K|etI)Q?Ah|J^gTu`whBDS?1w{m)!?c6QFp+LTv**mTZ)?%G^?+i&HSotIF{ z?y&uvjqR=7o$Xhd-JRzfFWq6=R~tLuKi}Qn$z>pJ7Uotnr5|7Yy0E>qnd$HOZn&pE zQ~Ps=YoE5afBU67Tx`Gg-=I6}b;0%Zzj59E^G`YV&lj8SaQze3t(2m;QgW^`3(kVA z=mPQr@)>XjEP_RFGTO^mCS6<^g)s^v59YlfhVLo`DHKpJ!2}8uUJyJMrBJL>XcaJ0h?|qM%G# ze$2Hk)s(>CdM{sHV-4494n{3Qk~&Tl$w{Z$D7S4k#&AhbnJk@jxYb?fqfqdwYaP{C zO6xqs(YeaF=MDJUS0Mzw8=QF?FUbuSYU8Cj2!e97cRekWK3_hi$e)o(9M-*1r__nr{rl0MYb{Mt9dH=8ivB@^Yl2VP>r5~mO(X4D}!2@ z?1yT%>b4jriAnEEoh8w08wGr-ZxlhLWFA!Xwzkn0gVAPZbff(Ye&pZeC1~|AqgS9; zpqIgA(6VUqGW_tK2D?UoCO3ZL`WG#J%a85L=vnd*Iz-5W<^ApDX=zvsu@Y&joz@Dm zKf#*8*=X}`#cO@E`Ziy#NySTkQV;u32b9j1(BJm3KgPp7(A^*mm?>~N`aNHrGSjp+ zwd@%LlYGWBVbmAZjWwR8Vd_IGV+=)Dq$Z~9o7 z>+1SiyK!trSm^*GWSR-1(D(TG>z3}0_xOZsmxQb}_g93N!8IB-l7nv;=3HTp zDpls=)EsK6%W6hBRQ$43I!?=BHGh`7viCwT%CMM0gJ4WUw}n})qxq};tHsr^w62uy zLaTG#87>f}w}(_;cfu9KBTBnus&ETmu-6A=-bg%p%!WvO}|{vNN_ zR9t@#6{p}1{5Ob7Hp*AB-Y%bB{lj@o!dC=EP_dW5P#_@z#xKBE9kkM4M6R393aDAW z2Cjkk!TWXmW#};DkJo>K{0aIo7=xSOCint;QOAD<{S5jk_!Km|$Iy?VAAyg+hu}kS z9b5-3t_RQ$pe?>t=v8Qo-{M??A8vLb-Ua9d^yWd`jpo2Pung9JmuPI3A7e9&FWgtL z_-fKMeTB6>?taqSO&tN3hl`A#POY{-c5I6*K^Grq+)NLMa*9k_TM_NaF%>MYbAj!ME zd=eg9!jMmNrAp`$2@|Ci;~aN>j;`3@@#o=^Toe+QQv&&9Q<}-+=^<#-QIbt>kUR`} zeU(V?5$unrO+Sa7d_*AA5#Pttrf&zJ7!-^6vR5ox-{ga-T>iwjSZngL#T#CM)Ax7u zm1)-JT9EXod+t~47AL4!Elw-uMw%bE60R5&xpXN+B|Gn6!r1 zRdtkbIgu_wH+t;BiQ6 zYhA5BY{3jejJUsCi^|@ApG}L!ubNYJiZl%i>qVCzU2==pLv!$!KGd%EtY^=AN4gvP z&eVS=K08x?dY1oprR86fU%oW`yHfvp_PjTs?_e+eJE?s=E&m;<{|@;~pD!Br5O)vp ze<;{XJiWy8rq~x9IBrsOS#{R|wOe)90SCK7REK2YW}qf70(qD~8WuK9jtEXN(V)-( s@^ePh!e1TEvdW*evN<|}_cQPD*2>~n1E6^Tba3R|p`d&R;h(4f1MAU7C;$Ke literal 0 HcmV?d00001