!******************************************************************************!
! Tests the TUNER command. Since NeXtMidas 3.3.1 the following tests also test
! the functionality of TUNER, though they look at slightly different things:
!   %TEST_FILTER   - Tests formerly in the 'test_filter_macro' procedure
!   %TEST_SBTUNER  - Tests that verify SBTUNER's command-line arguments (and
!                    for compatibility with X-Midas).
!
! @since    NeXtMidas 2.1.1
! @author:  Jason Broussard / J. Czechowski IV
!******************************************************************************
startmacro/SEDITLEGACY=false
  switch INTERACTIVE interAct GET DEF=TRUE  SDEF=TRUE  ! for normal interactive test(s)
  switch BASELINE    baseline GET DEF=FALSE SDEF=TRUE  ! for defects or more obscure test case(s)

  set additionalRunTime 0
  env get usenative tmpUseNative
  if SEDIT(tmpUseNative,SELECT,"Math") lt 0 then
    calc additionalRunTime additionalRunTime 1.0 +
  endif
  if SEDIT(tmpUseNative,SELECT,"Cast") lt 0 then
    calc additionalRunTime additionalRunTime 1.0 +
  endif
  if SEDIT(tmpUseNative,SELECT,"IO") lt 0 then
    calc additionalRunTime additionalRunTime 0.5 +
  endif
  if additionalRunTime gt 0 then
    say "^reg.main.name: This test may take ^{additionalRunTime}+ minutes to run b/c useNative=^tmpUseNative ..."
  endif
  timer set

  ! Currently SD/CD output is not supported, so test cases need to handle this
  ! case special.
  res inputFormats  "SD,CD,SF,CF,SL,CL,SI,CI,SB,CB"
  res outputFormats       "SF,CF,SL,CL,SI,CI,SB,CB"

  res  L:decim    3
  res  L:npts     10e3 ! Make sure it's longer than the input buffers
  res  L:fft_size 8192
  calc L:num_taps fft_size 8 /
  set  fnprefix "tuner_^{env.pid}"

  call test_decimation
  call test_outformat
  call test_fshift_dec
  call test_filter_nodec
  call test_filter_dec
  call test_image
  call test_fft_sf
  call test_partial_all
  call test_partial_none
  call test_partial_start
  call test_keywords
  call test_frequency_range_validation
  timer elapse ! for non-interactive portion of test case

  if interAct then
    set instructions "Should see the following warnings about adjusting the &
                      filter bandwidth to:\n  &
                      TUNER_1:   0.3 to  0.5 Hz\n  &
                      TUNER_2:   0.0 to  0.2 Hz\n  &
                      TUNER_3:   0.3 to  0.5 Hz\n  &
                      TUNER_4:  -0.5 to -0.3 Hz\n  &
                      \n&
                      When each layer is turned on, the Freq Domain plot should &
                      show the pass-band (non-zero values) between the frequency &
                      ranges listed above (e.g. TUNER_1 should be non-zero from &
                      300 mHz to 500 mHz, ignore outliers at edge of plot)."

    waveform ram:sfsine sf 10k sin 1 .2 0 0 1

    pipe on
      panel/msgid=MAIN/setup/controls=gc
      shellgui/id=idshellgui/attach

      gcontrol label macro "Test MACRO"
      gcontrol menu  main "Exit with Status =" "PASS,FAIL" TABLE={ItemColors={PASS="GREEN",FAIL="RED"}} /tleft

      gcontrol text  idtext "Instructions" instructions 24 14 /title
      gcontrol menu  layers "Layers"       "TUNER_1,TUNER_2,TUNER_3,TUNER_4" "TUNER_1" /toggle /chkbox
      set gc.layers.ItemColors {TUNER_1=GREEN,TUNER_2=CYAN,TUNER_3=RED,TUNER_4=YELLOW}

      ! ==== Tests for error handling ==========================================
      ! This was updated in 3.3.1 to include /TAPS=1000 to demonstrate a filter
      ! size that is not a power of 2.
      tuner/ID=TUNER_1 input=ram:sfsine output=ram:junk1 outformat=sf inputif=+.45 outputif=0 bandwidth=.2 decimation=1 filtout=ram:filt1 /TAPS=999
      tuner/ID=TUNER_2 input=ram:sfsine output=ram:junk2 outformat=sf inputif=+.05 outputif=0 bandwidth=.2 decimation=1 filtout=ram:filt2 /TAPS=999
      tuner/ID=TUNER_3 input=ram:sfsine output=ram:junk3 outformat=cf inputif=+.45 outputif=0 bandwidth=.2 decimation=1 filtout=ram:filt3 /TAPS=999
      tuner/ID=TUNER_4 input=ram:sfsine output=ram:junk4 outformat=cf inputif=-.45 outputif=0 bandwidth=.2 decimation=1 filtout=ram:filt4 /TAPS=999

      fft/psd ram:filt1 ram:psd1
      fft/psd ram:filt2 ram:psd2
      fft/psd ram:filt3 ram:psd3
      fft/psd ram:filt4 ram:psd4

      plot/id="TIME_PLOT" ram:filt1{LAYER={ENABLE=+Global,COLOR=GREEN}}|&
                          ram:filt2{LAYER={ENABLE=-Global,COLOR=CYAN}}|&
                          ram:filt3{LAYER={ENABLE=-Global,COLOR=RED}}|&
                          ram:filt4{LAYER={ENABLE=-Global,COLOR=YELLOW}}
      plot/id="FREQ_PLOT" ram:psd1(0:1){LAYER={ENABLE=+Global,COLOR=GREEN}}|&
                          ram:psd2(0:1){LAYER={ENABLE=-Global,COLOR=CYAN}}|&
                          ram:psd3(0:1){LAYER={ENABLE=-Global,COLOR=RED}}|&
                          ram:psd4(0:1){LAYER={ENABLE=-Global,COLOR=YELLOW}} /XMULT=-3
    pipe off
  endif

  %test_tuner_wind_outputif/interactive=^{interAct} ! NXM-4055
