Esempio n. 1
0
    private int GetMicSampleBufferIndexForBeat(int beat)
    {
        double beatInMs           = BpmUtils.BeatToMillisecondsInSong(songMeta, beat);
        double beatPassedBeforeMs = songAudioPlayer.PositionInSongInMillis - beatInMs;
        int    beatPassedBeforeSamplesInMicBuffer = Convert.ToInt32(((beatPassedBeforeMs - micProfile.DelayInMillis) / 1000) * micSampleRecorder.SampleRateHz);
        // The newest sample has the highest index in the MicSampleBuffer
        int sampleBufferIndex = micSampleRecorder.MicSamples.Length - beatPassedBeforeSamplesInMicBuffer;

        sampleBufferIndex = NumberUtils.Limit(sampleBufferIndex, 0, micSampleRecorder.MicSamples.Length - 1);
        return(sampleBufferIndex);
    }
Esempio n. 2
0
 private void CreateTimeLineRects(SongMeta songMeta, PlayerController playerController, double durationOfSongInMillis)
 {
     foreach (Sentence sentence in playerController.Voice.Sentences)
     {
         double        startPosInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, sentence.MinBeat);
         double        endPosInMillis   = BpmUtils.BeatToMillisecondsInSong(songMeta, sentence.MaxBeat);
         PlayerProfile playerProfile    = playerController.PlayerProfile;
         MicProfile    micProfile       = playerController.MicProfile;
         CreateTimeLineRect(playerProfile, micProfile, startPosInMillis, endPosInMillis, durationOfSongInMillis);
     }
 }
Esempio n. 3
0
    public void FitViewportHorizontal(int minBeat, int maxBeat)
    {
        maxBeat = NumberUtils.Limit(maxBeat, 0, maxBeat);
        minBeat = NumberUtils.Limit(minBeat, 0, maxBeat);
        double minPositionInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, minBeat);
        double maxPositionInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, maxBeat);
        int    newViewportX        = (int)Math.Floor(minPositionInMillis);
        int    newViewportWidth    = (int)Math.Ceiling(maxPositionInMillis - minPositionInMillis);

        SetViewportHorizontal(newViewportX, newViewportWidth);
    }
Esempio n. 4
0
    public void FitViewportHorizontalToNotes(List <Note> notes)
    {
        int    minBeat             = notes.Select(it => it.StartBeat).Min();
        int    maxBeat             = notes.Select(it => it.StartBeat).Max();
        double minPositionInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, minBeat);
        double maxPositionInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, maxBeat);
        int    newViewportX        = (int)Math.Floor(minPositionInMillis);
        int    newViewportWidth    = (int)Math.Ceiling(maxPositionInMillis - minPositionInMillis);

        SetViewportHorizontal(newViewportX, newViewportWidth);
    }
Esempio n. 5
0
    protected override void OnRecordingEvent(RecordingEvent recordingEvent)
    {
        int   firstBeatToAnalyze = nextBeatToAnalyze;
        float positionInSongInMillisConsideringMicDelay = (float)songAudioPlayer.PositionInSongInMillis - MicDelayInMillis;
        int   currentBeatConsideringMicDelay            = (int)BpmUtils.MillisecondInSongToBeat(songMeta, positionInSongInMillisConsideringMicDelay);

        for (int beatToAnalyze = firstBeatToAnalyze; beatToAnalyze < currentBeatConsideringMicDelay; beatToAnalyze++)
        {
            AnalyzeBeatAndNotify(beatToAnalyze);
            nextBeatToAnalyze = beatToAnalyze + 1;
        }
    }
Esempio n. 6
0
    private double GetCurrentBeat()
    {
        double positionInMillis = songAudioPlayer.PositionInSongInMillis;

        if (micProfile != null)
        {
            positionInMillis -= micProfile.DelayInMillis;
        }
        double currentBeat = BpmUtils.MillisecondInSongToBeat(songMeta, positionInMillis);

        return(currentBeat);
    }
