feature-window.cc (8701B)
1 // kaldi-native-fbank/csrc/feature-window.cc 2 // 3 // Copyright (c) 2022 Xiaomi Corporation (authors: Fangjun Kuang) 4 5 // This file is copied/modified from kaldi/src/feat/feature-window.cc 6 7 #include "kaldi-native-fbank/csrc/feature-window.h" 8 9 #include <algorithm> 10 #include <cmath> 11 #include <limits> 12 #include <vector> 13 #include <cstddef> 14 15 #include "kaldi-native-fbank/csrc/kaldi-math.h" 16 17 namespace knf { 18 19 std::ostream &operator<<(std::ostream &os, const FrameExtractionOptions &opts) { 20 os << opts.ToString(); 21 return os; 22 } 23 24 FeatureWindowFunction::FeatureWindowFunction(const FrameExtractionOptions &opts) 25 : window_(opts.WindowSize()) { 26 int32_t frame_length = opts.WindowSize(); 27 KNF_CHECK_GT(frame_length, 0); 28 29 float *window_data = window_.data(); 30 31 double a = M_2PI / (frame_length - 1); 32 if (opts.window_type == "hann") { 33 // see https://pytorch.org/docs/stable/generated/torch.hann_window.html 34 // We assume periodic is true 35 a = M_2PI / frame_length; 36 } 37 38 for (int32_t i = 0; i < frame_length; i++) { 39 double i_fl = static_cast<double>(i); 40 if (opts.window_type == "hanning") { 41 window_data[i] = 0.5 - 0.5 * cos(a * i_fl); 42 } else if (opts.window_type == "sine") { 43 // when you are checking ws wikipedia, please 44 // note that 0.5 * a = M_PI/(frame_length-1) 45 window_data[i] = sin(0.5 * a * i_fl); 46 } else if (opts.window_type == "hamming") { 47 window_data[i] = 0.54 - 0.46 * cos(a * i_fl); 48 } else if (opts.window_type == "hann") { 49 window_data[i] = 0.50 - 0.50 * cos(a * i_fl); 50 } else if (opts.window_type == "povey") { 51 // like hamming but goes to zero at edges. 52 window_data[i] = pow(0.5 - 0.5 * cos(a * i_fl), 0.85); 53 } else if (opts.window_type == "rectangular") { 54 window_data[i] = 1.0; 55 } else if (opts.window_type == "blackman") { 56 window_data[i] = opts.blackman_coeff - 0.5 * cos(a * i_fl) + 57 (0.5 - opts.blackman_coeff) * cos(2 * a * i_fl); 58 } else { 59 KNF_LOG(FATAL) << "Invalid window type " << opts.window_type; 60 } 61 } 62 } 63 64 void FeatureWindowFunction::Apply(float *wave) const { 65 int32_t window_size = window_.size(); 66 const float *p = window_.data(); 67 for (int32_t k = 0; k != window_size; ++k) { 68 wave[k] *= p[k]; 69 } 70 } 71 72 int64_t FirstSampleOfFrame(int32_t frame, const FrameExtractionOptions &opts) { 73 int64_t frame_shift = opts.WindowShift(); 74 if (opts.snip_edges) { 75 return frame * frame_shift; 76 } else { 77 int64_t midpoint_of_frame = frame_shift * frame + frame_shift / 2, 78 beginning_of_frame = midpoint_of_frame - opts.WindowSize() / 2; 79 return beginning_of_frame; 80 } 81 } 82 83 int32_t NumFrames(int64_t num_samples, const FrameExtractionOptions &opts, 84 bool flush /*= true*/) { 85 int64_t frame_shift = opts.WindowShift(); 86 int64_t frame_length = opts.WindowSize(); 87 if (opts.snip_edges) { 88 // with --snip-edges=true (the default), we use a HTK-like approach to 89 // determining the number of frames-- all frames have to fit completely into 90 // the waveform, and the first frame begins at sample zero. 91 if (num_samples < frame_length) 92 return 0; 93 else 94 return (1 + ((num_samples - frame_length) / frame_shift)); 95 // You can understand the expression above as follows: 'num_samples - 96 // frame_length' is how much room we have to shift the frame within the 97 // waveform; 'frame_shift' is how much we shift it each time; and the ratio 98 // is how many times we can shift it (integer arithmetic rounds down). 99 } else { 100 // if --snip-edges=false, the number of frames is determined by rounding the 101 // (file-length / frame-shift) to the nearest integer. The point of this 102 // formula is to make the number of frames an obvious and predictable 103 // function of the frame shift and signal length, which makes many 104 // segmentation-related questions simpler. 105 // 106 // Because integer division in C++ rounds toward zero, we add (half the 107 // frame-shift minus epsilon) before dividing, to have the effect of 108 // rounding towards the closest integer. 109 int32_t num_frames = (num_samples + (frame_shift / 2)) / frame_shift; 110 111 if (flush) return num_frames; 112 113 // note: 'end' always means the last plus one, i.e. one past the last. 114 int64_t end_sample_of_last_frame = 115 FirstSampleOfFrame(num_frames - 1, opts) + frame_length; 116 117 // the following code is optimized more for clarity than efficiency. 118 // If flush == false, we can't output frames that extend past the end 119 // of the signal. 120 while (num_frames > 0 && end_sample_of_last_frame > num_samples) { 121 num_frames--; 122 end_sample_of_last_frame -= frame_shift; 123 } 124 return num_frames; 125 } 126 } 127 128 void ExtractWindow(int64_t sample_offset, const std::vector<float> &wave, 129 int32_t f, const FrameExtractionOptions &opts, 130 const FeatureWindowFunction &window_function, 131 std::vector<float> *window, 132 float *log_energy_pre_window /*= nullptr*/) { 133 KNF_CHECK(sample_offset >= 0 && wave.size() != 0); 134 135 int32_t frame_length = opts.WindowSize(); 136 int32_t frame_length_padded = opts.PaddedWindowSize(); 137 138 int64_t num_samples = sample_offset + wave.size(); 139 int64_t start_sample = FirstSampleOfFrame(f, opts); 140 int64_t end_sample = start_sample + frame_length; 141 142 if (opts.snip_edges) { 143 KNF_CHECK(start_sample >= sample_offset && end_sample <= num_samples); 144 } else { 145 KNF_CHECK(sample_offset == 0 || start_sample >= sample_offset); 146 } 147 148 if (window->size() != frame_length_padded) { 149 window->resize(frame_length_padded); 150 } 151 152 // wave_start and wave_end are start and end indexes into 'wave', for the 153 // piece of wave that we're trying to extract. 154 int32_t wave_start = int32_t(start_sample - sample_offset); 155 int32_t wave_end = wave_start + frame_length; 156 157 if (wave_start >= 0 && wave_end <= wave.size()) { 158 // the normal case-- no edge effects to consider. 159 std::copy(wave.begin() + wave_start, 160 wave.begin() + wave_start + frame_length, window->data()); 161 } else { 162 // Deal with any end effects by reflection, if needed. This code will only 163 // be reached for about two frames per utterance, so we don't concern 164 // ourselves excessively with efficiency. 165 int32_t wave_dim = wave.size(); 166 for (int32_t s = 0; s < frame_length; ++s) { 167 int32_t s_in_wave = s + wave_start; 168 while (s_in_wave < 0 || s_in_wave >= wave_dim) { 169 // reflect around the beginning or end of the wave. 170 // e.g. -1 -> 0, -2 -> 1. 171 // dim -> dim - 1, dim + 1 -> dim - 2. 172 // the code supports repeated reflections, although this 173 // would only be needed in pathological cases. 174 if (s_in_wave < 0) 175 s_in_wave = -s_in_wave - 1; 176 else 177 s_in_wave = 2 * wave_dim - 1 - s_in_wave; 178 } 179 (*window)[s] = wave[s_in_wave]; 180 } 181 } 182 183 ProcessWindow(opts, window_function, window->data(), log_energy_pre_window); 184 } 185 186 static void RemoveDcOffset(float *d, int32_t n) { 187 float sum = 0; 188 for (int32_t i = 0; i != n; ++i) { 189 sum += d[i]; 190 } 191 192 float mean = sum / n; 193 194 for (int32_t i = 0; i != n; ++i) { 195 d[i] -= mean; 196 } 197 } 198 199 float InnerProduct(const float *a, const float *b, int32_t n) { 200 float sum = 0; 201 for (int32_t i = 0; i != n; ++i) { 202 sum += a[i] * b[i]; 203 } 204 return sum; 205 } 206 207 void Dither(float *d, int32_t n, float dither_value) { 208 if (dither_value == 0.0) { 209 return; 210 } 211 212 RandomState rstate; 213 for (int32_t i = 0; i < n; ++i) { 214 d[i] += RandGauss(&rstate) * dither_value; 215 } 216 } 217 218 static void Preemphasize(float *d, int32_t n, float preemph_coeff) { 219 if (preemph_coeff == 0.0) { 220 return; 221 } 222 223 KNF_CHECK(preemph_coeff >= 0.0 && preemph_coeff <= 1.0); 224 225 for (int32_t i = n - 1; i > 0; --i) { 226 d[i] -= preemph_coeff * d[i - 1]; 227 } 228 d[0] -= preemph_coeff * d[0]; 229 } 230 231 void ProcessWindow(const FrameExtractionOptions &opts, 232 const FeatureWindowFunction &window_function, float *window, 233 float *log_energy_pre_window /*= nullptr*/) { 234 int32_t frame_length = opts.WindowSize(); 235 236 if (opts.dither != 0.0) { 237 Dither(window, frame_length, opts.dither); 238 } 239 240 if (opts.remove_dc_offset) { 241 RemoveDcOffset(window, frame_length); 242 } 243 244 if (log_energy_pre_window != NULL) { 245 float energy = std::max<float>(InnerProduct(window, window, frame_length), 246 std::numeric_limits<float>::epsilon()); 247 *log_energy_pre_window = std::log(energy); 248 } 249 250 if (opts.preemph_coeff != 0.0) { 251 Preemphasize(window, frame_length, opts.preemph_coeff); 252 } 253 254 window_function.Apply(window); 255 } 256 257 } // namespace knf