endmacro

!******************************************************************************!
! Main process message for this macro. Should be right after the ENDMACRO.
!******************************************************************************!
procedure processMessage m:msg
  if msg.name eqs "MAIN" then
    assert/text="^reg.main.name: User indicated tests" msg.data eqss "PASS"
    pipe stop
  elseif msg.name eqs "LAYERS" then
    invoke filtName msg.data.replace("TUNER_","FILT")
    invoke psdName  msg.data.replace("TUNER_","PSD")

    set reg.TIME_PLOT.layers.^{filtName}.enable ~Global
    set reg.FREQ_PLOT.layers.^{psdName}.enable  ~Global
  else
    say "Unhandled message name: ^msg.name from fid: ^msg.fid data: ^msg.data"
  endif
return

!******************************************************************************!
! If an unexpected exception is caught, mark test as failed, unless it is expected
!******************************************************************************!
procedure processException m:emsg
  if expectedException REXISTS then
    assert/text="^reg.main.name: Expected ERROR starts with: ^{expectedException}" expectedException EQSS emsg.data.message
    remove expectedException
  else
    assert/text="^reg.main.name: Caught unexpected exception ^{emsg.data}" "FALSE" isTrue
    if "Trace" SUBS ENV.DEBUG then invoke ,, emsg.data.printStackTrace()
  endif
return

