RadioNurshat

Untitled

Dec 16th, 2016
223
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.38 KB | None | 0 0
  1. local component = require("component")
  2. local computer = require("computer")
  3. local shell = require("shell")
  4. local keyboard = require("keyboard")
  5. local note = require("note")
  6.  
  7. local args, options = shell.parse(...)
  8. if #args < 1 then
  9. print("Usage: midi [-i] <filename> [track1 [track2 [...]]]")
  10. print("Where the tracks are the numbers of the tracks to actually play.")
  11. print(" -i: only parse and show track info, don't play.")
  12. return
  13. end
  14.  
  15. -- Implements the essentials for MIDI file parsing, references:
  16. -- http://www.recordingblogs.com/sa/tabid/88/Default.aspx?topic=Musical+Instrument+Digital+Interface+(MIDI)
  17. -- http://www.sonicspot.com/guide/midifiles.html
  18.  
  19. local enabledTracks = {n=0}
  20. for i = 2, #args do
  21. enabledTracks[tonumber(args[i])] = true
  22. enabledTracks.n = enabledTracks.n + 1
  23. end
  24.  
  25. local instruments = {}
  26. for address in component.list("note_block") do
  27. table.insert(instruments, function(note)
  28. -- 60 = C in MIDI, 6 = C in Minecraft
  29. component.invoke(address, "trigger", (note + 6 - 60) % 24 + 1)
  30. end)
  31. end
  32. if #instruments == 0 then
  33. local function beepableFrequency(midiCode)
  34. local freq = note.freq(midiCode)
  35. if freq <= 0 then error("Nonpositive frequency") end
  36. -- shift it by octaves so we at least get the right pitch
  37. while freq < 20 do freq = freq * 2 end
  38. while freq > 2000 do freq = freq / 2 end
  39. return freq
  40. end
  41. if component.isAvailable("beep") then
  42. print("No note blocks found, falling back to beep card.")
  43. local notes = {}
  44. instruments[1] = function(note, duration)
  45. notes[beepableFrequency(note)] = duration or 0.05
  46. end
  47. instruments.flush = function()
  48. component.beep.beep(notes)
  49. for k, v in pairs(notes) do
  50. notes[k] = nil
  51. end
  52. end
  53. else
  54. print("No note blocks or beep card found, falling back to built-in speaker.")
  55. instruments[1] = function(note, duration)
  56. pcall(computer.beep, beepableFrequency(note), duration or 0.05)
  57. return true -- only one event per tick
  58. end
  59. end
  60. else
  61. print("Using " .. #instruments .. " note blocks.")
  62. end
  63.  
  64. local filename = shell.resolve(args[1])
  65. local f, reason = io.open(filename, "rb")
  66. if not f then
  67. print(reason)
  68. return
  69. end
  70.  
  71. local function parseVarInt(s, bits) -- parses multiple bytes as an integer
  72. if not s then
  73. error("error parsing file")
  74. end
  75. bits = bits or 8
  76. local mask = bit32.rshift(0xFF, 8 - bits)
  77. local num = 0
  78. for i = 1, s:len() do
  79. num = num + bit32.lshift(bit32.band(s:byte(i), mask), (s:len() - i) * bits)
  80. end
  81. return num
  82. end
  83.  
  84. local function readChunkInfo() -- reads chunk header info
  85. local id = f:read(4)
  86. if not id then
  87. return
  88. end
  89. return id, parseVarInt(f:read(4))
  90. end
  91.  
  92. -- Read the file header and with if file information.
  93. local id, size = readChunkInfo()
  94. if id ~= "MThd" or size ~= 6 then
  95. print("error parsing header (" .. id .. "/" .. size .. ")")
  96. return
  97. end
  98.  
  99. local format = parseVarInt(f:read(2))
  100. local tracks = parseVarInt(f:read(2))
  101. local delta = parseVarInt(f:read(2))
  102.  
  103. if format < 0 or format > 2 then
  104. print("unknown format")
  105. return
  106. end
  107.  
  108. local formatName = ({"single", "synchronous", "asynchronous"})[format + 1]
  109. print(string.format("Found %d %s tracks.", tracks, formatName))
  110.  
  111. if format == 2 then
  112. print("Sorry, asynchronous tracks are not supported.")
  113. return
  114. end
  115.  
  116. -- Figure out our time system and prepare accordingly.
  117. local time = {division = bit32.band(0x8000, delta) == 0 and "tpb" or "fps"}
  118. if time.division == "tpb" then
  119. time.tpb = bit32.band(0x7FFF, delta)
  120. time.mspb = 500000
  121. function time.tick()
  122. return time.mspb / time.tpb
  123. end
  124. print(string.format("Time division is in %d ticks per beat.", time.tpb))
  125. else
  126. time.fps = bit32.band(0x7F00, delta)
  127. time.tpf = bit32.band(0x00FF, delta)
  128. function time.tick()
  129. return 1000000 / (time.fps * time.tpf)
  130. end
  131. print(string.format("Time division is in %d frames per second with %d ticks per frame.", time.fps, time.tpf))
  132. end
  133. function time.calcDelay(later, earlier)
  134. return (later - earlier) * time.tick() / 1000000
  135. end
  136.  
  137. -- Parse all track chunks.
  138. local totalOffset = 0
  139. local totalLength = 0
  140. local tracks = {}
  141. while true do
  142. local id, size = readChunkInfo()
  143. if not id then
  144. break
  145. end
  146. if id == "MTrk" then
  147. local track = {}
  148. local cursor = 0
  149. local start, offset = f:seek(), 0
  150. local inSysEx = false
  151. local running = 0
  152.  
  153. local function read(n)
  154. n = n or 1
  155. if n > 0 then
  156. offset = offset + n
  157. return f:read(n)
  158. end
  159. end
  160. local function readVariableLength()
  161. local total = ""
  162. for i = 1, math.huge do
  163. local part = read()
  164. total = total .. part
  165. if bit32.band(0x80, part:byte(1)) == 0 then
  166. return parseVarInt(total, 7)
  167. end
  168. end
  169. end
  170. local function parseVoiceMessage(event)
  171. local channel = bit32.band(0xF, event)
  172. local note = parseVarInt(read())
  173. local velocity = parseVarInt(read())
  174. return channel, note, velocity
  175. end
  176. local currentNoteEvents = {}
  177. local function noteOn(cursor, channel, note, velocity)
  178. track[cursor] = {channel, note, velocity}
  179. if not currentNoteEvents[channel] then
  180. currentNoteEvents[channel] = {}
  181. end
  182. currentNoteEvents[channel][note] = {event=track[cursor], tick=cursor}
  183. end
  184. local function noteOff(cursor, channel, note, velocity)
  185. if not (currentNoteEvents[channel] and currentNoteEvents[channel][note]) then return end
  186. table.insert(currentNoteEvents[channel][note].event
  187. , time.calcDelay(cursor, currentNoteEvents[channel][note].tick))
  188. currentNoteEvents[channel][note] = nil
  189. end
  190.  
  191. while offset < size do
  192. cursor = cursor + readVariableLength()
  193. totalLength = math.max(totalLength, cursor)
  194. local test = parseVarInt(read())
  195. if inSysEx and test ~= 0xF7 then
  196. error("corrupt file: could not find continuation of divided sysex event")
  197. end
  198. local event
  199. if bit32.band(test, 0x80) == 0 then
  200. if running == 0 then
  201. error("corrupt file: invalid running status")
  202. end
  203. f.bufferRead = string.char(test) .. f.bufferRead
  204. offset = offset - 1
  205. event = running
  206. else
  207. event = test
  208. if test < 0xF0 then
  209. running = test
  210. end
  211. end
  212. local status = bit32.band(0xF0, event)
  213. if status == 0x80 then -- Note off.
  214. local channel, note, velocity = parseVoiceMessage(event)
  215. noteOff(cursor, channel, note, velocity)
  216. elseif status == 0x90 then -- Note on.
  217. local channel, note, velocity = parseVoiceMessage(event)
  218. if velocity == 0 then
  219. noteOff(cursor, channel, note, velocity)
  220. else
  221. noteOn(cursor, channel, note, velocity)
  222. end
  223. elseif status == 0xA0 then -- Aftertouch / key pressure
  224. parseVoiceMessage(event) -- not handled
  225. elseif status == 0xB0 then -- Controller
  226. parseVoiceMessage(event) -- not handled
  227. elseif status == 0xC0 then -- Program change
  228. parseVarInt(read()) -- not handled
  229. elseif status == 0xD0 then -- Channel pressure
  230. parseVarInt(read()) -- not handled
  231. elseif status == 0xE0 then -- Pitch / modulation wheel
  232. parseVarInt(read(2), 7) -- not handled
  233. elseif event == 0xF0 then -- System exclusive event
  234. local length = readVariableLength()
  235. if length > 0 then
  236. read(length - 1)
  237. inSysEx = read(1):byte(1) ~= 0xF7
  238. end
  239. elseif event == 0xF1 then -- MIDI time code quarter frame
  240. parseVarInt(read()) -- not handled
  241. elseif event == 0xF2 then -- Song position pointer
  242. parseVarInt(read(2), 7) -- not handled
  243. elseif event == 0xF3 then -- Song select
  244. parseVarInt(read(2), 7) -- not handled
  245. elseif event == 0xF7 then -- Divided system exclusive event
  246. local length = readVariableLength()
  247. if length > 0 then
  248. read(length - 1)
  249. inSysEx = read(1):byte(1) ~= 0xF7
  250. else
  251. inSysEx = false
  252. end
  253. elseif event >= 0xF8 and event <= 0xFE then -- System real-time event
  254. -- not handled
  255. elseif event == 0xFF then
  256. -- Meta message.
  257. local metaType = parseVarInt(read())
  258. local length = parseVarInt(read())
  259. local data = read(length)
  260.  
  261. if metaType == 0x00 then -- Sequence number
  262. track.sequence = parseVarInt(data)
  263. elseif metaType == 0x01 then -- Text event
  264. elseif metaType == 0x02 then -- Copyright notice
  265. elseif metaType == 0x03 then -- Sequence / track name
  266. track.name = data
  267. elseif metaType == 0x04 then -- Instrument name
  268. track.instrument = data
  269. elseif metaType == 0x05 then -- Lyric text
  270. elseif metaType == 0x06 then -- Marker text
  271. elseif metaType == 0x07 then -- Cue point
  272. elseif metaType == 0x20 then -- Channel prefix assignment
  273. elseif metaType == 0x2F then -- End of track
  274. track.eot = cursor
  275. elseif metaType == 0x51 then -- Tempo setting
  276. track[cursor] = parseVarInt(data)
  277. elseif metaType == 0x54 then -- SMPTE offset
  278. elseif metaType == 0x58 then -- Time signature
  279. elseif metaType == 0x59 then -- Key signature
  280. elseif metaType == 0x7F then -- Sequencer specific event
  281. end
  282. else
  283. f:seek("cur", -9)
  284. local area = f:read(16)
  285. local dump = ""
  286. for i = 1, area:len() do
  287. dump = dump .. string.format(" %02X", area:byte(i))
  288. if i % 4 == 0 then
  289. dump = dump .. "\n"
  290. end
  291. end
  292. error(string.format("midi file contains unhandled event types:\n0x%X at offset %d/%d\ndump of the surrounding area:\n%s", event, offset, size, dump))
  293. end
  294. end
  295. -- turn off any remaining notes
  296. for iChannel, iNotes in pairs(currentNoteEvents) do
  297. for iNote, iEntry in pairs(currentNoteEvents[iChannel]) do
  298. noteOff(cursor, iChannel, iNote)
  299. end
  300. end
  301. local delta = size - offset
  302. if delta ~= 0 then
  303. f:seek("cur", delta)
  304. end
  305. totalOffset = totalOffset + size
  306. table.insert(tracks, track)
  307. else
  308. print(string.format("Encountered unknown chunk type %s, skipping.", id))
  309. f:seek("cur", size)
  310. end
  311. end
  312.  
  313. f:close()
  314.  
  315. if options.i then
  316. print(string.format("Found %d tracks, total length is %d ticks.", #tracks, totalLength))
  317. for i, track in ipairs(tracks) do
  318. if track.name then
  319. print(string.format("#%d: %s", i, track.name))
  320. end
  321. end
  322. return
  323. end
  324.  
  325. local removed = 0
  326. if enabledTracks.n > 0 then
  327. for i = #tracks, 1, -1 do
  328. if not enabledTracks[i] then
  329. table.remove(tracks, i)
  330. removed = removed + 1
  331. end
  332. end
  333. end
  334. print("Playing " .. #tracks .. " tracks:")
  335. for _, track in ipairs(tracks) do
  336. if track.name then
  337. print(string.format("%s", track.name))
  338. end
  339. end
  340.  
  341. local channels = {n=0}
  342. local lastTick, lastTime = 0, computer.uptime()
  343. print("Press Ctrl+C to exit.")
  344. for tick = 1, totalLength do
  345. local hasEvent = false
  346. for _, track in ipairs(tracks) do
  347. if track[tick] then
  348. hasEvent = true
  349. break
  350. end
  351. end
  352. if hasEvent then
  353. local delay = time.calcDelay(tick, lastTick)
  354. -- delay % 0.05 == 0 doesn't seem to work
  355. if math.floor(delay * 100 + 0.5) % 5 == 0 then
  356. os.sleep(delay)
  357. else
  358. -- Busy idle otherwise, because a sleep will take up to 50ms.
  359. local begin = os.clock()
  360. while os.clock() - begin < delay do end
  361. end
  362. lastTick = tick
  363. lastTime = computer.uptime()
  364. for _, track in ipairs(tracks) do
  365. local event = track[tick]
  366. if event then
  367. if type(event) == "number" then
  368. time.mspb = event
  369. elseif type(event) == "table" then
  370. local channel, note, velocity, duration = table.unpack(event)
  371. local instrument
  372. if not channels[channel] then
  373. channels.n = channels.n + 1
  374. channels[channel] = instruments[1 + (channels.n % #instruments)]
  375. end
  376. if channels[channel](note, duration) then
  377. break
  378. end
  379. end
  380. end
  381. end
  382. if instruments.flush then instruments.flush() end
  383. end
  384. if keyboard.isKeyDown(keyboard.keys.c) and keyboard.isControlDown() then os.exit() end
  385. end
Add Comment
Please, Sign In to add comment