Exemple #1
0
    void Update()
    {
        // No sentence to analyze left (all done).
        if (RecordingSentence == null)
        {
            return;
        }

        // Analyze the next beat with fully recorded mic samples
        double nextBeatToAnalyzeEndPositionInMs = BpmUtils.BeatToMillisecondsInSong(songMeta, BeatToAnalyze + 1);

        if (nextBeatToAnalyzeEndPositionInMs < songAudioPlayer.PositionInSongInMillis - micProfile.DelayInMillis)
        {
            // The beat has passed and should have recorded samples in the mic buffer. Analyze the samples now.
            PitchEvent pitchEvent            = GetPitchEventOfBeat(BeatToAnalyze);
            Note       currentOrUpcomingNote = currentAndUpcomingNotesInRecordingSentence.IsNullOrEmpty()
                ? null
                : currentAndUpcomingNotesInRecordingSentence[0];
            Note noteAtBeat = (currentOrUpcomingNote.StartBeat <= BeatToAnalyze && BeatToAnalyze < currentOrUpcomingNote.EndBeat)
                ? currentOrUpcomingNote
                : null;

            FirePitchEvent(pitchEvent, BeatToAnalyze, noteAtBeat, RecordingSentence);

            GoToNextBeat();
        }
    }
    private void HandlePitchEvent(PitchEvent pitchEvent, double currentBeat)
    {
        if (pitchEvent == null || pitchEvent.MidiNote <= 0)
        {
            if (lastRecordedNote != null)
            {
                HandleRecordedNoteEnded(currentBeat);
            }
        }
        else
        {
            if (lastRecordedNote != null)
            {
                if (MidiUtils.GetRelativePitchDistance(lastRecordedNote.RoundedMidiNote, pitchEvent.MidiNote) <= roundingDistance)
                {
                    // Continue singing on same pitch
                    HandleRecordedNoteContinued(currentBeat);
                }
                else
                {
                    // Continue singing on different pitch. Finish the last recorded note.
                    HandleRecordedNoteEnded(currentBeat);
                }
            }

            // The lastRecordedNote could be ended above, so the following null check is not redundant.
            if (lastRecordedNote == null && currentBeat >= nextNoteStartBeat)
            {
                // Start singing of a new note
                HandleRecordedNoteStarted(pitchEvent.MidiNote, currentBeat);
            }
        }
    }
    private void TestPitchDetection(Func <int, IAudioSamplesAnalyzer> audioSamplesAnalyzerProvider)
    {
        MicProfile micProfile = CreateDummyMicProfile();

        string assetsPath      = Application.dataPath;
        string sineWaveToneDir = assetsPath + "/Common/Audio/SineWaveTones/";
        Dictionary <string, string> pathToExpectedMidiNoteNameMap = new();

        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-a3-220hz.ogg", "A3");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-a4-440hz.ogg", "A4");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-a5-880hz.ogg", "A5");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-c2-61,74hz.ogg", "C2");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-c4-261,64hz.ogg", "C4");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-c6-1046,50hz.ogg", "C6");

        foreach (KeyValuePair <string, string> pathAndNoteName in pathToExpectedMidiNoteNameMap)
        {
            // Load the audio clip
            string    uri       = "file://" + pathAndNoteName.Key;
            AudioClip audioClip = AudioUtils.GetAudioClipUncached(uri, false);
            float[]   samples   = new float[audioClip.samples];
            audioClip.GetData(samples, 0);

            // Analyze the samples
            IAudioSamplesAnalyzer audioSamplesAnalyzer = audioSamplesAnalyzerProvider(audioClip.frequency);
            PitchEvent            pitchEvent           = audioSamplesAnalyzer.ProcessAudioSamples(samples, 0, samples.Length - 1, micProfile);

            // Check result
            Assert.NotNull(pitchEvent, $"No pitch detected when analyzing audio resource {uri}");
            string expectedName = pathAndNoteName.Value;
            string analyzedName = MidiUtils.GetAbsoluteName(pitchEvent.MidiNote);
            Assert.AreEqual(expectedName, analyzedName,
                            $"Expected {expectedName} but was {analyzedName} when analyzing audio resource {uri}");
        }
    }
    protected override void OnRecordingEvent(RecordingEvent recordingEvent)
    {
        // Detect the pitch of the sample
        int newSampleLength = recordingEvent.NewSampleCount;

        bufferedNotAnalyzedSampleCount += newSampleLength;

        // Wait until enough new samples are buffered
        if (bufferedNotAnalyzedSampleCount < MinSampleCountToUse)
        {
            return;
        }

        // Do not analyze more than necessary
        if (bufferedNotAnalyzedSampleCount > MaxSampleCountToUse)
        {
            bufferedNotAnalyzedSampleCount = MaxSampleCountToUse;
        }

        // Analyze the newest portion of the not-yet-analyzed MicSamples
        int        startIndex = recordingEvent.MicSamples.Length - bufferedNotAnalyzedSampleCount;
        int        endIndex   = recordingEvent.MicSamples.Length;
        PitchEvent pitchEvent = AudioSamplesAnalyzer.ProcessAudioSamples(recordingEvent.MicSamples, startIndex, endIndex, MicProfile);

        bufferedNotAnalyzedSampleCount = 0;

        // Notify listeners
        pitchEventStream.OnNext(pitchEvent);
    }
