001/* Chop an input sequence and construct from it a new output sequence.
002
003 Copyright (c) 1997-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 ptolemy.data.BooleanToken;
031import ptolemy.data.IntToken;
032import ptolemy.data.Token;
033import ptolemy.data.expr.Parameter;
034import ptolemy.data.type.BaseType;
035import ptolemy.kernel.CompositeEntity;
036import ptolemy.kernel.util.Attribute;
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.NameDuplicationException;
039
040///////////////////////////////////////////////////////////////////
041//// Chop
042
043/**
044 This actor reads a sequence of input tokens of any type, and writes a
045 sequence of tokens constructed from the input sequence (possibly
046 supplemented with zeros).  The number of input tokens consumed
047 is given by <i>numberToRead</i>, and the number of output tokens
048 produced is given by <i>numberToWrite</i>.
049 The <i>offset</i> parameter (default 0) specifies where in the output
050 block the first (oldest) input that is read should go.
051 If <i>offset</i> is positive and <i>usePastInputs</i> is true,
052 then the first few outputs will come from values read in previous iterations.
053 <p>
054 A simple use of this actor is to pad a block of inputs with zeros.
055 Set <i>offset</i> to zero and use <i>numberToWrite &gt; numberToRead</i>.
056 <a name="zeroPadding"></a>
057 <a name="padding"></a></p>
058 <p>
059 Another simple use is to obtain overlapping windows from
060 an input stream.
061 Set <i>usePastInputs</i> to true, use <i>numberToWrite &gt; numberToRead</i>,
062 and set <i>offset</i> equal to <i>numberToWrite - numberToRead</i>.
063 <a name="overlappingWindows"></a>
064 <a name="windowing"></a></p>
065 <p>
066 The general operation is illustrated with the following examples.
067 If <i>offset</i> is positive,
068 there two possible scenarios, illustrated by the following examples:</p>
069 <pre>
070     iiiiii                  numberToRead = 6
071      \    \                 offset = 2
072     ppiiiiii00              numberToWrite = 10
073
074     iiiiii                  numberToRead = 6
075      \ \  \                 offset = 2
076     ppiii                   numberToWrite = 5
077 </pre>
078 <p>
079 The symbol "i" refers to any input token. The leftmost symbol
080 refers to the oldest input token of the ones consumed in a given
081 firing. The symbol "p" refers to a token that is either zero
082 (if <i>usePastInputs</i> is false) or is equal to a previously
083 consumed input token (if <i>usePastInputs</i> is true).
084 The symbol "0" refers to a zero-valued token.
085 In the first of the above examples, the entire input block is
086 copied to the output, and then filled out with zeros.
087 In the second example, only a portion of the input block fits.
088 The remaining input tokens are discarded, although they might
089 be used in subsequent firings if <i>usePastInputs</i> is true.</p>
090 <p>
091 When the <i>offset</i> is negative, this indicates that the
092 first <i>offset</i> input tokens that are read should be
093 discarded.  The corresponding scenarios are shown below:</p>
094 <pre>
095     iiiiii                  numberToRead = 6
096    / /  /                   offset = -2
097     iiii000000              numberToWrite = 10
098
099     iiiiii                  numberToRead = 6
100    / / //                   offset = -2
101     iii                     numberToWrite = 3
102 </pre>
103 <p>
104 In the first of these examples, the first two input tokens are
105 discarded.  In the second example, the first two and the last input
106 token are discarded.</p>
107 <p>
108 The zero-valued tokens are constructed using the zero() method of
109 the first input token that is read in the firing.  This returns
110 a zero-valued token with the same type as the input.</p>
111
112 @author Edward A. Lee
113 @version $Id$
114 @since Ptolemy II 1.0
115 @Pt.ProposedRating Green (eal)
116 @Pt.AcceptedRating Yellow (neuendor)
117 */
118public class Chop extends SDFTransformer {
119    /** Construct an actor in the specified container with the specified
120     *  name.
121     *  @param container The container.
122     *  @param name The name.
123     *  @exception IllegalActionException If the actor cannot be contained
124     *   by the proposed container.
125     *  @exception NameDuplicationException If the name coincides with
126     *   an actor already in the container.
127     */
128    public Chop(CompositeEntity container, String name)
129            throws IllegalActionException, NameDuplicationException {
130        super(container, name);
131
132        numberToRead = new Parameter(this, "numberToRead");
133        numberToRead.setExpression("128");
134        numberToRead.setTypeEquals(BaseType.INT);
135
136        numberToWrite = new Parameter(this, "numberToWrite");
137        numberToWrite.setExpression("64");
138        numberToWrite.setTypeEquals(BaseType.INT);
139
140        offset = new Parameter(this, "offset");
141        offset.setExpression("0");
142        offset.setTypeEquals(BaseType.INT);
143
144        usePastInputs = new Parameter(this, "usePastInputs");
145        usePastInputs.setExpression("true");
146        usePastInputs.setTypeEquals(BaseType.BOOLEAN);
147
148        input_tokenConsumptionRate.setExpression("numberToRead");
149        output_tokenProductionRate.setExpression("numberToWrite");
150    }
151
152    ///////////////////////////////////////////////////////////////////
153    ////                     ports and parameters                  ////
154
155    /** The number of input tokens to read.
156     *  This is an integer, with default 128.
157     */
158    public Parameter numberToRead;
159
160    /** The number of tokens to write to the output.
161     *  This is an integer, with default 64.
162     */
163    public Parameter numberToWrite;
164
165    /** Start of output block relative to start of input block.
166     *  This is an integer, with default 0.
167     */
168    public Parameter offset;
169
170    /**  If offset is greater than 0, specify whether to use previously
171     *   read inputs (otherwise use zeros).
172     *  This is a boolean, with default true.
173     */
174    public Parameter usePastInputs;
175
176    ///////////////////////////////////////////////////////////////////
177    ////                         public methods                    ////
178
179    /** Check the validity of parameter values and using the new
180     *  values, recompute the size of the internal buffers.
181     *  @param attribute The attribute that has changed.
182     *  @exception IllegalActionException If the parameters are out of range.
183     */
184    @Override
185    public void attributeChanged(Attribute attribute)
186            throws IllegalActionException {
187        // Note: it is important that none of these sections depend on
188        // each other.
189        if (attribute == numberToRead) {
190            _numberToRead = ((IntToken) numberToRead.getToken()).intValue();
191
192            if (_numberToRead <= 0) {
193                throw new IllegalActionException(this,
194                        "Invalid numberToRead: " + _numberToRead);
195            }
196        } else if (attribute == numberToWrite) {
197            _numberToWrite = ((IntToken) numberToWrite.getToken()).intValue();
198
199            if (_numberToWrite <= 0) {
200                throw new IllegalActionException(this,
201                        "Invalid numberToWrite: " + _numberToRead);
202            }
203
204            _buffer = new Token[_numberToWrite];
205        } else if (attribute == offset) {
206            _offsetValue = ((IntToken) offset.getToken()).intValue();
207        } else if (attribute == usePastInputs) {
208            _usePast = ((BooleanToken) usePastInputs.getToken()).booleanValue();
209        }
210
211        if (attribute == offset || attribute == usePastInputs) {
212            if (_offsetValue > 0) {
213                _pastBuffer = new Token[_offsetValue];
214                _pastNeedsInitializing = true;
215            }
216        }
217
218        if (attribute == numberToRead || attribute == numberToWrite
219                || attribute == offset || attribute == usePastInputs) {
220            // NOTE: The following computation gets repeated when each of
221            // these gets set, but it's a simple calculation, so we live
222            // with it.
223            // The variables _highLimit and _lowLimit indicate the range of
224            // output indexes that come directly from the input block
225            // that is read.
226            _highLimit = _offsetValue + _numberToRead - 1;
227
228            if (_highLimit >= _numberToWrite) {
229                _highLimit = _numberToWrite - 1;
230            }
231
232            if (_offsetValue >= 0) {
233                _lowLimit = _offsetValue;
234                _inputIndex = 0;
235            } else {
236                _lowLimit = 0;
237                _inputIndex = -_offsetValue;
238            }
239        } else {
240            super.attributeChanged(attribute);
241        }
242    }
243
244    /** Consume the specified number of input tokens, and produce
245     *  the specified number of output tokens.
246     *  @exception IllegalActionException If there is no director.
247     */
248    @Override
249    public void fire() throws IllegalActionException {
250        super.fire();
251
252        int inputIndex = _inputIndex;
253        int pastBufferIndex = 0;
254        Token[] inBuffer = input.get(0, _numberToRead);
255        Token zero = inBuffer[0].zero();
256
257        for (int i = 0; i < _numberToWrite; i++) {
258            if (i > _highLimit) {
259                _buffer[i] = zero;
260            } else if (i < _lowLimit) {
261                if (_usePast) {
262                    if (_pastNeedsInitializing) {
263                        // Fill past buffer with zeros.
264                        for (int j = 0; j < _pastBuffer.length; j++) {
265                            _pastBuffer[j] = zero;
266                        }
267
268                        _pastNeedsInitializing = false;
269                    }
270
271                    _buffer[i] = _pastBuffer[pastBufferIndex++];
272                } else {
273                    _buffer[i] = zero;
274                }
275            } else {
276                // FIXME: This will access past samples...
277                _buffer[i] = inBuffer[inputIndex];
278                inputIndex++;
279            }
280        }
281
282        if (_usePast && _offsetValue > 0) {
283            // Copy input buffer into past buffer.  Have to be careful
284            // here because the buffer might be longer than the
285            // input window.
286            int startCopy = _numberToRead - _offsetValue;
287            int length = _pastBuffer.length;
288            int destination = 0;
289
290            if (startCopy < 0) {
291                // Shift older data.
292                destination = _pastBuffer.length - _numberToRead;
293                System.arraycopy(_pastBuffer, _numberToRead, _pastBuffer, 0,
294                        destination);
295                startCopy = 0;
296                length = _numberToRead;
297            }
298
299            System.arraycopy(inBuffer, startCopy, _pastBuffer, destination,
300                    length);
301        }
302
303        output.send(0, _buffer, _numberToWrite);
304    }
305
306    /** Override the base class to ensure that the past buffer
307     *  gets initialized.
308     *  @exception IllegalActionException If the superclass throws it.
309     */
310    @Override
311    public void initialize() throws IllegalActionException {
312        super.initialize();
313        _pastNeedsInitializing = true;
314    }
315
316    ///////////////////////////////////////////////////////////////////
317    ////                         private members                   ////
318    private int _highLimit;
319
320    ///////////////////////////////////////////////////////////////////
321    ////                         private members                   ////
322    private int _inputIndex;
323
324    ///////////////////////////////////////////////////////////////////
325    ////                         private members                   ////
326    private int _lowLimit;
327
328    private int _numberToRead;
329
330    private int _numberToWrite;
331
332    private int _offsetValue;
333
334    private Token[] _buffer;
335
336    private Token[] _pastBuffer;
337
338    private boolean _usePast;
339
340    private boolean _pastNeedsInitializing;
341}