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