Can you guess what this game is about using it's source code:
---@type crove.Image[]
local sprites = {}
sprites.notes = {}
sprites.button_off = {}
sprites.button_on = {}

local notes = {}
local misses = 0
local hits = 0

---@param tab table
---@param val any
---@return boolean, number?
local function match(tab, val)
    for pos, value in ipairs(tab) do
        if value == val then return true, pos end
    end
    return false
end

local button_to_key = {
    keys.d,
    keys.f,
    keys.j,
    keys.k
}

local button_pos_origin = {
    {
        x = 10,
        y = 3
    },
    {
        x = 20,
        y = 3
    },
    {
        x = 30,
        y = 3
    },
    {
        x = 40,
        y = 3
    }
}
function crove.load()
    for i = 1, 4 do
        sprites.notes[#sprites.notes+1] = crove.graphics.newImage(("note%s.nfp"):format(i))
    end
    sprites.button_on[#sprites.button_on+1] = crove.graphics.newImage("button-on.nfp")
    sprites.button_off[#sprites.button_off+1] = crove.graphics.newImage("button-off.nfp")
end

function crove.update()
    local width_halved = math.floor(crove.graphics.getWidth()/2)
    button_pos_origin[1].x = width_halved - 18 - sprites.button_on[1].__size[1]/2
    button_pos_origin[2].x = width_halved - 6  - sprites.button_on[1].__size[1]/2
    button_pos_origin[3].x = width_halved + 6  - sprites.button_on[1].__size[1]/2
    button_pos_origin[4].x = width_halved + 18 - sprites.button_on[1].__size[1]/2
    for note_pos, note in ipairs(notes) do
            local notey = note.timestamp - crove.timer.getTime()
            if notey < -0.5 then
                table.remove(notes, note_pos)
                misses = misses + 1
            end
    end
end

function crove.keyPressed(key, isHeld)
    if key == keys.l then
        local but = math.random(1, 4)
        notes[#notes+1] = {button = but, timestamp = crove.timer.getTime()+2}
        crove.audio.playNote("iron_xylophone", 1, 6+12/but)
    end
    local is_button_key, button = match(button_to_key, key)
    if is_button_key then
        local is_hit = false
        for note_pos, note in ipairs(notes) do
            if note.button == button then
                local notey = note.timestamp - crove.timer.getTime()
                if notey <= 0.2 and notey >= -0.2 then
                    table.remove(notes, note_pos)
                    hits = hits + 1
                    is_hit = true
                    break
                end
            end
        end
        if not is_hit then
            misses = misses + 1
        end
    end
end

function crove.draw()
    for i = 1, 4 do
        if crove.keyboard.isDown(button_to_key[i]) == true then
            crove.graphics.draw(sprites.button_on[1], button_pos_origin[i].x, button_pos_origin[i].y)
        else
            crove.graphics.draw(sprites.button_off[1], button_pos_origin[i].x, button_pos_origin[i].y)
        end
    end
    for _, note in ipairs(notes) do
        local notex = button_pos_origin[note.button].x
        local notey = button_pos_origin[note.button].y+(note.timestamp - crove.timer.getTime())*40
        crove.graphics.draw(sprites.notes[note.button], notex, notey)
    end
    crove.graphics.print(tostring(crove.timer.getAverageFPS()), 1, 1)
    crove.graphics.print(tostring(crove.timer.getTime()), 1, 2)
    crove.graphics.print(tostring(#notes), 1, 3)
    crove.graphics.print(("hits: %s"):format(hits), 1, 4)
    crove.graphics.print(("misses: %s"):format(misses), 1, 5)
end