Exemple #5
0
    void Update()
    {
        // No sentence to analyze left (all done).
        if (RecordingSentence == null)
        {
            return;
        }

        // Analyze the next beat with fully recorded mic samples
        double nextBeatToAnalyzeEndPositionInMs = BpmUtils.BeatToMillisecondsInSong(songMeta, BeatToAnalyze + 1);

        if (nextBeatToAnalyzeEndPositionInMs < songAudioPlayer.PositionInSongInMillis - micProfile.DelayInMillis)
        {
            // The beat has passed and should have recorded samples in the mic buffer. Analyze the samples now.
            int startSampleBufferIndex = GetMicSampleBufferIndexForBeat(BeatToAnalyze);
            int endSampleBufferIndex   = GetMicSampleBufferIndexForBeat(BeatToAnalyze + 1);
            if (startSampleBufferIndex > endSampleBufferIndex)
            {
                ObjectUtils.Swap(ref startSampleBufferIndex, ref endSampleBufferIndex);
            }

            Note currentOrUpcomingNote = currentAndUpcomingNotesInRecordingSentence[0];
            Note noteAtBeat            = (currentOrUpcomingNote.StartBeat <= BeatToAnalyze && BeatToAnalyze < currentOrUpcomingNote.EndBeat)
                ? currentOrUpcomingNote
                : null;

            PitchEvent pitchEvent = audioSamplesAnalyzer.ProcessAudioSamples(micSampleRecorder.MicSamples, startSampleBufferIndex, endSampleBufferIndex, micProfile);
            FirePitchEvent(pitchEvent, BeatToAnalyze, noteAtBeat);

            GoToNextBeat();
        }
    }
Exemple #6
0
    private int ApplyJokerRule(PitchEvent pitchEvent, int roundedMidiNote, Note noteAtBeat)
    {
        // Earn a joker when singing correctly (without using a joker).
        // A failed beat can be undone via joker-rule.
        if (pitchEvent != null && roundedMidiNote == noteAtBeat.MidiNote)
        {
            hasJoker = true;
        }
        // The joker is only for continued singing.
        if (pitchEvent == null)
        {
            hasJoker = false;
        }

        // If the player fails a beat in continued singing, but the previous beats were sung correctly,
        // then this failed beat is ignored.
        if (roundedMidiNote != noteAtBeat.MidiNote &&
            hasJoker)
        {
            hasJoker = false;
            usedJokerCount++;
            return(noteAtBeat.MidiNote);
        }
        return(roundedMidiNote);
    }
