Spawn a SortableDictionary at specific time

Hello fellow Unity devs,
i am currently working on a Rhythm Game Project and need your help.

I have hand-mapped all notes into a SortableDictionary and saved that.
In my “Ingame” Scene I load the file and add the notes to a SortableDictionary again.
I now have this Object:

TIME (Time.time normalized), NOTE (1-5)

What I now want to do is detect when a note should spawn and Instiate a NoteObject at the top of my screen. I have the problem to check if the current time matches a time in the SortableDictionary.

My PlayManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

public class PlayManager : MonoBehaviour {

    public bool isPlaying = false;
    public float playingTime = 0f;
    public float playingStartedTime = 0f;
    AudioSource audioSource;
    SortedDictionary<float, float> notesTable;

    public void Start()
    {
        // Setup the file
        Debug.Log("Loading Song: " + PlayerPrefs.GetString("selectedSong"));
        audioSource = GetComponent<AudioSource>();
        audioSource.clip = Resources.Load("Songs/" + PlayerPrefs.GetString("selectedSong")) as AudioClip;

        // Setup the notes
        Debug.Log("Loading Song Notes: " + PlayerPrefs.GetString("selectedSong") + ".song");
        notesTable = new SortedDictionary<float, float>();

        String fileName = "Assets/Resources/Songs/" + PlayerPrefs.GetString("selectedSong") + ".song";
        if (File.Exists(fileName))
        {
            var sr = File.OpenText(fileName);
            var line = sr.ReadLine();
            while (line != null)
            {
                try
                {
                    String[] splitLine = line.Split("|".ToCharArray());
                    splitLine[1] = splitLine[1];
                    notesTable.Add(float.Parse(splitLine[0]), float.Parse(splitLine[1]));
                } catch(IndexOutOfRangeException ioore)
                {
                    String[] splitVersion = line.Split("-".ToCharArray());
                    Debug.Log("Using SONGNOTES Version: " + splitVersion[1]);
                }
                line = sr.ReadLine();
            }

            // DEBUG OUTPUT ALL NOTES
            foreach(KeyValuePair<float, float> note in notesTable)
            {
                Debug.Log(note.Key + " - " + note.Value);
            }
        } else
        {
            Debug.Log("CRITICAL ERROR! Could not find Song file!");
            return;
        }

        // Start Countdown
        StartCoroutine("startSong");
    }

    public void Update()
    {
        if (isPlaying)
        {
            playingTime = Time.time - playingStartedTime;

            // DETECT NOTE AND SPAWN NOTEOBJECT
        }
    }

    public void startPlaying()
    {
        playingStartedTime = Time.time;
        playingTime = 0;
        isPlaying = true;
        audioSource.Play();
        Debug.Log("Started Playing at" + playingStartedTime);
    }
    public void stopPlaying()
    {
        playingStartedTime = Time.time;
        isPlaying = false;
        audioSource.Stop();
        Debug.Log("Stopped Playing at " + playingStartedTime);
    }

    IEnumerator startSong()
    {
        yield return new WaitForSeconds(1f);
        Debug.Log("Play in 3 Seconds!");
        yield return new WaitForSeconds(1f);
        Debug.Log("Play in 2 Seconds!");
        yield return new WaitForSeconds(1f);
        Debug.Log("Play in 1 Second!");
        yield return new WaitForSeconds(1f);
        startPlaying();
    }
}

A sample song file

SONGNOTES-1.0.0
2.435412|2
2.799766|4
3.314533|2
3.562937|4
3.81162|1
4.225603|2
4.739169|4
4.971102|2
5.23617|4
5.502206|1
6.263303|2
6.72717|4
7.240736|1
7.489878|4
7.737736|1
8.21817|5
9.278503|4
9.477352|1
9.560068|4
9.709745|1
9.842085|4
10.10677|1
10.45507|2
10.83601|4
11.26644|2
11.51497|4
11.74687|2
12.17819|1
12.77437|4
12.98937|1
13.23833|2
13.48637|4
14.19908|2
14.6792|4
15.24244|2
15.47437|4
15.73993|2
16.253|5
18.27414|4
18.62204|1
18.80433|4
19.01964|1
19.99707|4
20.26219|4
20.5272|4
20.79263|1
21.04077|2
22.44894|2
22.714|2
22.94594|2
23.19444|1
23.42637|2
23.9565|3
26.47464|4
26.69|3
26.9882|4
27.23677|3
28.16479|1
28.69457|2
28.95963|1
29.24129|2
30.23527|4
30.69944|1
31.22927|4
32.2067|5
32.68714|3
33.21727|5
34.31067|2
34.7414|1
35.20527|2
35.45382|1
35.75197|4
36.31524|1
36.79567|2
37.04417|4
37.30923|1
37.47509|2
38.2701|4
38.75084|2
39.24754|1
39.49606|2
39.82739|1
40.22497|4
41.28754|4
41.45305|2
41.53628|4
41.65185|2
41.75125|4
41.93348|2
42.13241|4
42.38078|1
42.74525|4
43.25882|2
43.47419|4
43.72268|1
44.26947|5
44.78295|4
45.04802|1
45.44562|2
45.66098|4
46.27395|4
46.73794|1
47.23491|4
47.41705|1
47.71532|4
48.17912|5
50.29983|3
50.82986|3
51.32711|4
51.57538|4
51.80722|4
52.23795|1
52.80122|4
53.04972|1
53.26508|4
53.53015|1
53.76208|2
54.02752|4
54.27565|1
54.50758|2
54.70639|4
55.05436|2
55.28622|4
55.36905|2
55.63412|4
56.03193|1
56.28022|5
58.28479|2
58.78178|1
59.26221|2
59.52733|1
59.77579|4
60.25622|2
60.85262|2
61.08455|4
61.33305|1
61.54847|2
62.29394|4
62.82405|1
63.32105|4
63.53642|1
63.76835|2
64.29856|5
using System.Linq;
private float currentTime = 0;
public void Update()
    {
        if (isPlaying)
        {
            playingTime = Time.time - playingStartedTime;
            var kvp = notes.Where(x => x.Key < playingTime).LastOrDefault();
            if (kvp != null)
            {
                if (kvp.Key != currentTime)
                {
                      currentTime = kvp.Key;
                      InstantiateNote(kvp.Value);
                }
           }
        }
    }
public void InstantiateNote(float value)
{
      // Code here to spawn a note
}
public void startPlaying()
{
       currentTime  = 0;
       //rest of the code
}

I would also add a final time code to all your songs with a note value of -1. Then InstantiateNote can call stopPlaying when it receives a -1 for a note

Basically that LinQ line searches through the dictionary for all the notes that are less than currentTime. Find the last one. Checks if its different than our stored last currentTime. If it is we hit a new note so we instantiate it and update our currentTime (so we don’t keep spawning the same note over and over)