/*
 * (C) Copyright 2014 Amaury Crickx
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.bitsinharmony.recognito.features;

/**
 * Base class for windowed features extractor
 * <p>
 * Constructor computes default window size by calling {@link #getWindowSize(float)}. <br/>
 * @see {@link #getWindowSize(float)}
 * </p>
 * @author Amaury Crickx
 * @param <T> the kind of features to extract, specified by implementing classes
 */
public abstract class WindowedFeaturesExtractor<T> 
        implements FeaturesExtractor<T> {
    
    private static final int DEFAULT_TARGET_WINDOW_LENGTH_IN_MILLIS = 24;
    private static final float MIN_SAMPLE_RATE = 8000.0F;

    protected final int windowSize;
    protected final float sampleRate;

    /**
     * Base constructor required by this abstract class
     * @param sampleRate the sample rate of the voice samples, minimum 8000.0
     */
    public WindowedFeaturesExtractor(float sampleRate) {
        if(sampleRate < MIN_SAMPLE_RATE) {
            throw new IllegalArgumentException("Sample rate should be at least 8000 Hz");
        }
        this.sampleRate = sampleRate;
        this.windowSize = getWindowSize(sampleRate);
    }

    /* (non-Javadoc)
     * @see com.recognito.processing.features.FeaturesExtractor#extractFeatures(double[])
     */
    public abstract T extractFeatures(double[] voiceSample);

    /**
     * Called by the constructor of this class.
     * This implementation delegates to {@link #getClosestPowerOfTwoWindowSize(float, int)}
     * with default targetSizeInMillis value
     * <p>
     * Implementing classes may wish to override this method by delegating with another target value in millis
     * or implement another logic alltogether
     * </p>
     * @param sampleRate the sample rate in Hz (times per second), minimum 8000.0
     * @return the window size
     */
    protected int getWindowSize(float sampleRate) {
        return getClosestPowerOfTwoWindowSize(sampleRate, DEFAULT_TARGET_WINDOW_LENGTH_IN_MILLIS);
    }

    /**
     * Computes the window size that is both the closest to targetSizeInMillis and a power of 2
     * <p>
     * Note : window size using a power of 2 is required e.g. when using discrete FFT algorithm
     * </p>
     * @param sampleRate the sample rate in Hz (times per second), minimum 8000.0
     * @param targetSizeInMillis the target size in millis
     * @return the window size
     */
    protected final int getClosestPowerOfTwoWindowSize(float sampleRate, int targetSizeInMillis) {
        boolean done = false;
        int pow = 8; // 8 bytes == 1ms at 8000 Hz
        float previousMillis = 0.0f;
        
        while(!done) {
            float millis = 1000 / sampleRate * pow;
            if(millis < targetSizeInMillis) {
                previousMillis = millis;
                pow *= 2;
            } else {
                // closest value to target wins
                if(Math.abs(targetSizeInMillis - millis) > targetSizeInMillis - previousMillis) {
                    pow /= 2; // previousMillis was closer
                }
                done = true;
            }
        }
        return pow;
    }
    
}