!---------------------------------------------------------------
! Tests for decimation
procedure test_decimation
  ! ==== BASIC TESTS ===========================================================
  ! These basic tests simply create a file with multiples of a value and then
  ! decimate (e.g. "1,1,1,1" => dec by 4 => "1"). They test decimation only (no
  ! tuning) but check all of the supported format combinations. Note that we
  ! intentionally include all formats and include larger decimation values since
  ! there had been (unconfirmed) reports of issues with decimation values >30.
  foreach inForm inList inputFormats
    ramp/min=1e-20/max=99 out=ram:rampfile form="^inForm" elem=npts datastart=1e-20 datainc=1 start=0 delta=1

    if (inForm EQS "SD") then
      convert ram:rampfile FLOAT ram:exp_out
      res outForm "SF"
    elseif (inForm EQS "CD") then
      convert ram:rampfile FLOAT ram:exp_out
      res outForm "CF"
    else
      noop ram:rampfile ram:exp_out
      res outForm inForm
    endif

    headermod ram:rampfile TYPE=3000 SR=(SR01/^{inForm})

    for dec inList "  1,  2,  4,  8, 16, 32, 64,128,256" ! <-- only use powers of 2
      res L:ndec ^{dec}
      if (ndec == 1) then
        ! dec=1 -> 1 copy of file
        noop ram:rampfile ram:group
      else
        ! dec=N = (N/2)+(N/2)
        noop ram:group ram:temp
        group/warn=no ram:group ram:temp ram:temp
      endif

      noop ram:group ram:data_in
      headermod ram:data_in TYPE=1000 FORMAT=^inForm
      
      tuner ram:data_in ram:data_out decimation=^ndec outformat="^outForm"
      headermod ram:data_out xs=0 xd=1

      assert/text="Decimation by ^{dec} for ^{inForm}->^{outForm}" ram:data_out FEQ ram:exp_out
    endfor
  endfor
  erase ram:rampfile ram:exp_out ram:group ram:temp ram:data_in ram:data_out

  ! ==== ADVANCED TESTS ========================================================
  ! These tests put an actual signal through tuning, filtering, and decimation
  ! and make sure it looks right when it comes out.
  foreach outputIF inList "-100e3,0,+100e3"
    foreach decimation inList "1,2,5,10"
      res  D:inputCF      0.0  ! Complex, centered at 0.0
      res  D:inputIF    100e3  ! User-requested input IF
      res  D:inputIFt   105e3  ! Actual transmitter IF (close, but not identical to inputIF)
      res  D:inputBW    500e3
      res  D:outputBW    20e3  ! Bandwidth of the filter (not output file)
      calc D:outputBWf  inputBW decimation /
      calc D:outputIFt  inputIFt inputIF - outputIF +

      if (outputIF < -outputBWf/2) OR (outputIF > +outputBWf/2) then
        ! Outside of tuned BW so TUNER will adjust IF and BW, but this is already
        ! tested elsewhere.
        continue
      endif

      calc L:inputIF_kHz  inputIF  1e3 / ROUND
      calc L:outputIF_kHz outputIF 1e3 / ROUND
      calc L:outputBW_kHz outputBW 1e3 / ROUND
      res msg "Decimation (DEC=^{decimation} IN=^{inputIF_kHz} kHz OUT=^{outputIF_kHz} kHz BW=^{outputBW_kHz} kHz)"

      ! Signal should include some noise so it is clear that we have filtered it out
      waveform ^{fnprefix}_signal CF 1M SINE  1.0 inputIFt ,, 0 1/inputBW
      waveform ^{fnprefix}_noise  CF 1M WHITE 9.0 inputIFt ,, 0 1/inputBW
      fcalc ^{fnprefix}_data_in ^{fnprefix}_signal ^{fnprefix}_noise +
      tuner ^{fnprefix}_data_in ^{fnprefix}_data_out inputIF outputBW ,, outputIF decimation

      fft/psd ^{fnprefix}_data_in  ^{fnprefix}_psd_in  1M NAVG=100
      fft/psd ^{fnprefix}_data_out ^{fnprefix}_psd_out 1M NAVG=100

      ! At this point the output should have the proper outputBW (centered at 0.0)
      ! and should show the signal at (inputIFt-inputIF)+outputIF. We use a larger
      ! tolerance in a number of our tests simply because the bin-spacing in the
      ! PSD introduces errors.
      statistics ^{fnprefix}_psd_out AMAX=actOutputIFt
      calc actOutputBWf 1 FILE(^{fnprefix}_data_out).xdelta /

      assert/text="^{msg} Signal IF" actOutputIFt EQ/T outputIFt  /TOL=0.05
      assert/text="^{msg} File   BW" actOutputBWf EQ/T outputBWf  /TOL=1e-6

      ! To determine if the filter BW is ok, we look at the area to the left and right
      ! of the filter and make sure the mean value seen in those regions is well
      ! below that of the region within the pass band
      calc minFreqPlus  outputIF outputBW 2 / - 0.5e3 -   ! 0.5 kHz to account for roll-off
      calc maxFreqPlus  outputIF outputBW 2 / + 0.5e3 +   ! 0.5 kHz to account for roll-off
      calc minFreqMinus outputIF outputBW 2 / - 0.5e3 -   ! 0.5 kHz to account for roll-off
      calc maxFreqMinus outputIF outputBW 2 / + 0.5e3 +   ! 0.5 kHz to account for roll-off

      noop ^{fnprefix}_psd_out(0:1) ram:psd_out_frame1
      headermod ram:psd_out_frame1 FS=0 XSTART=FILE(^{fnprefix}_psd_out).XSTART XDELTA=FILE(^{fnprefix}_psd_out).XDELTA XUNITS=FILE(^{fnprefix}_psd_out).XUNITS

      statistics ram:psd_out_frame1(:~^{minFreqPlus})                   MEAN=leftMean
      statistics ram:psd_out_frame1(~^{minFreqMinus}:~^{maxFreqMinus})  MEAN=centerMean
      statistics ram:psd_out_frame1(~^{maxFreqPlus}:)                   MEAN=rightMean
      assert/text="^{msg} Tuned  BW" (leftMean LT centerMean/1000) AND (rightMean LT centerMean/1000)

      if (/PLOT) then
        ! For plotting purposes, a 1K FFT size works well, but we don't do this with
        ! the version we run through statistics since the bins are small enough that
        ! it throws off out frequency measurements by a few Hz.
        fft/psd ^{fnprefix}_data_in  ^{fnprefix}_psd_in2  1K NAVG=10
        fft/psd ^{fnprefix}_data_out ^{fnprefix}_psd_out2 1K NAVG=10
        plot ^{fnprefix}_psd_in2|^{fnprefix}_psd_out2 type=line cm=10log
        erase ^{fnprefix}_psd_in2 ^{fnprefix}_psd_out2
      endif
    endfor
    erase ^{fnprefix}_signal ^{fnprefix}_noise ^{fnprefix}_data_in ^{fnprefix}_data_out ^{fnprefix}_psd_in ^{fnprefix}_psd_out ram:psd_out_frame1
  endfor
return

!---------------------------------------------------------------
! Tests default output format and use of OUTFORM= this specifically includes tests
! for the output values (not just the header format code) as required for Bug 2698
procedure test_outformat
  ! Create all of the input files that will be used below.
  foreach inForm inList inputFormats
    waveform ram:sinewave_^{inForm} "^inForm" 10k sin 1 .2 0 0 1
  endfor

  ! Use SF->SF as the "gold standard" for expected REAL output
  tuner input=ram:sinewave_SF output=ram:output inputif=0.2 outputif=0.1 bandwidth=0.1
  fft ram:output ram:fft 32k
  statistics ram:fft OMAX=maxOffReal

  ! Use CF->CF as the "gold standard" for expected COMPLEX output
  tuner input=ram:sinewave_CF output=ram:output inputif=0.2 outputif=0.1 bandwidth=0.1
  fft ram:output ram:fft 32k
  statistics ram:fft OMAX=maxOffComplex

  foreach inForm inList inputFormats
    ! By default, input format follows output format EXCEPT that SD/CD become SF/CF
    res outForm inForm
    if (inForm EQS "SD") then res outForm "SF"
    if (inForm EQS "CD") then res outForm "CF"

    tuner input=ram:sinewave_^{inForm} output=ram:output inputif=0.2 outputif=0.1 bandwidth=0.1

    assert/text="Default output format for ^{inForm} is ^{outForm}" "^outForm" eqs FILE(ram:output).format

    ! Test output format specified by OUTFORMAT=
    foreach outForm inList outputFormats
      tuner input=ram:sinewave_^{inForm} output=ram:output inputif=0.2 outputif=0.1 bandwidth=0.1 outformat="^outForm"
      assert/text="Output format for OUTFORMAT=^{outForm} is ^{outForm}" "^outForm" eqs FILE(ram:output).format

      fft ram:output ram:fft 32k
      statistics ram:fft OMAX=maxOffset

      if ("^outForm" EQSS "S") then
        assert/text="Output values for OUTFORMAT=^{outForm} is correct" maxOffset EQ maxOffReal
      else
        assert/text="Output values for OUTFORMAT=^{outForm} is correct" maxOffset EQ maxOffComplex
      endif
    endfor
  endfor

  foreach inForm inList inputFormats
    erase ram:sinewave_^{inForm}
  endfor
  erase ram:output ram:fft
