void Update()
    {
        RunState runState = runManager.runState;

        // do nothing if there is a tutorial
        if (tutorialManager.canvas.activeSelf)
        {
            return;
        }

        // adjust light beam width based on current energy
        float targetDisplayBeamWidth = BeamWidth();

        beamWidth = Mathf.Lerp(beamWidth, targetDisplayBeamWidth, .02f);
        if (Mathf.Abs(beamWidth - targetDisplayBeamWidth) < .5f)
        {
            // extra adjustment so it coverges faster
            beamWidth = Mathf.Lerp(beamWidth, targetDisplayBeamWidth, .0f);
        }
        Light light = NoteLight.GetComponent <Light>();

        light.cookieSize = beamWidth + .015f; // this is to avoid small slivers of black initially

        // destroy all notes falling above of the beam
        foreach (Note note in new List <Note>(notes))
        {
            if (IsAboveBeam(note) && IsTouchingBeam(note))
            {
                note.OnDeflect();
                notes.Remove(note);
            }
            // only show arrows for active notes inside the beam
            note.arrow.enabled = !IsAboveBeam(note) && !note.isResolved && !note.IsSuppressed();
        }
        // deal with suppressed notes
        foreach (Note n in new List <Note>(notes))
        {
            if (n.IsSuppressed())
            {
                n.isResolved = true;
            }
            if (activity.suppressedEmotions.Contains(n.emotionType) && n.isResolved && time >= n.arrivalTime)
            {
                notes.Remove(n);
                n.OnSuppress(runState);
            }
        }
        // update time - with current settings goes in increments of about .016
        time += Time.deltaTime;
        // spawn the next preloaded note if the time has come
        if (notesToSpawn.Count > 0 && time >= notesToSpawn[0].spawnTime)
        {
            // spawn note, with time adjusted to be exact with intended pattern
            SpawnNote(notesToSpawn[0]);
            notesToSpawn.RemoveAt(0);
            if (notesToSpawn.Count == 0)
            {
                runState.CurrentActivityPlatform().isSongDone = true;
            }
        }
        // take inputs + trigger input animations
        bool       up            = Input.GetButtonDown("up");
        bool       left          = Input.GetButtonDown("left");
        bool       down          = Input.GetButtonDown("down");
        bool       right         = Input.GetButtonDown("right");
        GameObject inputAnimPreb =
            up ? upKey :
            down ? downKey :
            right ? rightKey :
            left ? leftKey : null;

        if (inputAnimPreb && Time.timeScale == 1)
        {
            Instantiate(inputAnimPreb, hitArea.transform.position, Quaternion.identity, hitArea.transform);
        }
        // detect rhythm hits/misses on the incoming group of notes
        if (notes.Count() > 0)
        {
            // deal with next wave of notes
            float epsilon = .01f;
            bool  isAboutToSpawnNearest = (notesToSpawn.Count() > 0 &&
                                           Mathf.Abs(notes[0].arrivalTime - (notesToSpawn[0].spawnTime + travelTime)) < epsilon);
            // contains all notes that are coming at the same time (only reset once all those notes have been resolved)
            if ((nearestNotes.Count() == 0 || nearestNotes.All(n => n.isResolved)) && !isAboutToSpawnNearest)
            {
                nearestNotes = notes.Where((n) => Mathf.Abs(n.arrivalTime - notes[0].arrivalTime) < epsilon).ToList();
            }
            // unresolved list has all the notes of the current group that are still active
            List <Note> unResolvedNearestNotes = nearestNotes.Where(n => !n.isResolved).ToList();
            // rhythm miss - too late
            if (nearestNotes.Count() > 0 && time > nearestNotes[0].arrivalTime + hitWindowLate)
            {
                // only trigger one miss of each type, to avoid drastic swings if song
                List <EmotionType> missTypes = new List <EmotionType>();
                foreach (Note n in unResolvedNearestNotes)
                {
                    notes.Remove(n);
                    if (!missTypes.Contains(n.emotionType))
                    {
                        missTypes.Add(n.emotionType);
                        n.OnMiss(runManager.runState);
                        // reactivate tutorials if miss many notes in a row
                        if (runManager.runState.rhythmBreakCount > 7)
                        {
                            tutorialManager.OnMissingALot();
                        }
                    }
                }
                // update late hit period so late hits do not affect future notes
                lateHitPeriodEnd = time + lateHitPeriod;
            }
            // otherwise, possible hit
            else if (up || left || down || right)
            {
                List <EmotionType> hitTypes = new List <EmotionType>();
                if (up)
                {
                    hitTypes.Add(EmotionType.None);
                }
                if (down)
                {
                    hitTypes.Add(EmotionType.anxiety);
                }
                if (left)
                {
                    hitTypes.Add(EmotionType.despair);
                }
                if (right)
                {
                    hitTypes.Add(EmotionType.frustration);
                }
                if (time > nearestNotes[0].arrivalTime - hitWindowEarly)
                {
                    List <EmotionType> noteTypes = nearestNotes.Select(note => note.emotionType).ToList();
                    // if an erroneous key was pressed, miss all these notes
                    if (!hitTypes.All(noteTypes.Contains))
                    {
                        foreach (Note n in unResolvedNearestNotes)
                        {
                            notes.Remove(n);
                            n.OnMiss(runManager.runState);
                        }
                    }
                    else
                    {
                        // hit the notes for which the key is pressed
                        foreach (Note n in unResolvedNearestNotes)
                        {
                            if (hitTypes.Contains(n.emotionType))
                            {
                                notes.Remove(n);
                                n.OnHit(time, runManager.runState);
                            }
                        }
                        // log hits for invisible suppressed notes!
                        foreach (Note n in nearestNotes)
                        {
                            if (n.IsSuppressed() && hitTypes.Contains(n.emotionType))
                            {
                                // be extra strict about hit window
                                if (time > n.arrivalTime - hitWindowEarly / 2 &&
                                    time < n.arrivalTime + hitWindowLate / 2)
                                {
                                    n.isInvisibleHit = true;
                                }
                            }
                        }
                    }
                }
                else if (time > lateHitPeriodEnd && (time > nearestNotes[0].arrivalTime - earlyHitPeriod))
                {
                    // meaningful false hits cause miss next note
                    foreach (Note n in unResolvedNearestNotes)
                    {
                        notes.Remove(n);
                        n.OnMiss(runManager.runState);
                    }
                }
            }
        }
        // no notes left - then spawn more to repeat the pattern
        // NOW WHOLE SONGS

        /* else if (activity != null && notesToSpawn.Count == 0)
         * {
         *  LoadSong();
         *  // abort if the player is almost at the end of the platform
         *  // (so no notes can spawn that reach player after they reach end)
         *  float lastSpawnTime = notesToSpawn.Last().spawnTime;
         *  ActivityPlatform ap = runState.CurrentActivityPlatform();
         *  float distLeft = ap.x + ap.length - player.transform.position.x;
         *  if ((lastSpawnTime + travelTime - time) * player.PlatformMinForwardSpeed(runState) > distLeft)
         *  {
         *      notesToSpawn.Clear();
         *      notesToSpawn.Clear();
         *  }
         * } */
        // activate appropriate tutorials
        // show rhythm tutorial once some notes appear on screen
        bool noteVisible = notes.Count > 0 && notes[0].transform.position.x < hitArea.transform.position.x + 5;

        if (gameManager.showRhythmTutorial && noteVisible)
        {
            tutorialManager.ActivateRhythmTutorial();
        }

        // show emotion note tutorial once some emotion note seen
        bool emotionNoteVisible = notes.Count > 0 && notes[0].emotionType != EmotionType.None &&
                                  !notes[0].IsSuppressed() && !IsAboveBeam(notes[0]);

        // bool emotionNoteArrived = emotionNoteVisible && (time > notes[0].arrivalTime - hitWindowEarly);
        if (gameManager.showEmotionNoteTutorial && emotionNoteVisible)
        {
            tutorialManager.ActivateEmotionNoteTutorial();
        }
    }