Variety, Low-Pass, FIR Filter Impulse Response Explorer [Loxx]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © loxx

//@version=5
indicator("Variety, Low-Pass, FIR Filter Impulse Response Explorer [Loxx]", 
     shorttitle = "VLFDFIFE [Loxx]",
     overlay = false, 
     max_lines_count = 500,
     precision = 8)

greencolor = #2DD204
redcolor = #D2042D 

powercos = "N-Order Power of Cosine"
sincfir_hamm = "Hamming"
sincfir_hanning = "Hanning"
sincfir_black = "Blackman"
sincfir_blackh = "Blackman Harris"
sincfir_blacknutt = "Blackman Nutall"
sincfir_nutt = "Nutall"
sincfir_bartzep = "Bartlet Zero End Points"
sincfir_barthann = "Bartlet-Hann"
sincfir_hann = "Hann"
sincfir_sine = "Sine"
sincfir_lan = "Lanczos"
sincfir_flat = "Flat Top"
fir_rect = "Rectangular"
fir_lwma = "Linear"
fir_tma  = "Triangular"

fact(int n)=>
    float out = 1
    for i = 1 to n 
        out *= i
    out

nOrderPowerOfCosineDesign(int per, int order, float frequencyCutoff, int multiplier, bool sinc)=>
    float ppastri = 0 
    var float[] pastri = array.new<float>(order + 1, 0.)
    int pdepth = order - 1
    for k = 0 to order / 2 - 1
        ppastri := nz(fact(pdepth) / (fact(pdepth - k) * fact(k)), 1)

    for k = 0 to order 
        array.set(pastri, k, nz(fact(order) / (fact(order - k) * fact(k)), 1))

    array<float> outpastri = array.slice(pastri, 0, order / 2)
    array.reverse(outpastri)
    float[] coeffs = array.new<float>(per, 0)
    int N = per - 1
    float sum = 0

    for n = 0 to per - 1 
        float div = n - N / 2.0
        if div == 0
            array.set(coeffs, n, 2.0 * math.pi * frequencyCutoff)
        else        
            array.set(coeffs, n, math.sin(2.0 * math.pi * frequencyCutoff * div) / div)
        int sign = -1
        float coeff = ppastri
        for k = 0 to array.size(outpastri) - 1
            coeff += sign * array.get(outpastri, k) * math.cos((k + 1) * 2 * math.pi * n / N)
            sign *= -1
        coeff := coeff / (array.sum(outpastri) + ppastri)
        if sinc
            array.set(coeffs, n, array.get(coeffs, n) * coeff)   
            sum += array.get(coeffs, n)
        else 
            array.set(coeffs, n, coeff)   
    if sinc
        for k = 0 to per - 1
            array.set(coeffs, k, array.get(coeffs, k) / sum)
            array.set(coeffs, k, array.get(coeffs, k) * multiplier)
    coeffs
    
    
fircoeff(string type, int per, float frequencyCutoff, int multiplier, bool sinc)=>
    array<float> coeffs = array.new<float>(per, 0)
    float sum = 0
    int N = per - 1
    
    for n = 0 to per - 1
    
        float div = n - N / 2.0
        
        if div == 0
            array.set(coeffs, n, 2.0 * math.pi * frequencyCutoff)
        else        
            array.set(coeffs, n, math.sin(2.0 * math.pi * frequencyCutoff * div) / div)
            
        float temp = 0.
        switch type
            sincfir_hamm =>
                temp := 0.54 - 0.46 * math.cos(2 * math.pi * n / N)
                
            sincfir_hanning =>
                temp := 0.50 - 0.50 * math.cos(2 * math.pi * n / N)
                
            sincfir_black =>
                temp := 0.42 
                     - 0.50 * math.cos(2 * math.pi * n / N) 
                     + 0.08 * math.cos(4 * math.pi * n / N)
                     
            sincfir_blackh =>
                temp := 0.35875 
                     - 0.48829 * math.cos(2 * math.pi * n / N) 
                     + 0.14128 * math.cos(4 * math.pi * n / N) 
                     + 0.01168 * math.cos(6 * math.pi * n / N)
                     
            sincfir_blacknutt =>
                temp := 0.3635819
                     - 0.4891775 * math.cos(2 * math.pi * n / N)
                     + 0.1365995 * math.cos(4 * math.pi * n / N)
                     + 0.0106411 * math.cos(6 * math.pi * n / N)
                     
            sincfir_nutt =>
                temp := 0.355768 
                     - 0.487396 * math.cos(2 * math.pi * n / N)
                     + 0.144232 * math.cos(4 * math.pi * n / N)
                     + 0.012604 * math.cos(6 * math.pi * n / N)
                 
            sincfir_bartzep =>
                temp := 2.0 / N * (N / 2.0 - math.abs(n - N / 2))
                
            sincfir_barthann =>
                temp := 0.62 - 0.48 * math.abs(n / N - 0.5) * 0.38 * math.cos(2.0 * math.pi * n / N)
                
            sincfir_hann =>
                temp := 0.50 * (1.0 - math.cos(2.0 * math.pi * n / N))
                
            sincfir_sine =>
                temp := math.sin(math.pi * n / N)
                
            sincfir_lan =>
                float ttx = (n / N) - 1.0
                if ttx == 0
                    temp := 0
                else
                    temp := math.sin(ttx) / (ttx)
                    
            sincfir_flat =>
                temp := 1
                     - 1.93 * math.cos(2 * math.pi * n / N)
                     + 1.29 * math.cos(4 * math.pi * n / N)
                     + 0.388 * math.cos(6 * math.pi * n / N)
                     + 0.388 * math.cos(8 * math.pi * n / N)
        if sinc
            array.set(coeffs, n, array.get(coeffs, n) * temp)   
            sum += array.get(coeffs, n)
        else 
            array.set(coeffs, n, temp)   
    if sinc
        for k = 0 to per - 1
            array.set(coeffs, k, array.get(coeffs, k) / sum)
            array.set(coeffs, k, array.get(coeffs, k) * multiplier)
    coeffs
    