return

!---------------------------------------------------------------
! Tests frequency shifting, followed by decimation, but not the filter
! Success is determined by measuring the signal peak relative to a
! section away from the peak
procedure test_fshift_dec
  ! First, shift a real sine wave, real output
  waveform ram:sfsine sf 100k sin 1 .2 0 0 1
  tuner/fft=fft_size/ntap=num_taps input=ram:sfsine output=ram:nmsfsfshift_out outformat=sf &
                                   inputif=.2 outputif=.1 Bandwidth=.15 decimation=decim
  ! FFT can't overlap successfully in 1.5.6  DR #5324
  fft/psd ram:nmsfsfshift_out ram:nmsfsfshiftspec 2k hanni 0 -1
  headermod ram:nmsfsfshiftspec type=1000
  call band_atten ram:nmsfsfshiftspec .099 .101 0 .08 dbout_sfsf
  assert/text="FSHIFT/DEC; SF to SF shift" dbout_sfsf nlt 70

  ! Now, real in, complex out
  tuner/fft=fft_size/ntap=num_taps input=ram:sfsine output=ram:nmsfcfshift_out outformat=cf &
                                   inputif=.2 outputif=.1 Bandwidth=.15 decimation=decim
  fft/psd ram:nmsfcfshift_out ram:nmsfcfshiftspec 2k hanni 0 -1
  headermod ram:nmsfcfshiftspec type=1000
  call band_atten ram:nmsfcfshiftspec .099 .101 0 .08 dbout_sfcf
  assert/text="FSHIFT/DEC; SF to CF shift" dbout_sfcf nlt 70

  ! Next a complex sine wave, real output
  waveform ram:cfsine cf 100k sin 1 .2 0 0 1
  tuner/fft=fft_size/ntap=num_taps input=ram:cfsine output=ram:nmcfsfshift_out outformat=sf &
                                   inputif=.2 outputif=.1 Bandwidth=.15 decimation=decim
  fft/psd ram:nmcfsfshift_out ram:nmcfsfshiftspec 2k hanni 0 -1
  headermod ram:nmcfsfshiftspec type=1000
  call band_atten ram:nmcfsfshiftspec .099 .101 0 .08 dbout_cfsf
  assert/text="FSHIFT/DEC; CF to SF shift" dbout_cfsf nlt 70

  ! Now complex in and out
  tuner/fft=fft_size/ntap=num_taps input=ram:cfsine output=ram:nmcfcfshift_out outformat=cf &
                                   inputif=.2 outputif=.1 Bandwidth=.15 decimation=decim
  fft/psd ram:nmcfcfshift_out ram:nmcfcfshiftspec 2k hanni 0 -1
  headermod ram:nmcfcfshiftspec type=1000
  call band_atten ram:nmcfcfshiftspec .099 .101 0 .08 dbout_cfcf
  assert/text="CF to CF shift" dbout_cfcf nlt 70

  if /plot gt 0 then
    plot ram:nmsfsfshiftspec|ram:nmsfcfshiftspec|ram:nmcfsfshiftspec|ram:nmcfcfshiftspec cm=10log
  endif

  ! Make sure that an output center frequency of -1 translates to
  !  the correct value (0 for CF, Fs/4 for SF)
  tuner/fft=fft_size/ntap=num_taps input=ram:sfsine output=ram:nmsfsfshift_out outformat=sf &
                                   inputif=.2 outputif=-1 Bandwidth=.15 decimation=1
  fft/psd ram:nmsfsfshift_out ram:nmsfsfshiftspec 2k hanni 0 -1
  headermod ram:nmsfsfshiftspec type=1000
  call band_atten ram:nmsfsfshiftspec .2495 .2505 0 .2 dbout_sfsf
  sedit ^dbout_sfsf x "NFORM" "###.##"
  assert/text="FSHIFT/DEC; SF-SF shift w/ def outputIF, is ^x > 100?" dbout_sfsf nlt 100

  ! Now, real in, complex out
  tuner/fft=fft_size/ntap=num_taps input=ram:sfsine output=ram:nmsfcfshift_out outformat=cf &
                                   inputif=.2 outputif=-1 Bandwidth=.15 decimation=1
  fft/psd ram:nmsfcfshift_out ram:nmsfcfshiftspec 2k hanni 0 -1
  headermod ram:nmsfcfshiftspec type=1000
  call band_atten ram:nmsfcfshiftspec -.0005 .0005 .1 .4 dbout_sfcf
  sedit ^dbout_sfcf x "NFORM" "###.##"
  assert/text="FSHIFT/DEC; SF-CF shift w/ def outputIF, is ^x > 100?" dbout_sfcf nlt 100

  erase ram:sfsine ram:nmsfsfshift_out ram:nmsfsfshiftspec ram:nmsfcfshift_out ram:nmsfcfshiftspec
  erase ram:cfsine ram:nmcfsfshift_out ram:nmcfsfshiftspec ram:nmcfcfshift_out ram:nmcfcfshiftspec
