Skip to content

Commit 83f6590

Browse files
author
FMS-Cat
committed
init
0 parents  commit 83f6590

File tree

1 file changed

+286
-0
lines changed

1 file changed

+286
-0
lines changed

midi-parser.lua

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
-- Usage:
2+
-- midi = midiParser( filePath )
3+
4+
local midiParser = function( _path )
5+
6+
local ret = {}
7+
8+
------------------
9+
-- prepare file --
10+
------------------
11+
12+
if _path == nil then
13+
error( 'path is not defined' )
14+
end
15+
16+
local file = io.open( _path, 'rb' )
17+
if file == nil then
18+
error( 'not found: ' .. _path )
19+
end
20+
21+
local midi = file:read( '*all' )
22+
midi = string.gsub( midi, '\r\n', '\n' )
23+
24+
--------------------
25+
-- some functions --
26+
--------------------
27+
28+
local byteArray = function( _start, _length )
29+
local retArray = {}
30+
for i = 1, _length do
31+
retArray[ i ] = string.byte( midi, i + _start - 1 )
32+
end
33+
return retArray
34+
end
35+
36+
local bytesToNumber = function( _start, _length )
37+
local retNumber = 0
38+
for i = 1, _length do
39+
retNumber = retNumber + string.byte( midi, i + _start - 1 ) * math.pow( 256, _length - i )
40+
end
41+
return retNumber
42+
end
43+
44+
local vlq = function( _start ) -- Variable-length quantity
45+
local retNumber = 0
46+
local head = 0
47+
local byte = 0
48+
repeat
49+
byte = string.byte( midi, _start + head )
50+
retNumber = retNumber * 128 + ( byte - math.floor( byte / 128 ) * 128 )
51+
head = head + 1
52+
until math.floor( byte / 128 ) ~= 1
53+
return retNumber, head
54+
end
55+
56+
local isSameTable = function( _a, _b )
57+
for i, v in ipairs( _a ) do
58+
if _a[ i ] ~= _b[ i ] then
59+
return false
60+
end
61+
end
62+
return true
63+
end
64+
65+
------------------
66+
-- check format --
67+
------------------
68+
69+
local head = 1
70+
71+
if not isSameTable( byteArray( head, 4 ), { 77, 84, 104, 100 } ) then
72+
error( 'input file seems not to be a .mid file' )
73+
end
74+
head = head + 4 -- header chunk magic number
75+
head = head + 4 -- header chunk length
76+
77+
ret.format = bytesToNumber( head, 2 )
78+
79+
if not ( ret.format == 0 or ret.format == 1 ) then
80+
error( 'not supported such format of .mid' )
81+
end
82+
head = head + 2 -- format
83+
84+
head = head + 2 -- trackCount
85+
86+
ret.timebase = bytesToNumber( head, 2 )
87+
head = head + 2 -- timeBase
88+
89+
------------------------
90+
-- fight against .mid --
91+
------------------------
92+
93+
ret.tracks = {}
94+
95+
while head < string.len( midi ) do
96+
97+
if not isSameTable( byteArray( head, 4 ), { 77, 84, 114, 107 } ) then -- if chunk is not track chunk
98+
99+
head = head + 4 -- unknown chunk magic number
100+
head = head + 4 + bytesToNumber( head, 4 ) -- chunk length + chunk data
101+
102+
else
103+
104+
head = head + 4 -- track chunk magic number
105+
106+
local chunkLength = bytesToNumber( head, 4 )
107+
head = head + 4 -- chunk length
108+
local chunkStart = head
109+
110+
local track = {}
111+
track.messages = {}
112+
table.insert( ret.tracks, track )
113+
114+
local status = 0
115+
while head < chunkStart + chunkLength do
116+
117+
local deltaTime, deltaHead = vlq( head ) -- timing
118+
head = head + deltaHead
119+
120+
local tempStatus = byteArray( head, 1 )[ 1 ]
121+
122+
if math.floor( tempStatus / 128 ) == 1 then -- event, running status
123+
head = head + 1
124+
status = tempStatus
125+
end
126+
127+
local type = math.floor( status / 16 )
128+
local channel = status - type * 16
129+
130+
if type == 8 then -- note off
131+
local data = byteArray( head, 2 )
132+
head = head + 2
133+
134+
table.insert( track.messages, {
135+
time = deltaTime,
136+
type = 'off',
137+
channel = channel,
138+
number = data[ 1 ],
139+
velocity = data[ 2 ]
140+
} )
141+
142+
elseif type == 9 then -- note on
143+
local data = byteArray( head, 2 )
144+
head = head + 2
145+
146+
table.insert( track.messages, {
147+
time = deltaTime,
148+
type = 'on',
149+
channel = channel,
150+
number = data[ 1 ],
151+
velocity = data[ 2 ]
152+
} )
153+
154+
elseif type == 10 then -- polyphonic keypressure
155+
head = head + 2
156+
157+
elseif type == 11 then -- control change
158+
head = head + 2
159+
160+
elseif type == 12 then -- program change
161+
head = head + 1
162+
163+
elseif type == 13 then -- channel pressure
164+
head = head + 1
165+
166+
elseif type == 14 then -- pitch bend
167+
head = head + 2
168+
169+
elseif status == 255 then -- meta event
170+
local metaType = byteArray( head, 1 )[ 1 ]
171+
head = head + 1
172+
local metaLength, metaHead = vlq( head )
173+
174+
if metaType == 3 then -- track name
175+
head = head + metaHead
176+
track.name = string.sub( midi, head, head + metaLength - 1 )
177+
head = head + metaLength
178+
179+
table.insert( track.messages, {
180+
time = deltaTime,
181+
type = 'meta',
182+
meta = 'Track Name',
183+
text = track.name
184+
} )
185+
186+
elseif metaType == 4 then -- instrument name
187+
head = head + metaHead
188+
track.instrument = string.sub( midi, head, head + metaLength - 1 )
189+
head = head + metaLength
190+
191+
table.insert( track.messages, {
192+
time = deltaTime,
193+
type = 'meta',
194+
meta = 'Instrument Name',
195+
text = track.instrument
196+
} )
197+
198+
elseif metaType == 5 then -- lyric
199+
head = head + metaHead
200+
track.lyric = string.sub( midi, head, head + metaLength - 1 )
201+
head = head + metaLength
202+
203+
table.insert( track.messages, {
204+
time = deltaTime,
205+
type = 'meta',
206+
meta = 'Lyric',
207+
text = track.lyric
208+
} )
209+
210+
elseif metaType == 47 then -- end of track
211+
head = head + 1
212+
213+
table.insert( track.messages, {
214+
time = deltaTime,
215+
type = 'meta',
216+
meta = 'End of Track'
217+
} )
218+
219+
break
220+
221+
elseif metaType == 81 then -- tempo
222+
head = head + 1
223+
224+
local micros = bytesToNumber( head, 3 )
225+
head = head + 3
226+
227+
table.insert( track.messages, {
228+
time = deltaTime,
229+
type = 'meta',
230+
meta = 'Set Tempo',
231+
tempo = micros
232+
} )
233+
234+
elseif metaType == 88 then -- time signature
235+
head = head + 1
236+
237+
local sig = byteArray( head, 4 )
238+
head = head + 4
239+
240+
table.insert( track.messages, {
241+
time = deltaTime,
242+
type = 'meta',
243+
meta = 'Time Signature',
244+
signature = sig
245+
} )
246+
247+
elseif metaType == 89 then -- key signature
248+
head = head + 1
249+
250+
local sig = byteArray( head, 2 )
251+
head = head + 2
252+
253+
table.insert( track.messages, {
254+
time = deltaTime,
255+
type = 'meta',
256+
meta = 'Key Signature',
257+
signature = sig
258+
} )
259+
260+
else -- comment
261+
head = head + metaHead
262+
local text = string.sub( midi, head, head + metaLength - 1 )
263+
head = head + metaLength
264+
265+
table.insert( track.messages, {
266+
time = deltaTime,
267+
type = 'meta',
268+
meta = 'Unknown Text: ' .. event[ 2 ],
269+
text = text
270+
} )
271+
272+
end
273+
274+
end
275+
276+
end
277+
278+
end
279+
280+
end
281+
282+
return ret
283+
284+
end
285+
286+
return midiParser

0 commit comments

Comments
 (0)