miniminater

track

Jun 11th, 2023 (edited)
66
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.26 KB | None | 0 0
  1. local args = {...}
  2.  
  3. if #args > 1 then
  4. error("Usage: tracker [path]")
  5. return
  6. end
  7.  
  8. local getFileExtension = function(path)
  9. local dot = path:find(".", 1, true)
  10. if dot then
  11. return path:sub(dot + 1)
  12. end
  13. return nil
  14. end
  15.  
  16. if not fs.exists(args[1]) then
  17. error("File does not exist")
  18. return
  19. end
  20. if fs.isDir(args[1]) then
  21. error("Cannot track a directory")
  22. return
  23. end
  24. if not getFileExtension(args[1]) == "xm" then
  25. error("File is not an XM file")
  26. return
  27. end
  28.  
  29.  
  30.  
  31.  
  32. local file = fs.open(args[1], "rb")
  33. file.seek("set", 0)
  34.  
  35. local function read_string(bytes)
  36. return file.read(bytes)
  37. end
  38. local function read_int(bytes)
  39. if bytes == 0 then return end
  40. if bytes == 1 then return string.byte(file.read(1)) end
  41. local sum = string.byte(file.read(1))
  42. for i = 2, bytes do
  43. sum = sum + string.byte(file.read(1)) * 256 ^ (i - 1)
  44. end
  45. return sum
  46. end
  47. local sbuf = {} --song buffer
  48. local pbuf = {} --pattern buffer
  49. if file then
  50. --#region songheader
  51. if not (read_string(17) == "Extended Module: ") then
  52. error("File is not an XM file")
  53. end
  54. sbuf.song_name = read_string(20):gsub("%z", "")
  55. if not (read_int(1) == 0x1A) then
  56. error("File is not an XM file")
  57. end
  58. sbuf.tracker_name = read_string(20):gsub("%z", "")
  59. local tmp = read_int(1)
  60. sbuf.version = read_int(1) .. "." .. tmp
  61. if sbuf.version ~= "1.4" then
  62. error("XM file is too old! (version " .. sbuf.version .. ")")
  63. end
  64. sbuf.header_size = read_int(4)
  65. sbuf.song_length = read_int(2)
  66. sbuf.restart_position = read_int(2)
  67. sbuf.number_of_channels = read_int(2)
  68. sbuf.number_of_patterns = read_int(2)
  69. --make sure this is <= 256
  70. if sbuf.number_of_patterns > 256 then
  71. error("XM file has too many patterns! (max 256, file may be corrupt)")
  72. end
  73. sbuf.number_of_instruments = read_int(2)
  74. --make sure this is <= 128
  75. if sbuf.number_of_instruments > 128 then
  76. error("XM file has too many instruments! (max 128, file may be corrupt)")
  77. end
  78. sbuf.flags = read_int(2)
  79. --we can't handle amiga, so if bit 0 is 0, error
  80. if bit.band(sbuf.flags, 1) == 0 then
  81. error("XM file is in Amiga frequency table, which is not supported")
  82. end
  83. sbuf.default_tempo = read_int(2)
  84. sbuf.default_bpm = read_int(2)
  85. --read for (number of patterns) bytes for the pattern order table
  86. sbuf.pattern_order_table = {}
  87. for _ = 1, sbuf.number_of_patterns do
  88. sbuf.pattern_order_table[#sbuf.pattern_order_table + 1] = read_int(1)
  89. end
  90. --#endregion songheader
  91.  
  92. --#region patterns
  93. sbuf.patterns = {}
  94. for i = 1, sbuf.number_of_patterns do
  95. local pattern = {}
  96. local prepos = file.seek()
  97. pattern.header_size = read_int(4)
  98. pattern.packing_type = read_int(1)
  99. --this should be 0 per spec, so if it isn't, error
  100. if pattern.packing_type ~= 0 then
  101. error("Unsupported packing type: " .. pattern.packing_type)
  102. end
  103. pattern.number_of_rows = read_int(2)
  104. -- should be 1 <= number_of_rows <= 256
  105. if pattern.number_of_rows < 1 or pattern.number_of_rows > 256 then
  106. error("Invalid number of rows: " .. pattern.number_of_rows)
  107. end
  108. pattern.packed_data_size = read_int(2)
  109. --read for (packed_data_size) bytes for the packed pattern data
  110. file.seek("set", prepos + pattern.header_size)
  111. if pattern.packed_data_size > 0 then
  112. pattern.data = {}
  113. for y = 1, pattern.number_of_rows do
  114. pattern.data[y] = {}
  115. for x = 1, sbuf.number_of_channels do
  116. local dbyte = read_int(1)
  117. pattern.data[y][x] = {}
  118. if bit32.btest(dbyte, 0x80) then
  119. if dbyte ~= 0x80 then
  120. if bit32.btest(dbyte, 0x01) then
  121. pattern.data[y][x].note = read_int(1)
  122. end
  123. if bit32.btest(dbyte, 0x02) then
  124. pattern.data[y][x].instrument = read_int(1)
  125. end
  126. if bit32.btest(dbyte, 0x04) then
  127. pattern.data[y][x].volume = read_int(1) - 16
  128. end
  129. if bit32.btest(dbyte, 0x08) then
  130. pattern.data[y][x].effect = read_int(1)
  131. end
  132. if bit32.btest(dbyte, 0x10) then
  133. if not pattern.data[y][x].effect then pattern.data[y][x].effect = 0 end
  134. pattern.data[y][x].effect_param = read_int(1)
  135. end
  136. end
  137. else
  138. pattern.data[y][x].note = dbyte
  139. pattern.data[y][x].instrument = read_int(1)
  140. pattern.data[y][x].volume = read_int(1) - 16
  141. pattern.data[y][x].effect = read_int(1)
  142. pattern.data[y][x].effect_param = read_int(1)
  143. end
  144. end
  145. end
  146. end
  147. sbuf.patterns[i] = pattern
  148. end
  149. --#endregion patterns
  150.  
  151. --#region instruments
  152. sbuf.instruments = {}
  153. for i = 1, sbuf.number_of_instruments do
  154. local inst = {}
  155. inst.header_size = read_int(4)
  156. inst.name = read_string(22):gsub("%z", "")
  157. inst.type = read_int(1)
  158. inst.number_of_samples = read_int(2)
  159. if inst.number_of_samples > 0 then
  160. inst.samples = {}
  161. inst.samplesByNumber = {}
  162. inst.volumeEnvelope = {points = {}}
  163. inst.panningEnvelope = {points = {}}
  164. inst.vibrato = {}
  165. for j = 1, inst.number_of_samples do inst.samplesByNumber[j] = {} end
  166. local samplesize = read_int(4)
  167. for j = 1, 96 do inst.samples[j] = inst.samplesByNumber[read_int(1)+1] end
  168. for j = 1, 12 do inst.volumeEnvelope.points[j] = {x = read_int(2), y = read_int(2)} end
  169. for j = 1, 12 do inst.panningEnvelope.points[j] = {x = read_int(2), y = read_int(2)} end
  170. for j = read_int(1) + 1, 12 do inst.volumeEnvelope.points[j] = nil end
  171. for j = read_int(1) + 1, 12 do inst.panningEnvelope.points[j] = nil end
  172. inst.volumeEnvelope.sustain = read_int(1) + 1
  173. inst.volumeEnvelope.loopStart = read_int(1) + 1
  174. inst.volumeEnvelope.loopEnd = read_int(1) + 1
  175. inst.panningEnvelope.sustain = read_int(1) + 1
  176. inst.panningEnvelope.loopStart = read_int(1) + 1
  177. inst.panningEnvelope.loopEnd = read_int(1) + 1
  178. inst.volumeEnvelope.loopType = read_int(1)
  179. inst.panningEnvelope.loopType = read_int(1)
  180. inst.vibrato.type = read_int(1)
  181. inst.vibrato.sweep = read_int(1)
  182. inst.vibrato.depth = read_int(1)
  183. inst.vibrato.rate = read_int(1)
  184. inst.fadeOut = read_int(2)
  185. file.seek("cur", inst.header_size - 241)
  186.  
  187. for j = 1, inst.number_of_samples do
  188. local sample = inst.samplesByNumber[j]
  189. sample.sample_size = read_int(4)
  190. sample.loop_start = read_int(4)
  191. sample.loop_length = read_int(4)
  192. sample.volume = read_int(1)
  193. sample.finetune = read_int(1)
  194. if sample.finetune > 0x7F then sample.finetune = sample.finetune - 256 end
  195. sample.type = read_int(1)
  196. sample.pan = read_int(1) --math.max((file.read() - 128) / 127, -1)
  197. sample.note = read_int(1)
  198. if sample.note > 0x7F then sample.note = sample.note - 256 end
  199. sample.data_type = read_int(1) -- reserved, but used by some trackers to store the sample type (0 = PCM, 4 = ADPCM)
  200. sample.name = read_string(22):gsub("[ %z]+$", "")
  201. file.seek("cur", samplesize - 40)
  202. end
  203.  
  204. --we've read past the headers, so now read the sample data
  205. for j = 1, inst.number_of_samples do
  206. local sample = inst.samplesByNumber[j]
  207. sample.wavetable = {}
  208. if bit32.btest(sample.type, 0x10) then
  209. local last = 0
  210. for i = 1, sample.sample_size / 2 do
  211. local d = read_int(2)
  212. if d > 0x7FFF then d = d - 65536 end
  213. d = d / (d > 0 and 65535 or 65536)
  214. sample.wavetable[i], last = last + d, d
  215. end
  216. else
  217. local last = 0
  218. for i = 1, sample.sample_size do
  219. local d = read_int(1)
  220. if d > 0x7F then d = d - 256 end
  221. sample.wavetable[i], last = math.max(math.min((last + d) / ((last + d) > 0 and 127 or 128), 1), -1), last + d
  222. while last > 127 do last = last - 256 end
  223. while last < -128 do last = last + 256 end
  224. end
  225. end
  226. end
  227. else
  228. --seek past the instrument data
  229. file.seek("cur", inst.header_size - 29)
  230. end
  231.  
  232. sbuf.instruments[i] = inst
  233. end
  234. end
  235. file.close()
  236. print("Done!")
  237.  
  238.  
  239.  
  240.  
  241.  
  242.  
  243. --print this all to a debug file
  244. local debug_file = fs.open("debug.txt", "w")
  245. debug_file.write(" Song name: " .. sbuf.song_name .. "\n")
  246. debug_file.write("Created with: " .. sbuf.tracker_name .. "\n")
  247. debug_file.write(" Version: " .. sbuf.version .. "\n")
  248. debug_file.write(" Header size: " .. sbuf.header_size .. "\n")
  249. debug_file.write(" Song length: " .. sbuf.song_length .. "\n")
  250. debug_file.write("Restart pos.: " .. sbuf.restart_position .. "\n")
  251. debug_file.write(" # channels: " .. sbuf.number_of_channels .. "\n")
  252. debug_file.write(" # patterns: " .. sbuf.number_of_patterns .. "\n")
  253. debug_file.write(" # insts: " .. sbuf.number_of_instruments .. "\n")
  254. debug_file.write(" Flags: " .. sbuf.flags .. "\n")
  255. debug_file.write("Default temp: " .. sbuf.default_tempo .. "\n")
  256. debug_file.write(" Default BPM: " .. sbuf.default_bpm .. "\n")
  257. debug_file.write("\nPattern order: ")
  258. for i = 1, #sbuf.pattern_order_table do
  259. debug_file.write(sbuf.pattern_order_table[i] .. " ")
  260. end
  261. debug_file.write("\n\n")
  262. debug_file.write("Patterns:\n")
  263. --write out the patterns like you'd see them in a tracker: such as C-4 01 00 00 00 (and values should be padded to 2 digits, except for note), each channel separated by 10 spaces
  264. --any nonexistent notes should be written as "XXX", and any nonexistent other values should be written as "XX"
  265. --[[for i = 1, #sbuf.patterns do
  266. debug_file.write("Pattern " .. i .. ":\n")
  267. for y = 1, sbuf.patterns[i].number_of_rows do
  268. for x = 1, sbuf.number_of_channels do
  269. local note = sbuf.patterns[i].data[y][x].note
  270. local instrument = sbuf.patterns[i].data[y][x].instrument
  271. local volume = sbuf.patterns[i].data[y][x].volume
  272. local effect = sbuf.patterns[i].data[y][x].effect
  273. local effect_param = sbuf.patterns[i].data[y][x].effect_param
  274. if note then
  275. if note == 97 then
  276. note = "XXX"
  277. else
  278. note = string.format("%02X", note)
  279. end
  280. else
  281. note = "XX"
  282. end
  283. if instrument then
  284. instrument = string.format("%02X", instrument)
  285. else
  286. instrument = "XX"
  287. end
  288. if volume then
  289. volume = string.format("%02X", volume)
  290. else
  291. volume = "XX"
  292. end
  293. if effect then
  294. effect = string.format("%02X", effect)
  295. else
  296. effect = "XX"
  297. end
  298. if effect_param then
  299. effect_param = string.format("%02X", effect_param)
  300. else
  301. effect_param = "XX"
  302. end
  303. debug_file.write(note .. " " .. instrument .. " " .. volume .. " " .. effect .. " " .. effect_param .. " ")
  304. end
  305. debug_file.write("\n")
  306. end
  307. debug_file.write("\n")
  308. sleep(0) --prevent crashing from failing to yield
  309. end]]
  310. --write out the instruments (without samples)
  311. debug_file.write("Instruments:\n")
  312. for i = 1, #sbuf.instruments do
  313. debug_file.write("Instrument " .. i .. ":\n")
  314. debug_file.write(" Header size: " .. sbuf.instruments[i].header_size .. "\n")
  315. debug_file.write(" # samples: " .. sbuf.instruments[i].number_of_samples .. "\n")
  316. debug_file.write(" Name: " .. sbuf.instruments[i].name .. "\n")
  317. debug_file.write("\n")
  318. end
  319.  
  320. debug_file.close()
  321.  
  322.  
  323.  
  324.  
  325.  
  326. print("Testing samples")
  327. --play the first sample of the first instrument
  328. --[[
  329. Example usage of the speaker:
  330.  
  331. local speaker = peripheral.find("speaker")
  332.  
  333. local t, dt = 0, 2 * math.pi * 220 / 48000
  334. while true do
  335. local buffer = {}
  336. for i = 1, 16 * 1024 * 8 do
  337. buffer[i] = math.floor(math.sin(t) * 127)
  338. t = (t + dt) % (math.pi * 2)
  339. end
  340.  
  341. while not speaker.playAudio(buffer) do
  342. os.pullEvent("speaker_audio_empty")
  343. end
  344. end
  345. ]]
  346.  
  347. periphemu.create("left", "speaker")
  348.  
  349. --returns an iterator that returns the next note in the sample at a given pitch change
  350. local function changePitch(sample, changeSemitones)
  351. changeSemitones = changeSemitones + 3 - (12 * 4) --the samples start at A3, so add 3 to start at C4, and subtract 12 * 4 to start at C0
  352. local t = 0
  353. local dt = 2 * math.pi * 440 * 2 ^ (changeSemitones / 12) / 48000
  354. return function()
  355. t = (t + dt) % (math.pi * 2)
  356. return sample.wavetable[math.floor(t * #sample.wavetable / (math.pi * 2)) + 1]
  357. end
  358. end
  359.  
  360. local speaker = peripheral.find("speaker")
  361. local sample = sbuf.instruments[10].samplesByNumber[1]
  362. local note = 0
  363. --play a note, as defined by note (pass note to changePitch)
  364. local ev, ev1
  365. while true do
  366. local buffer = {}
  367. local pitch = changePitch(sample, note)
  368. local pitch2 = changePitch(sample, note + 4)
  369. local pitch3 = changePitch(sample, note + 7)
  370. for i = 1, 16 * 1024 * 8 do
  371. buffer[i] = math.floor(pitch() * 127)
  372. buffer[i] = buffer[i] + math.floor(pitch2() * 127)
  373. buffer[i] = buffer[i] + math.floor(pitch3() * 127)
  374. buffer[i] = math.floor(buffer[i] / 3)
  375. end
  376.  
  377. ev, ev1 = nil, nil
  378. while not speaker.playAudio(buffer) do
  379. ev, ev1 = os.pullEvent()
  380. if ev == "speaker_audio_empty" then
  381. --do nothing
  382. elseif ev == "key" then
  383. term.clearLine(1)
  384. term.setCursorPos(1, 1)
  385. if ev1 == keys.up then
  386. note = note + 1
  387. speaker.stop()
  388. term.write("Note: " .. note)
  389. break
  390. elseif ev1 == keys.down then
  391. note = note - 1
  392. speaker.stop()
  393. term.write("Note: " .. note)
  394. break
  395. end
  396. end
  397. end
  398. end
  399.  
Advertisement
Add Comment
Please, Sign In to add comment