return

!---------------------------------------------------------------
! Tests the filter by filtering white noise and making sure that the
! passband/stopband ratio is OK.  No tuning or decimation are done here.
procedure test_filter_nodec
  ! First with SF format noise
  waveform ram:sfnoise sf 100k white 1 0 0 0 1
  tuner/fft=fft_size/ntap=num_taps input=ram:sfnoise output=ram:nmsfsfnoiseout outformat=sf &
                                   inputif=.2 Bandwidth=.2
  ! FFT can't overlap successfully in 1.5.6  DR #5324
  fft/psd ram:nmsfsfnoiseout ram:nmsfsffiltspec 2k hanni 0 -1
  headermod ram:nmsfsffiltspec type=1000
  call band_atten ram:nmsfsffiltspec .1 .3 .35 .499 dbout_sfsf
  assert/text="FILTER/NODEC; SF to SF shift" dbout_sfsf nlt 100

  ! Take the CF output
  tuner/fft=fft_size/ntap=num_taps input=ram:sfnoise output=ram:nmsfcfnoiseout outformat=cf &
                                   inputif=.2 Bandwidth=.2
  fft/psd ram:nmsfcfnoiseout ram:nmsfcffiltspec 2k hanni 0 -1
  headermod ram:nmsfcffiltspec type=1000
  call band_atten ram:nmsfcffiltspec .1 .3 .35 .499 dbout_sfcf
  assert/text="FILTER/NODEC; SF to CF shift" dbout_sfcf nlt 100

  ! Next with CF format noise
  waveform ram:cfnoise cf 100k white 1 0 0 0 1
  tuner/fft=fft_size/ntap=num_taps input=ram:cfnoise output=ram:nmcfsfnoiseout outformat=sf &
                                   inputif=0.2 Bandwidth=.2
  fft/psd ram:nmcfsfnoiseout ram:nmcfsffiltspec 2k hanni 0 -1
  headermod ram:nmcfsffiltspec type=1000
  call band_atten ram:nmcfsffiltspec .1 .3 .35 .499 dbout_cfsf
  assert/text="FILTER/NODEC; CF to SF shift" dbout_cfsf nlt 100
  
  tuner/fft=fft_size/ntap=num_taps input=ram:cfnoise output=ram:nmcfcfnoiseout outformat=cf &
                                   inputif=0.2 Bandwidth=.2
  fft/psd ram:nmcfcfnoiseout ram:nmcfcffiltspec 2k hanni 0 -1
  headermod ram:nmcfcffiltspec type=1000
  call band_atten ram:nmsfsffiltspec .1 .3 .35 .499 dbout_cfcf
  assert/text="FILTER/NODEC; CF to CF shift" dbout_cfcf nlt 100

  if /plot gt 0 then
    plot ram:nmsfsffiltspec|ram:nmsfcffiltspec|ram:nmcfsffiltspec|ram:nmcfcffiltspec cm=10log
  endif

  erase ram:sfnoise ram:nmsfsfnoiseout ram:nmsfsffiltspec ram:nmsfcfnoiseout ram:nmsfcffiltspec
  erase ram:cfnoise ram:nmcfsfnoiseout ram:nmcfsffiltspec ram:nmcfcfnoiseout ram:nmcfcffiltspec
return