Esempio n. 7
0
 public void OnInjectionFinished()
 {
     beatsPerSecond = BpmUtils.GetBeatsPerSecond(songMeta);
     playerNoteRecorder.RecordedNoteStartedEventStream.Subscribe(recordedNoteStartedEvent =>
     {
         DisplayRecordedNote(recordedNoteStartedEvent.RecordedNote);
     });
     playerNoteRecorder.RecordedNoteContinuedEventStream.Subscribe(recordedNoteContinuedEvent =>
     {
         DisplayRecordedNote(recordedNoteContinuedEvent.RecordedNote);
     });
 }
    // Compute the upcoming notes, i.e., the visible notes that have not yet been finished at the playback position.
    private List <Note> GetUpcomingSortedNotes(double positionInSongInMillis)
    {
        List <Note> result = songMeta.GetVoices()
                             .Where(voice => settings.SongEditorSettings.HideVoices.IsNullOrEmpty() ||
                                    !settings.SongEditorSettings.HideVoices.Contains(voice.Name))
                             .SelectMany(voice => voice.Sentences)
                             .SelectMany(sentence => sentence.Notes)
                             .Where(note => BpmUtils.BeatToMillisecondsInSong(songMeta, note.EndBeat) > positionInSongInMillis)
                             .ToList();

        result.Sort(Note.comparerByStartBeat);
        return(result);
    }
Esempio n. 9
0
    void Start()
    {
        MillisecondsPerBeat = BpmUtils.MillisecondsPerBeat(songMeta);

        // Initialize Viewport
        SetViewportX(0, true);
        SetViewportY(MidiUtils.MidiNoteMin + (MidiUtils.SingableNoteRange / 4), true);
        SetViewportWidth(3000, true);
        SetViewportHeight(MidiUtils.SingableNoteRange / 2, true);

        songAudioPlayer.PositionInSongEventStream.Subscribe(SetPositionInSongInMillis);

        FitViewportToVoices();
    }
Esempio n. 10
0
    protected Note GetNoteAtCurrentBeat(double currentBeat)
    {
        Sentence currentSentence = playerController?.GetRecordingSentence();

        if (currentSentence == null)
        {
            return(null);
        }

        double micDelayInBeats   = (playerController.MicProfile == null) ? 0 : BpmUtils.MillisecondInSongToBeatWithoutGap(songMeta, playerController.MicProfile.DelayInMillis);
        Note   noteAtCurrentBeat = PlayerNoteRecorder.GetNoteAtBeat(currentSentence, currentBeat - micDelayInBeats);

        return(noteAtCurrentBeat);
    }
Esempio n. 11
0
 public virtual void Init(int lineCount)
 {
     lineDisplayer.UpdateLines(lineCount);
     SetNoteRowCount(lineCount * 2);
     beatsPerSecond = BpmUtils.GetBeatsPerSecond(songMeta);
     playerNoteRecorder.RecordedNoteStartedEventStream.Subscribe(recordedNoteStartedEvent =>
     {
         DisplayRecordedNote(recordedNoteStartedEvent.RecordedNote);
     });
     playerNoteRecorder.RecordedNoteContinuedEventStream.Subscribe(recordedNoteContinuedEvent =>
     {
         DisplayRecordedNote(recordedNoteContinuedEvent.RecordedNote);
     });
 }