Exemple #7
0
 public BeatAnalyzedEvent(PitchEvent pitchEvent, int beat, Note noteAtBeat, int roundedMidiNote)
 {
     PitchEvent      = pitchEvent;
     Beat            = beat;
     NoteAtBeat      = noteAtBeat;
     RoundedMidiNote = roundedMidiNote;
 }
Exemple #8
0
    private void OnPitchDetected(PitchEvent pitchEvent)
    {
        if (pitchEvent == null || !isCalibrationInProgress || pauseTime > 0)
        {
            return;
        }

        string targetMidiNoteName = midiNoteNames[currentIteration];

        if (MidiUtils.GetAbsoluteName(pitchEvent.MidiNote) == targetMidiNoteName)
        {
            audioSource.Stop();
            float delayInSeconds = Time.time - startTimeInSeconds;
            int   delayInMillis  = (int)(delayInSeconds * 1000);
            delaysInMillis.Add(delayInMillis);
            Debug.Log($"Mic delay calibration - delay of iteration {currentIteration}: {delayInMillis}");

            currentIteration++;
            if (currentIteration >= midiNoteNames.Count)
            {
                OnEndCalibration();
            }
            else
            {
                // Wait a bit for silence before the next iteration.
                pauseTime = 0.5f;
            }
        }
        else
        {
            Debug.Log("Mic delay calibration - wrong pitch: " + MidiUtils.GetAbsoluteName(pitchEvent.MidiNote));
        }
    }
Exemple #9
0
    public PitchEvent GetPitchEventOfBeat(int beat)
    {
        int        startSampleBufferIndex = GetMicSampleBufferIndexForBeat(beat);
        int        endSampleBufferIndex   = GetMicSampleBufferIndexForBeat(beat + 1);
        PitchEvent pitchEvent             = GetPitchEventOfSamples(startSampleBufferIndex, endSampleBufferIndex);

        return(pitchEvent);
    }
Exemple #10
0
    protected override PitchEvent GetDummyPichtEvent(int beat, Note noteAtBeat)
    {
        PitchEvent pitchEvent = null;

        if (noteAtBeat != null && Random.Range(0, 5) != 0)
        {
            pitchEvent = new PitchEvent(noteAtBeat.MidiNote + Random.Range(-3, 3));
        }
        return(pitchEvent);
    }
Exemple #11
0
    public void FirePitchEvent(PitchEvent pitchEvent, int beat, Note noteAtBeat)
    {
        int roundedMidiNote = pitchEvent != null
            ? GetRoundedMidiNoteForRecordedMidiNote(noteAtBeat, pitchEvent.MidiNote)
            : -1;

        int roundedMidiNoteAfterJoker = ApplyJokerRule(pitchEvent, roundedMidiNote, noteAtBeat);

        beatAnalyzedEventStream.OnNext(new BeatAnalyzedEvent(pitchEvent, beat, noteAtBeat, roundedMidiNoteAfterJoker));
    }
Exemple #12
0
    protected override PitchEvent GetDummyPichtEvent(int beat, Note noteAtBeat)
    {
        PitchEvent pitchEvent = null;

        if (noteAtBeat != null)
        {
            pitchEvent = new PitchEvent(noteAtBeat.MidiNote + offset);
        }
        return(pitchEvent);
    }
Exemple #13
0
    public override void UpdateSinging(double currentBeat)
    {
        Note       noteAtCurrentBeat = GetNoteAtCurrentBeat(currentBeat);
        PitchEvent pitchEvent        = null;

        if (noteAtCurrentBeat != null)
        {
            pitchEvent = new PitchEvent(noteAtCurrentBeat.MidiNote + offset);
        }
        playerController.PlayerNoteRecorder.HandlePitchEvent(pitchEvent);
    }