!---------------------------------------------------------------
! Tests the filter by filtering white noise and making sure that the
! passband/stopband ratio is OK. Decimation and tuning are done here,
! which means that the acceptable pass/stop attenuation is less.
procedure test_filter_dec
  ! First with SF format noise
  waveform ram:sfnoise sf 100k white 1 0 0 0 1
  tuner/fft=fft_size/ntap=num_taps input=ram:sfnoise output=ram:nmsfsfnoiseout outformat=sf &
                                   inputif=.2 outputif=.05 Bandwidth=.05 decimation=decim

  ! FFT can't overlap successfully in 1.5.6  DR #5324
  fft/psd ram:nmsfsfnoiseout ram:nmsfsffiltspec 2k hanni 0 -1
  headermod ram:nmsfsffiltspec type=1000
  call band_atten ram:nmsfsffiltspec .025 .075 .1 .15  dbout_sfsf
  assert/text="FILTER/DEC; SF to SF shift" dbout_sfsf nlt 80

  ! Take the CF output
  tuner/fft=fft_size/ntap=num_taps input=ram:sfnoise output=ram:nmsfcfnoiseout outformat=cf &
                                   inputif=.2 outputif=.05 Bandwidth=.05 decimation=decim
  fft/psd ram:nmsfcfnoiseout ram:nmsfcffiltspec 2k hanni 0 -1
  headermod ram:nmsfcffiltspec type=1000
  call band_atten ram:nmsfcffiltspec .025 .075 .1 .15  dbout_sfcf
  assert/text="FILTER/DEC; SF to CF shift" dbout_sfcf nlt 80

  ! Next with CF format noise
  waveform ram:cfnoise cf 100k white 1 0 0 0 1
  tuner/fft=fft_size/ntap=num_taps input=ram:cfnoise output=ram:nmcfsfnoiseout outformat=sf &
                                   inputif=.2 outputif=.05 Bandwidth=.05 decimation=decim
  fft/psd ram:nmcfsfnoiseout ram:nmcfsffiltspec 2k hanni 0 -1
  headermod ram:nmcfsffiltspec type=1000
  call band_atten ram:nmcfsffiltspec .025 .075 .1 .15  dbout_cfsf
  assert/text="FILTER/DEC; CF to SF shift" dbout_cfsf nlt 80

  tuner/fft=fft_size/ntap=num_taps input=ram:cfnoise output=ram:nmcfcfnoiseout outformat=cf &
                                   inputif=.2 outputif=.05 Bandwidth=.05 decimation=decim
  fft/psd ram:nmcfcfnoiseout ram:nmcfcffiltspec 2k hanni 0 -1
  headermod ram:nmcfcffiltspec type=1000
  call band_atten ram:nmsfsffiltspec .025 .075 .1 .15  dbout_cfcf
  assert/text="FILTER/DEC; CF to CF shift" dbout_cfcf nlt 80

  if /plot gt 0 then
    plot ram:nmsfsffiltspec|ram:nmsfcffiltspec|ram:nmcfsffiltspec|ram:nmcfcffiltspec cm=10log
  endif

  erase ram:sfnoise ram:nmsfsfnoiseout ram:nmsfsffiltspec ram:nmsfcfnoiseout ram:nmsfcffiltspec
  erase ram:cfnoise ram:nmcfsfnoiseout ram:nmcfsffiltspec ram:nmcfcfnoiseout ram:nmcfcffiltspec
return

!---------------------------------------------------------------
! Tests for image rejection near 0 Hz
procedure test_image
  waveform ram:sfsine sf 100k sin 1 .01 0 0 1
  tuner/fft=fft_size/ntap=num_taps input=ram:sfsine output=ram:nmsfcfshift_out outformat=cf &
                                   inputif=.05 outputif=.05 Bandwidth=.1 decimation=1
  fft/psd ram:nmsfcfshift_out ram:nmsfcfshiftspec 2k hanni 0 -1
  headermod ram:nmsfcfshiftspec type=1000
  call band_atten ram:nmsfcfshiftspec .009 .011 -.011 -.009 dbout_sfcf
  if /plot gt 0 plot ram:nmsfcfshiftspec|ram:passband|ram:stopband cm=10log
  sedit dbout_sfcf x "NFORM" "###.##"
  assert/text="FILTER/IMAGE; SF test, is ^x > 50?" dbout_sfcf nlt 50

  waveform ram:cfsine cf 100k sin 1 .01 0 0 1
  tuner/fft=fft_size/ntap=num_taps input=ram:cfsine output=ram:nmcfcfshift_out outformat=cf &
                                   inputif=.01 outputif=.01 Bandwidth=.1 decimation=1
  fft/psd ram:nmcfcfshift_out ram:nmcfcfshiftspec 2k hanni 0 -1
  headermod ram:nmcfcfshiftspec type=1000
  call band_atten ram:nmcfcfshiftspec .009 .011 -.011 -.009 dbout_cfcf
  if /plot gt 0 plot ram:nmcfcfshiftspec|ram:passband|ram:stopband cm=10log
  sedit dbout_cfcf x "NFORM" "###.##"
  assert/text="FILTER/IMAGE; CF test, is ^x > 70?" dbout_cfcf nlt 70

  erase ram:sfsine ram:nmsfcfshift_out ram:nmsfcfshiftspec ram:cfsine ram:nmcfcfshift_out ram:nmcfcfshiftspec
return

!----------------------------------------------------------------------------
! Checks band attenuation -- NOTE THAT THIS PROCEDURE IS IDENTICAL IN BOTH
!                            %TEST_TUNER and %TEST_FILTER
procedure band_atten s:file d:pass_st d:pass_en d:stop_st d:stop_en u:ratio_db
  ! Calculate bandpass/bandstop ratios for input PSD
  call absc_to_index file pass_st pass_st_ind
  call absc_to_index file pass_en pass_en_ind
  call absc_to_index file stop_st stop_st_ind
  call absc_to_index file stop_en stop_en_ind

  noop ^{file}(^{pass_st_ind}:^{pass_en_ind}) ram:passband
  noop ^{file}(^{stop_st_ind}:^{stop_en_ind}) ram:stopband

  result D:pass_mx 0
  result D:pass_mn 0
  result D:stop_mx 0
  result D:stop_mn 0
  compare ram:passband max_out=pass_mx min_out=pass_mn
  compare ram:stopband max_out=stop_mx min_out=stop_mn
  ! maxmin/quiet ram:passband pass_mx pass_mn
  ! maxmin/quiet ram:stopband stop_mx stop_mn
  !!!say "After maxmin pass_mx=^PASS_MX, pass_mn=^PASS_MN"
  !!!say "After maxmin stop_mx=^STOP_MX, stop_mn=^STOP_MN"

  calc/quiet d:^ratio_db pass_mn stop_mx / log 10 *
  !!!say "ratio_db ^{^RATIO_DB}"
return

