janitor

janitor
git clone https://git.prime8.dev/janitor.git
Log | Files | Refs | README | LICENSE

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