Finish VAD code translation

This commit is contained in:
Daniel Wolf 2019-10-02 20:53:29 +02:00
parent 3ff1a74aeb
commit 62c179863e
1 changed files with 269 additions and 317 deletions

View File

@ -1,3 +1,4 @@
import org.apache.commons.lang3.mutable.MutableInt
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
/////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////
@ -64,13 +65,13 @@ inline fun GetSizeInBits(n: UInt): Int {
// GetScalingSquare(...) // GetScalingSquare(...)
// //
// Returns the # of bits required to scale the samples specified in the // Returns the # of bits required to scale the samples specified in the
// |in_vector| parameter so that, if the squares of the samples are added the // [in_vector] parameter so that, if the squares of the samples are added the
// # of times specified by the |times| parameter, the 32-bit addition will not // # of times specified by the [times] parameter, the 32-bit addition will not
// overflow (result in Int). // overflow (result in Int).
// //
// Input: // Input:
// - in_vector : Input vector to check scaling on // - in_vector : Input vector to check scaling on
// - in_vector_length : Samples in |in_vector| // - in_vector_length : Samples in [in_vector]
// - times : Number of additions to be performed // - times : Number of additions to be performed
// //
// Return value : Number of right bit shifts needed to avoid // Return value : Number of right bit shifts needed to avoid
@ -98,9 +99,9 @@ fun GetScalingSquare(buffer: AudioBuffer, times: Int): Int {
data class EnergyResult( data class EnergyResult(
// Number of left bit shifts needed to get the physical energy value, i.e, to get the Q0 value // Number of left bit shifts needed to get the physical energy value, i.e, to get the Q0 value
val scaleFactor: Int, val rightShifts: Int,
// Energy value in Q(-|scale_factor|) // Energy value in Q(-[scale_factor])
val energy: Int val energy: Int
) )
@ -117,7 +118,7 @@ data class EnergyResult(
// - scale_factor : Number of left bit shifts needed to get the physical // - scale_factor : Number of left bit shifts needed to get the physical
// energy value, i.e, to get the Q0 value // energy value, i.e, to get the Q0 value
// //
// Return value : Energy value in Q(-|scale_factor|) // Return value : Energy value in Q(-[scale_factor])
// //
fun Energy(buffer: AudioBuffer): EnergyResult { fun Energy(buffer: AudioBuffer): EnergyResult {
val scaling = GetScalingSquare(buffer, buffer.size) val scaling = GetScalingSquare(buffer, buffer.size)
@ -136,9 +137,9 @@ fun Energy(buffer: AudioBuffer): EnergyResult {
// //
// DivW32W16(...) // DivW32W16(...)
// //
// Divides a Int |num| by a Int |den|. // Divides a Int [num] by a Int [den].
// //
// If |den|==0, (Int)0x7FFFFFFF is returned. // If [den]==0, (Int)0x7FFFFFFF is returned.
// //
// Input: // Input:
// - num : Numerator // - num : Numerator
@ -154,18 +155,18 @@ fun DivW32W16(num: Int, den: Int) =
// webrtc/common_audio/vad/vad_gmm.c // webrtc/common_audio/vad/vad_gmm.c
data class GaussianProbabilityResult( data class GaussianProbabilityResult(
// (probability for |input|) = 1 / |std| * exp(-(|input| - |mean|)^2 / (2 * |std|^2)) // (probability for [input]) = 1 / [std] * exp(-([input] - [mean])^2 / (2 * [std]^2))
val probability: Int, val probability: Int,
// Input used when updating the model, Q11. // Input used when updating the model, Q11.
// |delta| = (|input| - |mean|) / |std|^2. // [delta] = ([input] - [mean]) / [std]^2.
val delta: Int val delta: Int
) )
val kCompVar = 22005; val kCompVar = 22005;
val kLog2Exp = 5909; // log2(exp(1)) in Q12. val kLog2Exp = 5909; // log2(exp(1)) in Q12.
// Calculates the probability for |input|, given that |input| comes from a // Calculates the probability for [input], given that [input] comes from a
// normal distribution with mean and standard deviation (|mean|, |std|). // normal distribution with mean and standard deviation ([mean], [std]).
// //
// Inputs: // Inputs:
// - input : input sample in Q4. // - input : input sample in Q4.
@ -175,22 +176,22 @@ val kLog2Exp = 5909; // log2(exp(1)) in Q12.
// Output: // Output:
// //
// - delta : input used when updating the model, Q11. // - delta : input used when updating the model, Q11.
// |delta| = (|input| - |mean|) / |std|^2. // [delta] = ([input] - [mean]) / [std]^2.
// //
// Return: // Return:
// (probability for |input|) = // (probability for [input]) =
// 1 / |std| * exp(-(|input| - |mean|)^2 / (2 * |std|^2)); // 1 / [std] * exp(-([input] - [mean])^2 / (2 * [std]^2));
//--------------------------------------------------------------------------------- //---------------------------------------------------------------------------------
// For a normal distribution, the probability of |input| is calculated and // For a normal distribution, the probability of [input] is calculated and
// returned (in Q20). The formula for normal distributed probability is // returned (in Q20). The formula for normal distributed probability is
// //
// 1 / s * exp(-(x - m)^2 / (2 * s^2)) // 1 / s * exp(-(x - m)^2 / (2 * s^2))
// //
// where the parameters are given in the following Q domains: // where the parameters are given in the following Q domains:
// m = |mean| (Q7) // m = [mean] (Q7)
// s = |std| (Q7) // s = [std] (Q7)
// x = |input| (Q4) // x = [input] (Q4)
// in addition to the probability we output |delta| (in Q11) used when updating // in addition to the probability we output [delta] (in Q11) used when updating
// the noise/speech model. // the noise/speech model.
fun GaussianProbability(input: Int, mean: Int, std: Int): GaussianProbabilityResult { fun GaussianProbability(input: Int, mean: Int, std: Int): GaussianProbabilityResult {
var tmp16 = 0 var tmp16 = 0
@ -199,13 +200,13 @@ fun GaussianProbability(input: Int, mean: Int, std: Int): GaussianProbabilityRes
var exp_value = 0 var exp_value = 0
var tmp32 = 0 var tmp32 = 0
// Calculate |inv_std| = 1 / s, in Q10. // Calculate [inv_std] = 1 / s, in Q10.
// 131072 = 1 in Q17, and (|std| shr 1) is for rounding instead of truncation. // 131072 = 1 in Q17, and ([std] shr 1) is for rounding instead of truncation.
// Q-domain: Q17 / Q7 = Q10. // Q-domain: Q17 / Q7 = Q10.
tmp32 = 131072 + (std shr 1) tmp32 = 131072 + (std shr 1)
inv_std = DivW32W16(tmp32, std) inv_std = DivW32W16(tmp32, std)
// Calculate |inv_std2| = 1 / s^2, in Q14. // Calculate [inv_std2] = 1 / s^2, in Q14.
tmp16 = inv_std shr 2 // Q10 -> Q8. tmp16 = inv_std shr 2 // Q10 -> Q8.
// Q-domain: (Q8 * Q8) shr 2 = Q14. // Q-domain: (Q8 * Q8) shr 2 = Q14.
inv_std2 = (tmp16 * tmp16) shr 2 inv_std2 = (tmp16 * tmp16) shr 2
@ -214,20 +215,20 @@ fun GaussianProbability(input: Int, mean: Int, std: Int): GaussianProbabilityRes
tmp16 -= mean // Q7 - Q7 = Q7 tmp16 -= mean // Q7 - Q7 = Q7
// To be used later, when updating noise/speech model. // To be used later, when updating noise/speech model.
// |delta| = (x - m) / s^2, in Q11. // [delta] = (x - m) / s^2, in Q11.
// Q-domain: (Q14 * Q7) shr 10 = Q11. // Q-domain: (Q14 * Q7) shr 10 = Q11.
val delta = (inv_std2 * tmp16) shr 10 val delta = (inv_std2 * tmp16) shr 10
// Calculate the exponent |tmp32| = (x - m)^2 / (2 * s^2), in Q10. Replacing // Calculate the exponent [tmp32] = (x - m)^2 / (2 * s^2), in Q10. Replacing
// division by two with one shift. // division by two with one shift.
// Q-domain: (Q11 * Q7) shr 8 = Q10. // Q-domain: (Q11 * Q7) shr 8 = Q10.
tmp32 = (delta * tmp16) shr 9 tmp32 = (delta * tmp16) shr 9
// If the exponent is small enough to give a non-zero probability we calculate // If the exponent is small enough to give a non-zero probability we calculate
// |exp_value| ~= exp(-(x - m)^2 / (2 * s^2)) // [exp_value] ~= exp(-(x - m)^2 / (2 * s^2))
// ~= exp2(-log2(exp(1)) * |tmp32|). // ~= exp2(-log2(exp(1)) * [tmp32]).
if (tmp32 < kCompVar) { if (tmp32 < kCompVar) {
// Calculate |tmp16| = log2(exp(1)) * |tmp32|, in Q10. // Calculate [tmp16] = log2(exp(1)) * [tmp32], in Q10.
// Q-domain: (Q12 * Q10) shr 12 = Q10. // Q-domain: (Q12 * Q10) shr 12 = Q10.
tmp16 = (kLog2Exp * tmp32) shr 12 tmp16 = (kLog2Exp * tmp32) shr 12
tmp16 = -tmp16 tmp16 = -tmp16
@ -235,7 +236,7 @@ fun GaussianProbability(input: Int, mean: Int, std: Int): GaussianProbabilityRes
tmp16 = tmp16 xor 0xFFFF tmp16 = tmp16 xor 0xFFFF
tmp16 = tmp16 shr 10 tmp16 = tmp16 shr 10
tmp16 += 1; tmp16 += 1;
// Get |exp_value| = exp(-|tmp32|) in Q10. // Get [exp_value] = exp(-[tmp32]) in Q10.
exp_value = exp_value shr tmp16 exp_value = exp_value shr tmp16
} }
@ -310,7 +311,7 @@ enum class Aggressiveness {
class VadInstT(aggressiveness: Aggressiveness = Aggressiveness.Quality) { class VadInstT(aggressiveness: Aggressiveness = Aggressiveness.Quality) {
// General variables // General variables
var vad: Int = 1 // Speech active (=1) var vad: Int = 1 // Speech active (=1)
// TODO(bjornv): Change to |frame_count|. // TODO(bjornv): Change to [frame_count].
var frame_counter: Int = 0 var frame_counter: Int = 0
var over_hang: Int = 0 var over_hang: Int = 0
var num_of_speech: Int = 0 var num_of_speech: Int = 0
@ -322,21 +323,21 @@ class VadInstT(aggressiveness: Aggressiveness = Aggressiveness.Quality) {
var speech_stds = kSpeechDataStds.clone() var speech_stds = kSpeechDataStds.clone()
// Index vector // Index vector
// TODO(bjornv): Change to |age_vector|. // TODO(bjornv): Change to [age_vector].
var index_vector = IntArray(16 * kNumChannels) { 0 } var index_vector = IntArray(16 * kNumChannels) { 0 }
// Minimum value vector // Minimum value vector
var low_value_vector = IntArray(16 * kNumChannels) { 10000 } var low_value_vector = IntArray(16 * kNumChannels) { 10000 }
// Splitting filter states // Splitting filter states
var upper_state = IntArray(5) { 0 } var upper_state = List(5) { MutableInt(0) }
var lower_state = IntArray(5) { 0 } var lower_state = List(5) { MutableInt(0) }
// High pass filter states // High pass filter states
var hp_filter_state = IntArray(4) { 0 } var hp_filter_state = IntArray(4) { 0 }
// Mean value memory for FindMinimum() // Mean value memory for FindMinimum()
// TODO(bjornv): Change to |median|. // TODO(bjornv): Change to [median].
var mean_value = IntArray(kNumChannels) { 1600 } var mean_value = IntArray(kNumChannels) { 1600 }
// Thresholds // Thresholds
@ -366,22 +367,22 @@ class VadInstT(aggressiveness: Aggressiveness = Aggressiveness.Quality) {
typealias WebRtcVadInst = VadInstT typealias WebRtcVadInst = VadInstT
// Calculates the weighted average w.r.t. number of Gaussians. The |data| are // Calculates the weighted average w.r.t. number of Gaussians. The [data] are
// updated with an |offset| before averaging. // updated with an [offset] before averaging.
// //
// - data [i/o] : Data to average. // - data [i/o] : Data to average.
// - offset [i] : An offset added to |data|. // - offset [i] : An offset to add to each element of [data].
// - weights [i] : Weights used for averaging. // - weights [i] : Weights used for averaging.
// //
// returns : The weighted average. // returns : The weighted average.
fun WeightedAverage(data: IntArray, channel: Int, offset: Int, weights: IntArray): Int { fun WeightedAverage(data: IntArray, channel: Int, offset: Int, weights: IntArray): Int {
var weighted_average = 0 var result = 0
for (k in 0 until kNumGaussians) { for (k in 0 until kNumGaussians) {
val index = k * kNumChannels + channel val index = k * kNumChannels + channel
data[index] += offset data[index] += offset
weighted_average += data[index] * weights[index] result += data[index] * weights[index]
} }
return weighted_average return result
} }
// Calculates the probabilities for both speech and background noise using // Calculates the probabilities for both speech and background noise using
@ -389,13 +390,13 @@ fun WeightedAverage(data: IntArray, channel: Int, offset: Int, weights: IntArray
// type of signal is most probable. // type of signal is most probable.
// //
// - self [i/o] : Pointer to VAD instance // - self [i/o] : Pointer to VAD instance
// - features [i] : Feature vector of length |kNumChannels| // - features [i] : Feature vector of length [kNumChannels]
// = log10(energy in frequency band) // = log10(energy in frequency band)
// - total_power [i] : Total power in audio frame. // - total_power [i] : Total power in audio frame.
// - frame_length [i] : Number of input samples // - frame_length [i] : Number of input samples
// //
// - returns : the VAD decision (0 - noise, 1 - speech). // - returns : the VAD decision (0 - noise, 1 - speech).
fun GmmProbability(self: VadInstT, features: IntArray, total_power: Int, frame_length: Int): Int { fun GmmProbability(self: VadInstT, features: List<Int>, total_power: Int, frame_length: Int): Int {
var vadflag = 0; var vadflag = 0;
var tmp_s16: Int var tmp_s16: Int
var tmp1_s16: Int var tmp1_s16: Int
@ -421,10 +422,10 @@ fun GmmProbability(self: VadInstT, features: IntArray, total_power: Int, frame_l
// H1: Speech // H1: Speech
// //
// We combine a global LRT with local tests, for each frequency sub-band, // We combine a global LRT with local tests, for each frequency sub-band,
// here defined as |channel|. // here defined as [channel].
for (channel in 0 until kNumChannels) { for (channel in 0 until kNumChannels) {
// For each channel we model the probability with a GMM consisting of // For each channel we model the probability with a GMM consisting of
// |kNumGaussians|, with different means and standard deviations depending // [kNumGaussians], with different means and standard deviations depending
// on H0 or H1. // on H0 or H1.
var h0_test = 0 var h0_test = 0
var h1_test = 0 var h1_test = 0
@ -462,7 +463,7 @@ fun GmmProbability(self: VadInstT, features: IntArray, total_power: Int, frame_l
val shifts_h1 = if (h1_test != 0) NormW32(h1_test) else 31 val shifts_h1 = if (h1_test != 0) NormW32(h1_test) else 31
val log_likelihood_ratio = shifts_h0 - shifts_h1; val log_likelihood_ratio = shifts_h0 - shifts_h1;
// Update |sum_log_likelihood_ratios| with spectrum weighting. This is // Update [sum_log_likelihood_ratios] with spectrum weighting. This is
// used for the global VAD decision. // used for the global VAD decision.
sum_log_likelihood_ratios += log_likelihood_ratio * kSpectrumWeight[channel] sum_log_likelihood_ratios += log_likelihood_ratio * kSpectrumWeight[channel]
@ -551,7 +552,7 @@ fun GmmProbability(self: VadInstT, features: IntArray, total_power: Int, frame_l
if (vadflag != 0) { if (vadflag != 0) {
// Update speech mean vector: // Update speech mean vector:
// |deltaS| = (x-mu)/sigma^2 // [deltaS] = (x-mu)/sigma^2
// sgprvec[k] = |speech_probability[k]| / // sgprvec[k] = |speech_probability[k]| /
// (|speech_probability[0]| + |speech_probability[1]|) // (|speech_probability[0]| + |speech_probability[1]|)
@ -634,30 +635,30 @@ fun GmmProbability(self: VadInstT, features: IntArray, total_power: Int, frame_l
} }
// Separate models if they are too close. // Separate models if they are too close.
// |noise_global_mean| in Q14 (= Q7 * Q7). // [noise_global_mean] in Q14 (= Q7 * Q7).
noise_global_mean = WeightedAverage(self.noise_means, channel, 0, kNoiseDataWeights) noise_global_mean = WeightedAverage(self.noise_means, channel, 0, kNoiseDataWeights)
// |speech_global_mean| in Q14 (= Q7 * Q7). // [speech_global_mean] in Q14 (= Q7 * Q7).
var speech_global_mean = WeightedAverage(self.speech_means, channel, 0, kSpeechDataWeights) var speech_global_mean = WeightedAverage(self.speech_means, channel, 0, kSpeechDataWeights)
// |diff| = "global" speech mean - "global" noise mean. // [diff] = "global" speech mean - "global" noise mean.
// (Q14 shr 9) - (Q14 shr 9) = Q5. // (Q14 shr 9) - (Q14 shr 9) = Q5.
val diff = (speech_global_mean shr 9) - (noise_global_mean shr 9); val diff = (speech_global_mean shr 9) - (noise_global_mean shr 9);
if (diff < kMinimumDifference[channel]) { if (diff < kMinimumDifference[channel]) {
tmp_s16 = kMinimumDifference[channel] - diff; tmp_s16 = kMinimumDifference[channel] - diff;
// |tmp1_s16| = ~0.8 * (kMinimumDifference - diff) in Q7. // [tmp1_s16] = ~0.8 * (kMinimumDifference - diff) in Q7.
// |tmp2_s16| = ~0.2 * (kMinimumDifference - diff) in Q7. // [tmp2_s16] = ~0.2 * (kMinimumDifference - diff) in Q7.
tmp1_s16 = (13 * tmp_s16) shr 2 tmp1_s16 = (13 * tmp_s16) shr 2
tmp2_s16 = (3 * tmp_s16) shr 2 tmp2_s16 = (3 * tmp_s16) shr 2
// Move Gaussian means for speech model by |tmp1_s16| and update // Move Gaussian means for speech model by [tmp1_s16] and update
// |speech_global_mean|. Note that |self.speech_means[channel]| is // [speech_global_mean]. Note that |self.speech_means[channel]| is
// changed after the call. // changed after the call.
speech_global_mean = WeightedAverage(self.speech_means, channel, tmp1_s16, kSpeechDataWeights) speech_global_mean = WeightedAverage(self.speech_means, channel, tmp1_s16, kSpeechDataWeights)
// Move Gaussian means for noise model by -|tmp2_s16| and update // Move Gaussian means for noise model by -[tmp2_s16] and update
// |noise_global_mean|. Note that |self.noise_means[channel]| is // [noise_global_mean]. Note that |self.noise_means[channel]| is
// changed after the call. // changed after the call.
noise_global_mean = WeightedAverage(self.noise_means, channel, -tmp2_s16, kNoiseDataWeights) noise_global_mean = WeightedAverage(self.noise_means, channel, -tmp2_s16, kNoiseDataWeights)
} }
@ -725,33 +726,38 @@ val kSmoothingUp = 32439; // 0.99 in Q15.
// //
// Returns: // Returns:
// : Smoothed minimum value for a moving window. // : Smoothed minimum value for a moving window.
// Inserts |feature_value| into |low_value_vector|, if it is one of the 16 // Inserts [feature_value] into [low_value_vector], if it is one of the 16
// smallest values the last 100 frames. Then calculates and returns the median // smallest values the last 100 frames. Then calculates and returns the median
// of the five smallest values. // of the five smallest values.
fun FindMinimum(self: VadInstT, feature_value: Int, channel: Int): Int { fun FindMinimum(self: VadInstT, feature_value: Int, channel: Int): Int {
var i = 0
var j = 0
var position = -1 var position = -1
// Offset to beginning of the 16 minimum values in memory.
val offset = channel shl 4
var current_median = 1600; var current_median = 1600;
var alpha = 0; var alpha = 0;
var tmp32 = 0; var tmp32 = 0;
// Pointer to memory for the 16 minimum values and the age of each value of val offset = channel shl 4
// the |channel|.
Int * age = &self.index_vector[offset]; // Accessor for the age of each value of the [channel]
Int * smallest_values = &self.low_value_vector[offset]; val age = object {
inline operator fun get(i: Int) = self.index_vector[offset + i]
inline operator fun set(i: Int, value: Int) { self.index_vector[offset + i] = value }
}
// Accessor for the 16 minimum values of the [channel]
val smallest_values = object {
inline operator fun get(i: Int) = self.low_value_vector[offset + i]
inline operator fun set(i: Int, value: Int) { self.low_value_vector[offset + i] = value }
}
assert(channel < kNumChannels); assert(channel < kNumChannels);
// Each value in |smallest_values| is getting 1 loop older. Update |age|, and // Each value in [smallest_values] is getting 1 loop older. Update [age], and
// remove old values. // remove old values.
for (i = 0; i < 16; i++) { for (i in 0 until 16) {
if (age[i] != 100) { if (age[i] != 100) {
age[i]++; age[i]++;
} else { } else {
// Too old value. Remove from memory and shift larger values downwards. // Too old value. Remove from memory and shift larger values downwards.
for (j = i; j < 16; j++) { for (j in i until 16) {
smallest_values[j] = smallest_values[j + 1]; smallest_values[j] = smallest_values[j + 1];
age[j] = age[j + 1]; age[j] = age[j + 1];
} }
@ -760,9 +766,9 @@ fun FindMinimum(self: VadInstT, feature_value: Int, channel: Int): Int {
} }
} }
// Check if |feature_value| is smaller than any of the values in // Check if [feature_value] is smaller than any of the values in
// |smallest_values|. If so, find the |position| where to insert the new value // [smallest_values]. If so, find the [position] where to insert the new value
// (|feature_value|). // ([feature_value]).
if (feature_value < smallest_values[7]) { if (feature_value < smallest_values[7]) {
if (feature_value < smallest_values[3]) { if (feature_value < smallest_values[3]) {
if (feature_value < smallest_values[1]) { if (feature_value < smallest_values[1]) {
@ -816,7 +822,7 @@ fun FindMinimum(self: VadInstT, feature_value: Int, channel: Int): Int {
// If we have detected a new small value, insert it at the correct position // If we have detected a new small value, insert it at the correct position
// and shift larger values up. // and shift larger values up.
if (position > -1) { if (position > -1) {
for (i = 15; i > position; i--) { for (i in 15 downTo position + 1) {
smallest_values[i] = smallest_values[i - 1]; smallest_values[i] = smallest_values[i - 1];
age[i] = age[i - 1]; age[i] = age[i - 1];
} }
@ -824,7 +830,7 @@ fun FindMinimum(self: VadInstT, feature_value: Int, channel: Int): Int {
age[position] = 1; age[position] = 1;
} }
// Get |current_median|. // Get [current_median].
if (self.frame_counter > 2) { if (self.frame_counter > 2) {
current_median = smallest_values[2]; current_median = smallest_values[2];
} else if (self.frame_counter > 0) { } else if (self.frame_counter > 0) {
@ -842,7 +848,7 @@ fun FindMinimum(self: VadInstT, feature_value: Int, channel: Int): Int {
tmp32 = (alpha + 1) * self.mean_value[channel]; tmp32 = (alpha + 1) * self.mean_value[channel];
tmp32 += (WEBRTC_SPL_WORD16_MAX - alpha) * current_median; tmp32 += (WEBRTC_SPL_WORD16_MAX - alpha) * current_median;
tmp32 += 16384; tmp32 += 16384;
self.mean_value[channel] = (Int)(tmp32 shr 15); self.mean_value[channel] = tmp32 shr 15
return self.mean_value[channel]; return self.mean_value[channel];
} }
@ -851,21 +857,22 @@ fun FindMinimum(self: VadInstT, feature_value: Int, channel: Int): Int {
// webrtc/common_audio/vad/vad_filterbank.c // webrtc/common_audio/vad/vad_filterbank.c
// Constants used in LogOfEnergy(). // Constants used in LogOfEnergy().
val Int kLogConst = 24660; // 160*log10(2) in Q9. val kLogConst = 24660; // 160*log10(2) in Q9.
val Int kLogEnergyIntPart = 14336; // 14 in Q10 val kLogEnergyIntPart = 14336; // 14 in Q10
// Coefficients used by HighPassFilter, Q14. // Coefficients used by HighPassFilter, Q14.
val Int kHpZeroCoefs[3] = { 6631, -13262, 6631 }; val kHpZeroCoefs = intArrayOf(6631, -13262, 6631)
val Int kHpPoleCoefs[3] = { 16384, -7756, 5620 }; val kHpPoleCoefs = intArrayOf(16384, -7756, 5620)
// Allpass filter coefficients, upper and lower, in Q15. // Allpass filter coefficients, upper and lower, in Q15.
// Upper: 0.64, Lower: 0.17 // Upper: 0.64, Lower: 0.17
val Int kAllPassCoefsQ15[2] = { 20972, 5571 }; val kUpperAllPassCoefsQ15 = 20972
val kLowerAllPassCoefsQ15 = 5571
// Adjustment for division with two in SplitFilter. // Adjustment for division with two in SplitFilter.
val Int kOffsetVector[6] = { 368, 368, 272, 176, 176, 176 }; val kOffsetVector = intArrayOf(368, 368, 272, 176, 176, 176)
// High pass filtering, with a cut-off frequency at 80 Hz, if the |data_in| is // High pass filtering, with a cut-off frequency at 80 Hz, if the [data_in] is
// sampled at 500 Hz. // sampled at 500 Hz.
// //
// - data_in [i] : Input audio data sampled at 500 Hz. // - data_in [i] : Input audio data sampled at 500 Hz.
@ -873,13 +880,7 @@ val Int kOffsetVector[6] = { 368, 368, 272, 176, 176, 176 };
// - filter_state [i/o] : State of the filter. // - filter_state [i/o] : State of the filter.
// - data_out [o] : Output audio data in the frequency interval // - data_out [o] : Output audio data in the frequency interval
// 80 - 250 Hz. // 80 - 250 Hz.
void HighPassFilter(val Sample* data_in, Int data_length, Int* filter_state, Sample* data_out) { fun HighPassFilter(input: AudioBuffer, filter_state: IntArray): AudioBuffer {
Int i;
val Sample* in_ptr = data_in;
Sample* out_ptr = data_out;
Int tmp32 = 0;
// The sum of the absolute values of the impulse response: // The sum of the absolute values of the impulse response:
// The zero/pole-filter has a max amplification of a single sample of: 1.4546 // The zero/pole-filter has a max amplification of a single sample of: 1.4546
// Impulse response: 0.4047 -0.6179 -0.0266 0.1993 0.1035 -0.0194 // Impulse response: 0.4047 -0.6179 -0.0266 0.1993 0.1035 -0.0194
@ -888,194 +889,200 @@ void HighPassFilter(val Sample* data_in, Int data_length, Int* filter_state, Sam
// The all-pole section has a max amplification of a single sample of: 1.9931 // The all-pole section has a max amplification of a single sample of: 1.9931
// Impulse response: 1.0000 0.4734 -0.1189 -0.2187 -0.0627 0.04532 // Impulse response: 1.0000 0.4734 -0.1189 -0.2187 -0.0627 0.04532
for (i = 0; i < data_length; i++) { val result = SampleArray(input.size)
for (i in 0 until input.size) {
// All-zero section (filter coefficients in Q14). // All-zero section (filter coefficients in Q14).
tmp32 = kHpZeroCoefs[0] * *in_ptr; var tmp32 = kHpZeroCoefs[0] * input[i]
tmp32 += kHpZeroCoefs[1] * filter_state[0]; tmp32 += kHpZeroCoefs[1] * filter_state[0];
tmp32 += kHpZeroCoefs[2] * filter_state[1]; tmp32 += kHpZeroCoefs[2] * filter_state[1];
filter_state[1] = filter_state[0]; filter_state[1] = filter_state[0];
filter_state[0] = *in_ptr++; filter_state[0] = input[i].toInt()
// All-pole section (filter coefficients in Q14). // All-pole section (filter coefficients in Q14).
tmp32 -= kHpPoleCoefs[1] * filter_state[2]; tmp32 -= kHpPoleCoefs[1] * filter_state[2];
tmp32 -= kHpPoleCoefs[2] * filter_state[3]; tmp32 -= kHpPoleCoefs[2] * filter_state[3];
filter_state[3] = filter_state[2]; filter_state[3] = filter_state[2];
filter_state[2] = (Int)(tmp32 shr 14); filter_state[2] = tmp32 shr 14
*out_ptr++ = filter_state[2]; result[i] = filter_state[2].toShort()
} }
return AudioBuffer(result)
} }
// All pass filtering of |data_in|, used before splitting the signal into two // All pass filtering of [data_in], used before splitting the signal into two
// frequency bands (low pass vs high pass). // frequency bands (low pass vs high pass).
// Note that |data_in| and |data_out| can NOT correspond to the same address. // Note that [data_in] and [data_out] can NOT correspond to the same address.
// //
// - data_in [i] : Input audio signal given in Q0. // - data_in [i] : Input audio signal given in Q0.
// - data_length [i] : Length of input and output data. // - data_length [i] : Length of input and output data.
// - filter_coefficient [i] : Given in Q15. // - filter_coefficient [i] : Given in Q15.
// - filter_state [i/o] : State of the filter given in Q(-1). // - filter_state [i/o] : State of the filter given in Q(-1).
// - data_out [o] : Output audio signal given in Q(-1). // - data_out [o] : Output audio signal given in Q(-1).
void AllPassFilter(val Sample* data_in, Int data_length, fun AllPassFilter(input: AudioBuffer, filter_coefficient: Int, filter_state: MutableInt): AudioBuffer {
Int filter_coefficient, Int* filter_state,
Sample* data_out) {
// The filter can only cause overflow (in the w16 output variable) // The filter can only cause overflow (in the w16 output variable)
// if more than 4 consecutive input numbers are of maximum value and // if more than 4 consecutive input numbers are of maximum value and
// has the the same sign as the impulse responses first taps. // has the the same sign as the impulse responses first taps.
// First 6 taps of the impulse response: // First 6 taps of the impulse response:
// 0.6399 0.5905 -0.3779 0.2418 -0.1547 0.0990 // 0.6399 0.5905 -0.3779 0.2418 -0.1547 0.0990
Int i; val result = SampleArray(input.size / 2)
Int tmp16 = 0; var state32 = filter_state.toInt() * (1 shl 16) // Q15
Int tmp32 = 0; for (i in 0 until input.size step 2) {
Int state32 = ((Int)(*filter_state) * (1 shl 16)); // Q15 val tmp32 = state32 + filter_coefficient * input[i]
val tmp16 = tmp32 shr 16 // Q(-1)
for (i = 0; i < data_length; i++) { result[i / 2] = tmp16.toShort()
tmp32 = state32 + filter_coefficient * *data_in; state32 = (input[i] * (1 shl 14)) - filter_coefficient * tmp16; // Q14
tmp16 = (Int)(tmp32 shr 16); // Q(-1)
*data_out++ = tmp16;
state32 = (*data_in * (1 shl 14)) - filter_coefficient * tmp16; // Q14
state32 *= 2; // Q15. state32 *= 2; // Q15.
data_in += 2;
} }
filter_state.setValue(state32 shr 16) // Q(-1)
*filter_state = (Int)(state32 shr 16); // Q(-1) return AudioBuffer(result)
} }
// Splits |data_in| into |hp_data_out| and |lp_data_out| corresponding to data class SplitFilterResult(
val highPassData: AudioBuffer,
val lowPassData: AudioBuffer
)
// Splits [data_in] into [hp_data_out] and [lp_data_out] corresponding to
// an upper (high pass) part and a lower (low pass) part respectively. // an upper (high pass) part and a lower (low pass) part respectively.
// //
// - data_in [i] : Input audio data to be split into two frequency bands. // - data_in [i] : Input audio data to be split into two frequency bands.
// - data_length [i] : Length of |data_in|. // - data_length [i] : Length of [data_in].
// - upper_state [i/o] : State of the upper filter, given in Q(-1). // - upper_state [i/o] : State of the upper filter, given in Q(-1).
// - lower_state [i/o] : State of the lower filter, given in Q(-1). // - lower_state [i/o] : State of the lower filter, given in Q(-1).
// - hp_data_out [o] : Output audio data of the upper half of the spectrum. // - hp_data_out [o] : Output audio data of the upper half of the spectrum.
// The length is |data_length| / 2. // The length is [data_length] / 2.
// - lp_data_out [o] : Output audio data of the lower half of the spectrum. // - lp_data_out [o] : Output audio data of the lower half of the spectrum.
// The length is |data_length| / 2. // The length is [data_length] / 2.
void SplitFilter(val Sample* data_in, Int data_length, fun SplitFilter(input: AudioBuffer, upper_state: MutableInt, lower_state: MutableInt): SplitFilterResult {
Int* upper_state, Int* lower_state, val resultSize = input.size / 2 // Downsampling by 2
Sample* hp_data_out, Sample* lp_data_out) {
Int i;
Int half_length = data_length shr 1; // Downsampling by 2.
Int tmp_out;
// All-pass filtering upper branch. // All-pass filtering upper branch.
AllPassFilter(&data_in[0], half_length, kAllPassCoefsQ15[0], upper_state, hp_data_out); val tempHighPass = AllPassFilter(input, kUpperAllPassCoefsQ15, upper_state)
assert(tempHighPass.size == resultSize)
// All-pass filtering lower branch. // All-pass filtering lower branch.
AllPassFilter(&data_in[1], half_length, kAllPassCoefsQ15[1], lower_state, lp_data_out); val tempLowPass = AllPassFilter(AudioBuffer(input, 1), kLowerAllPassCoefsQ15, lower_state)
assert(tempLowPass.size == resultSize)
// Make LP and HP signals. // Make LP and HP signals.
for (i = 0; i < half_length; i++) { val highPassData = SampleArray(resultSize)
tmp_out = *hp_data_out; val lowPassData = SampleArray(resultSize)
*hp_data_out++ -= *lp_data_out; for (i in 0 until resultSize) {
*lp_data_out++ += tmp_out; highPassData[i] = (tempHighPass[i] - tempLowPass[i]).toShort()
lowPassData[i] = (tempLowPass[i] + tempHighPass[i]).toShort()
} }
return SplitFilterResult(AudioBuffer(highPassData), AudioBuffer(lowPassData))
} }
// Calculates the energy of |data_in| in dB, and also updates an overall // Calculates the energy of [data_in] in dB, and also updates an overall
// |total_energy| if necessary. // [total_energy] if necessary.
// //
// - data_in [i] : Input audio data for energy calculation. // - data_in [i] : Input audio data for energy calculation.
// - data_length [i] : Length of input data. // - data_length [i] : Length of input data.
// - offset [i] : Offset value added to |log_energy|. // - offset [i] : Offset value added to [log_energy].
// - total_energy [i/o] : An external energy updated with the energy of // - total_energy [i/o] : An external energy updated with the energy of
// |data_in|. // [data_in].
// NOTE: |total_energy| is only updated if // NOTE: [total_energy] is only updated if
// |total_energy| <= |kMinEnergy|. // [total_energy] <= [kMinEnergy].
// - log_energy [o] : 10 * log10("energy of |data_in|") given in Q4. // - log_energy [o] : 10 * log10("energy of [data_in]") given in Q4.
void LogOfEnergy(val Sample* data_in, Int data_length, fun LogOfEnergy(input: AudioBuffer, offset: Int, total_energy: MutableInt): Int {
Int offset, Int* total_energy, assert(input.size > 0);
Int* log_energy) {
// |tot_rshifts| accumulates the number of right shifts performed on |energy|. val energyResult = Energy(input)
Int tot_rshifts = 0; // [tot_rshifts] accumulates the number of right shifts performed on [energy].
// The |energy| will be normalized to 15 bits. We use unsigned integer because var tot_rshifts = energyResult.rightShifts
// The [energy] will be normalized to 15 bits. We use unsigned integer because
// we eventually will mask out the fractional part. // we eventually will mask out the fractional part.
UInt energy = 0; var energy = energyResult.energy.toUInt()
assert(data_in != NULL); if (energy == 0u) {
assert(data_length > 0); return offset
energy = (UInt)Energy((Int*)data_in, data_length,
&tot_rshifts);
if (energy != 0) {
// By construction, normalizing to 15 bits is equivalent with 17 leading
// zeros of an unsigned 32 bit value.
Int normalizing_rshifts = 17 - NormU32(energy);
// In a 15 bit representation the leading bit is 2^14. log2(2^14) in Q10 is
// (14 shl 10), which is what we initialize |log2_energy| with. For a more
// detailed derivations, see below.
Int log2_energy = kLogEnergyIntPart;
tot_rshifts += normalizing_rshifts;
// Normalize |energy| to 15 bits.
// |tot_rshifts| is now the total number of right shifts performed on
// |energy| after normalization. This means that |energy| is in
// Q(-tot_rshifts).
if (normalizing_rshifts < 0) {
energy shl= -normalizing_rshifts;
} else {
energy shr= normalizing_rshifts;
}
// Calculate the energy of |data_in| in dB, in Q4.
//
// 10 * log10("true energy") in Q4 = 2^4 * 10 * log10("true energy") =
// 160 * log10(|energy| * 2^|tot_rshifts|) =
// 160 * log10(2) * log2(|energy| * 2^|tot_rshifts|) =
// 160 * log10(2) * (log2(|energy|) + log2(2^|tot_rshifts|)) =
// (160 * log10(2)) * (log2(|energy|) + |tot_rshifts|) =
// |kLogConst| * (|log2_energy| + |tot_rshifts|)
//
// We know by construction that |energy| is normalized to 15 bits. Hence,
// |energy| = 2^14 + frac_Q15, where frac_Q15 is a fractional part in Q15.
// Further, we'd like |log2_energy| in Q10
// log2(|energy|) in Q10 = 2^10 * log2(2^14 + frac_Q15) =
// 2^10 * log2(2^14 * (1 + frac_Q15 * 2^-14)) =
// 2^10 * (14 + log2(1 + frac_Q15 * 2^-14)) ~=
// (14 shl 10) + 2^10 * (frac_Q15 * 2^-14) =
// (14 shl 10) + (frac_Q15 * 2^-4) = (14 shl 10) + (frac_Q15 shr 4)
//
// Note that frac_Q15 = (|energy| & 0x00003FFF)
// Calculate and add the fractional part to |log2_energy|.
log2_energy += (Int)((energy & 0x00003FFF) shr 4);
// |kLogConst| is in Q9, |log2_energy| in Q10 and |tot_rshifts| in Q0.
// Note that we in our derivation above have accounted for an output in Q4.
*log_energy = (Int)(((kLogConst * log2_energy) shr 19) +
((tot_rshifts * kLogConst) shr 9));
if (*log_energy < 0) {
*log_energy = 0;
}
} else {
*log_energy = offset;
return;
} }
*log_energy += offset; // By construction, normalizing to 15 bits is equivalent with 17 leading
// zeros of an unsigned 32 bit value.
val normalizing_rshifts = 17 - NormU32(energy);
// In a 15 bit representation the leading bit is 2^14. log2(2^14) in Q10 is
// (14 shl 10), which is what we initialize [log2_energy] with. For a more
// detailed derivations, see below.
var log2_energy = kLogEnergyIntPart;
// Update the approximate |total_energy| with the energy of |data_in|, if tot_rshifts += normalizing_rshifts;
// |total_energy| has not exceeded |kMinEnergy|. |total_energy| is used as an // Normalize [energy] to 15 bits.
// [tot_rshifts] is now the total number of right shifts performed on
// [energy] after normalization. This means that [energy] is in
// Q(-tot_rshifts).
energy = if (normalizing_rshifts < 0)
energy shl -normalizing_rshifts
else
energy shr normalizing_rshifts
// Calculate the energy of [data_in] in dB, in Q4.
//
// 10 * log10("true energy") in Q4 = 2^4 * 10 * log10("true energy") =
// 160 * log10([energy] * 2^[tot_rshifts]) =
// 160 * log10(2) * log2([energy] * 2^[tot_rshifts]) =
// 160 * log10(2) * (log2([energy]) + log2(2^[tot_rshifts])) =
// (160 * log10(2)) * (log2([energy]) + [tot_rshifts]) =
// [kLogConst] * ([log2_energy] + [tot_rshifts])
//
// We know by construction that [energy] is normalized to 15 bits. Hence,
// [energy] = 2^14 + frac_Q15, where frac_Q15 is a fractional part in Q15.
// Further, we'd like [log2_energy] in Q10
// log2([energy]) in Q10 = 2^10 * log2(2^14 + frac_Q15) =
// 2^10 * log2(2^14 * (1 + frac_Q15 * 2^-14)) =
// 2^10 * (14 + log2(1 + frac_Q15 * 2^-14)) ~=
// (14 shl 10) + 2^10 * (frac_Q15 * 2^-14) =
// (14 shl 10) + (frac_Q15 * 2^-4) = (14 shl 10) + (frac_Q15 shr 4)
//
// Note that frac_Q15 = ([energy] & 0x00003FFF)
// Calculate and add the fractional part to [log2_energy].
log2_energy += ((energy and 0x00003FFFu) shr 4).toInt()
// [kLogConst] is in Q9, [log2_energy] in Q10 and [tot_rshifts] in Q0.
// Note that we in our derivation above have accounted for an output in Q4.
var log_energy = (((kLogConst * log2_energy) shr 19) + (tot_rshifts * kLogConst) shr 9)
if (log_energy < 0) {
log_energy = 0;
}
log_energy += offset;
// Update the approximate [total_energy] with the energy of [data_in], if
// [total_energy] has not exceeded [kMinEnergy]. [total_energy] is used as an
// energy indicator in GmmProbability() in vad_core.c. // energy indicator in GmmProbability() in vad_core.c.
if (*total_energy <= kMinEnergy) { if (total_energy.toInt() <= kMinEnergy) {
if (tot_rshifts >= 0) { if (tot_rshifts >= 0) {
// We know by construction that the |energy| > |kMinEnergy| in Q0, so add // We know by construction that the [energy] > [kMinEnergy] in Q0, so add
// an arbitrary value such that |total_energy| exceeds |kMinEnergy|. // an arbitrary value such that [total_energy] exceeds [kMinEnergy].
*total_energy += kMinEnergy + 1; total_energy.add(kMinEnergy + 1)
} else { } else {
// By construction |energy| is represented by 15 bits, hence any number of // By construction [energy] is represented by 15 bits, hence any number of
// right shifted |energy| will fit in an Int. In addition, adding the // right shifted [energy] will fit in an Int. In addition, adding the
// value to |total_energy| is wrap around safe as long as // value to [total_energy] is wrap around safe as long as
// |kMinEnergy| < 8192. // [kMinEnergy] < 8192.
*total_energy += (Int)(energy shr -tot_rshifts); // Q0. total_energy.add((energy shr -tot_rshifts).toInt()); // Q0.
} }
} }
return log_energy
} }
// Takes |data_length| samples of |data_in| and calculates the logarithm of the data class FeatureResult(
// energy of each of the |kNumChannels| = 6 frequency bands used by the VAD: // 10 * log10(energy in each frequency band), Q4
val features: List<Int>,
// Total energy of the signal
// NOTE: This value is not exact. It is only used in a comparison.
val totalEnergy: Int
)
// Takes [data_length] samples of [data_in] and calculates the logarithm of the
// energy of each of the [kNumChannels] = 6 frequency bands used by the VAD:
// 80 Hz - 250 Hz // 80 Hz - 250 Hz
// 250 Hz - 500 Hz // 250 Hz - 500 Hz
// 500 Hz - 1000 Hz // 500 Hz - 1000 Hz
@ -1083,10 +1090,10 @@ void LogOfEnergy(val Sample* data_in, Int data_length,
// 2000 Hz - 3000 Hz // 2000 Hz - 3000 Hz
// 3000 Hz - 4000 Hz // 3000 Hz - 4000 Hz
// //
// The values are given in Q4 and written to |features|. Further, an approximate // The values are given in Q4 and written to [features]. Further, an approximate
// overall energy is returned. The return value is used in // overall energy is returned. The return value is used in
// GmmProbability() as a signal indicator, hence it is arbitrary above // GmmProbability() as a signal indicator, hence it is arbitrary above
// the threshold |kMinEnergy|. // the threshold [kMinEnergy].
// //
// - self [i/o] : State information of the VAD. // - self [i/o] : State information of the VAD.
// - data_in [i] : Input audio data, for feature extraction. // - data_in [i] : Input audio data, for feature extraction.
@ -1094,91 +1101,56 @@ void LogOfEnergy(val Sample* data_in, Int data_length,
// - features [o] : 10 * log10(energy in each frequency band), Q4. // - features [o] : 10 * log10(energy in each frequency band), Q4.
// - returns : Total energy of the signal (NOTE! This value is not // - returns : Total energy of the signal (NOTE! This value is not
// exact. It is only used in a comparison.) // exact. It is only used in a comparison.)
Int CalculateFeatures(VadInstT* self, val Sample* data_in, Int data_length, Int* features) { fun CalculateFeatures(self: VadInstT, input: AudioBuffer): FeatureResult {
Int total_energy = 0; assert(input.size == 80)
// We expect |data_length| to be 80, 160 or 240 samples, which corresponds to
// 10, 20 or 30 ms in 8 kHz. Therefore, the intermediate downsampled data will
// have at most 120 samples after the first split and at most 60 samples after
// the second split.
Sample hp_120[120], lp_120[120];
Sample hp_60[60], lp_60[60];
val Int half_data_length = data_length shr 1;
Int length = half_data_length; // |data_length| / 2, corresponds to
// bandwidth = 2000 Hz after downsampling.
// Initialize variables for the first SplitFilter().
Int frequency_band = 0;
val Sample* in_ptr = data_in; // [0 - 4000] Hz.
Sample* hp_out_ptr = hp_120; // [2000 - 4000] Hz.
Sample* lp_out_ptr = lp_120; // [0 - 2000] Hz.
assert(data_length <= 240);
assert(4 < kNumChannels - 1); // Checking maximum |frequency_band|.
// Split at 2000 Hz and downsample. // Split at 2000 Hz and downsample.
SplitFilter(in_ptr, data_length, &self.upper_state[frequency_band], var frequency_band = 0
&self.lower_state[frequency_band], hp_out_ptr, lp_out_ptr); val `0 to 4000 Hz` = input
val (`2000 to 4000 Hz`, `0 to 2000 Hz`) =
SplitFilter(`0 to 4000 Hz`, self.upper_state[frequency_band], self.lower_state[frequency_band]);
// For the upper band (2000 Hz - 4000 Hz) split at 3000 Hz and downsample. // For the upper band (2000 to 4000 Hz) split at 3000 Hz and downsample.
frequency_band = 1; frequency_band = 1
in_ptr = hp_120; // [2000 - 4000] Hz. val (`3000 to 4000 Hz`, `2000 to 3000 Hz`) =
hp_out_ptr = hp_60; // [3000 - 4000] Hz. SplitFilter(`2000 to 4000 Hz`, self.upper_state[frequency_band], self.lower_state[frequency_band])
lp_out_ptr = lp_60; // [2000 - 3000] Hz.
SplitFilter(in_ptr, length, &self.upper_state[frequency_band],
&self.lower_state[frequency_band], hp_out_ptr, lp_out_ptr);
// Energy in 3000 Hz - 4000 Hz. // For the lower band (0 to 2000 Hz) split at 1000 Hz and downsample.
length shr= 1; // |data_length| / 4 <=> bandwidth = 1000 Hz.
LogOfEnergy(hp_60, length, kOffsetVector[5], &total_energy, &features[5]);
// Energy in 2000 Hz - 3000 Hz.
LogOfEnergy(lp_60, length, kOffsetVector[4], &total_energy, &features[4]);
// For the lower band (0 Hz - 2000 Hz) split at 1000 Hz and downsample.
frequency_band = 2; frequency_band = 2;
in_ptr = lp_120; // [0 - 2000] Hz. val (`1000 to 2000 Hz`, `0 to 1000 Hz`) =
hp_out_ptr = hp_60; // [1000 - 2000] Hz. SplitFilter(`0 to 2000 Hz`, self.upper_state[frequency_band], self.lower_state[frequency_band])
lp_out_ptr = lp_60; // [0 - 1000] Hz.
length = half_data_length; // |data_length| / 2 <=> bandwidth = 2000 Hz.
SplitFilter(in_ptr, length, &self.upper_state[frequency_band],
&self.lower_state[frequency_band], hp_out_ptr, lp_out_ptr);
// Energy in 1000 Hz - 2000 Hz. // For the lower band (0 to 1000 Hz) split at 500 Hz and downsample.
length shr= 1; // |data_length| / 4 <=> bandwidth = 1000 Hz.
LogOfEnergy(hp_60, length, kOffsetVector[3], &total_energy, &features[3]);
// For the lower band (0 Hz - 1000 Hz) split at 500 Hz and downsample.
frequency_band = 3; frequency_band = 3;
in_ptr = lp_60; // [0 - 1000] Hz. val (`500 to 1000 Hz`, `0 to 500 Hz`) =
hp_out_ptr = hp_120; // [500 - 1000] Hz. SplitFilter(`0 to 1000 Hz`, self.upper_state[frequency_band], self.lower_state[frequency_band]);
lp_out_ptr = lp_120; // [0 - 500] Hz.
SplitFilter(in_ptr, length, &self.upper_state[frequency_band],
&self.lower_state[frequency_band], hp_out_ptr, lp_out_ptr);
// Energy in 500 Hz - 1000 Hz. // For the lower band (0 t0 500 Hz) split at 250 Hz and downsample.
length shr= 1; // |data_length| / 8 <=> bandwidth = 500 Hz.
LogOfEnergy(hp_120, length, kOffsetVector[2], &total_energy, &features[2]);
// For the lower band (0 Hz - 500 Hz) split at 250 Hz and downsample.
frequency_band = 4; frequency_band = 4;
in_ptr = lp_120; // [0 - 500] Hz. val (`250 to 500 Hz`, `0 to 250 Hz`) =
hp_out_ptr = hp_60; // [250 - 500] Hz. SplitFilter(`0 to 500 Hz`, self.upper_state[frequency_band], self.lower_state[frequency_band])
lp_out_ptr = lp_60; // [0 - 250] Hz.
SplitFilter(in_ptr, length, &self.upper_state[frequency_band],
&self.lower_state[frequency_band], hp_out_ptr, lp_out_ptr);
// Energy in 250 Hz - 500 Hz. // Remove 0 to 80 Hz by high pass filtering the lower band.
length shr= 1; // |data_length| / 16 <=> bandwidth = 250 Hz. val `80 to 250 Hz` = HighPassFilter(`0 to 250 Hz`, self.hp_filter_state)
LogOfEnergy(hp_60, length, kOffsetVector[1], &total_energy, &features[1]);
// Remove 0 Hz - 80 Hz, by high pass filtering the lower band. val total_energy = MutableInt(0)
HighPassFilter(lp_60, length, self.hp_filter_state, hp_120); val `energy in 3000 to 4000 Hz` = LogOfEnergy(`3000 to 4000 Hz`, kOffsetVector[5], total_energy)
val `energy in 2000 to 3000 Hz` = LogOfEnergy(`2000 to 3000 Hz`, kOffsetVector[4], total_energy)
val `energy in 1000 to 2000 Hz` = LogOfEnergy(`1000 to 2000 Hz`, kOffsetVector[3], total_energy)
val `energy in 500 to 1000 Hz` = LogOfEnergy(`500 to 1000 Hz`, kOffsetVector[2], total_energy)
val `energy in 250 to 500 Hz` = LogOfEnergy(`250 to 500 Hz`, kOffsetVector[1], total_energy)
val `energy in 50 to 250 Hz` = LogOfEnergy(`80 to 250 Hz`, kOffsetVector[0], total_energy);
// Energy in 80 Hz - 250 Hz. val features = listOf(
LogOfEnergy(hp_120, length, kOffsetVector[0], &total_energy, &features[0]); `energy in 50 to 250 Hz`,
`energy in 250 to 500 Hz`,
return total_energy; `energy in 500 to 1000 Hz`,
`energy in 1000 to 2000 Hz`,
`energy in 2000 to 3000 Hz`,
`energy in 3000 to 4000 Hz`
)
assert(features.size == kNumChannels)
return FeatureResult(features, total_energy.toInt())
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////
@ -1205,20 +1177,17 @@ Int CalculateFeatures(VadInstT* self, val Sample* data_in, Int data_length, Int*
* 0 - No active speech * 0 - No active speech
* 1-6 - Active speech * 1-6 - Active speech
*/ */
Int CalcVad8khz(VadInstT* inst, val Sample* speech_frame, Int frame_length) { fun CalcVad8khz(inst: VadInstT, speech_frame: AudioBuffer): Int {
Int feature_vector[kNumChannels], total_power;
// Get power in the bands // Get power in the bands
total_power = CalculateFeatures(inst, speech_frame, frame_length, val (features, totalEnergy) = CalculateFeatures(inst, speech_frame);
feature_vector);
// Make a VAD // Make a VAD
inst->vad = GmmProbability(inst, feature_vector, total_power, frame_length); inst.vad = GmmProbability(inst, features, totalEnergy, speech_frame.size);
return inst->vad; return inst.vad;
} }
// Calculates a VAD decision for the |audio_frame|. For valid sampling rates // Calculates a VAD decision for the [audio_frame]. For valid sampling rates
// frame lengths, see the description of ValidRatesAndFrameLengths(). // frame lengths, see the description of ValidRatesAndFrameLengths().
// //
// - handle [i/o] : VAD Instance. Needs to be initialized by // - handle [i/o] : VAD Instance. Needs to be initialized by
@ -1230,26 +1199,9 @@ Int CalcVad8khz(VadInstT* inst, val Sample* speech_frame, Int frame_length) {
// returns : 1 - (Active Voice), // returns : 1 - (Active Voice),
// 0 - (Non-active Voice), // 0 - (Non-active Voice),
// -1 - (Error) // -1 - (Error)
Int ProcessVad(VadInst* handle, Int fs, val Sample* audio_frame, Int frame_length) { fun ProcessVad(self: VadInstT, fs: Int, audio_frame: AudioBuffer): Boolean {
Int vad = -1; assert(fs == 8000)
VadInstT* self = (VadInstT*)handle;
if (handle == NULL) { val vad = CalcVad8khz(self, audio_frame);
return -1; return vad != 0
}
if (self.init_flag != kInitCheck) {
return -1;
}
if (audio_frame == NULL) {
return -1;
}
if (fs != 8000) return -1;
vad = CalcVad8khz(self, audio_frame, frame_length);
if (vad > 0) {
vad = 1;
}
return vad;
} }