filter1d.mjs

import get from 'lodash-es/get.js'
import size from 'lodash-es/size.js'
import each from 'lodash-es/each.js'
import range from 'lodash-es/range.js'
import ispnum from 'wsemi/src/ispnum.mjs'
import isp0num from 'wsemi/src/isp0num.mjs'
import cdbl from 'wsemi/src/cdbl.mjs'
import fft1d from './fft1d.mjs'
import ifft1d from './ifft1d.mjs'


/**
 * FFT1D Filter
 *
 * @param {Array} arr 輸入數據陣列
 * @param {Number} dt 輸入數據點時間間隔數字,單位s
 * @param {Number} hzStart 輸入過濾用帶通頻率下限數字,單位Hz
 * @param {Number} hzEnd 輸入過濾用帶通頻率上限數字,單位Hz
 * @param {Object} [opt={}] 選項物件
 * @param {Boolean} [opt.useOneTurn=true] true=輸入視為一個完整週期(頭尾重複),週期(n-1)*dt;false=標準DFT,週期n*dt
 * @param {String} [opt.type='dft'] 輸入計算方式字串,'dft'為使用mathjs對任意n點做真實n點DFT(2冪次走Cooley-Tukey、其餘走Chirp-Z),數據品質最佳但非2冪次時較慢;'pow2'為先補零至2冪次(最少4點)再使用ml-fft之radix-2 FFT,速度極快適合前端即時繪圖,但輸出點數與頻率解析度df係以補零後之2冪次點數計算,預設'dft'
 * @return {Array} 回傳帶通處理後數據陣列
 * @example
 *
 * let dt
 * let arr
 * let res
 *
 * //dt=0.0078125s, 3+6hz
 * dt = 0.0078125
 *
 * arr = [
 *     0,
 *     0.437015151709824,
 *     0.845854910274064,
 *     1.20056554679302,
 *     1.47944976553089,
 *     1.66674368151922,
 *     1.75379573376597,
 *     1.73964987434863,
 *     1.63098631369783,
 *     1.44142799002054,
 *     1.19027504868833,
 *     0.900778315875612,
 *     0.598101848038141,
 *     0.307150781019375,
 *     0.0504516520458098,
 *     -0.153732804251563,
 *     -0.292893218813452,
 *     -0.361241031239776,
 *     -0.360072875476548,
 *     -0.297503430771426,
 *     -0.187593110348962,
 *     -0.0489494660021425,
 *     0.0970731816865672,
 *     0.228416556922734,
 *     0.324423348821458,
 *     0.367818520155133,
 *     0.346391996239585,
 *     0.254233601317238,
 *     0.0924099202087415,
 *     -0.130978839760706,
 *     -0.401370102712605,
 *     -0.698891832710318,
 *     -1,
 *     -1.27946118721924,
 *     -1.51251056875181,
 *     -1.67699974648618,
 *     -1.75534914481383,
 *     -1.73613585202716,
 *     -1.61517856456688,
 *     -1.39602400854158,
 *     -1.08979021355164,
 *     -0.714376916729265,
 *     -0.293107462345689,
 *     0.147084814656977,
 *     0.577773754381215,
 *     0.971283137555866,
 *     1.30286634912854,
 *     1.55263964022464,
 *     1.70710678118655,
 *     1.76014786721285,
 *     1.7133908766509,
 *     1.57593734934667,
 *     1.36346871276832,
 *     1.09681259653473,
 *     0.80009440465607,
 *     0.498634516368545,
 *     0.216772751324739,
 *     -0.0241926543480825,
 *     -0.207774827040493,
 *     -0.323625771825179,
 *     -0.368309299491684,
 *     -0.345455359932455,
 *     -0.265285555765141,
 *     -0.1435542027991,
 *     -3.67544536472586E-16,
 *     0.1435542027991,
 *     0.26528555576514,
 *     0.345455359932455,
 *     0.368309299491684,
 *     0.323625771825179,
 *     0.207774827040493,
 *     0.0241926543480835,
 *     -0.216772751324738,
 *     -0.498634516368544,
 *     -0.800094404656069,
 *     -1.09681259653473,
 *     -1.36346871276832,
 *     -1.57593734934667,
 *     -1.7133908766509,
 *     -1.76014786721285,
 *     -1.70710678118655,
 *     -1.55263964022464,
 *     -1.30286634912855,
 *     -0.971283137555864,
 *     -0.577773754381218,
 *     -0.14708481465698,
 *     0.293107462345686,
 *     0.714376916729258,
 *     1.08979021355163,
 *     1.39602400854158,
 *     1.61517856456688,
 *     1.73613585202716,
 *     1.75534914481383,
 *     1.67699974648618,
 *     1.51251056875181,
 *     1.27946118721924,
 *     1,
 *     0.698891832710321,
 *     0.40137010271261,
 *     0.130978839760704,
 *     -0.0924099202087405,
 *     -0.254233601317238,
 *     -0.346391996239584,
 *     -0.367818520155133,
 *     -0.324423348821457,
 *     -0.228416556922734,
 *     -0.0970731816865679,
 *     0.0489494660021418,
 *     0.18759311034896,
 *     0.297503430771423,
 *     0.360072875476548,
 *     0.361241031239776,
 *     0.292893218813452,
 *     0.153732804251566,
 *     -0.0504516520458086,
 *     -0.307150781019376,
 *     -0.598101848038137,
 *     -0.900778315875611,
 *     -1.19027504868833,
 *     -1.44142799002054,
 *     -1.63098631369783,
 *     -1.73964987434863,
 *     -1.75379573376597,
 *     -1.66674368151922,
 *     -1.47944976553089,
 *     -1.20056554679302,
 *     -0.845854910274064,
 *     -0.43701515170983,
 * ]
 *
 * res = wf.filter1d(arr, dt, 0, 2)
 * console.log(res)
 * // => [
 * //   -1.967983691641977e-17,
 * //   -2.307740440357446e-17,
 * //   -2.6914726362424797e-17,
 * //   -3.118255834826216e-17,
 * //   -3.587061878001477e-17,
 * //   -4.0967613709476845e-17,
 * //   -4.646126402941945e-17,
 * //   -5.2338335055034716e-17,
 * //   -5.858466840745145e-17,
 * //   -6.518521612250948e-17,
 * //   -7.212407690262343e-17,
 * //   -7.938453442440174e-17,
 * //   -8.694909760973329e-17,
 * //   -9.479954276332726e-17,
 * //   -1.029169574751909e-16,
 * //   -1.112817861822817e-16,
 * //   ... (112 more items)
 * // ]
 *
 * res = wf.filter1d(arr, dt, 0, 4)
 * console.log(res)
 * // => [
 * //   -4.560690164444523e-16,
 * //   0.14673047445536136,
 * //   0.290284677254462,
 * //   0.4275550934302818,
 * //   0.5555702330196021,
 * //   0.6715589548470182,
 * //   0.7730104533627369,
 * //   0.8577286100002721,
 * //   0.9238795325112868,
 * //   0.970031253194544,
 * //   0.9951847266721972,
 * //   0.9987954562051725,
 * //   0.9807852804032308,
 * //   0.9415440651830213,
 * //   0.8819212643483555,
 * //   0.8032075314806454,
 * //   ... (112 more items)
 * // ]
 *
 * res = wf.filter1d(arr, dt, 4, 8)
 * console.log(res)
 * // => [
 * //   -4.482834651769285e-16,
 * //   0.2902846772544623,
 * //   0.5555702330196025,
 * //   0.7730104533627374,
 * //   0.9238795325112875,
 * //   0.995184726672198,
 * //   0.9807852804032318,
 * //   0.8819212643483564,
 * //   0.7071067811865488,
 * //   0.47139673682599875,
 * //   0.19509032201612916,
 * //   -0.0980171403295599,
 * //   -0.3826834323650895,
 * //   -0.6343932841636456,
 * //   -0.8314696123025458,
 * //   -0.9569403357322097,
 * //   ... (112 more items)
 * // ]
 *
 */
