Advertisement
James0x57

Noteblock Studio (.nbs) player for the Iron Noteblock

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