!-----------------------------------------------------------------------------
! Converts ABSC to INDEX values -- NOTE THAT THIS PROCEDURE IS IDENTICAL IN BOTH
!                                  %TEST_TUNER and %TEST_FILTER
procedure absc_to_index s:file d:absc_in u:ind_out
  ! Make sure to truncate, NOOP goes south with floating point indices
  status/quiet file xs=file_st xd=file_delt
  calc/quiet l:^ind_out absc_in file_st - file_delt / 1 + round 1 -
return

!---------------------------------------------------------------
! Test tuner on SF data (which is passed to Fft library as byte[])
! @bug 1182, 1744
! @since NeXtMidas 2.5.1
!---------------------------------------------------------------
procedure test_fft_sf
  set D:amp        1
  set freq         0.2
  set outfreq      (freq / 2)
  set bandwidth    (freq / 2)

  waveform out=ram:sfsine form=SF elem=2K shape=SIN amp=^amp freq=^freq phase=0.25 start=0 delta=1
  tuner input=ram:sfsine output=ram:tuner_fft_sf inputif=^freq outputif=^outfreq bandwidth=^bandwidth

  statistics in=ram:tuner_fft_sf MAX=max MIN=min
  if /debug then statistics in=ram:tuner_fft_sf
  assert/text="TUNER; FFT of SF format, max value >= 1" max GE ^amp
  assert/text="TUNER; FFT of SF format, min value <=-1" min LE -^amp
return

!---------------------------------------------------------------
! Test tuner /partial=all switch
! @bug 1757
! @since NeXtMidas 3.1.0
!---------------------------------------------------------------
procedure test_partial_all
  waveform out=^{fnprefix}_samples form=SF elem=64k freq=2e3 delta=1e-5
  tuner/partial=all ^{fnprefix}_samples ^{fnprefix}_tuned inputif=6e3 outputif=2e5 bandwidth=10e3

  if /debug then statistics in=^{fnprefix}_samples
  if /debug then statistics in=^{fnprefix}_tuned

  status ^{fnprefix}_tuned sz=filesize
  assert/text="TUNER; Testing /PARTIAL=ALL" filesize eq 71690.0

  erase ^{fnprefix}_samples ^{fnprefix}_tuned
return

!---------------------------------------------------------------
! Test tuner /partial=none switch
! @bug 1757
! @since NeXtMidas 3.1.0
!---------------------------------------------------------------
procedure test_partial_none
  waveform out=ram:samples form=SF elem=64k freq=2e3 delta=1e-5
  tuner/partial=none ram:samples ram:tuned inputif=6e3 outputif=2e5 bandwidth=10e3

  if /debug then statistics in=ram:samples
  if /debug then statistics in=ram:tuned

  status ram:tuned sz=filesize
  assert/text="TUNER; Testing /PARTIAL=NONE" filesize eq 64513.0
  erase ram:samples ram:tuned
return

!---------------------------------------------------------------
! Test tuner /partial=start switch
! @bug 1757
! @since NeXtMidas 3.1.0
!---------------------------------------------------------------
procedure test_partial_start
  waveform out=ram:samples form=SF elem=1k freq=2e3 delta=1e-5
  tuner/partial=start ram:samples ram:tuned inputif=6e3 outputif=2e5 bandwidth=10e3

  if /debug then statistics in=ram:samples
  if /debug then statistics in=ram:tuned

  status ram:tuned sz=filesize
  assert/text="TUNER; Testing /PARTIAL=START" filesize eq 7169.0
  erase ram:samples ram:tuned
return