Esempio n. 12
0
    private void UpdateLines()
    {
        verticalGridImage.ClearTexture();

        int viewportStartBeat    = noteArea.MinBeatInViewport;
        int viewportEndBeat      = noteArea.MaxBeatInViewport;
        int viewportWidthInBeats = viewportEndBeat - viewportStartBeat;

        int drawStepRough = viewportWidthInBeats / 12;

        if (viewportWidthInBeats <= 256)
        {
            drawStepRough = Math.Max(1, (int)Math.Log10(viewportWidthInBeats) * 8);
        }

        int drawStepFine = 0;

        // Draw additional lines if zoomed in enough
        if (viewportWidthInBeats <= 128)
        {
            drawStepFine = drawStepRough / 2;
        }

        // Draw every line if zoomed in enough
        if (viewportWidthInBeats <= 48)
        {
            drawStepRough = 4;
            drawStepFine  = 1;
        }

        for (int beat = viewportStartBeat; beat < viewportEndBeat; beat++)
        {
            double beatPosInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, beat);

            bool hasRoughLine = drawStepRough > 0 && (beat % drawStepRough == 0);
            if (hasRoughLine)
            {
                DrawVerticalGridLine(beatPosInMillis, lineHighlightColor);
            }

            bool hasFineLine = drawStepFine > 0 && (beat % drawStepFine == 0);
            if (hasFineLine && !hasRoughLine)
            {
                DrawVerticalGridLine(beatPosInMillis, lineNormalColor);
            }
        }

        verticalGridImage.ApplyTexture();
    }
    override protected void PositionUiNote(RectTransform uiNote, int midiNote, double noteStartBeat, double noteEndBeat)
    {
        // The VerticalPitchIndicator's position is the position where recording happens.
        // Thus, a note with startBeat == (currentBeat + micDelayInBeats) will have its left side drawn where the VerticalPitchIndicator is.
        double millisInSong = songAudioPlayer.PositionInSongInMillis - micDelayInMillis;
        double currentBeatConsideringMicDelay = BpmUtils.MillisecondInSongToBeat(songMeta, millisInSong);

        Vector2 anchorY      = GetAnchorYForMidiNote(midiNote);
        float   anchorXStart = (float)((noteStartBeat - currentBeatConsideringMicDelay) / displayedBeats) + pitchIndicatorAnchorX;
        float   anchorXEnd   = (float)((noteEndBeat - currentBeatConsideringMicDelay) / displayedBeats) + pitchIndicatorAnchorX;

        uiNote.anchorMin = new Vector2(anchorXStart, anchorY.x);
        uiNote.anchorMax = new Vector2(anchorXEnd, anchorY.y);
        uiNote.MoveCornersToAnchors();
    }
Esempio n. 14
0
    private void SetViewportWidthWithoutChangeEvent(int newViewportWidth)
    {
        if (newViewportWidth < ViewportMinWidth)
        {
            newViewportWidth = ViewportMinWidth;
        }
        if (newViewportWidth >= ViewportMaxWidth)
        {
            newViewportWidth = ViewportMaxWidth;
        }

        ViewportWidth             = newViewportWidth;
        MaxMillisecondsInViewport = ViewportX + ViewportWidth;
        MaxBeatInViewport         = (int)Math.Ceiling(BpmUtils.MillisecondInSongToBeat(songMeta, MaxMillisecondsInViewport));
    }
Esempio n. 15
0
    private void SetViewportWidthWithoutChangeEvent(int newViewportWidth)
    {
        if (newViewportWidth < 1000)
        {
            newViewportWidth = 1000;
        }
        if (newViewportWidth >= songAudioPlayer.DurationOfSongInMillis)
        {
            newViewportWidth = (int)songAudioPlayer.DurationOfSongInMillis;
        }

        ViewportWidth             = newViewportWidth;
        MaxMillisecondsInViewport = ViewportX + ViewportWidth;
        MaxBeatInViewport         = (int)Math.Ceiling(BpmUtils.MillisecondInSongToBeat(songMeta, MaxMillisecondsInViewport));
    }
    private void DrawNotes(Voice voice, Color color)
    {
        if (dynamicTexture == null)
        {
            return;
        }

        List <Note> notes = voice.Sentences.SelectMany(sentence => sentence.Notes).ToList();

        if (notes.IsNullOrEmpty())
        {
            return;
        }

        int songDurationInMillis = (int)Math.Ceiling(songAudioPlayer.AudioClip.length * 1000);

        // constant offset to
        // (a) ensure that midiNoteRange > 0,
        // (b) have some space to the border of the texture.
        int minMaxOffset  = 1;
        int midiNoteMin   = notes.Select(note => note.MidiNote).Min() - minMaxOffset;
        int midiNoteMax   = notes.Select(note => note.MidiNote).Max() + minMaxOffset;
        int midiNoteRange = midiNoteMax - midiNoteMin;

        foreach (Note note in notes)
        {
            double startMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, note.StartBeat);
            double endMillis   = BpmUtils.BeatToMillisecondsInSong(songMeta, note.EndBeat);

            int yStart  = dynamicTexture.TextureHeight * (note.MidiNote - midiNoteMin) / midiNoteRange;
            int yLength = dynamicTexture.TextureHeight / midiNoteRange * 2;
            int yEnd    = yStart + yLength;
            int xStart  = (int)(dynamicTexture.TextureWidth * startMillis / songDurationInMillis);
            int xEnd    = (int)(dynamicTexture.TextureWidth * endMillis / songDurationInMillis);
            if (xEnd < xStart)
            {
                ObjectUtils.Swap(ref xStart, ref xEnd);
            }

            xEnd = xEnd < dynamicTexture.TextureWidth
                ? xEnd
                : dynamicTexture.TextureWidth - 1;
            yEnd = yEnd < dynamicTexture.TextureHeight
                ? yEnd
                : dynamicTexture.TextureHeight - 1;
            dynamicTexture.DrawRectByCorners(xStart, yStart, xEnd, yEnd, color);
        }
    }
