#!/bin/bash
# -*- mode: julia -*-
#=
JULIA="${JULIA:-julia --color=yes --startup-file=no}"
export JULIA_PROJECT="$(dirname ${BASH_SOURCE[0]})"

set -e
${JULIA} -e 'using Pkg; Pkg.instantiate()'

export JULIA_LOAD_PATH="@"
exec ${JULIA} "${BASH_SOURCE[0]}" "$@"
=#

# Copyright (c) 2021, Jean Pierre Cimalando
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# SPDX-License-Identifier: BSD-2-Clause

using DSP
using ArgParse

# Number of filters to compute
NF = 512
# The cutoffs, expressed as a proportion of the sample rate
Fc0 = 0.25 #0.25
Fc1 = 0.45 #0.5
# The filter order (number of coefficients - 1)
N = 8 #11
# The pass band ripple (dB)
Rp = 0.1
# The stop band attenuation (dB)
Rs = 80.0
# Use second order sections
UseSOS = false
# Output type: C++ or Faust
Lang = "C++"
# Output name
Name = "Elliptic"

####
function parse_commandline()
    s = ArgParseSettings()

    @add_arg_table s begin
        "--nf"
            help = "number of filters to compute"
            arg_type = Int
            default = NF
        "--f0"
            help = "cutoff of the first filter"
            arg_type = Float64
            default = Fc0
        "--f1"
            help = "cutoff of the last filter"
            arg_type = Float64
            default = Fc1
        "--order"
            help = "order of the filters"
            arg_type = Int
            default = N
        "--rp"
            help = "pass band ripple, in dB"
            arg_type = Float64
            default = Rp
        "--rs"
            help = "stop band attenuation, in dB"
            arg_type = Float64
            default = Rs
        "--sos"
            help = "generate second-order sections"
            action = :store_true
        "--lang"
            help = "generated language, C++ or Faust"
            arg_type = String
            default = Lang
        "--name"
            help = "output name"
            arg_type = String
            default = Name
    end

    d = parse_args(s)

    global NF; NF = d["nf"]
    global Fc0; Fc0 = d["f0"]
    global Fc1; Fc1 = d["f1"]
    global N; N = d["order"]
    global Rp; Rp = d["rp"]
    global Rs; Rs = d["rs"]
    global UseSOS; UseSOS = d["sos"]
    global Lang; Lang = d["lang"]
    global Name; Name = d["name"]
end

function generate()
    println("// File generated by ", basename(PROGRAM_FILE))
    println("// with arguments ", ARGS)

    if Lang != "Faust"
       println("#pragma once")
       println()
    end

    if Lang == "Faust"
        num2str = x -> string(x)
    else
        num2str = x -> string(x) * "f"
    end

    if Lang != "Faust"
    println("namespace ", Name, " {")
    end

    println()

    Fcs = LinRange(Fc0, Fc1, NF)

    if Lang == "Faust"
        println("F0 = ", Fcs[1], ";")
        println("F1 = ", Fcs[length(Fcs)], ";")
        println("NF = ", NF, ";")
        if (!UseSOS)
            println("NB = ", N+1, ";")
            println("NA = ", N, ";")
        else
            println("NS = ", ceil(Int, N/2.0), ";")
        end
    else
        println("static constexpr float F0 = ", Fcs[1], ";")
        println("static constexpr float F1 = ", Fcs[length(Fcs)], ";")
        println("static constexpr unsigned int NF = ", NF, ";")
        if (!UseSOS)
            println("static constexpr unsigned int NB = ", N+1, ";")
            println("static constexpr unsigned int NA = ", N, ";")
        else
            println("static constexpr unsigned int NS = ", ceil(Int, N/2.0), ";")
        end
    end

    println()

    if Lang == "Faust"
        println("BA = waveform{")
    else
        println("static constexpr float BA[] = {")
    end
    for j in 1:length(Fcs)
        f = digitalfilter(Lowpass(2*Fcs[j]), Elliptic(N, Rp, Rs))
        isLast = (j == length(Fcs))

        println("\t// Elliptic N=", N, " F/Fs=", Fcs[j], " Rp=", Rp, "dB Rs=", Rs, "dB")
        if (!UseSOS)
            tf = convert(PolynomialRatio, f)
            b = coefb(tf)
            a = coefa(tf)

            # Normalize, and remove coefficient a0
            a0 = a[1]
            bN = b./a0
            aN = a[2:end]./a0

            println("\t", join(map(num2str, bN), ", "), ",")
            println("\t", join(map(num2str, aN), ", "), isLast ? "" : ",")
        else
            sos = convert(SecondOrderSections, f)
            for bq in sos.biquads
                coefs = [bq.b0 bq.b1 bq.b2 bq.a1 bq.a2]
                println("\t", join(map(num2str, coefs), ", "), ",")
            end
            println("\t", num2str(sos.g), isLast ? "" : ",")
        end
    end
    if Lang == "Faust"
        println("} : (!, _);")
    else
        println("};")
    end

    println()

    if Lang != "Faust"
        println("} // namespace ", Name)
    end
end

###
parse_commandline()
generate()