Exemple #14
0
    public override void UpdateSinging(double currentBeat)
    {
        Note       noteAtCurrentBeat = GetNoteAtCurrentBeat(currentBeat);
        PitchEvent pitchEvent        = null;

        if (noteAtCurrentBeat != null && Random.Range(0, 5) != 0)
        {
            pitchEvent = new PitchEvent(noteAtCurrentBeat.MidiNote + Random.Range(-3, 3));
        }
        playerController.PlayerNoteRecorder.HandlePitchEvent(pitchEvent);
    }
Exemple #15
0
    public PitchEvent GetPitchEventOfSamples(int startSampleBufferIndex, int endSampleBufferIndex)
    {
        if (startSampleBufferIndex > endSampleBufferIndex)
        {
            ObjectUtils.Swap(ref startSampleBufferIndex, ref endSampleBufferIndex);
        }
        startSampleBufferIndex = NumberUtils.Limit(startSampleBufferIndex, 0, micSampleRecorder.MicSamples.Length - 1);
        endSampleBufferIndex   = NumberUtils.Limit(endSampleBufferIndex, 0, micSampleRecorder.MicSamples.Length - 1);
        PitchEvent pitchEvent = audioSamplesAnalyzer.ProcessAudioSamples(micSampleRecorder.MicSamples, startSampleBufferIndex, endSampleBufferIndex, micProfile);

        return(pitchEvent);
    }
Exemple #16
0
    private void OnPitchDetected(PitchEvent pitchEvent)
    {
        if (pitchEvent == null || lastPitchDetectedFrame == Time.frameCount)
        {
            return;
        }

        double positionInSongInMillis = songAudioPlayer.PositionInSongInMillis - EditorSettings.MicDelayInMillis;
        int    midiNote = pitchEvent.MidiNote + (EditorSettings.MicOctaveOffset * 12);

        RecordNote(midiNote, positionInSongInMillis, ESongEditorLayer.MicRecording);
    }
Exemple #17
0
 private void OnPitchDetected(PitchEvent pitchEvent)
 {
     // Show the note that has been detected
     if (pitchEvent != null && pitchEvent.MidiNote > 0)
     {
         currentNoteLabel.text = "Note: " + MidiUtils.GetAbsoluteName(pitchEvent.MidiNote);
     }
     else
     {
         currentNoteLabel.text = "Note: ?";
     }
 }
 private void UpdateLastMidiNoteFields(PitchEvent pitchEvent)
 {
     if (pitchEvent == null)
     {
         lastMidiNoteName = "";
         lastMidiNote     = 0;
     }
     else
     {
         lastMidiNoteName = MidiUtils.GetAbsoluteName(pitchEvent.MidiNote);
         lastMidiNote     = pitchEvent.MidiNote;
     }
 }
 private void OnPitchDetected(PitchEvent pitchEvent)
 {
     // Show the note that has been detected
     if (pitchEvent != null && pitchEvent.MidiNote > 0)
     {
         currentNoteLabel.text = TranslationManager.GetTranslation(R.Messages.options_note,
                                                                   "value", MidiUtils.GetAbsoluteName(pitchEvent.MidiNote));
     }
     else
     {
         currentNoteLabel.text = TranslationManager.GetTranslation(R.Messages.options_note,
                                                                   "value", "?");
     }
 }
 public BeatAnalyzedEvent(PitchEvent pitchEvent,
                          int beat,
                          Note noteAtBeat,
                          Sentence sentenceAtBeat,
                          int recordedMidiNote,
                          int roundedRecordedMidiNote)
 {
     PitchEvent              = pitchEvent;
     Beat                    = beat;
     NoteAtBeat              = noteAtBeat;
     SentenceAtBeat          = sentenceAtBeat;
     RecordedMidiNote        = recordedMidiNote;
     RoundedRecordedMidiNote = roundedRecordedMidiNote;
 }
