(* :Title: Musica *) (* :Author: Costis Merziotis *) (* :Summary: This package contains functions that import and export MIDI \ files. Also included are some functions that facilitate the algorithmic creation \ of MIDI files. *) (* :Package Version: 0.9beta*) (* :History: This is the first version of the package *) (* :Keywords: music, midi *) (* :Mathematica Version: 4.0 *) BeginPackage["Musica`", "Utilities`BinaryFiles`"] ToNoteNumber::usage = "ToNoteNumber[Note] gives the midi note number \ \ corresponding to the string Note which can range from C0 to G10." ToNote::usage = "ToNote[Number] gives the note name corresponding to Number." Durations::usage = "Durations[PPQ] is a set of rules that transforms \ \ durations to their corresponding ticks according to the PPQ given. PPQ is \ the \ number of ticks per quarter note." SortEventList::usage = "SortEventList[EventList,PPQ] sorts EventList and \ \ replaces note durations with their number values according to the PPQ \ given. \ PPQ is the number of ticks per quarter note and defaults to 192 if \ not \ given." ExportMIDI::usage = "ExportMIDI[EventList,Filename,options] exports EventList \ \ to a format 0 MIDI file named Filename. Possible options are PPQ and \ \ CustomEvents." ImportMIDI::usage = "ImportMIDI[Filename,options] returns an eventlist \ \ containing all the events of the format 0 MIDI file named Filename. Posible \ \ options are NoteConversion, ToCustomEvents and FinalFunction." GenerateEventList::usage = \ \ "GenerateEventList[Event1,Event2,...,{i,imin,imax},{j,jmin,jmax},...,PPQ] \ \ gives a sorted eventlist of the events Event1,Event2,... using the \ iterators \ i,j,... just like the built in Table command. The PPQ is the \ number of ticks \ per quarter note and defaults to 192 if not given. Note \ durations are \ substituted by their respective number values using PPQ" EventSequence::usage = "EventSequence[Event,Step,{i,imin,imax},PPQ] gives a \ \ sorted eventlist of the sequence of Event over the iterator i where step is \ \ the interval between two succesive events.Step can be a function of i too. \ \ The PPQ is the number of ticks per quarter note and defaults to 192 if not \ \ given. Note durations are substituted by their respective number values \ using \ PPQ" whole::usage = "Whole note duration." half::usage = "Half note duration." halftriplet::usage = "Half triplet note duration." quarter::usage = "Quarter note duration." quartertriplet::usage = "Quarter triplet note duration." eighth::usage = "Eighth note duration." eighthtriplet::usage = "Eighth triplet note duration." sixteenth::usage = "Sixteenth note duration." sixteenthtriplet::usage = "Sixteenth triplet note duration." thirtysecond::usage = "Thirtysecond note duration." thirtysecondtriplet::usage = "Thirtysecond triplet note duration." tick::usage = "1 tick duration." Note::usage = "Note[Note Number or Note Name,Duration,Velocity,Channel] is a \ \ note message. In reality this corresponds to one Note On and one Note Off \ \ message." NoteOn::usage = "NoteOn[Note Number,Velocity,Channel] is a Note On message." NoteOff::usage = "NoteOff[Note Number,Velocity,Channel] is a Note Off \ \ message. The equivalent NoteOn[Note Number or Note Name,0,Channel] is \ \ prefered however because it minimizes MIDI file length due to running \ \ status." KeyAftertouch::usage = "KeyAftertouch[Note Number,Value,Channel] is a Key \ \ Aftertouch message." Controller::usage = "Controller[Number,Value,Channel] is a Controller \ \ message." ProgramChange::usage = "ProgramChange[Program Number,Channel] is a Program \ \ Change message. Program Number ranges from 1 to 128." PitchBend::usage = "PitchBend[Value,Channel] is a Pitch Bend message. Value \ \ ranges from -8192 to 8191." ChannelAftertouch::usage = "ChannelAftertouch[Value,Channel] is a Channel \ \ Aftertouch message." Sysex::usage = "Sysex[Data List] is a Sysex message. The Data List is a list \ \ of bytes starting with 16^^F0 and ending with 16^^F7. The first byte can \ also \ be 16^^F7 for use with multi packet Sysex messages or escapes \ (advanced use \ only)." MetaEvent::usage = "MetaEvent[Type Number,Data List] is a Metaevent message. \ \ Data list is a list of bytes. This is for advanced use only, most popular \ \ Metaevents can be addressed with their respective names" Tempo::usage = "Tempo[BPM] is a Tempo metaevent. BPM is a real number \ \ representing beats per measure" TextEvent::usage = "TextEvent[String] is a Text metaevent. " Copyright::usage = "Copyright[String] is a Copyright Notice metaevent." TrackName::usage = "TrackName[String] is a Track Name metaevent." InstrumentName::usage = "InstrumentName[String] is a Instrument Name \ \ metaevent" Lyric::usage = "Lyric[String] is a Lyric metaevent." Marker::usage = "Marker[String] is a Marker metaevent." CuePoint::usage = "CuePoint[String] is a Cue Point metaevent." TimeSignature::usage = "TimeSignature[Nominator,Denominator] is a Time \ \ Signature metaevent. TimeSignature[Nominator,Denominator,Clocks per \ Metronome \ Click,Notated 32nds per Quarter Note] is the full Time Signature \ metaevent \ (advanced use only)." KeySignature::usage = "KeySignature[Number of Sharps or Flats,Mm] is a Key \ \ Signature metaevent. Range of first argument is -7 to 7, positive values \ for \ sharps and negative for flats. Mm is 0 for Major and 1 for Minor." EndOfTrack::usage = "EndOfTrack is the End of Track metaevent. It should be \ \ used only at the end of the eventlist." MIDIChannel::usage = "MIDIChannel[Eventlist,Channel] returns all events \ \ present in the specified Channel of EventList." ChannelEvents::usage = "ChannelEvents[Eventlist] returns all channel events \ \ present in EventList." MetaEvents::usage = "MetaEvents[EventList] returns all metaevents present in \ \ EventList." SysexEvents::usage = "SysexEvents[Eventlist] returns all sysex events present \ \ in EventList." PPQ::usage = "PPQ is an option of ExportMIDI that specifies the number of \ \ ticks per quarter note." CustomEvents::usage = "CustomEvents is an option of ExportMIDI that specifies \ \ the rules to be applied on the eventlist before exporting it. It is used to \ \ define custom events that are automatically transformed to a set of basic \ \ ones. The rules are applied once." NoteConversion::usage = "NoteConversion is an option of ImportMIDI that \ \ specifies whether NoteOn and NoteOff events are to be transformed to the \ more \ versatile Note event. If you don't want this conversion to be done \ change its \ value to False, otherwise leave it to True." ToCustomEvents::usage = "ToCustomEvents is an option of ImportMIDI that \ \ specifies the rules to be applied after a file has been imported, so that \ \ basic events are converted to custom ones. These rules are applied \ \ repeatedly." FinalFunction::usage = "Final function is a function that is applied to the \ \ imported eventlist. You can use it to convert basic events to custom ones." NoteRule::usage = "Transforms Note messages to the corresponding NoteOn and \ NoteOff events." Begin["`Private`"] NotesList = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", \ "B"}; MidiNotes = Flatten[Transpose[ Outer[StringJoin, NotesList, Map[ToString, Range[0, 10]]]]]; ToNoteNumber[x_] := Position[MidiNotes, x][[1, 1]] - 1 ToNote[x_] := MidiNotes[[x + 1]] MIDIChannel[l_List, c_Integer] := Cases[l, {_, s_Note | s_NoteOn | s_NoteOff | s_Controller | s_ChannelAftertouch | s_KeyAftertouch | s_ProgramChange | s_PitchBend} /; Last[s] == c] MetaEvents[l_List] := DeleteCases[ l, {_, _Note | _NoteOn | _NoteOff | _Controller | _ChannelAftertouch | \ \ _KeyAftertouch | _ProgramChange | _PitchBend | _Sysex}] ChannelEvents[l_List] := Cases[l, {_, _Note | _NoteOn | _NoteOff | _Controller | _ChannelAftertouch \ \ | _KeyAftertouch | _ProgramChange | _PitchBend}] SysexEvents[l_List] := Cases[l, {_, _Sysex}] ToVariableLength[x_] := Map[FromDigits[#, 2] &, Partition[ Insert[Insert[IntegerDigits[x, 2, 28], 0, {-8}], 1, {{-16}, {-23}, {-30}}], 8]] //. {128 , y__} -> {y} FromVariableLength[{x___, y_}] := Plus @@ MapIndexed[((#1 - 128) 128^#2[[1]]) &, Reverse[{x}]] + y Durations[PPQ_] := {whole -> 4PPQ, half -> 2PPQ, halftriplet -> (4PPQ/3), quarter -> PPQ, quartertriplet -> (2PPQ /3), eighth -> (PPQ/2), eighthtriplet -> (PPQ/3), sixteenth -> (PPQ/4), sixteenthtriplet -> (PPQ/6), thirtysecond -> (PPQ/8), thirtysecondtriplet -> (PPQ/12), tick -> 1} SortEventList[eventlist_, PPQ_:192] := Reverse[Sort[ eventlist /. (Note[name_String, rest__] :> Note[ToNoteNumber[name], rest]) /. Durations[PPQ], Greater[#1[[1]], #2[[1]]] &]] NoteRule = {{t_, Note[number_Integer, duration_, velocity_, channel_]} :> (Sequence[{t, NoteOn[number, velocity, channel]}, {t + duration, NoteOn[number, 0, channel]}]), {t_, Note[name_String, duration_, velocity_, channel_]} :> (Sequence[{t, NoteOn[ToNoteNumber[name], velocity, channel]}, {t + duration, NoteOn[ToNoteNumber[name], 0, channel]}])}; ToDeltaTime[l_] := Transpose[{Map[ToVariableLength, Drop[Append[l[[All, 1]], 0] - Prepend[l[[All, 1]], 0], -1]], l[[All, 2]]}] FromDeltaTime[l_] := Module[{s}, s = 0; Table[s += l[[i]], {i, Length[l]}]] TrackLength[x_] := Reverse[Apply[Plus[#1, 16 #2 ] &, Partition[Reverse[IntegerDigits[x, 16, 8]], 2], 1]] ToRunningStatus[l_] := Module[{rs, i, pos}, (rs = -1); (i = 1); (pos = {}); Do[If[(l[[i, 2]] == rs) && (128 <= l[[i, 2]] <= 239), AppendTo[pos, {i}], (rs = l[[i, 2]])], {i, Length[l]}]; MapAt[Drop[#, {2}] &, l, pos] ] NoteOn2Note[l_] := Module[{i, j, len}, (i = 0); (len = Length[l]); Nest[i++; (While[(#[[i, 2, 0]] =!= NoteOn) && (i <= len), i++]; (j = i + 1); While[! MatchQ[#[[j, 2]], NoteOn[#[[i, 2, 1]], 0, #[[i, 2, 3]]]], j++]; (len -= 1); Drop[ReplacePart[#, Note[#[[i, 2, 1]], #[[j, 1]] - #[[i, 1]], #[[i, 2, 2]], #[[i, \ 2, 3]]], {i, 2}], {j}]) &, l /. NoteOff[number_, _, channel_] :> NoteOn[number, 0, channel], Count[l, NoteOn[_, x_ /; x > 0, _], 2]]] EventRules = {NoteOn[number_, velocity_, channel_] -> Sequence[144 + channel - 1, number, velocity], Controller[number_, value_, channel_] -> Sequence[176 + channel - 1, number, value], PitchBend[value_, channel_] :> Sequence[224 + channel - 1, Mod[value + 8192, 128], Quotient[value + 8192, 128]], ProgramChange[number_, channel_] -> Sequence[192 + channel - 1, number - 1], ChannelAftertouch[value_, channel_] -> Sequence[208 + channel - 1, value], KeyAftertouch[number_, value_, channel_] -> Sequence[160 + channel - 1, number, value], NoteOff[number_, velocity_, channel_] -> Sequence[128 + channel - 1, number, velocity], MetaEvent[type_, data_] :> Sequence @@ Join[{16^^ff, type}, ToVariableLength[Length[data]], data], Sysex[data_] :> Sequence @@ Insert[data, Sequence @@ ToVariableLength[Length[data] - 1], 2]}; \!\(\(MetaEventRules = {Tempo[BPM_] :> MetaEvent[16^^51, Take[TrackLength[Round[6\/BPM\ 10\^7]], \(-3\)]], \ TextEvent[text_] :> MetaEvent[16^^1, ToCharacterCode[text]], Copyright[text_] :> MetaEvent[16^^2, ToCharacterCode[text]], TrackName[text_] :> MetaEvent[16^^3, ToCharacterCode[text]], InstrumentName[text_] :> MetaEvent[16^^4, ToCharacterCode[text]], Lyric[text_] :> MetaEvent[16^^5, ToCharacterCode[text]], Marker[text_] :> MetaEvent[16^^6, ToCharacterCode[text]], CuePoint[text_] :> MetaEvent[16^^7, ToCharacterCode[text]], TimeSignature[n_, d_] :> MetaEvent[88, {n, Log[2, d], 96/d, 8}], TimeSignature[n_, d_, x_, y_] :> MetaEvent[88, {n, Log[2, d], x, y}], \ KeySignature[sf_, mm_] :> MetaEvent[ 89, {Which[0 <= sf <= 7, sf, \(-7\) <= sf < 0, \ 256 + sf], mm}], \ EndOfTrack :> MetaEvent[47, {}]};\)\) Options[ExportMIDI] = {PPQ -> 192, CustomEvents -> {}} ExportMIDI[eventlist_, file_, options___] := Module[{l, midifile, ppq, ce}, midifile = OpenWriteBinary[ file]; ({ppq, ce} = {PPQ, CustomEvents} /. {options} /. Options[ExportMIDI]); l = Flatten[ ToRunningStatus[ ToDeltaTime[SortEventList[eventlist /. ce /. NoteRule, ppq]] /. MetaEventRules /. EventRules]]; WriteBinary[midifile, Join[{16^^4d, 16^^54, 16^^68, 16^^64, 0, 0, 0, 6, 0, 0, 0, 1}, {Quotient[ppq, 256], Mod[ppq, 256]}, {16^^4d, 16^^54, 16^^72, 16^^6b}, TrackLength[Length[l]], l], ByteConversion -> Identity]; Close[midifile]] ImportMIDI::format = "File in unsupported format." ImportMIDI::filetype = "Not a MIDI file." ImportMIDI::filenotfound = "File not found." Options[ImportMIDI] = {NoteConversion -> True, ToCustomEvents -> {}, FinalFunction -> Identity} ImportMIDI[filename_, options___] := Module[{l, e, b, status, t, len, type, data, format, hd, nc, tce, ff}, ({nc, tce, ff} = {If[NoteConversion, NoteOn2Note, Identity], ToCustomEvents, FinalFunction} /. {options} /. Options[ImportMIDI]); l = OpenRead[filename, DOSTextFormat -> False]; hd = {}; If[l=!=$Failed, Do[AppendTo[hd, Read[l, Byte]], {4}]; If[hd == {77, 84, 104, 100}, Skip[l, Byte, 5]; Print["Format: ", format = Read[l, Byte]]; Print["Tracks: ", 256Read[l, Byte] + Read[l, Byte]]; Print["PPQ: ", 256Read[l, Byte] + Read[l, Byte]]; Skip[l, Byte, 8]; If[format == 0, e = {}; While[(b = Read[l, Byte]) =!= EndOfFile, t = {}; len = {}; While[b > 128, AppendTo[t, b]; (b = Read[l, Byte])]; AppendTo[t, b]; (status = Read[l, Byte]); AppendTo[e, Which[status < 128, {FromVariableLength[t], Head[e[[-1, 2]]] @@ Join[{status}, (ReadList[l, Byte, Length[e[[-1, 2]]] - 2] /. (ReadList[l, Byte, 0] :> {})), {e[[-1, 2, -1]]}]}, 143 < status < 160, {FromVariableLength[t], NoteOn[Read[l, Byte], Read[l, Byte], status - 143]}, 175 < status < 192, {FromVariableLength[t], Controller[Read[l, Byte], Read[l, Byte], status - 175]}, 223 < status < 240, {FromVariableLength[t], PitchBend[Read[l, Byte], Read[l, Byte], status - 223]}, 207 < status < 224, {FromVariableLength[t], ChannelAftertouch[Read[l, Byte], status - 207]}, 191 < status < 208, {FromVariableLength[t], ProgramChange[Read[l, Byte], status - 191]}, 127 < status < 144, {FromVariableLength[t], NoteOff[Read[l, Byte], Read[l, Byte], status - 127]}, 159 < status < 176, {FromVariableLength[t], KeyAftertouch[Read[l, Byte], Read[l, Byte], status - 159]}, status == 255, (type = Read[l, Byte]); b = Read[l, Byte]; While[b > 128, AppendTo[len, b]; (b = Read[l, Byte])]; AppendTo[len, b]; data = (ReadList[l, Byte, FromVariableLength[ len]] /. (ReadList[l, Byte, 0] :> {})); {FromVariableLength[t], MetaEvent[type, data]}, status == 240, b = Read[l, Byte]; While[b > 128, AppendTo[len, b]; (b = Read[l, Byte])]; AppendTo[len, b]; data = ReadList[l, Byte, FromVariableLength[len]]; {FromVariableLength[t], Sysex[Prepend[data, 240]]}, status == 247, b = Read[l, Byte]; While[b > 128, AppendTo[len, b]; (b = Read[l, Byte])]; AppendTo[len, b]; data = ReadList[l, Byte, FromVariableLength[len]]; {FromVariableLength[t], Sysex[Prepend[data, 247]]} ] ]; ]; Close[l]; ff[nc[Transpose[{FromDeltaTime[e[[All, 1]]], e[[All, 2]]}] /. {PitchBend[fine_, coarse_, channel_] \ -> PitchBend[fine + 128coarse - 8192, channel], ProgramChange[num_, channel_] -> ProgramChange[num + 1, channel]} /. Dispatch[{MetaEvent[81, micros_] :> Tempo[N[6/Plus @@ ({65536, 256, 1}micros) 10^7]], MetaEvent[1, text_] :> \ TextEvent[FromCharacterCode[text]], MetaEvent[2, text_] :> Copyright[FromCharacterCode[text]], MetaEvent[3, text_] :> \ TrackName[FromCharacterCode[text]], MetaEvent[4, text_] :> InstrumentName[FromCharacterCode[text]], MetaEvent[5, text_] :> Lyric[FromCharacterCode[text]], MetaEvent[6, text_] :> Marker[FromCharacterCode[text]], MetaEvent[7, text_] :> CuePoint[FromCharacterCode[text]], \ MetaEvent[88, {n_, d_, x_, y_}] :> TimeSignature[n, 2^d, x, y], MetaEvent[47, {}] :> EndOfTrack, MetaEvent[89, {sf_, mm_}] :> KeySignature[ Which[0 <= sf <= 7, sf, sf >= 249, sf - 256], mm]}]] //. tce] , Message[ImportMIDI::format]], \ Message[ImportMIDI::filetype]],Message[ImportMIDI::filenotfound]]] GenerateEventList[x__ /; (Equal @@ (Prepend[Length /@ {x}, 2])), i__ /; (Equal @@ (Prepend[Length /@ {i}, 3])), PPQ_Integer:192] := SortEventList[Flatten[Table[{x}, i], Length[{i}] + Length[{x}] - 2], PPQ] SetAttributes[GenerateEventList, HoldAll] SumList[f_, i_] := Module[{l = {}, s = 0}, Do[AppendTo[l, s]; s += f, i]; l] SetAttributes[SumList, HoldAll] EventSequence[x_, step_, i_, PPQ_:192] := Module[{s, j}, s = SumList[step, i]; SortEventList[Transpose[{s, Flatten[Table[x, i], Length[{i}] - 1]}], PPQ]] SetAttributes[EventSequence, HoldAll] End[] EndPackage[]