Esempio n. 17
0
    public void SkipToNextSentence()
    {
        double nextStartBeat = PlayerControllers.Select(it => it.GetNextStartBeat()).Min();

        if (nextStartBeat < 0)
        {
            return;
        }

        double targetPositionInMillis = BpmUtils.BeatToMillisecondsInSong(SongMeta, nextStartBeat) - 500;

        if (targetPositionInMillis > 0 && targetPositionInMillis > PositionInSongInMillis)
        {
            songAudioPlayer.PositionInSongInMillis = targetPositionInMillis;
        }
    }
Esempio n. 18
0
    private void StopMidiSoundForLeftNotes(double positionInSongInMillis)
    {
        List <Note> newlyLeftNotes = new List <Note>();

        foreach (Note note in currentlyPlayingNotes)
        {
            double endMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, note.EndBeat);

            if (endMillis < positionInSongInMillis)
            {
                newlyLeftNotes.Add(note);
                midiManager.StopMidiNote(note.MidiNote);
            }
        }
        newlyLeftNotes.ForEach(it => currentlyPlayingNotes.Remove(it));
    }
    private void PlayAudioInRangeOfNotes(List <Note> notes)
    {
        if (songAudioPlayer.IsPlaying)
        {
            return;
        }

        int    minBeat   = notes.Select(it => it.StartBeat).Min();
        int    maxBeat   = notes.Select(it => it.EndBeat).Max();
        double maxMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, maxBeat);
        double minMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, minBeat);

        songEditorSceneController.StopPlaybackAfterPositionInSongInMillis = maxMillis;
        songAudioPlayer.PositionInSongInMillis = minMillis;
        songAudioPlayer.PlayAudio();
    }
Esempio n. 20
0
    private void UpdateLabels()
    {
        beatLabelContainer.DestroyAllDirectChildren();
        secondLabelContainer.DestroyAllDirectChildren();

        int viewportStartBeat    = noteArea.MinBeatInViewport;
        int viewportEndBeat      = noteArea.MaxBeatInViewport;
        int viewportWidthInBeats = viewportEndBeat - viewportStartBeat;

        int drawStepRough = viewportWidthInBeats / 12;

        if (viewportWidthInBeats <= 256)
        {
            drawStepRough = Math.Max(1, (int)Math.Log10(viewportWidthInBeats) * 8);
        }

        int drawStepVeryRough = drawStepRough * 2;

        if (viewportWidthInBeats <= 48)
        {
            drawStepVeryRough = 6;
            drawStepRough     = 4;
        }

        double millisPerBeat      = BpmUtils.MillisecondsPerBeat(songMeta);
        double labelWidthInMillis = millisPerBeat * drawStepRough;

        for (int beat = viewportStartBeat; beat < viewportEndBeat; beat++)
        {
            double beatPosInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, beat);

            bool hasRoughLine = drawStepRough > 0 && (beat % drawStepRough == 0);
            if (hasRoughLine)
            {
                Text uiText = CreateLabel(beatPosInMillis, labelWidthInMillis, beatLabelPrefab, beatLabelContainer);
                uiText.text = beat.ToString();
            }

            bool hasSecondLabel = drawStepVeryRough > 0 && (beat % drawStepVeryRough == 0);
            if (hasSecondLabel)
            {
                double beatPosInSeconds = beatPosInMillis / 1000;
                Text   uiText           = CreateLabel(beatPosInMillis, labelWidthInMillis, secondLabelPrefab, secondLabelContainer);
                uiText.text = beatPosInSeconds.ToString("F3", CultureInfo.InvariantCulture) + " s";
            }
        }
    }