Exemple #21
0
        public async Task <IActionResult> OnGetAsync(int?id)
        {
            if (id == null)
            {
                return(NotFound());
            }

            PitchEvent = await _context.PitchEvents.FirstOrDefaultAsync(m => m.PitchEventId == id);

            if (PitchEvent == null)
            {
                return(NotFound());
            }
            return(Page());
        }
Exemple #22
0
    void Update()
    {
        int currentBeat   = (int)singSceneController.CurrentBeat;
        int beatToAnalyze = playerController.PlayerPitchTracker.BeatToAnalyze;

        if (currentBeat > 0 &&
            beatToAnalyze <= currentBeat &&
            playerController.PlayerPitchTracker.RecordingSentence != null)
        {
            Note       noteAtBeat = GetNoteAtBeat(beatToAnalyze);
            PitchEvent pitchEvent = GetDummyPichtEvent(beatToAnalyze, noteAtBeat);
            playerController.PlayerPitchTracker.FirePitchEvent(pitchEvent, beatToAnalyze, noteAtBeat);
            playerController.PlayerPitchTracker.GoToNextBeat();
        }
    }
        public async Task <IActionResult> OnPostAsync(int?id)
        {
            if (id == null)
            {
                return(NotFound());
            }

            PitchEvent = await _context.PitchEvents.FindAsync(id);

            if (PitchEvent != null)
            {
                _context.PitchEvents.Remove(PitchEvent);
                await _context.SaveChangesAsync();
            }

            return(RedirectToPage("./Index"));
        }
Exemple #24
0
    protected override PitchEvent GetDummyPichtEvent(int beat, Note noteAtBeat)
    {
        PitchEvent pitchEvent = null;

        if (noteAtBeat != null)
        {
            pitchEvent = new PitchEvent(noteAtBeat.MidiNote + noteOffset);
        }

        // Change noteOffset when note changes.
        if (lastNote != null && noteAtBeat != lastNote)
        {
            noteOffset = (noteOffset + 1) % maxOffset;
        }
        lastNote = noteAtBeat;
        return(pitchEvent);
    }
Exemple #25
0
    void Update()
    {
        if (songAudioPlayer.PositionInSongInMillis <= 0)
        {
            return;
        }

        PitchEvent pitchEvent = playerPitchTracker.GetPitchEventOfSamples(micSampleRecorder.MicSamples.Length - 1 - samplesPerBeat, micSampleRecorder.MicSamples.Length - 1);

        if (pitchEvent != null)
        {
            // Shift midi note to octave of last recorded midi note (can be different because PlayerPitchTracker is rounding towards the target note)
            int midiNote = lastRecordedRoundedMidiNote > 0
                ? MidiUtils.GetRelativePitch(pitchEvent.MidiNote) + (12 * (MidiUtils.GetOctave(lastRecordedRoundedMidiNote) + 1))
                : pitchEvent.MidiNote;
            UpdatePosition(midiNote);
        }
    }
Exemple #26
0
    public void HandlePitchEvent(PitchEvent pitchEvent)
    {
        double currentBeat = GetCurrentBeat();

        // It could be that some beats have been missed, for example because the frame rate was too low.
        // In this case, the pitch event is fired here also for the missed beats.
        if (lastBeatWithPitchEvent < currentBeat)
        {
            int missedBeats = (int)(currentBeat - lastBeatWithPitchEvent);
            for (int i = 1; i <= missedBeats; i++)
            {
                HandlePitchEvent(pitchEvent, lastBeatWithPitchEvent + i);
            }
        }
        HandlePitchEvent(pitchEvent, currentBeat);

        lastBeatWithPitchEvent = currentBeat;
    }
