001/* An IIR filter actor that uses a direct form II implementation. 002 003 Copyright (c) 1998-2015 The Regents of the University of California and 004 Research in Motion Limited. 005 All rights reserved. 006 Permission is hereby granted, without written agreement and without 007 license or royalty fees, to use, copy, modify, and distribute this 008 software and its documentation for any purpose, provided that the above 009 copyright notice and the following two paragraphs appear in all copies 010 of this software. 011 012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA OR RESEARCH IN MOTION 013 LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, 014 INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS 015 SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA 016 OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF 017 SUCH DAMAGE. 018 019 THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED 020 SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 021 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 022 PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" 023 BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION 024 LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 025 ENHANCEMENTS, OR MODIFICATIONS. 026 PT_COPYRIGHT_VERSION_2 027 COPYRIGHTENDKEY 028 029 */ 030package ptolemy.actor.lib; 031 032import ptolemy.data.ArrayToken; 033import ptolemy.data.Token; 034import ptolemy.data.expr.Parameter; 035import ptolemy.data.type.ArrayType; 036import ptolemy.kernel.CompositeEntity; 037import ptolemy.kernel.util.Attribute; 038import ptolemy.kernel.util.IllegalActionException; 039import ptolemy.kernel.util.NameDuplicationException; 040import ptolemy.kernel.util.Workspace; 041import ptolemy.util.CancelException; 042import ptolemy.util.MessageHandler; 043 044/////////////////////////////////////////////////////////////////// 045//// IIR 046 047/** 048 049 This actor is an implementation of an infinite impulse response IIR 050 filter. A direct form II [1] implementation is used. This actor is type 051 polymorphic. Its input, output, 052 numerator and denominator types can be any type of Token supporting the 053 basic arithmetic operations (add, subtract and multiply). 054 <p> 055 This filter has a transfer function given by: 056 057 <b>References</b> 058 <p>[1]A. V. Oppenheim, R. W. Schafer, <i>Discrete-Time Signal Processing</i>, 059 Prentice Hall, 1989. 060 061 @author Brian K. Vogel, Steve Neuendorffer 062 @author Aleksandar Necakov, Research in Motion Limited 063 @version $Id$ 064 @since Ptolemy II 1.0 065 @Pt.ProposedRating Red (vogel) 066 @Pt.AcceptedRating Red (cxh) 067 */ 068public class IIR extends Transformer { 069 /** Construct an actor with the given container and name. 070 * @param container The container. 071 * @param name The name of this actor. 072 * @exception IllegalActionException If the actor cannot be contained 073 * by the proposed container. 074 * @exception NameDuplicationException If the container already has an 075 * actor with this name. 076 */ 077 public IIR(CompositeEntity container, String name) 078 throws NameDuplicationException, IllegalActionException { 079 super(container, name); 080 081 // Parameters 082 numerator = new Parameter(this, "numerator"); 083 numerator.setExpression("{1.0}"); 084 attributeChanged(numerator); 085 denominator = new Parameter(this, "denominator"); 086 denominator.setExpression("{1.0}"); 087 attributeChanged(denominator); 088 089 output.setTypeAtLeast(ArrayType.elementType(numerator)); 090 output.setTypeAtLeast(ArrayType.elementType(denominator)); 091 input.setTypeAtLeast(output); 092 output.setTypeAtLeast(input); 093 } 094 095 /////////////////////////////////////////////////////////////////// 096 //// ports and parameters //// 097 098 /** This parameter represents the numerator coefficients as an array 099 * of tokens. The format is 100 * {b<sub>0</sub>, b<sub>1</sub>, ..., b<sub>M</sub>}. The default 101 * value of this parameter is {1.0}. 102 */ 103 public Parameter numerator; 104 105 /** This parameter represents the denominator coefficients as an 106 * array of a tokens. The format is 107 * {a<sub>0</sub>, a<sub>1</sub>, ..., a<sub>N</sub>}. Note that 108 * the value of a<sub>0</sub> is constrained to be 1.0. This 109 * implementation will issue a warning if it is not. 110 * The default value of this parameter is {1.0}. 111 */ 112 public Parameter denominator; 113 114 /////////////////////////////////////////////////////////////////// 115 //// public methods //// 116 117 /** Handle parameter change events on the 118 * <i>numerator</i> and <i>denominator</i> parameters. The 119 * filter state vector is reinitialized to zero state. 120 * @param attribute The attribute that changed. 121 * @exception IllegalActionException If this method is invoked 122 * with an unrecognized parameter. 123 */ 124 @Override 125 public void attributeChanged(Attribute attribute) 126 throws IllegalActionException { 127 if (attribute == numerator) { 128 ArrayToken numeratorValue = (ArrayToken) numerator.getToken(); 129 _numerator = numeratorValue.arrayValue(); 130 } else if (attribute == denominator) { 131 ArrayToken denominatorValue = (ArrayToken) denominator.getToken(); 132 _denominator = denominatorValue.arrayValue(); 133 134 // Note: a<sub>0</sub> must always be 1. 135 // Issue a warning if it isn't. 136 if (!_denominator[0].isEqualTo(_denominator[0].one()) 137 .booleanValue()) { 138 try { 139 MessageHandler.warning( 140 "First denominator value is required to be 1. " 141 + "Using 1."); 142 } catch (CancelException ex) { 143 throw new IllegalActionException(this, 144 "Canceled parameter change."); 145 } 146 147 // Override the user and just use 1. 148 _denominator[0] = _denominator[0].one(); 149 } 150 } else { 151 super.attributeChanged(attribute); 152 return; 153 } 154 155 // Initialize filter state. 156 if (_numerator != null && _denominator != null) { 157 _initStateVector(); 158 } 159 } 160 161 /** Clone the actor into the specified workspace. This calls the 162 * base class and then sets the type constraints. 163 * @param workspace The workspace for the new object. 164 * @return A new actor. 165 * @exception CloneNotSupportedException If a derived class has 166 * an attribute that cannot be cloned. 167 */ 168 @Override 169 public Object clone(Workspace workspace) throws CloneNotSupportedException { 170 IIR newObject = (IIR) super.clone(workspace); 171 172 try { 173 newObject.output 174 .setTypeAtLeast(ArrayType.elementType(newObject.numerator)); 175 newObject.output.setTypeAtLeast( 176 ArrayType.elementType(newObject.denominator)); 177 newObject.input.setTypeAtLeast(newObject.output); 178 newObject.output.setTypeAtLeast(newObject.input); 179 180 ArrayToken numeratorValue = (ArrayToken) numerator.getToken(); 181 newObject._numerator = numeratorValue.arrayValue(); 182 183 ArrayToken denominatorValue = (ArrayToken) denominator.getToken(); 184 newObject._denominator = denominatorValue.arrayValue(); 185 } catch (IllegalActionException ex) { 186 // CloneNotSupportedException does not have a constructor 187 // that takes a cause argument, so we use initCause 188 CloneNotSupportedException throwable = new CloneNotSupportedException(); 189 throwable.initCause(ex); 190 throw throwable; 191 } 192 193 newObject._stateVector = new Token[_stateVector.length]; 194 System.arraycopy(_stateVector, 0, newObject._stateVector, 0, 195 _stateVector.length); 196 197 return newObject; 198 } 199 200 /** If at least one input token is available, consume a single 201 * input token, apply the filter to that input token, and 202 * compute a single output token. If this method is invoked 203 * multiple times in one iteration, then only the input read 204 * on the last invocation in the iteration will affect the 205 * filter state. 206 * 207 * @exception IllegalActionException Not thrown in this base class. 208 */ 209 @Override 210 public void fire() throws IllegalActionException { 211 super.fire(); 212 if (input.hasToken(0)) { 213 // Save state vector value. 214 Token savedState = _stateVector[_currentTap]; 215 216 // Compute the current output sample given the input sample. 217 Token yCurrent = _computeOutput(input.get(0)); 218 219 // Shadowed state. used in postfire(). 220 _latestWindow = _stateVector[_currentTap]; 221 222 // Restore state vector to previous state. 223 _stateVector[_currentTap] = savedState; 224 output.send(0, yCurrent); 225 } 226 } 227 228 /** Initialize the filter state vector with zero state. 229 * @exception IllegalActionException If the base class throws 230 * it. 231 */ 232 @Override 233 public void initialize() throws IllegalActionException { 234 super.initialize(); 235 236 // Initialize filter state. 237 _initStateVector(); 238 _currentTap = 0; 239 } 240 241 /** Return false if the input does not have a token. 242 * @return false if the input does not have a token. 243 * @exception IllegalActionException If thrown by the 244 * super class or while checking to see if there is a token. 245 */ 246 @Override 247 public boolean prefire() throws IllegalActionException { 248 boolean result = super.prefire(); 249 return result && input.hasToken(0); 250 } 251 252 /** Update the filter state. 253 * 254 * @exception IllegalActionException If the base class throws it. 255 */ 256 @Override 257 public boolean postfire() throws IllegalActionException { 258 _stateVector[_currentTap] = _latestWindow; 259 260 // Update the state vector pointer. 261 if (--_currentTap < 0) { 262 _currentTap = _stateVector.length - 1; 263 } 264 265 return super.postfire(); 266 } 267 268 /////////////////////////////////////////////////////////////////// 269 //// private methods //// 270 private Token _computeOutput(Token xCurrent) throws IllegalActionException { 271 for (int j = 1; j < _denominator.length; j++) { 272 xCurrent = xCurrent.subtract(_denominator[j].multiply( 273 _stateVector[(_currentTap + j) % _stateVector.length])); 274 } 275 276 _stateVector[_currentTap] = xCurrent; 277 278 Token yCurrent = _numerator[0].zero(); 279 280 for (int k = 0; k < _numerator.length; k++) { 281 yCurrent = yCurrent.add(_numerator[k].multiply( 282 _stateVector[(_currentTap + k) % _stateVector.length])); 283 } 284 285 return yCurrent; 286 } 287 288 private void _initStateVector() throws IllegalActionException { 289 if (_numerator.length > 0) { 290 int stateSize = java.lang.Math.max(_numerator.length, 291 _denominator.length); 292 _stateVector = new Token[stateSize]; 293 294 Token zero = _numerator[0].zero(); 295 296 for (int j = 0; j < _stateVector.length; j++) { 297 _stateVector[j] = zero; 298 } 299 } 300 } 301 302 /////////////////////////////////////////////////////////////////// 303 //// private variables //// 304 // Filter parameters 305 private Token[] _numerator = new Token[0]; 306 307 private Token[] _denominator = new Token[0]; 308 309 // Filter state vector 310 private Token[] _stateVector; 311 312 // State vector pointer 313 private int _currentTap; 314 315 // Shadow state. 316 private Token _latestWindow; 317}