function filter1d(arr, dt, hzStart, hzEnd, opt = {}) {

    //check dt
    if (!ispnum(dt)) {
        throw new Error(`dt[${dt}] is not a positive number`)
    }
    dt = cdbl(dt)

    //check hzStart
    if (!isp0num(hzStart)) {
        throw new Error(`hzStart[${hzStart}] is not a positive number`)
    }
    hzStart = cdbl(hzStart)

    //check hzEnd
    if (!isp0num(hzEnd)) {
        throw new Error(`hzEnd[${hzEnd}] is not a positive number`)
    }
    hzEnd = cdbl(hzEnd)

    //useOneTurn: true(預設)=輸入視為一個完整週期(頭尾重複), 週期(n-1)*dt; false=標準DFT, 週期n*dt
    let useOneTurn = get(opt, 'useOneTurn', true)

    //type: 'dft'(預設)=mathjs真實n點DFT; 'pow2'=補零至2冪次後用ml-fft, 速度極快
    let type = get(opt, 'type', 'dft')

    //fft1d
    let rm = fft1d(arr, { type })
    // console.log('rm', rm)
    // console.log('n1', size(arr))

    //n
    let n = size(rm)
    // console.log('n', n)

    //T
    let T = useOneTurn ? dt * (n - 1) : dt * n
    // console.log('T', T)

    //df
    let df = 1 / T
    // console.log('df', df)

    //F
    // let F = df * (n - 1) //舊版hzs建構用, 新遮罩改用min(k,n-k)不需要
    // console.log('F', F)

    //hzs
    //舊版: 對稱建構hzs; 對奇數n時 take(hzs, n/2) 取整少一個 -> mirror後長度n-1, 末尾bin永遠漏遮罩, 且負頻半段與正頻不共軛對齊 -> 輸出非實數(被ifft取實部截斷), 保留供比對
    // let hzs = range(df, F + df, df)
    // let hzs = range(0, F, df)
    // let hzsHalf = take(hzs, n / 2)
    // hzs = [
    //     ...hzsHalf,
    //     ...reverse(hzsHalf),
    // ]
    //新版: 每個bin k的頻率大小=min(k,n-k)*df(bin k 與其共軛 bin n-k 同頻率→一起遮罩, 輸出保持實數), 涵蓋全部n個bin, 奇偶n皆正確
    let hzs = range(0, n).map((k) => Math.min(k, n - k) * df)
    // console.log('hzs', JSON.stringify(hzs), size(hzs))

    //帶通, 依照指定起訖頻率清除rm
    each(hzs, (v, k) => {
        let b = hzStart <= v && v <= hzEnd //允許通過
        if (!b) {
            rm[k][0] = 0
            rm[k][1] = 0
        }
    })
    // console.log('rm(clean)', JSON.stringify(rm))

    //ifft1d, rm之點數已為2冪次, 'pow2'不會再補零
    let res = ifft1d(rm, { type })
    // console.log('res', res)

    return res
}


export default filter1d