Files
Techmino/parts/scenes/app_piano.lua
2024-09-26 02:29:24 +08:00

243 lines
8.5 KiB
Lua

local gc=love.graphics
local kbIsDown=love.keyboard.isDown
local moIsDown=love.mouse.isDown
local min,max=math.min,math.max
local instList={'lead','bell','bass'}
local keys={
['1']=61,['2']=63,['3']=65,['4']=66,['5']=68,['6']=70,['7']=72,['8']=73,['9']=75,['0']=77,['-']=78,['=']=80,['backspace']=82,
['q']=49,['w']=51,['e']=53,['r']=54,['t']=56,['y']=58,['u']=60,['i']=61,['o']=63,['p']=65,['[']=66,[']']=68,['\\']=70,
['a']=37,['s']=39,['d']=41,['f']=42,['g']=44,['h']=46,['j']=48,['k']=49,['l']=51,[';']=53,["'"]=54,['return']=56,
['z']=25,['x']=27,['c']=29,['v']=30,['b']=32,['n']=34,['m']=36,[',']=37,['.']=39,['/']=41,
}
local inst
local offset
local tempoffset=0
local generateVKey
local showingKey
local pianoVK={} -- All piano key can be appear on the screen, want to see? Check the end of the code
local touches={}
local keyCount=0 -- Get key count (up to 262, can be larger), used to check if we need to launch Lua's garbage collector or not
local textObj={} -- We will keep all text objects of note here, only used for virutal keys
local lastKeyTime -- Last time any key pressed
local lastPlayBGM
local scene={}
-- Rename all virtual key's text
local function _renameKeyText(_offset)
for keyName,K in pairs(pianoVK) do
if keys[keyName] then
local keynameoffset=keyName.._offset -- Achivement? Hashtable implemented
if not textObj[keynameoffset] then
textObj[keynameoffset]=gc.newText(FONT.get(K.font),SFX.getNoteName(keys[keyName]+_offset))
end
K:setObject(textObj[keynameoffset])
end
end
end
-- Show virtual key
local function _showVirtualKey(switch)
if switch~=nil then showingKey=switch else showingKey=not showingKey end
end
local function _notHoldCS(dct) -- dct=don't change (key's) text
tempoffset=0
if not dct then _renameKeyText(offset) end
pianoVK.ctrl.color,pianoVK.shift.color=COLOR.Z,COLOR.Z
end
local function _holdingCtrl()
_notHoldCS(true)
tempoffset=-1
pianoVK.ctrl.color=COLOR.R
_renameKeyText(offset-1)
end
local function _holdingShift()
_notHoldCS(true)
tempoffset=1
pianoVK.shift.color=COLOR.R
_renameKeyText(offset+1)
end
local function checkMultiTouch() -- Check for every touch
if not showingKey then return end
if not kbIsDown('lctrl','rctrl','lshift','rshift') then _notHoldCS() end
for tid,t in pairs(touches) do
local x,y=t[1],t[2]
for kID,key in pairs(pianoVK) do
if not (kID=="ctrl" or kID=="shift") then
if key:isAbove(x,y) then key:code(); key:update(1); touches[tid]=nil end
end
end
if pianoVK.ctrl:isAbove(x,y) then
_holdingCtrl()
elseif pianoVK.shift:isAbove(x,y) then
_holdingShift()
end
end
end
-- Set scene's variables
function scene.enter()
TABLE.cut(touches)
inst='lead'
offset=0
lastPlayBGM=BGM.getPlaying()[1]
BGM.stop()
keyCount=0
lastKeyTime=nil
generateVKey()
_notHoldCS()
_showVirtualKey(MOBILE)
DiscordRPC.update("Playing music")
end
function scene.leave()
showingKey=false
TABLE.clear(textObj)
TABLE.clear(pianoVK)
collectgarbage()
BGM.play(lastPlayBGM)
end
function scene.touchDown(x,y,id)
touches[id]={x,y}
checkMultiTouch()
end
function scene.touchUp(_,_,id)
touches[id]=nil
checkMultiTouch()
end
function scene.keyDown(key,isRep)
if not isRep and keys[key] then
local note=keys[key]+offset+tempoffset
SFX.playSample(inst,note)
keyCount=keyCount+1
lastKeyTime=TIME()
if showingKey then
pianoVK[key]:update(1)
TEXT.show(SFX.getNoteName(note),math.random(75,1205),math.random(162,260),60,'score',.8)
else
TEXT.show(SFX.getNoteName(note),math.random(75,1205),math.random(162,620),60,'score',.8)
end
elseif kbIsDown('lctrl','rctrl') and not kbIsDown('lshift','rshift') then
_holdingCtrl()
elseif kbIsDown('lshift','rshift') and not kbIsDown('lctrl','rctrl') then
_holdingShift()
elseif key=='tab' then
inst=TABLE.next(instList,inst)
elseif key=='lalt' then
offset=math.max(offset-1,-12)
_renameKeyText(offset)
elseif key=='ralt' then
offset=math.min(offset+1,12)
_renameKeyText(offset)
elseif key=='f5' then
_showVirtualKey()
elseif key=='escape' then SCN.back() end
end
function scene.keyUp()
if (
not kbIsDown('lctrl','rctrl','lshift','rshift') -- If we are not holding Ctrl or Shift keys
) and not moIsDown(1) -- and the left mouse button is not being held
-- The implementation is really wild but I hope it will good enough to keep the virtual keys from bugs
then _notHoldCS() end
end
scene.mouseDown=scene.touchDown -- The ID arg is being used by button, nvm the code still not crash
scene.mouseUp=scene.touchUp -- Don't need to do anything more complicated here
function scene.draw()
setFont(30)
GC.setColor(1,1,1)
gc.print(inst.." | "..offset,30,40)
-- Drawing virtual keys
if showingKey then
for _,key in pairs(pianoVK) do key:draw() end
gc.setLineWidth(1)
gc.setColor(COLOR.Z)
gc.line(685.5,297,685.5,642)
end
end
function scene.update(dt)
for _,key in pairs(pianoVK) do key:update(nil,dt) end
if lastKeyTime and keyCount>262 and TIME()-lastKeyTime>10 then
collectgarbage()
lastKeyTime=nil
keyCount=0
end
end
scene.widgetList={
WIDGET.newButton{name='back' ,x=1150,y=60,w=170,h=80,sound='back',font=60,fText=CHAR.icon.back,code=pressKey'escape'},
WIDGET.newSwitch{name='showKey' ,x=970 ,y=60,fText='Virtual key (F5)',disp=function() return showingKey end,code=pressKey'f5'},
WIDGET.newKey {name='changeIns',x=265 ,y=60,w=200,h=60,fText='Instrument' ,code=pressKey"tab" ,hideF=function() return not showingKey end},
WIDGET.newKey {name='offset-' ,x=405 ,y=60,w=60 ,h=60,fText=CHAR.key.left ,code=pressKey"lalt",hideF=function() return not showingKey end},
WIDGET.newKey {name='offset+' ,x=475 ,y=60,w=60 ,h=60,fText=CHAR.key.right,code=pressKey"ralt",hideF=function() return not showingKey end},
}
-- Generate virtual keys (seperate from ZFramework, to use only in this scene)
-- Using hashtable to reduce usage time
generateVKey=function()
local allRow={
{'1','2','3','4','5','6','7','8','9','0' ,'-','=','backspace'},
{'q','w','e','r','t','y','u','i','o','p','[' ,']','\\'},
{'a','s','d','f','g','h','j','k','l',';','\'','return'},
{'z','x','c','v','b','n','m',',','.','/',},
}
local keyColorInHomeRow={'R','W','P','N','Z','Z','O','L','G','C','Z','Z'}
for row,keysInRow in pairs(allRow) do
for keyIndex,keyChar in pairs(keysInRow) do
-- Create the base first
local K=WIDGET.newKey{
x=75+90*(keyIndex-1)+50*(keyIndex>7 and 1 or 0),
y=335+90*(row-1),
w=75,h=75,
font=35,fText='',sound=false,
color=row==3 and keyColorInHomeRow[keyIndex] or 'Z',
code=function() scene.keyDown(keyChar) end
}
-- Then modify the base to get the key we expected
function K:update(activateState,dt)
-- activateState: 0=off, 1=on then off, 2=on
local activationTime=self.ATV or 0
local maxTime=6.2
if activateState~=nil then self.activateState=activateState
elseif (self.activateState==1 and activationTime==maxTime) or not self.activateState then self.activateState=0 end
-- TODO: when the note can be extended longer, this will need remaking
if dt then
if self.activateState>0 then self.ATV=min(activationTime+dt*60,maxTime)
elseif activationTime>0 then self.ATV=max(activationTime-dt*30,0)
end
end
end
K.getCenter,K.drag,K.release=nil
pianoVK[keyChar]=K
K=nil
end
end
-- Special case
pianoVK.ctrl =WIDGET.newKey{x=1115,y=605,w=75,h=75,sound=false,font=35,fText='',color='Z',code=function() if not tempoffset==-1 then _holdingCtrl() else _notHoldCS() end end}
pianoVK.ctrl :setObject(CHAR.key.ctrl )
pianoVK.shift=WIDGET.newKey{x=1205,y=605,w=75,h=75,sound=false,font=35,fText='',color='Z',code=function() if not tempoffset== 1 then _holdingShift() else _notHoldCS() end end}
pianoVK.shift:setObject(CHAR.key.shift)
end
return scene