001/* An FIR filter with a raised cosine frequency response.
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 ptolemy.data.ArrayToken;
031import ptolemy.data.BooleanToken;
032import ptolemy.data.DoubleToken;
033import ptolemy.data.IntToken;
034import ptolemy.data.expr.Parameter;
035import ptolemy.data.type.BaseType;
036import ptolemy.kernel.CompositeEntity;
037import ptolemy.kernel.util.Attribute;
038import ptolemy.kernel.util.IllegalActionException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.Settable;
041import ptolemy.math.DoubleUnaryOperation;
042import ptolemy.math.SignalProcessing;
043
044///////////////////////////////////////////////////////////////////
045//// RaisedCosine
046
047/**
048 This actor implements an FIR filter with
049 a raised cosine or square-root raised cosine frequency response.
050 The excess bandwidth is given
051 by <i>excessBW</i> and the symbol interval (in number of samples)
052 by <i>interpolation</i> (which by default is 16).
053 The length of the filter (the number of taps) is given by <i>length</i>.
054 <p>
055 For the ordinary raised cosine response,
056 the impulse response of the filter would ideally be
057 <pre>
058        sin(pi n/T)   cos(alpha pi n/T)
059 h(n) = ----------- * -----------------
060          pi n/T      1-(2 alpha n/T)<sup>2</sup>
061 </pre>
062 where <i>alpha</i> is <i>excessBW</i> and <i>T</i> is the
063 <i>interpolation</i> factor.
064 However, this pulse is centered at zero, and we can only implement causal
065 filters in the SDF domain in Ptolemy.  Hence, the impulse response is
066 actually
067 <pre>
068 g(n) = h(n - M)
069 </pre>
070 where <i>M</i> = <i>length/</i>2 if <i>length</i> is even, and <i>M
071 </i>= (<i>length+</i>1)<i>/</i>2 if <i>length</i> is odd.
072 The impulse response is simply truncated outside this range, so
073 the impulse response will generally not be symmetric if <i>length</i> is even
074 because it will have one more sample to the left than to the right of center.
075 Unless this extra sample is zero, the filter will not have linear phase
076 if <i>length</i> is even.
077 <p>
078 For the ordinary raised cosine response, the
079 distance (in number of samples) from the center
080 to the first zero crossing is given by <i>symbolInterval</i>.
081 For the square-root raised cosine response, a cascade of two identical
082 square-root raised cosine filters would be equivalent to a single
083 ordinary raised cosine filter.
084 <p>
085 The impulse response of the square-root raised cosine pulse is given by
086 <pre>
087         4 alpha(cos((1+alpha)pi n/T)+Tsin((1-alpha)pi n/T)/(4n alpha/T))
088 h(n) = -----------------------------------------------------------------
089                      pi sqrt(T)(1-(4 alpha n/T)<sup>2</sup>)
090 </pre>
091 This impulse response convolved with itself will, in principle, be equal
092 to a raised cosine pulse.  However, because of the abrupt rectangular
093 windowing of the pulse, with low excess bandwidth, this ideal is not
094 closely approximated except for very long filters.
095 <p>
096 The output sample rate is <i>interpolation</i> times the input.
097 This is set by default to 16 because in digital communication systems
098 this pulse is used for the line coding of symbols, and upsampling is necessary.
099 Typically, the value of <i>interpolation</i> is the same as that of
100 <i>symbolInterval</i>, at least when the filter is being used
101 as a transmit pulse shaper.
102 <h3>References</h3>
103 <p>[1]
104 E. A. Lee and D. G. Messerschmitt,
105 <i>Digital Communication,</i> Kluwer Academic Publishers, Boston, 1988.
106 <p>[2]
107 I. Korn, <i>Digital Communications</i>, Van Nostrand Reinhold, New York, 1985.
108
109 @author Edward A. Lee
110 @version $Id$
111 @since Ptolemy II 0.2
112 @Pt.ProposedRating Yellow (neuendor)
113 @Pt.AcceptedRating Yellow (neuendor)
114 */
115public class RaisedCosine extends FIR {
116    /** Construct an actor with the given container and name.
117     *  @param container The container.
118     *  @param name The name of this actor.
119     *  @exception IllegalActionException If the actor cannot be contained
120     *   by the proposed container.
121     *  @exception NameDuplicationException If the container already has an
122     *   actor with this name.
123     */
124    public RaisedCosine(CompositeEntity container, String name)
125            throws NameDuplicationException, IllegalActionException {
126        super(container, name);
127
128        length = new Parameter(this, "length", new IntToken(64));
129        interpolation.setToken(new IntToken(16));
130        excessBW = new Parameter(this, "excessBW", new DoubleToken(1.0));
131        root = new Parameter(this, "root", new BooleanToken(false));
132        symbolInterval = new Parameter(this, "symbolInterval",
133                new IntToken(16));
134
135        // Hide taps from UI.
136        taps.setVisibility(Settable.NONE);
137        _initialize();
138    }
139
140    ///////////////////////////////////////////////////////////////////
141    ////                         public variables                  ////
142
143    /** The excess bandwidth.  This contains a
144     *  DoubleToken, and by default it has value 1.0.
145     */
146    public Parameter excessBW;
147
148    /** The length of the pulse.  This contains an
149     *  IntToken, and by default it has value 64.
150     */
151    public Parameter length;
152
153    /** If true, use the square root of the raised cosine instead of the
154     *  raised cosine.  This contains a
155     *  BooleanToken, and by default it has value false.
156     */
157    public Parameter root;
158
159    /** The symbol interval, which is the number of samples to the first
160     *  zero crossing on each side of the main lobe.  Its value is an
161     *  IntToken, and by default it has value 16.
162     */
163    public Parameter symbolInterval;
164
165    ///////////////////////////////////////////////////////////////////
166    ////                         public methods                    ////
167
168    /** Reevaluate the filter taps if the attribute is any of the ones
169     *  defined locally, and otherwise call the superclass.
170     *  @param attribute The attribute that changed.
171     *  @exception IllegalActionException If the parameters are out of range.
172     */
173    @Override
174    public void attributeChanged(Attribute attribute)
175            throws IllegalActionException {
176        if (attribute == excessBW || attribute == length || attribute == root
177                || attribute == symbolInterval) {
178            _initialize();
179        } else {
180            super.attributeChanged(attribute);
181        }
182    }
183
184    // Initialize the state of the actor based on the current state of the
185    // parameters.
186    private void _initialize() throws IllegalActionException {
187        double excessBWValue = ((DoubleToken) excessBW.getToken())
188                .doubleValue();
189        int symbolIntervalValue = ((IntToken) symbolInterval.getToken())
190                .intValue();
191        int lengthValue = ((IntToken) length.getToken()).intValue();
192        boolean sqrt = ((BooleanToken) root.getToken()).booleanValue();
193
194        if (excessBWValue < 0.0) {
195            throw new IllegalActionException(this,
196                    "Excess bandwidth was " + excessBWValue
197                            + " which is not greater than or equal to zero.");
198        }
199
200        if (lengthValue <= 0) {
201            throw new IllegalActionException(this, "Length was " + lengthValue
202                    + " which is not greater than zero.");
203        }
204
205        double center = lengthValue * 0.5;
206
207        DoubleUnaryOperation raisedCosineSampleGenerator = sqrt
208                ? (DoubleUnaryOperation) new SignalProcessing.SqrtRaisedCosineSampleGenerator(
209                        symbolIntervalValue, excessBWValue)
210                : (DoubleUnaryOperation) new SignalProcessing.RaisedCosineSampleGenerator(
211                        symbolIntervalValue, excessBWValue);
212
213        double[] tapsArray = SignalProcessing.sampleWave(lengthValue, -center,
214                1.0, raisedCosineSampleGenerator);
215        DoubleToken[] tapsArrayToken = new DoubleToken[tapsArray.length];
216
217        for (int i = 0; i < tapsArray.length; i++) {
218            tapsArrayToken[i] = new DoubleToken(tapsArray[i]);
219        }
220
221        taps.setToken(new ArrayToken(BaseType.DOUBLE, tapsArrayToken));
222    }
223}