· 5 years ago · Jan 03, 2021, 08:14 PM
1-- Noteblock Studio file (.nbs) player for FTB: Revelations
2-- Original Program by James0x57
3-- Updated by Tan Huynh
4-- Noteblock Studio by _Davve_ - http://www.minecraftforum.net/topic/136749-minecraft-note-block-studio-150000-downloads/
5-- Computronics by asie, maintained by Vexatos - https://wiki.vexatos.com/wiki:computronics
6-- CC: Tweaked by SquidDev - https://tweaked.cc/
7
8local args = {...};
9local menu = {};
10local box = nil;
11local song = nil;
12local action = 'mainList';
13local isPlaying = false;
14local stopFlag = false;
15local settings = { rptMode = "allRandom"; }; -- "repeat" is reserved in lua
16local maxtoshow = 15;
17
18if turtle ~= nil then
19 maxtoshow = 9;
20end
21
22function mountIronNoteblock()
23 for _,side in ipairs({"left"; "right"; "back"; "bottom"; "top"; "front"}) do
24 if peripheral.getType(side) == "iron_noteblock" then
25 box = peripheral.wrap(side);
26 return side;
27 end
28 end
29 return nil;
30end
31
32function newSong(x, selectedIndex)
33 return {
34 fh = x;
35 index = selectedIndex;
36 length = 0;
37 height = 0;
38 name = "";
39 author = "";
40 originalAuthor = "";
41 description = "";
42 tempo = 10.00; --tks per second
43 autoSaving = 0;
44 autoSaveDur = 0;
45 timeSig = 4;
46 minSpent = 0;
47 leftClicks = 0;
48 rightClicks = 0;
49 blocksAdded = 0;
50 blocksRemoved = 0;
51 midi = "";
52 music = { wait={}; inst={}; note={}; };
53 };
54end
55
56function loadMenu(fromDir)
57 if fs.isDir(fromDir) then
58 for _, file in ipairs(fs.list(fromDir)) do
59 if fs.isDir(file) == false and string.find(file, ".nbs", -4, true) ~= nil then -- if file and ends in ".nbs"
60 menu[#menu+1] = { d=fromDir; fn=file };
61 end
62 end
63 end
64 return #menu;
65end
66
67function readInt(fh) --little endian, fh is open in rb mode
68 local ret = 0;
69 local x = fh.read();
70 if x == nil then return nil; end
71 ret = x;
72 x = fh.read();
73 if x == nil then return nil; end
74 ret = (x * 0x100) + ret;
75 x = fh.read();
76 if x == nil then return nil; end
77 ret = (x * 0x10000) + ret;
78 x = fh.read();
79 if x == nil then return nil; end
80 ret = (x * 0x1000000) + ret;
81 return ret;
82end
83
84function readShort(fh) --little endian, fh is open in rb mode
85 local ret = 0;
86 local x = fh.read();
87 if x == nil then return nil; end
88 ret = x;
89 x = fh.read();
90 if x == nil then return nil; end
91 ret = (x * 0x100) + ret;
92 return ret;
93end
94
95function readString(fh, len) --fh is open in rb mode
96 local ret = "";
97 local x = 0;
98 for i = 1, len do
99 x = fh.read();
100 if x == nil then return nil; end
101 ret = ret .. string.char(x);
102 end
103 return ret;
104end
105
106function readHeader()
107 song.length = readShort(song.fh);
108 song.height = readShort(song.fh);
109 song.name = readString(song.fh, readInt(song.fh));
110 song.author = readString(song.fh, readInt(song.fh));
111 song.originalAuthor = readString(song.fh, readInt(song.fh));
112 song.description = readString(song.fh, readInt(song.fh));
113 song.tempo = 1.000 / ( readShort(song.fh) / 100.00 );
114 song.autoSaving = song.fh.read();
115 song.autoSaveDur = song.fh.read();
116 song.timeSig = song.fh.read();
117 song.minSpent = readInt(song.fh);
118 song.leftClicks = readInt(song.fh);
119 song.rightClicks = readInt(song.fh);
120 song.blocksAdded = readInt(song.fh);
121 song.blocksRemoved = readInt(song.fh);
122 song.midi = readString(song.fh, readInt(song.fh));
123end
124
125function readNotes()
126 local curtk = 1;
127 local tk = -1;
128 local layer = -1;
129 local inst = 0;
130 local note = 33; -- MC is 33 to 57
131
132 while true do
133 tk = readShort(song.fh);
134 if tk == nil then return false; end
135 if tk == 0 then break; end
136 while true do
137 song.music.wait[curtk] = (tk * song.tempo) * 0.965; -- * 0.965 to speed it up a bit because lua slow
138 layer = readShort(song.fh); --can't do anything with this info (yet?)
139 if layer == nil then return false; end
140 if layer == 0 then break; end
141 song.music.inst[curtk]=song.fh.read();
142 if song.music.inst[curtk] == 0 then
143 song.music.inst[curtk] = 0;
144 elseif song.music.inst[curtk] == 2 then
145 song.music.inst[curtk] = 1;
146 elseif song.music.inst[curtk] == 3 then
147 song.music.inst[curtk] = 2;
148 elseif song.music.inst[curtk] == 4 then
149 song.music.inst[curtk] = 3;
150 elseif song.music.inst[curtk] == 1 then
151 song.music.inst[curtk] = 4;
152 end
153 song.music.note[curtk]=song.fh.read()-33;
154 tk = 0;
155 curtk = curtk + 1;
156 end
157 end
158 return true;
159end
160
161function showInfo()
162 term.clear();
163 print("Now Playing: \n\n\n\n\n\n " .. song.name);
164 print("\n\n\n\n\n\nAuthor: " .. song.author);
165 print("Original Author: " .. song.originalAuthor);
166 print("Description: ");
167 print(song.description);
168 parallel.waitForAny(function()
169 _, key = os.pullEvent("key");
170 end, function()
171 while true do
172 if not isPlaying then break; end
173 os.sleep(0.125);
174 end
175 end);
176 if settings.rptMode == "none" or isPlaying then --song finished in single play mode or key pressed exit
177 action = 'mainList';
178 end
179end
180
181function getRepeateMode() -- returns the name of currently selected repeat mode
182 local rptText = "";
183
184 if settings.rptMode == "allRandom" then
185 rptText = "All (Random)";
186 elseif settings.rptMode == "allOrdered" then
187 rptText = "All (In Order)";
188 elseif settings.rptMode == "one" then
189 rptText = "One (Loop Song)";
190 elseif settings.rptMode == "none" then
191 rptText = "None";
192 end
193
194 return rptText;
195end
196
197function changeRepeatMode() -- cycles to next repeat mode and returns its name
198 if settings.rptMode == "allRandom" then
199 settings.rptMode = "allOrdered";
200 elseif settings.rptMode == "allOrdered" then
201 settings.rptMode = "one";
202 elseif settings.rptMode == "one" then
203 settings.rptMode = "none";
204 elseif settings.rptMode == "none" then
205 settings.rptMode = "allRandom";
206 end
207
208 return getRepeateMode();
209end
210
211function options()
212 local selectedIndex = 1;
213 while true do
214 term.clear();
215 local opts = {};
216 opts[1] = "Show Now Playing";
217 opts[2] = "Repeat: " .. getRepeateMode();
218 opts[3] = "Next Song";
219 opts[4] = "Stop";
220 opts[5] = "Back To Song List";
221 -- "Load Playlist" --> default song lists & any saved lists
222 -- "Add " .. selected main menu song .. " to a playlist"
223 -- "Queue " .. selected main menu song -- takes priority over repeat mode
224 -- "Load songs from ..." -- prompts for folder path, erases current queue
225
226 for i = 1, #opts do
227 if i == selectedIndex then
228 print("> " .. opts[i]);
229 else
230 print(" " .. opts[i]);
231 end
232 end
233 print("----------------------\nUse Arrow keys and Enter to navigate.\n");
234 _, key = os.pullEvent("key");
235
236 if key == 208 or key == 31 then -- down or s
237 selectedIndex = selectedIndex + 1;
238 if selectedIndex > #opts then selectedIndex = 1; end
239 elseif key == 200 or key == 17 then -- up or w
240 selectedIndex = selectedIndex-1;
241 if selectedIndex < 1 then selectedIndex = 1; end
242 elseif key == 28 or key == 57 then -- enter or space
243 if selectedIndex == 1 and isPlaying then
244 action = 'nowPlaying';
245 break;
246 elseif selectedIndex == 2 then
247 changeRepeatMode();
248 elseif selectedIndex == 3 then
249 skipSong();
250 break;
251 elseif selectedIndex == 4 then
252 stopSong();
253 action = 'mainList';
254 break;
255 else
256 action = 'mainList';
257 break;
258 end
259 end
260 end
261end
262
263function mainList(startat, selectedIndex)
264 term.clear();
265 for i = startat, #menu do
266 if startat + maxtoshow <= i then break end
267 if i == selectedIndex then
268 print("> " .. menu[i].fn);
269 else
270 print(" " .. menu[i].fn);
271 end
272 end
273 print("----------------------\nUse Arrow keys and Enter to navigate.");
274 print("Press m to access options or press e to exit.")
275 _, key = os.pullEvent("key");
276
277 if key == 208 or key == 31 then -- down or s
278 selectedIndex = selectedIndex + 1;
279 if selectedIndex >= startat + maxtoshow then startat = startat + maxtoshow; end
280 if selectedIndex > #menu then selectedIndex = 1; startat = 1; end
281 elseif key == 200 or key == 17 then -- up or w
282 selectedIndex = selectedIndex-1;
283 if selectedIndex < 1 then selectedIndex = 1; end
284 if selectedIndex < startat then startat = startat - maxtoshow; end
285 if startat < 1 then startat = 1; end
286 elseif key == 205 or key == 32 then -- right or d
287 selectedIndex = startat + maxtoshow;
288 if selectedIndex > #menu then selectedIndex = 1; end
289 startat = selectedIndex;
290 elseif key == 203 or key == 30 then -- left or a
291 selectedIndex = startat - maxtoshow;
292 startat = startat - maxtoshow;
293 if selectedIndex < 1 then selectedIndex = 1; end
294 if startat < 1 then startat = 1; end
295 elseif key == 28 or key == 57 then -- enter or space
296 action = 'playSong';
297 elseif key == 50 then -- m for menu
298 action = 'options';
299 elseif key == 18 then -- e to exit
300 os.reboot();
301 end
302
303 return startat, selectedIndex;
304end
305
306function continueWith() -- returns a menu index of the next song based on the selected repeat mode
307 local contWith = 1;
308 if #menu > 0 then
309 if settings.rptMode == "allRandom" then
310 contWith = math.random(#menu);
311 elseif settings.rptMode == "allOrdered" then
312 contWith = song.index + 1;
313 if contWith > #menu then
314 contWith = 1;
315 end
316 elseif settings.rptMode == "one" then
317 contWith = song.index;
318 end
319 end
320 return contWith;
321end
322
323function playNotes(doReturn)
324 while true do
325 if action == 'songReady' then
326 isPlaying = true;
327 action = 'nowPlaying';
328 os.queueEvent("playStarted");
329 for i = 1, #song.music.wait - 1 do
330 if song.music.wait[i] ~= 0 then
331 os.sleep(song.music.wait[i]);
332 if stopFlag then break; end
333 end
334 pcall(box.playNote, song.music.inst[i], song.music.note[i]);
335 end
336 isPlaying = false;
337 os.queueEvent("playEnded");
338 if not stopFlag then --song finished (instead of controller terminated)
339 if #menu > 0 and settings.rptMode ~= "none" then
340 menuAt(continueWith()); --continue playing songs based on current repeat mode
341 end
342 end
343 end
344 if doReturn ~= nil and doReturn == true then break; end
345 os.sleep(0.25);
346 end
347end
348
349function stopSong()
350 if isPlaying then
351 stopFlag = true;
352 parallel.waitForAny(function()
353 while true do
354 if not isPlaying then break; end
355 os.sleep(0.125);
356 end
357 end);
358 stopFlag = false;
359 end
360end
361
362function menuAt(x) -- plays the song on the menu at index x
363 stopSong();
364 song = newSong(fs.open(menu[x].d .. "/" .. menu[x].fn, "rb"), x);
365 readHeader();
366 readNotes();
367 song.fh.close();
368 action = 'songReady';
369end
370
371function skipSong()
372 if #menu > 0 and settings.rptMode ~= "none" then
373 menuAt(continueWith()); --continue playing songs based on current repeat mode
374 else
375 stopSong();
376 end
377end
378
379function controller() --handles actions
380 local startat = 1;
381 local selectedIndex = 1;
382 while true do
383 if action == 'mainList' then
384 startat, selectedIndex = mainList(startat, selectedIndex);
385 elseif action == 'playSong' then
386 menuAt(selectedIndex);
387 action = 'songReady';
388 elseif action == 'songReady' then
389 os.sleep(0.125);
390 elseif action == 'nowPlaying' then
391 showInfo();
392 elseif action == 'options' then
393 options();
394 end
395 end
396end
397
398function clearMenu()
399 menu = {};
400end
401
402function menuTable() -- returns all the loaded songs that would show up in the menu
403 return menu;
404end
405
406function launchUI()
407 if box == nil then
408 print("No Iron Noteblock Detected");
409 return;
410 end
411
412 if args[1] == nil or fs.isDir(args[1]) == false then
413 args[1] = "songs";
414 if fs.isDir("songs") == false then
415 fs.makeDir("songs");
416 end
417 elseif args[1] ~= nil and fs.isDir(args[1]) == true then
418 loadMenu(args[1]);
419 end
420
421 clearMenu();
422 loadMenu("songs");
423 loadMenu("rom/songs"); -- \Desktop\MindCrack\minecraft\mods\ComputerCraft\lua\rom\songs (ComputerCraft folder inside \mods\ must be created, it is not there by default)
424
425 parallel.waitForAll(playNotes, controller);
426end
427
428function current() -- returns convinient 'song' table with complete header info, notes, and etc, --OR-- nil if nothing is playing (useful)
429 if isPlaying == true then
430 return song; --full song table, has everything
431 else
432 return nil;
433 end
434end
435
436mountIronNoteblock();
437if shell ~= nil then --ran normally, not loaded as API
438 launchUI();
439else --else it was loaded as an api so don't do anything else automatically
440 settings.rptMode = "none";
441end