Dimencia

Untitled

Jan 6th, 2023
43
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.13 KB | None | 0 0
  1. -- Usage:
  2. -- midi = midiParser( filePath )
  3. args = {...}
  4.  
  5. local midiParser = function( _binaryContent )
  6.  
  7. local ret = {}
  8.  
  9. ------------------
  10. -- prepare file --
  11. ------------------
  12.  
  13. local midi = _binaryContent
  14. midi = string.gsub( midi, '\r\n', '\n' )
  15.  
  16. --------------------
  17. -- some functions --
  18. --------------------
  19.  
  20. local byteArray = function( _start, _length )
  21. local retArray = {}
  22. for i = 1, _length do
  23. retArray[ i ] = string.byte( midi, i + _start - 1 )
  24. end
  25. return retArray
  26. end
  27.  
  28. local bytesToNumber = function( _start, _length )
  29. local retNumber = 0
  30. for i = 1, _length do
  31. retNumber = retNumber + string.byte( midi, i + _start - 1 ) * math.pow( 256, _length - i )
  32. end
  33. return retNumber
  34. end
  35.  
  36. local vlq = function( _start ) -- Variable-length quantity
  37. local retNumber = 0
  38. local head = 0
  39. local byte = 0
  40. repeat
  41. byte = string.byte( midi, _start + head )
  42. retNumber = retNumber * 128 + ( byte - math.floor( byte / 128 ) * 128 )
  43. head = head + 1
  44. until math.floor( byte / 128 ) ~= 1
  45. return retNumber, head
  46. end
  47.  
  48. local isSameTable = function( _a, _b )
  49. for i, v in ipairs( _a ) do
  50. if _a[ i ] ~= _b[ i ] then
  51. return false
  52. end
  53. end
  54. return true
  55. end
  56.  
  57. ------------------
  58. -- check format --
  59. ------------------
  60.  
  61. local head = 1
  62.  
  63. if not isSameTable( byteArray( head, 4 ), { 77, 84, 104, 100 } ) then
  64. error( 'input file seems not to be a .mid file' )
  65. end
  66. head = head + 4 -- header chunk magic number
  67. head = head + 4 -- header chunk length
  68.  
  69. ret.format = bytesToNumber( head, 2 )
  70.  
  71. if not ( ret.format == 0 or ret.format == 1 ) then
  72. error( 'not supported such format of .mid' )
  73. end
  74. head = head + 2 -- format
  75.  
  76. head = head + 2 -- trackCount
  77.  
  78. ret.timebase = bytesToNumber( head, 2 )
  79. head = head + 2 -- timeBase
  80.  
  81. ------------------------
  82. -- fight against .mid --
  83. ------------------------
  84.  
  85. ret.tracks = {}
  86.  
  87. while head < string.len( midi ) do
  88.  
  89. if not isSameTable( byteArray( head, 4 ), { 77, 84, 114, 107 } ) then -- if chunk is not track chunk
  90.  
  91. head = head + 4 -- unknown chunk magic number
  92. head = head + 4 + bytesToNumber( head, 4 ) -- chunk length + chunk data
  93.  
  94. else
  95.  
  96. head = head + 4 -- track chunk magic number
  97.  
  98. local chunkLength = bytesToNumber( head, 4 )
  99. head = head + 4 -- chunk length
  100. local chunkStart = head
  101.  
  102. local track = {}
  103. track.messages = {}
  104. table.insert( ret.tracks, track )
  105.  
  106. local status = 0
  107. while head < chunkStart + chunkLength do
  108.  
  109. local deltaTime, deltaHead = vlq( head ) -- timing
  110. head = head + deltaHead
  111.  
  112. local tempStatus = byteArray( head, 1 )[ 1 ]
  113.  
  114. if math.floor( tempStatus / 128 ) == 1 then -- event, running status
  115. head = head + 1
  116. status = tempStatus
  117. end
  118.  
  119. local type = math.floor( status / 16 )
  120. local channel = status - type * 16
  121.  
  122. if type == 8 then -- note off
  123. local data = byteArray( head, 2 )
  124. head = head + 2
  125.  
  126. table.insert( track.messages, {
  127. time = deltaTime,
  128. type = 'off',
  129. channel = channel,
  130. number = data[ 1 ],
  131. velocity = data[ 2 ]
  132. } )
  133.  
  134. elseif type == 9 then -- note on
  135. local data = byteArray( head, 2 )
  136. head = head + 2
  137.  
  138. table.insert( track.messages, {
  139. time = deltaTime,
  140. type = 'on',
  141. channel = channel,
  142. number = data[ 1 ],
  143. velocity = data[ 2 ]
  144. } )
  145.  
  146. elseif type == 10 then -- polyphonic keypressure
  147. head = head + 2
  148.  
  149. elseif type == 11 then -- control change
  150. head = head + 2
  151.  
  152. elseif type == 12 then -- program change
  153. head = head + 1
  154.  
  155. elseif type == 13 then -- channel pressure
  156. head = head + 1
  157.  
  158. elseif type == 14 then -- pitch bend
  159. head = head + 2
  160.  
  161. elseif status == 255 then -- meta event
  162. local metaType = byteArray( head, 1 )[ 1 ]
  163. head = head + 1
  164. local metaLength, metaHead = vlq( head )
  165.  
  166. if metaType == 3 then -- track name
  167. head = head + metaHead
  168. track.name = string.sub( midi, head, head + metaLength - 1 )
  169. head = head + metaLength
  170.  
  171. table.insert( track.messages, {
  172. time = deltaTime,
  173. type = 'meta',
  174. meta = 'Track Name',
  175. text = track.name
  176. } )
  177.  
  178. elseif metaType == 4 then -- instrument name
  179. head = head + metaHead
  180. track.instrument = string.sub( midi, head, head + metaLength - 1 )
  181. head = head + metaLength
  182.  
  183. table.insert( track.messages, {
  184. time = deltaTime,
  185. type = 'meta',
  186. meta = 'Instrument Name',
  187. text = track.instrument
  188. } )
  189.  
  190. elseif metaType == 5 then -- lyric
  191. head = head + metaHead
  192. track.lyric = string.sub( midi, head, head + metaLength - 1 )
  193. head = head + metaLength
  194.  
  195. table.insert( track.messages, {
  196. time = deltaTime,
  197. type = 'meta',
  198. meta = 'Lyric',
  199. text = track.lyric
  200. } )
  201.  
  202. elseif metaType == 47 then -- end of track
  203. head = head + 1
  204.  
  205. table.insert( track.messages, {
  206. time = deltaTime,
  207. type = 'meta',
  208. meta = 'End of Track'
  209. } )
  210.  
  211. break
  212.  
  213. elseif metaType == 81 then -- tempo
  214. head = head + 1
  215.  
  216. local micros = bytesToNumber( head, 3 )
  217. head = head + 3
  218.  
  219. table.insert( track.messages, {
  220. time = deltaTime,
  221. type = 'meta',
  222. meta = 'Set Tempo',
  223. tempo = micros
  224. } )
  225.  
  226. elseif metaType == 88 then -- time signature
  227. head = head + 1
  228.  
  229. local sig = byteArray( head, 4 )
  230. head = head + 4
  231.  
  232. table.insert( track.messages, {
  233. time = deltaTime,
  234. type = 'meta',
  235. meta = 'Time Signature',
  236. signature = sig
  237. } )
  238.  
  239. elseif metaType == 89 then -- key signature
  240. head = head + 1
  241.  
  242. local sig = byteArray( head, 2 )
  243. head = head + 2
  244.  
  245. table.insert( track.messages, {
  246. time = deltaTime,
  247. type = 'meta',
  248. meta = 'Key Signature',
  249. signature = sig
  250. } )
  251.  
  252. else -- comment
  253. head = head + metaHead
  254. local text = string.sub( midi, head, head + metaLength - 1 )
  255. head = head + metaLength
  256.  
  257. table.insert( track.messages, {
  258. time = deltaTime,
  259. type = 'meta',
  260. meta = 'Unknown Text: ' .. event[ 2 ],
  261. text = text
  262. } )
  263.  
  264. end
  265.  
  266. end
  267.  
  268. end
  269.  
  270. end
  271.  
  272. end
  273.  
  274. return ret
  275.  
  276. end
  277.  
  278. --return midiParser
  279.  
  280. -- I guess we'll have a loop here to ask for a name and URL
  281. local shouldExit = false
  282. while not shouldExit do
  283.  
  284. io.stdout.write("\nEnter Midi Name: ")
  285. local midiName = io.stdin.read()
  286. io.stdout.write("\nEnter Midi URL or blank for file: ")
  287. local midiUrl = io.stdin.read()
  288. local midiPath = "/midi/".. midiName .. ".mid"
  289.  
  290. local response = nil
  291.  
  292. if fs.exists(midiPath) then
  293. response = fs.open(midiPath, "rb")
  294. else
  295. response = http.get{ url = midiUrl, binary = true}
  296. end
  297.  
  298. local binaryContent = response.readAll()
  299. response.close()
  300.  
  301. if not fs.exists(midiPath) then
  302. -- Save it I guess
  303. local midiFile = fs.open(midiPath, "wb")
  304. midiFile.write(binaryContent)
  305. midiFile.close()
  306. end
  307.  
  308. local song = midiParser(binaryContent)
  309.  
  310. print(#song.tracks .. " tracks loaded")
  311.  
  312. -- Find speakers; I guess we can support as many as we want and play on them all
  313.  
  314. local speakers = peripheral.find("speaker")
  315.  
  316. end
  317.  
Advertisement
Add Comment
Please, Sign In to add comment