Advertisement
filloax

Play.lua

Nov 12th, 2014
220
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.48 KB | None | 0 0
  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.  
  12. local args = {...};
  13. local menu = {};
  14. local box = nil;
  15. local song = nil;
  16. local action = 'mainList';
  17. local isPlaying = false;
  18. local stopFlag = false;
  19. local settings = { rptMode = "allRandom"; }; -- "repeat" is reserved in lua
  20. local maxtoshow = 15;
  21.  
  22. if turtle ~= nil then
  23.     maxtoshow = 9;
  24. end
  25.  
  26. function mountIronNoteblock()
  27.     for _,side in ipairs({"left"; "right"; "back"; "bottom"; "top"; "front"}) do
  28.         if (peripheral.getType(side) == "note") or (peripheral.getType(side) == "iron_noteblock") then
  29.             box = peripheral.wrap(side);
  30.             return side;
  31.         end
  32.     end
  33.     return nil;
  34. end
  35.  
  36. function 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.     };
  58. end
  59.  
  60. function 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;
  69. end
  70.  
  71. function 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;
  86. end
  87.  
  88. function 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;
  97. end
  98.  
  99. function 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;
  108. end
  109.  
  110. function 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));
  127. end
  128.  
  129. function 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;
  163. end
  164.  
  165. function 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
  183. end
  184.  
  185. function 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;
  199. end
  200.  
  201. function 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();
  213. end
  214.  
  215. function 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
  265. end
  266.  
  267. function 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;
  306. end
  307.  
  308. function 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;
  323. end
  324.  
  325. function 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
  349. end
  350.  
  351. function 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
  362. end
  363.  
  364. function 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';
  371. end
  372.  
  373. function 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
  379. end
  380.  
  381. function 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
  398. end
  399.  
  400. function clearMenu()
  401.     menu = {};
  402. end
  403.  
  404. function menuTable() -- returns all the loaded songs that would show up in the menu
  405.     return menu;
  406. end
  407.  
  408. function 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);
  428. end
  429.  
  430. function 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
  436. end
  437.  
  438. mountIronNoteblock();
  439. if shell ~= nil then --ran normally, not loaded as API
  440.     launchUI();
  441. else --else it was loaded as an api so don't do anything else automatically
  442.     settings.rptMode = "none";
  443. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement