diff --git a/src/channel/metadata/MutableMetadata.java b/src/channel/metadata/MutableMetadata.java index 1769b85b..0fad83ea 100644 --- a/src/channel/metadata/MutableMetadata.java +++ b/src/channel/metadata/MutableMetadata.java @@ -20,11 +20,15 @@ package channel.metadata; import channel.state.State; import module.decode.DecoderType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sample.Broadcaster; import sample.Listener; public class MutableMetadata extends Metadata implements Listener { + private final static Logger mLog = LoggerFactory.getLogger(MutableMetadata.class); + private Broadcaster mMetadataChangeEventBroadcaster = new Broadcaster<>(); /** @@ -138,7 +142,7 @@ public class MutableMetadata extends Metadata implements Listener(Attribute.CHANNEL_STATE, mState)); } @@ -475,6 +473,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder */ public void configureAsTrafficChannel(TrafficChannelManager manager, CallEvent callEvent) { + mLog.debug("Configuring for traffic channel: " + callEvent.toCSV()); mTrafficChannelEndListener = manager; mTrafficChannelCallEvent = callEvent; @@ -487,30 +486,32 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY_LABEL, channel)); } + long frequency = mTrafficChannelCallEvent.getFrequency(); + + if(frequency > 0) + { + mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY, frequency)); + } + mMutableMetadata.receive(new AttributeChangeRequest(Attribute.PRIMARY_DECODER_TYPE, mTrafficChannelCallEvent.getDecoderType())); - String details = mTrafficChannelCallEvent.getDetails(); + mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_CONFIGURATION_LABEL_1, "TRAFFIC")); String from = mTrafficChannelCallEvent.getFromID(); if(from != null) { - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_FROM, from)); + mMutableMetadata.receive(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_FROM, from, + mTrafficChannelCallEvent.getFromIDAlias())); } String to = mTrafficChannelCallEvent.getToID(); if(to != null) { - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_TO, to)); - } - - long frequency = mTrafficChannelCallEvent.getFrequency(); - - if(frequency > 0) - { - mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_FREQUENCY, frequency)); + mMutableMetadata.receive(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_TO, to, + mTrafficChannelCallEvent.getToIDAlias())); } /* Rebroadcast the allocation event so that the internal decoder states diff --git a/src/controller/channel/ChannelProcessingManager.java b/src/controller/channel/ChannelProcessingManager.java index d4b16f63..ecd76a6d 100644 --- a/src/controller/channel/ChannelProcessingManager.java +++ b/src/controller/channel/ChannelProcessingManager.java @@ -194,8 +194,7 @@ public class ChannelProcessingManager implements ChannelEventListener if(processingChain == null) { - processingChain = new ProcessingChain(channel.getName(), - channel.getChannelType()); + processingChain = new ProcessingChain(channel.getName(), channel.getChannelType()); /* Register global listeners */ for(Listener listener : mAudioPacketListeners) diff --git a/src/controller/channel/map/ChannelMapModel.java b/src/controller/channel/map/ChannelMapModel.java index e0b486d4..5b0cbce1 100644 --- a/src/controller/channel/map/ChannelMapModel.java +++ b/src/controller/channel/map/ChannelMapModel.java @@ -1,155 +1,157 @@ package controller.channel.map; +import controller.channel.map.ChannelMapEvent.Event; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sample.Broadcaster; +import sample.Listener; + +import javax.swing.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import javax.swing.AbstractListModel; - -import controller.channel.map.ChannelMapEvent.Event; -import sample.Broadcaster; -import sample.Listener; - public class ChannelMapModel extends AbstractListModel { - private static final long serialVersionUID = 1L; - - private List mChannelMaps = new ArrayList<>(); - private Broadcaster mEventBroadcaster = new Broadcaster<>(); + private static final long serialVersionUID = 1L; + private final static Logger mLog = LoggerFactory.getLogger(ChannelMapModel.class); - public ChannelMapModel() - { - } + private List mChannelMaps = new ArrayList<>(); + private Broadcaster mEventBroadcaster = new Broadcaster<>(); - /** - * Returns an unmodifiable list of channel maps currently in the model - */ - public List getChannelMaps() - { - return Collections.unmodifiableList( mChannelMaps ); - } + public ChannelMapModel() + { + } - /** - * Returns the channel map with a matching name or null - */ - public ChannelMap getChannelMap( String name ) - { - for( ChannelMap channelMap: mChannelMaps ) - { - if( channelMap.getName().equalsIgnoreCase( name ) ) - { - return channelMap; - } - } - - return null; - } + /** + * Returns an unmodifiable list of channel maps currently in the model + */ + public List getChannelMaps() + { + return Collections.unmodifiableList(mChannelMaps); + } - /** - * Adds a listener to receive notifications when channel map updates occur - */ - public void addListener( Listener listener ) - { - mEventBroadcaster.addListener( listener ); - } + /** + * Returns the channel map with a matching name or null + */ + public ChannelMap getChannelMap(String name) + { + for(ChannelMap channelMap : mChannelMaps) + { + if(channelMap.getName().equalsIgnoreCase(name)) + { + return channelMap; + } + } - /** - * Removes the channel map update notification listener - */ - public void removeListener( Listener listener ) - { - mEventBroadcaster.removeListener( listener ); - } + return null; + } - /** - * Broadcasts a channel map change. - * - * Note: use the add/remove methods to add or remove channel maps from this - * model. When using those methods, an add or delete event will automatically - * be generated. - */ - public void broadcast( ChannelMapEvent event ) - { - if( event.getEvent() == ChannelMapEvent.Event.CHANGE || - event.getEvent() == ChannelMapEvent.Event.RENAME ) - { - int index = mChannelMaps.indexOf( event.getChannelMap() ); - - if( index >= 0 ) - { - fireContentsChanged( this, index, index ); - } - } - - mEventBroadcaster.broadcast( event ); - } - - /** - * Adds a list of channel maps to this model - */ - public void addChannelMaps( List channelMaps ) - { - for( ChannelMap channelMap: channelMaps ) - { - addChannelMap( channelMap ); - } - } + /** + * Adds a listener to receive notifications when channel map updates occur + */ + public void addListener(Listener listener) + { + mEventBroadcaster.addListener(listener); + } - /** - * Adds the channel map to this model - */ - public void addChannelMap( ChannelMap channelMap ) - { - if( !mChannelMaps.contains( channelMap ) ) - { - mChannelMaps.add( channelMap ); - - int index = mChannelMaps.indexOf( channelMap ); - - fireIntervalAdded( this, index, index ); - - broadcast( new ChannelMapEvent( channelMap, Event.ADD ) ); - } - - } + /** + * Removes the channel map update notification listener + */ + public void removeListener(Listener listener) + { + mEventBroadcaster.removeListener(listener); + } - /** - * Removes the channel map from this model - */ - public void removeChannelMap( ChannelMap channelMap ) - { - if( mChannelMaps.contains( channelMap ) ) - { - int index = mChannelMaps.indexOf( channelMap ); - - mChannelMaps.remove( channelMap ); - - fireIntervalRemoved( this, index, index ); + /** + * Broadcasts a channel map change. + * + * Note: use the add/remove methods to add or remove channel maps from this + * model. When using those methods, an add or delete event will automatically + * be generated. + */ + public void broadcast(ChannelMapEvent event) + { + if(event.getEvent() == ChannelMapEvent.Event.CHANGE || + event.getEvent() == ChannelMapEvent.Event.RENAME) + { + int index = mChannelMaps.indexOf(event.getChannelMap()); - broadcast( new ChannelMapEvent( channelMap, Event.DELETE ) ); - } - } + if(index >= 0) + { + fireContentsChanged(this, index, index); + } + } - /** - * Size/Number of channel maps in this model - */ - @Override - public int getSize() - { - return mChannelMaps.size(); - } + mEventBroadcaster.broadcast(event); + } - /** - * Returns the channel map at the specified index - */ - @Override - public ChannelMap getElementAt( int index ) - { - if( index <= mChannelMaps.size() ) - { - return mChannelMaps.get( index ); - } - - return null; - } + /** + * Adds a list of channel maps to this model + */ + public void addChannelMaps(List channelMaps) + { + for(ChannelMap channelMap : channelMaps) + { + addChannelMap(channelMap); + } + } + + /** + * Adds the channel map to this model + */ + public void addChannelMap(ChannelMap channelMap) + { + if(!mChannelMaps.contains(channelMap)) + { + mChannelMaps.add(channelMap); + + int index = mChannelMaps.indexOf(channelMap); + + fireIntervalAdded(this, index, index); + + broadcast(new ChannelMapEvent(channelMap, Event.ADD)); + } + + } + + /** + * Removes the channel map from this model + */ + public void removeChannelMap(ChannelMap channelMap) + { + if(mChannelMaps.contains(channelMap)) + { + int index = mChannelMaps.indexOf(channelMap); + + mChannelMaps.remove(channelMap); + + fireIntervalRemoved(this, index, index); + + broadcast(new ChannelMapEvent(channelMap, Event.DELETE)); + } + } + + /** + * Size/Number of channel maps in this model + */ + @Override + public int getSize() + { + return mChannelMaps.size(); + } + + /** + * Returns the channel map at the specified index + */ + @Override + public ChannelMap getElementAt(int index) + { + if(index <= mChannelMaps.size()) + { + return mChannelMaps.get(index); + } + + return null; + } } diff --git a/src/dsp/filter/FilterFactory.java b/src/dsp/filter/FilterFactory.java index 8f6b0065..5ca5a078 100644 --- a/src/dsp/filter/FilterFactory.java +++ b/src/dsp/filter/FilterFactory.java @@ -11,729 +11,751 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ package dsp.filter; -import java.util.Arrays; -import java.util.Set; -import java.util.TreeSet; - +import dsp.filter.Window.WindowType; +import dsp.filter.cic.ComplexPrimeCICDecimate; +import dsp.filter.design.FilterDesignException; +import dsp.filter.fir.FIRFilterSpecification; +import dsp.filter.fir.remez.RemezFIRFilterDesigner; import org.jtransforms.fft.FloatFFT_1D; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import dsp.filter.Window.WindowType; -import dsp.filter.cic.ComplexPrimeCICDecimate; +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; public class FilterFactory { - private final static Logger mLog = - LoggerFactory.getLogger( FilterFactory.class ); + private final static Logger mLog = + LoggerFactory.getLogger(FilterFactory.class); - /** - * Generates coefficients for a unity-gain, windowed low-pass filter - * @param sampleRate - hertz - * @param frequency - cutoff in hertz - * @param length - filter length - * @param window - to apply against the coefficients - * @return - */ - public static float[] getSinc( int sampleRate, - long frequency, - int length, - WindowType window ) - { - //Ensure we have an odd length - assert( length % 2 == 0 ); + /** + * Generates coefficients for a unity-gain, windowed low-pass filter + * + * @param sampleRate - hertz + * @param frequency - cutoff in hertz + * @param length - filter length + * @param window - to apply against the coefficients + * @return + */ + public static float[] getSinc(int sampleRate, + long frequency, + int length, + WindowType window) + { + //Ensure we have an odd length + assert (length % 2 == 0); - //Get unity response array (one element longer to align with IDFT size) - float[] frequencyResponse = getUnityResponseArray( sampleRate, frequency, length + 1 ); + //Get unity response array (one element longer to align with IDFT size) + float[] frequencyResponse = getUnityResponseArray(sampleRate, frequency, length + 1); - //Apply Inverse DFT against frequency response unity values, leaving the - //IDFT bin results in the frequency response array - FloatFFT_1D idft = new FloatFFT_1D( length + 1 ); - idft.realInverseFull( frequencyResponse, false ); + //Apply Inverse DFT against frequency response unity values, leaving the + //IDFT bin results in the frequency response array + FloatFFT_1D idft = new FloatFFT_1D(length + 1); + idft.realInverseFull(frequencyResponse, false); - //Transfer the IDFT results to the return array - float[] coefficients = new float[ length ]; - int middleCoefficient = (int)( length / 2 ); + //Transfer the IDFT results to the return array + float[] coefficients = new float[length]; + int middleCoefficient = (int) (length / 2); - //Bin 0 of the idft is our center coefficient - coefficients[ middleCoefficient ] = frequencyResponse[ 0 ]; + //Bin 0 of the idft is our center coefficient + coefficients[middleCoefficient] = frequencyResponse[0]; - //The remaining idft bins from 1 to (middle - 1) are the mirror image - //coefficients - for( int x = 1; x < middleCoefficient; x++ ) - { - coefficients[ middleCoefficient + x ] = frequencyResponse[ 2 * x ]; - coefficients[ middleCoefficient - x ] = frequencyResponse[ 2 * x ]; - } + //The remaining idft bins from 1 to (middle - 1) are the mirror image + //coefficients + for(int x = 1; x < middleCoefficient; x++) + { + coefficients[middleCoefficient + x] = frequencyResponse[2 * x]; + coefficients[middleCoefficient - x] = frequencyResponse[2 * x]; + } - //Apply the window against the coefficients - coefficients = Window.apply( window, coefficients ); + //Apply the window against the coefficients + coefficients = Window.apply(window, coefficients); - //Normalize to unity (1) gain - coefficients = normalize( coefficients ); - - return coefficients; - } - - /** - * Normalizes all filter coefficients to achieve unity (1) gain, by ensuring - * that the sum of the absolute value of all coefficients adds up to 1. - * @param coefficients - * @return - */ - private static float[] normalize( float[] coefficients ) - { - float accumulator = 0; - - for( int x = 0; x < coefficients.length; x++ ) - { - accumulator += Math.abs( coefficients[ x ] ); - } - - for( int x = 0; x < coefficients.length; x++ ) - { - coefficients[ x ] = coefficients[ x ] / accumulator; - } + //Normalize to unity (1) gain + coefficients = normalize(coefficients); - return coefficients; - } - - /** - * Constructs an array of unity (1) response values representing the - * desired (pre-windowing) frequency response, used by the sync function - * - * Returns an array twice the length, filled with unity (1) response values - * in the desired pass-band with the positive frequency response starting at - * the lower end of the array, and the negative frequency response at the - * higher end of the first half of the array. The remaining zero-valued - * indexes in the second half will store the results of the JTransforms - * inverse DFT operation - * - * @param sampleRate - * @param frequency - * @param length - * @return - */ - public static float[] getUnityResponseArray( int sampleRate, - long frequency, - int length ) - { - float[] unityArray = new float[ length * 2 ]; + return coefficients; + } - int binCount = (int)( ( Math.round( - (float)frequency / (float)sampleRate * (float)length ) ) ); + /** + * Normalizes all filter coefficients to achieve unity (1) gain, by ensuring + * that the sum of the absolute value of all coefficients adds up to 1. + * + * @param coefficients + * @return + */ + private static float[] normalize(float[] coefficients) + { + float accumulator = 0; - if( length % 2 == 0 ) //even length - { - for( int x = 0; x < binCount; x++ ) - { - unityArray[ x ] = 1.0f; - unityArray[ length - 1 - x ] = 1.0f; - } - } - else //odd length - { - unityArray[ 0 ] = 1.0f; + for(int x = 0; x < coefficients.length; x++) + { + accumulator += Math.abs(coefficients[x]); + } - for( int x = 1; x <= binCount; x++ ) - { - unityArray[ x ] = 1.0f; - unityArray[ length - x ] = 1.0f; - } - } + for(int x = 0; x < coefficients.length; x++) + { + coefficients[x] = coefficients[x] / accumulator; + } - return unityArray; - } - - public static float[] getSine( float sampleRate, float frequency, int length ) - { - float[] retVal = new float[ length ]; - - float radianFrequency = (float)( 2.0d * Math.PI * ( frequency / sampleRate ) ); + return coefficients; + } - int middle = (int)( length / 2 ); + /** + * Constructs an array of unity (1) response values representing the + * desired (pre-windowing) frequency response, used by the sync function + * + * Returns an array twice the length, filled with unity (1) response values + * in the desired pass-band with the positive frequency response starting at + * the lower end of the array, and the negative frequency response at the + * higher end of the first half of the array. The remaining zero-valued + * indexes in the second half will store the results of the JTransforms + * inverse DFT operation + * + * @param sampleRate + * @param frequency + * @param length + * @return + */ + public static float[] getUnityResponseArray(int sampleRate, + long frequency, + int length) + { + float[] unityArray = new float[length * 2]; - for( int x = 0; x < middle; x++ ) - { - float val = (float)Math.sin( radianFrequency * x ); - - retVal[ middle + x ] = val; - retVal[ middle - x ] = -val; - } - - return retVal; - } - - /** - * Applies a repeating sequence of 1, -1 to the coefficients to invert - * the frequency response of the filter. Used in converting a low-pass - * filter to a high-pass filter. - * - * Inverts the sign of all odd index coefficients ( 1, 3, 5, etc.) - * returning: - * Index 0: same - * Index 1: inverted - * Index 2: same - * ... - * Index length - 1: same - */ - public static float[] invert( float[] coefficients ) - { - for( int x = 1; x < coefficients.length; x += 2 ) - { - coefficients[ x ] = -coefficients[ x ]; - } - - return coefficients; - } - - /** - * Generates filter coefficients for a unity-gain, odd-length, windowed, - * low pass filter with passband from 0-hertz to the cutoff frequency. - * - * @param sampleRate - hertz - * @param cutoff - frequency in hertz - * @param filterLength - odd filter length - * @param windowType - window to apply against the generated coefficients - * @return - */ - public static float[] getLowPass( int sampleRate, - long cutoff, - int filterLength, - WindowType windowType ) - { - if( filterLength % 2 == 0 ) //even length - { - float[] values = getSinc( sampleRate, cutoff, filterLength + 2, windowType ); - - //throw away the 0 index and the last index - return Arrays.copyOfRange( values, 1, values.length - 2 ); - } - else - { - float[] values = getSinc( sampleRate, cutoff, filterLength + 1, windowType ); - - //throw away the 0 index - return Arrays.copyOfRange( values, 1, values.length ); - } - } - - /** - * Creates a low-pass filter with ~ 0.1 db ripple in the pass band - * - * Note: stop frequency - pass frequency defines the transition band. - */ - /** - * Creates a low-pass filter with ~0.1 db ripple in the pass band. The - * transition region is defined by the stop frequency minus the pass - * frequency. - * - * Requires: - * - passFrequency < stopFrequency - * - stopFrequency <= sampleRate/2 - */ - public static float[] getLowPass( int sampleRate, - int passFrequency, - int stopFrequency, - int attenuation, - WindowType windowType, - boolean forceOddLength ) - { - if( stopFrequency < passFrequency || stopFrequency > ( sampleRate / 2 ) ) - { - throw new IllegalArgumentException( "FilterFactory - low pass " - + "filter pass frequency [" + passFrequency + "] must be " - + "less than the stop frequency [" + stopFrequency + "] " - + "and must be less than half [" + (int)( sampleRate / 2 ) + - "] of the sample rate [" + sampleRate + "]" ); - } - - int tapCount = getTapCount( sampleRate, - passFrequency, - stopFrequency, - attenuation ); - - if( forceOddLength ) - { - if( tapCount % 2 == 0 ) - { - tapCount--; - } - } - - return getLowPass( sampleRate, passFrequency, tapCount, windowType ); - } + int binCount = (int) ((Math.round( + (float) frequency / (float) sampleRate * (float) length))); - /** - * Generates filter coefficients for a unity-gain, odd-length, windowed, - * high pass filter with passband from cutoff frequency to half the sample - * rate. - * - * @param sampleRate - hertz - * @param cutoff - frequency in hertz - * @param filterLength - odd filter length - * @param windowType - window to apply against the generated coefficients - * @return - */ - public static float[] getHighPass( int sampleRate, - long cutoff, - int filterLength, - WindowType windowType ) - { - //Convert the high frequency cutoff to its low frequency cutoff - //equivalent, so that when we generate the low pass filter, prior to - //inversion, its at the correct frequency - long convertedCutoff = sampleRate / 2 - cutoff; - - return invert( getSinc( sampleRate, - convertedCutoff, - filterLength, - windowType ) ); - } - - public static float[] getHighPass( int sampleRate, - long stopFrequency, - long passFrequency, - int attenuation, - WindowType windowType, - boolean forceOddLength ) - { - /* reverse the stop and pass frequency to get the low pass variant */ - int tapCount = getTapCount( sampleRate, - stopFrequency, - passFrequency, - attenuation ); + if(length % 2 == 0) //even length + { + for(int x = 0; x < binCount; x++) + { + unityArray[x] = 1.0f; + unityArray[length - 1 - x] = 1.0f; + } + } + else //odd length + { + unityArray[0] = 1.0f; - if( forceOddLength ) - { - if( tapCount % 2 == 0 ) - { - tapCount--; - } - } + for(int x = 1; x <= binCount; x++) + { + unityArray[x] = 1.0f; + unityArray[length - x] = 1.0f; + } + } - return invert( getLowPass( sampleRate, stopFrequency, tapCount, windowType ) ); - } - - /** - * Utility to log the arrays of doubles with line breaks - */ - public static String arrayToString( float[] array, boolean breaks ) - { - StringBuilder sb = new StringBuilder(); - for( int x = 0; x < array.length; x++ ) - { - sb.append( x + ": " + array[ x ] ); + return unityArray; + } - if( breaks ) - { - sb.append( "\n" ); - } - else - { - if( x % 8 == 7 ) - { - sb.append( "\n" ); - } - else - { - sb.append( "\t" ); - } - } - } - - return sb.toString(); - } + public static float[] getSine(float sampleRate, float frequency, int length) + { + float[] retVal = new float[length]; - /** - * Determines the number of fir filter taps required to produce the - * specified frequency response with passband ripple near .1dB. - * - * Implements the algorithm from Understanding Digital Signal Processing, 3e - * , Lyons, section 5.10.5. - * - * @param sampleRate in hertz - * @param pass pass frequency in hertz - * @param stop stop frequency in hertz - * @param attenuation in dB - * @return - */ - public static int getTapCount( int sampleRate, - long pass, - long stop, - int attenuation ) - { - double frequency = ( (double)stop - (double)pass ) / (double)sampleRate; - - return (int)( Math.round( (double)attenuation / ( 22.0d * frequency ) ) ); - } - - public static ComplexPrimeCICDecimate getDecimationFilter( int sampleRate, - int decimatedRate, int order, int passFrequency, int attenuation, - WindowType windowType ) - { - int decimationRate = (int)( sampleRate / decimatedRate ); - - return new ComplexPrimeCICDecimate( decimationRate, order, - passFrequency, attenuation, windowType ); - } - - /** - * Determines decimation rate(s) for a polyphase decimation filter. - * - * @param sampleRate - starting sample rate - * @param decimatedRate - final (decimated) output rate - * @return - set of integer decimation rates for a single or multi-stage - * polyphase filter decimation chain - * @throws - AssertionException if sample rate is not a multiple of 48 kHz - */ - public static int[] getPolyphaseDecimationRates( int sampleRate, - int decimatedRate, - long passFrequency, - long stopFrequency ) - { - int[] rates; + float radianFrequency = (float) (2.0d * Math.PI * (frequency / sampleRate)); - if( sampleRate % decimatedRate != 0 ) - { - throw new IllegalArgumentException( "Decimated rate must be an " - + "integer multiple of sample rate" ); - } - - int decimation = (int)( sampleRate / decimatedRate ); + int middle = (int) (length / 2); + + for(int x = 0; x < middle; x++) + { + float val = (float) Math.sin(radianFrequency * x); + + retVal[middle + x] = val; + retVal[middle - x] = -val; + } + + return retVal; + } + + /** + * Applies a repeating sequence of 1, -1 to the coefficients to invert + * the frequency response of the filter. Used in converting a low-pass + * filter to a high-pass filter. + * + * Inverts the sign of all odd index coefficients ( 1, 3, 5, etc.) + * returning: + * Index 0: same + * Index 1: inverted + * Index 2: same + * ... + * Index length - 1: same + */ + public static float[] invert(float[] coefficients) + { + for(int x = 1; x < coefficients.length; x += 2) + { + coefficients[x] = -coefficients[x]; + } + + return coefficients; + } + + /** + * Generates filter coefficients for a unity-gain, odd-length, windowed, + * low pass filter with passband from 0-hertz to the cutoff frequency. + * + * @param sampleRate - hertz + * @param cutoff - frequency in hertz + * @param filterLength - odd filter length + * @param windowType - window to apply against the generated coefficients + * @return + */ + public static float[] getLowPass(int sampleRate, + long cutoff, + int filterLength, + WindowType windowType) + { + if(filterLength % 2 == 0) //even length + { + float[] values = getSinc(sampleRate, cutoff, filterLength + 2, windowType); + + //throw away the 0 index and the last index + return Arrays.copyOfRange(values, 1, values.length - 2); + } + else + { + float[] values = getSinc(sampleRate, cutoff, filterLength + 1, windowType); + + //throw away the 0 index + return Arrays.copyOfRange(values, 1, values.length); + } + } + + /** + * Creates a low-pass filter with ~ 0.1 db ripple in the pass band + * + * Note: stop frequency - pass frequency defines the transition band. + */ + /** + * Creates a low-pass filter with ~0.1 db ripple in the pass band. The + * transition region is defined by the stop frequency minus the pass + * frequency. + * + * Requires: + * - passFrequency < stopFrequency + * - stopFrequency <= sampleRate/2 + */ + public static float[] getLowPass(int sampleRate, + int passFrequency, + int stopFrequency, + int attenuation, + WindowType windowType, + boolean forceOddLength) + { + if(stopFrequency < passFrequency || stopFrequency > (sampleRate / 2)) + { + throw new IllegalArgumentException("FilterFactory - low pass " + + "filter pass frequency [" + passFrequency + "] must be " + + "less than the stop frequency [" + stopFrequency + "] " + + "and must be less than half [" + (int) (sampleRate / 2) + + "] of the sample rate [" + sampleRate + "]"); + } + + int tapCount = getTapCount(sampleRate, + passFrequency, + stopFrequency, + attenuation); + + if(forceOddLength) + { + if(tapCount % 2 == 0) + { + tapCount--; + } + } + + return getLowPass(sampleRate, passFrequency, tapCount, windowType); + } + + /** + * Generates filter coefficients for a unity-gain, odd-length, windowed, + * high pass filter with passband from cutoff frequency to half the sample + * rate. + * + * @param sampleRate - hertz + * @param cutoff - frequency in hertz + * @param filterLength - odd filter length + * @param windowType - window to apply against the generated coefficients + * @return + */ + public static float[] getHighPass(int sampleRate, + long cutoff, + int filterLength, + WindowType windowType) + { + //Convert the high frequency cutoff to its low frequency cutoff + //equivalent, so that when we generate the low pass filter, prior to + //inversion, its at the correct frequency + long convertedCutoff = sampleRate / 2 - cutoff; + + return invert(getSinc(sampleRate, + convertedCutoff, + filterLength, + windowType)); + } + + public static float[] getHighPass(int sampleRate, + long stopFrequency, + long passFrequency, + int attenuation, + WindowType windowType, + boolean forceOddLength) + { + /* reverse the stop and pass frequency to get the low pass variant */ + int tapCount = getTapCount(sampleRate, + stopFrequency, + passFrequency, + attenuation); + + if(forceOddLength) + { + if(tapCount % 2 == 0) + { + tapCount--; + } + } + + return invert(getLowPass(sampleRate, stopFrequency, tapCount, windowType)); + } + + /** + * Utility to log the arrays of doubles with line breaks + */ + public static String arrayToString(float[] array, boolean breaks) + { + StringBuilder sb = new StringBuilder(); + for(int x = 0; x < array.length; x++) + { + sb.append(x + ": " + array[x]); + + if(breaks) + { + sb.append("\n"); + } + else + { + if(x % 8 == 7) + { + sb.append("\n"); + } + else + { + sb.append("\t"); + } + } + } + + return sb.toString(); + } + + /** + * Determines the number of fir filter taps required to produce the + * specified frequency response with passband ripple near .1dB. + * + * Implements the algorithm from Understanding Digital Signal Processing, 3e + * , Lyons, section 5.10.5. + * + * @param sampleRate in hertz + * @param pass pass frequency in hertz + * @param stop stop frequency in hertz + * @param attenuation in dB + * @return + */ + public static int getTapCount(int sampleRate, + long pass, + long stop, + int attenuation) + { + double frequency = ((double) stop - (double) pass) / (double) sampleRate; + + return (int) (Math.round((double) attenuation / (22.0d * frequency))); + } + + public static ComplexPrimeCICDecimate getDecimationFilter(int sampleRate, + int decimatedRate, int order, int passFrequency, int attenuation, + WindowType windowType) + { + int decimationRate = (int) (sampleRate / decimatedRate); + + return new ComplexPrimeCICDecimate(decimationRate, order, + passFrequency, attenuation, windowType); + } + + /** + * Determines decimation rate(s) for a polyphase decimation filter. + * + * @param sampleRate - starting sample rate + * @param decimatedRate - final (decimated) output rate + * @return - set of integer decimation rates for a single or multi-stage + * polyphase filter decimation chain + * @throws - AssertionException if sample rate is not a multiple of 48 kHz + */ + public static int[] getPolyphaseDecimationRates(int sampleRate, + int decimatedRate, + long passFrequency, + long stopFrequency) + { + int[] rates; + + if(sampleRate % decimatedRate != 0) + { + throw new IllegalArgumentException("Decimated rate must be an " + + "integer multiple of sample rate"); + } + + int decimation = (int) (sampleRate / decimatedRate); + + //Decimation rates below 20 will use single stage polyphase filter + if(decimation < 20) + { + rates = new int[1]; + rates[0] = decimation; + return rates; + } + else + { + int optimalStage1 = + getOptimalStageOneRate(sampleRate, decimation, passFrequency, stopFrequency); + + Set factors = getFactors(decimation); + + int stage1 = findClosest(optimalStage1, factors); - //Decimation rates below 20 will use single stage polyphase filter - if( decimation < 20 ) - { - rates = new int[ 1 ]; - rates[ 0 ] = decimation; - return rates; - } - else - { - int optimalStage1 = - getOptimalStageOneRate( sampleRate, decimation, passFrequency, stopFrequency ); - - Set factors = getFactors( decimation ); - - int stage1 = findClosest( optimalStage1, factors ); - // mLog.info( "Decimation rate [" + decimation + // "] stage1 optimal [" + optimalStage1 + // "] stage1 actual [" + stage1 + // "]"); - if( stage1 == decimation || stage1 == 1 ) - { - rates = new int[ 1 ]; - rates[ 0 ] = decimation; - return rates; - } - else - { - rates = new int[ 2 ]; - - int stage2 = (int)( decimation / stage1); + if(stage1 == decimation || stage1 == 1) + { + rates = new int[1]; + rates[0] = decimation; + return rates; + } + else + { + rates = new int[2]; - if( stage1 > stage2 ) - { - rates[ 0 ] = stage1; - rates[ 1 ] = stage2; - } - else - { - rates[ 0 ] = stage2; - rates[ 1 ] = stage1; - } - return rates; - } - } - } - - /** - * Finds the factor that is closest to the desired factor, from an ordered - * list of factors. - */ - private static int findClosest( int desiredFactor, Set factors ) - { - int bestFactor = 1; - int bestDelta = desiredFactor; - - for( Integer factor: factors ) - { - int testDelta = Math.abs( desiredFactor - factor ); - - if( testDelta < bestDelta ) - { - bestDelta = testDelta; - bestFactor = factor; - } - } - - return bestFactor; - } - - /** - * Determines the factors that make up an integer. Uses a brute force - * method to iterate all integers from 1 to value, determining which factors - * are evenly divisible into the value. - * - * @param value - integer decimation value - * @return - ordered set of factors for value - */ - private static Set getFactors( int value ) - { - Set factors = new TreeSet(); + int stage2 = (int) (decimation / stage1); + + if(stage1 > stage2) + { + rates[0] = stage1; + rates[1] = stage2; + } + else + { + rates[0] = stage2; + rates[1] = stage1; + } + return rates; + } + } + } + + /** + * Finds the factor that is closest to the desired factor, from an ordered + * list of factors. + */ + private static int findClosest(int desiredFactor, Set factors) + { + int bestFactor = 1; + int bestDelta = desiredFactor; + + for(Integer factor : factors) + { + int testDelta = Math.abs(desiredFactor - factor); + + if(testDelta < bestDelta) + { + bestDelta = testDelta; + bestFactor = factor; + } + } + + return bestFactor; + } + + /** + * Determines the factors that make up an integer. Uses a brute force + * method to iterate all integers from 1 to value, determining which factors + * are evenly divisible into the value. + * + * @param value - integer decimation value + * @return - ordered set of factors for value + */ + private static Set getFactors(int value) + { + Set factors = new TreeSet(); /* Brute force */ - for( int x = 1; x <= value; x++ ) - { - int remainder = (int)( value / x ); - - if( remainder * x == value ) - { - factors.add( x ); - } - } - - return factors; - } + for(int x = 1; x <= value; x++) + { + int remainder = (int) (value / x); - /** - * Determines the optimal decimation rate for stage 1 of a two-stage - * poly-phase decimation filter chain, to produce a final sample rate of - * 48 kHz using a pass bandwidth of 25 kHz. - * - * Use for total decimation rates of 20 or higher. - * - * Implements the algorithm described in Lyons, Understanding Digital Signal - * Processing, 3e, section 10.2.1, page 511. - * - * @param sampleRate - * @param decimation - * @param passFrequency frequency of the pass band - * @return optimum integer decimation rate for the first stage decimation - * filter - */ - public static int getOptimalStageOneRate( int sampleRate, - int decimation, - long passFrequency, - long stopFrequency ) - { - double ratio = getBandwidthRatio( passFrequency, stopFrequency ); - - double numerator = 1.0d - ( - Math.sqrt( (double)decimation * ratio / ( 2.0d - ratio ) ) ); + if(remainder * x == value) + { + factors.add(x); + } + } - double denominator = 2.0d - ( ratio * ( decimation + 1.0d ) ); - - int retVal = (int)( 2.0d * decimation * ( numerator / denominator ) ); + return factors; + } + + /** + * Determines the optimal decimation rate for stage 1 of a two-stage + * poly-phase decimation filter chain, to produce a final sample rate of + * 48 kHz using a pass bandwidth of 25 kHz. + * + * Use for total decimation rates of 20 or higher. + * + * Implements the algorithm described in Lyons, Understanding Digital Signal + * Processing, 3e, section 10.2.1, page 511. + * + * @param sampleRate + * @param decimation + * @param passFrequency frequency of the pass band + * @return optimum integer decimation rate for the first stage decimation + * filter + */ + public static int getOptimalStageOneRate(int sampleRate, + int decimation, + long passFrequency, + long stopFrequency) + { + double ratio = getBandwidthRatio(passFrequency, stopFrequency); + + double numerator = 1.0d - ( + Math.sqrt((double) decimation * ratio / (2.0d - ratio))); + + double denominator = 2.0d - (ratio * (decimation + 1.0d)); + + int retVal = (int) (2.0d * decimation * (numerator / denominator)); // mLog.info( "Optimal Stage 1 Decimation - rate [" + sampleRate + // "] pass [" + passFrequency + // "] bw ratio [" + ratio + // "] optimal [" + retVal + // "]" ); - - return retVal; - } - - /** - * Determines the F ratio as described in Lyons, Understanding Digital - * Signal Processing, 3e, section 10.2.1, page 511 - * - * Used in conjunction with the optimal stage one decimation rate method - * above. - */ - private static double getBandwidthRatio( long passFrequency, long stopFrequency ) - { - assert( passFrequency < stopFrequency ); - return ( (double)( stopFrequency - passFrequency ) / - (double)stopFrequency ); - } + return retVal; + } - /** - * Assumes that the pass band is 1/4 of the output sample rate. - * - * Assumes the stop band is: pass + (pass * .25). - * @param outputSampleRate - * @param stageCount - * @return - */ - public static float[] getCICCleanupFilter( int outputSampleRate, - int passFrequency, - int attenuation, - WindowType window ) - { - int taps = getTapCount( outputSampleRate, passFrequency, passFrequency + 1500, - attenuation ); + /** + * Determines the F ratio as described in Lyons, Understanding Digital + * Signal Processing, 3e, section 10.2.1, page 511 + * + * Used in conjunction with the optimal stage one decimation rate method + * above. + */ + private static double getBandwidthRatio(long passFrequency, long stopFrequency) + { + assert (passFrequency < stopFrequency); + + return ((double) (stopFrequency - passFrequency) / + (double) stopFrequency); + } + + /** + * Assumes that the pass band is 1/4 of the output sample rate. + * + * Assumes the stop band is: pass + (pass * .25). + * + * @param outputSampleRate + * @return + */ + public static float[] getCICCleanupFilter(int outputSampleRate, + int passFrequency, + int attenuation, + WindowType window) + { + int taps = getTapCount(outputSampleRate, passFrequency, passFrequency + 1500, + attenuation); /* Make tap count odd */ - if( taps% 2 == 0 ) - { - taps ++; - } + if(taps % 2 == 0) + { + taps++; + } - float[] frequencyResponse = - getCICResponseArray( outputSampleRate, passFrequency, taps ); - - //Apply Inverse DFT against frequency response unity values, leaving the - //IDFT bin results in the frequency response array - FloatFFT_1D idft = new FloatFFT_1D( taps ); - idft.realInverseFull( frequencyResponse, false ); + float[] frequencyResponse = + getCICResponseArray(outputSampleRate, passFrequency, taps); - //Transfer the IDFT results to the odd length return array - float[] coefficients = new float[ taps ]; - int middleCoefficient = (int)( taps / 2 ); + //Apply Inverse DFT against frequency response unity values, leaving the + //IDFT bin results in the frequency response array + FloatFFT_1D idft = new FloatFFT_1D(taps); + idft.realInverseFull(frequencyResponse, false); - //Bin 0 of the idft is our center coefficient - coefficients[ middleCoefficient ] = frequencyResponse[ 0 ]; + //Transfer the IDFT results to the odd length return array + float[] coefficients = new float[taps]; + int middleCoefficient = (int) (taps / 2); - //The remaining idft bins from 1 to (middle - 1) are the mirror image - //coefficients - for( int x = 1; x <= middleCoefficient; x++ ) - { - coefficients[ middleCoefficient + x ] = frequencyResponse[ 2 * x ]; - coefficients[ middleCoefficient - x ] = frequencyResponse[ 2 * x ]; - } + //Bin 0 of the idft is our center coefficient + coefficients[middleCoefficient] = frequencyResponse[0]; - //Apply the window against the coefficients + //The remaining idft bins from 1 to (middle - 1) are the mirror image + //coefficients + for(int x = 1; x <= middleCoefficient; x++) + { + coefficients[middleCoefficient + x] = frequencyResponse[2 * x]; + coefficients[middleCoefficient - x] = frequencyResponse[2 * x]; + } + + //Apply the window against the coefficients // coefficients = Window.apply( window, coefficients ); - - normalize( coefficients ); - - return coefficients; - } - public static float[] getCICResponseArray( int sampleRate, - int frequency, - int length ) - { - float[] cicArray = new float[ length * 2 ]; - - int binCount = (int)( ( Math.round( - (double)frequency / (double)sampleRate * 2.0d * (double)length ) ) ); - - cicArray[ 0 ] = 1.0f; - - float unityResponse = (float)( Math.sin( 1.0d / (double)length ) / - ( 1.0d / (double)length ) ); - - for( int x = 1; x <= binCount; x++ ) - { + normalize(coefficients); + + return coefficients; + } + + public static float[] getCICResponseArray(int sampleRate, + int frequency, + int length) + { + float[] cicArray = new float[length * 2]; + + int binCount = (int) ((Math.round( + (double) frequency / (double) sampleRate * 2.0d * (double) length))); + + cicArray[0] = 1.0f; + + float unityResponse = (float) (Math.sin(1.0d / (double) length) / + (1.0d / (double) length)); + + for(int x = 1; x <= binCount; x++) + { /* Calculate unity response plus amplification for droop */ - float compensated = 1.0f + ( unityResponse - - (float)( Math.sin( (double)x / (double)length ) / - ( (double)x / (double)length ) ) ); - - cicArray[ x ] = compensated; - cicArray[ length - x ] = compensated; - } + float compensated = 1.0f + (unityResponse - + (float) (Math.sin((double) x / (double) length) / + ((double) x / (double) length))); - return cicArray; - } + cicArray[x] = compensated; + cicArray[length - x] = compensated; + } - /** - * Creates root raised cosine filter coefficients with a tap count equal - * to the symbols x samplesPerSymbol + 1. - * - * Symbol count should be an even integer. - * - * Ported to java from gnuradio/filter/firdes.cc - * - * For 40db attenuation, calculate the number of symbols based on the - * following formula: - * - * Symbols = -44 * alpha + 33 - * - * @param samplesPerSymbol - number of samples per symbol - * @param symbols - number of symbols - must be even - * @param alpha - roll-off factor - * - * @return - filter coefficients - */ - public static float[] getRootRaisedCosine( int samplesPerSymbol, - int symbols, - float alpha ) - { - int taps = samplesPerSymbol * symbols + 1; - - float scale = 0; - - float[] coefficients = new float[ taps ]; - - for( int x = 0; x < taps; x++ ) - { - float index = (float)x - ( (float)taps / 2.0f ); - - float x1 = (float)Math.PI * index / (float)samplesPerSymbol; - float x2 = 4.0f * alpha * index / (float)samplesPerSymbol; - float x3 = x2 * x2 - 1.0f; + return cicArray; + } - float numerator, denominator; - - if( Math.abs( x3 ) >= 0.000001 ) - { - if( x != taps / 2 ) - { - numerator = (float)Math.cos( ( 1.0 + alpha ) * x1 ) + - (float)Math.sin( ( 1.0f - alpha ) * x1 ) / - ( 4.0f * alpha * index / (float)samplesPerSymbol ); - } - else - { - numerator = (float)Math.cos( ( 1.0f + alpha ) * x1 ) + - ( 1.0f - alpha ) * (float)Math.PI / ( 4.0f * alpha ); - } - - denominator = x3 * (float)Math.PI; - } - else - { - if( alpha == 1.0f ) - { - coefficients[ x ] = -1.0f; - continue; - } - - x3 = ( 1.0f - alpha ) * x1; - x2 = ( 1.0f + alpha ) * x1; - - numerator = (float)( Math.sin( x2 ) * ( 1.0f + alpha ) * Math.PI - - Math.cos( x3 ) * ( ( 1.0 - alpha ) * Math.PI * - (double)samplesPerSymbol ) / ( 4 * alpha * index ) + - Math.sin( x3 ) * (double)samplesPerSymbol * - (double)samplesPerSymbol / ( 4.0 * alpha * index * index ) ); - - denominator = (float)( -32.0 * Math.PI * alpha * alpha * index / - (double)samplesPerSymbol ); - } - - coefficients[ x ] = 4.0f * alpha * numerator / denominator; - - scale += coefficients[ x ]; - } + /** + * Creates root raised cosine filter coefficients with a tap count equal + * to the symbols x samplesPerSymbol + 1. + * + * Symbol count should be an even integer. + * + * Ported to java from gnuradio/filter/firdes.cc + * + * For 40db attenuation, calculate the number of symbols based on the + * following formula: + * + * Symbols = -44 * alpha + 33 + * + * @param samplesPerSymbol - number of samples per symbol + * @param symbols - number of symbols - must be even + * @param alpha - roll-off factor + * @return - filter coefficients + */ + public static float[] getRootRaisedCosine(int samplesPerSymbol, + int symbols, + float alpha) + { + int taps = samplesPerSymbol * symbols + 1; - /** - * Normalize (scale) coefficients to 1.0 sum and apply gain - */ - for( int x = 0; x < taps; x++ ) - { - coefficients[ x ] = coefficients[ x ] / scale; - } + float scale = 0; - return coefficients; - } + float[] coefficients = new float[taps]; + + for(int x = 0; x < taps; x++) + { + float index = (float) x - ((float) taps / 2.0f); + + float x1 = (float) Math.PI * index / (float) samplesPerSymbol; + float x2 = 4.0f * alpha * index / (float) samplesPerSymbol; + float x3 = x2 * x2 - 1.0f; + + float numerator, denominator; + + if(Math.abs(x3) >= 0.000001) + { + if(x != taps / 2) + { + numerator = (float) Math.cos((1.0 + alpha) * x1) + + (float) Math.sin((1.0f - alpha) * x1) / + (4.0f * alpha * index / (float) samplesPerSymbol); + } + else + { + numerator = (float) Math.cos((1.0f + alpha) * x1) + + (1.0f - alpha) * (float) Math.PI / (4.0f * alpha); + } + + denominator = x3 * (float) Math.PI; + } + else + { + if(alpha == 1.0f) + { + coefficients[x] = -1.0f; + continue; + } + + x3 = (1.0f - alpha) * x1; + x2 = (1.0f + alpha) * x1; + + numerator = (float) (Math.sin(x2) * (1.0f + alpha) * Math.PI - + Math.cos(x3) * ((1.0 - alpha) * Math.PI * + (double) samplesPerSymbol) / (4 * alpha * index) + + Math.sin(x3) * (double) samplesPerSymbol * + (double) samplesPerSymbol / (4.0 * alpha * index * index)); + + denominator = (float) (-32.0 * Math.PI * alpha * alpha * index / + (double) samplesPerSymbol); + } + + coefficients[x] = 4.0f * alpha * numerator / denominator; + + scale += coefficients[x]; + } + + /** + * Normalize (scale) coefficients to 1.0 sum and apply gain + */ + for(int x = 0; x < taps; x++) + { + coefficients[x] = coefficients[x] / scale; + } + + return coefficients; + } + + /** + * Creates a filter from the filter specification + * + * @param specification + * @return + * @throws FilterDesignException if the filter cannot be designed + */ + public static float[] getTaps(FIRFilterSpecification specification) throws FilterDesignException + { + RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification); + + if(designer.isValid()) + { + return designer.getImpulseResponse(); + } + + return null; + } } diff --git a/src/dsp/filter/design/FilterViewer.java b/src/dsp/filter/design/FilterViewer.java index 3b84c4a4..f34f03f6 100644 --- a/src/dsp/filter/design/FilterViewer.java +++ b/src/dsp/filter/design/FilterViewer.java @@ -51,25 +51,25 @@ public class FilterViewer extends Application // .stopBandRipple( 0.027 ) // .build(); - FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder() - .sampleRate( 8000 ) - .stopBandCutoff( 250 ) - .stopBandAmplitude( 0.0 ) - .stopBandRipple( 0.01 ) - .passBandStart( 300 ) - .passBandAmplitude( 1.0 ) - .passBandRipple( 0.01 ) - .build(); +// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder() +// .sampleRate( 8000 ) +// .stopBandCutoff( 250 ) +// .stopBandAmplitude( 0.0 ) +// .stopBandRipple( 0.01 ) +// .passBandStart( 300 ) +// .passBandAmplitude( 1.0 ) +// .passBandRipple( 0.01 ) +// .build(); -// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder() -// .sampleRate( 48000 ) -// .stopFrequency1( 150 ) -// .passFrequencyBegin( 200 ) -// .passFrequencyEnd( 3950 ) -// .stopFrequency2( 4000 ) -// .stopRipple( 0.0005 ) -// .passRipple( 0.003) -// .build(); + FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder() + .sampleRate( 48000 ) + .stopFrequency1( 200 ) + .passFrequencyBegin( 400 ) + .passFrequencyEnd( 3200 ) + .stopFrequency2( 3400 ) + .stopRipple( 0.0003 ) + .passRipple( 0.008) + .build(); float[] taps = null; diff --git a/src/dsp/fsk/FSK2Decoder.java b/src/dsp/fsk/FSK2Decoder.java index e54c3aae..2a429546 100644 --- a/src/dsp/fsk/FSK2Decoder.java +++ b/src/dsp/fsk/FSK2Decoder.java @@ -1,296 +1,302 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014 Dennis Sheirer - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ package dsp.fsk; +import buffer.BooleanAveragingBuffer; +import dsp.symbol.SymbolEvent; +import dsp.symbol.SymbolEvent.Shift; import instrument.Instrumentable; import instrument.tap.Tap; import instrument.tap.TapGroup; import instrument.tap.stream.SymbolEventTap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sample.Listener; +import sample.real.RealBuffer; import java.util.ArrayList; import java.util.BitSet; import java.util.List; -import sample.Listener; -import sample.real.RealBuffer; -import buffer.BooleanAveragingBuffer; -import dsp.symbol.SymbolEvent; -import dsp.symbol.SymbolEvent.Shift; - /** * Binary Frequency Shift Keying (FSK) decoder. Implements a BFSK correlation * decoder. Provides normal or inverted decoded output. Requires the symbol * rate to be an integer multiple of the sample rate. - * - * Implements a correlation decoder that correlates a modulated FSK signal upon - * against a one-baud delayed version of itself, converting all float samples to - * binary (0 or 1), and uses XOR to produce the correlation value. Low pass + * + * Implements a correlation decoder that correlates a modulated FSK signal upon + * against a one-baud delayed version of itself, converting all float samples to + * binary (0 or 1), and uses XOR to produce the correlation value. Low pass * filtering smoothes the correlated output. - * + * * Automatically aligns to symbol timing during symbol transitions (0 to 1, or - * 1 to 0) by inspecting the samples at the symbol edges and advancing or + * 1 to 0) by inspecting the samples at the symbol edges and advancing or * retarding symbol window to maintain continuous symbol alignment. - * + * * Use a DC-removal filter prior to this decoder to ensure samples don't have * a DC component. - * + * * Implements instrumentable interface, so that slice events can be received * externally to analyze decoder performance. */ public class FSK2Decoder implements Instrumentable, Listener { - public enum Output{ NORMAL, INVERTED }; + private final static Logger mLog = LoggerFactory.getLogger(FSK2Decoder.class); - /* Instrumentation taps */ - private static final String INSTRUMENT_DECISION = - "Tap Point: FSK2 Symbol Decision"; - private List mAvailableTaps; - private ArrayList mTaps = new ArrayList(); - - private Listener mListener; - private BooleanAveragingBuffer mDelayBuffer; - private BooleanAveragingBuffer mLowPassFilter; - private Slicer mSlicer; - private boolean mNormalOutput; - private int mSamplesPerSymbol; - private int mSymbolRate; - - public FSK2Decoder( int sampleRate, int symbolRate, Output output ) - { - /* Ensure we're using an integral symbolRate of the sampleRate */ - assert( sampleRate % symbolRate == 0 ); - - mSamplesPerSymbol = (int)( sampleRate / symbolRate ); - mNormalOutput = ( output == Output.NORMAL ); - mSymbolRate = symbolRate; - - mDelayBuffer = new BooleanAveragingBuffer( mSamplesPerSymbol ); - mLowPassFilter = new BooleanAveragingBuffer( mSamplesPerSymbol ); - mSlicer = new Slicer( mSamplesPerSymbol ); - } - - /** - * Disposes of all references to prepare for garbage collection - */ - public void dispose() - { - mListener = null; - } + public enum Output + { + NORMAL, INVERTED + } - /** - * Primary sample input - */ - @Override - public void receive( RealBuffer buffer ) - { - for( float sample: buffer.getSamples() ) - { + /* Instrumentation taps */ + private static final String INSTRUMENT_DECISION = + "Tap Point: FSK2 Symbol Decision"; + private List mAvailableTaps; + private ArrayList mTaps = new ArrayList(); + + private Listener mListener; + private BooleanAveragingBuffer mDelayBuffer; + private BooleanAveragingBuffer mLowPassFilter; + private Slicer mSlicer; + private boolean mNormalOutput; + private int mSamplesPerSymbol; + private int mSymbolRate; + + public FSK2Decoder(int sampleRate, int symbolRate, Output output) + { + /* Ensure we're using an integral symbolRate of the sampleRate */ + assert (sampleRate % symbolRate == 0); + + mSamplesPerSymbol = (int) (sampleRate / symbolRate); + mNormalOutput = (output == Output.NORMAL); + mSymbolRate = symbolRate; + + mDelayBuffer = new BooleanAveragingBuffer(mSamplesPerSymbol); + mLowPassFilter = new BooleanAveragingBuffer(mSamplesPerSymbol); + mSlicer = new Slicer(mSamplesPerSymbol); + } + + /** + * Disposes of all references to prepare for garbage collection + */ + public void dispose() + { + mListener = null; + } + + /** + * Primary sample input + */ + @Override + public void receive(RealBuffer buffer) + { + for(float sample : buffer.getSamples()) + { /* Square the sample. Greater than zero is a 1 (true) and less than * zero is a 0 (false) */ - boolean bitSample = ( sample >= 0.0f ); + boolean bitSample = (sample >= 0.0f); /* Feed the delay buffer and fetch the one-baud delayed sample */ - boolean delayedBitSample = mDelayBuffer.get( bitSample ); + boolean delayedBitSample = mDelayBuffer.get(bitSample); /* Correlation: xor current bit with delayed bit */ - boolean softBit = bitSample ^ delayedBitSample; + boolean softBit = bitSample ^ delayedBitSample; /* Low pass filter to smooth the correlated values */ - boolean filteredSoftBit = mLowPassFilter.getAverage( softBit ); + boolean filteredSoftBit = mLowPassFilter.getAverage(softBit); /* Send the filtered correlated bit to the slicer */ - mSlicer.receive( filteredSoftBit ); - } - } + mSlicer.receive(filteredSoftBit); + } + } - /** - * Registers a listener to receive the decoded FSK bits - */ - public void setListener( Listener listener ) - { - mListener = listener; - } + /** + * Registers a listener to receive the decoded FSK bits + */ + public void setListener(Listener listener) + { + mListener = listener; + } - /** - * Removes the listener - */ - public void removeListener( Listener listener ) - { - mListener = null; - } + /** + * Removes the listener + */ + public void removeListener(Listener listener) + { + mListener = null; + } - /** - * Symbol slicer with auto-aligning baud timing - */ - public class Slicer - { - private BitSet mBitSet = new BitSet(); - private int mSymbolLength; - private int mDecisionThreshold; - private int mSampleCounter; - - public Slicer( int samplesPerSymbol ) - { - mSymbolLength = samplesPerSymbol; - - mDecisionThreshold = (int)( mSymbolLength / 2 ); + /** + * Symbol slicer with auto-aligning baud timing + */ + public class Slicer + { + private BitSet mBitSet = new BitSet(); + private int mSymbolLength; + private int mDecisionThreshold; + private int mSampleCounter; + + public Slicer(int samplesPerSymbol) + { + mSymbolLength = samplesPerSymbol; + + mDecisionThreshold = (int) (mSymbolLength / 2); /* Adjust for an odd number of samples per baud */ - if( mSymbolLength % 2 == 1 ) - { - mDecisionThreshold++; - } - } - - public void receive( boolean softBit ) - { - if( mSampleCounter >= 0 ) - { - if( softBit ) - { - mBitSet.set( mSampleCounter ); - } - else - { - mBitSet.clear( mSampleCounter ); - } - } - - mSampleCounter++; - - if( mSampleCounter >= mSymbolLength ) - { - boolean decision = mBitSet.cardinality() >= mDecisionThreshold; - - send( decision ); + if(mSymbolLength % 2 == 1) + { + mDecisionThreshold++; + } + } + + public void receive(boolean softBit) + { + if(mSampleCounter >= 0) + { + if(softBit) + { + mBitSet.set(mSampleCounter); + } + else + { + mBitSet.clear(mSampleCounter); + } + } + + mSampleCounter++; + + if(mSampleCounter >= mSymbolLength) + { + boolean decision = mBitSet.cardinality() >= mDecisionThreshold; + + send(decision); /* Shift timing left if the left bit in the bitset is opposite * the decision and the right bit is the same */ - if( ( mBitSet.get( 0 ) ^ decision ) && - ( !( mBitSet.get( mSymbolLength - 1 ) ^ decision ) ) ) - { - sendTapEvent( mBitSet, Shift.LEFT, decision ); - - reset(); - - mSampleCounter--; - } + if((mBitSet.get(0) ^ decision) && + (!(mBitSet.get(mSymbolLength - 1) ^ decision))) + { + sendTapEvent(mBitSet, Shift.LEFT, decision); + + reset(); + + mSampleCounter--; + } /* Shift timing right if the left bit is the same as the * decision and the right bit is opposite */ - else if( ( !( mBitSet.get( 0 ) ^ decision ) ) && - ( mBitSet.get( mSymbolLength - 1 ) ^ decision ) ) - { - sendTapEvent( mBitSet, Shift.RIGHT, decision ); + else if((!(mBitSet.get(0) ^ decision)) && + (mBitSet.get(mSymbolLength - 1) ^ decision)) + { + sendTapEvent(mBitSet, Shift.RIGHT, decision); /* Last bit from previous symbol to pre-fill next symbol */ - boolean previousSoftBit = mBitSet.get( mSymbolLength - 1 ); - - reset(); - - if( previousSoftBit ) - { - mBitSet.set( 0 ); - } - - mSampleCounter++; - } + boolean previousSoftBit = mBitSet.get(mSymbolLength - 1); + + reset(); + + if(previousSoftBit) + { + mBitSet.set(0); + } + + mSampleCounter++; + } /* No shift */ - else - { - sendTapEvent( mBitSet, Shift.NONE, decision ); - - reset(); - } - } - } - - /** - * Sends the bit decision to the listener - */ - private void send( boolean decision ) - { - if( mListener != null ) - { - mListener.receive( mNormalOutput ? decision : !decision ); - } - } - - private void reset() - { - mBitSet.clear(); - mSampleCounter = 0; - } + else + { + sendTapEvent(mBitSet, Shift.NONE, decision); - /** - * Sends instrumentation tap event to all registered listeners - */ - private void sendTapEvent( BitSet bitset, Shift shift, boolean decision ) - { - for( SymbolEventTap tap: mTaps ) - { - SymbolEvent event = - new SymbolEvent( bitset.get( 0, mSymbolLength ), - mSymbolLength, - decision, - shift ); - - tap.receive( event ); - } - } - } + reset(); + } + } + } - /** - * Get instrumentation taps - */ - @Override + /** + * Sends the bit decision to the listener + */ + private void send(boolean decision) + { + if(mListener != null) + { + mListener.receive(mNormalOutput ? decision : !decision); + } + } + + private void reset() + { + mBitSet.clear(); + mSampleCounter = 0; + } + + /** + * Sends instrumentation tap event to all registered listeners + */ + private void sendTapEvent(BitSet bitset, Shift shift, boolean decision) + { + for(SymbolEventTap tap : mTaps) + { + SymbolEvent event = + new SymbolEvent(bitset.get(0, mSymbolLength), + mSymbolLength, + decision, + shift); + + tap.receive(event); + } + } + } + + /** + * Get instrumentation taps + */ + @Override public List getTapGroups() { - if( mAvailableTaps == null ) - { - mAvailableTaps = new ArrayList(); - - TapGroup tapGroup = new TapGroup( "FSK2 Decoder" ); - tapGroup.add( new SymbolEventTap( INSTRUMENT_DECISION, 0, .025f ) ); - - mAvailableTaps.add( tapGroup ); - } - - return mAvailableTaps; + if(mAvailableTaps == null) + { + mAvailableTaps = new ArrayList(); + + TapGroup tapGroup = new TapGroup("FSK2 Decoder"); + tapGroup.add(new SymbolEventTap(INSTRUMENT_DECISION, 0, .025f)); + + mAvailableTaps.add(tapGroup); + } + + return mAvailableTaps; } - /** - * Add instrumentation tap - */ - @Override - public void registerTap( Tap tap ) + /** + * Add instrumentation tap + */ + @Override + public void registerTap(Tap tap) { - if( tap instanceof SymbolEventTap && !mTaps.contains( tap ) ) - { - mTaps.add( (SymbolEventTap)tap ); - } + if(tap instanceof SymbolEventTap && !mTaps.contains(tap)) + { + mTaps.add((SymbolEventTap) tap); + } } - /** - * Remove instrumentation tap - */ - @Override - public void unregisterTap( Tap tap ) + /** + * Remove instrumentation tap + */ + @Override + public void unregisterTap(Tap tap) { - mTaps.remove( tap ); + mTaps.remove(tap); } } diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java index cb056047..720a7faa 100644 --- a/src/gui/SDRTrunk.java +++ b/src/gui/SDRTrunk.java @@ -386,7 +386,7 @@ public class SDRTrunk implements Listener if(mBroadcastStatusPanel == null) { mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel); - mBroadcastStatusPanel.setPreferredSize(new Dimension(880, 100)); + mBroadcastStatusPanel.setPreferredSize(new Dimension(880, 70)); mBroadcastStatusPanel.getTable().setEnabled(false); } diff --git a/src/module/decode/DecoderFactory.java b/src/module/decode/DecoderFactory.java index 1ef26a34..7b7068b6 100644 --- a/src/module/decode/DecoderFactory.java +++ b/src/module/decode/DecoderFactory.java @@ -30,6 +30,8 @@ import controller.channel.ChannelModel; import controller.channel.ChannelProcessingManager; import controller.channel.map.ChannelMap; import controller.channel.map.ChannelMapModel; +import dsp.filter.FilterFactory; +import dsp.filter.fir.FIRFilterSpecification; import filter.AllPassFilter; import filter.FilterSet; import filter.IFilter; @@ -89,12 +91,34 @@ import module.decode.tait.Tait1200DecoderState; import module.demodulate.am.AMDemodulatorModule; import module.demodulate.audio.DemodulatedAudioFilterModule; import module.demodulate.fm.FMDemodulatorModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; public class DecoderFactory { + private final static Logger mLog = LoggerFactory.getLogger(DecoderFactory.class); + + private static final FIRFilterSpecification MPT1327_FILTER_SPECIFICATION = FIRFilterSpecification.bandPassBuilder() + .sampleRate(48000).stopFrequency1(200).passFrequencyBegin(400).passFrequencyEnd(3200) + .stopFrequency2(3400).stopRipple(0.0003).passRipple(0.008).build(); + + private static float[] MPT1327_BANDPASS_FILTER; + + static + { + try + { + MPT1327_BANDPASS_FILTER = FilterFactory.getTaps(MPT1327_FILTER_SPECIFICATION); + } + catch(Exception e) + { + mLog.error("Couldn't design MPT1327 bandpass filter"); + } + } + public static final boolean NO_REMOVE_DC = false; public static final boolean REMOVE_DC = true; @@ -197,7 +221,7 @@ public class DecoderFactory } modules.add(new FMDemodulatorModule(iqPass, iqStop)); - modules.add(new DemodulatedAudioFilterModule(4000, 6000)); + modules.add(new DemodulatedAudioFilterModule(MPT1327_BANDPASS_FILTER, 2.0f)); modules.add(new AudioModule(metadata)); break; case PASSPORT: diff --git a/src/module/decode/mpt1327/MPT1327Decoder.java b/src/module/decode/mpt1327/MPT1327Decoder.java index cb495924..48abb18d 100644 --- a/src/module/decode/mpt1327/MPT1327Decoder.java +++ b/src/module/decode/mpt1327/MPT1327Decoder.java @@ -1,322 +1,290 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see + * sdrtrunk + * Copyright (C) 2014-2017 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ******************************************************************************/ package module.decode.mpt1327; +import alias.AliasList; +import bits.MessageFramer; +import bits.SyncPattern; +import dsp.filter.Filters; +import dsp.filter.dc.DCRemovalFilterLyons; +import dsp.filter.fir.real.RealFIRFilter_RB_RB; +import dsp.filter.halfband.real.HalfBandFilter_RB_RB; +import dsp.fsk.FSK2Decoder; +import dsp.fsk.FSK2Decoder.Output; import instrument.Instrumentable; import instrument.tap.Tap; import instrument.tap.TapGroup; import instrument.tap.stream.BinaryTap; import instrument.tap.stream.FloatBufferTap; import instrument.tap.stream.FloatTap; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ScheduledExecutorService; - import module.decode.Decoder; import module.decode.DecoderType; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sample.Broadcaster; import sample.Listener; import sample.real.IFilteredRealBufferListener; import sample.real.RealBuffer; -import alias.AliasList; -import bits.MessageFramer; -import bits.SyncPattern; -import dsp.filter.FilterFactory; -import dsp.filter.Filters; -import dsp.filter.Window.WindowType; -import dsp.filter.dc.DCRemovalFilterLyons; -import dsp.filter.fir.real.RealFIRFilter_RB_RB; -import dsp.filter.halfband.real.HalfBandFilter_RB_RB; -import dsp.fsk.FSK2Decoder; -import dsp.fsk.FSK2Decoder.Output; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; /** * MPT1327 Decoder - 1200 baud 2FSK decoder that can process 48k sample rate * complex or floating point samples and output fully framed MPT1327 control and * traffic messages. */ -public class MPT1327Decoder extends Decoder - implements IFilteredRealBufferListener, Instrumentable +public class MPT1327Decoder extends Decoder implements IFilteredRealBufferListener, Instrumentable { - private final static Logger mLog = LoggerFactory.getLogger( MPT1327Decoder.class ); - - /* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */ - private static final int sDECIMATED_SAMPLE_RATE = 24000; - - /* Baud or Symbol Rate */ - private static final int sSYMBOL_RATE = 1200; + private final static Logger mLog = LoggerFactory.getLogger(MPT1327Decoder.class); - /* Message length -- longest possible message is: - * 4xREVS + 16xSYNC + 64xADD1 + 64xDCW1 + 64xDCW2 + 64xDCW3 + 64xDCW4 */ + /* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */ + private static final int sDECIMATED_SAMPLE_RATE = 24000; + + /* Baud or Symbol Rate */ + private static final int sSYMBOL_RATE = 1200; + + /* Message length -- longest possible message is: + * 4xREVS + 16xSYNC + 64xADD1 + 64xDCW1 + 64xDCW2 + 64xDCW3 + 64xDCW4 */ private static final int sMESSAGE_LENGTH = 350; - + /* Instrumentation Taps */ - private static final String INSTRUMENT_HB1_FILTER_TO_LOW_PASS = - "Tap Point: Half-band Decimation Filter > < Low Pass Filter"; - private static final String INSTRUMENT_LOW_PASS_TO_DECODER = - "Tap Point: Low Pass Filter > < Decoder"; - private static final String INSTRUMENT_DECODER_TO_FRAMER = - "Tap Point: Decoder > < Sync Detect/Message Framer"; + private static final String INSTRUMENT_HB1_FILTER_TO_LOW_PASS = + "Tap Point: Half-band Decimation Filter > < Low Pass Filter"; + private static final String INSTRUMENT_LOW_PASS_TO_DECODER = + "Tap Point: Low Pass Filter > < Decoder"; + private static final String INSTRUMENT_DECODER_TO_FRAMER = + "Tap Point: Decoder > < Sync Detect/Message Framer"; private List mAvailableTaps; - - private HalfBandFilter_RB_RB mDecimationFilter; - private RealFIRFilter_RB_RB mLowPassFilter; - private DCRemovalFilterLyons mDCRemovalFilter; - private FSK2Decoder mFSKDecoder; - private Broadcaster mSymbolBroadcaster; + + private HalfBandFilter_RB_RB mDecimationFilter; + private RealFIRFilter_RB_RB mLowPassFilter; + private DCRemovalFilterLyons mDCRemovalFilter; + private FSK2Decoder mFSKDecoder; + private Broadcaster mSymbolBroadcaster; private MessageFramer mControlMessageFramer; private MessageFramer mTrafficMessageFramer; private MPT1327MessageProcessor mMessageProcessor; - public MPT1327Decoder( AliasList aliasList, Sync sync ) - { + public MPT1327Decoder(AliasList aliasList, Sync sync) + { + /* Decimation filter - 48000 / 2 = 24000 output */ + mDecimationFilter = new HalfBandFilter_RB_RB(Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0002f, true); - /* Decimation filter - 48000 / 2 = 24000 output */ - mDecimationFilter = new HalfBandFilter_RB_RB( - Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0002f, true ); - - /* Low pass filter: 2kHz to pass the 1200 & 1800 Hz FSK */ - mLowPassFilter = new RealFIRFilter_RB_RB( - FilterFactory.getLowPass( 24000, //sampleRate, - 2000, //passFrequency, - 4000, //stopFrequency, - 60, //attenuation, - WindowType.HANNING, //windowType, - true ) //forceOddLength - , 2.0f ); //Gain - mDecimationFilter.setListener( mLowPassFilter ); + /** + * Normal: 2FSK Decoder with inverted output + * French: 2FSK Decoder with normal output + */ + if(sync == Sync.NORMAL) + { + mFSKDecoder = new FSK2Decoder(24000, 1200, Output.INVERTED); + } + else if(sync == Sync.FRENCH) + { + mFSKDecoder = new FSK2Decoder(24000, 1200, Output.NORMAL); + } + else + { + throw new IllegalArgumentException("MPT1327 Decoder - unrecognized Sync type"); + } - /** - * Normal: 2FSK Decoder with inverted output - * French: 2FSK Decoder with normal output - */ - if( sync == Sync.NORMAL ) - { - mFSKDecoder = new FSK2Decoder( 24000, 1200, Output.INVERTED ); - } - else if( sync == Sync.FRENCH ) - { - mFSKDecoder = new FSK2Decoder( 24000, 1200, Output.NORMAL ); - } - else - { - throw new IllegalArgumentException( "MPT1327 Decoder - unrecognized Sync type" ); - } - mLowPassFilter.setListener( mFSKDecoder ); - - mSymbolBroadcaster = new Broadcaster(); - mFSKDecoder.setListener( mSymbolBroadcaster ); + mDecimationFilter.setListener(mFSKDecoder); + + mSymbolBroadcaster = new Broadcaster(); + mFSKDecoder.setListener(mSymbolBroadcaster); /* Message framer for control channel messages */ - mControlMessageFramer = - new MessageFramer( sync.getControlSyncPattern().getPattern(), - sMESSAGE_LENGTH ); - mSymbolBroadcaster.addListener( mControlMessageFramer ); + mControlMessageFramer = new MessageFramer(sync.getControlSyncPattern().getPattern(), sMESSAGE_LENGTH); + mSymbolBroadcaster.addListener(mControlMessageFramer); /* Message framer for traffic channel massages */ - mTrafficMessageFramer = - new MessageFramer( sync.getTrafficSyncPattern().getPattern(), - sMESSAGE_LENGTH ); - mSymbolBroadcaster.addListener( mTrafficMessageFramer ); + mTrafficMessageFramer = new MessageFramer(sync.getTrafficSyncPattern().getPattern(), sMESSAGE_LENGTH); + mSymbolBroadcaster.addListener(mTrafficMessageFramer); /* Fully decoded and framed messages processor */ - mMessageProcessor = new MPT1327MessageProcessor( aliasList ); - mMessageProcessor.setMessageListener( this ); - mControlMessageFramer.addMessageListener( mMessageProcessor ); - mTrafficMessageFramer.addMessageListener( mMessageProcessor ); - } - - @Override - public Listener getFilteredRealBufferListener() - { - return mDecimationFilter; - } + mMessageProcessor = new MPT1327MessageProcessor(aliasList); + mMessageProcessor.setMessageListener(this); + mControlMessageFramer.addMessageListener(mMessageProcessor); + mTrafficMessageFramer.addMessageListener(mMessageProcessor); + } - @Override - public DecoderType getDecoderType() - { - return DecoderType.MPT1327; - } - - /** - * Cleanup method - */ - public void dispose() - { - super.dispose(); + @Override + public Listener getFilteredRealBufferListener() + { + return mDecimationFilter; + } - mSymbolBroadcaster.dispose(); - - mControlMessageFramer.dispose(); + @Override + public DecoderType getDecoderType() + { + return DecoderType.MPT1327; + } - if( mDCRemovalFilter != null ) - { - mDCRemovalFilter.dispose(); - } + /** + * Cleanup method + */ + public void dispose() + { + super.dispose(); - mFSKDecoder.dispose(); + mSymbolBroadcaster.dispose(); - mDecimationFilter.dispose(); + mControlMessageFramer.dispose(); - mLowPassFilter.dispose(); - - mMessageProcessor.dispose(); + if(mDCRemovalFilter != null) + { + mDCRemovalFilter.dispose(); + } - mTrafficMessageFramer.dispose(); - } + mFSKDecoder.dispose(); - /* Instrumentation Taps */ - @Override + mDecimationFilter.dispose(); + + mLowPassFilter.dispose(); + + mMessageProcessor.dispose(); + + mTrafficMessageFramer.dispose(); + } + + /* Instrumentation Taps */ + @Override public List getTapGroups() { - if( mAvailableTaps == null ) - { - mAvailableTaps = new ArrayList(); + if(mAvailableTaps == null) + { + mAvailableTaps = new ArrayList(); - TapGroup group = new TapGroup( "MPT-1327 Decoder" ); - - group.add( - new FloatTap( INSTRUMENT_HB1_FILTER_TO_LOW_PASS, 0, 0.5f ) ); - group.add( - new FloatTap( INSTRUMENT_LOW_PASS_TO_DECODER, 0, 0.5f ) ); - group.add( - new BinaryTap( INSTRUMENT_DECODER_TO_FRAMER, 0, 0.025f ) ); + TapGroup group = new TapGroup("MPT-1327 Decoder"); - mAvailableTaps.add( group ); + group.add(new FloatTap(INSTRUMENT_HB1_FILTER_TO_LOW_PASS, 0, 0.5f)); + group.add(new FloatTap(INSTRUMENT_LOW_PASS_TO_DECODER, 0, 0.5f)); + group.add(new BinaryTap(INSTRUMENT_DECODER_TO_FRAMER, 0, 0.025f)); + + mAvailableTaps.add(group); /* Add the taps from the FSK decoder */ - mAvailableTaps.addAll( mFSKDecoder.getTapGroups() ); - } - - return mAvailableTaps; + mAvailableTaps.addAll(mFSKDecoder.getTapGroups()); + } + + return mAvailableTaps; } - @Override - public void registerTap( Tap tap ) + @Override + public void registerTap(Tap tap) { - /* Send request to decoder */ - mFSKDecoder.registerTap( tap ); - - switch( tap.getName() ) - { - case INSTRUMENT_HB1_FILTER_TO_LOW_PASS: - FloatBufferTap hb1Tap = (FloatBufferTap)tap; - mDecimationFilter.setListener( hb1Tap ); - hb1Tap.setListener( mLowPassFilter ); - break; - case INSTRUMENT_LOW_PASS_TO_DECODER: - FloatBufferTap lowTap = (FloatBufferTap)tap; - mLowPassFilter.setListener( lowTap ); - lowTap.setListener( mFSKDecoder ); - break; - case INSTRUMENT_DECODER_TO_FRAMER: - BinaryTap decoderTap = (BinaryTap)tap; - mFSKDecoder.setListener( decoderTap ); - decoderTap.setListener( mSymbolBroadcaster ); - break; - } + /* Send request to decoder */ + mFSKDecoder.registerTap(tap); + + switch(tap.getName()) + { + case INSTRUMENT_HB1_FILTER_TO_LOW_PASS: + FloatBufferTap hb1Tap = (FloatBufferTap) tap; + mDecimationFilter.setListener(hb1Tap); + hb1Tap.setListener(mLowPassFilter); + break; + case INSTRUMENT_LOW_PASS_TO_DECODER: + FloatBufferTap lowTap = (FloatBufferTap) tap; + mLowPassFilter.setListener(lowTap); + lowTap.setListener(mFSKDecoder); + break; + case INSTRUMENT_DECODER_TO_FRAMER: + BinaryTap decoderTap = (BinaryTap) tap; + mFSKDecoder.setListener(decoderTap); + decoderTap.setListener(mSymbolBroadcaster); + break; + } } - @Override - public void unregisterTap( Tap tap ) + @Override + public void unregisterTap(Tap tap) { - mFSKDecoder.unregisterTap( tap ); - - switch( tap.getName() ) - { - case INSTRUMENT_HB1_FILTER_TO_LOW_PASS: - mDecimationFilter.setListener( mLowPassFilter ); - break; - case INSTRUMENT_LOW_PASS_TO_DECODER: - mLowPassFilter.setListener( mFSKDecoder ); - break; - case INSTRUMENT_DECODER_TO_FRAMER: - mFSKDecoder.setListener( mSymbolBroadcaster ); - break; - } + mFSKDecoder.unregisterTap(tap); + + switch(tap.getName()) + { + case INSTRUMENT_HB1_FILTER_TO_LOW_PASS: + mDecimationFilter.setListener(mLowPassFilter); + break; + case INSTRUMENT_LOW_PASS_TO_DECODER: + mLowPassFilter.setListener(mFSKDecoder); + break; + case INSTRUMENT_DECODER_TO_FRAMER: + mFSKDecoder.setListener(mSymbolBroadcaster); + break; + } } - public enum Sync - { - NORMAL( "Normal", - SyncPattern.MPT1327_CONTROL, - SyncPattern.MPT1327_TRAFFIC ), - FRENCH( "French", - SyncPattern.MPT1327_CONTROL_FRENCH, - SyncPattern.MPT1327_TRAFFIC_FRENCH ); - - private String mLabel; - private SyncPattern mControlSyncPattern; - private SyncPattern mTrafficSyncPattern; + public enum Sync + { + NORMAL("Normal", SyncPattern.MPT1327_CONTROL, SyncPattern.MPT1327_TRAFFIC), + FRENCH("French", SyncPattern.MPT1327_CONTROL_FRENCH, SyncPattern.MPT1327_TRAFFIC_FRENCH); - private Sync( String label, - SyncPattern controlPattern, - SyncPattern trafficPattern ) - { - mLabel = label; - mControlSyncPattern = controlPattern; - mTrafficSyncPattern = trafficPattern; - } - - public String getLabel() - { - return mLabel; - } - - public SyncPattern getControlSyncPattern() - { - return mControlSyncPattern; - } - - public SyncPattern getTrafficSyncPattern() - { - return mTrafficSyncPattern; - } - - public String toString() - { - return getLabel(); - } - } + private String mLabel; + private SyncPattern mControlSyncPattern; + private SyncPattern mTrafficSyncPattern; - @Override - public void reset() - { - mControlMessageFramer.reset(); - mTrafficMessageFramer.reset(); - } + private Sync(String label, SyncPattern controlPattern, SyncPattern trafficPattern) + { + mLabel = label; + mControlSyncPattern = controlPattern; + mTrafficSyncPattern = trafficPattern; + } - @Override - public void start( ScheduledExecutorService executor ) - { - // TODO Auto-generated method stub - - } + public String getLabel() + { + return mLabel; + } - @Override - public void stop() - { - // TODO Auto-generated method stub - - } + public SyncPattern getControlSyncPattern() + { + return mControlSyncPattern; + } + + public SyncPattern getTrafficSyncPattern() + { + return mTrafficSyncPattern; + } + + public String toString() + { + return getLabel(); + } + } + + @Override + public void reset() + { + mControlMessageFramer.reset(); + mTrafficMessageFramer.reset(); + } + + @Override + public void start(ScheduledExecutorService executor) + { + // TODO Auto-generated method stub + } + + @Override + public void stop() + { + // TODO Auto-generated method stub + } } diff --git a/src/module/demodulate/audio/DemodulatedAudioFilterModule.java b/src/module/demodulate/audio/DemodulatedAudioFilterModule.java index 63da95da..532786ce 100644 --- a/src/module/demodulate/audio/DemodulatedAudioFilterModule.java +++ b/src/module/demodulate/audio/DemodulatedAudioFilterModule.java @@ -1,102 +1,118 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see + * sdrtrunk + * Copyright (C) 2014-2017 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ******************************************************************************/ package module.demodulate.audio; -import java.util.concurrent.ScheduledExecutorService; - -import module.Module; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sample.Listener; -import sample.real.IFilteredRealBufferProvider; -import sample.real.IUnFilteredRealBufferListener; -import sample.real.RealBuffer; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; import dsp.filter.dc.DCRemovalFilter_RealBuffer; import dsp.filter.fir.real.RealFIRFilter_RB_RB; +import module.Module; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sample.Listener; +import sample.real.IFilteredRealBufferProvider; +import sample.real.IUnFilteredRealBufferListener; +import sample.real.RealBuffer; -public class DemodulatedAudioFilterModule extends Module - implements IUnFilteredRealBufferListener, IFilteredRealBufferProvider +import java.util.concurrent.ScheduledExecutorService; + +public class DemodulatedAudioFilterModule extends Module implements IUnFilteredRealBufferListener, IFilteredRealBufferProvider { - private final static Logger mLog = LoggerFactory.getLogger( DemodulatedAudioFilterModule.class ); + private final static Logger mLog = LoggerFactory.getLogger(DemodulatedAudioFilterModule.class); - private DCRemovalFilter_RealBuffer mDCFilter = new DCRemovalFilter_RealBuffer(); - private RealFIRFilter_RB_RB mBandPassFilter; - /** - * Filters demodulated audio and removes any DC component from the buffer - * - * @param pass - audio pass band pass frequency - * @param stop - audio pass band stop frequency - */ - public DemodulatedAudioFilterModule( int pass, int stop ) - { - assert( stop > pass ); - -//TODO: change this to a band pass filter - mBandPassFilter = new RealFIRFilter_RB_RB( FilterFactory.getLowPass( 48000, pass, stop, 60, - WindowType.HANNING, true ), 1.0f ); - - mDCFilter.setListener( mBandPassFilter ); - } + private DCRemovalFilter_RealBuffer mDCFilter; + private RealFIRFilter_RB_RB mBandPassFilter; - @Override - public void dispose() - { - mBandPassFilter.dispose(); - mBandPassFilter = null; - - mDCFilter.dispose(); - mDCFilter = null; - } + /** + * Filters demodulated audio and removes any DC component from the buffer + * + * @param pass - audio pass band pass frequency + * @param stop - audio pass band stop frequency + */ + public DemodulatedAudioFilterModule(int pass, int stop) + { + assert (stop > pass); - @Override - public void reset() - { - } + mDCFilter = new DCRemovalFilter_RealBuffer(); - @Override - public void setFilteredRealBufferListener( Listener listener ) - { - mBandPassFilter.setListener( listener ); - } + //TODO: change this to a band pass filter + mBandPassFilter = new RealFIRFilter_RB_RB(FilterFactory.getLowPass(48000, pass, stop, 60, + WindowType.HANNING, true), 1.0f); - @Override - public void removeFilteredRealBufferListener() - { - mBandPassFilter.removeListener(); - } + mDCFilter.setListener(mBandPassFilter); + } - @Override - public void start( ScheduledExecutorService executor ) - { - } + /** + * Constructs a demodulated audio filter using the filter taps and gain provided + */ + public DemodulatedAudioFilterModule(float[] taps, float gain) + { + mBandPassFilter = new RealFIRFilter_RB_RB(taps, gain); + } - @Override - public void stop() - { - } + @Override + public void dispose() + { + mBandPassFilter.dispose(); + mBandPassFilter = null; - @Override - public Listener getUnFilteredRealBufferListener() - { - return mDCFilter; - } + mDCFilter.dispose(); + mDCFilter = null; + } + + @Override + public void reset() + { + } + + @Override + public void setFilteredRealBufferListener(Listener listener) + { + mBandPassFilter.setListener(listener); + } + + @Override + public void removeFilteredRealBufferListener() + { + mBandPassFilter.removeListener(); + } + + @Override + public void start(ScheduledExecutorService executor) + { + } + + @Override + public void stop() + { + } + + @Override + public Listener getUnFilteredRealBufferListener() + { + if(mDCFilter != null) + { + return mDCFilter; + } + else + { + return mBandPassFilter; + } + } } diff --git a/src/module/demodulate/fm/FMDemodulatorModule.java b/src/module/demodulate/fm/FMDemodulatorModule.java index 0b4033a0..8bac1bc4 100644 --- a/src/module/demodulate/fm/FMDemodulatorModule.java +++ b/src/module/demodulate/fm/FMDemodulatorModule.java @@ -1,112 +1,108 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014-2016 Dennis Sheirer - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ package module.demodulate.fm; -import java.util.concurrent.ScheduledExecutorService; - +import dsp.filter.FilterFactory; +import dsp.filter.Window.WindowType; +import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; +import dsp.fm.FMDemodulator_CB; import module.Module; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sample.Listener; import sample.complex.ComplexBuffer; import sample.complex.IComplexBufferListener; import sample.real.IUnFilteredRealBufferProvider; import sample.real.RealBuffer; -import dsp.filter.FilterFactory; -import dsp.filter.Window.WindowType; -import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; -import dsp.fm.FMDemodulator_CB; -public class FMDemodulatorModule extends Module - implements IComplexBufferListener, IUnFilteredRealBufferProvider +import java.util.concurrent.ScheduledExecutorService; + +public class FMDemodulatorModule extends Module implements IComplexBufferListener, IUnFilteredRealBufferProvider { - private final static Logger mLog = LoggerFactory.getLogger( FMDemodulatorModule.class ); - - private static final int SAMPLE_RATE = 48000; - - private ComplexFIRFilter_CB_CB mIQFilter; - private FMDemodulator_CB mDemodulator; - - /** - * FM Demodulator with I/Q filter. Demodulated output is unfiltered and - * may contain a DC component. - * - * @param pass - pass frequency for IQ filtering prior to demodulation. This - * frequency should be half of the signal bandwidth since the filter will - * be applied against each of the inphase and quadrature signals and the - * combined pass bandwidth will be twice this value. - * - * @param stop - stop frequency for IQ filtering prior to demodulation. - */ - public FMDemodulatorModule( int pass, int stop ) - { - assert( stop > pass ); + private final static Logger mLog = LoggerFactory.getLogger(FMDemodulatorModule.class); - mIQFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass( - SAMPLE_RATE, pass, stop, 60, WindowType.HAMMING, true ), 1.0f ); - - mDemodulator = new FMDemodulator_CB( 1.0f ); - mIQFilter.setListener( mDemodulator ); - } + private static final int SAMPLE_RATE = 48000; - @Override - public Listener getComplexBufferListener() - { - return mIQFilter; - } + private ComplexFIRFilter_CB_CB mIQFilter; + private FMDemodulator_CB mDemodulator; - @Override - public void dispose() - { - mIQFilter.dispose(); - mIQFilter = null; - - mDemodulator.dispose(); - mDemodulator = null; - } + /** + * FM Demodulator with I/Q filter. Demodulated output is unfiltered and + * may contain a DC component. + * + * @param pass - pass frequency for IQ filtering prior to demodulation. This + * frequency should be half of the signal bandwidth since the filter will + * be applied against each of the inphase and quadrature signals and the + * combined pass bandwidth will be twice this value. + * @param stop - stop frequency for IQ filtering prior to demodulation. + */ + public FMDemodulatorModule(int pass, int stop) + { + assert (stop > pass); - @Override - public void reset() - { - mDemodulator.reset(); - } + mIQFilter = new ComplexFIRFilter_CB_CB(FilterFactory.getLowPass( + SAMPLE_RATE, pass, stop, 60, WindowType.HAMMING, true), 1.0f); - @Override - public void setUnFilteredRealBufferListener( Listener listener ) - { - mDemodulator.setListener( listener ); - } + mDemodulator = new FMDemodulator_CB(1.0f); + mIQFilter.setListener(mDemodulator); + } - @Override - public void removeUnFilteredRealBufferListener() - { - mDemodulator.removeListener(); - } + @Override + public Listener getComplexBufferListener() + { + return mIQFilter; + } - @Override - public void start( ScheduledExecutorService executor ) - { - } + @Override + public void dispose() + { + mIQFilter.dispose(); + mIQFilter = null; - @Override - public void stop() - { - } + mDemodulator.dispose(); + mDemodulator = null; + } + + @Override + public void reset() + { + mDemodulator.reset(); + } + + @Override + public void setUnFilteredRealBufferListener(Listener listener) + { + mDemodulator.setListener(listener); + } + + @Override + public void removeUnFilteredRealBufferListener() + { + mDemodulator.removeListener(); + } + + @Override + public void start(ScheduledExecutorService executor) + { + } + + @Override + public void stop() + { + } } diff --git a/src/playlist/PlaylistManager.java b/src/playlist/PlaylistManager.java index 9163c5ef..e04cc477 100644 --- a/src/playlist/PlaylistManager.java +++ b/src/playlist/PlaylistManager.java @@ -166,9 +166,11 @@ public class PlaylistManager implements ChannelEventListener mAliasModel.addAliases(playlist.getAliases()); mBroadcastModel.addBroadcastConfigurations(playlist.getBroadcastConfigurations()); - mChannelModel.addChannels(playlist.getChannels()); mChannelMapModel.addChannelMaps(playlist.getChannelMaps()); + //Channel model has to be loaded last since it will auto-start channels that are enabled + mChannelModel.addChannels(playlist.getChannels()); + mPlaylistLoading = false; } }