simplefircoeff(int per, string type)=>
    float[] coeffs = array.new<float>(per, 0)
    float coeff = 1
    for i = 0 to per - 1 
        switch type 
            fir_rect =>
                coeff := 1.0
            fir_lwma =>
                coeff := per - i
            fir_tma =>
                coeff := i + 1.0
                if (coeff > ((per + 1.0) / 2.0)) 
                    coeff := per - i
        array.set(coeffs,i, coeff)
    coeffs

//Normalize data 
_InSigNormalize(float[] aa)=>
    float sum_sqrt = 0.
    int element_count = array.size(aa)
    for i = 0 to element_count - 1 
        sum_sqrt += math.pow(array.get(aa, i), 2)

    sum_sqrt := math.sqrt(sum_sqrt)
    if (sum_sqrt != 0) 
        for i = 0 to element_count - 1 
            array.set(aa, i, array.get(aa, i) / sum_sqrt)
    aa
    
wper = input.int(300, "Number of Coefficients to Calculatt", maxval = 500, group = "Basic Settings")
type = input.string(sincfir_black, "FIR Digital Filter Type", 
     options = [powercos, fir_rect, fir_lwma, fir_tma, sincfir_hamm, 
         sincfir_hanning, sincfir_black, sincfir_blackh, sincfir_blacknutt, 
         sincfir_nutt, sincfir_bartzep, sincfir_barthann, 
         sincfir_hann, sincfir_sine ,sincfir_lan, sincfir_flat], 
     group = "Basic Settings")
    

mult = input.int(1, "Multiplier", group = "Sinc Settings")
fcut = input.float(0.01, "Frequency Cutoff", maxval = 0.5, minval = 0, step = 0.01, group = "Sinc Settings")
addsinc = input.bool(false, "Turn on Sinc?", group = "Sinc Settings", tooltip = "Applies to all except for Rectangular, Linear Weighted, and Triangular")

order = input.int(10, "Order", group = "Power-of-Cosine Settings")

var pvlines = array.new_line(0)

if barstate.isfirst
    for i = 0 to wper - 1
        array.push(pvlines, line.new(na, na, na, na))
        
if barstate.islast

    sincarry = array.new_string(15, "")
    array.push(sincarry, sincfir_hamm)
    array.push(sincarry, sincfir_hanning)
    array.push(sincarry, sincfir_black)
    array.push(sincarry, sincfir_blacknutt)
    array.push(sincarry, sincfir_bartzep)
    array.push(sincarry, sincfir_blackh)
    array.push(sincarry, sincfir_nutt)
    array.push(sincarry, sincfir_barthann)
    array.push(sincarry, sincfir_flat)
    array.push(sincarry, sincfir_lan)
    array.push(sincarry, sincfir_sine)
    array.push(sincarry, sincfir_hann)

    LastBar = int(wper / 2)

    regarray = array.new_string(10, "")
    array.push(regarray, fir_rect)
    array.push(regarray, fir_tma)
    array.push(regarray, fir_lwma)

    var testTable = table.new(position = position.top_center, columns = 1, rows = 1, bgcolor = color.yellow, border_width = 1)
    table.cell(table_id = testTable, column = 0, row = 0, text = type)

    var coeffs = 
         array.includes(sincarry, type) ? array.copy(fircoeff(type, wper, fcut, mult, addsinc)) : 
         array.includes(regarray, type) ? array.copy(simplefircoeff(wper, type)) : nOrderPowerOfCosineDesign(wper, order, fcut, mult, addsinc)

    var ttd = array.size(coeffs)
    _InSigNormalize(coeffs)

    for i = 0 to array.size(pvlines) - 1 
        if i > wper - 2
            break
        pvline = array.get(pvlines, i)
        line.set_xy1(pvline, bar_index + i - LastBar, array.get(coeffs, i + 1))
        line.set_xy2(pvline, bar_index + i - 1 - LastBar, array.get(coeffs, i))
        line.set_color(pvline, greencolor)
        line.set_style(pvline, line.style_solid)
        line.set_width(pvline, 1)

hline(0)