Esempio n. 21
0
    public void OnPointerClick(PointerEventData ped)
    {
        // Ignore any drag motion. Dragging is used to select notes.
        float dragDistance = Vector2.Distance(ped.pressPosition, ped.position);
        bool  isDrag       = dragDistance > 5f;

        if (isDrag)
        {
            return;
        }

        // Only listen to left mouse button. Right mouse button is for context menu.
        if (ped.button != PointerEventData.InputButton.Left &&
            Touch.activeTouches.Count == 0)
        {
            return;
        }

        // Check double click to edit lyrics
        bool isDoubleClick = Time.time - lastClickTime < InputUtils.DoubleClickThresholdInSeconds;

        lastClickTime = Time.time;
        if (isDoubleClick)
        {
            StartEditingNoteText();
            return;
        }

        // Select / deselect notes via Shift.
        if (InputUtils.IsKeyboardShiftPressed())
        {
            if (selectionController.IsSelected(Note))
            {
                selectionController.RemoveFromSelection(this);
            }
            else
            {
                selectionController.AddToSelection(this);
            }
        }
        else if (!InputUtils.IsKeyboardControlPressed())
        {
            // Move the playback position to the start of the note
            double positionInSongInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, Note.StartBeat);
            songAudioPlayer.PositionInSongInMillis = positionInSongInMillis;
        }
    }
Esempio n. 22
0
    private void RecordNote(int midiNote, double positionInSongInMillis, ESongEditorLayer targetLayer)
    {
        int currentBeat = (int)BpmUtils.MillisecondInSongToBeat(songMeta, positionInSongInMillis);

        if (lastRecordedNote != null &&
            lastRecordedNote.MidiNote == midiNote &&
            lastPitchDetectedFrame == Time.frameCount - 1)
        {
            ContinueLastRecordedNote(currentBeat);
        }
        else
        {
            CreateNewRecordedNote(midiNote, currentBeat, targetLayer);
        }

        lastPitchDetectedFrame = Time.frameCount;
    }
Esempio n. 23
0
    private void HandleStartOfNote(MidiEvent midiEvent, Dictionary <int, Note> midiPitchToNoteUnderConstruction)
    {
        int  midiPitch         = midiEvent.parameter1;
        int  deltaTimeInMillis = GetDeltaTimeInMillis(midiEvent);
        Note newNote           = new Note();
        int  startBeat         = (int)BpmUtils.MillisecondInSongToBeat(songMeta, deltaTimeInMillis);

        newNote.SetStartAndEndBeat(startBeat, startBeat);
        newNote.SetMidiNote(midiPitch);

        if (midiPitchToNoteUnderConstruction.ContainsKey(midiPitch))
        {
            Debug.LogWarning($"A Note with pitch {midiPitch} started but did not end before the next. The note will be ignored.");
        }

        midiPitchToNoteUnderConstruction[midiPitch] = newNote;
    }
Esempio n. 24
0
    private void SetViewportXWithoutChangeEvent(int newViewportX)
    {
        if (newViewportX < 0)
        {
            newViewportX = 0;
        }
        if (newViewportX >= songAudioPlayer.DurationOfSongInMillis)
        {
            newViewportX = (int)songAudioPlayer.DurationOfSongInMillis;
        }

        ViewportX = newViewportX;
        MinMillisecondsInViewport = ViewportX;
        MaxMillisecondsInViewport = ViewportX + ViewportWidth;
        MinBeatInViewport         = (int)Math.Floor(BpmUtils.MillisecondInSongToBeat(songMeta, MinMillisecondsInViewport));
        MaxBeatInViewport         = (int)Math.Ceiling(BpmUtils.MillisecondInSongToBeat(songMeta, MaxMillisecondsInViewport));
    }
Esempio n. 25
0
    private void SetViewportXWithoutChangeEvent(int newViewportX)
    {
        if (newViewportX < 0)
        {
            newViewportX = 0;
        }
        if (newViewportX >= ViewportMaxWidth)
        {
            newViewportX = ViewportMaxWidth;
        }

        ViewportX = newViewportX;
        MinMillisecondsInViewport = ViewportX;
        MaxMillisecondsInViewport = ViewportX + ViewportWidth;
        MinBeatInViewport         = (int)Math.Floor(BpmUtils.MillisecondInSongToBeat(songMeta, MinMillisecondsInViewport));
        MaxBeatInViewport         = (int)Math.Ceiling(BpmUtils.MillisecondInSongToBeat(songMeta, MaxMillisecondsInViewport));
    }