Exemple #27
0
    public override void UpdateSinging(double currentBeat)
    {
        Note       noteAtCurrentBeat = GetNoteAtCurrentBeat(currentBeat);
        PitchEvent pitchEvent        = null;

        if (noteAtCurrentBeat != null)
        {
            pitchEvent = new PitchEvent(noteAtCurrentBeat.MidiNote + noteOffset);
        }
        else if (lastNote != null)
        {
            // Change noteOffset on falling flank of note
            // (falling flank: now there is no note, but in last frame there was one)
            noteOffset = (noteOffset + 1) % 5;
        }
        playerController.PlayerNoteRecorder.HandlePitchEvent(pitchEvent);

        lastNote = noteAtCurrentBeat;
    }
    protected override PitchEvent GetDummyPichtEvent(int beat, Note noteAtBeat)
    {
        // Change noteOffset when note changes.
        if (lastNote != null && noteAtBeat != lastNote)
        {
            noteOffset = (noteOffset + 1) % maxOffset;
        }

        PitchEvent pitchEvent = null;

        if (noteAtBeat != null)
        {
            int dummyMidiNote = noteAtBeat.MidiNote + noteOffset;
            int dummyMidiNoteInSingableNoteRange = (dummyMidiNote % 12) + (5 * 12);
            pitchEvent = new PitchEvent(dummyMidiNoteInSingableNoteRange);
        }

        lastNote = noteAtBeat;
        return(pitchEvent);
    }
Exemple #29
0
    private void AnalyzeBeatAndNotify(int beat)
    {
        PitchEvent pitchEvent = AnalyzeBeat(
            songMeta,
            beat,
            songAudioPlayer.PositionInSongInMillis,
            MicSampleRecorder.MicProfile,
            MicSampleRecorder.MicSamples,
            AudioSamplesAnalyzer);

        // Notify listeners
        if (pitchEvent == null)
        {
            pitchEventStream.OnNext(null);
        }
        else
        {
            int shiftedMidiNote = pitchEvent.MidiNote + (songEditorSettings.SongEditorSettings.MicOctaveOffset * 12);
            pitchEventStream.OnNext(new BeatPitchEvent(shiftedMidiNote, beat));
        }
    }
Exemple #30
0
    public void TestPitchDetection()
    {
        MicProfile micProfile = CreateDummyMicProfile();

        string assetsPath      = Application.dataPath;
        string sineWaveToneDir = assetsPath + "/Editor/Tests/SineWaveTones/";
        Dictionary <string, string> pathToExpectedMidiNoteNameMap = new Dictionary <string, string>();

        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-a3-220hz.ogg", "A3");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-a4-440hz.ogg", "A4");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-a5-880hz.ogg", "A5");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-c2-61,74hz.ogg", "C2");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-c4-261,64hz.ogg", "C4");
        pathToExpectedMidiNoteNameMap.Add(sineWaveToneDir + "sine-wave-c6-1046,50hz.ogg", "C6");

        foreach (KeyValuePair <string, string> pathAndNoteName in pathToExpectedMidiNoteNameMap)
        {
            // Load the audio clip
            string    path      = pathAndNoteName.Key;
            AudioClip audioClip = AudioUtils.GetAudioClip(path);
            float[]   samples   = new float[audioClip.samples];
            audioClip.GetData(samples, 0);

            // Analyze the samples
            IAudioSamplesAnalyzer audioSamplesAnalyzer = new CamdAudioSamplesAnalyzer(audioClip.frequency);
            audioSamplesAnalyzer.Enable();
            PitchEvent pitchEvent = audioSamplesAnalyzer.ProcessAudioSamples(samples, samples.Length, micProfile);

            // Check result
            Assert.NotNull(pitchEvent, $"No pitch detected when analyzing {path}");
            string expectedName = pathAndNoteName.Value;
            string analyzedName = MidiUtils.GetAbsoluteName(pitchEvent.MidiNote);
            Assert.AreEqual(expectedName, analyzedName,
                            $"Expected {expectedName} but was {analyzedName} when analyzing {path}");
        }
    }