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); }
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); } }
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); }
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); }
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; } }
private double GetCurrentBeat() { double positionInMillis = songAudioPlayer.PositionInSongInMillis; if (micProfile != null) { positionInMillis -= micProfile.DelayInMillis; } double currentBeat = BpmUtils.MillisecondInSongToBeat(songMeta, positionInMillis); return(currentBeat); }
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); }
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(); }
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); }
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); }); }
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(); }
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)); }
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); } }
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; } }
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(); }
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"; } } }
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; } }
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; }
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; }
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)); }
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)); }
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; } }
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(); }
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(); } }
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); }