!---------------------------------------------------------------
! Test /SBT= and /KWDUPDATE=
! @bug   2698
! @since NeXtMidas 3.3.1
!---------------------------------------------------------------
procedure test_keywords
  res  D:inputCF      0.0  ! Complex, centered at 0.0
  res  D:inputIF    100e3  ! User-requested input IF
  res  D:inputIFt   105e3  ! Actual transmitter IF (close, but not identical to inputIF)
  res  D:inputBW    500e3
  res  D:outputBW    20e3  ! Bandwidth of the filter (not output file)
  res  D:outputIF    10e3
  res  L:decimation   2
  calc D:outputBWf  inputBW decimation /
  calc D:outputIFt  inputIFt inputIF - outputIF +

  waveform ^{fnprefix}_samples_none CF 1M SINE  1.0 inputIFt ,, 0 1/inputBW
  waveform ^{fnprefix}_samples_sbt  CF 1M SINE  1.0 inputIFt ,, 0 1/inputBW
  waveform ^{fnprefix}_samples_foo  CF 1M SINE  1.0 inputIFt ,, 0 1/inputBW

  res  L:inRF  1e6                       ! old keyword value
  calc L:outRF inRF inputIF outputIF - + ! UPDATE value when kwd present
  calc L:insRF 111                       ! INSERT value when kwd missing
  calc L:badRF inputIF outputIF -
  keyword ^{fnprefix}_samples_sbt PUT SBT inRF
  keyword ^{fnprefix}_samples_foo PUT FOO inRF

  res T:tests  {0={KWDUPDATE="null",  SBT="null",EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^inRF, FOO="null"},FOO={SBT="null",FOO=^inRF }}},&
                1={KWDUPDATE="null",  SBT="SBT", EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^outRF,FOO="null"},FOO={SBT="null",FOO=^inRF }}},&
                2={KWDUPDATE="null",  SBT="FOO", EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^inRF, FOO="null"},FOO={SBT="null",FOO=^outRF}}},&
                3={KWDUPDATE="AUTO",  SBT="null",EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^inRF, FOO="null"},FOO={SBT="null",FOO=^inRF }}},&
                4={KWDUPDATE="AUTO",  SBT="SBT", EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^outRF,FOO="null"},FOO={SBT="null",FOO=^inRF }}},&
                5={KWDUPDATE="AUTO",  SBT="FOO", EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^inRF, FOO="null"},FOO={SBT="null",FOO=^outRF}}},&
                6={KWDUPDATE="UPDATE",SBT="null",EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^outRF,FOO="null"},FOO={SBT="null",FOO=^inRF }}},&
                7={KWDUPDATE="UPDATE",SBT="SBT", EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^outRF,FOO="null"},FOO={SBT="null",FOO=^inRF }}},&
                8={KWDUPDATE="UPDATE",SBT="FOO", EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^inRF, FOO="null"},FOO={SBT="null",FOO=^outRF}}},&
                9={KWDUPDATE="INSERT",SBT="null",EXP={NONE={SBT=^badRF,FOO="null"},SBT={SBT=^outRF,FOO="null"},FOO={SBT=^badRF,FOO=^inRF }}},&
               10={KWDUPDATE="INSERT",SBT="SBT", EXP={NONE={SBT=^badRF,FOO="null"},SBT={SBT=^outRF,FOO="null"},FOO={SBT=^badRF,FOO=^inRF }}},&
               11={KWDUPDATE="INSERT",SBT="FOO", EXP={NONE={SBT="null",FOO=^badRF},SBT={SBT=^inRF, FOO=^badRF},FOO={SBT="null",FOO=^outRF}}},&
               12={KWDUPDATE="IGNORE",SBT="null",EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^inRF, FOO="null"},FOO={SBT="null",FOO=^inRF }}},&
               13={KWDUPDATE="IGNORE",SBT="SBT", EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^inRF, FOO="null"},FOO={SBT="null",FOO=^inRF }}},&
               14={KWDUPDATE="IGNORE",SBT="FOO", EXP={NONE={SBT="null",FOO="null"},SBT={SBT=^inRF, FOO="null"},FOO={SBT="null",FOO=^inRF }}}}
  foreach test inTable/V tests
    res switch_KWDUPDATE ""
    res switch_SBT       ""
    if (test.KWDUPDATE NEQS "null") then res switch_KWDUPDATE "/KWDUPDATE=^{test.KWDUPDATE}"
    if (test.SBT       NEQS "null") then res switch_SBT       "/SBT=^{test.SBT}"
    
    foreach tag inList "none,sbt,foo"
      res    expKeys test.exp.^{tag}
      invoke tagUC   tag.toUpperCase()
      res    msg     "Keywords (/SBT=^{test.SBT} /KWDUPDATE=^{test.KWDUPDATE}) with ^{tagUC}:"

      if ((tag EQS "SBT") AND ((test.KWDUPDATE EQS "null") OR (test.KWDUPDATE EQS "AUTO")) &
                          AND (test.SBT EQS "null")) then
        if (interAct) then
          info "This will print a 'Skipping update for SBT' warning..."
        else
          ! Skip these tests when in non-interactive mode to avoid warnings
          foreach key inTable expKeys
            assert/text="^{msg} ^{key}=^{expKeys.^{key}}"/NOOP="SKIP"
          endfor
          continue
        endif
      endif

      tuner ^{fnprefix}_samples_^{tag} ^{fnprefix}_out_data inputIF outputBW ,, outputIF decimation ^{switch_KWDUPDATE} ^{switch_SBT}

      keyword ^{fnprefix}_out_data TGET fileKeys

      table  actKeys MERGE {SBT="null",FOO="null"} fileKeys

      foreach key inTable expKeys
        if ((expKeys.^{key} EQS "null") OR (actKeys.^{key} EQS "null")) then
          assert/text="^{msg} ^{key}=^{expKeys.^{key}}" expKeys.^{key} EQS actKeys.^{key}
        else
          assert/text="^{msg} ^{key}=^{expKeys.^{key}}" expKeys.^{key} EQT actKeys.^{key}
        endif
      endfor
    endfor
  endfor
  erase ^{fnprefix}_samples_none ^{fnprefix}_samples_sbt ^{fnprefix}_samples_foo ^{fnprefix}_out_data
return

!---------------------------------------------------------------
! Test valid frequency anges and make sure they do not error out.
! (Validation code was moved/updated in NeXtMidas 3.3.1 that caused this regression)
! @bug   2826
! @since NeXtMidas 3.4.3
!---------------------------------------------------------------
procedure test_frequency_range_validation
! Test value where Center freqency  - 1/2 Bandwidth is greater than -nyquist, but not greater than -nyquist/2
  res inputfile  "ram:tuner_infile"
  res elem 270336
  res amp 1.0
  res freq 159
  res absStartTime 0
  res absDelta 8.0E-5

  waveform inputfile form=CI ELEM=elem SHAPE=White AMP=amp FREQ=freq START=absStartTime  DELTA=absDelta

  res demodfreq -345.777428658165
  res demodBW 10000
  tuner inputfile ram:tuner_output ^demodfreq ^demodBW,,0,1,cf,

  assert/text="Frequency Range Validation Test" FILE(ram:tuner_output).size GT 0
  erase inputfile ram:tuner_output
return
