package com.brackeen.javagamebook.sound;

/**
    Klasa EchoFilter dziedziczy po SoundFilter i emuluje efekt echa.
    @see FilteredSoundStream
*/
public class EchoFilter extends SoundFilter {

    private short[] delayBuffer;
    private int delayBufferPos;
    private float decay;

    /**
        Tworzenie EchoFilter z określoną liczbą opóźnionych próbek
        oraz wytłumieniem.
        <p>Liczba opóźnionych próbek określa czas, po którym jest
        słyszalne pierwsze echo. W przypadku echa o opóźnieniu 1 sekundy
        dla dźwięku mono (44100 Hz) należy użyć wartości 44100.
        <p>Wartość wytłumienia oznacza stopień wyciszenia echa.
        Wartość 0,5 oznacza, że echo jest słyszane z połową
        głośności.
    */
    public EchoFilter(int numDelaySamples, float decay) {
        delayBuffer = new short[numDelaySamples];
        this.decay = decay;
    }


    /**
        Odczytanie ilości pozostałych danych, generowanych przez filtr
        po zakończeniu odtwarzania oryginalnego dźwięku.
        Zapewnia podtrzymanie dźwięku do 1%
        oryginalnej głośności (amplitudy).
    */
    public int getRemainingSize() {
        float finalDecay = 0.01f;
        // Pobrane z Math.pow(decay,x) <= finalDecay
        int numRemainingBuffers = (int)Math.ceil(
            Math.log(finalDecay) / Math.log(decay));
        int bufferSize = delayBuffer.length * 2;

        return bufferSize * numRemainingBuffers;
    }

    /**
        Kasuje wewnętrzne bufory opóźniające obiektu EchoFilter.
    */
    public void reset() {
        for (int i=0; i<delayBuffer.length; i++) {
            delayBuffer[i] = 0;
        }
        delayBufferPos = 0;
    }

    /**
        Filtruje próbki dźwiękowe w celu dodania echa. Odtwarzane próbki
        są dodawane do dźwięku z bufora opóźnienia, pomnożonego 
        przez wartość wygłuszania. Wynik jest również zapisywany 
        w buforze opóźnienia, można więc usłyszeć wielokrotne echo.
    */
    public void filter(byte[] samples, int offset, int length) {

        for (int i=offset; i<offset+length; i+=2) {
            // Aktualizacja próbek.
            short oldSample = getSample(samples, i);
            short newSample = (short)(oldSample + decay *
                delayBuffer[delayBufferPos]);
            setSample(samples, i, newSample);

            // Aktualizacja bufora opóźnienia.
            delayBuffer[delayBufferPos] = newSample;
            delayBufferPos++;
            if (delayBufferPos == delayBuffer.length) {
                delayBufferPos = 0;
            }
        }
    }
}