Esempio n. 26
0
    public void SkipToNextSentence()
    {
        double nextStartBeat = PlayerControllers.Select(it => it.GetNextStartBeat()).Min();

        if (nextStartBeat < 0)
        {
            return;
        }

        // For debugging, go fast to next lyrics. In production, give the player some time to prepare.
        double offsetInMillis         = Application.isEditor ? 500 : 1500;
        double targetPositionInMillis = BpmUtils.BeatToMillisecondsInSong(SongMeta, nextStartBeat) - offsetInMillis;

        if (targetPositionInMillis > 0 && targetPositionInMillis > PositionInSongInMillis)
        {
            songAudioPlayer.PositionInSongInMillis = targetPositionInMillis;
        }
    }
Esempio n. 27
0
    void Start()
    {
        MillisecondsPerBeat = BpmUtils.MillisecondsPerBeat(songMeta);

        // Initialize Viewport
        int x      = 0;
        int width  = 5000;
        int y      = MidiUtils.MidiNoteMin + (MidiUtils.SingableNoteRange / 4);
        int height = MidiUtils.SingableNoteRange / 2;

        SetViewport(x, y, width, height);

        songAudioPlayer.PositionInSongEventStream.Subscribe(SetPositionInSongInMillis);

        songMetaChangeEventStream.Subscribe(OnSongMetaChanged);

        FitViewportVerticalToNotes();
    }
Esempio n. 28
0
    public void OnPointerClick(PointerEventData ped)
    {
        // Ignore any drag motion. Dragging is used to select notes.
        float dragDistance = Vector2.Distance(ped.pressPosition, ped.position);
        bool  isDrag       = dragDistance > 5f;

        if (isDrag)
        {
            return;
        }

        // Only listen to left mouse button. Right mouse button is for context menu.
        if (ped.button != PointerEventData.InputButton.Left)
        {
            return;
        }

        if (ped.clickCount == 1)
        {
            // Select / deselect notes via Shift.
            if (Input.GetKey(KeyCode.LeftShift))
            {
                if (selectionController.IsSelected(Note))
                {
                    selectionController.RemoveFromSelection(this);
                }
                else
                {
                    selectionController.AddToSelection(this);
                }
            }
            else
            {
                // Move the playback position to the start of the note
                double positionInSongInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, Note.StartBeat);
                songAudioPlayer.PositionInSongInMillis = positionInSongInMillis;
            }
        }
        else if (ped.clickCount == 2)
        {
            StartEditingNoteText();
        }
    }
Esempio n. 29
0
    public bool IsNoteVisible(Note note)
    {
        // Check y axis, which is the midi note
        bool isMidiNoteOk = (note.MidiNote >= MinMidiNoteInViewport) &&
                            (note.MidiNote <= MaxMidiNoteInViewport);

        if (!isMidiNoteOk)
        {
            return(false);
        }

        // Check x axis, which is the position in the song
        double startPosInMillis = BpmUtils.BeatToMillisecondsInSong(songMeta, note.StartBeat);
        double endPosInMillis   = BpmUtils.BeatToMillisecondsInSong(songMeta, note.EndBeat);
        bool   isMillisecondsOk = (startPosInMillis >= MinMillisecondsInViewport) &&
                                  (endPosInMillis <= MaxMillisecondsInViewport);

        return(isMillisecondsOk);
    }
    override public void Init(int lineCount)
    {
        if (micProfile != null)
        {
            micDelayInMillis = micProfile.DelayInMillis;
        }
        base.Init(lineCount);

        upcomingNotes = voice.Sentences
                        .SelectMany(sentence => sentence.Notes)
                        .ToList();
        upcomingNotes.Sort(Note.comparerByStartBeat);

        avgMidiNote        = CalculateAvgMidiNote(voice.Sentences.SelectMany(sentence => sentence.Notes).ToList());
        maxNoteRowMidiNote = avgMidiNote + (noteRowCount / 2);
        minNoteRowMidiNote = avgMidiNote - (noteRowCount / 2);

        displayedBeats = (int)Math.Ceiling(BpmUtils.GetBeatsPerSecond(songMeta) * displayedNoteDurationInSeconds);
    }