#pragma TextEncoding = "UTF-8" #pragma rtGlobals=3 // Use modern global access method and strict wave access #pragma DefaultTab={3,20,4} // Set default tab width in Igor Pro 9 and later #pragma moduleName=FilterDialog #pragma IndependentModule=WMFilter #pragma IgorVersion=10 // for WaveStats/M #pragma version=10.03 // Revisions: // Version 10.03, JP260122, Corrected Apply Filter listbox to accept 1-D (and stereo/multi-channel) waves. // - Eliminated GetUserData error message when generated filter command fails. // - Showed filtered stereo and multi-column output in traces in response panel. // Version 10.02, JP250721, FilterIIR/DIM=0 enabled. Fixed command line generation. // - Adjusted created coefs field width. // Version 10.01, JP250424, WaveStats/M=0 fixed. // Version 10.0, JP250306, control positions adjusted, changes for #pragma rtGlobals=3. // Version 9.0, JP210527, added controls for FilterFIR/ENDV={startFill,endFill}, FilterIIR/ENDV=fillValue. // Version 7.09, JP180215, Filtering a wave in a sub-datafolder no longer causes an erroneous Duplicate command. // - Fixed by using AbsoluteToRelativePath(). // Version 7, JP160411, Panel Resolution fixes, raised /HI and /LO num filter coefs to max (32767). // Version 6.37, JP141111, Traces added to the response graph by the user are not removed on every update, which allows for response comparisons. // Version 6.31, LH, PanelResolution // 2/10/2012 - 6.30: fix for Show Phase checkbox state not being remembered when switching tabs, added db - 50dB option to response popup. // 2/10/2012 - 6.23: fixes for coefs waves not showing up in the Select Filter Coefficients Wave tab's wave list. // 5/26/2009 - 6.1: allows negative notchQ to get Igor 6.0 IIR notch design. // 3/20/2009 - Revised WMFilterDialogHook to avoid recursion. // 12/22/2008 - Revised WMFilterDialogHook to work better on Windows if maximized. // 8/13/2008 - liberal name for filtered result and coefficients result are now properly quoted. // 4/9/2008 - Fixed " FilterFIR gave error: Length of wave 'impulseResponse' is less than the coefficient wave" error that occured when filter length exceeded 1000. // - Increased impulse length from 1000 to 5000 to improve frequency resolution of the response graph. // 3/24/2008 - Fixed highpass fir_high_n SetVariable, resized some too-big FIR controls. // 3/16/2006 - deletes self when the dialog closes. // 10/02/2006 - dialog size restored properly on Windows, added Gain log10 option. // 11/08/2006 - doesn't DELETEINCLUDE itself, to avoid menu compilation problem. // 12/18/2006 - Set phase to 45 degree increments. // 12/29/2006 - Set phase to degree-friendly increments, added From target and Sort By controls. // 3/12/2007 - uses ToCommandLine operation for the "To Cmd Line" button. // 4/24/2007 - Changes to aid in localization. // 5/2/2007 - The panel and panel subwindows are now not editable. //#include menus=0 // defer this until ZeroPoleButton is pressed #include version >=1.20 // has fix for excessive datafolder filtering #include #include // for stereo, etc traces static Constant kNoEdit=1 // panels not editable, set to 0 for development or use SetIgorOption independentModuleDev=1 // START OF LOCALIZABLE STRINGS [ // Menus StrConstant ksFilterDotDotDotMenu = "Filter..." // See DescribeWaveType() static StrConstant ksDescribeTEXT="TEXT" static StrConstant ksDescribeUnsigned="U" static StrConstant ksDescribeSinglePrecision="SP" static StrConstant ksDescribeDoublePrecsion="DP" static StrConstant ksDescribeInteger8="INT8" static StrConstant ksDescribeInteger16="INT16" static StrConstant ksDescribeInteger32="INT32" static StrConstant ksDescribeComplex=" CMPLX" // NOTE THE LEADING SPACE CHARACTER // See DescribeCoefs() static StrConstant ksDescribeCoefsFIRFmt= "FIR (%d coefficients)" static StrConstant ksDescribeCoefsIIRDF1Fmt= "IIR Direct Form I format %s" static StrConstant ksDescribeCoefsIIRCascFmt= "IIR Cascade format (%d sections)" static StrConstant ksDescribeCoefsIIRPZFmt= "IIR Zeros and Poles format (%d zeros and %d poles)" // Dialog error messages - See GenerateCommand() static StrConstant ksDlgErrSelectCoefsWave= "*** Select a coefficients wave in the Select Filter Coefficients Wave tab ***" static StrConstant ksDlgErrSelectLowPassHiOrNotch= "*** Select Low Pass, High Pass, or Notch ***" static StrConstant ksDlgErrSelectApplyWave= "*** Select a wave in the Apply Filter tab ***" // Dialog error messages - See AutoUpdateFilteredOutCheckProc() and UpdateFilterOutputNowButtonProc() static StrConstant ksDlgErrSelectInputToFilter= "Select an Input to Filter from the list above." // Dialog error messages - See EditZerosPolesButtonProc() static StrConstant ksDlgErrNotPoleZeroWaveFormat= "%s is not a Zeros and Poles filter coefficients wave." // %s is wave's path (name) // UpdateResponse() and UpdateFilteredOutput() static StrConstant ksErrFilterCouldNotBeCreated= "Filter could not be created\rusing those values." static StrConstant ksErrSelectCoefsWave= "Select a coefficients wave in the Select Filter Coefficients Wave tab." // UpdateFilteredOutput() static StrConstant ksErrEnterAnOutputName= "Enter an Output Name" static StrConstant ksErrSelectInputToFilter= "Select Input to Filter" static StrConstant ksErrAndClickUpdateOutputNow= " and click Update Output Now" // NOTE THE LEADING SPACE CHARACTER // ChangeFIRIIRTab() static StrConstant ksTitleDesignUsingFrequency= "\\K(65535,0,0)Design using this Sampling Frequency (Hz)" static StrConstant ksTitleResponseUsingFrequency= "\\K(65535,0,0)Show Response using this Sampling Frequency (Hz)" // Dialog controls - see WMFilterDialog() static StrConstant ksFilterDialogTitle= "Filter Design and Application" // CheckBox createFilter static StrConstant ksCreateFilterTitle= "Create Coefs" // SetVariable fs static StrConstant ksFsTitle= "\\K(65535,0,0)Design using this Sampling Frequency (Hz)" // TabControl firIIRTab static StrConstant ksFIR_IIRTab0Title="Design FIR Filter" static StrConstant ksFIR_IIRTab1Title="Design IIR Filter" static StrConstant ksFIR_IIRTab2Title="Select Filter Coefficients Wave" // **** FIR Tab **** static StrConstant ksFirLowpassGroupTitle=" Low Pass" static StrConstant ksFirLowpassF1Title="End of Pass Band" static StrConstant ksFirLowpassF2Title="Start of Reject Band" static StrConstant ksFirLowpassNTitle="Number of Coefficients" static StrConstant ksFirHighpassGroupTitle=" High Pass" static StrConstant ksFirHighpassF1Title="End of Reject Band" static StrConstant ksFirHighpassF2Title="Start of Pass Band" static StrConstant ksFirHighpassNTitle="Number of Coefficients" static StrConstant ksFirWindowKindTitle="Window" static StrConstant ksFirNotchGroupTitle=" Notch" static StrConstant ksFirNotchFcTitle="Notch Frequency" static StrConstant ksFirNotchFwTitle="Notch Width" static StrConstant ksFirNotchNMultTitle="Improve Notch Accuracy by" static StrConstant ksFirNotchEPSTitle="Omit Coefs smaller than" // **** IIR Tab **** static StrConstant ksIIRFormatList = "Direct Form 1;Cascade (Direct Form II);Zeros and Poles;" static StrConstant ksIIRCoefsFormatTitle="Filter Coefficients Format:" static StrConstant ksIIRCreatePZButtonTitle="Create Filter using Poles and Zeros Editor..." static StrConstant ksIIRLowpassGroupTitle=" Low Pass" static StrConstant ksIIRLowpassFcTitle="Cutoff Frequency" static StrConstant ksIIRHighpassGroupTitle=" High Pass" static StrConstant ksIIRHighpassFcTitle="Cutoff Frequency" static StrConstant ksIIROrderTitle="Order" static StrConstant ksIIRNotchGroupTitle=" Notch" static StrConstant ksIIRNotchFcTitle="Notch Frequency" static StrConstant ksIIRNotchFwTitle="Notch Width" // **** Select Coefs Tab **** static StrConstant ksSelectFilterEditTitle="Edit Zeros and Poles" // Filter Dialog P0 Response/Apply Filter controls // TabControl responseTab static StrConstant ksResponseTab0Title="Response" static StrConstant ksResponseTab1Title="Apply Filter" // **** Response Tab **** static StrConstant ksResponseShowMagTitle="Show Magnitude" static StrConstant ksResponseShowPhaseTitle="Show Phase" static StrConstant ksResponsePhaseDegreesTitle="degrees" static StrConstant ksResponsePhaseRadiansTitle="radians" static StrConstant ksResponsePhaseUnwrapTitle="Unwrap" // **** Apply Filter Tab **** static StrConstant ksApplyFilterInputTitle="Input to Filter" // PopupMenu sort static StrConstant ksSortByTitle= "Sort By" // CheckBox fromTarget static StrConstant ksFromTargetTitle= "From target" // PopupMenu endEffect static StrConstant ksEndEffectsTitle= "End Effect(s)" // SetVariable fillvalue static StrConstant ksFillValueTitle= "Fill Value" static StrConstant ksStartFillValueTitle= "Start Fill Value" // Checkbox endFillCheck static StrConstant ksEndFillValueTitleLong= "End Fill Value" static StrConstant ksEndFillValueTitleShort= "End Fill" // SetVariable endFill is untitled (title=" ") // Checkbox iir_endFillCheck is untitled (title=" ") // SetVariable iir_endFillValue static StrConstant ksIIRFillValueTitle= "End Fill Value" static StrConstant ksApplyFilterOutputTitle="Output Name" static StrConstant ksApplyFilterAutoUpdateTitle="Auto-update Filtered Output" static StrConstant ksApplyFilterUpdateNowTitle="Update Output Now" // Filter Dialog P1 command controls static StrConstant ksCmdDoItTitle="Do It" static StrConstant ksCmdToCmdTitle="To Cmd Line" static StrConstant ksCmdToClipTitle="To Clip" static StrConstant ksCmdCancelTitle="Cancel" static StrConstant ksCmdHelpTitle="Help" // PopupMenu magnitudePop selections static StrConstant ksMagnitudePopItems= "dB;dB min -100;dB min -50;dB min -20;Gain;Gain log10;" // Help Topic for Filter Dialog's Help button static StrConstant ksFilterDialogHelpTopic="Filter Dialog" // END OF LOCALIZABLE STRINGS ] // Menus Menu "Analysis" "Filter...", /Q, WMFilterDialog() End static StrConstant ksFIRWindowsList = "Bartlett;Blackman367;Blackman361;Blackman492;Blackman474;Cos1;Cos2;Cos3;Cos4;Hamming;Hanning;KaiserBessel20;KaiserBessel25;KaiserBessel30;Parzen;Poisson2;Poisson3;Poisson4;Riemann;" // Definitions for Main FIR/IIR/Select Coefs tabs static Constant firTabNum = 0 static Constant iirTabNum = 1 static Constant coefsTabNum = 2 static StrConstant ksFIRControls="fir_highPass;fir_high_f1;fir_high_f2;fir_high_n;fir_highpass_group;fir_low_f1;fir_low_f2;fir_low_n;fir_lowpass;fir_lowpass_group;fir_notch;fir_notch_eps;fir_notch_fc;fir_notch_fw;fir_notch_group;fir_notch_nmult;fir_windowKind;" static StrConstant ksIIRControls="iir_coefsFormat;iir_fHigh;iir_fLow;iir_fNotch;iir_highPass;iir_highpass_group;iir_lowpass;iir_lowpass_group;iir_notch;iir_notchWidth;iir_notch_group;iir_order;createCustomZerosPolesCoefs;" static StrConstant ksSelectCoefsControls="coefsDetails;coefsWavesList;editZerosPoles;" static StrConstant ksCreateCoefsControls="coefsOutputName;createFilter;" // Definitions for Response/Apply tabs static StrConstant ksResponseControls="showMagnitude;magnitudePop;showPhase;phaseDegrees;phaseRadians;phaseUnwrap;" static StrConstant ksApplyFilterControls="inputTitle;filterThisWave;filteredOutputName;autoUpdateFilteredOutput;updateNow;endEffect;fillValue;endFillCheck;endFillValue;iir_endFillCheck;iir_endFillValue;" static Constant ResponseTabNum = 0 static Constant ApplyFilterTabNum = 1 // Panel and subwindow names static StrConstant ksPanelName= "FilterDialog" static StrConstant ksGraphName="FilterDialog#G0" static StrConstant ksResponsePanelName="FilterDialog#P0" static StrConstant ksCommandPanelName="FilterDialog#P1" // Panel and Guides metrics static Constant kGraphTopPos= 215 // panel units //static Constant kMinResponseHeightPoints= 160 static Constant kMinResponseHeightPoints= 242 static Constant kGraphBottomOffset= -93 // panel units static Constant kPanelHeight= 559 // kGraphTopPos + kMinResponseHeightPoints - kGraphBottomOffset ? // Variable vLeft=39, vTop=60, vRight=721, vBottom=559 // vBottom += 60 // Igor 9: make room for Fill Value controls // Variable minHeight = vBottom - vTop // this is the smallest permitted size. // filter coefs types static Constant kCoefsNone= 0 static Constant kCoefsFIR= 1 static Constant kCoefsIIRDF1= 2 static Constant kCoefsIIRCascade= 3 static Constant kCoefsIIRZerosPoles= 4 #if Exists("PanelResolution") != 3 Static Function PanelResolution(wName) // For compatibility with Igor 7 String wName return 72 End #endif Static Function PanelCoordsToPoints(win, panelOrControlCoordinate) String win Variable panelOrControlCoordinate // Igor 6 wsizeDC or Igor 7 points if screen resolution > 96 Variable points= panelOrControlCoordinate * PanelResolution(win) / ScreenResolution return points End // "Panel Coordinates" are used to store panel sizes to match the control coordinates. // In this way, one control that fills the entire panel would have the same width and height values // as the panel's "Panel Coordinates". Static Function PointsToPanelCoords(win, points) String win Variable points Variable panelOrControlCoordinate= points / PanelResolution(win) * ScreenResolution return panelOrControlCoordinate // Igor 6 wsizeDC or Igor 7 points if screen resolution > 96 End static Function ResponseTabProc(tca) : TabControl STRUCT WMTabControlAction &tca switch( tca.eventCode ) case 2: // mouse up Variable tab = tca.tab // new value String win= tca.win // Show/Hide the Response or Apply controls ShowOrHideControlList(win, ksResponseControls, tab==ResponseTabNum) ShowOrHideControlList(win, ksApplyFilterControls, tab==ApplyFilterTabNum) if( tab==ApplyFilterTabNum ) ShowHideApplyFilterControls() endif // switch the content of the Graph. UpdateResponse(0) break endswitch return 0 End // NOTE: the wave DOES exist // NOTE: this is NOT the CREATED coefs wave, NOR the temporary coefs wave static Function/S SelectedCoefsWavePath() String wavePaths= WS_SelectedObjectsList(ksPanelName, "coefsWavesList") return StringFromList(0,wavePaths) // can be "" End // RelativelyEqual() fails when both values are very close to (or equal) to zero) static Function RelativelyEqual(v1,v2, releps) Variable v1, v2 Variable releps // relative difference allowed to be considered equal. Use 0.1 to specify equality to within 10%. Variable mag= max(abs(v1),abs(v2)) Variable error= abs(v1-v2) Variable relError= error/mag return relError < releps End // FilterFIR creates both symmetrical filters when n is odd, // and nearly-symmetrical filters when n is even. static Function SymmetricWave(w) Wave w // must be 1-D wave Variable n= numpnts(w) Variable i, diff, n2= floor(n/2) // When n is even the first element isn't symmetrical, and is skipped Variable isEven= (n & 0x1) == 0 Variable offset= isEven ? 1 : 0 WaveStats/Q/M=1 w Variable threshold= 1e-8 * max(abs(V_max),abs(V_Min)) for(i=0; i 0 ) // a zero-points wave means the filter couldn't be realized. // filter an impulse Variable coefsSizeEven= (DimSize(coefs,0)+1) %& ~0x1 Variable npnts=max(50000, coefsSizeEven) WAVE/D/Z impulseResponse if( !WaveExists(impulseResponse) || numpnts(impulseResponse) != npnts ) Make/O/D/N=(npnts) impulseResponse endif Variable fs= NumVarOrDefault("root:Packages:WM_FilterDialog:fs", 1) SetScale/P x, 0, 1/fs, "s" impulseResponse if( isFIR ) impulseResponse= p==npnts/2 else impulseResponse= p==0 endif ApplyFilterToWave(coefs, impulseResponse) // measure the response FFT/MAG/DEST=magnitude impulseResponse SetScale d, 0, 0, "dB", magnitude Variable ignorePhaseIfMagLE= -inf // that is, don't ignore any phase values Variable logType= 0 ControlInfo/W=$ksResponsePanelName magnitudePop strswitch(S_value) // static StrConstant ksMagnitudePopItems= "dB;dB min -100;dB min -50;dB min -20;Gain;Gain log10;" case "dB": magnitude= 20*log(magnitude) break case "dB min -100": magnitude= max(-100,20*log(magnitude)) ignorePhaseIfMagLE= -100 break case "dB min -50": magnitude= max(-50,50*log(magnitude)) ignorePhaseIfMagLE= -50 break case "dB min -20": magnitude= max(-20,20*log(magnitude)) ignorePhaseIfMagLE= -20 break case "Gain log10": logType= 1 // fall through case "Gain": default: SetScale d, 0, 0, "", magnitude break endswitch FFT/OUT=5/DEST=phase impulseResponse if( isFIR ) phase= 0 else // omit phase where magnitude < minDB if( numtype(ignorePhaseIfMagLE) == 0 ) phase= (magnitude[p] <= ignorePhaseIfMagLE) ? NaN : phase[p] endif Variable modulus= 2*pi ControlInfo/W=$ksResponsePanelName phaseDegrees phaseIsInDegrees= V_Value if( phaseIsInDegrees ) modulus= 360 phase *= 180/pi // convert to degrees SetScale d, 0, 0, "deg", phase else SetScale d, 0, 0, "rad", phase endif ControlInfo/W=$ksResponsePanelName phaseUnwrap if(V_Value ) phase[0] = phase[1] Unwrap modulus, phase // continuous phase endif endif else Make/O/D/N=0 magnitude, phase SetScale d, 0, 0, "", magnitude, phase endif SetDataFolder dfSave // remove any traces that aren't magnitude or phase Variable needToAppendMagnitude= 1 Variable needToAppendPhase= !isFIR String pathToMagnitude= "root:Packages:WM_FilterDialog:magnitude" String pathToPhase= "root:Packages:WM_FilterDialog:phase" String traces= TraceNameList(ksGraphName, ";", 1) Variable i, n= ItemsInList(traces) for(i=0; i= 0 if( WaveExists(phase) && rightAxisExists ) Variable inc= 0, minorTicks if( phaseIsInDegrees ) inc= ChooseDegreesIncrement(phase, ksGraphName, "right", minorTicks) endif if( inc == 0 ) ModifyGraph/W=$ksGraphName minor(right)=1,manTick(right)=0 else ModifyGraph/W=$ksGraphName manTick(right)={0,inc,0,0},manMinor(right)={minorTicks,0} endif endif ControlInfo/W=$ksResponsePanelName showMagnitude ModifyGraph/W=$ksGraphName hideTrace(magnitude)=!V_value, log(left)=logType if( isFIR ) Variable/G root:Packages:WM_FilterDialog:zero = 0 Checkbox showPhase win=$ksResponsePanelName, variable= root:Packages:WM_FilterDialog:zero ModifyControlList/Z "showPhase;phaseDegrees;phaseRadians;phaseUnwrap;" win=$ksResponsePanelName, disable= 2 else Checkbox showPhase win=$ksResponsePanelName, disable= 0 ,variable= root:Packages:WM_FilterDialog:phase_check ControlInfo/W=$ksResponsePanelName showPhase ModifyControlList/Z "phaseDegrees;phaseRadians;phaseUnwrap;" win=$ksResponsePanelName, disable= V_value ? 0 : 2 ModifyGraph/W=$ksGraphName hideTrace(phase)=!V_value endif if( DimSize(coefs,0) > 0 ) Legend/W=$ksGraphName/C/N=ResponseLegend "" else String text=ksErrFilterCouldNotBeCreated // "Filter could not be created\rusing those values." if( DoingUserCoefs() ) text=ksErrSelectCoefsWave // "Select a coefficients wave in the Select Filter Coefficients Wave tab." endif Legend/W=$ksGraphName/C/N=ResponseLegend "\\JC\\K(65535,0,0)"+text endif End static Function ChooseDegreesIncrement(wDegrees, graphName, axisName, minorTicks) Wave wDegrees String graphName, axisName Variable &minorTicks GetWindow $graphName psize Variable approxGraphHeightInPoints= V_bottom- V_top String fontName Variable fontStyle Variable fontSize= AxisLabelFontSizeStyle(graphName, axisName, fontName, fontStyle) // in points // figure out how many labels will fit on the axis. // here we're assuming that the axis is vertical (left/right) // and the labels are horizontal, so they stack their heights Variable labelHeightPixels= FontSizeHeight(fontName, fontSize, fontStyle) Variable labelHeightPoints= PanelCoordsToPoints(graphName,labelHeightPixels) Variable maxLabels= floor(approxGraphHeightInPoints/labelHeightPoints) WaveStats/Q/M=1 wDegrees Variable range= V_max-V_min Variable delta= range/(maxLabels / 1.1) Variable inc // round to multiple of 360, 90, 45, 15, or 5 inc= 360 * round(delta/360) minorTicks= 3 // 90 degrees if( inc == 0 ) inc= 90 * round(delta/90) minorTicks= 2 // 30 degrees if( inc == 0 ) inc= 45 * round(delta/45) minorTicks= 2 // 15 degrees if( inc == 0 ) inc= 15 * round(delta/15) minorTicks= 2 // 5 degrees if( inc == 0 ) inc= 5 * round(delta/5) minorTicks= 4 // 1 degrees endif endif endif endif return inc End // this belongs in // Requires Igor 6.0 static Function AxisLabelFontSizeStyle(graphName, axisName, fontName, fontStyle) String graphName, axisName String &fontName Variable &fontStyle String info= AxisInfo(graphName,axisName) fontName= StringByKey("FONT", info) fontStyle= NumberByKey("FONTSTYLE", info) return NumberByKey("FONTSIZE",info) End static Function ApplyFilterToWave(filter, filterThis [,endEffect, fillValue, endFillValue]) Wave filter // IIR or FIR coefs, will be 0 points if no filter was designed. Wave filterThis // replaced with filtered result // Optional params Variable endEffect, fillValue, endFillValue if( ParamIsDefault(endEffect) ) endEffect=1 endif if( ParamIsDefault(fillValue) ) fillValue=0 endif if( ParamIsDefault(endFillValue) ) endFillValue=fillValue endif Variable filterLen= DimSize(filter,0) Variable V_Flag= filterLen == 0 if( V_Flag ) filterThis= NaN // without this, it looks like there actually is an all-pass filter. else Variable isFIR= DimSize(filter,1) == 0 if( isFIR ) // avoid "filtered wave is shorter than filter" error if( filterLen > DimSize(filterThis,0) ) if( filterLen & 0x1 ) // if odd, filterLen += 1 // make even endif Redimension/N=(filterLen,-1,-1,-1) filterThis endif if( endEffect == 2 ) // fill FilterFIR/ENDV={fillValue, endFillValue}/COEF=filter filterThis // /ENDV implies /E=2 else FilterFIR/E=(endEffect)/COEF=filter filterThis endif else Variable wt= WaveType(filter) Variable isComplex= wt & 0x1 if( isComplex ) FilterIIR/Z/COEF=filter/ZP/ENDV=(endFillValue) filterThis // zero-poles format else if( DimSize(filter,1) >= 6 ) FilterIIR/Z/COEF=filter/CASC/ENDV=(endFillValue) filterThis // DF2 format else FilterIIR/Z/COEF=filter/ENDV=(endFillValue) filterThis // DF1 format endif endif endif endif return V_Flag End // Perhaps the panel has already been killed. Function PossiblyUpdateWaveSelectorWidget(String windowName, String controlName) Variable wt = WinType(windowName) if( wt != 0 ) WS_UpdateWaveSelectorWidget(windowName, controlName) endif End static Function AutoColorTraces(String graphName) String traces= TraceNameList(graphName, ";", 1) Variable n= ItemsInList(traces) if (n == 1) ModifyGraph/W=$graphName rgb[0]=(65535, 0, 0) else ApplyCommonColors(graphName) // KBColorizeTraces endif End static Function InstanceNumber(String instanceName) // for example, "myData" or "myData#1" or "'my #5 test'#1" String instanceNumStr SplitString/E="#(\\d+)$" instanceName, instanceNumStr Variable instance = str2num(instanceNumStr) if (numtype(instance) != 0 ) instance = 0 endif return instance End static Function/S FDReverseList(String list, String sep) Wave/T tw = ListToTextWave(list, sep) Variable i, n = DimSize(tw,0) String out = "" for(i = 0; i < n; i += 1) // By default itemStr is added to the start of the list. out = AddListItem(tw[i], out, sep) endfor return out End // creates a filtered wave named "filtered" in the WM_FilterDialog data folder. // This wave could potentially be large and should be cleaned up when the dialog is closed. Static Function UpdateFilteredOutput(updateNow) Variable updateNow // if true, filter the input data, otherwise just ensure the traces are shown/hidden Variable isFIR String coefsPath= CreateCoefs(isFIR) WAVE/Z/D filter= $coefsPath // the filter coefficients in IIR or FIR format, will be 0 points if no filter was designed. Wave/Z filterInputWave= $FilterInputWavePath() // that is, the input TO the filter String outputName= StrVarOrDefault("root:Packages:WM_FilterDialog:filteredOutputName", "filtered") // allow "" outputName to mean "don't filter the input" if( strlen(outputName) == 0 ) Textbox/W=$ksGraphName/C/N=ResponseLegend ksErrEnterAnOutputName // "Enter an Output Name" return 0 endif // protect against inputName==outputName if( WaveExists(filterInputWave) && CmpStr(NameOfWave(filterInputWave),outputName) == 0 ) String df= GetDataFolder(1) SetDataFolder GetWavesDataFolder(filterInputWave,1) outputName= UniqueName(outputName, 1, 0) String/G root:Packages:WM_FilterDialog:filteredOutputName= outputName SetDataFolder df endif // NOTE: the outputName is used ONLY to create the final filtered result when the user presses "Do It". // the filtered output is always stored in a wave named "filtered" in the WM_FilterDialog data folder, // and it is this wave that is displayed in the Filtered Output graph ksGraphName outputName="filtered" Wave/Z output= TraceNameToWaveRef(ksGraphName, outputName) Variable filterFailed= 0 if( updateNow && WaveExists(filter) && WaveExists(filterInputWave) ) String dfSave= GetDataFolder(1) NewDataFolder/O root:Packages NewDataFolder/O/S root:Packages:WM_FilterDialog // filter the input wave using the given output name Duplicate/O filterInputWave, $outputName Wave output=$outputName if( isFIR ) NVAR endEffect= root:Packages:WM_FilterDialog:endEffect NVAR fillValue= root:Packages:WM_FilterDialog:fillValue NVAR endFillCheck= root:Packages:WM_FilterDialog:endFillCheck NVAR endFillValue= root:Packages:WM_FilterDialog:endFillValue if( endFillCheck ) filterFailed= ApplyFilterToWave(filter, output, endEffect=endEffect, fillValue=fillValue, endFillValue=endFillValue) else filterFailed= ApplyFilterToWave(filter, output, endEffect=endEffect, fillValue=fillValue) endif else NVAR iir_endFillCheck= root:Packages:WM_FilterDialog:iir_endFillCheck if( iir_endFillCheck ) NVAR iir_endFillValue= root:Packages:WM_FilterDialog:iir_endFillValue filterFailed= ApplyFilterToWave(filter, output, endFillValue=iir_endFillValue) else filterFailed= ApplyFilterToWave(filter, output) endif endif SetDataFolder dfSave String cmd= GetIndependentModulename()+"#PossiblyUpdateWaveSelectorWidget(\"" + ksResponsePanelName +"\", \"filterThisWave\")" Execute/P/Q/Z cmd endif // update the graph Variable ncolumns = 0 String outputPath= "" if( WaveExists(output) ) outputPath= GetWavesDataFolder(output,2) ncolumns = DimSize(output,1) if( ncolumns == 1 ) ncolumns = 0 // treat a 2D wave with one column like a 1D wave endif endif // Remove any other traces that aren't part of the output wave // (in the case of multi-channel audio, multiple traces may be a column of the same output wave) // strategy: // 1. if a trace in the graph isn't displaying a part of the output wave, remove it. // 2. trace(s) that display a part of the output wave are removed // if they have an instance number that is greater than the number of columns. Variable outputInstanceMax = -1 // -1 means no traces represent the output wave. // A 1-D wave needs only output#0 aka output, outputInstanceMax will be 1 String traces= TraceNameList(ksGraphName, ";", 1) traces = FDReverseList(traces, ";") // remove output#3 before output#2 to keep the instance numbers valid. Variable i, n= ItemsInList(traces) for(i=0; i= ncolumns ) RemoveFromGraph/W=$ksGraphName $tracename else // We are encountering instance numbers from high to low because of reversed list. outputInstanceMax = max(outputInstanceMax,instance) // ncolumns-1 if no change in number of traces, 0 if 1D wave endif else RemoveFromGraph/W=$ksGraphName $tracename endif endfor // append the filtered output if( WaveExists(output) ) if( outputInstanceMax == -1 ) AppendToGraph/W=$ksGraphName output // column 0 outputInstanceMax = 0 endif // append additional column(s) as needed Variable col for( col = outputInstanceMax+1; col < ncolumns; col += 1 ) AppendToGraph/W=$ksGraphName output[][col] // second column if stereo endfor endif AutoColorTraces(ksGraphName) traces= TraceNameList(ksGraphName, ";", 1) n= ItemsInList(traces) if( n < 1 ) String text=ksErrSelectInputToFilter // "Select Input to Filter" if( !AutoUpdateFiltered() ) text += ksErrAndClickUpdateOutputNow // " and click Update Output Now" endif Textbox/W=$ksGraphName/C/N=ResponseLegend text else if( filterFailed ) Textbox/W=$ksGraphName/C/N=ResponseLegend ksErrFilterCouldNotBeCreated // "Filter could not be created\rusing those values." else Legend/W=$ksGraphName/C/N=ResponseLegend "" // default legend endif endif End // NOTE: the wave DOES exist static Function/S FilterInputWavePath() String wavePaths= WS_SelectedObjectsList(ksResponsePanelName, "filterThisWave") return StringFromList(0,wavePaths) // can be "" End // return the path to where to create the filtered output wave, which is to be created in the CURRENT data folder (often the root data folder) // NOTE: the output wave is NOT created here. static Function/S FilteredOutputWavePath() String outputName= StrVarOrDefault("root:Packages:WM_FilterDialog:filteredOutputName", "filtered") if( strlen(outputName) == 0 ) return "" // blank text in the Output Name SetVariable endif if( CmpStr(CleanupName(outputName, 1), outputName) != 0 ) return "" // illegal name endif String outputWavePath= GetDataFolder(1) + PossiblyQuoteName(outputName) // protect against overwriting important waves in the WM_FilterDialog data folder if( CmpStr(outputWavePath,"root:Packages:WM_FilterDialog:magnitude") == 0 ) return "" // cannot overwrite this wave endif if( CmpStr(outputWavePath,"root:Packages:WM_FilterDialog:phase") == 0 ) return "" // cannot overwrite this wave endif if( CmpStr(outputWavePath,"root:Packages:WM_FilterDialog:coefs") == 0 ) return "" // cannot overwrite this wave endif // protect against nothing for the output to be duplicated from. String inputWavePath= FilterInputWavePath() Wave/Z filterInputWave= $inputWavePath if( !WaveExists(filterInputWave) ) return "" // no input selected endif // protect against input == output if( CmpStr(inputWavePath,outputWavePath) == 0 ) return "" // cannot overwrite self endif return outputWavePath // the wave may very well NOT exist, even if the result is a path to a wave that COULD exist. End static Function ChangeFIRIIRTab(tab) Variable tab // Show/Hide the FIR or IIR controls ShowOrHideControlList(ksPanelName, ksFIRControls, tab==firTabNum) ShowOrHideControlList(ksPanelName, ksIIRControls, tab==iirTabNum) ShowOrHideControlList(ksPanelName, ksCreateCoefsControls, tab==firTabNum || tab==iirTabNum) ShowOrHideControlList(ksPanelName, ksSelectCoefsControls, tab==coefsTabNum) String title=ksTitleDesignUsingFrequency // "Design using this Sampling Frequency (Hz)" if( tab==coefsTabNum ) title= ksTitleResponseUsingFrequency // "Show Response using this Sampling Frequency (Hz)" endif ModifyControl/Z fs, win=$ksPanelName, title=title Variable/G root:Packages:WM_FilterDialog:firIIRTab= tab UpdateForIIRCoefsFormat() PossiblyShowHideApplyFilterControls() UpdateResponse(0) End static Function FIRIIRTabProc(tca) : TabControl STRUCT WMTabControlAction &tca switch( tca.eventCode ) case 2: // mouse up ChangeFIRIIRTab(tca.tab) break endswitch return 0 End Static Function ShowOrHideControlList(win, controls, show) String win String controls // semicolon-separated controls Variable show // Use the Asylum Research trick: // disable= 0 = showing and active // disable= 2 = showing and disabled // disable= 1 = hidden and (latent) active // disable= 3 = hidden and (latent) disabled Variable i, n= ItemsInList(controls) for(i=0; i