001/* A type polymorphic FIR filter. 002 003 Copyright (c) 1998-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.domains.sdf.lib; 029 030import java.util.HashSet; 031import java.util.Set; 032 033import ptolemy.data.ArrayToken; 034import ptolemy.data.IntToken; 035import ptolemy.data.Token; 036import ptolemy.data.expr.Parameter; 037import ptolemy.data.type.ArrayType; 038import ptolemy.data.type.BaseType; 039import ptolemy.data.type.MonotonicFunction; 040import ptolemy.data.type.Type; 041import ptolemy.graph.Inequality; 042import ptolemy.graph.InequalityTerm; 043import ptolemy.kernel.CompositeEntity; 044import ptolemy.kernel.util.Attribute; 045import ptolemy.kernel.util.IllegalActionException; 046import ptolemy.kernel.util.InternalErrorException; 047import ptolemy.kernel.util.NameDuplicationException; 048import ptolemy.kernel.util.Workspace; 049 050/////////////////////////////////////////////////////////////////// 051//// FIR 052 053/** 054 This actor implements a type polymorphic finite-impulse response 055 filter with multirate capability. Since this filter operates on 056 Tokens, it is polymorphic in the type of data it operates on. 057 <p> 058 Note that the current implementation of this actor only reads its 059 parameters during initialization, so the filter cannot be 060 changed during execution. 061 <p> 062 When the <i>decimation</i> (<i>interpolation</i>) 063 parameters are different from unity, the filter behaves exactly 064 as it were followed (preceded) by a DownSample (UpSample) actor. 065 However, the implementation is much more efficient than 066 it would be using UpSample or DownSample actors; 067 a polyphase structure is used internally, avoiding unnecessary use 068 of memory and unnecessary multiplication by zero. 069 Arbitrary sample-rate conversions by rational factors can 070 be accomplished this way. 071 <p> 072 To design a filter for a multirate system, simply assume the 073 sample rate is the product of the interpolation parameter and 074 the input sample rate, or equivalently, the product of the decimation 075 parameter and the output sample rate. 076 In particular, considerable care must be taken to avoid aliasing. 077 Specifically, if the input sample rate is <i>f</i>, 078 then the filter stopband should begin before <i>f</i>/2. 079 If the interpolation ratio is <i>i</i>, then <i>f</i>/2 is a fraction 080 1/2<i>i</i> of the sample rate at which you must design your filter. 081 <p> 082 The <i>decimationPhase</i> parameter is somewhat subtle. 083 It is exactly equivalent the phase parameter of the DownSample actor. 084 Its interpretation is as follows; when decimating, 085 samples are conceptually discarded (although a polyphase structure 086 does not actually compute the discarded samples). 087 If you are decimating by a factor of three, then you will select 088 one of every three outputs, with three possible phases. 089 When decimationPhase is zero (the default), 090 the latest (most recent) samples are the ones selected. 091 The decimationPhase must be strictly less than 092 the decimation ratio. 093 <p> 094 <i>Note: in this description "sample rate" refers to the physical sampling 095 rate of an A/D converter in the system. In other words, the number of 096 data samples per second. This is not usually specified anywhere in an 097 SDF system, and most definitely does NOT correspond to the SDF rate parameters 098 of this actor. This actor automatically sets the rates of the input 099 and output ports to the decimation and interpolation ratios, respectively.</i> 100 <p> 101 For more information about polyphase filters, see F. J. Harris, 102 "Multirate FIR Filters for Interpolating and Desampling", in 103 <i>Handbook of Digital Signal Processing</i>, Academic Press, 1987. 104 105 @author Edward A. Lee, Bart Kienhuis, Steve Neuendorffer 106 @version $Id$ 107 @since Ptolemy II 0.2 108 @Pt.ProposedRating Yellow (neuendor) 109 @Pt.AcceptedRating Yellow (neuendor) 110 @see ptolemy.data.Token 111 */ 112public class FIR extends SDFTransformer { 113 /** Construct an actor with the given container and name. 114 * @param container The container. 115 * @param name The name of this actor. 116 * @exception IllegalActionException If the actor cannot be contained 117 * by the proposed container. 118 * @exception NameDuplicationException If the container already has an 119 * actor with this name. 120 */ 121 public FIR(CompositeEntity container, String name) 122 throws NameDuplicationException, IllegalActionException { 123 super(container, name); 124 125 decimation = new Parameter(this, "decimation"); 126 decimation.setExpression("1"); 127 decimation.setTypeEquals(BaseType.INT); 128 129 decimationPhase = new Parameter(this, "decimationPhase"); 130 decimationPhase.setExpression("0"); 131 decimationPhase.setTypeEquals(BaseType.INT); 132 133 interpolation = new Parameter(this, "interpolation"); 134 interpolation.setExpression("1"); 135 interpolation.setTypeEquals(BaseType.INT); 136 137 taps = new Parameter(this, "taps"); 138 taps.setExpression("{1.0}"); 139 taps.setTypeAtLeast(ArrayType.ARRAY_BOTTOM); 140 141 input_tokenConsumptionRate.setExpression("decimation"); 142 output_tokenProductionRate.setExpression("interpolation"); 143 144 } 145 146 /////////////////////////////////////////////////////////////////// 147 //// public variables //// 148 149 /** The decimation ratio of the filter. This must contain an 150 * IntToken, and by default it has value one. 151 */ 152 public Parameter decimation; 153 154 /** The decimation phase of the filter. This must contain an 155 * IntToken, and by default it has value zero. 156 */ 157 public Parameter decimationPhase; 158 159 /** The interpolation ratio of the filter. This must contain an 160 * IntToken, and by default it has value one. 161 */ 162 public Parameter interpolation; 163 164 /** The taps of the filter. This has a type of ArrayToken. 165 * By default, it contains an array with a single integer one, 166 * meaning that the output of the filter is the same as the input. 167 */ 168 public Parameter taps; 169 170 /////////////////////////////////////////////////////////////////// 171 //// public methods //// 172 173 /** Set a flag that causes recalculation of various local variables 174 * that are used in execution on the next invocation of fire(). 175 * @param attribute The attribute that changed. 176 * @exception IllegalActionException If the attribute contains 177 * an invalid value or if the super method throws it. 178 */ 179 @Override 180 public void attributeChanged(Attribute attribute) 181 throws IllegalActionException { 182 if (attribute == interpolation) { 183 IntToken token = (IntToken) interpolation.getToken(); 184 _interpolationValue = token.intValue(); 185 186 if (_interpolationValue <= 0) { 187 throw new IllegalActionException(this, "Invalid interpolation: " 188 + _interpolationValue + ". Must be positive."); 189 } 190 191 _reinitializeNeeded = true; 192 } else if (attribute == decimation) { 193 IntToken token = (IntToken) decimation.getToken(); 194 _decimationValue = token.intValue(); 195 196 if (_decimationValue <= 0) { 197 throw new IllegalActionException(this, "Invalid decimation: " 198 + _decimationValue + ". Must be positive."); 199 } 200 201 _reinitializeNeeded = true; 202 } else if (attribute == decimationPhase) { 203 IntToken token = (IntToken) decimationPhase.getToken(); 204 _decimationPhaseValue = token.intValue(); 205 206 if (_decimationPhaseValue < 0) { 207 throw new IllegalActionException(this, 208 "Invalid decimationPhase: " + _decimationPhaseValue 209 + ". Must be nonnegative."); 210 } 211 212 _reinitializeNeeded = true; 213 } else if (attribute == taps) { 214 _initializeTaps(); 215 } else { 216 super.attributeChanged(attribute); 217 } 218 } 219 220 /** Clone the actor into the specified workspace. This calls the 221 * base class and then resets the type constraints. 222 * @param workspace The workspace for the new object. 223 * @return A new actor. 224 * @exception CloneNotSupportedException If a derived class contains 225 * an attribute that cannot be cloned. 226 */ 227 @Override 228 public Object clone(Workspace workspace) throws CloneNotSupportedException { 229 FIR newObject = (FIR) super.clone(workspace); 230 231 // Set the type constraints. 232 newObject.taps.setTypeAtLeast(ArrayType.ARRAY_BOTTOM); 233 newObject._taps = null; 234 235 return newObject; 236 } 237 238 // FIXME: State update should occur in postfire. 239 240 /** Consume the inputs and produce the outputs of the FIR filter. 241 * @exception IllegalActionException If parameter values are invalid, 242 * or if there is no director, or if runtime type conflicts occur. 243 */ 244 @Override 245 public void fire() throws IllegalActionException { 246 super.fire(); 247 248 // Phase keeps track of which phase of the filter coefficients 249 // are used. Starting phase depends on the _decimationPhaseValue value. 250 int phase = _decimationValue - _decimationPhaseValue - 1; 251 252 // Transfer _decimationValue inputs to _data[] 253 for (int inC = 1; inC <= _decimationValue; inC++) { 254 if (--_mostRecent < 0) { 255 _mostRecent = _data.length - 1; 256 } 257 258 // Note explicit type conversion, which is required to generate 259 // code. 260 // _data[_mostRecent] = output.getType().convert(input.get(0)); 261 _data[_mostRecent] = input.get(0); 262 } 263 264 // Interpolate once for each input consumed 265 for (int inC = 1; inC <= _decimationValue; inC++) { 266 // Produce however many outputs are required 267 // for each input consumed 268 while (phase < _interpolationValue) { 269 _outToken = _zero; 270 271 // Compute the inner product. 272 for (int i = 0; i < _phaseLength; i++) { 273 int tapsIndex = i * _interpolationValue + phase; 274 275 int dataIndex = (_mostRecent + _decimationValue - inC + i) 276 % _data.length; 277 278 if (tapsIndex < _taps.length) { 279 _tapItem = _taps[tapsIndex]; 280 _dataItem = _data[dataIndex]; 281 _dataItem = _tapItem.multiply(_dataItem); 282 _outToken = _outToken.add(_dataItem); 283 } 284 285 // else assume tap is zero, so do nothing. 286 } 287 288 output.send(0, _outToken); 289 phase += _decimationValue; 290 } 291 292 phase -= _interpolationValue; 293 } 294 } 295 296 /** Perform domain-specific initialization by calling the 297 * initialize(Actor) method of the director. The director may 298 * reject the actor by throwing an exception if the actor is 299 * incompatible with the domain. 300 * Set a flag that reinitializes the data buffer at the first firing. 301 * @exception IllegalActionException If the superclass throws it. 302 */ 303 @Override 304 public void initialize() throws IllegalActionException { 305 super.initialize(); 306 307 // Must be sure to throw away the old data buffer. 308 _data = null; 309 310 // If this object was created by cloning, then the _taps 311 // variable may be null. 312 _initializeTaps(); 313 } 314 315 /** Return false if the input does not have enough tokens to fire. 316 * Otherwise, return what the superclass returns. 317 * @return False if the number of input tokens available is not at least 318 * equal to the decimation parameter. 319 * @exception IllegalActionException If the superclass throws it. 320 */ 321 @Override 322 public boolean prefire() throws IllegalActionException { 323 // If an attribute has changed since the last fire(), or if 324 // this is the first fire(), then reinitialize. 325 if (_reinitializeNeeded) { 326 _reinitialize(); 327 } 328 329 if (input.hasToken(0, _decimationValue)) { 330 return super.prefire(); 331 } 332 if (_debugging) { 333 _debug("Called prefire(), which returns false."); 334 } 335 336 return false; 337 } 338 339 /////////////////////////////////////////////////////////////////// 340 //// protected methods //// 341 342 /** Set the output to be ≥ the monotonic function of the input port type. 343 * @return A set of type constraints 344 */ 345 @Override 346 protected Set<Inequality> _customTypeConstraints() { 347 Set<Inequality> result = new HashSet<Inequality>(); 348 result.add( 349 new Inequality(new OutputTypeFunction(), output.getTypeTerm())); 350 return result; 351 } 352 353 /** Initialize the taps. 354 * @exception IllegalActionException If we can't get the token from 355 * the parameter taps. 356 * 357 */ 358 protected void _initializeTaps() throws IllegalActionException { 359 ArrayToken tapsToken = (ArrayToken) taps.getToken(); 360 _taps = tapsToken.arrayValue(); 361 362 // Get a token representing zero in the appropriate type. 363 _zero = _taps[0].zero(); 364 365 _reinitializeNeeded = true; 366 } 367 368 /** Reinitialize local variables in response to changes in attributes. 369 * @exception IllegalActionException If there is a problem reinitializing. 370 */ 371 protected void _reinitialize() throws IllegalActionException { 372 if (_decimationPhaseValue >= _decimationValue) { 373 throw new IllegalActionException(this, 374 "Invalid decimationPhase: " + _decimationPhaseValue 375 + ". Must be less than decimation: " 376 + _decimationValue + "."); 377 } 378 379 _phaseLength = _taps.length / _interpolationValue; 380 381 if (_taps.length % _interpolationValue != 0) { 382 _phaseLength++; 383 } 384 385 // Create new data array and initialize index into it. 386 // Avoid losing the data if possible. 387 // NOTE: If the filter length increases, then it is impossible 388 // to correctly initialize the delay line to contain previously 389 // seen data, because that data has not been saved. 390 int length = _phaseLength + _decimationValue; 391 392 if (_data == null) { 393 _data = new Token[length]; 394 395 for (int i = 0; i < length; i++) { 396 _data[i] = _zero; 397 } 398 399 _mostRecent = _phaseLength; 400 } else if (_data.length != length) { 401 Token[] _oldData = _data; 402 _data = new Token[length]; 403 404 for (int i = 0; i < length; i++) { 405 if (i < _oldData.length) { 406 _data[i] = _oldData[i]; 407 } else { 408 _data[i] = _zero; 409 } 410 } 411 412 _mostRecent = _phaseLength; 413 } 414 415 _reinitializeNeeded = false; 416 } 417 418 /////////////////////////////////////////////////////////////////// 419 //// protected variables //// 420 421 /** The delay line. */ 422 protected Token[] _data; 423 424 /** The index into the delay line of the most recent input. */ 425 protected int _mostRecent; 426 427 /** The phaseLength is ceiling(length/interpolation), where 428 * length is the number of taps. 429 */ 430 protected int _phaseLength; 431 432 /** Decimation value. */ 433 protected int _decimationValue = 1; 434 435 /** Interpolation value. */ 436 protected int _interpolationValue = 1; 437 438 /** DecimationPhase value. */ 439 protected int _decimationPhaseValue = 0; 440 441 /** Indicator that at least one attribute has been changed 442 * since the last initialization. 443 */ 444 protected boolean _reinitializeNeeded = true; 445 446 /** Local cache of the tap values. */ 447 protected Token[] _taps; 448 449 /** Local cache of the zero token. */ 450 protected Token _zero; 451 452 /////////////////////////////////////////////////////////////////// 453 //// private variables //// 454 // The tokens needed in FIR 455 private Token _outToken; 456 457 private Token _tapItem; 458 459 private Token _dataItem; 460 461 /////////////////////////////////////////////////////////////////// 462 //// inner classes //// 463 464 /** This class implements a monotonic function of the input port 465 * type. The result type of this actor is generally the input type, 466 * unless the input type is a FixType, in which case the output 467 * type will be a FixType with (in most cases) a different precision. 468 */ 469 private class OutputTypeFunction extends MonotonicFunction { 470 471 /////////////////////////////////////////////////////////////// 472 //// public inner methods //// 473 474 /** Return the function result. 475 * @return A Type. 476 */ 477 @Override 478 public Object getValue() { 479 Type inputType = input.getType(); 480 Type tapsElementType = BaseType.UNKNOWN; 481 Type tapsType = taps.getType(); 482 if (!tapsType.equals(BaseType.UNKNOWN)) { 483 if (tapsType instanceof ArrayType) { 484 tapsElementType = ((ArrayType) tapsType).getElementType(); 485 } else { 486 tapsElementType = BaseType.UNKNOWN; 487 } 488 } 489 Type productType = inputType.multiply(tapsElementType); 490 Type outputType = productType; 491 492 // Taps are normally initialized in attributeChanged when they change. 493 // Evaluation of parameters however is a lazy process and hence if the public 494 // parameter taps is not evaluated for some reason the private member 495 // variable _taps is not initialized which results in a crash when 496 // getValue is called. 497 // To avoid this we explicitly initialize the taps here. 498 // There is however an issue. When _initializeTaps is executed, the 499 // public parameter taps will be executed, which results in a call of 500 // attributeChanged, which will call _initializeTaps and hence the taps 501 // are now initialized twice. 502 if (_taps == null) { 503 try { 504 _initializeTaps(); 505 } catch (IllegalActionException e) { 506 throw new IllegalStateException(e); 507 } 508 } 509 510 int phaseLength = _taps.length / _interpolationValue; 511 512 if (_taps.length % _interpolationValue != 0) { 513 phaseLength++; 514 } 515 for (int i = 0; i < phaseLength; i++) { 516 outputType = outputType.add(productType); 517 } 518 return outputType; 519 } 520 521 /** Return the variables in this term. If the type of the input port 522 * is a variable, return a one element array containing the 523 * InequalityTerm of that port; otherwise, return an array of zero 524 * length. 525 * @return An array of InequalityTerm. 526 */ 527 @Override 528 public InequalityTerm[] getVariables() { 529 try { 530 InequalityTerm elementTerm = ArrayType.elementType(taps); 531 if (input.getTypeTerm().isSettable() 532 && elementTerm.isSettable()) { 533 InequalityTerm[] variable = new InequalityTerm[2]; 534 variable[0] = input.getTypeTerm(); 535 variable[1] = elementTerm; 536 return variable; 537 } else if (elementTerm.isSettable()) { 538 InequalityTerm[] variable = new InequalityTerm[1]; 539 variable[0] = elementTerm; 540 return variable; 541 } else if (input.getTypeTerm().isSettable()) { 542 InequalityTerm[] variable = new InequalityTerm[1]; 543 variable[0] = input.getTypeTerm(); 544 return variable; 545 } else { 546 return new InequalityTerm[0]; 547 } 548 } catch (IllegalActionException e) { 549 throw new InternalErrorException(e); 550 } 551 } 552 } 553}