Vital
Loading...
Searching...
No Matches
pitch_detector.cpp
Go to the documentation of this file.
1/*
2Summary:
3The PitchDetector class attempts to find the fundamental period of a loaded signal using a method inspired by the YIN algorithm. By computing an error metric that measures how well the waveform repeats at different periods, it identifies a period length that best represents the fundamental frequency. This helps ensure that wavetables and other audio processing steps align wave cycles with their fundamental pitch.
4 */
5
6#include "pitch_detector.h"
7#include "synth_constants.h"
8#include "wave_frame.h"
9
10#include <climits>
11
13 size_ = 0;
14 signal_data_ = nullptr;
15}
16
17void PitchDetector::loadSignal(const float* signal, int size) {
18 size_ = size;
19 signal_data_ = std::make_unique<float[]>(size);
20
21 memcpy(signal_data_.get(), signal, sizeof(float) * size);
22}
23
24float PitchDetector::getPeriodError(float period) {
25 // This function measures how repeating a given period is within the signal.
26 // It segments the signal into 'waves' (integral multiples of the period) and compares them.
27 // It also penalizes DC differences by considering the mean offsets.
28 static constexpr float kDcDeltaErrorMultiplier = 0.015f;
29 float error = 0.0f;
30 int waves = size_ / period - 1;
31 VITAL_ASSERT(waves > 0);
32 int points = kNumPoints / waves;
33 for (int w = 0; w < waves; ++w) {
34 float total_from = 0.0f;
35 float total_to = 0.0f;
36 for (int i = 0; i < points; ++i) {
37 float first_position = w * period + i * period / points;
38 float second_position = (w + 1) * period + i * period / points;
39
40 int first_index = first_position;
41 float first_t = first_position - first_index;
42 float first_from = signal_data_[first_index];
43 float first_to = signal_data_[first_index + 1];
44 float first_value = vital::utils::interpolate(first_from, first_to, first_t);
45 total_from += first_value;
46
47 int second_index = second_position;
48 float second_t = second_position - second_index;
49 float second_from = signal_data_[second_index];
50 float second_to = signal_data_[second_index + 1];
51 float second_value = vital::utils::interpolate(second_from, second_to, second_t);
52 total_to += second_value;
53
54 float delta = first_value - second_value;
55 error += delta * delta;
56 }
57
58 float total_diff = total_from - total_to;
59 error += total_diff * total_diff * kDcDeltaErrorMultiplier;
60 }
61
62 return error;
63}
64
65float PitchDetector::findYinPeriod(int max_period) {
66 // The method tries a range of candidate periods and selects the one with the smallest error.
67 // It first does a coarse search, then refines around the best candidate to find a more exact period.
68 constexpr float kMinLength = 300.0f;
69
70 float max_length = std::min<float>(size_ / 2.0f, max_period);
71
72 float best_error = INT_MAX;
73 float match = kMinLength;
74
75 for (float length = kMinLength; length < max_length; length += 1.0f) {
76 float error = getPeriodError(length);
77 if (error < best_error) {
78 best_error = error;
79 match = length;
80 }
81 }
82
83 float best_match = match;
84 for (float length = match - 1.0f; length <= match + 1.0f; length += 0.1f) {
85 float error = getPeriodError(length);
86 if (error < best_error) {
87 best_error = error;
88 best_match = length;
89 }
90 }
91
92 return best_match;
93}
94
95float PitchDetector::matchPeriod(int max_period) {
96 // A simple wrapper calling findYinPeriod to find the period.
97 return findYinPeriod(max_period);
98}
float findYinPeriod(int max_period)
Searches for a period using a YIN-like algorithm, up to a specified maximum period.
Definition pitch_detector.cpp:65
float matchPeriod(int max_period)
High-level method to find the best matching period using the YIN approach.
Definition pitch_detector.cpp:95
int size_
Number of samples in the loaded signal.
Definition pitch_detector.h:86
void loadSignal(const float *signal, int size)
Loads a signal into the PitchDetector for analysis.
Definition pitch_detector.cpp:17
static constexpr int kNumPoints
A fixed number of points used in the period error computation.
Definition pitch_detector.h:25
std::unique_ptr< float[]> signal_data_
Buffer holding the loaded signal samples.
Definition pitch_detector.h:87
float getPeriodError(float period)
Computes the error metric for a given period length.
Definition pitch_detector.cpp:24
PitchDetector()
Constructs a PitchDetector with no loaded signal.
Definition pitch_detector.cpp:12
#define VITAL_ASSERT(x)
Definition common.h:11
force_inline poly_float interpolate(poly_float from, poly_float to, mono_float t)
Performs a linear interpolation between two poly_floats using a scalar t in [0..1].
Definition poly_utils.h:182