mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 17:17:29 -05:00
Compare commits
12 Commits
v0.3.0-bet
...
v0.3.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea46a8b0e8 | ||
|
|
d510c34356 | ||
|
|
ae8a44caed | ||
|
|
1aa85a86ca | ||
|
|
17539d4d55 | ||
|
|
1db1d28390 | ||
|
|
37a941e904 | ||
|
|
57f6655824 | ||
|
|
4cd8f8209f | ||
|
|
61bfef5798 | ||
|
|
ef172a04c6 | ||
|
|
a877afcceb |
@@ -7,7 +7,7 @@
|
|||||||
<property name="major" value="0"/>
|
<property name="major" value="0"/>
|
||||||
<property name="minor" value="3"/>
|
<property name="minor" value="3"/>
|
||||||
<property name="patch" value="0"/>
|
<property name="patch" value="0"/>
|
||||||
<property name="release" value="-beta7"/>
|
<property name="release" value="-beta8"/>
|
||||||
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
|
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
|
||||||
|
|
||||||
<!-- Build properties -->
|
<!-- Build properties -->
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
loadSettings();
|
loadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the saved mixer configuration or a default configuration for audio playback.
|
||||||
|
*/
|
||||||
private void loadSettings()
|
private void loadSettings()
|
||||||
{
|
{
|
||||||
MixerChannelConfiguration configuration = null;
|
MixerChannelConfiguration configuration = null;
|
||||||
@@ -132,6 +135,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a default audio playback configuration with a mono audio playback channel.
|
||||||
|
*/
|
||||||
private MixerChannelConfiguration getDefaultConfiguration()
|
private MixerChannelConfiguration getDefaultConfiguration()
|
||||||
{
|
{
|
||||||
/* Use the system default mixer and mono channel as default startup */
|
/* Use the system default mixer and mono channel as default startup */
|
||||||
@@ -161,6 +167,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
mAudioOutputConnections.clear();
|
mAudioOutputConnections.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary ingest point for audio produced by all decoding channels, for distribution to audio playback devices.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized void receive(AudioPacket packet)
|
public synchronized void receive(AudioPacket packet)
|
||||||
{
|
{
|
||||||
@@ -176,11 +185,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
|
|
||||||
for(AudioOutputConnection connection : mAudioOutputConnections)
|
for(AudioOutputConnection connection : mAudioOutputConnections)
|
||||||
{
|
{
|
||||||
if(connection.isInactive() &&
|
if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
|
||||||
mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
|
|
||||||
{
|
{
|
||||||
mChannelConnectionMap.remove(connection.getChannelMetadataID());
|
mChannelConnectionMap.remove(connection.getChannelMetadataID());
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
|
mAvailableConnectionCount++;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,7 +201,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the lowest priority channel connection
|
* Identifies the lowest priority channel connection where the a higher value indicates a lower priority.
|
||||||
*/
|
*/
|
||||||
private void updateLowestPriorityAssignment()
|
private void updateLowestPriorityAssignment()
|
||||||
{
|
{
|
||||||
@@ -201,19 +210,32 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
for(AudioOutputConnection connection : mAudioOutputConnections)
|
for(AudioOutputConnection connection : mAudioOutputConnections)
|
||||||
{
|
{
|
||||||
if(connection.isConnected() &&
|
if(connection.isConnected() &&
|
||||||
(mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() > connection.getPriority()))
|
(mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() < connection.getPriority()))
|
||||||
{
|
{
|
||||||
mLowestPriorityConnection = connection;
|
mLowestPriorityConnection = connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures audio playback to use the configuration specified in the entry argument.
|
||||||
|
*
|
||||||
|
* @param entry to use in configuring the audio playback setup.
|
||||||
|
* @throws AudioException if there is an error
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException
|
public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException
|
||||||
{
|
{
|
||||||
setMixerChannelConfiguration(entry, true);
|
setMixerChannelConfiguration(entry, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures audio playback to use the configuration specified in the entry argument.
|
||||||
|
*
|
||||||
|
* @param entry to use in configuring the audio playback setup.
|
||||||
|
* @param saveSettings to save the audio playback configuration settings in the properties file.
|
||||||
|
* @throws AudioException if there is an error
|
||||||
|
*/
|
||||||
public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException
|
public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException
|
||||||
{
|
{
|
||||||
if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
|
if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
|
||||||
@@ -266,9 +288,8 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all channel assignments and terminates all audio outputs in
|
* Clears all channel assignments and terminates all audio outputs in preparation for complete shutdown or change
|
||||||
* preparation for complete shutdown or change to another mixer/channel
|
* to another mixer/channel configuration
|
||||||
* configuration
|
|
||||||
*/
|
*/
|
||||||
private void disposeCurrentConfiguration()
|
private void disposeCurrentConfiguration()
|
||||||
{
|
{
|
||||||
@@ -288,12 +309,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
mLowestPriorityConnection = null;
|
mLowestPriorityConnection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current audio playback mixer channel configuration setting.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
||||||
{
|
{
|
||||||
return mMixerChannelConfiguration;
|
return mMixerChannelConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of audio outputs available for the current mixer channel configuration
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<AudioOutput> getAudioOutputs()
|
public List<AudioOutput> getAudioOutputs()
|
||||||
{
|
{
|
||||||
@@ -311,12 +338,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
return outputs;
|
return outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an audio event listener to receive audio event notifications.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void addControllerListener(Listener<AudioEvent> listener)
|
public void addControllerListener(Listener<AudioEvent> listener)
|
||||||
{
|
{
|
||||||
mControllerBroadcaster.addListener(listener);
|
mControllerBroadcaster.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an audio event listener from receiving audio event notifications.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void removeControllerListener(Listener<AudioEvent> listener)
|
public void removeControllerListener(Listener<AudioEvent> listener)
|
||||||
{
|
{
|
||||||
@@ -324,16 +357,15 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an audio output connection for the packet if one is
|
* Returns an audio output connection for the packet if one is available, or overrides an existing lower priority
|
||||||
* available, or overrides an existing lower priority connection.
|
* connection. Returns null if no connection is available for the audio packet.
|
||||||
* Returns null if no connection is available for the audio packet.
|
|
||||||
*
|
*
|
||||||
* @param packet from a source
|
* @param audioPacket from a decoding channel source
|
||||||
* @return an audio output connection or null
|
* @return an audio output connection or null
|
||||||
*/
|
*/
|
||||||
private AudioOutputConnection getConnection(AudioPacket packet)
|
private AudioOutputConnection getConnection(AudioPacket audioPacket)
|
||||||
{
|
{
|
||||||
int channelMetadataID = packet.getMetadata().getMetadataID();
|
int channelMetadataID = audioPacket.getMetadata().getMetadataID();
|
||||||
|
|
||||||
//Use an existing connection
|
//Use an existing connection
|
||||||
if(mChannelConnectionMap.containsKey(channelMetadataID))
|
if(mChannelConnectionMap.containsKey(channelMetadataID))
|
||||||
@@ -341,37 +373,35 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
return mChannelConnectionMap.get(channelMetadataID);
|
return mChannelConnectionMap.get(channelMetadataID);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Connect to and use an available connection
|
//Connect to an unused, available connection
|
||||||
if(mAvailableConnectionCount > 0)
|
if(mAvailableConnectionCount > 0)
|
||||||
{
|
{
|
||||||
for(AudioOutputConnection connection : mAudioOutputConnections)
|
for(AudioOutputConnection connection : mAudioOutputConnections)
|
||||||
{
|
{
|
||||||
if(connection.isDisconnected())
|
if(connection.isDisconnected())
|
||||||
{
|
{
|
||||||
connection.connect(channelMetadataID);
|
connection.connect(channelMetadataID, audioPacket.getMetadata().getAudioPriority());
|
||||||
mChannelConnectionMap.put(channelMetadataID, connection);
|
mChannelConnectionMap.put(channelMetadataID, connection);
|
||||||
|
mAvailableConnectionCount--;
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Preempt an existing connection and connect it to this higher priority source
|
//Preempt an existing lower priority connection and connect when this is a higher priority packet
|
||||||
else //Check for a channel priority override
|
else
|
||||||
{
|
{
|
||||||
int priority = packet.getMetadata().getAudioPriority();
|
int priority = audioPacket.getMetadata().getAudioPriority();
|
||||||
|
|
||||||
if(mLowestPriorityConnection != null &&
|
AudioOutputConnection connection = mLowestPriorityConnection;
|
||||||
priority < mLowestPriorityConnection.getPriority())
|
|
||||||
|
if(connection != null && priority < connection.getPriority())
|
||||||
{
|
{
|
||||||
AudioOutputConnection connection = mLowestPriorityConnection;
|
|
||||||
|
|
||||||
mChannelConnectionMap.remove(connection.getChannelMetadataID());
|
mChannelConnectionMap.remove(connection.getChannelMetadataID());
|
||||||
|
|
||||||
connection.connect(channelMetadataID);
|
connection.connect(channelMetadataID, priority);
|
||||||
|
|
||||||
mChannelConnectionMap.put(channelMetadataID, connection);
|
mChannelConnectionMap.put(channelMetadataID, connection);
|
||||||
|
|
||||||
updateLowestPriorityAssignment();
|
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,7 +426,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
|
|
||||||
for(AudioPacket packet : packets)
|
for(AudioPacket packet : packets)
|
||||||
{
|
{
|
||||||
/* Don't process any packet's marked as do not monitor */
|
/* Don't process any packet's marked as do not monitor */
|
||||||
if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == AudioPacket.Type.AUDIO)
|
if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == AudioPacket.Type.AUDIO)
|
||||||
{
|
{
|
||||||
AudioOutputConnection connection = getConnection(packet);
|
AudioOutputConnection connection = getConnection(packet);
|
||||||
@@ -494,9 +524,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
* Connects this assignment to the indicated source so that audio
|
* Connects this assignment to the indicated source so that audio
|
||||||
* packets from this source can be sent to the audio output
|
* packets from this source can be sent to the audio output
|
||||||
*/
|
*/
|
||||||
public void connect(int source)
|
public void connect(int source, int priority)
|
||||||
{
|
{
|
||||||
mChannelMetadataID = source;
|
mChannelMetadataID = source;
|
||||||
|
mPriority = priority;
|
||||||
|
updateLowestPriorityAssignment();
|
||||||
|
|
||||||
mAudioOutput.updateTimestamp();
|
mAudioOutput.updateTimestamp();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent>
|
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent>
|
||||||
{
|
{
|
||||||
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.0000" );
|
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" );
|
||||||
|
|
||||||
public static final int COLUMN_STATE = 0;
|
public static final int COLUMN_STATE = 0;
|
||||||
public static final int COLUMN_DECODER = 1;
|
public static final int COLUMN_DECODER = 1;
|
||||||
|
|||||||
@@ -36,14 +36,19 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import sample.real.IOverflowListener;
|
import sample.real.IOverflowListener;
|
||||||
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
|
import source.tuner.frequency.IFrequencyChangeListener;
|
||||||
|
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
|
||||||
|
|
||||||
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
|
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
|
||||||
IDecoderStateEventProvider, ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener
|
IDecoderStateEventProvider, IFrequencyChangeListener, ISquelchStateProvider, IAttributeChangeRequestListener,
|
||||||
|
IOverflowListener
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
|
||||||
|
|
||||||
@@ -61,6 +66,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
|||||||
private ChannelType mChannelType;
|
private ChannelType mChannelType;
|
||||||
private TrafficChannelManager mTrafficChannelEndListener;
|
private TrafficChannelManager mTrafficChannelEndListener;
|
||||||
private CallEvent mTrafficChannelCallEvent;
|
private CallEvent mTrafficChannelCallEvent;
|
||||||
|
private FrequencyChangeListener mFrequencyChangeListener;
|
||||||
|
|
||||||
private boolean mSquelchLocked = false;
|
private boolean mSquelchLocked = false;
|
||||||
private boolean mSelected = false;
|
private boolean mSelected = false;
|
||||||
@@ -189,6 +195,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
|||||||
mStateMonitor = null;
|
mStateMonitor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
if(mFrequencyChangeListener == null)
|
||||||
|
{
|
||||||
|
mFrequencyChangeListener = new FrequencyChangeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFrequencyChangeListener;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isStandardChannel()
|
private boolean isStandardChannel()
|
||||||
{
|
{
|
||||||
return mChannelType == ChannelType.STANDARD;
|
return mChannelType == ChannelType.STANDARD;
|
||||||
@@ -532,6 +549,23 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
|
|||||||
broadcast(mTrafficChannelCallEvent);
|
broadcast(mTrafficChannelCallEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener to receive frequency change events and rebroadcast them to the decoder states
|
||||||
|
*/
|
||||||
|
public class FrequencyChangeListener implements Listener<FrequencyChangeEvent>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void receive(FrequencyChangeEvent frequencyChangeEvent)
|
||||||
|
{
|
||||||
|
if(frequencyChangeEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
|
||||||
|
{
|
||||||
|
long frequency = frequencyChangeEvent.getValue().longValue();
|
||||||
|
|
||||||
|
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DecoderStateEvent receiver wrapper
|
* DecoderStateEvent receiver wrapper
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,28 +1,24 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* sdrtrunk
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package controller.channel;
|
package controller.channel;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAttribute;
|
import controller.config.Configuration;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
|
||||||
import javax.xml.bind.annotation.XmlSeeAlso;
|
|
||||||
import javax.xml.bind.annotation.XmlTransient;
|
|
||||||
|
|
||||||
import module.decode.DecoderFactory;
|
import module.decode.DecoderFactory;
|
||||||
import module.decode.DecoderType;
|
import module.decode.DecoderType;
|
||||||
import module.decode.config.AuxDecodeConfiguration;
|
import module.decode.config.AuxDecodeConfiguration;
|
||||||
@@ -31,6 +27,7 @@ import module.log.EventLogType;
|
|||||||
import module.log.config.EventLogConfiguration;
|
import module.log.config.EventLogConfiguration;
|
||||||
import record.RecorderType;
|
import record.RecorderType;
|
||||||
import record.config.RecordConfiguration;
|
import record.config.RecordConfiguration;
|
||||||
|
import sample.Listener;
|
||||||
import source.SourceType;
|
import source.SourceType;
|
||||||
import source.config.SourceConfigFactory;
|
import source.config.SourceConfigFactory;
|
||||||
import source.config.SourceConfigRecording;
|
import source.config.SourceConfigRecording;
|
||||||
@@ -40,424 +37,444 @@ import source.tuner.TunerChannel;
|
|||||||
import source.tuner.TunerChannel.Type;
|
import source.tuner.TunerChannel.Type;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent;
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||||
import source.tuner.frequency.IFrequencyChangeProcessor;
|
|
||||||
import controller.config.Configuration;
|
|
||||||
|
|
||||||
@XmlSeeAlso( { Configuration.class } )
|
import javax.xml.bind.annotation.XmlAttribute;
|
||||||
@XmlRootElement( name = "channel" )
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
public class Channel extends Configuration implements IFrequencyChangeProcessor
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||||
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
|
||||||
|
@XmlSeeAlso({Configuration.class})
|
||||||
|
@XmlRootElement(name = "channel")
|
||||||
|
public class Channel extends Configuration implements Listener<FrequencyChangeEvent>
|
||||||
{
|
{
|
||||||
// Standard channels are persisted and traffic channels are temporary
|
// Standard channels are persisted and traffic channels are temporary
|
||||||
public enum ChannelType { STANDARD, TRAFFIC };
|
public enum ChannelType
|
||||||
|
{
|
||||||
|
STANDARD, TRAFFIC
|
||||||
|
}
|
||||||
|
|
||||||
// Static unique channel identifier tracking
|
;
|
||||||
private static int UNIQUE_ID = 0;
|
|
||||||
|
|
||||||
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
|
|
||||||
private AuxDecodeConfiguration mAuxDecodeConfiguration =
|
|
||||||
new AuxDecodeConfiguration();
|
|
||||||
private SourceConfiguration mSourceConfiguration =
|
|
||||||
SourceConfigFactory.getDefaultSourceConfiguration();
|
|
||||||
private EventLogConfiguration mEventLogConfiguration =
|
|
||||||
new EventLogConfiguration();
|
|
||||||
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
|
|
||||||
|
|
||||||
private String mAliasListName;
|
// Static unique channel identifier tracking
|
||||||
private String mSystem = "System";
|
private static int UNIQUE_ID = 0;
|
||||||
|
|
||||||
|
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
|
||||||
|
private AuxDecodeConfiguration mAuxDecodeConfiguration =
|
||||||
|
new AuxDecodeConfiguration();
|
||||||
|
private SourceConfiguration mSourceConfiguration =
|
||||||
|
SourceConfigFactory.getDefaultSourceConfiguration();
|
||||||
|
private EventLogConfiguration mEventLogConfiguration =
|
||||||
|
new EventLogConfiguration();
|
||||||
|
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
|
||||||
|
|
||||||
|
private String mAliasListName;
|
||||||
|
private String mSystem = "System";
|
||||||
private String mSite = "Site";
|
private String mSite = "Site";
|
||||||
private String mName = "Channel";
|
private String mName = "Channel";
|
||||||
|
|
||||||
private boolean mEnabled;
|
|
||||||
private boolean mSelected;
|
|
||||||
private TunerChannel mTunerChannel = null;
|
|
||||||
|
|
||||||
private ChannelType mChannelType = ChannelType.STANDARD;
|
|
||||||
|
|
||||||
private int mChannelID;
|
private boolean mEnabled;
|
||||||
private int mChannelFrequencyCorrection = 0;
|
private boolean mSelected;
|
||||||
|
private TunerChannel mTunerChannel = null;
|
||||||
/**
|
|
||||||
* Channel represents a complete set of configurations needed to setup and
|
|
||||||
* manage a processing chain and/or manage as a set of business objects that
|
|
||||||
* can be displayed in graphical components.
|
|
||||||
*
|
|
||||||
* @param channelName
|
|
||||||
* @param channelType
|
|
||||||
*/
|
|
||||||
public Channel( String channelName, ChannelType channelType )
|
|
||||||
{
|
|
||||||
this( channelName );
|
|
||||||
mChannelType = channelType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private ChannelType mChannelType = ChannelType.STANDARD;
|
||||||
* Constructs a new standard channel with the specified channel name
|
|
||||||
*/
|
|
||||||
public Channel( String channelName )
|
|
||||||
{
|
|
||||||
this();
|
|
||||||
mName = channelName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private int mChannelID;
|
||||||
* Constructs a new standard channel with a default name of "Channel"
|
private int mChannelFrequencyCorrection = 0;
|
||||||
*/
|
|
||||||
public Channel()
|
|
||||||
{
|
|
||||||
mChannelID = UNIQUE_ID++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a (new) deep copy of this channel
|
* Channel represents a complete set of configurations needed to setup and
|
||||||
*/
|
* manage a processing chain and/or manage as a set of business objects that
|
||||||
public Channel copyOf()
|
* can be displayed in graphical components.
|
||||||
{
|
*
|
||||||
Channel channel = new Channel( mName );
|
* @param channelName
|
||||||
channel.setSystem( mSystem );
|
* @param channelType
|
||||||
channel.setSite( mSite );
|
*/
|
||||||
channel.setAliasListName( mAliasListName );
|
public Channel(String channelName, ChannelType channelType)
|
||||||
|
{
|
||||||
|
this(channelName);
|
||||||
|
mChannelType = channelType;
|
||||||
|
}
|
||||||
|
|
||||||
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
|
/**
|
||||||
|
* Constructs a new standard channel with the specified channel name
|
||||||
for( DecoderType auxType: aux.getAuxDecoders() )
|
*/
|
||||||
{
|
public Channel(String channelName)
|
||||||
aux.addAuxDecoder( auxType );
|
{
|
||||||
}
|
this();
|
||||||
|
mName = channelName;
|
||||||
channel.setAuxDecodeConfiguration( aux );
|
}
|
||||||
|
|
||||||
channel.setDecodeConfiguration( DecoderFactory.copy( mDecodeConfiguration ) );
|
|
||||||
|
|
||||||
EventLogConfiguration log = new EventLogConfiguration();
|
|
||||||
|
|
||||||
for( EventLogType logType: mEventLogConfiguration.getLoggers() )
|
|
||||||
{
|
|
||||||
log.addLogger( logType );
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.setEventLogConfiguration( log );
|
|
||||||
|
|
||||||
RecordConfiguration record = new RecordConfiguration();
|
/**
|
||||||
|
* Constructs a new standard channel with a default name of "Channel"
|
||||||
for( RecorderType recordType: mRecordConfiguration.getRecorders() )
|
*/
|
||||||
{
|
public Channel()
|
||||||
record.addRecorder( recordType );
|
{
|
||||||
}
|
mChannelID = UNIQUE_ID++;
|
||||||
|
}
|
||||||
channel.setRecordConfiguration( record );
|
|
||||||
|
/**
|
||||||
|
* Creates a (new) deep copy of this channel
|
||||||
|
*/
|
||||||
|
public Channel copyOf()
|
||||||
|
{
|
||||||
|
Channel channel = new Channel(mName);
|
||||||
|
channel.setSystem(mSystem);
|
||||||
|
channel.setSite(mSite);
|
||||||
|
channel.setAliasListName(mAliasListName);
|
||||||
|
|
||||||
|
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
|
||||||
|
|
||||||
|
for(DecoderType auxType : aux.getAuxDecoders())
|
||||||
|
{
|
||||||
|
aux.addAuxDecoder(auxType);
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.setAuxDecodeConfiguration(aux);
|
||||||
|
|
||||||
|
channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration));
|
||||||
|
|
||||||
|
EventLogConfiguration log = new EventLogConfiguration();
|
||||||
|
|
||||||
|
for(EventLogType logType : mEventLogConfiguration.getLoggers())
|
||||||
|
{
|
||||||
|
log.addLogger(logType);
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.setEventLogConfiguration(log);
|
||||||
|
|
||||||
|
RecordConfiguration record = new RecordConfiguration();
|
||||||
|
|
||||||
|
for(RecorderType recordType : mRecordConfiguration.getRecorders())
|
||||||
|
{
|
||||||
|
record.addRecorder(recordType);
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.setRecordConfiguration(record);
|
||||||
|
|
||||||
|
channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration));
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique identifier for this channel. Value is transient (not persisted).
|
||||||
|
*/
|
||||||
|
@XmlTransient
|
||||||
|
public int getChannelID()
|
||||||
|
{
|
||||||
|
return mChannelID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this channel has been selected for prioritized audio output
|
||||||
|
* and display of processing chain products
|
||||||
|
*/
|
||||||
|
@XmlTransient
|
||||||
|
public boolean isSelected()
|
||||||
|
{
|
||||||
|
return mSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets selection status of the channel.
|
||||||
|
*
|
||||||
|
* NOTE: this method is package private so that only the ChannelSelectionManager
|
||||||
|
* can toggle the selection state. Use the channel model to broadcast a
|
||||||
|
* request select/deselect channel event.
|
||||||
|
*
|
||||||
|
* @param selected
|
||||||
|
*/
|
||||||
|
void setSelected(boolean selected)
|
||||||
|
{
|
||||||
|
mSelected = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlTransient
|
||||||
|
public ChannelType getChannelType()
|
||||||
|
{
|
||||||
|
return mChannelType;
|
||||||
|
}
|
||||||
|
|
||||||
channel.setSourceConfiguration( SourceConfigFactory.copy( mSourceConfiguration ) );
|
|
||||||
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unique identifier for this channel. Value is transient (not persisted).
|
|
||||||
*/
|
|
||||||
@XmlTransient
|
|
||||||
public int getChannelID()
|
|
||||||
{
|
|
||||||
return mChannelID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if this channel has been selected for prioritized audio output
|
|
||||||
* and display of processing chain products
|
|
||||||
*/
|
|
||||||
@XmlTransient
|
|
||||||
public boolean isSelected()
|
|
||||||
{
|
|
||||||
return mSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets selection status of the channel.
|
|
||||||
*
|
|
||||||
* NOTE: this method is package private so that only the ChannelSelectionManager
|
|
||||||
* can toggle the selection state. Use the channel model to broadcast a
|
|
||||||
* request select/deselect channel event.
|
|
||||||
*
|
|
||||||
* @param selected
|
|
||||||
*/
|
|
||||||
void setSelected( boolean selected )
|
|
||||||
{
|
|
||||||
mSelected = selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
@XmlTransient
|
|
||||||
public ChannelType getChannelType()
|
|
||||||
{
|
|
||||||
return mChannelType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the owning system for this channel.
|
* Returns the owning system for this channel.
|
||||||
*/
|
*/
|
||||||
@XmlAttribute( name = "system" )
|
@XmlAttribute(name = "system")
|
||||||
public String getSystem()
|
public String getSystem()
|
||||||
{
|
{
|
||||||
return mSystem;
|
return mSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSystem( String system )
|
|
||||||
{
|
|
||||||
mSystem = system;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasSystem()
|
public void setSystem(String system)
|
||||||
{
|
{
|
||||||
return mSystem != null;
|
mSystem = system;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean hasSystem()
|
||||||
* Returns the owning site for this channel.
|
{
|
||||||
*/
|
return mSystem != null;
|
||||||
@XmlAttribute( name = "site" )
|
}
|
||||||
public String getSite()
|
|
||||||
{
|
/**
|
||||||
return mSite;
|
* Returns the owning site for this channel.
|
||||||
}
|
*/
|
||||||
|
@XmlAttribute(name = "site")
|
||||||
public void setSite( String site )
|
public String getSite()
|
||||||
{
|
{
|
||||||
mSite = site;
|
return mSite;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasSite()
|
public void setSite(String site)
|
||||||
{
|
{
|
||||||
return mSite != null;
|
mSite = site;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean hasSite()
|
||||||
* Returns the name of this channel.
|
{
|
||||||
*/
|
return mSite != null;
|
||||||
@XmlAttribute( name = "name" )
|
}
|
||||||
public String getName()
|
|
||||||
{
|
/**
|
||||||
return mName;
|
* Returns the name of this channel.
|
||||||
}
|
*/
|
||||||
|
@XmlAttribute(name = "name")
|
||||||
/**
|
public String getName()
|
||||||
* Sets the name of this channel.
|
{
|
||||||
*/
|
return mName;
|
||||||
public void setName( String name )
|
}
|
||||||
{
|
|
||||||
mName = name;
|
/**
|
||||||
}
|
* Sets the name of this channel.
|
||||||
|
*/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default display string for this channel: SYSTEM_SITE_NAME
|
* Default display string for this channel: SYSTEM_SITE_NAME
|
||||||
*/
|
*/
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append( hasSystem() ? getSystem() : "SYSTEM" );
|
sb.append(hasSystem() ? getSystem() : "SYSTEM");
|
||||||
sb.append( "_" );
|
sb.append("_");
|
||||||
sb.append( hasSite() ? getSite() : "SITE" );
|
sb.append(hasSite() ? getSite() : "SITE");
|
||||||
sb.append( "_" );
|
sb.append("_");
|
||||||
sb.append( getName() );
|
sb.append(getName());
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return sb.toString();
|
||||||
* Indicates if this channel is enabled for processing.
|
}
|
||||||
*/
|
|
||||||
@XmlAttribute( name = "enabled" )
|
|
||||||
public boolean getEnabled()
|
|
||||||
{
|
|
||||||
return mEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this channel will automatically start processing on
|
* Indicates if this channel is enabled for processing.
|
||||||
* application startup or if this channel is currently processing after
|
*/
|
||||||
* application startup.
|
@XmlAttribute(name = "enabled")
|
||||||
*
|
public boolean getEnabled()
|
||||||
* Note: this method is package private and is intended to be managed
|
{
|
||||||
* by a co-package central processing manager
|
return mEnabled;
|
||||||
*
|
}
|
||||||
* @see controller.ChannelProcessingManager
|
|
||||||
*/
|
|
||||||
void setEnabled( boolean enabled )
|
|
||||||
{
|
|
||||||
mEnabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the alias list that is used by this channel for looking up alias
|
* Indicates if this channel will automatically start processing on
|
||||||
* values for the various identifiers produced by the decoder(s).
|
* application startup or if this channel is currently processing after
|
||||||
*/
|
* application startup.
|
||||||
@XmlElement( name = "alias_list_name" )
|
*
|
||||||
public String getAliasListName()
|
* Note: this method is package private and is intended to be managed
|
||||||
{
|
* by a co-package central processing manager
|
||||||
return mAliasListName;
|
*
|
||||||
}
|
* @see controller.channel.ChannelProcessingManager
|
||||||
|
*/
|
||||||
|
void setEnabled(boolean enabled)
|
||||||
|
{
|
||||||
|
mEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the alias list to be used for looking up the alias values for the
|
* Returns the alias list that is used by this channel for looking up alias
|
||||||
* various identifiers produced by the decoder
|
* values for the various identifiers produced by the decoder(s).
|
||||||
*/
|
*/
|
||||||
public void setAliasListName( String name )
|
@XmlElement(name = "alias_list_name")
|
||||||
{
|
public String getAliasListName()
|
||||||
mAliasListName = name;
|
{
|
||||||
}
|
return mAliasListName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the primary decoder configuration used by this channel
|
* Sets the alias list to be used for looking up the alias values for the
|
||||||
*/
|
* various identifiers produced by the decoder
|
||||||
@XmlElement( name = "decode_configuration" )
|
*/
|
||||||
public DecodeConfiguration getDecodeConfiguration()
|
public void setAliasListName(String name)
|
||||||
{
|
{
|
||||||
return mDecodeConfiguration;
|
mAliasListName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the primary decoder configuration used by this channel
|
* Gets the primary decoder configuration used by this channel
|
||||||
*/
|
*/
|
||||||
public void setDecodeConfiguration( DecodeConfiguration config )
|
@XmlElement(name = "decode_configuration")
|
||||||
{
|
public DecodeConfiguration getDecodeConfiguration()
|
||||||
mDecodeConfiguration = config;
|
{
|
||||||
}
|
return mDecodeConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the aux decoder configuration used by this channel. Aux decoders
|
* Sets the primary decoder configuration used by this channel
|
||||||
* operate on demodulated audio
|
*/
|
||||||
*/
|
public void setDecodeConfiguration(DecodeConfiguration config)
|
||||||
@XmlElement( name = "aux_decode_configuration" )
|
{
|
||||||
public AuxDecodeConfiguration getAuxDecodeConfiguration()
|
mDecodeConfiguration = config;
|
||||||
{
|
}
|
||||||
return mAuxDecodeConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the decoder configuration used by this channel
|
* Gets the aux decoder configuration used by this channel. Aux decoders
|
||||||
*/
|
* operate on demodulated audio
|
||||||
public void setAuxDecodeConfiguration( AuxDecodeConfiguration config )
|
*/
|
||||||
{
|
@XmlElement(name = "aux_decode_configuration")
|
||||||
mAuxDecodeConfiguration = config;
|
public AuxDecodeConfiguration getAuxDecodeConfiguration()
|
||||||
}
|
{
|
||||||
|
return mAuxDecodeConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the source configuration for this channel. A channel source
|
* Sets the decoder configuration used by this channel
|
||||||
* identifies where the processing chain will get source sample data from.
|
*/
|
||||||
*/
|
public void setAuxDecodeConfiguration(AuxDecodeConfiguration config)
|
||||||
@XmlElement( name = "source_configuration" )
|
{
|
||||||
public SourceConfiguration getSourceConfiguration()
|
mAuxDecodeConfiguration = config;
|
||||||
{
|
}
|
||||||
return mSourceConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the source configuration for this channel.
|
* Returns the source configuration for this channel. A channel source
|
||||||
*/
|
* identifies where the processing chain will get source sample data from.
|
||||||
public void setSourceConfiguration( SourceConfiguration config )
|
*/
|
||||||
{
|
@XmlElement(name = "source_configuration")
|
||||||
mSourceConfiguration = config;
|
public SourceConfiguration getSourceConfiguration()
|
||||||
|
{
|
||||||
|
return mSourceConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
//Clear the tune channel object so that it can be recreated if the
|
/**
|
||||||
//source configuration changes
|
* Sets the source configuration for this channel.
|
||||||
mTunerChannel = null;
|
*/
|
||||||
}
|
public void setSourceConfiguration(SourceConfiguration config)
|
||||||
|
{
|
||||||
|
mSourceConfiguration = config;
|
||||||
|
|
||||||
/**
|
//Clear the tune channel object so that it can be recreated if the
|
||||||
* Returns the event logger configuration for this channel.
|
//source configuration changes
|
||||||
*/
|
mTunerChannel = null;
|
||||||
@XmlElement( name = "event_log_configuration" )
|
}
|
||||||
public EventLogConfiguration getEventLogConfiguration()
|
|
||||||
{
|
|
||||||
return mEventLogConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the event logger configuration for this channel.
|
* Returns the event logger configuration for this channel.
|
||||||
*/
|
*/
|
||||||
public void setEventLogConfiguration( EventLogConfiguration config )
|
@XmlElement(name = "event_log_configuration")
|
||||||
{
|
public EventLogConfiguration getEventLogConfiguration()
|
||||||
mEventLogConfiguration = config;
|
{
|
||||||
}
|
return mEventLogConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the recorder configuration for this channel.
|
* Sets the event logger configuration for this channel.
|
||||||
*/
|
*/
|
||||||
@XmlElement( name = "record_configuration" )
|
public void setEventLogConfiguration(EventLogConfiguration config)
|
||||||
public RecordConfiguration getRecordConfiguration()
|
{
|
||||||
{
|
mEventLogConfiguration = config;
|
||||||
return mRecordConfiguration;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the recorder configuration for this channel.
|
* Returns the recorder configuration for this channel.
|
||||||
*/
|
*/
|
||||||
public void setRecordConfiguration( RecordConfiguration config )
|
@XmlElement(name = "record_configuration")
|
||||||
{
|
public RecordConfiguration getRecordConfiguration()
|
||||||
mRecordConfiguration = config;
|
{
|
||||||
}
|
return mRecordConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to construct a tuner channel object representing a
|
* Sets the recorder configuration for this channel.
|
||||||
* tuner or recording source frequency and bandwidth that can be used by
|
*/
|
||||||
* application components for graphically representing this channel.
|
public void setRecordConfiguration(RecordConfiguration config)
|
||||||
*
|
{
|
||||||
* If the source configuration is not a tuner or recording, this method
|
mRecordConfiguration = config;
|
||||||
* returns null.
|
}
|
||||||
*/
|
|
||||||
@XmlTransient
|
/**
|
||||||
|
* Convenience method to construct a tuner channel object representing a
|
||||||
|
* tuner or recording source frequency and bandwidth that can be used by
|
||||||
|
* application components for graphically representing this channel.
|
||||||
|
*
|
||||||
|
* If the source configuration is not a tuner or recording, this method
|
||||||
|
* returns null.
|
||||||
|
*/
|
||||||
|
@XmlTransient
|
||||||
public TunerChannel getTunerChannel()
|
public TunerChannel getTunerChannel()
|
||||||
{
|
{
|
||||||
if( mTunerChannel == null )
|
if(mTunerChannel == null)
|
||||||
{
|
{
|
||||||
if( mSourceConfiguration.getSourceType() == SourceType.TUNER )
|
if(mSourceConfiguration.getSourceType() == SourceType.TUNER)
|
||||||
{
|
{
|
||||||
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
|
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
|
||||||
|
|
||||||
mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(),
|
mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
|
||||||
mDecodeConfiguration.getDecoderType().getChannelBandwidth() );
|
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
|
||||||
}
|
}
|
||||||
else if( mSourceConfiguration.getSourceType() == SourceType.RECORDING )
|
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
|
||||||
{
|
{
|
||||||
SourceConfigRecording config =
|
SourceConfigRecording config =
|
||||||
(SourceConfigRecording)mSourceConfiguration;
|
(SourceConfigRecording)mSourceConfiguration;
|
||||||
|
|
||||||
|
mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
|
||||||
|
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(),
|
|
||||||
mDecodeConfiguration.getDecoderType().getChannelBandwidth() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mTunerChannel;
|
return mTunerChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to indicate if any part of this channel is contained
|
* Convenience method to indicate if any part of this channel is contained
|
||||||
* within the minimum and maximum frequency values.
|
* within the minimum and maximum frequency values.
|
||||||
*/
|
*/
|
||||||
public boolean isWithin( long minimum, long maximum )
|
public boolean isWithin(long minimum, long maximum)
|
||||||
{
|
{
|
||||||
TunerChannel tunerChannel = getTunerChannel();
|
TunerChannel tunerChannel = getTunerChannel();
|
||||||
|
|
||||||
return tunerChannel != null &&
|
|
||||||
tunerChannel.overlaps( minimum, maximum );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method to make the current channel frequency correction value
|
|
||||||
* available for use outside of a constructed processing chain while maintaining
|
|
||||||
* linkage to the source channel. This hack allows the correction value to
|
|
||||||
* be used to visually show in the spectral display.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void frequencyChanged( FrequencyChangeEvent event )
|
|
||||||
{
|
|
||||||
if( event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE )
|
|
||||||
{
|
|
||||||
mChannelFrequencyCorrection = event.getValue().intValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@XmlTransient
|
return tunerChannel != null && tunerChannel.overlaps(minimum, maximum);
|
||||||
public int getChannelFrequencyCorrection()
|
}
|
||||||
{
|
|
||||||
return mChannelFrequencyCorrection;
|
/**
|
||||||
}
|
* Convenience method to make the current channel frequency correction value
|
||||||
|
* available for use outside of a constructed processing chain while maintaining
|
||||||
|
* linkage to the source channel. This hack allows the correction value to
|
||||||
|
* be used to visually show in the spectral display.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(FrequencyChangeEvent event)
|
||||||
|
{
|
||||||
|
if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
|
||||||
|
{
|
||||||
|
mChannelFrequencyCorrection = event.getValue().intValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current channel frequency correction value (when this channel is processing)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@XmlTransient
|
||||||
|
public int getChannelFrequencyCorrection()
|
||||||
|
{
|
||||||
|
return mChannelFrequencyCorrection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets frequency correction value to 0
|
||||||
|
*/
|
||||||
|
public void resetFrequencyCorrection()
|
||||||
|
{
|
||||||
|
mChannelFrequencyCorrection = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ public class ChannelProcessingManager implements ChannelEventListener
|
|||||||
|
|
||||||
if(processingChain == null)
|
if(processingChain == null)
|
||||||
{
|
{
|
||||||
processingChain = new ProcessingChain(channel.getName(), channel.getChannelType());
|
processingChain = new ProcessingChain(channel.getChannelType());
|
||||||
|
|
||||||
/* Register global listeners */
|
/* Register global listeners */
|
||||||
for(Listener<AudioPacket> listener : mAudioPacketListeners)
|
for(Listener<AudioPacket> listener : mAudioPacketListeners)
|
||||||
@@ -244,6 +244,9 @@ public class ChannelProcessingManager implements ChannelEventListener
|
|||||||
processingChain.addMessageListener(listener);
|
processingChain.addMessageListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Register channel to receive frequency correction events to show in the spectral display (hack!)
|
||||||
|
processingChain.addFrequencyChangeListener(channel);
|
||||||
|
|
||||||
/* Processing Modules */
|
/* Processing Modules */
|
||||||
List<Module> modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this,
|
List<Module> modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this,
|
||||||
mAliasModel, channel, processingChain.getChannelState().getMutableMetadata());
|
mAliasModel, channel, processingChain.getChannelState().getMutableMetadata());
|
||||||
@@ -347,6 +350,9 @@ public class ChannelProcessingManager implements ChannelEventListener
|
|||||||
|
|
||||||
processingChain.removeRecordingModules();
|
processingChain.removeRecordingModules();
|
||||||
|
|
||||||
|
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
|
||||||
|
processingChain.removeFrequencyChangeListener(channel);
|
||||||
|
channel.resetFrequencyCorrection();
|
||||||
|
|
||||||
mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP));
|
mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP));
|
||||||
|
|
||||||
|
|||||||
@@ -1,408 +1,412 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* sdrtrunk
|
||||||
* Copyright (C) 2015 Dennis Sheirer
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package dsp.filter.cic;
|
package dsp.filter.cic;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import dsp.filter.FilterFactory;
|
||||||
import java.util.List;
|
import dsp.filter.Filters;
|
||||||
|
import dsp.filter.Window.WindowType;
|
||||||
|
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||||
|
import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import sample.complex.ComplexBuffer;
|
import sample.complex.ComplexBuffer;
|
||||||
import sample.complex.ComplexSampleListener;
|
import sample.complex.ComplexSampleListener;
|
||||||
import sample.complex.ComplexToComplexBufferAssembler;
|
import sample.complex.ComplexToComplexBufferAssembler;
|
||||||
import sample.decimator.ComplexDecimator;
|
import sample.decimator.ComplexDecimator;
|
||||||
import dsp.filter.FilterFactory;
|
|
||||||
import dsp.filter.Filters;
|
import java.util.ArrayList;
|
||||||
import dsp.filter.Window.WindowType;
|
import java.util.List;
|
||||||
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
|
||||||
import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
|
|
||||||
|
|
||||||
public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
|
public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
|
||||||
{
|
{
|
||||||
public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,
|
public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
|
||||||
59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,
|
59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
|
||||||
157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,
|
157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
|
||||||
257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,
|
257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359,
|
||||||
367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,
|
367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
|
||||||
467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,
|
467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593,
|
||||||
599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691 };
|
599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691};
|
||||||
|
|
||||||
private ArrayList<DecimatingStage> mDecimatingStages =
|
|
||||||
new ArrayList<DecimatingStage>();
|
|
||||||
|
|
||||||
private DecimatingStage mFirstDecimatingStage;
|
|
||||||
|
|
||||||
private Output mOutput;
|
private ArrayList<DecimatingStage> mDecimatingStages =
|
||||||
|
new ArrayList<DecimatingStage>();
|
||||||
|
|
||||||
/**
|
private DecimatingStage mFirstDecimatingStage;
|
||||||
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
|
|
||||||
* decimated, single paired i/q float sample output.
|
|
||||||
*
|
|
||||||
* Implements the CIC filter described in Understanding Digital Signal
|
|
||||||
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
|
|
||||||
* decimating stages each with a prime factor decimation rate. Multiple
|
|
||||||
* stages are cascaded to achieve the overall decimation rate.
|
|
||||||
*
|
|
||||||
* This filter supports a maximum decimation rate of 700. This filter can
|
|
||||||
* be adapted to higher decimation rates by adding additional prime factors
|
|
||||||
* to the PRIMES array.
|
|
||||||
*
|
|
||||||
* @param decimation - overall decimation rate
|
|
||||||
* @param order - filter order
|
|
||||||
*/
|
|
||||||
public ComplexPrimeCICDecimate( int decimation, int order,
|
|
||||||
int passFrequency, int attenuation, WindowType windowType )
|
|
||||||
{
|
|
||||||
Validate.isTrue(decimation <= 700);
|
|
||||||
|
|
||||||
|
|
||||||
List<Integer> stageSizes = getPrimeFactors( decimation );
|
private Output mOutput;
|
||||||
|
|
||||||
for( int x = 0; x < stageSizes.size(); x++ )
|
|
||||||
{
|
|
||||||
DecimatingStage stage = new DecimatingStage( stageSizes.get( x ), order );
|
|
||||||
|
|
||||||
mDecimatingStages.add( stage );
|
|
||||||
|
|
||||||
if( x == 0 )
|
|
||||||
{
|
|
||||||
/* Reference to first stage -- will receive all samples */
|
|
||||||
mFirstDecimatingStage = stage;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Wire the current stage to the previous stage */
|
|
||||||
mDecimatingStages.get( x - 1 ).setListener( stage );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mOutput = new Output( 48000, passFrequency, attenuation, windowType );
|
|
||||||
|
|
||||||
mDecimatingStages.get( mDecimatingStages.size() - 1 )
|
|
||||||
.setListener( mOutput );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
for( DecimatingStage stage: mDecimatingStages )
|
|
||||||
{
|
|
||||||
stage.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
mDecimatingStages.clear();
|
|
||||||
mDecimatingStages = null;
|
|
||||||
mFirstDecimatingStage = null;
|
|
||||||
|
|
||||||
mOutput.dispose();
|
|
||||||
mOutput = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a listener to receive the output of this CIC decimation filter
|
|
||||||
*/
|
|
||||||
public void setListener( Listener<ComplexBuffer> listener )
|
|
||||||
{
|
|
||||||
mOutput.setListener( listener );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes listener from output of this CIC decimation filter
|
|
||||||
*/
|
|
||||||
public void removeListener()
|
|
||||||
{
|
|
||||||
mOutput.removeListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the prime factors of the decimation rate up to a maximum
|
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
|
||||||
* decimation rate of 700. If you wish to have decimation rates higher
|
* decimated, single paired i/q float sample output.
|
||||||
* than 700, then add additional prime factors to the PRIMES array.
|
*
|
||||||
*
|
* Implements the CIC filter described in Understanding Digital Signal
|
||||||
* @param decimation - integral decimation rate
|
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
|
||||||
* @return - ordered list (smallest to largest) of prime factors
|
* decimating stages each with a prime factor decimation rate. Multiple
|
||||||
*/
|
* stages are cascaded to achieve the overall decimation rate.
|
||||||
public static List<Integer> getPrimeFactors( int decimation )
|
*
|
||||||
{
|
* This filter supports a maximum decimation rate of 700. This filter can
|
||||||
ArrayList<Integer> stages = new ArrayList<Integer>();
|
* be adapted to higher decimation rates by adding additional prime factors
|
||||||
|
* to the PRIMES array.
|
||||||
int pointer = 0;
|
*
|
||||||
|
* @param decimation - overall decimation rate
|
||||||
while( decimation > 0 && pointer < PRIMES.length )
|
* @param order - filter order
|
||||||
{
|
*/
|
||||||
int prime = PRIMES[ pointer ];
|
public ComplexPrimeCICDecimate(int decimation, int order,
|
||||||
|
int passFrequency, int attenuation, WindowType windowType)
|
||||||
if( decimation % prime == 0 )
|
|
||||||
{
|
|
||||||
stages.add( prime );
|
|
||||||
|
|
||||||
decimation /= prime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pointer++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void receive( ComplexBuffer buffer )
|
|
||||||
{
|
{
|
||||||
if( mFirstDecimatingStage != null )
|
Validate.isTrue(decimation <= 700);
|
||||||
{
|
|
||||||
float[] samples = buffer.getSamples();
|
|
||||||
|
|
||||||
for( int x = 0; x < samples.length; x += 2 )
|
|
||||||
{
|
|
||||||
mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decimating stage combines multiple CIC stages with a decimator. The
|
|
||||||
* number of stages is indicated by the order value and the size indicates
|
|
||||||
* the decimation rate of this stage.
|
|
||||||
*/
|
|
||||||
public class DecimatingStage implements ComplexSampleListener
|
|
||||||
{
|
|
||||||
private ArrayList<Stage> mStages = new ArrayList<Stage>();
|
|
||||||
private Stage mFirstStage;
|
|
||||||
private ComplexDecimator mDecimator;
|
|
||||||
|
|
||||||
public DecimatingStage( int size, int order )
|
|
||||||
{
|
|
||||||
for( int x = 0; x < order; x++ )
|
|
||||||
{
|
|
||||||
Stage stage;
|
|
||||||
|
|
||||||
if( size == 2 )
|
|
||||||
{
|
|
||||||
stage = new TwoStage();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stage = new Stage( size );
|
|
||||||
}
|
|
||||||
|
|
||||||
mStages.add( stage );
|
|
||||||
|
|
||||||
if( x == 0 )
|
|
||||||
{
|
|
||||||
mFirstStage = stage;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mStages.get( x - 1 ).setListener( stage );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mDecimator = new ComplexDecimator( size );
|
|
||||||
|
|
||||||
mStages.get( mStages.size() - 1 ).setListener( mDecimator );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
for( Stage stage: mStages )
|
|
||||||
{
|
|
||||||
stage.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
mStages.clear();
|
|
||||||
mDecimator.dispose();
|
|
||||||
mDecimator = null;
|
|
||||||
mFirstStage = null;
|
|
||||||
mStages = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive( float i, float q )
|
List<Integer> stageSizes = getPrimeFactors(decimation);
|
||||||
|
|
||||||
|
for(int x = 0; x < stageSizes.size(); x++)
|
||||||
{
|
{
|
||||||
mFirstStage.receive( i, q );
|
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
|
||||||
}
|
|
||||||
|
|
||||||
public void setListener( ComplexSampleListener listener )
|
|
||||||
{
|
|
||||||
mDecimator.setListener( listener );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Single non-decimating CIC stage component. Uses a circular buffer and a
|
|
||||||
* running average internally to implement the stage so that stage size has
|
|
||||||
* essentially no impact on the computational requirements of the stage
|
|
||||||
*/
|
|
||||||
public class Stage implements ComplexSampleListener
|
|
||||||
{
|
|
||||||
protected ComplexSampleListener mListener;
|
|
||||||
|
|
||||||
private float[] mISamples;
|
|
||||||
private float[] mQSamples;
|
|
||||||
|
|
||||||
protected float mISum;
|
|
||||||
protected float mQSum;
|
|
||||||
|
|
||||||
private int mSamplePointer = 0;
|
|
||||||
private int mSize;
|
|
||||||
|
|
||||||
protected float mGain;
|
|
||||||
|
|
||||||
public Stage()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stage( int size )
|
mDecimatingStages.add(stage);
|
||||||
{
|
|
||||||
mSize = size - 1;
|
if(x == 0)
|
||||||
|
{
|
||||||
mISamples = new float[ mSize ];
|
/* Reference to first stage -- will receive all samples */
|
||||||
mQSamples = new float[ mSize ];
|
mFirstDecimatingStage = stage;
|
||||||
|
}
|
||||||
mGain = 1.0f / (float)size;
|
else
|
||||||
}
|
{
|
||||||
|
/* Wire the current stage to the previous stage */
|
||||||
public void dispose()
|
mDecimatingStages.get(x - 1).setListener(stage);
|
||||||
{
|
}
|
||||||
mListener = null;
|
}
|
||||||
}
|
|
||||||
|
mOutput = new Output(48000, passFrequency, attenuation, windowType);
|
||||||
public void receive( float i, float q )
|
|
||||||
{
|
mDecimatingStages.get(mDecimatingStages.size() - 1)
|
||||||
|
.setListener(mOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
for(DecimatingStage stage : mDecimatingStages)
|
||||||
|
{
|
||||||
|
stage.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mDecimatingStages.clear();
|
||||||
|
mDecimatingStages = null;
|
||||||
|
mFirstDecimatingStage = null;
|
||||||
|
|
||||||
|
mOutput.dispose();
|
||||||
|
mOutput = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener to receive the output of this CIC decimation filter
|
||||||
|
*/
|
||||||
|
public void setListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mOutput.setListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes listener from output of this CIC decimation filter
|
||||||
|
*/
|
||||||
|
public void removeListener()
|
||||||
|
{
|
||||||
|
mOutput.removeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the prime factors of the decimation rate up to a maximum
|
||||||
|
* decimation rate of 700. If you wish to have decimation rates higher
|
||||||
|
* than 700, then add additional prime factors to the PRIMES array.
|
||||||
|
*
|
||||||
|
* @param decimation - integral decimation rate
|
||||||
|
* @return - ordered list (smallest to largest) of prime factors
|
||||||
|
*/
|
||||||
|
public static List<Integer> getPrimeFactors(int decimation)
|
||||||
|
{
|
||||||
|
ArrayList<Integer> stages = new ArrayList<Integer>();
|
||||||
|
|
||||||
|
int pointer = 0;
|
||||||
|
|
||||||
|
while(decimation > 0 && pointer < PRIMES.length)
|
||||||
|
{
|
||||||
|
int prime = PRIMES[pointer];
|
||||||
|
|
||||||
|
if(decimation % prime == 0)
|
||||||
|
{
|
||||||
|
stages.add(prime);
|
||||||
|
|
||||||
|
decimation /= prime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pointer++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(ComplexBuffer buffer)
|
||||||
|
{
|
||||||
|
if(mFirstDecimatingStage != null)
|
||||||
|
{
|
||||||
|
float[] samples = buffer.getSamples();
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.length; x += 2)
|
||||||
|
{
|
||||||
|
mFirstDecimatingStage.receive(samples[x], samples[x + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decimating stage combines multiple CIC stages with a decimator. The
|
||||||
|
* number of stages is indicated by the order value and the size indicates
|
||||||
|
* the decimation rate of this stage.
|
||||||
|
*/
|
||||||
|
public class DecimatingStage implements ComplexSampleListener
|
||||||
|
{
|
||||||
|
private ArrayList<Stage> mStages = new ArrayList<Stage>();
|
||||||
|
private Stage mFirstStage;
|
||||||
|
private ComplexDecimator mDecimator;
|
||||||
|
|
||||||
|
public DecimatingStage(int size, int order)
|
||||||
|
{
|
||||||
|
for(int x = 0; x < order; x++)
|
||||||
|
{
|
||||||
|
Stage stage;
|
||||||
|
|
||||||
|
if(size == 2)
|
||||||
|
{
|
||||||
|
stage = new TwoStage();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stage = new Stage(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
mStages.add(stage);
|
||||||
|
|
||||||
|
if(x == 0)
|
||||||
|
{
|
||||||
|
mFirstStage = stage;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mStages.get(x - 1).setListener(stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mDecimator = new ComplexDecimator(size);
|
||||||
|
|
||||||
|
mStages.get(mStages.size() - 1).setListener(mDecimator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
for(Stage stage : mStages)
|
||||||
|
{
|
||||||
|
stage.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mStages.clear();
|
||||||
|
mDecimator.dispose();
|
||||||
|
mDecimator = null;
|
||||||
|
mFirstStage = null;
|
||||||
|
mStages = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(float i, float q)
|
||||||
|
{
|
||||||
|
mFirstStage.receive(i, q);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(ComplexSampleListener listener)
|
||||||
|
{
|
||||||
|
mDecimator.setListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single non-decimating CIC stage component. Uses a circular buffer and a
|
||||||
|
* running average internally to implement the stage so that stage size has
|
||||||
|
* essentially no impact on the computational requirements of the stage
|
||||||
|
*/
|
||||||
|
public class Stage implements ComplexSampleListener
|
||||||
|
{
|
||||||
|
protected ComplexSampleListener mListener;
|
||||||
|
|
||||||
|
private float[] mISamples;
|
||||||
|
private float[] mQSamples;
|
||||||
|
|
||||||
|
protected float mISum;
|
||||||
|
protected float mQSum;
|
||||||
|
|
||||||
|
private int mSamplePointer = 0;
|
||||||
|
private int mSize;
|
||||||
|
|
||||||
|
protected float mGain;
|
||||||
|
|
||||||
|
public Stage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stage(int size)
|
||||||
|
{
|
||||||
|
mSize = size - 1;
|
||||||
|
|
||||||
|
mISamples = new float[mSize];
|
||||||
|
mQSamples = new float[mSize];
|
||||||
|
|
||||||
|
mGain = 1.0f / (float)size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
mListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receive(float i, float q)
|
||||||
|
{
|
||||||
/* Subtract the oldest sample and add back in the newest */
|
/* Subtract the oldest sample and add back in the newest */
|
||||||
mISum = mISum - mISamples[ mSamplePointer ] + i;
|
mISum = mISum - mISamples[mSamplePointer] + i;
|
||||||
mQSum = mQSum - mQSamples[ mSamplePointer ] + q;
|
mQSum = mQSum - mQSamples[mSamplePointer] + q;
|
||||||
|
|
||||||
/* Overwrite the oldest sample with the newest */
|
/* Overwrite the oldest sample with the newest */
|
||||||
mISamples[ mSamplePointer ] = i;
|
mISamples[mSamplePointer] = i;
|
||||||
mQSamples[ mSamplePointer ] = q;
|
mQSamples[mSamplePointer] = q;
|
||||||
|
|
||||||
mSamplePointer++;
|
|
||||||
|
|
||||||
if( mSamplePointer >= mSize )
|
|
||||||
{
|
|
||||||
mSamplePointer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mListener != null )
|
mSamplePointer++;
|
||||||
{
|
|
||||||
mListener.receive( ( mISum * mGain ), ( mQSum * mGain ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setListener( ComplexSampleListener listener )
|
|
||||||
{
|
|
||||||
mListener = listener;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Size 2 stage that removes the unnecessary circular buffer management for
|
|
||||||
* a two-stage.
|
|
||||||
*/
|
|
||||||
public class TwoStage extends Stage
|
|
||||||
{
|
|
||||||
public TwoStage()
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
|
|
||||||
mGain = 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void receive( float i, float q )
|
|
||||||
{
|
|
||||||
float iSum = mISum + i;
|
|
||||||
float qSum = mQSum + q;
|
|
||||||
|
|
||||||
mISum = i;
|
|
||||||
mQSum = q;
|
|
||||||
|
|
||||||
if( mListener != null )
|
|
||||||
{
|
|
||||||
mListener.receive( ( iSum * mGain ), ( qSum * mGain ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
if(mSamplePointer >= mSize)
|
||||||
* Output adapter - applies gain correction and cleanup filter and broadcast
|
{
|
||||||
* to registered listener.
|
mSamplePointer = 0;
|
||||||
*/
|
}
|
||||||
public class Output implements ComplexSampleListener
|
|
||||||
{
|
|
||||||
/* Decimated output buffers will contain 1024 samples */
|
|
||||||
private ComplexToComplexBufferAssembler mAssembler =
|
|
||||||
new ComplexToComplexBufferAssembler( 2048 );
|
|
||||||
|
|
||||||
private ComplexFIRFilter_CB_CB mCleanupFilter;
|
if(mListener != null)
|
||||||
private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
|
{
|
||||||
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false );
|
mListener.receive((mISum * mGain), (mQSum * mGain));
|
||||||
|
}
|
||||||
public Output( int outputSampleRate, int passFrequency, int attenuation,
|
|
||||||
WindowType windowType )
|
|
||||||
{
|
|
||||||
mCleanupFilter = new ComplexFIRFilter_CB_CB( FilterFactory
|
|
||||||
.getCICCleanupFilter( outputSampleRate,
|
|
||||||
passFrequency,
|
|
||||||
attenuation,
|
|
||||||
windowType ), 0.4f );
|
|
||||||
|
|
||||||
mAssembler.setListener( mCleanupFilter );
|
|
||||||
mCleanupFilter.setListener( mHalfBandFilter );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
mAssembler.dispose();
|
|
||||||
mCleanupFilter.dispose();
|
|
||||||
mHalfBandFilter.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receiver method for the output adapter to receive a filtered,
|
|
||||||
* decimated sample, apply gain correction, apply cleanup filtering
|
|
||||||
* and output the sample values as a complex sample.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void receive( float inphase, float quadrature )
|
|
||||||
{
|
|
||||||
mAssembler.receive( inphase, quadrature );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a listener to receive output samples
|
|
||||||
*/
|
|
||||||
public void setListener( Listener<ComplexBuffer> listener )
|
|
||||||
{
|
|
||||||
mHalfBandFilter.setListener( listener );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setListener(ComplexSampleListener listener)
|
||||||
* Removes the listener from receiving output samples
|
{
|
||||||
*/
|
mListener = listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size 2 stage that removes the unnecessary circular buffer management for
|
||||||
|
* a two-stage.
|
||||||
|
*/
|
||||||
|
public class TwoStage extends Stage
|
||||||
|
{
|
||||||
|
public TwoStage()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
mGain = 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receive(float i, float q)
|
||||||
|
{
|
||||||
|
float iSum = mISum + i;
|
||||||
|
float qSum = mQSum + q;
|
||||||
|
|
||||||
|
mISum = i;
|
||||||
|
mQSum = q;
|
||||||
|
|
||||||
|
if(mListener != null)
|
||||||
|
{
|
||||||
|
mListener.receive((iSum * mGain), (qSum * mGain));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output adapter - applies gain correction and cleanup filter and broadcast
|
||||||
|
* to registered listener.
|
||||||
|
*/
|
||||||
|
public class Output implements ComplexSampleListener
|
||||||
|
{
|
||||||
|
/* Decimated output buffers will contain 1024 samples */
|
||||||
|
private ComplexToComplexBufferAssembler mAssembler =
|
||||||
|
new ComplexToComplexBufferAssembler(2048);
|
||||||
|
|
||||||
|
private ComplexFIRFilter_CB_CB mCleanupFilter;
|
||||||
|
private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
|
||||||
|
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
|
||||||
|
|
||||||
|
public Output(int outputSampleRate, int passFrequency, int attenuation,
|
||||||
|
WindowType windowType)
|
||||||
|
{
|
||||||
|
mCleanupFilter = new ComplexFIRFilter_CB_CB(FilterFactory
|
||||||
|
.getCICCleanupFilter(outputSampleRate,
|
||||||
|
passFrequency,
|
||||||
|
attenuation,
|
||||||
|
windowType), 0.4f);
|
||||||
|
|
||||||
|
//Bypassing the CIC cleanup filter for now
|
||||||
|
mAssembler.setListener(mHalfBandFilter);
|
||||||
|
|
||||||
|
// mAssembler.setListener( mCleanupFilter );
|
||||||
|
// mCleanupFilter.setListener( mHalfBandFilter );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
mAssembler.dispose();
|
||||||
|
mCleanupFilter.dispose();
|
||||||
|
mHalfBandFilter.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receiver method for the output adapter to receive a filtered,
|
||||||
|
* decimated sample, apply gain correction, apply cleanup filtering
|
||||||
|
* and output the sample values as a complex sample.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
mAssembler.receive(inphase, quadrature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener to receive output samples
|
||||||
|
*/
|
||||||
|
public void setListener(Listener<ComplexBuffer> listener)
|
||||||
|
{
|
||||||
|
mHalfBandFilter.setListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the listener from receiving output samples
|
||||||
|
*/
|
||||||
public void removeListener()
|
public void removeListener()
|
||||||
{
|
{
|
||||||
mHalfBandFilter.removeListener();
|
mHalfBandFilter.removeListener();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,82 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
package instrument;
|
package instrument;
|
||||||
|
|
||||||
|
import controller.channel.Channel.ChannelType;
|
||||||
import instrument.tap.Tap;
|
import instrument.tap.Tap;
|
||||||
import instrument.tap.TapGroup;
|
import instrument.tap.TapGroup;
|
||||||
|
import module.Module;
|
||||||
|
import module.ProcessingChain;
|
||||||
|
import source.Source;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import module.Module;
|
|
||||||
import module.ProcessingChain;
|
|
||||||
import source.Source;
|
|
||||||
import controller.channel.Channel.ChannelType;
|
|
||||||
|
|
||||||
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
|
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
|
||||||
{
|
{
|
||||||
public InstrumentableProcessingChain()
|
public InstrumentableProcessingChain()
|
||||||
{
|
{
|
||||||
super( "instrumented", ChannelType.STANDARD );
|
super(ChannelType.STANDARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSource( Source source ) throws IllegalStateException
|
public void setSource(Source source) throws IllegalStateException
|
||||||
{
|
{
|
||||||
mSource = source;
|
mSource = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TapGroup> getTapGroups()
|
public List<TapGroup> getTapGroups()
|
||||||
{
|
{
|
||||||
List<TapGroup> groups = new ArrayList<>();
|
List<TapGroup> groups = new ArrayList<>();
|
||||||
|
|
||||||
for( Module module: getModules() )
|
|
||||||
{
|
|
||||||
if( module instanceof Instrumentable )
|
|
||||||
{
|
|
||||||
groups.addAll( ((Instrumentable)module).getTapGroups() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
for(Module module : getModules())
|
||||||
public void registerTap( Tap tap )
|
{
|
||||||
{
|
if(module instanceof Instrumentable)
|
||||||
for( Module module: getModules() )
|
{
|
||||||
{
|
groups.addAll(((Instrumentable)module).getTapGroups());
|
||||||
if( module instanceof Instrumentable )
|
}
|
||||||
{
|
}
|
||||||
((Instrumentable)module).registerTap( tap );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return groups;
|
||||||
public void unregisterTap( Tap tap )
|
}
|
||||||
{
|
|
||||||
for( Module module: getModules() )
|
@Override
|
||||||
{
|
public void registerTap(Tap tap)
|
||||||
if( module instanceof Instrumentable )
|
{
|
||||||
{
|
for(Module module : getModules())
|
||||||
((Instrumentable)module).unregisterTap( tap );
|
{
|
||||||
}
|
if(module instanceof Instrumentable)
|
||||||
}
|
{
|
||||||
}
|
((Instrumentable)module).registerTap(tap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterTap(Tap tap)
|
||||||
|
{
|
||||||
|
for(Module module : getModules())
|
||||||
|
{
|
||||||
|
if(module instanceof Instrumentable)
|
||||||
|
{
|
||||||
|
((Instrumentable)module).unregisterTap(tap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
|
private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
|
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
private String mName;
|
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
|
|
||||||
protected Source mSource;
|
protected Source mSource;
|
||||||
@@ -120,13 +119,10 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
/**
|
/**
|
||||||
* Creates a processing chain for managing a set of modules
|
* Creates a processing chain for managing a set of modules
|
||||||
*
|
*
|
||||||
* @param name for thread pool naming each thread
|
|
||||||
* @param channelType
|
* @param channelType
|
||||||
*/
|
*/
|
||||||
public ProcessingChain(String name, ChannelType channelType)
|
public ProcessingChain(ChannelType channelType)
|
||||||
{
|
{
|
||||||
mName = name;
|
|
||||||
|
|
||||||
mChannelState = new ChannelState(channelType);
|
mChannelState = new ChannelState(channelType);
|
||||||
addModule(mChannelState);
|
addModule(mChannelState);
|
||||||
|
|
||||||
@@ -211,6 +207,8 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
mSource = source;
|
mSource = source;
|
||||||
|
|
||||||
|
addModule(mSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -557,25 +555,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
+ "sample type - cannot start processing chain");
|
+ "sample type - cannot start processing chain");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If this is a tuner source, broadcast the frequency to all
|
|
||||||
* of the decoder state's and start the samples flowing */
|
|
||||||
if(mSource instanceof TunerChannelSource)
|
|
||||||
{
|
|
||||||
TunerChannelSource tcs = (TunerChannelSource) mSource;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
long frequency = tcs.getFrequency();
|
|
||||||
|
|
||||||
mDecoderStateEventBroadcaster.broadcast(
|
|
||||||
new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, State.IDLE, frequency));
|
|
||||||
}
|
|
||||||
catch(SourceException e)
|
|
||||||
{
|
|
||||||
mLog.error("Error getting frequency from tuner channel source", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Start each of the modules */
|
/* Start each of the modules */
|
||||||
for(Module module : mModules)
|
for(Module module : mModules)
|
||||||
{
|
{
|
||||||
@@ -588,8 +567,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
mLog.error("Error starting module", e);
|
mLog.error("Error starting module", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mSource.start(ThreadPool.SCHEDULED);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -606,9 +583,16 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
{
|
{
|
||||||
if(mRunning.compareAndSet(true, false))
|
if(mRunning.compareAndSet(true, false))
|
||||||
{
|
{
|
||||||
|
/* Stop each of the modules */
|
||||||
|
for(Module module : mModules)
|
||||||
|
{
|
||||||
|
module.stop();
|
||||||
|
}
|
||||||
|
|
||||||
if(mSource != null)
|
if(mSource != null)
|
||||||
{
|
{
|
||||||
mSource.stop();
|
removeModule(mSource);
|
||||||
|
|
||||||
mSource.setOverflowListener(null);
|
mSource.setOverflowListener(null);
|
||||||
|
|
||||||
switch(mSource.getSampleType())
|
switch(mSource.getSampleType())
|
||||||
@@ -629,11 +613,6 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
mSource = null;
|
mSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stop each of the modules */
|
|
||||||
for(Module module : mModules)
|
|
||||||
{
|
|
||||||
module.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,6 +739,24 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
mMessageBroadcaster.removeListener(listener);
|
mMessageBroadcaster.removeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the listener to receive frequency change events from the processing chain
|
||||||
|
* @param listener to receive events
|
||||||
|
*/
|
||||||
|
public void addFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
|
{
|
||||||
|
mFrequencyChangeEventBroadcaster.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the listener from receiving frequency change events
|
||||||
|
* @param listener to remove
|
||||||
|
*/
|
||||||
|
public void removeFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
|
{
|
||||||
|
mFrequencyChangeEventBroadcaster.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the listener to receive call events from all modules.
|
* Adds the listener to receive call events from all modules.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,21 +9,21 @@ import instrument.tap.stream.FloatTap;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
import sample.real.RealBuffer;
|
import sample.real.RealBuffer;
|
||||||
import sample.real.RealSampleListener;
|
import sample.real.RealSampleListener;
|
||||||
import sample.real.RealSampleProvider;
|
import sample.real.RealSampleProvider;
|
||||||
|
import source.tuner.TunerChannelSource;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent;
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||||
import source.tuner.frequency.IFrequencyChangeListener;
|
import source.tuner.frequency.IFrequencyChangeListener;
|
||||||
import dsp.gain.DirectGainControl;
|
import dsp.gain.DirectGainControl;
|
||||||
|
|
||||||
public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
public class C4FMSymbolFilter implements Listener<RealBuffer>, IFrequencyChangeListener, RealSampleProvider, Instrumentable
|
||||||
IFrequencyChangeListener,
|
|
||||||
RealSampleProvider,
|
|
||||||
Instrumentable
|
|
||||||
{
|
{
|
||||||
private static final float TAPS[][] =
|
private static final float TAPS[][] =
|
||||||
{
|
{
|
||||||
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128
|
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128
|
||||||
{ -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128
|
{ -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128
|
||||||
@@ -198,8 +198,7 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
|||||||
|
|
||||||
private RealSampleListener mListener;
|
private RealSampleListener mListener;
|
||||||
|
|
||||||
private DirectGainControl mGainController =
|
private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
|
||||||
new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
|
|
||||||
|
|
||||||
private int mFrequencyAdjustmentRequested = 0;
|
private int mFrequencyAdjustmentRequested = 0;
|
||||||
private int mFrequencyCorrection = 0;
|
private int mFrequencyCorrection = 0;
|
||||||
@@ -258,9 +257,8 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
|||||||
correction = -mFrequencyCorrectionMaximum;
|
correction = -mFrequencyCorrectionMaximum;
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcast( new FrequencyChangeEvent(
|
broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
|
||||||
Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
|
|
||||||
|
|
||||||
mFrequencyAdjustmentRequested = 0;
|
mFrequencyAdjustmentRequested = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -452,6 +450,10 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts a frequency change event to the registered listener
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
public void broadcast( FrequencyChangeEvent event )
|
public void broadcast( FrequencyChangeEvent event )
|
||||||
{
|
{
|
||||||
if( mFrequencyChangeListener != null )
|
if( mFrequencyChangeListener != null )
|
||||||
@@ -459,7 +461,11 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
|
|||||||
mFrequencyChangeListener.receive( event );
|
mFrequencyChangeListener.receive( event );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the listener for frequency change events.
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
|
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
|
||||||
{
|
{
|
||||||
mFrequencyChangeListener = listener;
|
mFrequencyChangeListener = listener;
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import alias.AliasList;
|
|||||||
import instrument.Instrumentable;
|
import instrument.Instrumentable;
|
||||||
import module.decode.Decoder;
|
import module.decode.Decoder;
|
||||||
import module.decode.DecoderType;
|
import module.decode.DecoderType;
|
||||||
|
import source.tuner.frequency.IFrequencyChangeListener;
|
||||||
|
import source.tuner.frequency.IFrequencyChangeProvider;
|
||||||
|
|
||||||
public abstract class P25Decoder extends Decoder implements Instrumentable
|
public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable
|
||||||
{
|
{
|
||||||
private P25MessageProcessor mMessageProcessor;
|
private P25MessageProcessor mMessageProcessor;
|
||||||
|
|
||||||
|
|||||||
@@ -689,8 +689,7 @@ public class P25MessageFramer implements Listener<Dibit>
|
|||||||
private LSMDemodulator mDemodulator;
|
private LSMDemodulator mDemodulator;
|
||||||
private double mCorrection;
|
private double mCorrection;
|
||||||
|
|
||||||
public CostasPhaseErrorDetector( FrameSync frameSync,
|
public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator demodulator, double correction )
|
||||||
LSMDemodulator demodulator, double correction )
|
|
||||||
{
|
{
|
||||||
super( frameSync.getSync() );
|
super( frameSync.getSync() );
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* sdrtrunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package module.decode.p25;
|
package module.decode.p25;
|
||||||
|
|
||||||
@@ -41,8 +42,7 @@ import dsp.filter.FilterFactory;
|
|||||||
import dsp.filter.Window.WindowType;
|
import dsp.filter.Window.WindowType;
|
||||||
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
|
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
|
||||||
|
|
||||||
public class P25_C4FMDecoder extends P25Decoder
|
public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
|
||||||
implements IFrequencyChangeListener, IFrequencyChangeProvider,IFilteredRealBufferListener
|
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
|
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
|||||||
import dsp.gain.ComplexFeedForwardGainControl;
|
import dsp.gain.ComplexFeedForwardGainControl;
|
||||||
import dsp.psk.LSMDemodulator;
|
import dsp.psk.LSMDemodulator;
|
||||||
import dsp.psk.QPSKPolarSlicer;
|
import dsp.psk.QPSKPolarSlicer;
|
||||||
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
|
|
||||||
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
||||||
{
|
{
|
||||||
@@ -56,8 +57,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
|||||||
private List<TapGroup> mAvailableTaps;
|
private List<TapGroup> mAvailableTaps;
|
||||||
|
|
||||||
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
||||||
private ComplexBufferToStreamConverter mStreamConverter =
|
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
|
||||||
new ComplexBufferToStreamConverter();
|
|
||||||
private ComplexFeedForwardGainControl mAGC =
|
private ComplexFeedForwardGainControl mAGC =
|
||||||
new ComplexFeedForwardGainControl( 32 );
|
new ComplexFeedForwardGainControl( 32 );
|
||||||
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
|
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
|
||||||
@@ -107,8 +107,31 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
|||||||
mMessageFramer.dispose();
|
mMessageFramer.dispose();
|
||||||
mMessageFramer = null;
|
mMessageFramer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
return new Listener<FrequencyChangeEvent>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void receive(FrequencyChangeEvent frequencyChangeEvent)
|
||||||
|
{
|
||||||
|
//Ignored
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||||
{
|
{
|
||||||
return mBasebandFilter;
|
return mBasebandFilter;
|
||||||
|
|||||||
@@ -25,11 +25,15 @@ import properties.SystemProperties;
|
|||||||
import record.wave.ComplexBufferWaveRecorder;
|
import record.wave.ComplexBufferWaveRecorder;
|
||||||
import record.wave.RealBufferWaveRecorder;
|
import record.wave.RealBufferWaveRecorder;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
|
import sample.OverflowableTransferQueue;
|
||||||
|
import sample.real.IOverflowListener;
|
||||||
import util.ThreadPool;
|
import util.ThreadPool;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -42,7 +46,8 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds
|
public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds
|
||||||
|
|
||||||
private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>();
|
private Map<String,RealBufferWaveRecorder> mRecorders = new HashMap<>();
|
||||||
private ScheduledFuture<?> mRecorderMonitorFuture;
|
private OverflowableTransferQueue<AudioPacket> mAudioPacketQueue = new OverflowableTransferQueue<>(1000, 100);
|
||||||
|
private ScheduledFuture<?> mBufferProcessorFuture;
|
||||||
|
|
||||||
private boolean mCanStartNewRecorders = true;
|
private boolean mCanStartNewRecorders = true;
|
||||||
|
|
||||||
@@ -55,8 +60,24 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
*/
|
*/
|
||||||
public RecorderManager()
|
public RecorderManager()
|
||||||
{
|
{
|
||||||
mRecorderMonitorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecorderMonitor(), 2,
|
mAudioPacketQueue.setOverflowListener(new IOverflowListener()
|
||||||
2, TimeUnit.SECONDS);
|
{
|
||||||
|
@Override
|
||||||
|
public void sourceOverflow(boolean overflow)
|
||||||
|
{
|
||||||
|
if(overflow)
|
||||||
|
{
|
||||||
|
mLog.warn("overflow - audio packets will be dropped until recording catches up");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("audio recorder packet processing has returned to normal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mBufferProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferProcessor(), 0,
|
||||||
|
1, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,9 +85,9 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
*/
|
*/
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
if(mRecorderMonitorFuture != null)
|
if(mBufferProcessorFuture != null)
|
||||||
{
|
{
|
||||||
mRecorderMonitorFuture.cancel(true);
|
mBufferProcessorFuture.cancel(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,10 +100,25 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
{
|
{
|
||||||
if(audioPacket.hasMetadata() && audioPacket.getMetadata().isRecordable())
|
if(audioPacket.hasMetadata() && audioPacket.getMetadata().isRecordable())
|
||||||
{
|
{
|
||||||
String identifier = audioPacket.getMetadata().getUniqueIdentifier();
|
mAudioPacketQueue.offer(audioPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
synchronized(mRecorders)
|
/**
|
||||||
|
* Process any queued audio buffers and dispatch them to the audio recorders
|
||||||
|
*/
|
||||||
|
private void processBuffers()
|
||||||
|
{
|
||||||
|
List<AudioPacket> audioPackets = new ArrayList<>();
|
||||||
|
|
||||||
|
mAudioPacketQueue.drainTo(audioPackets, 50);
|
||||||
|
|
||||||
|
while(!audioPackets.isEmpty())
|
||||||
|
{
|
||||||
|
for(AudioPacket audioPacket: audioPackets)
|
||||||
{
|
{
|
||||||
|
String identifier = audioPacket.getMetadata().getUniqueIdentifier();
|
||||||
|
|
||||||
if(mRecorders.containsKey(identifier))
|
if(mRecorders.containsKey(identifier))
|
||||||
{
|
{
|
||||||
RealBufferWaveRecorder recorder = mRecorders.get(identifier);
|
RealBufferWaveRecorder recorder = mRecorders.get(identifier);
|
||||||
@@ -129,6 +165,29 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audioPackets.clear();
|
||||||
|
mAudioPacketQueue.drainTo(audioPackets, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes recorders that have not received any new audio buffers in the last 6 seconds.
|
||||||
|
*/
|
||||||
|
private void removeIdleRecorders()
|
||||||
|
{
|
||||||
|
Iterator<Map.Entry<String,RealBufferWaveRecorder>> it = mRecorders.entrySet().iterator();
|
||||||
|
|
||||||
|
while(it.hasNext())
|
||||||
|
{
|
||||||
|
Map.Entry<String,RealBufferWaveRecorder> entry = it.next();
|
||||||
|
|
||||||
|
if(entry.getValue().getLastBufferReceived() + IDLE_RECORDER_REMOVAL_THRESHOLD < System.currentTimeMillis())
|
||||||
|
{
|
||||||
|
mLog.info("Removing idle recorder [" + entry.getKey() + "]");
|
||||||
|
it.remove();
|
||||||
|
entry.getValue().stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,29 +234,16 @@ public class RecorderManager implements Listener<AudioPacket>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitors currently running recorders and removes/closes any recorders that are idle more than 6 seconds
|
* Processes queued audio packets and distributes to each of the audio recorders. Removes any idle recorders
|
||||||
|
* that have not been updated according to an idle threshold period
|
||||||
*/
|
*/
|
||||||
public class RecorderMonitor implements Runnable
|
public class BufferProcessor implements Runnable
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
synchronized(mRecorders)
|
processBuffers();
|
||||||
{
|
removeIdleRecorders();
|
||||||
Iterator<Map.Entry<String,RealBufferWaveRecorder>> it = mRecorders.entrySet().iterator();
|
|
||||||
|
|
||||||
while(it.hasNext())
|
|
||||||
{
|
|
||||||
Map.Entry<String,RealBufferWaveRecorder> entry = it.next();
|
|
||||||
|
|
||||||
if(entry.getValue().getLastBufferReceived() + IDLE_RECORDER_REMOVAL_THRESHOLD < System.currentTimeMillis())
|
|
||||||
{
|
|
||||||
mLog.info("Removing idle recorder [" + entry.getKey() + "]");
|
|
||||||
it.remove();
|
|
||||||
entry.getValue().stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ public class Broadcaster<T> implements Listener<T>
|
|||||||
|
|
||||||
public void addListener( Listener<T> listener )
|
public void addListener( Listener<T> listener )
|
||||||
{
|
{
|
||||||
|
if(listener == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Listener cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
mListeners.add( listener );
|
mListeners.add( listener );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ package source;
|
|||||||
import module.Module;
|
import module.Module;
|
||||||
import sample.SampleType;
|
import sample.SampleType;
|
||||||
import sample.real.IOverflowListener;
|
import sample.real.IOverflowListener;
|
||||||
|
import source.tuner.frequency.IFrequencyChangeListener;
|
||||||
|
import source.tuner.frequency.IFrequencyChangeProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class to define the minimum functionality of a sample data provider.
|
* Abstract class to define the minimum functionality of a sample data provider.
|
||||||
*/
|
*/
|
||||||
public abstract class Source extends Module
|
public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider
|
||||||
{
|
{
|
||||||
protected SampleType mSampleType;
|
protected SampleType mSampleType;
|
||||||
protected IOverflowListener mOverflowListener;
|
protected IOverflowListener mOverflowListener;
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ import sample.real.RealBuffer;
|
|||||||
import source.RealSource;
|
import source.RealSource;
|
||||||
import source.SourceException;
|
import source.SourceException;
|
||||||
import dsp.gain.AutomaticGainControl;
|
import dsp.gain.AutomaticGainControl;
|
||||||
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
|
|
||||||
public class RealMixerSource extends RealSource
|
public class RealMixerSource extends RealSource
|
||||||
{
|
{
|
||||||
private final static Logger mLog =
|
private final static Logger mLog = LoggerFactory.getLogger( RealMixerSource.class );
|
||||||
LoggerFactory.getLogger( RealMixerSource.class );
|
|
||||||
|
|
||||||
private long mFrequency = 0;
|
private long mFrequency = 0;
|
||||||
private int mBufferSize = 16384;
|
private int mBufferSize = 16384;
|
||||||
@@ -48,13 +48,11 @@ public class RealMixerSource extends RealSource
|
|||||||
private BufferReader mBufferReader = new BufferReader();
|
private BufferReader mBufferReader = new BufferReader();
|
||||||
private TargetDataLine mTargetDataLine;
|
private TargetDataLine mTargetDataLine;
|
||||||
private AudioFormat mAudioFormat;
|
private AudioFormat mAudioFormat;
|
||||||
private String mDisplayName;
|
|
||||||
private int mBytesPerFrame = 0;
|
private int mBytesPerFrame = 0;
|
||||||
private ISampleAdapter mSampleAdapter;
|
private ISampleAdapter mSampleAdapter;
|
||||||
private AutomaticGainControl mAGC = new AutomaticGainControl();
|
private AutomaticGainControl mAGC = new AutomaticGainControl();
|
||||||
|
|
||||||
CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners =
|
CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = new CopyOnWriteArrayList<Listener<RealBuffer>>();
|
||||||
new CopyOnWriteArrayList<Listener<RealBuffer>>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Real Mixer Source - constructs a reader on the mixer/sound card target
|
* Real Mixer Source - constructs a reader on the mixer/sound card target
|
||||||
@@ -80,8 +78,27 @@ public class RealMixerSource extends RealSource
|
|||||||
mAudioFormat = format;
|
mAudioFormat = format;
|
||||||
mSampleAdapter = sampleAdapter;
|
mSampleAdapter = sampleAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
|
{
|
||||||
|
//Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
//Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
//Not implemented
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -133,23 +150,15 @@ public class RealMixerSource extends RealSource
|
|||||||
* buffer, otherwise send him a copy of the buffer */
|
* buffer, otherwise send him a copy of the buffer */
|
||||||
if( it.hasNext() )
|
if( it.hasNext() )
|
||||||
{
|
{
|
||||||
// next.receive( mAGC.process( samples.copyOf() ) );
|
|
||||||
next.receive( samples.copyOf() );
|
next.receive( samples.copyOf() );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// next.receive( mAGC.process( samples ) );
|
|
||||||
next.receive( samples );
|
next.receive( samples );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return mDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSampleRate()
|
public int getSampleRate()
|
||||||
{
|
{
|
||||||
if( mTargetDataLine != null )
|
if( mTargetDataLine != null )
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ import source.ComplexSource;
|
|||||||
import source.SourceException;
|
import source.SourceException;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent;
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||||
|
import source.tuner.frequency.IFrequencyChangeListener;
|
||||||
import source.tuner.frequency.IFrequencyChangeProcessor;
|
import source.tuner.frequency.IFrequencyChangeProcessor;
|
||||||
|
import source.tuner.frequency.IFrequencyChangeProvider;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -65,6 +67,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
private ComplexPrimeCICDecimate mDecimationFilter;
|
private ComplexPrimeCICDecimate mDecimationFilter;
|
||||||
private Listener<ComplexBuffer> mListener;
|
private Listener<ComplexBuffer> mListener;
|
||||||
private IFrequencyChangeProcessor mFrequencyChangeProcessor;
|
private IFrequencyChangeProcessor mFrequencyChangeProcessor;
|
||||||
|
private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
|
||||||
private ScheduledFuture<?> mTaskHandle;
|
private ScheduledFuture<?> mTaskHandle;
|
||||||
|
|
||||||
private long mTunerFrequency = 0;
|
private long mTunerFrequency = 0;
|
||||||
@@ -75,7 +78,6 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
|
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
private boolean mExpended = false;
|
private boolean mExpended = false;
|
||||||
private int mCount;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
|
* Provides a Digital Drop Channel (DDC) to decimate the IQ output from a
|
||||||
@@ -109,7 +111,8 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
|
mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate());
|
||||||
|
|
||||||
/* Fire a sample rate change event to setup the decimation chain */
|
/* Fire a sample rate change event to setup the decimation chain */
|
||||||
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate()));
|
frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE,
|
||||||
|
mTuner.getTunerController().getSampleRate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,6 +134,10 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
|
|
||||||
if(mRunning.compareAndSet(false, true))
|
if(mRunning.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
|
//Broadcast current frequency and sample rate settings so all downstream components are aware
|
||||||
|
mDownstreamFrequencyEventProcessor.broadcastCurrentFrequency();
|
||||||
|
mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
|
||||||
|
|
||||||
//Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
|
//Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative
|
||||||
//to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
|
//to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention
|
||||||
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
|
mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS);
|
||||||
@@ -184,6 +191,20 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
|
||||||
|
* @param correction current frequency correction value.
|
||||||
|
*/
|
||||||
|
private void setFrequencyCorrection(int correction)
|
||||||
|
{
|
||||||
|
mChannelFrequencyCorrection = correction;
|
||||||
|
|
||||||
|
updateMixerFrequencyOffset();
|
||||||
|
|
||||||
|
mDownstreamFrequencyEventProcessor.broadcast(
|
||||||
|
new FrequencyChangeEvent( Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
|
||||||
|
}
|
||||||
|
|
||||||
public Tuner getTuner()
|
public Tuner getTuner()
|
||||||
{
|
{
|
||||||
return mTuner;
|
return mTuner;
|
||||||
@@ -242,50 +263,49 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||||
mTunerFrequency = event.getValue().longValue();
|
mTunerFrequency = event.getValue().longValue();
|
||||||
updateMixerFrequencyOffset();
|
updateMixerFrequencyOffset();
|
||||||
break;
|
|
||||||
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
|
||||||
mChannelFrequencyCorrection = event.getValue().intValue();
|
|
||||||
|
|
||||||
updateMixerFrequencyOffset();
|
//Reset frequency correction so that downstream components can recalculate the value
|
||||||
|
setFrequencyCorrection(0);
|
||||||
if(mFrequencyChangeProcessor != null)
|
|
||||||
{
|
|
||||||
mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent(
|
|
||||||
Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||||
int sampleRate = event.getValue().intValue();
|
int sampleRate = event.getValue().intValue();
|
||||||
|
setSampleRate(sampleRate);
|
||||||
if(mTunerSampleRate != sampleRate)
|
|
||||||
{
|
|
||||||
mMixer.setSampleRate(sampleRate);
|
|
||||||
|
|
||||||
/* Get new decimation filter */
|
|
||||||
mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
|
|
||||||
CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
|
|
||||||
|
|
||||||
/* re-add the original output listener */
|
|
||||||
mDecimationFilter.setListener(mListener);
|
|
||||||
|
|
||||||
mTunerSampleRate = sampleRate;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the sample rate to the requested value and notifies any downstream components of the change
|
||||||
|
* @param sampleRate to set
|
||||||
|
*/
|
||||||
|
private void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
if(mTunerSampleRate != sampleRate)
|
||||||
|
{
|
||||||
|
mMixer.setSampleRate(sampleRate);
|
||||||
|
|
||||||
|
/* Get new decimation filter */
|
||||||
|
mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1,
|
||||||
|
CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING);
|
||||||
|
|
||||||
|
/* re-add the original output listener */
|
||||||
|
mDecimationFilter.setListener(mListener);
|
||||||
|
|
||||||
|
mTunerSampleRate = sampleRate;
|
||||||
|
|
||||||
|
mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the local mixer frequency offset from the tuned frequency,
|
* Calculates the local mixer frequency offset from the tuned frequency,
|
||||||
* channel's requested frequency, and channel frequency correction.
|
* channel's requested frequency, and channel frequency correction.
|
||||||
*/
|
*/
|
||||||
private void updateMixerFrequencyOffset()
|
private void updateMixerFrequencyOffset()
|
||||||
{
|
{
|
||||||
long offset = mTunerFrequency -
|
long offset = mTunerFrequency - mTunerChannel.getFrequency() - mChannelFrequencyCorrection;
|
||||||
mTunerChannel.getFrequency() -
|
|
||||||
mChannelFrequencyCorrection;
|
|
||||||
|
|
||||||
mMixer.setFrequency(offset);
|
mMixer.setFrequency(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +319,140 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
|||||||
return mTunerChannel.getFrequency();
|
return mTunerChannel.getFrequency();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements IFrequencyChangeProvider to enable this source to broadcast frequency change events to downstream
|
||||||
|
* listeners.
|
||||||
|
*
|
||||||
|
* @param listener to receive downstream events
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
|
{
|
||||||
|
mDownstreamFrequencyEventProcessor.setFrequencyChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements IFrequencyChangeProvider to remove the frequency change listener from receiving down-stream frequency
|
||||||
|
* change events.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void removeFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
mDownstreamFrequencyEventProcessor.removeFrequencyChangeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements IFrequencyChangeListener to receive frequency change events containing requests from downstream
|
||||||
|
* listeners to change frequency values.
|
||||||
|
* @return listener
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Managers frequency change requests and notifications from/to any downstream component. Downstream
|
||||||
|
* components are those that receive samples from this tuner channel source. These downstream components will be
|
||||||
|
* notified of any frequency or sample rate change events and will also be able to request frequency correction
|
||||||
|
* updates.
|
||||||
|
*/
|
||||||
|
public class DownstreamProcessor implements IFrequencyChangeListener, IFrequencyChangeProvider,
|
||||||
|
Listener<FrequencyChangeEvent>
|
||||||
|
{
|
||||||
|
//Listener to receive downstream events
|
||||||
|
private Listener<FrequencyChangeEvent> mListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts the frequency change event to the downstream frequency change listener
|
||||||
|
* @param event to broadcast
|
||||||
|
*/
|
||||||
|
public void broadcast(FrequencyChangeEvent event)
|
||||||
|
{
|
||||||
|
if(mListener != null)
|
||||||
|
{
|
||||||
|
mListener.receive(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts the current frequency of this tuner channel source to the downstream listener
|
||||||
|
*/
|
||||||
|
public void broadcastCurrentFrequency()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long frequency = getFrequency();
|
||||||
|
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency));
|
||||||
|
}
|
||||||
|
catch(SourceException se)
|
||||||
|
{
|
||||||
|
mLog.error("Error obtaining frequency from tuner to broadcast downstream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts the current decimated sample rate of this tuner channel source
|
||||||
|
*/
|
||||||
|
public void broadcastCurrentSampleRate()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Note: downstream sample rate is currently a fixed value -- it will change in the future
|
||||||
|
broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, getSampleRate()));
|
||||||
|
}
|
||||||
|
catch(SourceException se)
|
||||||
|
{
|
||||||
|
mLog.error("Error obtaining sample rate from tuner to broadcast downstream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the downstream listener to receive frequency change events from this tuner channel source
|
||||||
|
* @param listener to receive events
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
|
{
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the downstream listener from receiving frequency change events.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void removeFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
mListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for receiving frequency change events from downstream components
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes frequency change events from downstream components.
|
||||||
|
* @param event to process
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(FrequencyChangeEvent event)
|
||||||
|
{
|
||||||
|
switch(event.getEvent())
|
||||||
|
{
|
||||||
|
//Frequency correction requests are the only change requests supported from downstream components
|
||||||
|
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||||
|
setFrequencyCorrection(event.getValue().intValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decimates an inbound buffer of I/Q samples from the source down to the
|
* Decimates an inbound buffer of I/Q samples from the source down to the
|
||||||
* standard 48000 channel sample rate
|
* standard 48000 channel sample rate
|
||||||
|
|||||||
@@ -1,9 +1,30 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
package source.tuner.frequency;
|
package source.tuner.frequency;
|
||||||
|
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider of Frequency Change Events
|
||||||
|
*/
|
||||||
public interface IFrequencyChangeProvider
|
public interface IFrequencyChangeProvider
|
||||||
{
|
{
|
||||||
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener );
|
void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener );
|
||||||
public void removeFrequencyChangeListener();
|
void removeFrequencyChangeListener();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* sdrtrunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package source.wave;
|
package source.wave;
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ import sample.complex.ComplexBuffer;
|
|||||||
import source.ComplexSource;
|
import source.ComplexSource;
|
||||||
import source.IControllableFileSource;
|
import source.IControllableFileSource;
|
||||||
import source.IFrameLocationListener;
|
import source.IFrameLocationListener;
|
||||||
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
|
|
||||||
public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
|
public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
|
||||||
{
|
{
|
||||||
@@ -54,8 +56,27 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
|
|||||||
{
|
{
|
||||||
mFile = file;
|
mFile = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
|
{
|
||||||
|
//Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
//Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
//Not implemented
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import sample.real.RealBuffer;
|
|||||||
import source.IControllableFileSource;
|
import source.IControllableFileSource;
|
||||||
import source.IFrameLocationListener;
|
import source.IFrameLocationListener;
|
||||||
import source.RealSource;
|
import source.RealSource;
|
||||||
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
|
|
||||||
public class RealWaveSource extends RealSource
|
public class RealWaveSource extends RealSource
|
||||||
implements IControllableFileSource, AutoCloseable
|
implements IControllableFileSource, AutoCloseable
|
||||||
@@ -55,8 +56,27 @@ public class RealWaveSource extends RealSource
|
|||||||
{
|
{
|
||||||
mFile = file;
|
mFile = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||||
|
{
|
||||||
|
//Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
//Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||||
|
{
|
||||||
|
//Not implemented
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class);
|
private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class);
|
||||||
|
private final DecimalFormat PPM_FORMATTER = new DecimalFormat( "#.0" );
|
||||||
|
|
||||||
private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
|
private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
|
||||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
@@ -187,7 +188,6 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
|
|||||||
* 6 64x Zoom
|
* 6 64x Zoom
|
||||||
*
|
*
|
||||||
* @param zoom level, 0 - 6.
|
* @param zoom level, 0 - 6.
|
||||||
* @param offset into the FFT bins for start zoom window
|
|
||||||
*/
|
*/
|
||||||
public void setZoom(int zoom)
|
public void setZoom(int zoom)
|
||||||
{
|
{
|
||||||
@@ -412,20 +412,41 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a vertical line at the xaxis
|
* Draws the Automatic Frequency Control (AFC) channel center offset
|
||||||
*/
|
*/
|
||||||
private void drawAFC(Graphics2D graphics, double frequencyAxis, double errorAxis)
|
private void drawAFC(Graphics2D graphics, double frequencyAxis, double errorAxis, double bandwidth,
|
||||||
|
int correction, long frequency)
|
||||||
{
|
{
|
||||||
double height = getSize().getHeight() - mSpectrumInset;
|
double height = getSize().getHeight() - mSpectrumInset;
|
||||||
double lineHeight = height * 0.75d;
|
double verticalAxisTop = height * 0.88d;
|
||||||
|
double verticalAxisBottom = height * 0.98d;
|
||||||
|
|
||||||
|
double halfBandwidth = bandwidth / 2.0;
|
||||||
|
double errorEdgeStart = errorAxis - halfBandwidth;
|
||||||
|
double errorEdgeStop = errorAxis + halfBandwidth;
|
||||||
|
|
||||||
graphics.setColor(Color.YELLOW);
|
graphics.setColor(Color.YELLOW);
|
||||||
|
|
||||||
//Vertical frequency error line
|
//Horizontal line connecting frequency and error line
|
||||||
graphics.draw(new Line2D.Double(errorAxis, lineHeight, errorAxis, height - 1.0d));
|
graphics.draw(new Line2D.Double(errorEdgeStart, verticalAxisBottom, errorEdgeStop, verticalAxisBottom));
|
||||||
|
|
||||||
//Horizontal line connecting frequency and error lines
|
//Vertical band edge lines
|
||||||
graphics.draw(new Line2D.Double(frequencyAxis, lineHeight, errorAxis, lineHeight));
|
graphics.draw(new Line2D.Double(errorEdgeStart, verticalAxisTop, errorEdgeStart, verticalAxisBottom));
|
||||||
|
graphics.draw(new Line2D.Double(errorEdgeStop, verticalAxisTop, errorEdgeStop, verticalAxisBottom));
|
||||||
|
|
||||||
|
double ppm = (double)correction / ((double)frequency / 1E6d);
|
||||||
|
|
||||||
|
String label = "PPM " + PPM_FORMATTER.format(ppm) ;
|
||||||
|
|
||||||
|
FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont());
|
||||||
|
|
||||||
|
Rectangle2D rect = fontMetrics.getStringBounds(label, graphics);
|
||||||
|
|
||||||
|
//Only render the correction value label if the spacing is large enough
|
||||||
|
if(rect.getWidth() <= bandwidth && rect.getHeight() * 5 <= height)
|
||||||
|
{
|
||||||
|
graphics.drawString(label, (float)(errorEdgeStart + 1.0), (float)(verticalAxisBottom - 2.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -564,7 +585,8 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ
|
|||||||
{
|
{
|
||||||
long error = frequency + correction;
|
long error = frequency + correction;
|
||||||
|
|
||||||
drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error));
|
drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error), width, correction,
|
||||||
|
tunerChannel.getFrequency());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,493 +1,483 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* sdrtrunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package spectrum;
|
package spectrum;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.GradientPaint;
|
|
||||||
import java.awt.Graphics;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Rectangle;
|
|
||||||
import java.awt.RenderingHints;
|
|
||||||
import java.awt.geom.GeneralPath;
|
|
||||||
import java.awt.geom.Line2D;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.Validate;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import settings.ColorSetting;
|
|
||||||
import settings.ColorSetting.ColorSettingName;
|
|
||||||
import settings.Setting;
|
|
||||||
import settings.SettingChangeListener;
|
|
||||||
import settings.SettingsManager;
|
|
||||||
import dsp.filter.smoothing.GaussianSmoothingFilter;
|
import dsp.filter.smoothing.GaussianSmoothingFilter;
|
||||||
import dsp.filter.smoothing.NoSmoothingFilter;
|
import dsp.filter.smoothing.NoSmoothingFilter;
|
||||||
import dsp.filter.smoothing.RectangularSmoothingFilter;
|
import dsp.filter.smoothing.RectangularSmoothingFilter;
|
||||||
import dsp.filter.smoothing.SmoothingFilter;
|
import dsp.filter.smoothing.SmoothingFilter;
|
||||||
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
||||||
import dsp.filter.smoothing.TriangularSmoothingFilter;
|
import dsp.filter.smoothing.TriangularSmoothingFilter;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import settings.ColorSetting;
|
||||||
|
import settings.ColorSetting.ColorSettingName;
|
||||||
|
import settings.Setting;
|
||||||
|
import settings.SettingChangeListener;
|
||||||
|
import settings.SettingsManager;
|
||||||
|
|
||||||
public class SpectrumPanel extends JPanel
|
import javax.swing.*;
|
||||||
implements DFTResultsListener,
|
import java.awt.*;
|
||||||
SettingChangeListener,
|
import java.awt.geom.GeneralPath;
|
||||||
SpectralDisplayAdjuster
|
import java.awt.geom.Line2D;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class SpectrumPanel extends JPanel implements DFTResultsListener, SettingChangeListener, SpectralDisplayAdjuster
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private final static Logger mLog =
|
private final static Logger mLog = LoggerFactory.getLogger(SpectrumPanel.class);
|
||||||
LoggerFactory.getLogger( SpectrumPanel.class );
|
|
||||||
|
|
||||||
private static final RenderingHints RENDERING_HINTS =
|
|
||||||
new RenderingHints( RenderingHints.KEY_ANTIALIASING,
|
|
||||||
RenderingHints.VALUE_ANTIALIAS_ON );
|
|
||||||
|
|
||||||
static
|
private static final RenderingHints RENDERING_HINTS =
|
||||||
{
|
new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
RENDERING_HINTS.put( RenderingHints.KEY_RENDERING,
|
|
||||||
RenderingHints.VALUE_RENDER_QUALITY );
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color mColorSpectrumBackground;
|
|
||||||
private Color mColorSpectrumGradientTop;
|
|
||||||
private Color mColorSpectrumGradientBottom;
|
|
||||||
private Color mColorSpectrumLine;
|
|
||||||
|
|
||||||
//Defines the panel inset along the bottom for frequency display
|
static
|
||||||
private float mSpectrumInset = 20.0f;
|
|
||||||
|
|
||||||
//Current DFT output bins in dB
|
|
||||||
private float[] mDisplayFFTBins = new float[ 1 ];
|
|
||||||
|
|
||||||
//Averaging across multiple DFT result sets
|
|
||||||
private int mAveraging = 4;
|
|
||||||
|
|
||||||
//Smoothing across bins in the same DFT result set
|
|
||||||
private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
|
|
||||||
|
|
||||||
//Reference dB value set according to the source sample size
|
|
||||||
private float mDBScale;
|
|
||||||
|
|
||||||
private int mZoom = 0;
|
|
||||||
private int mZoomWindowOffset = 0;;
|
|
||||||
|
|
||||||
private SettingsManager mSettingsManager;
|
|
||||||
|
|
||||||
public SpectrumPanel( SettingsManager settingsManager )
|
|
||||||
{
|
{
|
||||||
mSettingsManager = settingsManager;
|
RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
|
||||||
if( mSettingsManager != null )
|
|
||||||
{
|
|
||||||
mSettingsManager.addListener( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
setSampleSize( 16.0 );
|
|
||||||
|
|
||||||
mSmoothingFilter.setPointSize( SmoothingFilter.SMOOTHING_DEFAULT );
|
|
||||||
|
|
||||||
getColors();
|
|
||||||
|
|
||||||
setAveraging( mAveraging );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
private Color mColorSpectrumBackground;
|
||||||
{
|
private Color mColorSpectrumGradientTop;
|
||||||
if( mSettingsManager != null )
|
private Color mColorSpectrumGradientBottom;
|
||||||
{
|
private Color mColorSpectrumLine;
|
||||||
mSettingsManager.removeListener( this );
|
|
||||||
}
|
//Defines the panel inset along the bottom for frequency display
|
||||||
|
private float mSpectrumInset = 20.0f;
|
||||||
mSettingsManager = null;
|
|
||||||
}
|
//Current DFT output bins in dB
|
||||||
|
private float[] mDisplayFFTBins = new float[1];
|
||||||
|
|
||||||
|
//Averaging across multiple DFT result sets
|
||||||
|
private int mAveraging = 4;
|
||||||
|
|
||||||
|
//Smoothing across bins in the same DFT result set
|
||||||
|
private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
|
||||||
|
|
||||||
|
//Reference dB value set according to the source sample size
|
||||||
|
private float mDBScale;
|
||||||
|
|
||||||
|
private int mZoom = 0;
|
||||||
|
private int mZoomWindowOffset = 0;
|
||||||
|
;
|
||||||
|
|
||||||
|
private SettingsManager mSettingsManager;
|
||||||
|
|
||||||
|
public SpectrumPanel(SettingsManager settingsManager)
|
||||||
|
{
|
||||||
|
mSettingsManager = settingsManager;
|
||||||
|
|
||||||
|
if(mSettingsManager != null)
|
||||||
|
{
|
||||||
|
mSettingsManager.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSampleSize(16.0);
|
||||||
|
|
||||||
|
mSmoothingFilter.setPointSize(SmoothingFilter.SMOOTHING_DEFAULT);
|
||||||
|
|
||||||
|
getColors();
|
||||||
|
|
||||||
|
setAveraging(mAveraging);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
if(mSettingsManager != null)
|
||||||
|
{
|
||||||
|
mSettingsManager.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
mSettingsManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DFTResultsListener interface for receiving the processed data
|
* DFTResultsListener interface for receiving the processed data
|
||||||
* to display
|
* to display
|
||||||
*/
|
*/
|
||||||
public void receive( float[] currentFFTBins )
|
public void receive(float[] currentFFTBins)
|
||||||
{
|
{
|
||||||
//Prevent arrays of NaN values from being rendered. The first few
|
//Prevent arrays of NaN values from being rendered. The first few
|
||||||
//DFT result sets on startup will contain NaN values
|
//DFT result sets on startup will contain NaN values
|
||||||
if( Float.isInfinite( currentFFTBins[ 0 ] ) || Float.isNaN( currentFFTBins[ 0 ] ) )
|
if(Float.isInfinite(currentFFTBins[0]) || Float.isNaN(currentFFTBins[0]))
|
||||||
{
|
{
|
||||||
currentFFTBins = new float[ currentFFTBins.length ];
|
currentFFTBins = new float[currentFFTBins.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
//Construct and/or resize our DFT results variables
|
//Construct and/or resize our DFT results variables
|
||||||
if( mDisplayFFTBins == null ||
|
if(mDisplayFFTBins == null ||
|
||||||
mDisplayFFTBins.length != currentFFTBins.length )
|
mDisplayFFTBins.length != currentFFTBins.length)
|
||||||
{
|
{
|
||||||
mDisplayFFTBins = currentFFTBins;
|
mDisplayFFTBins = currentFFTBins;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Apply smoothing across the bins of the DFT results
|
//Apply smoothing across the bins of the DFT results
|
||||||
float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins );
|
float[] smoothedBins = mSmoothingFilter.filter(currentFFTBins);
|
||||||
|
|
||||||
//Apply averaging over multiple DFT output frames
|
//Apply averaging over multiple DFT output frames
|
||||||
if( mAveraging > 1 )
|
if(mAveraging > 1)
|
||||||
{
|
{
|
||||||
float gain = 1.0f / (float)mAveraging;
|
float gain = 1.0f / (float)mAveraging;
|
||||||
|
|
||||||
for( int x = 0; x < mDisplayFFTBins.length; x++ )
|
for(int x = 0; x < mDisplayFFTBins.length; x++)
|
||||||
{
|
{
|
||||||
mDisplayFFTBins[ x ] +=
|
mDisplayFFTBins[x] +=
|
||||||
( smoothedBins[ x ] - mDisplayFFTBins[ x ] ) * gain;
|
(smoothedBins[x] - mDisplayFFTBins[x]) * gain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mDisplayFFTBins = smoothedBins;
|
mDisplayFFTBins = smoothedBins;
|
||||||
}
|
}
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paintComponent( Graphics g )
|
public void paintComponent(Graphics g)
|
||||||
{
|
{
|
||||||
super.paintComponent( g );
|
super.paintComponent(g);
|
||||||
|
|
||||||
Graphics2D graphics = (Graphics2D) g;
|
|
||||||
|
|
||||||
graphics.setBackground( mColorSpectrumBackground );
|
Graphics2D graphics = (Graphics2D)g;
|
||||||
|
|
||||||
graphics.setRenderingHints( RENDERING_HINTS );
|
graphics.setBackground(mColorSpectrumBackground);
|
||||||
|
|
||||||
drawSpectrum( graphics );
|
graphics.setRenderingHints(RENDERING_HINTS);
|
||||||
|
|
||||||
|
drawSpectrum(graphics);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the current fft spectrum with a line and a gradient fill.
|
* Draws the current fft spectrum with a line and a gradient fill.
|
||||||
*/
|
*/
|
||||||
private void drawSpectrum( Graphics2D graphics )
|
private void drawSpectrum(Graphics2D graphics)
|
||||||
{
|
{
|
||||||
Dimension size = getSize();
|
Dimension size = getSize();
|
||||||
|
|
||||||
//Draw the background
|
//Draw the background
|
||||||
Rectangle background = new Rectangle( 0, 0, size.width, size.height );
|
Rectangle background = new Rectangle(0, 0, size.width, size.height);
|
||||||
graphics.setColor( mColorSpectrumBackground );
|
graphics.setColor(mColorSpectrumBackground);
|
||||||
graphics.draw( background );
|
graphics.draw(background);
|
||||||
graphics.fill( background );
|
graphics.fill(background);
|
||||||
|
|
||||||
//Define the gradient
|
//Define the gradient
|
||||||
GradientPaint gradient = new GradientPaint( 0,
|
GradientPaint gradient = new GradientPaint(0,
|
||||||
( getSize().height - mSpectrumInset ) / 2,
|
(getSize().height - mSpectrumInset) / 2,
|
||||||
mColorSpectrumGradientTop,
|
mColorSpectrumGradientTop,
|
||||||
0,
|
0,
|
||||||
getSize().height,
|
getSize().height,
|
||||||
mColorSpectrumGradientBottom );
|
mColorSpectrumGradientBottom);
|
||||||
|
|
||||||
graphics.setBackground( mColorSpectrumBackground );
|
|
||||||
|
|
||||||
GeneralPath spectrumShape = new GeneralPath();
|
|
||||||
|
|
||||||
//Start at the lower right inset point
|
graphics.setBackground(mColorSpectrumBackground);
|
||||||
spectrumShape.moveTo( size.getWidth(),
|
|
||||||
size.getHeight() - mSpectrumInset );
|
|
||||||
|
|
||||||
//Draw to the lower left
|
|
||||||
spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset );
|
|
||||||
|
|
||||||
float[] bins = getBins();
|
GeneralPath spectrumShape = new GeneralPath();
|
||||||
|
|
||||||
//If we have FFT data to display ...
|
|
||||||
if( bins != null )
|
|
||||||
{
|
|
||||||
float insideHeight = size.height - mSpectrumInset;
|
|
||||||
|
|
||||||
float scalor = insideHeight / -mDBScale;
|
//Start at the lower right inset point
|
||||||
|
spectrumShape.moveTo(size.getWidth(),
|
||||||
|
size.getHeight() - mSpectrumInset);
|
||||||
|
|
||||||
|
//Draw to the lower left
|
||||||
|
spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
|
||||||
|
|
||||||
|
float[] bins = getBins();
|
||||||
|
|
||||||
|
//If we have FFT data to display ...
|
||||||
|
if(bins != null)
|
||||||
|
{
|
||||||
|
float insideHeight = size.height - mSpectrumInset;
|
||||||
|
|
||||||
|
float scalor = insideHeight / -mDBScale;
|
||||||
|
|
||||||
/* Calculate based on bin size - 1, since bin 0 is rendered at zero
|
/* Calculate based on bin size - 1, since bin 0 is rendered at zero
|
||||||
* and the last bin is rendered at the width */
|
* and the last bin is rendered at the width */
|
||||||
float binSize = (float)size.width / ( (float)( bins.length ) );
|
float binSize = (float)size.width / ((float)(bins.length));
|
||||||
|
|
||||||
for( int x = 0; x < bins.length; x++ )
|
for(int x = 0; x < bins.length; x++)
|
||||||
{
|
{
|
||||||
float height;
|
float height;
|
||||||
|
|
||||||
height = bins[ x ] * scalor;
|
|
||||||
|
|
||||||
if( height > insideHeight )
|
|
||||||
{
|
|
||||||
height = insideHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( height < 0 )
|
|
||||||
{
|
|
||||||
height = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
float xAxis = (float)x * binSize;
|
|
||||||
|
|
||||||
spectrumShape.lineTo( xAxis, height );
|
height = bins[x] * scalor;
|
||||||
}
|
|
||||||
}
|
|
||||||
//Otherwise show an empty spectrum
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Draw Left Size
|
|
||||||
graphics.setPaint( gradient );
|
|
||||||
spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset );
|
|
||||||
//Draw Middle
|
|
||||||
spectrumShape.lineTo( size.getWidth(),
|
|
||||||
size.getHeight() - mSpectrumInset );
|
|
||||||
}
|
|
||||||
|
|
||||||
//Draw Right Side
|
|
||||||
spectrumShape.lineTo( size.getWidth(),
|
|
||||||
size.getHeight() - mSpectrumInset );
|
|
||||||
|
|
||||||
graphics.setPaint( gradient );
|
|
||||||
graphics.draw( spectrumShape );
|
|
||||||
graphics.fill( spectrumShape );
|
|
||||||
|
|
||||||
graphics.setPaint( mColorSpectrumLine );
|
|
||||||
|
|
||||||
//Draw the bottom line under the spectrum
|
if(height > insideHeight)
|
||||||
graphics.draw( new Line2D.Float( 0,
|
{
|
||||||
size.height - mSpectrumInset,
|
height = insideHeight;
|
||||||
size.width,
|
}
|
||||||
size.height - mSpectrumInset ) );
|
|
||||||
|
if(height < 0)
|
||||||
|
{
|
||||||
|
height = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float xAxis = (float)x * binSize;
|
||||||
|
|
||||||
|
spectrumShape.lineTo(xAxis, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Otherwise show an empty spectrum
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Draw Left Size
|
||||||
|
graphics.setPaint(gradient);
|
||||||
|
spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
|
||||||
|
//Draw Middle
|
||||||
|
spectrumShape.lineTo(size.getWidth(),
|
||||||
|
size.getHeight() - mSpectrumInset);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Draw Right Side
|
||||||
|
spectrumShape.lineTo(size.getWidth(),
|
||||||
|
size.getHeight() - mSpectrumInset);
|
||||||
|
|
||||||
|
graphics.setPaint(gradient);
|
||||||
|
graphics.draw(spectrumShape);
|
||||||
|
graphics.fill(spectrumShape);
|
||||||
|
|
||||||
|
graphics.setPaint(mColorSpectrumLine);
|
||||||
|
|
||||||
|
//Draw the bottom line under the spectrum
|
||||||
|
graphics.draw(new Line2D.Float(0,
|
||||||
|
size.height - mSpectrumInset,
|
||||||
|
size.width,
|
||||||
|
size.height - mSpectrumInset));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current zoom level
|
* Sets the current zoom level
|
||||||
*
|
*
|
||||||
* 0 No Zoom
|
* 0 No Zoom
|
||||||
* 1 2x Zoom
|
* 1 2x Zoom
|
||||||
* 2 4x Zoom
|
* 2 4x Zoom
|
||||||
* 3 8x Zoom
|
* 3 8x Zoom
|
||||||
* 4 16x Zoom
|
* 4 16x Zoom
|
||||||
* 5 32x Zoom
|
* 5 32x Zoom
|
||||||
*
|
* 6 64x Zoom
|
||||||
* @param zoom level, 0 - 5.
|
*
|
||||||
|
* @param zoom level, 0 - 6.
|
||||||
*/
|
*/
|
||||||
public void setZoom( int zoom )
|
public void setZoom(int zoom)
|
||||||
{
|
{
|
||||||
Validate.isTrue( 0 <= zoom && zoom <= 5 );
|
Validate.isTrue(0 <= zoom && zoom <= 6, "Unrecognized Zoom Level: " + zoom);
|
||||||
|
|
||||||
mZoom = zoom;
|
mZoom = zoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the offset to define which subset of zoomed bins to display
|
* Sets the offset to define which subset of zoomed bins to display
|
||||||
*
|
*
|
||||||
* @param offset
|
* @param offset
|
||||||
*/
|
*/
|
||||||
public void setZoomWindowOffset( int offset )
|
public void setZoomWindowOffset(int offset)
|
||||||
{
|
{
|
||||||
mZoomWindowOffset = offset;
|
mZoomWindowOffset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the source sample size in bits which defines the lowest dB value to
|
* Sets the source sample size in bits which defines the lowest dB value to
|
||||||
* display in the spectrum
|
* display in the spectrum
|
||||||
*
|
*
|
||||||
* @param sampleSize in bits
|
* @param sampleSize in bits
|
||||||
*/
|
*/
|
||||||
public void setSampleSize( double sampleSize )
|
public void setSampleSize(double sampleSize)
|
||||||
{
|
|
||||||
Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0);
|
|
||||||
|
|
||||||
mDBScale = (float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize - 1 ) ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the number of DFT result sets to average across
|
|
||||||
*/
|
|
||||||
public void setAveraging( int size )
|
|
||||||
{
|
|
||||||
mAveraging = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DFT result sets averaging value
|
|
||||||
*/
|
|
||||||
public int getAveraging()
|
|
||||||
{
|
|
||||||
return mAveraging;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the spectral display
|
|
||||||
*/
|
|
||||||
public void clearSpectrum()
|
|
||||||
{
|
|
||||||
mDisplayFFTBins = null;
|
|
||||||
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getColors()
|
|
||||||
{
|
|
||||||
mColorSpectrumBackground =
|
|
||||||
getColor( ColorSettingName.SPECTRUM_BACKGROUND );
|
|
||||||
|
|
||||||
mColorSpectrumGradientBottom =
|
|
||||||
getColor( ColorSettingName.SPECTRUM_GRADIENT_BOTTOM );
|
|
||||||
|
|
||||||
mColorSpectrumGradientTop =
|
|
||||||
getColor( ColorSettingName.SPECTRUM_GRADIENT_TOP );
|
|
||||||
|
|
||||||
mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the current setting for the named color setting
|
|
||||||
*/
|
|
||||||
private Color getColor( ColorSettingName name )
|
|
||||||
{
|
|
||||||
ColorSetting setting = mSettingsManager.getColorSetting( name );
|
|
||||||
|
|
||||||
return setting.getColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener interface to receive setting change notifications
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void settingChanged( Setting setting )
|
|
||||||
{
|
{
|
||||||
if( setting instanceof ColorSetting )
|
Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0);
|
||||||
{
|
|
||||||
ColorSetting colorSetting = (ColorSetting)setting;
|
mDBScale = (float)(20.0 * Math.log10(Math.pow(2.0, sampleSize - 1)));
|
||||||
|
|
||||||
switch( ( (ColorSetting) setting ).getColorSettingName() )
|
|
||||||
{
|
|
||||||
case SPECTRUM_BACKGROUND:
|
|
||||||
mColorSpectrumBackground = colorSetting.getColor();
|
|
||||||
break;
|
|
||||||
case SPECTRUM_GRADIENT_BOTTOM:
|
|
||||||
mColorSpectrumGradientBottom = colorSetting.getColor();
|
|
||||||
break;
|
|
||||||
case SPECTRUM_GRADIENT_TOP:
|
|
||||||
mColorSpectrumGradientTop = colorSetting.getColor();
|
|
||||||
break;
|
|
||||||
case SPECTRUM_LINE:
|
|
||||||
mColorSpectrumLine = colorSetting.getColor();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getZoomMultiplier()
|
/**
|
||||||
{
|
* Defines the number of DFT result sets to average across
|
||||||
return (int)Math.pow( 2.0, mZoom );
|
*/
|
||||||
}
|
public void setAveraging(int size)
|
||||||
|
{
|
||||||
/**
|
mAveraging = size;
|
||||||
* Returns the DFT result bins, or a zoomed and offset version of the bins
|
}
|
||||||
* when the display is zoomed.
|
|
||||||
*/
|
/**
|
||||||
|
* DFT result sets averaging value
|
||||||
|
*/
|
||||||
|
public int getAveraging()
|
||||||
|
{
|
||||||
|
return mAveraging;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the spectral display
|
||||||
|
*/
|
||||||
|
public void clearSpectrum()
|
||||||
|
{
|
||||||
|
mDisplayFFTBins = null;
|
||||||
|
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getColors()
|
||||||
|
{
|
||||||
|
mColorSpectrumBackground =
|
||||||
|
getColor(ColorSettingName.SPECTRUM_BACKGROUND);
|
||||||
|
|
||||||
|
mColorSpectrumGradientBottom =
|
||||||
|
getColor(ColorSettingName.SPECTRUM_GRADIENT_BOTTOM);
|
||||||
|
|
||||||
|
mColorSpectrumGradientTop =
|
||||||
|
getColor(ColorSettingName.SPECTRUM_GRADIENT_TOP);
|
||||||
|
|
||||||
|
mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current setting for the named color setting
|
||||||
|
*/
|
||||||
|
private Color getColor(ColorSettingName name)
|
||||||
|
{
|
||||||
|
ColorSetting setting = mSettingsManager.getColorSetting(name);
|
||||||
|
|
||||||
|
return setting.getColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface to receive setting change notifications
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void settingChanged(Setting setting)
|
||||||
|
{
|
||||||
|
if(setting instanceof ColorSetting)
|
||||||
|
{
|
||||||
|
ColorSetting colorSetting = (ColorSetting)setting;
|
||||||
|
|
||||||
|
switch(((ColorSetting)setting).getColorSettingName())
|
||||||
|
{
|
||||||
|
case SPECTRUM_BACKGROUND:
|
||||||
|
mColorSpectrumBackground = colorSetting.getColor();
|
||||||
|
break;
|
||||||
|
case SPECTRUM_GRADIENT_BOTTOM:
|
||||||
|
mColorSpectrumGradientBottom = colorSetting.getColor();
|
||||||
|
break;
|
||||||
|
case SPECTRUM_GRADIENT_TOP:
|
||||||
|
mColorSpectrumGradientTop = colorSetting.getColor();
|
||||||
|
break;
|
||||||
|
case SPECTRUM_LINE:
|
||||||
|
mColorSpectrumLine = colorSetting.getColor();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getZoomMultiplier()
|
||||||
|
{
|
||||||
|
return (int)Math.pow(2.0, mZoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the DFT result bins, or a zoomed and offset version of the bins
|
||||||
|
* when the display is zoomed.
|
||||||
|
*/
|
||||||
private float[] getBins()
|
private float[] getBins()
|
||||||
{
|
{
|
||||||
if( mZoom == 0 )
|
if(mZoom == 0)
|
||||||
{
|
{
|
||||||
return mDisplayFFTBins;
|
return mDisplayFFTBins;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int length = mDisplayFFTBins.length / getZoomMultiplier();
|
int length = mDisplayFFTBins.length / getZoomMultiplier();
|
||||||
|
|
||||||
int offset = mZoomWindowOffset;
|
|
||||||
|
|
||||||
if( ( offset + length ) >= mDisplayFFTBins.length )
|
|
||||||
{
|
|
||||||
offset = mDisplayFFTBins.length - length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( offset < 0 )
|
|
||||||
{
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Arrays.copyOfRange( mDisplayFFTBins, offset, offset + length );
|
int offset = mZoomWindowOffset;
|
||||||
}
|
|
||||||
|
if((offset + length) >= mDisplayFFTBins.length)
|
||||||
|
{
|
||||||
|
offset = mDisplayFFTBins.length - length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(offset < 0)
|
||||||
|
{
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.copyOfRange(mDisplayFFTBins, offset, offset + length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void settingDeleted( Setting setting ) { /* not implemented */ }
|
public void settingDeleted(Setting setting)
|
||||||
|
{ /* not implemented */ }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current inter-bin smoothing setting
|
* Returns the current inter-bin smoothing setting
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getSmoothing()
|
public int getSmoothing()
|
||||||
{
|
{
|
||||||
return mSmoothingFilter.getPointSize();
|
return mSmoothingFilter.getPointSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the bin smoothing value to define how wide the smoothing window is
|
* Sets the bin smoothing value to define how wide the smoothing window is
|
||||||
* when averaging across DFT bins
|
* when averaging across DFT bins
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setSmoothing( int smoothing )
|
public void setSmoothing(int smoothing)
|
||||||
{
|
{
|
||||||
mSmoothingFilter.setPointSize( smoothing );
|
mSmoothingFilter.setPointSize(smoothing);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current smoothing filter type
|
* Returns the current smoothing filter type
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SmoothingType getSmoothingType()
|
public SmoothingType getSmoothingType()
|
||||||
{
|
{
|
||||||
return mSmoothingFilter.getSmoothingType();
|
return mSmoothingFilter.getSmoothingType();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the smoothing filter type
|
* Sets the smoothing filter type
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setSmoothingType( SmoothingType type )
|
public void setSmoothingType(SmoothingType type)
|
||||||
{
|
{
|
||||||
if( mSmoothingFilter.getSmoothingType() != type )
|
if(mSmoothingFilter.getSmoothingType() != type)
|
||||||
{
|
{
|
||||||
int pointSize = getSmoothing();
|
int pointSize = getSmoothing();
|
||||||
|
|
||||||
synchronized ( mSmoothingFilter )
|
synchronized(mSmoothingFilter)
|
||||||
{
|
{
|
||||||
switch( type )
|
switch(type)
|
||||||
{
|
{
|
||||||
case GAUSSIAN:
|
case GAUSSIAN:
|
||||||
mSmoothingFilter = new GaussianSmoothingFilter();
|
mSmoothingFilter = new GaussianSmoothingFilter();
|
||||||
break;
|
break;
|
||||||
case RECTANGLE:
|
case RECTANGLE:
|
||||||
mSmoothingFilter = new RectangularSmoothingFilter();
|
mSmoothingFilter = new RectangularSmoothingFilter();
|
||||||
break;
|
break;
|
||||||
case TRIANGLE:
|
case TRIANGLE:
|
||||||
mSmoothingFilter = new TriangularSmoothingFilter();
|
mSmoothingFilter = new TriangularSmoothingFilter();
|
||||||
break;
|
break;
|
||||||
case NONE:
|
case NONE:
|
||||||
default:
|
default:
|
||||||
mSmoothingFilter = new NoSmoothingFilter();
|
mSmoothingFilter = new NoSmoothingFilter();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSmoothing( pointSize );
|
setSmoothing(pointSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user