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