Advertisement
adriweb

Music Composer - TI Nspire Lua

Aug 2nd, 2011
212
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.70 KB | None | 0 0
  1. -- Music Composer for TI-Nspire
  2. -- Adriweb 2011
  3. -- Version 0.5a
  4.  
  5. -- Visit   http://www.inspired-lua.org   for more information about TI-Nspire Lua programming !
  6.  
  7. -- final string syntax : table of "[Note][Octave][Length][Alteration]", for example : "C342"
  8. -- with C the note, 3 for the 3rd octave (middle of keyboard I think ?), 4 for the eight-note length
  9. -- (whole note is 1, half is 2, fourth is 3, sixteenth is 5), 2 for sharp (1 is flat, 0 is natural)
  10.  
  11. -- We are assuming it's all binary (2/4, 3/4, 4/4)
  12. -- I'll do later for ternary.
  13.  
  14. -- TODO :
  15. ----------
  16. -- ternary support
  17. -- Other "screens" than menu and new music
  18. -- loading
  19. -- delete (backspacekey)
  20. -- playMusic and playNote
  21. -- properly encoding lower and upper notes (celles qui sont pas dans l'octave 3)
  22. -- better graphics
  23. -- scrolling
  24. -- toNbr() function
  25.  
  26.  
  27. --------------- Globals etc.
  28.  
  29. gc = platform.gc()
  30. needMenu = true
  31. needHelp = false
  32. tile = ""
  33. creating = false
  34. editing = false
  35. possibleToEncode = false
  36. currentNote = {x,y,octave,length,alteration} -- see init() for default values
  37. saveCurrentNote = {} -- will be a copy of the currentNote table (for the init() "bug" fix)
  38. currentMusic = {} -- will be the list of encoded notes
  39. notesLengths = { 180, 90, 45, 24, 12 } -- the width space between notes. same order as the notes lengths (1 to 5)
  40. notesLengthsMusic = { 4, 2, 1, 0.5, 0.25 } -- the real duration of the notes (number of beats)
  41.  
  42. --------------- End Globals
  43.  
  44.  
  45. --------------- BetterLuaAPI
  46.  
  47. function copyTable(t)
  48.   local t2 = {}
  49.   for k,v in pairs(t) do
  50.     t2[k] = v
  51.   end
  52.   return t2
  53. end
  54.  
  55. function deepcopy(t) -- This function recursively copies a table's contents, and ensures that metatables are preserved. That is, it will correctly clone a pure Lua object.
  56.     if type(t) ~= 'table' then return t end
  57.     local mt = getmetatable(t)
  58.     local res = {}
  59.     for k,v in pairs(t) do
  60.         if type(v) == 'table' then
  61.         v = deepcopy(v)
  62.         end
  63.     res[k] = v
  64.     end
  65.     setmetatable(res,mt)
  66.     return res
  67. end -- from http://snippets.luacode.org/snippets/Deep_copy_of_a_Lua_Table_2
  68.  
  69. function add(arg1,arg2)
  70.     return arg1+arg2
  71. end
  72.  
  73. function subs(arg1,arg2)
  74.     return arg1-arg2
  75. end
  76.  
  77. function test(arg)
  78.     if type(arg) == "boolean" then
  79.         if arg == true then
  80.             return 1
  81.         elseif arg == false then
  82.             return 0
  83.         end
  84.     else
  85.         print("error in test - not bool")
  86.     end
  87. end
  88.  
  89. function toNbr(str)
  90.     if (tostring(tonumber(str)) == "nil") then return -99 else return tonumber(str) end
  91.     -- to redo with regexp in string.gmatch
  92. end
  93.  
  94. function refresh()
  95.     platform.window:invalidate()
  96. end
  97.  
  98. function pww()
  99.     return platform.window:width()
  100. end
  101.  
  102. function pwh()
  103.     return platform.window:height()
  104. end
  105.  
  106. function drawPoint(x, y)
  107.     platform.gc():fillRect(x, y, 1, 1)
  108. end
  109.  
  110. function drawCircle(x, y, diameter)
  111.     platform.gc():drawArc(x - diameter/2, y - diameter/2, diameter,diameter,0,360)
  112. end
  113.  
  114. function drawFilledCircle(x, y, diameter)
  115.     platform.gc():fillArc(x - diameter/2, y - diameter/2, diameter,diameter,0,360)
  116. end
  117.  
  118. function drawCenteredString(str)
  119.     platform.gc():drawString(str, (pww() - platform.gc():getStringWidth(str)) / 2, pwh() / 2, "middle")
  120. end
  121.  
  122. function setColor(theColor)
  123.     theColor = string.lower(theColor)
  124.     platform.gc():setColorRGB(0,0,0) -- set black as default is nothing else valid is found
  125.     if theColor == "blue" then platform.gc():setColorRGB(0,0,255)
  126.     elseif theColor == "gray" or theColor == "grey" then platform.gc():setColorRGB(127,127,127)
  127.     elseif theColor == "green" then platform.gc():setColorRGB(0,128,0)
  128.     elseif theColor == "orange" then platform.gc():setColorRGB(255,165,0)
  129.     elseif theColor == "red" then platform.gc():setColorRGB(255,0,0)
  130.     elseif theColor == "white" then platform.gc():setColorRGB(255,255,255)
  131.     elseif theColor == "yellow" then platform.gc():setColorRGB(255,255,0)
  132.     end
  133. end
  134.  
  135. function verticalBar(x)
  136.     platform.gc():drawLine(x,1,x,platform.window:height())
  137. end
  138.  
  139. function horizontalBar(y)
  140.     platform.gc():drawLine(1,y,platform.window:width(),y)
  141. end
  142.  
  143. function drawSquare(x,y,l)
  144.     platform.gc():drawPolyLine({(x-l/2),(y-l/2), (x+l/2),(y-l/2), (x+l/2),(y+l/2), (x-l/2),(y+l/2), (x-l/2),(y-l/2)})
  145. end
  146.  
  147. function drawRoundRect(x,y,width,height,radius)
  148.     x = x-width/2  -- let the center of the square be the origin (x coord)
  149.     y = y-height/2 -- same for y coord
  150.     if radius > height/2 then radius = height/2 end -- avoid drawing cool but unexpected shapes. This will draw a circle (max radius)
  151.     platform.gc():drawLine(x + radius, y, x + width - (radius), y);
  152.     platform.gc():drawArc(x + width - (radius*2), y + height - (radius*2), radius*2, radius*2, 270, 90);
  153.     platform.gc():drawLine(x + width, y + radius, x + width, y + height - (radius));
  154.     platform.gc():drawArc(x + width - (radius*2), y, radius*2, radius*2,0,90);
  155.     platform.gc():drawLine(x + width - (radius), y + height, x + radius, y + height);
  156.     platform.gc():drawArc(x, y, radius*2, radius*2, 90, 90);
  157.     platform.gc():drawLine(x, y + height - (radius), x, y + radius);
  158.     platform.gc():drawArc(x, y + height - (radius*2), radius*2, radius*2, 180, 90);
  159. end
  160.  
  161. function fillRoundRect(x,y,wd,ht,radius)  -- wd = width and ht = height -- renders badly when transparency (alpha) is not at maximum >< will re-code later
  162.     if radius > ht/2 then radius = ht/2 end -- avoid drawing cool but unexpected shapes. This will draw a circle (max radius)
  163.     platform.gc():fillPolygon({(x-wd/2),(y-ht/2+radius), (x+wd/2),(y-ht/2+radius), (x+wd/2),(y+ht/2-radius), (x-wd/2),(y+ht/2-radius), (x-wd/2),(y-ht/2+radius)})
  164.     platform.gc():fillPolygon({(x-wd/2-radius+1),(y-ht/2), (x+wd/2-radius+1),(y-ht/2), (x+wd/2-radius+1),(y+ht/2), (x-wd/2+radius),(y+ht/2), (x-wd/2+radius),(y-ht/2)})
  165.     x = x-wd/2  -- let the center of the square be the origin (x coord)
  166.     y = y-ht/2 -- same
  167.     platform.gc():fillArc(x + wd - (radius*2), y + ht - (radius*2), radius*2, radius*2, 1, -91);
  168.     platform.gc():fillArc(x + wd - (radius*2), y, radius*2, radius*2,-2,91);
  169.     platform.gc():fillArc(x, y, radius*2, radius*2, 85, 95);
  170.     platform.gc():fillArc(x, y + ht - (radius*2), radius*2, radius*2, 180, 95);
  171. end
  172.  
  173. function drawLinearGradient(r1,g1,b1,r2,g2,b2)
  174.     -- not sure if it's a good idea...
  175. end
  176.  
  177. ---------------  End of BetterLuaAPI
  178.  
  179.  
  180. function on.paint(gc)
  181.     setColor("black")
  182.     --gc:drawString(" mem=" .. tostring(collectgarbage("count")*1024) .. " bytes", 0, 192, "top")
  183.     gc:drawString(" Music Composer - Adriweb 2011",64,0,"top")
  184.        
  185.     if needMenu then
  186.         menu(gc)
  187.     elseif needHelp then
  188.         help(gc)
  189.     else
  190.         drawMain(gc)
  191.     end
  192. end
  193.  
  194. function on.help()
  195.     -- todo
  196.     print("I'm in the help frame")
  197.     needHelp = true
  198.     refresh()
  199. end
  200.  
  201. function on.escapeKey()
  202.     needMenu = true
  203.     refresh()
  204. end
  205.  
  206. function on.arrowKey(key)
  207.     if key == "down" then
  208.         if currentNote.y < 128 then currentNote.y = currentNote.y + 6 end
  209.         if currentNote.y > 116 then currentNote.octave = 2 end
  210.     elseif key == "up" then
  211.         if currentNote.y > 62 then currentNote.y = currentNote.y - 6 end
  212.         if currentNote.y < 74 then currentNote.octave = 4 end
  213.     end
  214.     print("currentNote.x = ",currentNote.x)
  215.     print("currentNote.y = ",currentNote.y)
  216.     refresh()
  217. end
  218.  
  219. function on.charIn(ch)
  220.     if editing then
  221.         if (toNbr(ch) > 0 and toNbr(ch) < 6) then currentNote.length = toNbr(ch) end
  222.         if ch == "*" then
  223.             if currentNote.octave > 2 then subs(currentNote.octave,1) end
  224.         end
  225.         if ch == "/" then
  226.             if currentNote.octave < 5 then add(currentNote.octave,1) end
  227.         end
  228.         if ch == "-" then
  229.             if currentNote.alteration > 0 then subs(currentNote.alteration,1) end
  230.         end
  231.         if ch == "+" then
  232.             if currentNote.alteration < 2 then add(currentNote.alteration,1) end
  233.         end
  234.     elseif needMenu then
  235.         if (toNbr(ch) > 0 and toNbr(ch) < 4) then needMenu = false end
  236.         if ch == "1" then newMusic(gc) end
  237.         if ch == "2" then loadMusic(gc) end
  238.         if ch == "3" then settings(gc) end
  239.     end
  240.     refresh()
  241. end
  242.  
  243. function on.enterKey() -- a lot to do here ? -- to finish
  244.     if editing then
  245.         if possibleToEncode then
  246.             insertEncodedNote(encode(noteFromY(currentNote.y),currentNote.octave,currentNote.length,currentNote.alteration))
  247.             currentNote.x = currentNote.x + notesLengths[currentNote.length]
  248.         end
  249.     end
  250. end
  251.  
  252. function on.timer()
  253.     -- what for ? no idea right now
  254. end
  255.  
  256. function on.save()
  257.     saveMusic(platform.gc())
  258. end
  259.  
  260. function on.destroy()
  261.     saveMusic(platform.gc())
  262. end
  263.  
  264.  
  265. ---------------  End of events
  266.  
  267.  
  268. ---------------  Functions
  269.  
  270. function menu(gc)
  271.    
  272.     editing = false
  273.     print("i'm in the menu")
  274.  
  275.     local xmax,ymax
  276.  
  277.     xmax = pww()
  278.     ymax = pwh()
  279.    
  280.     setColor("black")
  281.     gc:fillRect(xmax/5, ymax/5,3*xmax/5,3*ymax/5)
  282.    
  283.     gc:setColorRGB(200,200,200)
  284.     gc:fillRect(xmax/5+1, ymax/5+1,3*xmax/5-2,3*ymax/5-2)
  285.    
  286.     gc:setColorRGB(0,0,0)
  287.     gc:drawString("Choose Action : ",xmax*0.5-44,48,"top")
  288.        
  289.     makeButton(gc,"1.  New Music",xmax*0.5,88)
  290.     makeButton(gc,"2.  Open Existing",xmax*0.5,115)
  291.     makeButton(gc,"3.  Settings...",xmax*0.5,142)
  292.    
  293.     gc:setColorRGB(100,100,100)
  294.     tmpstr = "(?) or Ctrl-? to show help"
  295.     gc:drawString(tmpstr,0.5*(pww()-gc:getStringWidth(tmpstr)),190,"top")
  296.  
  297.     tmpstr = "Esc to go to this Menu"
  298.     gc:drawString(tmpstr,0.5*(pww()-gc:getStringWidth(tmpstr)),175,"top")
  299.    
  300. end
  301.  
  302. function help(gc)
  303.     -- todo
  304.     print("I'm in help")
  305. end
  306.  
  307. function loadMusic(gc)
  308.     -- todo
  309.     print("loading music")
  310.     init()
  311.     -- faire qqchose du genre currentMusic = var.recall(blablabla) ; drawMain(gc) --
  312. end
  313.  
  314. function saveMusic(gc)
  315.     -- todo
  316.     print("Saving")
  317.     for i,v in ipairs(currentMusic) do print(i,v) end
  318.     var.store("Music"..tostring(#currentMusic),currentMusic) -- saves as an external math variable.
  319.     -- a changer pour inclure le titre dans le nom
  320. end
  321.  
  322. function playMusic(gc)
  323.     -- todo
  324.     -- with jimbauwens' routines...
  325. end
  326.  
  327. function playNote(encodedNote) -- encodedNote = string with full syntax (see top)
  328.     -- todo
  329.     -- also with Jimbauwens' routines...
  330. end
  331.  
  332. function settings(gc)
  333.     -- todo
  334.     print("I'm in the settings")
  335. end
  336.  
  337. function newMusic(gc)
  338.     print("New Music...")
  339.     init()
  340.     creating = true
  341.     -- todo properly (input boxes etc.)
  342.     print("  getting settings...")
  343.     --title = var.recallstr("newtitle")
  344.     title = "Essai" -- line to delete - debug only !
  345.     print("  settings are : ","title = ",title)
  346.     creating = false
  347.     drawMain(gc)
  348. end
  349.  
  350. function init()
  351.     print("  **init called**")
  352.     currentNote.x = 70
  353.     currentNote.y = 92
  354.     currentNote.octave = 3
  355.     currentNote.alteration = 0
  356.     currentNote.length = 3 -- 5 = double-croche, 4 = croche, 3 = noire, 2 = blanche, 1 = ronde
  357. end
  358.  
  359. function drawMain(gc)
  360.     local tmpStr
  361.     print("I'm in the main thing")
  362.     drawBackground(gc)
  363.    
  364.     editing = true
  365.    
  366.     -- TODO : draw la clรฉ de sol
  367.    
  368.     saveCurrentNote = copyTable(currentNote) -- voir 2 lignes apres
  369.     drawExistingNotes(gc)
  370.     currentNote = copyTable(saveCurrentNote) -- pour รฉviter le problรจme du init dans le drawExistingNotes
  371.    
  372.     possibleToEncode = checkIfEncodingPossible()
  373.     if possibleToEncode then
  374.         drawNote(gc,currentNote.length,currentNote.x,currentNote.y,1,"preview")
  375.     end
  376.    
  377.     if currentNote.alteration == 1 then
  378.         tmpStr = "flat"
  379.     elseif currentNote.alteration == 2 then
  380.         tmpStr = "sharp"
  381.     else
  382.         tmpStr = "natural"
  383.     end
  384.     gc:drawString("Current Note Octave : " .. currentNote.octave,18,165,"top")
  385.     gc:drawString("Current Note Alteration : " .. tmpStr,18,180,"top")
  386.    
  387.     drawSquare(currentNote.x+6,145, 2) -- show which note is currently being edited
  388.    
  389.     refresh()
  390.     print("  end drawMain (refreshed)")
  391. end
  392.  
  393. function drawBackground(gc)
  394.     print("  drawing the background")
  395.    
  396.     local h
  397.    
  398.     -- Drawing the measure etc.
  399.     for h=1,5 do
  400.         gc:drawLine(52,56+h*12,294,56+h*12)
  401.     end
  402.    
  403.     -- Drawing the notes length template
  404.     for h=1,5 do
  405.         gc:drawRect(18,20+h*22,16,22)
  406.         gc:drawString(tostring(h),8,19+h*22,"top")
  407.         drawNote(gc,h,21,36-6*test(h<3)+h*22,0.5,"")
  408.     end
  409.  
  410.     print("  done template")
  411.    
  412.     -- highlights the currently chosen note length
  413.     setColor("red")
  414.     gc:drawRect(19,21+currentNote.length*22,14,20)
  415.     setColor("black")
  416.    
  417. end
  418.  
  419. function drawExistingNotes(gc)
  420.     if #currentMusic > 0 then
  421.         init()
  422.         print("  drawing existing...")
  423.         for i,v in ipairs(currentMusic) do
  424.             currentNote.y = YFromNote(string.sub(v,1,1),tonumber(string.sub(v,2,2)))
  425.             currentNote.octave = tonumber(string.sub(v,2,2))
  426.             currentNote.length = tonumber(string.sub(v,3,3))
  427.             currentNote.alteration = tonumber(string.sub(v,4,4))
  428.             drawNote(gc,currentNote.length,currentNote.x, currentNote.y,1,"done")
  429.             currentNote.x = currentNote.x + notesLengths[currentNote.length] -- + test(currentNote.length
  430.         end
  431.     end
  432. end
  433.  
  434. function checkIfEncodingPossible()
  435.     --todo
  436.     return true -- debug only, to delete after
  437. end
  438.  
  439. function noteFromY(ycoord)
  440.     if (ycoord == 128 or ycoord == 86) then return "C" end
  441.     if (ycoord == 122 or ycoord == 80) then return "D" end
  442.     if (ycoord == 116 or ycoord == 74) then return "E" end
  443.     if (ycoord == 110 or ycoord == 68) then return "F" end
  444.     if (ycoord == 104 or ycoord == 62) then return "G" end
  445.     if ycoord == 98 then return "A" end
  446.     if ycoord == 92 then return "B" end
  447. end
  448.  
  449. function YFromNote(note,octave)
  450.     if note == "C" then
  451.         if octave < 3 then return 128 else return 86 end
  452.     end
  453.     if note == "D" then
  454.         if octave < 3 then return 122 else return 80 end
  455.     end
  456.     if note == "E" then
  457.         if octave < 3 then return 116 else return 74 end
  458.     end
  459.     if note == "F" then
  460.         if octave < 3 then return 110 else return 68 end
  461.     end
  462.     if note == "G" then
  463.         if octave < 3 then return 104 else return 62 end
  464.     end
  465.     if note == "A" then return 98 end
  466.     if note == "B" then return 92 end
  467. end
  468.  
  469. function makeButton(gc,string,x,y)
  470.     gc:setColorRGB(150,150,150)
  471.     drawRoundRect(x,y,140,20,5)
  472.     gc:setColorRGB(50,50,50)
  473.     fillRoundRect(x,y,139,18,5)
  474.     gc:setColorRGB(255,255,255)
  475.     gc:drawString(string,x-gc:getStringWidth(string)*0.5,y-12,"top")
  476. end
  477.  
  478. function encode(noteName,octave,length,alteration)
  479.     return noteName .. tostring(octave) .. tostring(length) .. tostring(alteration) -- encode using the syntax
  480. end
  481.  
  482. function insertEncodedNote(encodedNote)
  483.     table.insert(currentMusic,encodedNote) -- adds the new note to the table
  484. end
  485.  
  486. function drawNote(gc,notelen,x,y,scale,state)
  487.     if state == "preview" then setColor("grey") else setColor("black") end
  488.  
  489.     drawCircle(x+6*scale,y,scale*12) -- le rond de la note
  490.    
  491.     if notelen ~= 1 then
  492.         gc:drawLine(x+test(y>90)*12*scale,y,x+test(y>90)*12*scale,y-test(y>90)*30*scale+test(y<90)*30*scale) -- queue de la note
  493.         if notelen > 2 then
  494.             drawFilledCircle(x+6*scale,y,12*scale) -- le rond rempli de la note
  495.         end
  496.     end
  497.    
  498. --  if currentNote.y >
  499. -- faire le trait pour upper et lower
  500.    
  501.     if notelen > 3 then
  502.         gc:drawArc(x-8*scale+test(y>90)*12*scale,y-test(y>90)*30*scale+test(y<90)*10*scale,scale*18,scale*20,0,90-180*test(y<90))
  503.         if notelen == 5 then
  504.             gc:drawArc(x-8*scale+test(y>90)*12*scale,y-test(y>90)*20*scale,scale*18,scale*20,0,90-180*test(y<90))
  505.         end
  506.     end
  507.    
  508.     setColor("black")
  509. end
  510.  
  511. ---------------  End of Functions
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement