public void MoveNotesToVoice(SongMeta songMeta, List <Note> selectedNotes, string voiceName)
    {
        Voice           voice            = SongMetaUtils.GetOrCreateVoice(songMeta, voiceName);
        List <Sentence> changedSentences = new List <Sentence>();

        foreach (Note note in selectedNotes)
        {
            Sentence oldSentence = note.Sentence;
            // Find a sentence in the new voice for the note
            Sentence sentenceForNote = SongMetaUtils.FindExistingSentenceForNote(voice.Sentences, note);
            if (sentenceForNote == null)
            {
                // Create new sentence in the voice.
                // Use the min and max value from the sentence of the original note if possible.
                if (note.Sentence != null)
                {
                    sentenceForNote = new Sentence(note.Sentence.MinBeat, note.Sentence.MaxBeat);
                }
                else
                {
                    sentenceForNote = new Sentence();
                }
                sentenceForNote.SetVoice(voice);
            }
            sentenceForNote.AddNote(note);

            changedSentences.Add(sentenceForNote);
            if (oldSentence != null)
            {
                // Remove old sentence if empty now
                if (oldSentence.Notes.Count == 0 && oldSentence.Voice != null)
                {
                    oldSentence.SetVoice(null);
                }
                else
                {
                    changedSentences.Add(oldSentence);
                }
            }
        }

        // Fit changed sentences to their notes (make them as small as possible)
        foreach (Sentence sentence in changedSentences)
        {
            sentence.FitToNotes();
        }
    }
    public MovedNotesToVoiceEvent MoveNotesToVoice(SongMeta songMeta, List <Note> selectedNotes, string voiceName)
    {
        Voice           targetVoice      = SongMetaUtils.GetOrCreateVoice(songMeta, voiceName);
        List <Sentence> changedSentences = new();
        List <Sentence> removedSentences = new();
        List <Sentence> createdSentences = new();

        List <SentenceWithRange> createdSentencesWithRange = new();

        List <Sentence> sortedTargetSentences = targetVoice.Sentences.ToList();

        sortedTargetSentences.Sort(Sentence.comparerByStartBeat);

        selectedNotes.Sort(Note.comparerByStartBeat);
        selectedNotes.ForEach(note =>
        {
            Sentence oldSentence = note.Sentence;
            // Find or create a sentence in the target voice for the note
            Sentence targetSentence;
            Sentence existingTargetSentence = SongMetaUtils.FindExistingSentenceForNote(sortedTargetSentences, note);
            if (existingTargetSentence == null)
            {
                SentenceWithRange existingSentenceWithRange = createdSentencesWithRange
                                                              .FirstOrDefault(sentenceWithRange => sentenceWithRange.ContainsBeatRange(note.StartBeat, note.EndBeat));
                if (existingSentenceWithRange != null)
                {
                    existingTargetSentence = existingSentenceWithRange.Sentence;
                }
            }

            if (existingTargetSentence != null)
            {
                targetSentence = existingTargetSentence;
            }
            else
            {
                // Create sentence to fill the gap between adjacent sentences.
                Sentence previousSentence = sortedTargetSentences
                                            .LastOrDefault(sentence => sentence.MaxBeat < note.StartBeat);
                Sentence nextSentence = sortedTargetSentences
                                        .LastOrDefault(sentence => sentence.MinBeat > note.EndBeat);
                int newSentenceFromBeat = previousSentence != null
                    ? previousSentence.ExtendedMaxBeat
                    : int.MinValue;
                int newSentenceUntilBeat = nextSentence != null
                    ? nextSentence.MinBeat
                    : int.MaxValue;
                Sentence createdSentence = new(newSentenceFromBeat, newSentenceUntilBeat);
                createdSentence.SetVoice(targetVoice);

                createdSentences.Add(createdSentence);
                sortedTargetSentences.Add(createdSentence);
                sortedTargetSentences.Sort(Sentence.comparerByStartBeat);

                // Remember this sentence with its full range (that fills the gap between adjacent sentences)
                // The MinBeat and MaxBeat of the sentence will change to fit the notes,
                // such that the original range has to be stored in a dedicated data structure.
                createdSentencesWithRange.Add(new SentenceWithRange(createdSentence, newSentenceFromBeat, newSentenceUntilBeat));

                targetSentence = createdSentence;
            }
            targetSentence.AddNote(note);

            // Set lyrics if none yet (otherwise, there is a warning because of missing lyrics)
            if (note.Text.IsNullOrEmpty())
            {
                note.SetText(" ");
            }

            changedSentences.Add(targetSentence);
            if (oldSentence != null)
            {
                // Remove old sentence if empty now
                if (oldSentence.Notes.Count == 0 && oldSentence.Voice != null)
                {
                    removedSentences.Add(oldSentence);
                    oldSentence.SetVoice(null);
                }
                else
                {
                    changedSentences.Add(oldSentence);
                }
            }
        });

        // Fit sentences to their notes (make them as small as possible)
        changedSentences
        .Union(createdSentences)
        .ForEach(sentence =>
        {
            sentence.FitToNotes();
        });

        return(new MovedNotesToVoiceEvent(selectedNotes, changedSentences, removedSentences));
    }