Compare commits

...

55 Commits

Author SHA1 Message Date
Denny
81b8f5a423 Updates build to version 0.4.0 alpha 4 2018-09-12 06:23:09 -04:00
Denny Sheirer
bf186a7408 Merge pull request #414 from DSheirer/405-airspy-tuner-waterfall-freeze
405 Airspy Spectrum/Waterfall Freezes
2018-09-12 06:20:36 -04:00
Denny
43fc326f93 Resolves #405
Adds 500 ms delay to initial registering for samples from all tuners to
feed the spectral display/waterfall.  The existing approach of immediately
registering for samples was causing the airspy tuner to lockup
aperiodically.  Investigation via debugger didn't show any application
thread locks ... usb master processor was continuing to service libusb
timeouts.  Unable to debug internal airspy processes.  Adding the delay
to startup seems to be a usable solution.
2018-09-12 06:19:11 -04:00
Denny Sheirer
7cdbb75b42 Merge pull request #413 from DSheirer/375-poor-p25-decode-performance
375 P25 Decode Performance
2018-09-10 04:22:40 -04:00
Denny
96a56cab68 Resolves #375.
Reverts PLL gain values to version 0.3.4beta2 values to restore P25
decoder performance.

This change in conjuction with recent channel sample rate change should
restore P25 channel decoding to comparable levels from 0.3.4b2.
2018-09-09 09:55:40 -04:00
Denny Sheirer
29d4b36cf7 Merge pull request #412 from DSheirer/411-variable-channel-sample-rates
#411 Update Tuner Source Manager to provide variable sample rate channels
2018-09-09 09:50:11 -04:00
Denny
b15a3e59a0 Resolves #411
Updates decoder types to specify channel sample rate and pass/stop
frequencies for requested channels.  Updates tuner channel source
managers to accept a channel specification for channels that are
allocated.
2018-09-09 09:49:05 -04:00
Denny Sheirer
9950e46e5a Merge pull request #408 from DSheirer/406-tuner-sample-delay-buffer
406 Tuner Sample Delay Buffer
2018-09-09 08:47:17 -04:00
Denny
570321da3e Implements a complex sample delay buffer integrated into the tuner
channel manager so that channel source requests can have a start
timestamp specified and the channel can be preloaded with samples from
the delay buffer.  If no timestamp is specified, current time is assumed.

This feature requires corresponding updates to any decoder that wishes
to implement the timestamped sample preloading functionality.
2018-09-09 08:46:21 -04:00
Denny
bb0bcd3198 Updates build to 0.4.0 alpha 3 2018-09-05 07:34:09 -04:00
Denny Sheirer
cb54850f99 Merge pull request #402 from DSheirer/heterodyne_channelizer
401 Update Tuners to use Heterodyne Channelizer
2018-09-05 07:31:57 -04:00
Denny
5bb8c46bd5 Merge remote-tracking branch 'origin/heterodyne_channelizer' into heterodyne_channelizer 2018-09-05 07:27:39 -04:00
Denny
2a3fd2c5a3 Class renaming and cleanup. 2018-09-05 07:27:18 -04:00
Denny
d712feb45a Class renaming and cleanup. 2018-09-05 07:26:52 -04:00
Denny
fbb24a4c05 Class renaming and cleanup. 2018-09-05 07:24:42 -04:00
Denny
3992f8178c Merge remote-tracking branch 'origin/heterodyne_channelizer' into heterodyne_channelizer
# Conflicts:
#	src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
2018-09-05 07:19:37 -04:00
Denny
8642def77e Cleanup, renaming and removing legacy code. 2018-09-05 07:17:51 -04:00
Denny
38e6de6cb2 Resolves cutoff frequency for 2 channel polyphase synthesizer. 2018-09-05 07:17:51 -04:00
Denny
994e78f42b Resolves issue with polyphase filter gain and channel output processor gain 2018-09-05 07:16:51 -04:00
Denny
b8a46af836 Creates new HeterodyneChannelSourceManager that uses frequency translation
and CIC decimation for each channel provided by the tuner.
2018-09-05 07:15:57 -04:00
Denny
1c6d19959a Class renaming and cleanup. 2018-09-05 06:34:13 -04:00
Denny
50bc3088b7 Removes unused legacy code. 2018-09-05 06:31:39 -04:00
Denny
09356848eb Merge branch 'master' into heterodyne_channelizer
# Conflicts:
#	src/main/java/io/github/dsheirer/dsp/filter/design/FilterViewer.java
2018-09-05 06:18:27 -04:00
Denny Sheirer
316bfb19c2 Merge pull request #400 from DSheirer/375-channelizer-synthesizer-gain-issue
375 channelizer synthesizer gain issue
2018-09-05 06:17:47 -04:00
Denny
c045c8f16d Updates heterodyne channel manager to correctly lock/unlock the tuner
controller.
2018-09-05 06:11:28 -04:00
Denny
8946d789ff WIP - updated tuners to use CIC decimation tuner channel sources instead
of polyphase channelizer.

Channel sample rate reduced to 30 kHz - seems to work better with P25
decoders than previous 25 kHz.
2018-09-04 08:16:57 -04:00
Denny
19399d9138 Resolves cutoff frequency for 2 channel polyphase synthesizer. 2018-09-01 06:32:24 -04:00
Denny
9acdf22b91 WIP - identifier framework and restructuring P25 messages to provide
identifiers.
2018-08-31 04:50:58 -04:00
Denny Sheirer
f963d821a1 Merge pull request #396 from DSheirer/394-p25-bitstream-output
394 P25 bit stream output
2018-08-18 03:52:36 -04:00
Denny
ade0cef50b Updates P25 C4FM and LSM decoders to provide demodulated bit streams
for recording using the new binary recorders/format.
2018-08-18 03:36:17 -04:00
Denny
2044c2a2de WIP - Updated P25 C4FM and LSM decoders to assemble dibits and push
reusable byte buffers out to the processing chain.  This enables the
processing chain to broadcast these buffers back to the decoder for
message assembly and to any running binary recorder.

Updated P25Decoder to be a reusable byte buffer listener.  The decoder
will now pass reusable byte buffers down to the message framer for
message processing.  This should make the code more efficient since it
can process byte arrays versus handling each individual dibit.

The P25MessageFramer has been updated to be a listener of reusable byte
buffers instead of Dibits, but the actual processing of byte arrays
has to be coded.

Added max recording size to the binary recorder so that it cycles at
500 kB max file size.
2018-08-17 07:27:04 -04:00
Denny Sheirer
1c312a50b6 Merge pull request #395 from DSheirer/393-raw-bitstream-recording
393 Recording raw decoded bitstream from supported decoder types
2018-08-11 08:09:22 -04:00
Denny
54f6bd09e2 Cleanup 2018-08-11 08:08:57 -04:00
Denny
5f8f2ec330 Resolves #393 - Adds support for recording demodulated (raw) bitstream
buffers.  None of the decoders currently support this yet, but the P25
will be updated to provide bitstream output for recording.

Renames the ReusableBuffer class to ReusableFloatBuffer for
consistency.
2018-08-11 08:02:58 -04:00
Denny Sheirer
d7e018f66e Merge pull request #392 from DSheirer/390-polyphase-channel-source-npe
390 PolyphaseChannelSource NPE
2018-08-11 05:43:35 -04:00
Denny
c2a232fdee Adds null checking to output processor. 2018-08-11 05:42:45 -04:00
Denny Sheirer
c5d2dcd0cf Merge pull request #391 from DSheirer/389-add-active-to-channel-states
389 Add ACTIVE Channel State and Update P25 Decoder State processing
2018-08-11 05:39:20 -04:00
Denny
8e4e4129ca Adds 'ACTIVE' as a valid channel state to indicate that the channel is
actively decoding, but no call or data is being transmitted.  This is
primarily used for P25 channels when TDU Link Control messages are
being transmitted in between voice or data transmissions on a channel.

Updated several of the P25 decoder state message processing sections to
keep the channel state UI updated with current call identifiers and
states.

Hard-coded channel state colors to remove inefficiency in the way that
channel state coloration was being handled.
2018-08-11 05:36:30 -04:00
Denny
9176ca610f Updates build to Version 0.4.0 Alpha 2 2018-08-05 07:51:40 -04:00
Denny Sheirer
97b31e4d9b Merge pull request #385 from DSheirer/372-icecast-streaming-reconnect
372 Icecast Streaming Reconnect and Metadata Updates
2018-08-05 07:48:28 -04:00
Denny
a3e5520d3e Resolves #372 - Icecast reconnect following network outage. Updates
the lost connection timeout from default of 60 seconds down to 5 seconds
so that the connection state updates more quickly.  Removed complex
error handling and replaced with a simpler approach to catch an error
and disconnect and then attempt a reconnect.

Added new 'Network Unavailable' error state that detects when the network
connection is unavaialable.

Updated the icecast metadata updater to close the TCP session after
successfully writing metadata to the socket.
2018-08-05 07:44:46 -04:00
Denny
8c456972cc Decreases usable bandwidth of the R820T tuner to 98% so that channels
do not get allocated at the spectral extremes where the signal is
degraded.
2018-08-03 11:02:47 -04:00
Denny
35d81ab31d Decreases buffer size for low sample rates for RTL2832 tuners. 2018-08-03 08:06:00 -04:00
Denny
b90b06e600 Merge remote-tracking branch 'origin/master' 2018-08-03 07:57:58 -04:00
Denny
04144ca677 Adds additional RTL 2832 sample rate options 2018-08-03 07:57:47 -04:00
Denny Sheirer
d3c7a50918 Merge pull request #384 from DSheirer/382-rtl-tuner-sample-rate-options
382 - Additional Sample Rate Options for RTL2832 Tuners
2018-08-03 07:53:01 -04:00
Denny
0fd29fa35a Resolves #382 - Adds additional sample rate options for the RTL2832
tuner and adjusts the low sample rate buffer size down to 16 kb.
2018-08-03 07:50:36 -04:00
Denny Sheirer
c00b173064 Merge pull request #383 from DSheirer/381-rtl-tuner-adaptive-native-buffer-size
381 Adaptive USB Transfer Buffer Sizes for RTL2832 Tuners
2018-08-03 07:29:31 -04:00
Denny
2718367259 Resolves #381. Updates USB transfer processor to use native transfer
buffer sizes that adapt to the selected sample rate.
2018-08-03 07:27:58 -04:00
Denny Sheirer
3f0c13096a Merge pull request #380 from DSheirer/374-rtl-tuner-sample-rate-issue
374 RTL Tuner Sample Rates Not Supported (1.44 & 2.88 MHz)
2018-08-03 07:04:28 -04:00
Denny
71ba4adbd7 Resolves #374
Updates polyphase channel manager to correctly calculate the number of
channels for each sample rate change.
2018-08-03 07:02:58 -04:00
Denny Sheirer
3908e29a2e Merge pull request #376 from DSheirer/373-tuner-ppm-monitoring-and-adjustment
#373 - Tuner PPM Error Monitoring and R820T PPM Auto-Correction
2018-07-29 11:18:47 -04:00
Denny
0c47efea03 Resolves #373 - Adds support for Tuner frequency error PPM monitoring
and auto-correction for R820T tuners.

-Updated TunerModel and TunerViewPanel to add 2 new columns to
display the current tuner's PPM setting and (when available) the
measured frequency error averaged over 5 second intervals provided by
certain decoders (e.g. P25 C4FM and LSM).  Frequency error value from
P25 decoders is provided from the Costas Loop PLL once 2 or more P25
message sync events have been observed in a short period.
-Updated R820T tuner to auto-correct PPM when measured frequency error
exceeds the +/- 0.4PPM threshold for a period of 30 seconds or more.
Feature can be enabled/disabled by user in the tuner editor panel.

Resolved several issues with P25 C4FM/LSM decoders that was impacting
performance related to channel misalignment from tuner PPM offsets.
2018-07-29 10:48:29 -04:00
Denny
5c0d97d942 Resolves issue with array out of bounds exception on shutdown while
removing tuners from the tuner model.
2018-07-20 05:59:00 -04:00
Denny
61ae0d2d83 Resolves issue with saved setting for spectral display panel enabled state 2018-07-20 05:58:23 -04:00
161 changed files with 4755 additions and 1828 deletions

View File

@@ -14,6 +14,7 @@
<w>nutall</w>
<w>overflowable</w>
<w>polyphase</w>
<w>qpsk</w>
<w>remez</w>
<w>resampler</w>
<w>samplerate</w>

View File

@@ -15,7 +15,7 @@ repositories {
mavenCentral()
}
version = '0.4.0-alpha.1'
version = '0.4.0-alpha.4'
sourceCompatibility = 1.8
sourceSets {

View File

@@ -27,7 +27,7 @@ import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.buffer.ReusableAudioPacketQueue;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
* This class is designed to support 8 kHz sample rate demodulated audio.
*/
public class AudioModule extends Module implements IAudioPacketProvider, IReusableBufferListener,
ISquelchStateListener, Listener<ReusableBuffer>
ISquelchStateListener, Listener<ReusableFloatBuffer>
{
protected static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private ReusableAudioPacketQueue mAudioPacketQueue = new ReusableAudioPacketQueue("AudioModule");
@@ -139,11 +139,11 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
if(mAudioPacketListener != null && mSquelchState == SquelchState.UNSQUELCH)
{
ReusableBuffer highPassFiltered = mHighPassFilter.filter(reusableBuffer);
ReusableFloatBuffer highPassFiltered = mHighPassFilter.filter(reusableFloatBuffer);
ReusableAudioPacket audioPacket = mAudioPacketQueue.getBuffer(highPassFiltered.getSampleCount());
audioPacket.loadAudioFrom(highPassFiltered);
@@ -155,7 +155,7 @@ public class AudioModule extends Module implements IAudioPacketProvider, IReusab
}
else
{
reusableBuffer.decrementUserCount();
reusableFloatBuffer.decrementUserCount();
}
}

View File

@@ -245,7 +245,11 @@ public abstract class AudioBroadcaster implements Listener<AudioRecording>
{
if(mBroadcastState != state)
{
mLog.info("[" + getStreamName() + "] status: " + state);
if(state == BroadcastState.CONNECTED || state == BroadcastState.DISCONNECTED)
{
mLog.info("[" + getStreamName() + "] status: " + state);
}
mBroadcastState = state;
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_STATE_CHANGE));

View File

@@ -70,6 +70,11 @@ public enum BroadcastState
*/
MOUNT_POINT_IN_USE("Mount Point In Use", false),
/**
* Network is unavailable or the server address cannot be resolved
*/
NETWORK_UNAVAILABLE("Network Unavailable", false),
/**
* Server is not known or reachable
*/

View File

@@ -20,9 +20,14 @@ package io.github.dsheirer.audio.broadcast;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.Color;
import java.awt.Component;
public class BroadcastStatusPanel extends JPanel
{
@@ -96,7 +101,8 @@ public class BroadcastStatusPanel extends JPanel
setBackground(table.getBackground());
setForeground(Color.LIGHT_GRAY);
}
else if(state == BroadcastState.INVALID_SETTINGS)
else if(state == BroadcastState.INVALID_SETTINGS ||
state == BroadcastState.NETWORK_UNAVAILABLE)
{
setBackground(Color.YELLOW);
setForeground(table.getForeground());

View File

@@ -22,6 +22,7 @@ import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
import io.github.dsheirer.channel.metadata.Metadata;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
@@ -35,7 +36,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.nio.channels.UnresolvedAddressException;
@@ -117,70 +117,64 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
{
mMetadataQueue.offer(getSong(metadata));
if(mUpdating.compareAndSet(false, true))
if(!mUpdating.get() && !mMetadataQueue.isEmpty())
{
String song = mMetadataQueue.poll();
while(song != null)
Runnable runnable = new Runnable()
{
HttpRequest updateRequest = createUpdateRequest(song);
if(updateRequest != null)
@Override
public void run()
{
ThreadPool.SCHEDULED.schedule(new Runnable()
if(!mMetadataQueue.isEmpty() && mUpdating.compareAndSet(false, true))
{
@Override
public void run()
try
{
try
{
ConnectFuture connectFuture = getSocketConnector()
.connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
mIcecastConfiguration.getPort()));
connectFuture.awaitUninterruptibly();
IoSession session = connectFuture.getSession();
ConnectFuture connectFuture = getSocketConnector()
.connect(new InetSocketAddress(mIcecastConfiguration.getHost(),
mIcecastConfiguration.getPort()));
connectFuture.awaitUninterruptibly();
IoSession session = connectFuture.getSession();
if(session != null)
{
session.write(updateRequest);
}
}
catch(UnresolvedAddressException uae)
if(session != null)
{
//Do nothing - the server is temporarily unavailable
}
catch(Exception e)
{
Throwable throwableCause = e.getCause();
String song = mMetadataQueue.poll();
if(throwableCause instanceof ConnectException)
while(song != null)
{
//Do nothing, the server is unavailable
}
else if(throwableCause instanceof UnresolvedAddressException)
{
//Do nothing - the server is temporarily unavailable
}
else
{
if(!mStackTraceLoggingSuppressed)
HttpRequest updateRequest = createUpdateRequest(song);
if(updateRequest != null)
{
mLog.error("Error sending metadata update. Future errors will " +
"be suppressed", e);
mStackTraceLoggingSuppressed = true;
session.write(updateRequest);
}
//Fetch next metadata update to send
song = mMetadataQueue.poll();
}
session.closeNow();
}
}
}, 0l, TimeUnit.SECONDS);
catch(UnresolvedAddressException | RuntimeIoException ure)
{
//Do nothing - the server is temporarily unavailable
}
catch(Exception e)
{
Throwable throwableCause = e.getCause();
if(!mStackTraceLoggingSuppressed)
{
mLog.error("Error sending metadata update. Future errors will be suppressed", e);
mStackTraceLoggingSuppressed = true;
}
}
mUpdating.set(false);
}
}
};
//Fetch next metadata update to send
song = mMetadataQueue.poll();
}
mUpdating.set(false);
ThreadPool.SCHEDULED.schedule(runnable, 0l, TimeUnit.SECONDS);
}
}

View File

@@ -27,15 +27,14 @@ import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.core.write.WriteTimeoutException;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.UnresolvedAddressException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -46,7 +45,8 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
private final static String TERMINATOR = "\r\n";
private final static String SEPARATOR = ":";
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
private static final long CONNECTION_ATTEMPT_TIMEOUT = 5000; //5 seconds
private static final long CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS = 5000; //5 seconds
private static final int WRITE_TIMEOUT_SECONDS = 5;
private NioSocketConnector mSocketConnector;
private IoSession mStreamingSession = null;
@@ -101,7 +101,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
if(mSocketConnector == null)
{
mSocketConnector = new NioSocketConnector();
mSocketConnector.setConnectTimeoutCheckInterval(CONNECTION_ATTEMPT_TIMEOUT);
mSocketConnector.getSessionConfig().setWriteTimeout(WRITE_TIMEOUT_SECONDS);
// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
// loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
@@ -127,45 +127,41 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
getBroadcastConfiguration().getPort()));
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT, TimeUnit.MILLISECONDS);
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
mStreamingSession = future.getSession();
if(connected)
{
mStreamingSession = future.getSession();
mConnecting.set(false);
return;
}
}
catch(RuntimeIoException rioe)
{
if(rioe.getCause() instanceof SocketException)
{
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
mConnecting.set(false);
return;
}
}
catch(UnresolvedAddressException uae)
{
//What do you do with this? Nothing.
setBroadcastState(BroadcastState.NETWORK_UNAVAILABLE);
mConnecting.set(false);
return;
}
catch(InterruptedException ie)
catch(Exception e)
{
//We couldn't connect within the timeout period -- disconnect so that we can try again later
disconnect();
}
catch(RuntimeIoException rie)
{
Throwable throwableCause = rie.getCause();
if(throwableCause instanceof ConnectException)
{
setBroadcastState(BroadcastState.NO_SERVER);
}
else if(throwableCause != null)
{
setBroadcastState(BroadcastState.ERROR);
mLog.debug("[" + getStreamName() + "] failed to connect", rie);
}
else
{
setBroadcastState(BroadcastState.ERROR);
mLog.debug("[" + getStreamName() + "] failed to connect - no exception is available");
}
disconnect();
mLog.error("Error", e);
//Disregard ... we'll disconnect and try again
}
catch(Throwable t)
{
disconnect();
mLog.error("Throwable error caught", t);
}
disconnect();
mConnecting.set(false);
}
};
@@ -188,6 +184,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
}
else
{
setBroadcastState(BroadcastState.DISCONNECTED);
mLastConnectionAttempt = System.currentTimeMillis();
}
}
@@ -263,49 +260,12 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception
{
if(cause instanceof IOException)
{
IOException ioe = (IOException)cause;
if(ioe.getMessage() != null)
{
String reason = ioe.getMessage();
if(connected())
{
disconnect();
}
else if(reason.startsWith("Connection reset"))
{
disconnect();
}
else if(reason.startsWith("Operation timed out"))
{
disconnect();
}
else
{
setBroadcastState(BroadcastState.ERROR);
disconnect();
}
}
else if(ioe instanceof WriteTimeoutException)
{
disconnect();
}
else
{
setBroadcastState(BroadcastState.ERROR);
disconnect();
mLog.error("[" + getStreamName() + "] Unspecified IO error - streaming halted.", cause);
}
}
else
if(!(cause instanceof IOException))
{
mLog.error("[" + getStreamName() + "] Broadcast error", cause);
setBroadcastState(BroadcastState.ERROR);
disconnect();
}
disconnect();
}
@Override

View File

@@ -1,6 +1,26 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.bits;
public interface ISyncProcessor
{
public void checkSync( long value );
/**
* Checks if the value matches a known sync pattern
* @param value
* @return
*/
boolean checkSync(long value);
}

View File

@@ -1,111 +1,130 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.bits;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Multi-Sync pattern matcher. Accepts multiple sync detector objects for
* Multi-Sync pattern matcher. Accepts multiple sync detector objects for
* comparision against the incoming bit stream.
*
*
* Note: works for sync patterns up to 63 bits (integer size - 1 ) long.
* If a 64 bit or larger sync pattern is applied, then the wrapping feature
* of the Long.rotate methods will wrap the MSB around to the LSB and
* corrupt the matching value.
*
*/
public class MultiSyncPatternMatcher
{
private List<ISyncProcessor> mSyncProcessors = new ArrayList<ISyncProcessor>();
private long mBits = 0;
private long mMask = 0;
public MultiSyncPatternMatcher( int syncSize )
{
Validate.isTrue(syncSize < 64);
//Setup a bit mask of all ones the length of the sync pattern
mMask = (long)( ( Math.pow( 2, syncSize ) ) - 1 );
}
public void dispose()
{
mSyncProcessors.clear();
}
/**
* Processes two bits before checking sync processors for a match.
*/
public void receive( boolean bit1, boolean bit2 )
{
mBits = Long.rotateLeft( mBits, 1 );
mBits &= mMask;
if( bit1 )
{
mBits += 1;
}
private final static Logger mLog = LoggerFactory.getLogger(MultiSyncPatternMatcher.class);
mBits = Long.rotateLeft( mBits, 1 );
mBits &= mMask;
if( bit2 )
{
mBits += 1;
}
private ISyncDetectListener mSyncDetectListener;
private int mSyncLossThreshold;
private int mBitCount;
private List<ISyncProcessor> mSyncProcessors = new ArrayList<ISyncProcessor>();
for( ISyncProcessor processor: mSyncProcessors )
{
processor.checkSync( mBits );
}
}
/**
* Processes one bit before checking sync processors for a match.
*/
public void receive( boolean bit )
{
mBits = Long.rotateLeft( mBits, 1 );
mBits &= mMask;
if( bit )
{
mBits += 1;
}
private long mBits = 0;
private long mMask = 0;
for( ISyncProcessor processor: mSyncProcessors )
{
processor.checkSync( mBits );
}
}
public MultiSyncPatternMatcher(ISyncDetectListener syncDetectListener, int syncLossThreshold, int syncSize)
{
mSyncDetectListener = syncDetectListener;
mSyncLossThreshold = syncLossThreshold;
Validate.isTrue(syncSize < 64);
/**
* Adds a sync processor to receive the bit stream.
*/
public void add( ISyncProcessor processor )
{
mSyncProcessors.add( processor );
}
//Setup a bit mask of all ones the length of the sync pattern
mMask = (long)((Math.pow(2, syncSize)) - 1);
}
public void dispose()
{
mSyncProcessors.clear();
}
/**
* Processes two bits before checking sync processors for a match.
*/
public void receive(boolean bit1, boolean bit2)
{
mBits = Long.rotateLeft(mBits, 1);
mBits &= mMask;
if(bit1)
{
mBits += 1;
}
mBits = Long.rotateLeft(mBits, 1);
mBits &= mMask;
if(bit2)
{
mBits += 1;
}
mBitCount += 2;
for(ISyncProcessor processor : mSyncProcessors)
{
if(processor.checkSync(mBits))
{
mSyncDetectListener.syncDetected();
mBitCount = 0;
}
}
if(mBitCount > mSyncLossThreshold)
{
mBitCount = 0;
mSyncDetectListener.syncLost();
}
}
/**
* Processes one bit before checking sync processors for a match.
*/
public void receive(boolean bit)
{
mBits = Long.rotateLeft(mBits, 1);
mBits &= mMask;
if(bit)
{
mBits += 1;
}
for(ISyncProcessor processor : mSyncProcessors)
{
processor.checkSync(mBits);
}
}
/**
* Adds a sync processor to receive the bit stream.
*/
public void add(ISyncProcessor processor)
{
mSyncProcessors.add(processor);
}
}

View File

@@ -1,50 +1,68 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.bits;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
public class SoftSyncDetector implements ISyncProcessor
{
private ISyncDetectListener mListener;
private long mPattern;
private int mThreshold;
public SoftSyncDetector( ISyncDetectListener listener, long pattern, int threshold )
{
this( pattern, threshold );
mListener = listener;
}
public SoftSyncDetector( long pattern, int threshold )
{
mPattern = pattern;
mThreshold = threshold;
}
public void dispose()
{
mListener = null;
}
private ISyncDetectListener mListener;
private long mPattern;
private int mThreshold;
@Override
public void checkSync( long value )
{
long difference = value ^ mPattern;
if( ( difference == 0 || Long.bitCount( difference ) <= mThreshold ) &&
mListener != null )
{
mListener.syncDetected();
}
}
public void setThreshold( int threshold )
{
mThreshold = threshold;
}
public void setListener( ISyncDetectListener listener )
{
mListener = listener;
}
public SoftSyncDetector(ISyncDetectListener listener, long pattern, int threshold)
{
this(pattern, threshold);
mListener = listener;
}
public SoftSyncDetector(long pattern, int threshold)
{
mPattern = pattern;
mThreshold = threshold;
}
public void dispose()
{
mListener = null;
}
@Override
public boolean checkSync(long value)
{
long difference = value ^ mPattern;
if((difference == 0 || Long.bitCount(difference) <= mThreshold) &&
mListener != null)
{
mListener.syncDetected();
return true;
}
return false;
}
public void setThreshold(int threshold)
{
mThreshold = threshold;
}
public void setListener(ISyncDetectListener listener)
{
mListener = listener;
}
}

View File

@@ -1,35 +1,57 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.bits;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
public class SyncDetector implements ISyncProcessor
{
private long mPattern;
private ISyncDetectListener mListener;
private long mPattern;
private ISyncDetectListener mListener;
public SyncDetector( long pattern )
{
mPattern = pattern;
}
public SyncDetector( long pattern, ISyncDetectListener listener )
{
this( pattern );
mListener = listener;
}
public void setListener( ISyncDetectListener listener )
{
mListener = listener;
}
public SyncDetector(long pattern)
{
mPattern = pattern;
}
@Override
public void checkSync( long value )
{
if( value == mPattern && mListener != null )
{
mListener.syncDetected();
}
}
public SyncDetector(long pattern, ISyncDetectListener listener)
{
this(pattern);
mListener = listener;
}
public void setListener(ISyncDetectListener listener)
{
mListener = listener;
}
@Override
public boolean checkSync(long value)
{
if(value == mPattern)
{
if(mListener != null)
{
mListener.syncDetected();
}
return true;
}
return false;
}
}

View File

@@ -123,6 +123,11 @@ public class AttributeMonitor<T>
{
mCurrentValue = null;
mOccurrenceCounts.clear();
if(mListener != null)
{
mListener.receive(new AttributeChangeRequest(mAttribute, null, null));
}
}
/**

View File

@@ -25,32 +25,36 @@ import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.controller.channel.ChannelUtils;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
public class ChannelMetadataPanel extends JPanel implements ListSelectionListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelMetadataPanel.class);
private static final String PROPERTY_PREFIX_BACKGROUND = "channel.metadata.panel.state.color.";
private static final String PROPERTY_PREFIX_FOREGROUND = "channel.metadata.panel.text.color.";
private ChannelProcessingManager mChannelProcessingManager;
private IconManager mIconManager;
private JTable mTable;
private Broadcaster<ProcessingChain> mSelectedProcessingChainBroadcaster = new Broadcaster<>();
private Map<State,Color> mBackgroundColors = new HashMap<>();
private Map<State,Color> mForegroundColors = new HashMap<>();
/**
* Table view for currently decoding channel metadata
@@ -94,7 +98,34 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
add(scrollPane);
// new TableColumnWidthMonitor(mTable, PROPERTY_PREFIX, new int[] {15,15,15,15,25,25,25,25,15,40});
setColors();
}
/**
* Setup the background and foreground color palette for the various channel states.
*/
private void setColors()
{
mBackgroundColors.put(State.ACTIVE, Color.CYAN);
mForegroundColors.put(State.ACTIVE, Color.YELLOW);
mBackgroundColors.put(State.CALL, Color.BLUE);
mForegroundColors.put(State.CALL, Color.YELLOW);
mBackgroundColors.put(State.CONTROL, Color.ORANGE);
mForegroundColors.put(State.CONTROL, Color.BLUE);
mBackgroundColors.put(State.DATA, Color.GREEN);
mForegroundColors.put(State.DATA, Color.BLUE);
mBackgroundColors.put(State.ENCRYPTED, Color.MAGENTA);
mForegroundColors.put(State.ENCRYPTED, Color.WHITE);
mBackgroundColors.put(State.FADE, Color.LIGHT_GRAY);
mForegroundColors.put(State.FADE, Color.DARK_GRAY);
mBackgroundColors.put(State.IDLE, Color.WHITE);
mForegroundColors.put(State.IDLE, Color.DARK_GRAY);
mBackgroundColors.put(State.OVERFLOW, Color.RED);
mForegroundColors.put(State.OVERFLOW, Color.YELLOW);
mBackgroundColors.put(State.RESET, Color.PINK);
mForegroundColors.put(State.RESET, Color.YELLOW);
mBackgroundColors.put(State.TEARDOWN, Color.DARK_GRAY);
mForegroundColors.put(State.TEARDOWN, Color.WHITE);
}
@Override
@@ -185,44 +216,14 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
State state = (State)value;
label.setText(state.getDisplayValue());
switch(state)
if(mBackgroundColors.containsKey(state))
{
case CALL:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "call", Color.BLUE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "call", Color.YELLOW);
break;
case CONTROL:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "control", Color.ORANGE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "control", Color.BLUE);
break;
case DATA:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "data", Color.GREEN);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "data", Color.BLUE);
break;
case ENCRYPTED:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "encrypted", Color.MAGENTA);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "encrypted", Color.WHITE);
break;
case FADE:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "fade", Color.LIGHT_GRAY.BLUE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "fade", Color.YELLOW);
break;
case IDLE:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "idle", Color.WHITE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "idle", Color.BLUE);
break;
case OVERFLOW:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "overflow", Color.RED);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "overflow", Color.YELLOW);
break;
case RESET:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "reset", Color.PINK);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "reset", Color.YELLOW);
break;
case TEARDOWN:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "teardown", Color.DARK_GRAY);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "teardown", Color.WHITE);
break;
background = mBackgroundColors.get(state);
}
if(mForegroundColors.containsKey(state))
{
foreground = mForegroundColors.get(state);
}
}
else

View File

@@ -35,17 +35,16 @@ import io.github.dsheirer.module.decode.event.ICallEventProvider;
import io.github.dsheirer.sample.IOverflowListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.github.dsheirer.source.SourceEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, ISourceEventListener, IHeartbeatListener, ISquelchStateProvider,
IAttributeChangeRequestListener, IOverflowListener
IDecoderStateEventProvider, ISourceEventListener, ISourceEventProvider, IHeartbeatListener,
ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
@@ -57,12 +56,13 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
private Listener<CallEvent> mCallEventListener;
private Listener<DecoderStateEvent> mDecoderStateListener;
private Listener<SquelchState> mSquelchStateListener;
private Listener<SourceEvent> mExternalSourceEventListener;
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent;
private SourceChangeListener mSourceChangeListener;
private SourceEventListener mInternalSourceEventListener;
private boolean mSquelchLocked = false;
private boolean mSelected = false;
@@ -148,6 +148,8 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mTrafficChannelCallEvent = null;
mSquelchLocked = false;
setState(State.TEARDOWN);
}
public void dispose()
@@ -160,12 +162,12 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
@Override
public Listener<SourceEvent> getSourceEventListener()
{
if(mSourceChangeListener == null)
if(mInternalSourceEventListener == null)
{
mSourceChangeListener = new SourceChangeListener();
mInternalSourceEventListener = new SourceEventListener();
}
return mSourceChangeListener;
return mInternalSourceEventListener;
}
private boolean isStandardChannel()
@@ -255,6 +257,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
}
/**
* Broadcasts the source event to a registered external source event listener
*/
protected void broadcast(SourceEvent sourceEvent)
{
if(mExternalSourceEventListener != null)
{
mExternalSourceEventListener.receive(sourceEvent);
}
}
/**
* Sets the squelch state listener
*/
@@ -292,6 +305,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{
switch(state)
{
case ACTIVE:
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
break;
case CONTROL:
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
if(isStandardChannel())
@@ -516,18 +534,46 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
/**
* Listener to receive frequency change events and rebroadcast them to the decoder states
* Registers the listener to receive source events from the channel state
*/
public class SourceChangeListener implements Listener<SourceEvent>
@Override
public void setSourceEventListener(Listener<SourceEvent> listener)
{
mExternalSourceEventListener = listener;
}
/**
* De-Registers a listener from receiving source events from the channel state
*/
@Override
public void removeSourceEventListener()
{
mExternalSourceEventListener = null;
}
/**
* Listener to receive source events.
*/
public class SourceEventListener implements Listener<SourceEvent>
{
@Override
public void receive(SourceEvent sourceEvent)
{
if(sourceEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
switch(sourceEvent.getEvent())
{
long frequency = sourceEvent.getValue().longValue();
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
case NOTIFICATION_FREQUENCY_CHANGE:
//Rebroadcast source frequency change events for the decoder(s) to process
long frequency = sourceEvent.getValue().longValue();
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
//Rebroadcast frequency error measurements to external listener if we're currently
//in an active (ie sync locked) state.
if(getState().isActiveState())
{
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(), mChannelType.name()));
}
break;
}
}
}

View File

@@ -24,54 +24,114 @@ import java.util.EnumSet;
*/
public enum State
{
IDLE("IDLE")
/**
* Active state indicates that valid messages are being decoded that do not indicate any call or data
* activity. For example, this is used for P25 when a channel is actively transmitting Terminator Data
* Units in between data packets or at the beginning or end of a call.
*/
ACTIVE("ACTIVE")
{
@Override
public boolean canChangeTo(State state)
{
return state != TEARDOWN && state != RESET && state != OVERFLOW;
return state == CALL |
state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
/**
* Indicates that call messages are being decoded, or a call is in progress (ie producing audio).
*/
CALL("CALL")
{
@Override
public boolean canChangeTo(State state)
{
return state == CONTROL || state == DATA || state == ENCRYPTED || state == FADE || state == TEARDOWN;
}
},
DATA("DATA")
{
@Override
public boolean canChangeTo(State state)
{
return state == CALL || state == CONTROL || state == ENCRYPTED || state == FADE || state == TEARDOWN;
}
},
ENCRYPTED("ENCRYPTED")
{
@Override
public boolean canChangeTo(State state)
{
return state == FADE || state == TEARDOWN;
return state == ACTIVE ||
state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
/**
* Indicates a trunking control channel is being decoded.
*/
CONTROL("CONTROL")
{
@Override
public boolean canChangeTo(State state)
{
return state == IDLE || state == FADE;
return state == IDLE ||
state == FADE;
}
},
/**
* Indicates data packets are being decoded
*/
DATA("DATA")
{
@Override
public boolean canChangeTo(State state)
{
return state == ACTIVE ||
state == CALL ||
state == CONTROL ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
/**
* Indicates encrypted audio is detected.
*/
ENCRYPTED("ENCRYPTED")
{
@Override
public boolean canChangeTo(State state)
{
return state == FADE ||
state == TEARDOWN;
}
},
/**
* Fade state occurs when the channel is no longer actively decoding an messages. This is a transitory
* state before transition to IDLE or back to an active state.
*/
FADE("FADE")
{
@Override
public boolean canChangeTo(State state)
{
return state != FADE && state != RESET && state != OVERFLOW;
return state != FADE &&
state != RESET &&
state != OVERFLOW;
}
},
/**
* Indicates the channel is idle and not actively decoding any messages
*/
IDLE("IDLE")
{
@Override
public boolean canChangeTo(State state)
{
return state != TEARDOWN &&
state != RESET &&
state != OVERFLOW;
}
},
/**
* Indicates that the channel's sample buffer has overflowed and new buffer samples are no longer being
* delivered to the channel. This state persists until the processing catches up and reduces the number
* of samples in the input buffer below a threshold value. Transition to and from this state is exclusively
* managed by the Channel State and no other channel states can be processed from the channel decoders
* until the channel state is taken out of overflow.
*/
OVERFLOW("OVERFLOW")
{
@Override
@@ -80,6 +140,9 @@ public enum State
return false;
}
},
/**
* Indicates that the channel has been reset and is ready to use for a new decoding session.
*/
RESET("RESET")
{
@Override
@@ -88,6 +151,10 @@ public enum State
return state == IDLE;
}
},
/**
* Call or data teardown. This state is used by traffic channels to indicate that a call or data session
* has concluded and the channel is ready for tear down and reuse.
*/
TEARDOWN("TEARDOWN")
{
@Override
@@ -99,7 +166,7 @@ public enum State
private String mDisplayValue;
public static final EnumSet<State> CALL_STATES = EnumSet.of(CALL, CONTROL, DATA, ENCRYPTED);
public static final EnumSet<State> CALL_STATES = EnumSet.of(ACTIVE, CALL, CONTROL, DATA, ENCRYPTED);
public static final EnumSet<State> IDLE_STATES = EnumSet.of(IDLE, FADE);
private State(String displayValue)
@@ -109,6 +176,17 @@ public enum State
public abstract boolean canChangeTo(State state);
/**
* Indicates that this state is an active (ie decoding) state and is one of the states
* enumerated in the CALL_STATES enumeration set.
*
* @return true if this state is an active state
*/
public boolean isActiveState()
{
return CALL_STATES.contains(this);
}
public String getDisplayValue()
{
return mDisplayValue;

View File

@@ -1,3 +1,18 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.controller.channel;
import com.jidesoft.swing.JideSplitPane;
@@ -24,8 +39,7 @@ import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ChannelController extends JPanel
implements ActionListener, ListSelectionListener
public class ChannelController extends JPanel implements ActionListener, ListSelectionListener
{
private static final long serialVersionUID = 1L;
@@ -168,9 +182,13 @@ public class ChannelController extends JPanel
if( index >= 0 )
{
int translatedIndex = mChannelTable.convertRowIndexToView( index );
mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
mChannelTable.scrollRectToVisible(
new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
if(translatedIndex >= 0)
{
mChannelTable.setRowSelectionInterval( translatedIndex, translatedIndex );
mChannelTable.scrollRectToVisible(
new Rectangle( mChannelTable.getCellRect( translatedIndex, 0, true ) ) );
}
}
}

View File

@@ -33,6 +33,7 @@ import io.github.dsheirer.module.decode.event.MessageActivityModel;
import io.github.dsheirer.module.log.EventLogManager;
import io.github.dsheirer.record.RecorderManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.binary.BinaryRecorder;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.source.Source;
@@ -211,7 +212,8 @@ public class ChannelProcessingManager implements ChannelEventListener
try
{
source = mSourceManager.getSource(channel.getSourceConfiguration(),
channel.getDecodeConfiguration().getDecoderType().getChannelBandwidth());
channel.getDecodeConfiguration().getDecoderType().getChannelBandwidth(),
channel.getDecodeConfiguration().getDecoderType().getChannelSpecification());
}
catch(SourceException se)
{
@@ -308,6 +310,25 @@ public class ChannelProcessingManager implements ChannelEventListener
{
processingChain.addModule(mRecorderManager.getBasebandRecorder(channel.toString()));
}
/* Add decoded bit stream recorder if the decoder supports bitstream output */
if(DecoderFactory.getBitstreamDecoders().contains(channel.getDecodeConfiguration().getDecoderType()))
{
if((recorders.contains(RecorderType.DEMODULATED_BIT_STREAM) &&
channel.getChannelType() == Channel.ChannelType.STANDARD))
{
processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(),
channel.toString()));
}
/* Add traffic channel decoded bit stream recorder */
if(recorders.contains(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM) &&
channel.getChannelType() == Channel.ChannelType.TRAFFIC)
{
processingChain.addModule(new BinaryRecorder(mRecorderManager.getRecordingBasePath(),
channel.toString()));
}
}
}
processingChain.setSource(source);

View File

@@ -21,7 +21,7 @@ import io.github.dsheirer.dsp.filter.resample.RealResampler;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory;
*
* Provides normal or inverted decoded output.
*/
public class AFSK1200Decoder implements Listener<ReusableBuffer>
public class AFSK1200Decoder implements Listener<ReusableFloatBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(AFSK1200Decoder.class);
@@ -110,7 +110,7 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
*/
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
mResampler.resample(buffer);
}
@@ -142,10 +142,10 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>
}
public class Decoder implements Listener<ReusableBuffer>
public class Decoder implements Listener<ReusableFloatBuffer>
{
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
//Calculate correlation values against each 1200/1800 reference signal
mCorrelationValuesMark = mCorrelatorMark.process(buffer);
@@ -212,17 +212,17 @@ public class AFSK1200Decoder implements Listener<ReusableBuffer>
* Processes each sample in the incoming sample buffer against a generated reference sample set for one symbol
* period to derive a correlation value that is in-turn averaged over one symbol period.
*
* @param reusableBuffer containing FM demodulated samples
* @param reusableFloatBuffer containing FM demodulated samples
* @return a reusable array of correlation values for each sample
*/
public float[] process(ReusableBuffer reusableBuffer)
public float[] process(ReusableFloatBuffer reusableFloatBuffer)
{
if(mCorrelationValues == null || mCorrelationValues.length != reusableBuffer.getSampleCount())
if(mCorrelationValues == null || mCorrelationValues.length != reusableFloatBuffer.getSampleCount())
{
mCorrelationValues = new float[reusableBuffer.getSampleCount()];
mCorrelationValues = new float[reusableFloatBuffer.getSampleCount()];
}
float[] samples = reusableBuffer.getSamples();
float[] samples = reusableFloatBuffer.getSamples();
int y;

View File

@@ -15,9 +15,9 @@
******************************************************************************/
package io.github.dsheirer.dsp.am;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
@@ -63,11 +63,11 @@ public class AMDemodulator
* @param complexBuffer to demodulate
* @return demodulated audio buffer.
*/
public ReusableBuffer demodulate(ReusableComplexBuffer complexBuffer)
public ReusableFloatBuffer demodulate(ReusableComplexBuffer complexBuffer)
{
ReusableBuffer reusableBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
ReusableFloatBuffer reusableFloatBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
float[] input = complexBuffer.getSamples();
float[] output = reusableBuffer.getSamples();
float[] output = reusableFloatBuffer.getSamples();
mOutputBufferPointer = 0;
for(int x= 0; x < complexBuffer.getSamples().length; x += 2)
@@ -77,6 +77,6 @@ public class AMDemodulator
complexBuffer.decrementUserCount();
return reusableBuffer;
return reusableFloatBuffer;
}
}

View File

@@ -22,7 +22,6 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter;
import io.github.dsheirer.dsp.filter.cic.ComplexPrimeCICDecimate;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
@@ -358,15 +357,6 @@ public class FilterFactory
return (int)(Math.round((double)attenuation / (22.0d * frequency)));
}
public static ComplexPrimeCICDecimate getDecimationFilter(int sampleRate, int decimatedRate, int order,
int passFrequency, int attenuation, Window.WindowType windowType)
{
int decimationRate = (int)(sampleRate / decimatedRate);
return new ComplexPrimeCICDecimate(decimationRate, order,
passFrequency, attenuation, windowType);
}
/**
* Determines decimation rate(s) for a polyphase decimation filter.
*
@@ -760,26 +750,28 @@ public class FilterFactory
* Polyphase M2 synthesizer sync filter. Designed for multiple M2 oversampled channel inputs and
* an M*channel count output.
*
* @param channelBandwidth per channel
* @param channels count
* @param channelSampleRate per input channel
* @param channelBandwidth per input channel
* @param channels input count
* @param tapsPerChannel minumum. This may be increased to meet the band edge -6.02dB requirement
* @return filter
* @throws FilterDesignException
*/
public static float[] getSincM2Synthesizer(double channelBandwidth, int channels, int tapsPerChannel)
throws FilterDesignException
public static float[] getSincM2Synthesizer(double channelSampleRate, double channelBandwidth, int channels,
int tapsPerChannel) throws FilterDesignException
{
int filterLength = (channels * tapsPerChannel) - 1;
double cutoff = (channelBandwidth * 1.10) / (channelSampleRate * (double)channels);
//Design the prototype synthesizer with 105% of the channel bandwidth produced by the channelizer.
float[] taps = FilterFactory.getKaiserSinc(filterLength, channelBandwidth * 1.05, 80.0);
float[] taps = FilterFactory.getKaiserSinc(filterLength, cutoff, 80.0);
//This is an odd length filter - increase the length by 1 by pre-padding a zero coefficient
float[] extendedTaps = new float[taps.length + 1];
System.arraycopy(taps, 0, extendedTaps, 1, taps.length);
//Adjust the overall gain of the filter to an objective of 1.0 divided by the number of channels
return normalize(extendedTaps, 1.0f);
return extendedTaps;
}
/**
@@ -918,17 +910,18 @@ public class FilterFactory
mLog.debug("Filter Length: " + (extendedTaps.length));
mLog.debug("Requested Cutoff Frequency: " + (sampleRate * bandEdge));
mLog.debug("Actual Cutoff Frequency: " + (sampleRate * cutoffFrequency));
mLog.debug("Attenuation at 0.0 OBJECTIVE: " + 0.0);
mLog.debug("Attenuation at 1.0 OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE);
mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00) + "\tFrequency: " + (sampleRate * bandEdge * 1.0));
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25));
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50) + "\tFrequency: " + (sampleRate * bandEdge * 1.5));
mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75) + "\tFrequency: " + (sampleRate * bandEdge * 1.75));
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00) + "\tFrequency: " + (sampleRate * bandEdge * 2.0));
mLog.debug("Attenuation at 0.25 Channels: " + evaluate(taps, bandEdge * 0.25) + "\tFrequency: " + (sampleRate * bandEdge * 0.25) + " [" + (bandEdge * 0.25) + "]");
mLog.debug("Attenuation at 0.50 Channels: " + evaluate(taps, bandEdge * 0.50) + "\tFrequency: " + (sampleRate * bandEdge * 0.50) + " [" + (bandEdge * 0.50) + "]");
mLog.debug("Attenuation at 0.75 Channels: " + evaluate(taps, bandEdge * 0.75) + "\tFrequency: " + (sampleRate * bandEdge * 0.75) + " [" + (bandEdge * 0.75) + "]");
mLog.debug("Attenuation OBJECTIVE: " + PERFECT_RECONSTRUCTION_GAIN_AT_BAND_EDGE);
mLog.debug("Attenuation at 1.00 Channels: " + evaluate(taps, bandEdge * 1.00) + "\tFrequency: " + (sampleRate * bandEdge * 1.0) + " [" + (bandEdge * 1.0) + "]");
mLog.debug("Attenuation at 1.25 Channels: " + evaluate(taps, bandEdge * 1.25) + "\tFrequency: " + (sampleRate * bandEdge * 1.25) + " [" + (bandEdge * 1.25) + "]");
mLog.debug("Attenuation at 1.50 Channels: " + evaluate(taps, bandEdge * 1.50) + "\tFrequency: " + (sampleRate * bandEdge * 1.5) + " [" + (bandEdge * 1.5) + "]");
mLog.debug("Attenuation at 1.75 Channels: " + evaluate(taps, bandEdge * 1.75) + "\tFrequency: " + (sampleRate * bandEdge * 1.75) + " [" + (bandEdge * 1.75) + "]");
mLog.debug("Attenuation at 2.00 Channels: " + evaluate(taps, bandEdge * 2.00) + "\tFrequency: " + (sampleRate * bandEdge * 2.0) + " [" + (bandEdge * 2.0) + "]");
}
//Adjust the overall gain of the filter to an objective of 1.0 times the number of channels
return normalize(extendedTaps, 1.0f * (float)channels);
return extendedTaps;
}
/**

View File

@@ -1719,7 +1719,32 @@ public enum Filters
0.011873357051047719f, 0.0f, -0.007901319195893647f, 0.0f,
0.005055504379767936f, 0.0f, -0.003054430179754289f, 0.0f,
0.001695637278417295f, 0.0f, -0.000998606272947510f
} );
}),
/**
* FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
* .sampleRate(100)
* .gridDensity(16)
* .oddLength(true)
* .passBandCutoff(21)
* .passBandAmplitude(1.0)
* .passBandRipple(0.01)
* .stopBandStart(29)
* .stopBandAmplitude(0.0)
* .stopBandRipple(0.01) //Approximately -85 dB suppression
* .build();
*/
HALF_BAND_FILTER_27T(new float[]
{
0.007495115f, 0.0f, -0.011045938f, 0.0f,
0.019129561f, 0.0f, -0.031747308f, 0.0f,
0.053220540f, 0.0f, -0.099569574f, 0.0f,
0.316083550f, 0.5f, 0.316083550f, 0.0f,
-0.099569574f, 0.0f, 0.053220540f, 0.0f,
-0.031747308f, 0.0f, 0.019129561f, 0.0f,
-0.011045938f, 0.0f, 0.007495115f
});
float[] mCoefficients;

View File

@@ -136,6 +136,46 @@ public class ContinuousBufferProcessor<E> implements Listener<E>
}
}
/**
* Stops this buffer processor,stops queuing of incoming buffers and flushes the internal buffer queue
* contents to the listener.
*/
public void flushAndStop()
{
if(mRunning.compareAndSet(true, false))
{
if(mScheduledFuture != null)
{
mScheduledFuture.cancel(true);
mScheduledFuture = null;
}
process();
}
}
/**
* Distributes queued buffers to the listener
*/
private void process()
{
List<E> buffers = new ArrayList<>();
try
{
mQueue.drainTo(buffers);
if(mListener != null)
{
mListener.receive(buffers);
}
}
catch(Throwable throwable)
{
mLog.error("Error while dispatching buffers to listener", throwable);
}
}
/**
* Clears any buffers from the queue
*/
@@ -162,21 +202,7 @@ public class ContinuousBufferProcessor<E> implements Listener<E>
@Override
public void run()
{
List<E> buffers = new ArrayList<>();
try
{
mQueue.drainTo(buffers);
if(mListener != null)
{
mListener.receive(buffers);
}
}
catch(Throwable throwable)
{
mLog.error("Error while dispatching buffers to listeners", throwable);
}
process();
}
}
}

View File

@@ -15,10 +15,10 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.buffer.AbstractReusableBuffer;
import io.github.dsheirer.sample.buffer.OverflowableReusableBufferTransferQueue;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
public class ContinuousReusableBufferProcessor<T extends ReusableBuffer> extends ContinuousBufferProcessor<T>
public class ContinuousReusableBufferProcessor<T extends AbstractReusableBuffer> extends ContinuousBufferProcessor<T>
{
/**
* Scheduled Reusable Buffer Processor combines an internal overflowable buffer with a scheduled runnable processing

View File

@@ -34,7 +34,6 @@ import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -172,36 +171,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
return channelSource;
}
public TunerChannelSource getTestChannel(int index)
{
PolyphaseChannelSource channelSource = null;
try
{
List<Integer> polyphaseIndexes = new ArrayList<>();
polyphaseIndexes.add(index);
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
if(outputProcessor != null)
{
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
TunerChannel tunerChannel = new TunerChannel(centerFrequency, 12500);
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency);
}
}
catch(IllegalArgumentException iae)
{
mLog.error("Couldn't get test channel with index:" + index, iae);
}
return channelSource;
}
/**
* Creates a processor to process the channelizer channel indexes into a composite output stream providing
* channelized complex sample buffers to a registered source listener.
@@ -213,12 +182,14 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
switch(indexes.size())
{
case 1:
return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes);
return new OneChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes,
mChannelCalculator.getChannelCount());
case 2:
try
{
float[] filter = getOutputProcessorFilter(2);
return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter);
return new TwoChannelOutputProcessor(mChannelCalculator.getChannelSampleRate(), indexes, filter,
mChannelCalculator.getChannelCount());
}
catch(FilterDesignException fde)
{
@@ -314,7 +285,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
case NOTIFICATION_SAMPLE_RATE_CHANGE:
//Update channel calculator immediately so that channels can be allocated
double sampleRate = sourceEvent.getValue().doubleValue();
int channelCount = (int)Math.ceil(sampleRate / MINIMUM_CHANNEL_BANDWIDTH);
int channelCount = ComplexPolyphaseChannelizerM2.getChannelCount(sampleRate);
mChannelCalculator.setRates(sampleRate, channelCount);
break;
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
@@ -504,8 +475,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(taps == null)
{
taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelBandwidth(), channels,
POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL);
taps = FilterFactory.getSincM2Synthesizer(mChannelCalculator.getChannelSampleRate(),
mChannelCalculator.getChannelBandwidth(), channels, POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL);
mOutputProcessorFilters.put(channels, taps);
}
@@ -556,6 +527,10 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//ignore
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
//Rebroadcast so that the tuner source can process this event
mSourceEventBroadcaster.broadcast(sourceEvent);
break;
default:
mLog.error("Received unrecognized source event from polyphase channel source [" +
sourceEvent.getEvent() + "]");

View File

@@ -1,171 +0,0 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.filter.channelizer;
import java.util.Arrays;
public class PolyphaseChannelResultsBuffer
{
private long mTimestamp;
private int mPointer = 0;
private int mMaxSize;
private float[][] mChannelResults;
/**
* Buffer containing an array of polyphase channelizer complex channel results.
* @param timestamp reference relative to the first channel results.
* @param maxSize of channel results to store in this buffer
*/
public PolyphaseChannelResultsBuffer(long timestamp, int maxSize)
{
mTimestamp = timestamp;
mMaxSize = maxSize;
}
/**
* Reference timestamp relative to the first channel results contained in this buffer.
* @return reference timestamp in milliseconds since epoch
*/
public long getTimestamp()
{
return mTimestamp;
}
/**
* Indicates if this buffer is full
*/
public boolean isFull()
{
return mPointer >= mMaxSize;
}
/**
* Indicates if this buffer is empty
*/
public boolean isEmpty()
{
return mPointer == 0;
}
/**
* Adds the channel results to this buffer
* @param channelResults to add
* @throws IllegalArgumentException if the buffer is full or if the size of the channel results array is a different
* length than any of the preceeding channel results that were added to this buffer
*/
public void add(float[] channelResults) throws IllegalArgumentException
{
if(mPointer >= mMaxSize)
{
throw new IllegalArgumentException("Buffer is full - cannot add new channel results");
}
if(mPointer == 0)
{
mChannelResults = new float[mMaxSize][channelResults.length];
}
else
{
int currentLength = mChannelResults[0].length;
if(channelResults.length != currentLength)
{
throw new IllegalArgumentException("Channel results length [" + channelResults.length + "] cannot be " +
"a different length than the channel results that are already contained in this buffer [" +
currentLength + "]");
}
}
mChannelResults[mPointer++] = channelResults;
}
/**
* Channel results array contained in this buffer
*/
public float[][] getChannelResults()
{
//If this buffer is full, return all of the channel results
if(mPointer == mMaxSize)
{
return mChannelResults;
}
//Otherwise, truncate the results to return
else
{
return Arrays.copyOf(mChannelResults, mPointer);
}
}
/**
* Extracts a single I/Q interleaved sample buffer from the channel results
* @param iChannelIndex to extract for the inphase sample. The q index is assumed to be one greater than i.
* @return float[] containing interleaved I and Q samples
* @throws IllegalArgumentException if this buffer doesn't contain any samples or if the requested channel is
* not within the channel range of the contained channel results.
*/
public float[] getChannel(int iChannelIndex)
{
if(isEmpty())
{
throw new IllegalArgumentException("Buffer is empty - cannot extract channel [" + iChannelIndex + "]");
}
if(!isValidChannelIndex(iChannelIndex))
{
throw new IllegalArgumentException("Channel [" + iChannelIndex + "] is not valid for the contained channel " +
"results -- max channel is " + getMaxChannelIndex());
}
float[] samples = new float[mChannelResults.length * 2];
int pointer = 0;
int qChannelIndex = iChannelIndex + 1;
for(int x = 0; x < mPointer; x++)
{
float[] channelResults = mChannelResults[x];
samples[pointer++] = channelResults[iChannelIndex];
samples[pointer++] = channelResults[qChannelIndex];
}
return samples;
}
/**
* Indicates if the channel argument is valid for the contained channel results.
* @param channelIndex number (0 to N) to check
* @return false if the buffer is empty or if the channel is greater than the contained channel results
*/
private boolean isValidChannelIndex(int channelIndex)
{
return channelIndex <= getMaxChannelIndex();
}
/**
* Maximum channel index represented in the channel results contained in this buffer.
* @return max channel number, or 0 if the buffer is empty
*/
private int getMaxChannelIndex()
{
if(isEmpty())
{
return 0;
}
return mChannelResults[0].length;
}
}

View File

@@ -134,7 +134,11 @@ public class PolyphaseChannelSource extends TunerChannelSource
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
{
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
}
}
/**
@@ -197,7 +201,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
* @param frequency in hertz
*/
@Override
protected void setFrequency(long frequency)
public void setFrequency(long frequency)
{
mIndexCenterFrequency = frequency;
@@ -237,4 +241,11 @@ public class PolyphaseChannelSource extends TunerChannelSource
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
}
@Override
public String toString()
{
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
getTunerChannel().getFrequency();
}
}

View File

@@ -42,6 +42,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
private int mInputChannelCount;
private IOscillator mFrequencyCorrectionMixer;
private boolean mFrequencyCorrectionEnabled;
private double mGain = 1.0;
/**
* Base class for polyphase channelizer output channel processing. Provides built-in frequency translation
@@ -51,9 +52,10 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
* channel sample rate for frequency translation/correction.
*/
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, double gain)
{
mInputChannelCount = inputChannelCount;
mGain = gain;
//TODO: swap this out and use the LowPhaseNoiseOscillator
mFrequencyCorrectionMixer = new Oscillator(0, sampleRate);
@@ -62,6 +64,17 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
mChannelResultsQueue = new OverflowableReusableBufferTransferQueue<>((int)(sampleRate * 3), (int)(sampleRate * 0.5));
}
protected double getGain()
{
return mGain;
}
@Override
public int getPolyphaseChannelIndexCount()
{
return mInputChannelCount;
}
public void dispose()
{
if(mChannelResultsQueue != null)

View File

@@ -59,6 +59,11 @@ public interface IPolyphaseChannelOutputProcessor
*/
void setPolyphaseChannelIndices(List<Integer> indexes);
/**
* List of current polyphase channel indices for the output processor
*/
int getPolyphaseChannelIndexCount();
/**
* Updates the synthesis filter taps for this output processor
* @param filter

View File

@@ -38,10 +38,11 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
*
* @param sampleRate of the output sample stream.
* @param channelIndexes containing a single channel index.
* @param gain value to apply. Typically this is the same as the channelizer's channel count.
*/
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes)
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, double gain)
{
super(1, sampleRate);
super(1, sampleRate, gain);
setPolyphaseChannelIndices(channelIndexes);
}
@@ -91,6 +92,8 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
getFrequencyCorrectionMixer().mixComplex(channelBuffer.getSamples());
}
channelBuffer.applyGain(getGain());
reusableComplexBufferAssembler.receive(channelBuffer);
}
catch(IllegalArgumentException iae)

View File

@@ -49,12 +49,13 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
*
* @param sampleRate of the output sample stream.
* @param channelIndexes containing two channel indices.
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
*/
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter)
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, double gain)
{
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
//after synthesizing both input channels
super(2, sampleRate);
super(2, sampleRate, gain);
setPolyphaseChannelIndices(channelIndexes);
setSynthesisFilter(filter);
@@ -141,6 +142,8 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
lowPassFiltered.applyGain(getGain());
reusableComplexBufferAssembler.receive(lowPassFiltered);
buffer.decrementUserCount();

View File

@@ -15,11 +15,10 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.cic;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.Window;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.filter.halfband.complex.ComplexHalfBandFilter;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
@@ -28,52 +27,93 @@ import io.github.dsheirer.sample.decimator.ComplexDecimator;
import org.apache.commons.lang3.Validate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Non-Recursive Prime-Factor CIC Filter.
*
* 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 currently supports a maximum decimation rate of 2801. Higher decimation rates can be added by
* adding additional prime factors to the PRIMES array.
*/
public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
{
// private final static Logger mLog = LoggerFactory.getLogger(ComplexPrimeCICDecimate.class);
/**
* PRIME numbers to use for decimation stage sizing. This list contains values that should
* support input sample rates up to 70 MHz with an output channel rate of at least 25 kHz.
*/
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,
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,
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,
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, 701,
709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827,
829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953,
967, 971, 977, 983, 991, 997, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051,
1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163,
1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279,
1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399,
1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489,
1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601,
1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709,
1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831,
1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951,
1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069,
2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179,
2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297,
2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399,
2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543,
2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671,
2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753,
2767, 2777, 2789, 2791, 2797, 2801
};
private ArrayList<DecimatingStage> mDecimatingStages =
new ArrayList<DecimatingStage>();
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
private static Map<Integer,List<Integer>> sPrimeFactors = new HashMap();
private List<DecimatingStage> mDecimatingStages = new ArrayList<DecimatingStage>();
private DecimatingStage mFirstDecimatingStage;
private Output mOutput;
/**
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
* decimated, single paired i/q float sample output.
* Constructs a new decimation filter.
*
* 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.
* An example setup for a 2.4 MHz input sample rate and 25 kHz output channel rate with a desired 12.5 kHz channel
* pass band might be:
* sampleRate = 2,400,000.0
* decimation = 96
* passFrequency = 6250 (1/4 of the channel rate)
* stopFrequency = 7000
*
* 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.
* Note: the transition bandwidth (stop - start) directly impacts the size of the low-pass cleanup filter,
* where smaller transition bandwidth creates a longer filter that requires more processing effort.
*
* @param sampleRate of the input sample stream
* @param decimation - overall decimation rate
* @param order - filter order
* @param passFrequency for the final cleanup filter
* @param stopFrequency for the final cleanup filter
* @throws FilterDesignException if a final low-pass cleanup filter cannot be created for the output channel rate
* and specified pass/stop frequencies.
*/
public ComplexPrimeCICDecimate(int decimation, int order,
int passFrequency, int attenuation, Window.WindowType windowType)
public ComplexPrimeCICDecimate(double sampleRate, int decimation, double passFrequency, double stopFrequency)
throws FilterDesignException
{
Validate.isTrue(decimation <= 700);
Validate.isTrue(decimation <= PRIMES[PRIMES.length - 1]);
List<Integer> stageSizes = getPrimeFactors(decimation);
for(int x = 0; x < stageSizes.size(); x++)
{
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), 1);
mDecimatingStages.add(stage);
@@ -89,10 +129,11 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
}
mOutput = new Output(48000, passFrequency, attenuation, windowType);
double channelRate = sampleRate / (double)decimation;
mDecimatingStages.get(mDecimatingStages.size() - 1)
.setListener(mOutput);
mOutput = new Output(channelRate, passFrequency, stopFrequency);
mDecimatingStages.get(mDecimatingStages.size() - 1).setListener(mOutput);
}
public void dispose()
@@ -127,16 +168,26 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
/**
* 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.
* Calculates the prime factors of the decimation rate. If you wish to have decimation rates higher than the
* highest rate listed in the PRIMES array, 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>();
if(decimation > PRIMES[PRIMES.length - 1])
{
throw new IllegalArgumentException("Decimation rates higher than [" + PRIMES[PRIMES.length - 1] +
"] are not supported. Update the PRIMES array to add support for higher decimation rates");
}
if(sPrimeFactors.containsKey(decimation))
{
return sPrimeFactors.get(decimation);
}
List<Integer> primeFactors = new ArrayList<Integer>();
int pointer = 0;
@@ -146,8 +197,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
if(decimation % prime == 0)
{
stages.add(prime);
primeFactors.add(prime);
decimation /= prime;
}
else
@@ -156,7 +206,9 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
}
return stages;
sPrimeFactors.put(decimation, primeFactors);
return primeFactors;
}
/**
@@ -185,7 +237,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
*/
public class DecimatingStage implements ComplexSampleListener
{
private ArrayList<Stage> mStages = new ArrayList<Stage>();
private List<Stage> mStages = new ArrayList<Stage>();
private Stage mFirstStage;
private ComplexDecimator mDecimator;
@@ -217,7 +269,6 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
mDecimator = new ComplexDecimator(size);
mStages.get(mStages.size() - 1).setListener(mDecimator);
}
@@ -248,9 +299,8 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
/**
* 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
* 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
{
@@ -267,7 +317,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
protected float mGain;
public Stage()
protected Stage()
{
}
@@ -278,7 +328,8 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
mISamples = new float[mSize];
mQSamples = new float[mSize];
mGain = 1.0f / (float)size;
float baseGain = 1.0f / (float)size;
mGain = baseGain * (1.0f / (1.0f - baseGain));
}
public void dispose()
@@ -288,7 +339,7 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
public void receive(float i, float q)
{
/* Subtract the oldest sample and add back in the newest */
/* Subtract the oldest sample and add in the newest sample */
mISum = mISum - mISamples[mSamplePointer] + i;
mQSum = mQSum - mQSamples[mSamplePointer] + q;
@@ -316,30 +367,24 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
}
/**
* Size 2 stage that removes the unnecessary circular buffer management for
* a two-stage.
* Size 2 stage that eliminates 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;
if(mListener != null)
{
mListener.receive(((mISum + i) * mGain), ((mQSum + q) * mGain));
}
mISum = i;
mQSum = q;
if(mListener != null)
{
mListener.receive((iSum * mGain), (qSum * mGain));
}
}
}
@@ -350,31 +395,29 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
*/
public class Output implements ComplexSampleListener
{
/* Decimated output buffers will contain 1024 samples */
private ReusableComplexBufferAssembler mAssembler = new ReusableComplexBufferAssembler(2048, 48000.0);
private ComplexFIRFilter2 mCleanupFilter;
private ComplexHalfBandFilter mHalfBandFilter = new ComplexHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
/* Decimated output buffers will contain 1024 complex samples */
private ReusableComplexBufferAssembler mBufferAssembler;
private ComplexFIRFilter2 mLowPassFilter;
private Listener<ReusableComplexBuffer> mReusableComplexBufferListener;
public Output(int outputSampleRate, int passFrequency, int attenuation,
Window.WindowType windowType)
public Output(double outputSampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
mCleanupFilter = new ComplexFIRFilter2(FilterFactory
.getCICCleanupFilter(outputSampleRate, passFrequency, attenuation, windowType), 0.4f);
mBufferAssembler = new ReusableComplexBufferAssembler(2400, outputSampleRate);
//Bypassing the CIC cleanup filter for now
mAssembler.setListener(new Listener<ReusableComplexBuffer>()
//This may throw an exception if we can't design a filter for the sample rate and pass/stop frequencies
float[] filterCoefficients = getLowPassFilter(outputSampleRate, passFrequency, stopFrequency);
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
mBufferAssembler.setListener(new Listener<ReusableComplexBuffer>()
{
@Override
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
if(mReusableComplexBufferListener != null)
{
ReusableComplexBuffer cleanupFiltered = mCleanupFilter.filter(reusableComplexBuffer);
ReusableComplexBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
mReusableComplexBufferListener.receive(halfBandFiltered);
ReusableComplexBuffer filteredBuffer = mLowPassFilter.filter(reusableComplexBuffer);
mReusableComplexBufferListener.receive(filteredBuffer);
// mReusableComplexBufferListener.receive(reusableComplexBuffer);
}
else
{
@@ -386,20 +429,17 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
public void dispose()
{
mAssembler.dispose();
mCleanupFilter.dispose();
mHalfBandFilter.dispose();
mBufferAssembler.dispose();
mLowPassFilter.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.
* Interface for receiving CIC decimated output samples
*/
@Override
public void receive(float inphase, float quadrature)
{
mAssembler.receive(new float[]{inphase, quadrature});
mBufferAssembler.receive(inphase, quadrature);
}
public void setListener(Listener<ReusableComplexBuffer> listener)
@@ -411,5 +451,43 @@ public class ComplexPrimeCICDecimate implements Listener<ReusableComplexBuffer>
{
mReusableComplexBufferListener = null;
}
/**
* Creates a low-pass filter to use as the final cleanup filter for the decimated output stream
*
* @param sampleRate for the final output channel rate
* @param passFrequency for half of the desired channel rate
* @param stopFrequency for the attenuated band
* @return a newly designed filter or a previously designed (cached) filter
* @throws FilterDesignException
*/
private float[] getLowPassFilter(double sampleRate, double passFrequency, double stopFrequency) throws FilterDesignException
{
//Use existing filter if we've already designed one
if(sLowPassFilters.containsKey((int)sampleRate))
{
return sLowPassFilters.get((int)sampleRate);
}
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.passBandCutoff(passFrequency)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(stopFrequency)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
//This will throw an exception if the filter cannot be designed
float[] taps = designer.getImpulseResponse();
sLowPassFilters.put((int)sampleRate, taps);
return taps;
}
}
}

View File

@@ -23,8 +23,8 @@ import io.github.dsheirer.dsp.filter.Window.WindowType;
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferAssembler;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.decimator.RealDecimator;
import io.github.dsheirer.sample.real.RealSampleListener;
import org.apache.commons.lang3.Validate;
@@ -124,7 +124,7 @@ public class RealPrimeCICDecimate
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener(Listener<ReusableBuffer> listener)
public void setListener(Listener<ReusableFloatBuffer> listener)
{
mOutput.setListener(listener);
}
@@ -173,7 +173,7 @@ public class RealPrimeCICDecimate
/**
* Primary input method for receiving real buffers.
*/
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
if(mFirstDecimatingStage != null)
{
@@ -353,7 +353,7 @@ public class RealPrimeCICDecimate
{
/* Decimated output buffers will contain 1024 samples */
private ReusableBufferAssembler mAssembler;
private Listener<ReusableBuffer> mReusableBufferListener;
private Listener<ReusableFloatBuffer> mReusableBufferListener;
private RealFIRFilter2 mCleanupFilter;
private HalfBandFilter mHalfBandFilter = new HalfBandFilter(
@@ -365,21 +365,21 @@ public class RealPrimeCICDecimate
attenuation, windowType), 0.4f);
mAssembler = new ReusableBufferAssembler(outputBufferSize);
mAssembler.setListener(new Listener<ReusableBuffer>()
mAssembler.setListener(new Listener<ReusableFloatBuffer>()
{
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
if(mReusableBufferListener != null)
{
ReusableBuffer cleanupFiltered = mCleanupFilter.filter(reusableBuffer);
ReusableBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
ReusableFloatBuffer cleanupFiltered = mCleanupFilter.filter(reusableFloatBuffer);
ReusableFloatBuffer halfBandFiltered = mHalfBandFilter.filter(cleanupFiltered);
mReusableBufferListener.receive(halfBandFiltered);
}
else
{
reusableBuffer.decrementUserCount();
reusableFloatBuffer.decrementUserCount();
}
}
});
@@ -406,7 +406,7 @@ public class RealPrimeCICDecimate
/**
* Adds a listener to receive output samples
*/
public void setListener(Listener<ReusableBuffer> listener)
public void setListener(Listener<ReusableFloatBuffer> listener)
{
mReusableBufferListener = listener;
}

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.dc;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -77,11 +77,11 @@ public class BufferDCFilter
}
}
public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
{
float[] unfilteredSamples = unfilteredBuffer.getSamples();
ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
float[] filteredSamples = filteredBuffer.getSamples();
float offset = getOffset(unfilteredSamples);

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.dc;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* DC removal filter for removing any DC offset that may be present when a signal is not aligned within the
@@ -77,9 +77,9 @@ public class DCRemovalFilter
* @param unfilteredBuffer containing samples to filter
* @return a new reusable buffer containing the filtered samples with the user count set to 1.
*/
public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
{
ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredBuffer.getSampleCount());
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredBuffer.getSampleCount());
float[] unfilteredSamples = unfilteredBuffer.getSamples();
float[] filteredSamples = filteredBuffer.getSamples();

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.dc;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.real.RealSampleListener;
public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
@@ -67,9 +67,9 @@ public class IIRSinglePoleDCRemovalFilter implements RealSampleListener
* @param inputBuffer to filter
* @return filtered samples in a new (ie reused) output buffer
*/
public ReusableBuffer filter(ReusableBuffer inputBuffer)
public ReusableFloatBuffer filter(ReusableFloatBuffer inputBuffer)
{
ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount());
ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(inputBuffer.getSampleCount());
float[] inputSamples = inputBuffer.getSamples();
float[] outputSamples = outputBuffer.getSamples();

View File

@@ -61,14 +61,15 @@ public class FilterViewer extends Application
// .passBandRipple(0.01)
// .build();
FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
.sampleRate(8000)
.stopFrequency1(1000)
.passFrequencyBegin(1100)
.passFrequencyEnd(1900)
.stopFrequency2(2000)
.stopRipple(0.000001)
.passRipple(0.00001)
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(50000.0)
.gridDensity(16)
.passBandCutoff(6000)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(6700)
.stopBandAmplitude(0.0)
.stopBandRipple(0.01)
.build();
float[] taps = null;
@@ -102,7 +103,6 @@ public class FilterViewer extends Application
throw new IllegalStateException("Couldn't design filter");
}
return taps;
}
}

View File

@@ -299,8 +299,8 @@ public class FIRFilterSpecification
private int mOrder;
private Boolean mOddLength;
private int mGridDensity = 16;
private int mPassBandEndFrequency;
private int mStopBandStartFrequency;
private double mPassBandEndFrequency;
private double mStopBandStartFrequency;
private double mPassBandRipple;
private double mStopBandRipple;
private double mPassBandAmplitude = 1.0;
@@ -342,13 +342,13 @@ public class FIRFilterSpecification
return this;
}
public LowPassBuilder passBandCutoff(int frequencyHz)
public LowPassBuilder passBandCutoff(double frequencyHz)
{
mPassBandEndFrequency = frequencyHz;
return this;
}
public LowPassBuilder stopBandStart(int frequencyHz)
public LowPassBuilder stopBandStart(double frequencyHz)
{
mStopBandStartFrequency = frequencyHz;
return this;
@@ -905,7 +905,7 @@ public class FIRFilterSpecification
* @param stopBandRipple stop band ripple in dB
* @return estimated filter length
*/
public static int estimateFilterOrder(double sampleRate, int frequency1, int frequency2,
public static int estimateFilterOrder(double sampleRate, double frequency1, double frequency2,
double passBandRipple, double stopBandRipple)
{
double df = Math.abs(frequency2 - frequency1) / sampleRate;
@@ -1013,13 +1013,13 @@ public class FIRFilterSpecification
* @param amplitude of the frequency band relative to unity (0.0 - 1.0)
* @param ripple of the frequency band specified as db ripple
*/
public FrequencyBand(double sampleRate, int start, int end, double amplitude, double ripple)
public FrequencyBand(double sampleRate, double start, double end, double amplitude, double ripple)
{
this((double)start / sampleRate, (double)end / sampleRate,
this(start / sampleRate, end / sampleRate,
amplitude, ripple);
}
public FrequencyBand(double sampleRate, int start, int end, double amplitude, double ripple, double weight)
public FrequencyBand(double sampleRate, double start, double end, double amplitude, double ripple, double weight)
{
this(sampleRate, start, end, amplitude, ripple);
mWeight = weight;

View File

@@ -16,8 +16,8 @@
package io.github.dsheirer.dsp.filter.fir.real;
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* Finite Impulse Response (FIR) filter for filtering individual float samples or float sample arrays.
@@ -113,11 +113,11 @@ public class RealFIRFilter2 extends FIRFilter
* @param unfilteredBuffer containing a sample array to be filtered
* @return a new reusable buffer with the filtered samples.
*/
public ReusableBuffer filter(ReusableBuffer unfilteredBuffer)
public ReusableFloatBuffer filter(ReusableFloatBuffer unfilteredBuffer)
{
float[] unfilteredSamples = unfilteredBuffer.getSamples();
ReusableBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
ReusableFloatBuffer filteredBuffer = mReusableBufferQueue.getBuffer(unfilteredSamples.length);
float[] filteredSamples = filteredBuffer.getSamples();
for(int x = 0; x < unfilteredSamples.length; x++)

View File

@@ -15,92 +15,94 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.halfband.complex;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter;
import io.github.dsheirer.dsp.filter.Filters;
import io.github.dsheirer.dsp.filter.halfband.real.HalfBandFilter2;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferQueue;
public class ComplexHalfBandFilter
{
private HalfBandFilter mIFilter;
private HalfBandFilter mQFilter;
private boolean mDecimate;
private boolean mDecimateFlag;
private ReusableComplexBufferQueue mBufferQueue = new ReusableComplexBufferQueue("ComplexHalfBandFilter");
/**
* @param coefficients
* @param gain
* @param decimate
*/
public ComplexHalfBandFilter(float[] coefficients, float gain, boolean decimate)
private HalfBandFilter2 mIFilter;
private HalfBandFilter2 mQFilter;
private float mResidualISample;
private float mResidualQSample;
private boolean mHasResidual;
private int mOutputBufferLength;
private float[] mSamples;
private int mSamplesPointer;
private float[] mFilteredSamples;
private int mFilteredSamplesPointer;
public ComplexHalfBandFilter(float[] coefficients, float gain)
{
mIFilter = new HalfBandFilter(coefficients, gain);
mQFilter = new HalfBandFilter(coefficients, gain);
mDecimate = decimate;
mIFilter = new HalfBandFilter2(coefficients, gain);
mQFilter = new HalfBandFilter2(coefficients, gain);
}
public void dispose()
public ReusableComplexBuffer filter(ReusableComplexBuffer originalBuffer)
{
mIFilter.dispose();
mQFilter.dispose();
mSamples = originalBuffer.getSamples();
mSamplesPointer = 0;
mOutputBufferLength = (mHasResidual ? (mSamples.length + 2) : mSamples.length) / 4 * 2;
ReusableComplexBuffer filteredBuffer = mBufferQueue.getBuffer(mOutputBufferLength);
mFilteredSamples = filteredBuffer.getSamples();
mFilteredSamplesPointer = 0;
if(mHasResidual)
{
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mResidualISample, mSamples[mSamplesPointer++]);
mFilteredSamples[mFilteredSamplesPointer++] = mQFilter.filter(mResidualQSample, mSamples[mSamplesPointer++]);
mHasResidual = false;
}
while(mSamplesPointer + 3 < mSamples.length)
{
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer], mSamples[mSamplesPointer + 2]);
mFilteredSamples[mFilteredSamplesPointer++] = mIFilter.filter(mSamples[mSamplesPointer + 1], mSamples[mSamplesPointer + 3]);
mSamplesPointer += 4;
}
if(mSamplesPointer < mSamples.length)
{
mHasResidual = true;
mResidualISample = mSamples[mSamplesPointer++];
mResidualQSample = mSamples[mSamplesPointer];
}
originalBuffer.decrementUserCount();
return filteredBuffer;
}
public ReusableComplexBuffer filter(ReusableComplexBuffer buffer)
public static void main(String[] args)
{
throw new IllegalStateException("This filter has to be updated to work with reusable buffers");
Oscillator oscillator = new Oscillator(1, 16);
ComplexHalfBandFilter filter = new ComplexHalfBandFilter(Filters.HALF_BAND_FILTER_27T.getCoefficients(), 1.0f);
ReusableComplexBufferQueue bufferQueue = new ReusableComplexBufferQueue("Test");
ReusableComplexBuffer complexBuffer = bufferQueue.getBuffer(8);
oscillator.generateComplex(complexBuffer);
ReusableComplexBuffer filteredBuffer = filter.filter(complexBuffer);
filteredBuffer.decrementUserCount();
ReusableComplexBuffer complexBuffer2 = bufferQueue.getBuffer(10);
oscillator.generateComplex(complexBuffer2);
ReusableComplexBuffer filteredBuffer2 = filter.filter(complexBuffer2);
filteredBuffer2.decrementUserCount();
ReusableComplexBuffer complexBuffer3 = bufferQueue.getBuffer(8);
oscillator.generateComplex(complexBuffer3);
ReusableComplexBuffer filteredBuffer3 = filter.filter(complexBuffer3);
filteredBuffer3.decrementUserCount();
// if(mDecimate)
// {
// float[] samples = buffer.getSamples();
//
// int half = samples.length / 2;
//
// float[] decimated;
//
// if(half % 2 == 0 || mDecimateFlag)
// {
// decimated = new float[half];
// }
// else
// {
// /* If inbound buffer size is odd-length, then we have to
// * adjust when the first operation is non-decimation, since
// * that will produce an outbound buffer 1 sample larger */
// decimated = new float[half + 1];
// }
//
// int decimatedPointer = 0;
//
// for(int x = 0; x < samples.length; x += 2)
// {
// /* Insert the sample but don't filter */
// if(mDecimateFlag)
// {
// mIFilter.insert(samples[x]);
// mQFilter.insert(samples[x + 1]);
// }
// else
// {
// decimated[decimatedPointer++] = mIFilter.filter(samples[x]);
// decimated[decimatedPointer++] = mQFilter.filter(samples[x + 1]);
// }
//
// /* Toggle the decimation flag for every sample */
// mDecimateFlag = !mDecimateFlag;
// }
//
// //return buffer
// }
// else
// {
// /* Non Decimate - filter each of the values and return the buffer */
// float[] samples = buffer.getSamples();
//
// for(int x = 0; x < samples.length; x += 2)
// {
// samples[x] = mIFilter.filter(samples[x]);
// samples[x + 1] = mQFilter.filter(samples[x + 1]);
// }
//
// //return buffer
// }
}
}

View File

@@ -17,10 +17,11 @@
******************************************************************************/
package io.github.dsheirer.dsp.filter.halfband.real;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.apache.commons.lang3.Validate;
@Deprecated
public class HalfBandFilter
{
private float[] mBuffer;
@@ -84,10 +85,10 @@ public class HalfBandFilter
mBufferPointer = mBufferPointer % mBufferSize;
}
public ReusableBuffer filter(ReusableBuffer input)
public ReusableFloatBuffer filter(ReusableFloatBuffer input)
{
float[] inputSamples = input.getSamples();
ReusableBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length);
ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(inputSamples.length);
float[] outputSamples = output.getSamples();
for(int x = 0; x < inputSamples.length; x++)

View File

@@ -0,0 +1,89 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.filter.halfband.real;
/**
* Implements a half-band filter that produces one filtered output for every two input samples.
*
* This filter is structured for Java 8+ compiler JIT optimization for SIMD instructions when
* supported by the host CPU.
*/
public class HalfBandFilter2
{
private static final float CENTER_COEFFICIENT = 0.5f;
private float[] mCoefficients;
private float[] mEvenSamples;
private float[] mOddSamples;
private int mCenterSampleIndex;
private float mAccumulator;
private int mPointer;
private float mGain;
/**
* Creates a half band filter with inherent decimation by two.
*
* @param coefficients of the half-band filter that is odd-length where all odd index coefficients are
* zero valued except for the middle odd index coefficient which should be valued 0.5
* @param gain value to apply to the output
*/
public HalfBandFilter2(float[] coefficients, float gain)
{
if(coefficients.length % 2 == 0)
{
throw new IllegalArgumentException("Half-band filter coefficients must be odd-length");
}
mGain = gain;
int half = coefficients.length / 2 + 1;
mCoefficients = new float[half];
mEvenSamples = new float[half];
//Use only the even coefficients since the odd coefficients are all zero-valued
for(int x = 0; x < coefficients.length; x += 2)
{
mCoefficients[x / 2] = coefficients[x];
}
//Setup the odd samples array as a simple delay line, half the size of the even samples array
mOddSamples = new float[half / 2];
mCenterSampleIndex = mOddSamples.length - 1;
}
public float filter(float sample1, float sample2)
{
//Structured for SIMD array copy optimization when supported by host CPU
System.arraycopy(mEvenSamples, 0, mEvenSamples, 1, mEvenSamples.length - 1);
System.arraycopy(mOddSamples, 0, mOddSamples, 1, mOddSamples.length - 1);
mEvenSamples[0] = sample1;
mOddSamples[0] = sample2;
mAccumulator = 0.0f;
//Structured for SIMD dot.product optimization when supported by host CPU
for(mPointer = 0; mPointer < mEvenSamples.length; mPointer++)
{
mAccumulator += (mEvenSamples[mPointer] * mCoefficients[mPointer]);
}
mAccumulator += (mOddSamples[mCenterSampleIndex] * CENTER_COEFFICIENT);
mAccumulator *= mGain;
return mAccumulator;
}
}

View File

@@ -18,8 +18,8 @@ package io.github.dsheirer.dsp.filter.resample;
import com.laszlosystems.libresample4j.Resampler;
import com.laszlosystems.libresample4j.SampleBuffers;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,7 +32,7 @@ public class RealResampler
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("RealResampler");
private Resampler mResampler;
private Listener<ReusableBuffer> mResampledListener;
private Listener<ReusableFloatBuffer> mResampledListener;
private BufferManager mBufferManager;
private double mResampleFactor;
@@ -50,11 +50,11 @@ public class RealResampler
/**
* Primary input method to the resampler
* @param reusableBuffer to resample
* @param reusableFloatBuffer to resample
*/
public void resample(ReusableBuffer reusableBuffer)
public void resample(ReusableFloatBuffer reusableFloatBuffer)
{
mBufferManager.load(reusableBuffer);
mBufferManager.load(reusableFloatBuffer);
mResampler.process(mResampleFactor, mBufferManager, false);
}
@@ -62,7 +62,7 @@ public class RealResampler
* Registers the listener to receive the resampled buffer output
* @param resampledBufferListener to receive buffers
*/
public void setListener(Listener<ReusableBuffer> resampledBufferListener)
public void setListener(Listener<ReusableFloatBuffer> resampledBufferListener)
{
mResampledListener = resampledBufferListener;
}
@@ -89,10 +89,10 @@ public class RealResampler
/**
* Queues the buffer sample for resampling
*/
public void load(ReusableBuffer reusableBuffer)
public void load(ReusableFloatBuffer reusableFloatBuffer)
{
mInputBuffer.put(reusableBuffer.getSamples());
reusableBuffer.decrementUserCount();
mInputBuffer.put(reusableFloatBuffer.getSamples());
reusableFloatBuffer.decrementUserCount();
}
@Override
@@ -128,7 +128,7 @@ public class RealResampler
while(mOutputBuffer.position() > mOutputBufferSize)
{
ReusableBuffer outputBuffer = mReusableBufferQueue.getBuffer(mOutputBufferSize);
ReusableFloatBuffer outputBuffer = mReusableBufferQueue.getBuffer(mOutputBufferSize);
mOutputBuffer.flip();
mOutputBuffer.get(outputBuffer.getSamples());

View File

@@ -17,9 +17,9 @@
******************************************************************************/
package io.github.dsheirer.dsp.fm;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.complex.Complex;
/**
@@ -129,9 +129,9 @@ public class FMDemodulator
* @param basebandSampleBuffer containing samples to demodulate
* @return demodulated sample buffer.
*/
public ReusableBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer)
public ReusableFloatBuffer demodulate(ReusableComplexBuffer basebandSampleBuffer)
{
ReusableBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount());
ReusableFloatBuffer demodulatedBuffer = mReusableBufferQueue.getBuffer(basebandSampleBuffer.getSampleCount());
float[] basebandSamples = basebandSampleBuffer.getSamples();
float[] demodulatedSamples = demodulatedBuffer.getSamples();

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter2;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory;
* adjustment may not fully compensate for mis-tuned signals and may result in missed decodes of idle bursts. However,
* the DC filter will track a mis-tuned signal to baseband quickly upon a continuous transmission.
*/
public class LTRDecoder implements Listener<ReusableBuffer>, ISyncDetectListener, ISyncStateListener
public class LTRDecoder implements Listener<ReusableFloatBuffer>, ISyncDetectListener, ISyncStateListener
{
private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
@@ -134,6 +134,12 @@ public class LTRDecoder implements Listener<ReusableBuffer>, ISyncDetectListener
mSynchronizationMonitor.syncDetected();
}
@Override
public void syncLost()
{
//no-op
}
/**
* Processes the buffer samples by converting all samples to boolean values reflecting if the sample value is
* greater than zero (or not). Average symbol timing offset is calculated for the full buffer and the offset is
@@ -142,10 +148,10 @@ public class LTRDecoder implements Listener<ReusableBuffer>, ISyncDetectListener
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
*/
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
ReusableBuffer dcFiltered = mDCFilter.filter(buffer);
ReusableBuffer lowPassFiltered = mLowPassFilter.filter(dcFiltered);
ReusableFloatBuffer dcFiltered = mDCFilter.filter(buffer);
ReusableFloatBuffer lowPassFiltered = mLowPassFilter.filter(dcFiltered);
for(float sample : lowPassFiltered.getSamples())
{

View File

@@ -30,13 +30,13 @@ import org.slf4j.LoggerFactory;
*
* Synchronization States:
*
* -COARSE - default state. demoted from MEDIUM after one message period with no sync detection.
* -COARSE - default state. demoted from MEDIUM after one message period with no sync detection.
*
* -MEDIUM - promoted from COARSE when one sync detection occurs after two message periods, or demoted from FINE
* after two message periods with no sync detections.
* -MEDIUM - promoted from COARSE when one sync detection occurs after two message periods, or demoted from FINE
* after two message periods with no sync detections.
*
* -FINE - promoted from MEDIUM after two or more consecutive sync detections within the preceding two or more
* message periods.
* -FINE - promoted from MEDIUM after two or more consecutive sync detections within the preceding two or more
* message periods.
*/
public class SynchronizationMonitor implements ISyncDetectListener
{
@@ -50,6 +50,7 @@ public class SynchronizationMonitor implements ISyncDetectListener
/**
* Constructs the synchronization monitor for the specified message symbol length.
*
* @param messageLength in symbols.
*/
public SynchronizationMonitor(int messageLength)
@@ -127,6 +128,12 @@ public class SynchronizationMonitor implements ISyncDetectListener
updateSyncState();
}
@Override
public void syncLost()
{
//no-op
}
/**
* Increments the symbol count for each decoded symbol.
*/

View File

@@ -2,8 +2,8 @@ package io.github.dsheirer.dsp.gain;
import io.github.dsheirer.buffer.DoubleCircularBuffer;
import io.github.dsheirer.buffer.RealCircularBuffer;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/*******************************************************************************
* SDR Trunk
@@ -147,9 +147,9 @@ public class AutomaticGainControl
* @param input samples to process
* @return output samples with gain applied
*/
public ReusableBuffer process(ReusableBuffer input)
public ReusableFloatBuffer process(ReusableFloatBuffer input)
{
ReusableBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount());
ReusableFloatBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount());
float[] inputSamples = input.getSamples();
float[] outputSamples = output.getSamples();

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.mixer;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.complex.Complex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -127,8 +127,8 @@ public abstract class AbstractOscillator implements IOscillator
for(int x = 0; x < sampleCount; x++)
{
samples[x] = inphase();
rotate();
samples[x] = quadrature();
}
return samples;
@@ -182,11 +182,11 @@ public abstract class AbstractOscillator implements IOscillator
/**
* Generates enough real samples to fill the reusable buffer
* @param reusableBuffer to fill with real samples
* @param reusableFloatBuffer to fill with real samples
*/
@Override
public void generateReal(ReusableBuffer reusableBuffer)
public void generateReal(ReusableFloatBuffer reusableFloatBuffer)
{
reusableBuffer.reloadFrom(generateReal(reusableBuffer.getSampleCount()), System.currentTimeMillis());
reusableFloatBuffer.reloadFrom(generateReal(reusableFloatBuffer.getSampleCount()), System.currentTimeMillis());
}
}

View File

@@ -15,8 +15,8 @@
******************************************************************************/
package io.github.dsheirer.dsp.mixer;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* Oscillator interface.
@@ -102,9 +102,9 @@ public interface IOscillator
* Generates real samples and fills the reusable buffer using the current system time in millis for
* the update timestamp.
*
* @param reusableBuffer to fill with complex samples
* @param reusableFloatBuffer to fill with complex samples
*/
void generateReal(ReusableBuffer reusableBuffer);
void generateReal(ReusableFloatBuffer reusableFloatBuffer);
/**
* Mixes (heterodynes) the complex sample array using the current settings of this oscillator.

View File

@@ -18,6 +18,10 @@ package io.github.dsheirer.dsp.mixer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class LowPhaseNoiseOscillator extends AbstractOscillator
{
private final static Logger mLog = LoggerFactory.getLogger(LowPhaseNoiseOscillator.class);
@@ -82,4 +86,54 @@ public class LowPhaseNoiseOscillator extends AbstractOscillator
//Update the gain value for the next rotation
mGain = THREE_HALVES - ((mPreviousInphase * mPreviousInphase) + (mPreviousQuadrature * mPreviousQuadrature));
}
public static long process(AbstractOscillator oscillator, int iterations, int sampleCount)
{
long start = System.nanoTime();
for(int x = 0; x < iterations; x++)
{
float[] samples = oscillator.generateComplex(sampleCount);
}
long duration = System.nanoTime() - start;
return duration;
}
public static void main(String[] args)
{
mLog.debug("Starting ...");
SimpleDateFormat sdf = new SimpleDateFormat("mm:ss.SSS");
AbstractOscillator lpno = new LowPhaseNoiseOscillator(100, 800);
AbstractOscillator regular = new Oscillator(100, 800);
int iterations = 10000;
int sampleCount = 1000000;
mLog.debug("Warm up - lpno");
process(lpno, iterations, sampleCount);
process(lpno, iterations, sampleCount);
process(lpno, iterations, sampleCount);
mLog.debug("Warm up - regular");
process(regular, iterations, sampleCount);
process(regular, iterations, sampleCount);
process(regular, iterations, sampleCount);
mLog.debug("Test - lpno");
process(lpno, iterations, sampleCount);
process(lpno, iterations, sampleCount);
long duration = process(lpno, iterations, sampleCount);
mLog.debug("Duration Low Phase Noise: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration))));
mLog.debug("Test - regular");
process(regular, iterations, sampleCount);
process(regular, iterations, sampleCount);
duration = process(regular, iterations, sampleCount);
mLog.debug("Duration Regular: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration))));
// mLog.debug(" Delta: " + sdf.format(new Date(TimeUnit.NANOSECONDS.toMillis(duration2 - duration))));
}
}

View File

@@ -95,10 +95,45 @@ public class DQPSKDecisionDirectedSymbolEvaluator implements IPSKSymbolEvaluator
//Since we've rotated the error symbol back to 0 radians, the quadrature value closely approximates the
//arctan of the error angle relative to 0 radians and this provides our error value
mPhaseError = -mEvaluationSymbol.quadrature();
float errorNormalized = normalize(mEvaluationSymbol.quadrature(), 0.3f);
mPhaseError = -errorNormalized;
//Timing error is the same as phase error with the sign corrected according to the vector's angular rotation
mTimingError = mEvaluationSymbol.quadrature() * mTimingErrorPolarity;
mTimingError = errorNormalized * mTimingErrorPolarity;
}
/**
* Constrains value to the range of ( -maximum <> maximum )
*/
private static float clip(float value, float maximum)
{
if(value > maximum)
{
return maximum;
}
else if(value < -maximum)
{
return -maximum;
}
return value;
}
/**
* Constrains timing error to +/- the maximum value and corrects any
* floating point invalid numbers
*/
private float normalize(float error, float maximum)
{
if(Float.isNaN(error))
{
return 0.0f;
}
else
{
return clip(error, maximum);
}
}
/**

View File

@@ -100,7 +100,7 @@ public class DQPSKGardnerSymbolEvaluator implements IPSKSymbolEvaluator<Dibit>
//Since we've rotated the error symbol back to 0 radians, the quadrature value closely approximates the
//arctan of the error angle relative to 0 radians and this provides our error value
mPhaseError = -mEvaluationSymbol.quadrature();
mPhaseError = normalize(-mEvaluationSymbol.quadrature(), 0.3f);
}
/**

View File

@@ -0,0 +1,119 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk.pll;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.module.decode.p25.P25Decoder;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Monitors decode sync events to adaptively control PLL loop gain.
*/
public class AdaptivePLLGainMonitor implements ISyncDetectListener, IFrequencyErrorProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(AdaptivePLLGainMonitor.class);
private static final int MAX_SYNC_COUNT = 6;
private CostasLoop mCostasLoop;
private P25Decoder mP25Decoder;
private PLLGain mPLLGain = PLLGain.LEVEL_1;
private int mSyncCount;
/**
* Constructs an adaptive monitor to monitor the sync state of a decoder and adjust the gain
* level of the costas loop accordingly.
*
* @param costasLoop to receive adaptive gain updates.
* @param p25Decoder to receive frequency error source events
*/
public AdaptivePLLGainMonitor(CostasLoop costasLoop, P25Decoder p25Decoder)
{
mCostasLoop = costasLoop;
mCostasLoop.setPLLGain(mPLLGain);
mCostasLoop.setFrequencyErrorProcessor(this);
mP25Decoder = p25Decoder;
}
/**
* Sync detection event. Updates the running sync count and updates the PLL gain level.
*/
@Override
public void syncDetected()
{
mSyncCount++;
update();
}
/**
* Sync loss event. Updates the running sync count and updates the PLL gain level.
*/
@Override
public void syncLost()
{
mSyncCount -= 2;
update();
}
/**
* Updates the gain state for a sync detect or sync loss
*/
private void update()
{
if(mSyncCount < 0)
{
mSyncCount = 0;
return;
}
if(mSyncCount > MAX_SYNC_COUNT)
{
mSyncCount = MAX_SYNC_COUNT;
return;
}
PLLGain gain = PLLGain.fromSyncCount(mSyncCount);
//Only update the costas loop if the gain value changes
if(gain != mPLLGain)
{
mPLLGain = gain;
mCostasLoop.setPLLGain(mPLLGain);
}
}
/**
* Resets the monitor and commands the PLL to use the highest gain level.
*/
public void reset()
{
mSyncCount = 0;
mPLLGain = PLLGain.LEVEL_1;
mCostasLoop.setPLLGain(mPLLGain);
}
@Override
public void processFrequencyError(long frequencyError)
{
//Only rebroadcast the costas loop frequency error if the gain level has progressed above the
//default gain of 1, meaning that we have received at least 2 sync events
if(mPLLGain != PLLGain.LEVEL_1)
{
mP25Decoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
}
}
}

View File

@@ -41,10 +41,12 @@ public class CostasLoop implements IPhaseLockedLoop
private double mDamping = Math.sqrt(2.0) / 2.0;
private double mAlphaGain;
private double mBetaGain;
private Tracking mTracking = Tracking.FASTEST;
private PLLGain mPLLGain = PLLGain.LEVEL_1;
private double mSymbolRate;
private double mSampleRate;
private int mCounter;
private IFrequencyErrorProcessor mFrequencyErrorProcessor;
private long mFrequencyError;
private int mBaudCounter;
/**
* Costas Loop for tracking and correcting frequency error in a received carrier signal.
@@ -60,6 +62,17 @@ public class CostasLoop implements IPhaseLockedLoop
updateLoopBandwidth();
}
/**
* Registers the listener to receive the measured frequency error as tracked by the PLL at one
* second intervals.
*
* @param listener to receive frequency error measurement.
*/
public void setFrequencyErrorProcessor(IFrequencyErrorProcessor listener)
{
mFrequencyErrorProcessor = listener;
}
/**
* Corrects the current phase tracker when an inverted output is detected. The inversion will take the form of
* +/- 90 degrees or +/- 180 degrees, although the latter correction is equal regardless of the sign.
@@ -89,21 +102,21 @@ public class CostasLoop implements IPhaseLockedLoop
*/
private void updateLoopBandwidth()
{
double bandwidth = TWO_PI / mTracking.getLoopBandwidth();
double bandwidth = TWO_PI / mPLLGain.getLoopBandwidth();
mAlphaGain = (4.0 * mDamping * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
mBetaGain = (4.0 * bandwidth * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
}
/**
* Sets the tracking state for this costas loop. The tracking state affects the aggressiveness of the alpha/beta
* Sets the PLLGain state for this costas loop. The PLLGain state affects the aggressiveness of the alpha/beta
* gain values in synchronizing with the signal carrier.
*
* @param tracking
* @param PLLGain
*/
public void setTracking(Tracking tracking)
public void setPLLGain(PLLGain PLLGain)
{
mTracking = tracking;
mPLLGain = PLLGain;
updateLoopBandwidth();
}
@@ -159,7 +172,7 @@ public class CostasLoop implements IPhaseLockedLoop
mLoopFrequency += (mBetaGain * phaseError);
mLoopPhase += mLoopFrequency + (mAlphaGain * phaseError);
/* Normalize phase between +/- 2 * PI */
//Normalize phase between +/- 2 * PI
if(mLoopPhase > TWO_PI)
{
mLoopPhase -= TWO_PI;
@@ -170,7 +183,7 @@ public class CostasLoop implements IPhaseLockedLoop
mLoopPhase += TWO_PI;
}
/* Limit frequency to +/- maximum loop frequency */
//Limit frequency to +/- maximum loop frequency
if(mLoopFrequency > mMaximumLoopFrequency)
{
mLoopFrequency = mMaximumLoopFrequency;
@@ -181,17 +194,25 @@ public class CostasLoop implements IPhaseLockedLoop
mLoopFrequency = -mMaximumLoopFrequency;
}
//TODO: broadcast channel offset to the tuner to use in auto-adjusting tuner PPM
// mCounter++;
//
// if(mCounter > (5 * mSymbolRate))
// {
// mCounter = 0;
// mLog.info("Current Costas Loop Frequency: " + mLoopFrequency + " [" +
// (int)(mSampleRate / TWO_PI * mLoopFrequency) + " Hz Offset] sample rate [" + mSampleRate + "]");
// }
//Broadcast current frequency error measurement once a second to an external listener
mBaudCounter++;
if(mBaudCounter > mSymbolRate)
{
mBaudCounter = 0;
mFrequencyError = (long)(mSampleRate / TWO_PI * mLoopFrequency);
if(mFrequencyErrorProcessor != null)
{
mFrequencyErrorProcessor.processFrequencyError(mFrequencyError);
}
}
}
/**
* Resets the PLL internal tracking values
*/
@Override
public void reset()
{

View File

@@ -16,27 +16,14 @@
package io.github.dsheirer.dsp.psk.pll;
/**
* Phase Locked Loop (PLL) tracking bandwidth - controls how fast/slow the PLL tracks the carrier frequency
* Interface for passing frequency error measurements.
*/
public enum Tracking
public interface IFrequencyErrorProcessor
{
FASTEST(50.0),
FAST(100.0),
NORMAL(200.0),
SLOW(400.0);
private double mLoopBandwidth;
Tracking(double loopBandwidth)
{
mLoopBandwidth = loopBandwidth;
}
/**
* Loop bandwidth setting
* Provides a frequency error measurement to the listener.
*
* @param error value as measured by the provider
*/
public double getLoopBandwidth()
{
return mLoopBandwidth;
}
void processFrequencyError(long error);
}

View File

@@ -0,0 +1,89 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk.pll;
/**
* Phase Locked Loop (PLL) tracking bandwidth - controls how fast/slow the PLL tracks the carrier frequency
*/
public enum PLLGain
{
//NOTE: static gain level of 200 produced the best results for releases prior to 0.3.4b2
LEVEL_1(150.0, 0, 1),
LEVEL_2(175.0, 2, 4),
LEVEL_3(200.0, 5, 6),
LEVEL_4(200.0, 7, 8),
LEVEL_5(200.0, 9, 10);
private double mLoopBandwidth;
private int mRangeStart;
private int mRangeEnd;
PLLGain(double loopBandwidth, int start, int end)
{
mLoopBandwidth = loopBandwidth;
mRangeStart = start;
mRangeEnd = end;
}
/**
* Loop bandwidth setting
*/
public double getLoopBandwidth()
{
return mLoopBandwidth;
}
public int getRangeStart()
{
return mRangeStart;
}
public int getRangeEnd()
{
return mRangeEnd;
}
/**
* Look up PLL gain from a running sync detection count.
*
* @param syncCount 0-10
* @return PLL gain appropriate for the sync detection value
*/
public static PLLGain fromSyncCount(int syncCount)
{
if(syncCount <= LEVEL_1.getRangeEnd())
{
return LEVEL_1;
}
else if(syncCount <= LEVEL_2.getRangeEnd())
{
return LEVEL_2;
}
else if(syncCount <= LEVEL_3.getRangeEnd())
{
return LEVEL_3;
}
else if(syncCount <= LEVEL_4.getRangeEnd())
{
return LEVEL_4;
}
else
{
return LEVEL_5;
}
}
}

View File

@@ -0,0 +1,125 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.symbol;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import io.github.dsheirer.sample.buffer.ReusableByteBufferQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Assembles reusable byte buffers from an incoming stream of Dibits.
*/
public class DibitToByteBufferAssembler implements Listener<Dibit>, IReusableByteBufferProvider
{
private final static Logger mLog = LoggerFactory.getLogger(DibitToByteBufferAssembler.class);
private ReusableByteBufferQueue mBufferQueue = new ReusableByteBufferQueue("DibitToByteBufferAssembler");
private ReusableByteBuffer mCurrentBuffer;
private int mBufferPointer;
private int mBufferSize;
private byte mCurrentByte;
private int mDibitCount;
private Listener<ReusableByteBuffer> mBufferListener;
/**
* Constructs an assembler to produce reusable byte buffers of the specified size
*
* @param bufferSize
*/
public DibitToByteBufferAssembler(int bufferSize)
{
mBufferSize = bufferSize;
getNextBuffer();
}
/**
* Broadcasts the current buffer to the registered listener and creates a new buffer, resetting
* the buffer pointer to zero so that new dibits can be loaded.
*/
private void getNextBuffer()
{
if(mCurrentBuffer != null && mBufferListener != null)
{
mBufferListener.receive(mCurrentBuffer);
}
mCurrentBuffer = mBufferQueue.getBuffer(mBufferSize);
mBufferPointer = 0;
}
@Override
public void receive(Dibit dibit)
{
mCurrentByte <<= 2;
switch(dibit)
{
case D01_PLUS_3:
mCurrentByte |= 0x01;
break;
case D00_PLUS_1:
//no-op - value is already 00
break;
case D10_MINUS_1:
mCurrentByte |= 0x02;
break;
case D11_MINUS_3:
mCurrentByte |= 0x03;
break;
}
mDibitCount++;
if(mDibitCount >= 4)
{
mCurrentBuffer.getBytes()[mBufferPointer++] = mCurrentByte;
mCurrentByte = 0x00;
mDibitCount = 0;
if(mBufferPointer >= mBufferSize)
{
getNextBuffer();
}
}
}
/**
* Registers the listener to receive fully assembled byte buffers from this assembler.
*/
@Override
public void setBufferListener(Listener<ReusableByteBuffer> listener)
{
mBufferListener = listener;
}
/**
* Removes the listener from receiving buffers from this assembler
*/
@Override
public void removeBufferListener(Listener<ReusableByteBuffer> listener)
{
mBufferListener = null;
}
@Override
public boolean hasBufferListeners()
{
return mBufferListener != null;
}
}

View File

@@ -15,7 +15,13 @@
******************************************************************************/
package io.github.dsheirer.dsp.symbol;
/**
* Listener interface to be notified each time a sync pattern has been detected and/or when the sync
* has been lost.
*/
public interface ISyncDetectListener
{
void syncDetected();
void syncDetected();
void syncLost();
}

View File

@@ -259,7 +259,8 @@ public class ChannelizerViewer extends JFrame
for(int x = 0; x < mChannelCount; x++)
{
TunerChannelSource source = mTestTuner.getChannelSourceManager().getTestingSource(x);
TunerChannel tunerChannel = new TunerChannel(100000000, 12500);
TunerChannelSource source = mTestTuner.getChannelSourceManager().getSource(tunerChannel, null);
DiscreteChannelPanel channelPanel = new DiscreteChannelPanel(mSettingsManager, source, x);
channelPanel.setDFTSize(mChannelPanelDFTSize);
@@ -361,7 +362,7 @@ public class ChannelizerViewer extends JFrame
mComplexDecibelConverter.addListener(mSpectrumPanel);
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, null);
if(mSource != null)
{
@@ -555,7 +556,7 @@ public class ChannelizerViewer extends JFrame
{
if(sourceCount < maxSourceCount)
{
TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel);
TunerChannelSource source = tuner.getChannelSourceManager().getSource(tunerChannel, null);
if(source != null)
{

View File

@@ -376,7 +376,7 @@ public class ChannelizerViewer2 extends JFrame
mComplexDecibelConverter.addListener(mSpectrumPanel);
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, null);
if(mSource != null)
{

View File

@@ -281,7 +281,7 @@ public class SynthesizerViewer extends JFrame
{
try
{
float[] taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 12);
float[] taps = FilterFactory.getSincM2Synthesizer( 25000.0, 12500.0, 2, 12);
mSynthesizer = new TwoChannelSynthesizerM2(taps);
}
catch(FilterDesignException fde)

View File

@@ -34,6 +34,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.prefs.Preferences;
/**
* Application for viewing and working with demodulators and recording files.
@@ -42,6 +43,9 @@ public class DemodulatorViewerFX extends Application
{
private final static Logger mLog = LoggerFactory.getLogger(DemodulatorViewerFX.class);
private static final String PREFERENCE_FILE_OPEN_DEFAULT_FOLDER = "default.folder";
private Preferences mPreferences = Preferences.userNodeForPackage(DemodulatorViewerFX.class);
private Stage mStage;
private MenuBar mMenuBar;
private RecentFilesMenu mRecentFilesMenu;
@@ -117,9 +121,22 @@ public class DemodulatorViewerFX extends Application
{
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open an I/Q recording file");
String directory = mPreferences.get(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, null);
if(directory != null)
{
fileChooser.setInitialDirectory(new File(directory));
}
File file = fileChooser.showOpenDialog(mStage);
getViewerDesktop().load(file);
getRecentFilesMenu().add(file);
if(file != null)
{
getViewerDesktop().load(file);
getRecentFilesMenu().add(file);
mPreferences.put(PREFERENCE_FILE_OPEN_DEFAULT_FOLDER, file.getParent());
}
}
});

View File

@@ -17,8 +17,8 @@ package io.github.dsheirer.gui.instrument;
import io.github.dsheirer.gui.instrument.decoder.AbstractDecoderPane;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.source.IControllableFileSource;
import io.github.dsheirer.source.IFrameLocationListener;
import io.github.dsheirer.source.wave.ComplexWaveSource;
@@ -177,7 +177,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener
/**
* Adds listener to receive complex buffers from this playback
*/
public void addListener(Listener<ReusableBuffer> listener)
public void addListener(Listener<ReusableFloatBuffer> listener)
{
mReusableBufferBroadcaster.addListener(listener);
}
@@ -185,7 +185,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener
/**
* Removes the listener from receiving complex buffers from this playback
*/
public void removeListener(Listener<ReusableBuffer> listener)
public void removeListener(Listener<ReusableFloatBuffer> listener)
{
mReusableBufferBroadcaster.removeListener(listener);
}

View File

@@ -26,6 +26,7 @@ import java.util.prefs.Preferences;
public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
{
private static final String PREFERENCE_RECENT_FILE = ".recent.file.";
private Preferences mPreferences = Preferences.userNodeForPackage(RecentFilesMenu.class);
private IFileSelectionListener mFileSelectionListener;
private String mIdentifier;
@@ -111,7 +112,7 @@ public class RecentFilesMenu extends Menu implements EventHandler<ActionEvent>
*/
private String getKey(int index)
{
return mIdentifier + ".recent.file." + index;
return mIdentifier + PREFERENCE_RECENT_FILE + index;
}
/**

View File

@@ -21,6 +21,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ValueAxis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +43,24 @@ public class DoubleLineChart extends LineChart implements Listener<Double>
init(length);
}
/**
* Sets the minimum displayable value for the y axis
* @param min
*/
public void setMin(double min)
{
((ValueAxis)getYAxis()).setLowerBound(min);
}
/**
* Sets the maximum displayable value for the y axis
* @param max
*/
public void setMax(double max)
{
((ValueAxis)getYAxis()).setUpperBound(max);
}
private void init(int length)
{
mDoubleCircularBuffer = new DoubleCircularBuffer(length);

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.buffer.RealCircularBuffer;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.LineChart;
@@ -27,7 +27,7 @@ import javafx.scene.layout.StackPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RealSampleLineChart extends LineChart implements Listener<ReusableBuffer>
public class RealSampleLineChart extends LineChart implements Listener<ReusableFloatBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger(RealSampleLineChart.class);
private ObservableList<Data<Integer,Float>> mSamples = FXCollections.observableArrayList();
@@ -65,7 +65,7 @@ public class RealSampleLineChart extends LineChart implements Listener<ReusableB
}
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
for(float sample: buffer.getSamples())
{

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.gui.instrument.chart;
import javafx.collections.ObservableList;
public class SamplesPerSymbolChart extends DoubleLineChart
{
private double mFractionalSamplesPerSymbol;
private double mPreviousSamplesPerSymbol;
public SamplesPerSymbolChart(int length, double samplesPerSymbol)
{
super("Samples Per Symbol", -1.0, 1.0, 0.1, length);
setSamplesPerSymbol(samplesPerSymbol);
}
public void setSamplesPerSymbol(double samplesPerSymbol)
{
mFractionalSamplesPerSymbol = samplesPerSymbol - ((int)samplesPerSymbol);
mPreviousSamplesPerSymbol = samplesPerSymbol;
ObservableList<Series> seriesList = getData();
seriesList.get(0).setName("Samples Per Symbol:" + samplesPerSymbol);
}
@Override
public void receive(Double samplesPerSymbol)
{
double delta = samplesPerSymbol - mPreviousSamplesPerSymbol - mFractionalSamplesPerSymbol;
while(delta < (-1.0 + mFractionalSamplesPerSymbol))
{
delta++;
}
while(delta > 1.0)
{
delta--;
}
super.receive(delta);
mPreviousSamplesPerSymbol = samplesPerSymbol;
}
}

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.gui.instrument.chart.RealSampleLineChart;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
@@ -52,7 +52,7 @@ public abstract class AbstractAFSK1200Pane extends RealDecoderPane
private void init()
{
//This is force-cast without type checking ....
addListener((Listener<ReusableBuffer>)getDecoder());
addListener((Listener<ReusableFloatBuffer>)getDecoder());
addListener(getSampleLineChart());

View File

@@ -17,10 +17,10 @@ package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.scene.layout.VBox;
public abstract class AbstractDecoderPane<T extends ReusableBuffer> extends VBox implements Listener<T>
public abstract class AbstractDecoderPane<T extends ReusableFloatBuffer> extends VBox implements Listener<T>
{
abstract SampleType getSampleType();

View File

@@ -44,6 +44,12 @@ public class LJ1200Pane extends AbstractAFSK1200Pane
{
mLog.debug("Tower Sync Detected!");
}
@Override
public void syncLost()
{
//no-op
}
});
mDecoder.getTransponderMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@@ -52,6 +58,12 @@ public class LJ1200Pane extends AbstractAFSK1200Pane
{
mLog.debug("Transponder Sync Detected!");
}
@Override
public void syncLost()
{
//no-op
}
});
}

View File

@@ -44,6 +44,12 @@ public class MDC1200Pane extends AbstractAFSK1200Pane
{
mLog.debug("Sync Detected!");
}
@Override
public void syncLost()
{
mLog.debug("Sync Lost!");
}
});
}

View File

@@ -44,6 +44,12 @@ public class MPT1327Pane extends AbstractAFSK1200Pane
{
// mLog.debug("Control Sync Detected!");
}
@Override
public void syncLost()
{
//no-op
}
});
mDecoder.getTrafficMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@@ -52,6 +58,12 @@ public class MPT1327Pane extends AbstractAFSK1200Pane
{
// mLog.debug("Traffic Sync Detected!");
}
@Override
public void syncLost()
{
//no-op
}
});
}

View File

@@ -19,6 +19,7 @@ import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
import io.github.dsheirer.gui.instrument.chart.SamplesPerSymbolChart;
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.module.decode.DecoderType;
@@ -34,14 +35,14 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
{
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1LSMPane.class);
private HBox mSampleChartBox;
private ComplexSampleLineChart mSampleLineChart;
private HBox mTopChartBox;
private ComplexSampleLineChart mDifferentialDemodulatedSamplesChartBox;
private EyeDiagramChart mEyeDiagramChart;
private HBox mDecoderChartBox;
private SymbolChart mSymbolChart;
private HBox mBottomChartBox;
private SymbolChart mSymbolConstellationChart;
private PhaseLineChart mPLLPhaseErrorLineChart;
private DoubleLineChart mPLLFrequencyLineChart;
private DoubleLineChart mSamplesPerSymbolLineChart;
private SamplesPerSymbolChart mSamplesPerSymbolLineChart;
private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
private P25DecoderLSMInstrumented mDecoder = new P25DecoderLSMInstrumented(null);
@@ -56,16 +57,16 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
addListener(getDecoder());
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
getDecoder().setComplexSymbolListener(getSymbolChart());
getDecoder().setComplexSymbolListener(getSymbolConstellationChart());
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
mFilteredBufferBroadcaster.addListener(getSampleLineChart());
mFilteredBufferBroadcaster.addListener(getDifferentialDemodulatedSamplesChartBox());
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
HBox.setHgrow(getTopChartBox(), Priority.ALWAYS);
HBox.setHgrow(getBottomChartBox(), Priority.ALWAYS);
getChildren().addAll(getTopChartBox(), getBottomChartBox());
mDecoder.setMessageListener(new Listener<Message>()
{
@@ -86,7 +87,8 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
mDecoder.setSampleRate(sampleRate);
double samplesPerSymbol = sampleRate / 4800.0;
getSampleLineChart().setSamplesPerSymbol((int)samplesPerSymbol);
getDifferentialDemodulatedSamplesChartBox().setSamplesPerSymbol((int)samplesPerSymbol);
getSamplesPerSymbolLineChart().setSamplesPerSymbol(samplesPerSymbol);
}
private P25DecoderLSMInstrumented getDecoder()
@@ -94,69 +96,69 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
return mDecoder;
}
private SymbolChart getSymbolChart()
private SymbolChart getSymbolConstellationChart()
{
if(mSymbolChart == null)
if(mSymbolConstellationChart == null)
{
mSymbolChart = new SymbolChart(10);
mSymbolConstellationChart = new SymbolChart(10);
}
return mSymbolChart;
return mSymbolConstellationChart;
}
private HBox getDecoderChartBox()
private HBox getBottomChartBox()
{
if(mDecoderChartBox == null)
if(mBottomChartBox == null)
{
mDecoderChartBox = new HBox();
mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
mBottomChartBox = new HBox();
mBottomChartBox.setMaxHeight(Double.MAX_VALUE);
getSymbolConstellationChart().setMaxWidth(Double.MAX_VALUE);
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
HBox.setHgrow(getSymbolConstellationChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
mBottomChartBox.getChildren().addAll(getSymbolConstellationChart(), getPLLPhaseErrorLineChart(),
getPLLFrequencyLineChart());
}
return mDecoderChartBox;
return mBottomChartBox;
}
private HBox getSampleChartBox()
private HBox getTopChartBox()
{
if(mSampleChartBox == null)
if(mTopChartBox == null)
{
mSampleChartBox = new HBox();
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
mTopChartBox = new HBox();
mTopChartBox.setMaxHeight(Double.MAX_VALUE);
getDifferentialDemodulatedSamplesChartBox().setMaxWidth(Double.MAX_VALUE);
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
HBox.setHgrow(getDifferentialDemodulatedSamplesChartBox(), Priority.ALWAYS);
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
mTopChartBox.getChildren().addAll(getDifferentialDemodulatedSamplesChartBox(), getEyeDiagramChart(),
getSamplesPerSymbolLineChart());
}
return mSampleChartBox;
return mTopChartBox;
}
private ComplexSampleLineChart getSampleLineChart()
private ComplexSampleLineChart getDifferentialDemodulatedSamplesChartBox()
{
if(mSampleLineChart == null)
if(mDifferentialDemodulatedSamplesChartBox == null)
{
mSampleLineChart = new ComplexSampleLineChart(100, 10);
mDifferentialDemodulatedSamplesChartBox = new ComplexSampleLineChart(100, 10);
}
return mSampleLineChart;
return mDifferentialDemodulatedSamplesChartBox;
}
private EyeDiagramChart getEyeDiagramChart()
{
if(mEyeDiagramChart == null)
{
mEyeDiagramChart = new EyeDiagramChart(10, "Middle: 3-4 / Symbol: 8-9");
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol/Eye Diagram");
}
return mEyeDiagramChart;
@@ -176,17 +178,17 @@ public class P25Phase1LSMPane extends ComplexDecoderPane
{
if(mPLLFrequencyLineChart == null)
{
mPLLFrequencyLineChart = new DoubleLineChart( "PLL Frequency", -500, 500, 50, 40);
mPLLFrequencyLineChart = new DoubleLineChart( "PLL Frequency", -2500, 2500, 200, 40);
}
return mPLLFrequencyLineChart;
}
private DoubleLineChart getSamplesPerSymbolLineChart()
private SamplesPerSymbolChart getSamplesPerSymbolLineChart()
{
if(mSamplesPerSymbolLineChart == null)
{
mSamplesPerSymbolLineChart = new DoubleLineChart( "Sample Point", 9.5, 11.5, 0.1, 40);
mSamplesPerSymbolLineChart = new SamplesPerSymbolChart(40, 6.25);
}
return mSamplesPerSymbolLineChart;

View File

@@ -18,11 +18,11 @@ package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.SampleType;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.scene.text.Text;
public class RealDecoderPane extends AbstractDecoderPane<ReusableBuffer>
public class RealDecoderPane extends AbstractDecoderPane<ReusableFloatBuffer>
{
private ReusableBufferBroadcaster mBufferBroadcaster = new ReusableBufferBroadcaster();
private DecoderType mDecoderType;
@@ -47,17 +47,17 @@ public class RealDecoderPane extends AbstractDecoderPane<ReusableBuffer>
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mBufferBroadcaster.broadcast(reusableBuffer);
mBufferBroadcaster.broadcast(reusableFloatBuffer);
}
public void addListener(Listener<ReusableBuffer> listener)
public void addListener(Listener<ReusableFloatBuffer> listener)
{
mBufferBroadcaster.addListener(listener);
}
public void removeListener(Listener<ReusableBuffer> listener)
public void removeListener(Listener<ReusableFloatBuffer> listener)
{
mBufferBroadcaster.removeListener(listener);
}

View File

@@ -44,6 +44,12 @@ public class Tait1200Pane extends AbstractAFSK1200Pane
{
mLog.debug("ANI Sync Detected!");
}
@Override
public void syncLost()
{
}
});
mDecoder.getGPSMessageFramer().setSyncDetectListener(new ISyncDetectListener()
{
@@ -52,6 +58,12 @@ public class Tait1200Pane extends AbstractAFSK1200Pane
{
mLog.debug("GPS Sync Detected!");
}
@Override
public void syncLost()
{
}
});
}

View File

@@ -49,11 +49,14 @@ import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableByteBufferListener;
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableAudioPacket;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.source.ComplexSource;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
@@ -93,8 +96,9 @@ public class ProcessingChain implements IChannelEventListener
private final static Logger mLog = LoggerFactory.getLogger(ProcessingChain.class);
private ReusableBufferBroadcaster<ReusableAudioPacket> mAudioPacketBroadcaster = new ReusableBufferBroadcaster<>();
private ReusableBufferBroadcaster<ReusableBuffer> mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster();
private ReusableBufferBroadcaster<ReusableFloatBuffer> mDemodulatedAudioBufferBroadcaster = new ReusableBufferBroadcaster();
private ReusableBufferBroadcaster<ReusableComplexBuffer> mBasebandComplexBufferBroadcaster = new ReusableBufferBroadcaster();
private ReusableBufferBroadcaster<ReusableByteBuffer> mDemodulatedBitstreamBufferBroadcaster = new ReusableBufferBroadcaster();
private Broadcaster<AttributeChangeRequest> mAttributeChangeRequestBroadcaster = new Broadcaster<>();
private Broadcaster<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster<>();
@@ -163,6 +167,7 @@ public class ProcessingChain implements IChannelEventListener
mCallEventBroadcaster.dispose();
mChannelEventBroadcaster.dispose();
mBasebandComplexBufferBroadcaster.dispose();
mDemodulatedBitstreamBufferBroadcaster.dispose();
mMessageBroadcaster.dispose();
mSquelchStateBroadcaster.dispose();
}
@@ -323,6 +328,11 @@ public class ProcessingChain implements IChannelEventListener
mDemodulatedAudioBufferBroadcaster.addListener(((IReusableBufferListener)module).getReusableBufferListener());
}
if(module instanceof IReusableByteBufferListener)
{
mDemodulatedBitstreamBufferBroadcaster.addListener(((IReusableByteBufferListener)module).getReusableByteBufferListener());
}
if(module instanceof IReusableComplexBufferListener)
{
mBasebandComplexBufferBroadcaster.addListener(((IReusableComplexBufferListener)module).getReusableComplexBufferListener());
@@ -385,6 +395,11 @@ public class ProcessingChain implements IChannelEventListener
mDemodulatedAudioBufferBroadcaster.removeListener(((IReusableBufferListener)module).getReusableBufferListener());
}
if(module instanceof IReusableByteBufferListener)
{
mDemodulatedBitstreamBufferBroadcaster.removeListener(((IReusableByteBufferListener) module).getReusableByteBufferListener());
}
if(module instanceof IReusableComplexBufferListener)
{
mBasebandComplexBufferBroadcaster.removeListener(((IReusableComplexBufferListener) module).getReusableComplexBufferListener());
@@ -452,6 +467,11 @@ public class ProcessingChain implements IChannelEventListener
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
}
if(module instanceof IReusableByteBufferProvider)
{
((IReusableByteBufferProvider)module).setBufferListener(mDemodulatedBitstreamBufferBroadcaster);
}
if(module instanceof IReusableBufferProvider)
{
((IReusableBufferProvider)module).setBufferListener(mDemodulatedAudioBufferBroadcaster);
@@ -484,6 +504,11 @@ public class ProcessingChain implements IChannelEventListener
((IAudioPacketProvider) module).setAudioPacketListener(null);
}
if(module instanceof IReusableByteBufferProvider)
{
((IReusableByteBufferProvider)module).removeBufferListener(mDemodulatedBitstreamBufferBroadcaster);
}
if(module instanceof ICallEventProvider)
{
((ICallEventProvider) module).removeCallEventListener(mCallEventBroadcaster);
@@ -549,7 +574,7 @@ public class ProcessingChain implements IChannelEventListener
//Broadcast the source sample rate so that each of the modules can self-configure
mSourceEventBroadcaster.broadcast(SourceEvent.sampleRateChange(mSource.getSampleRate(), "Processing Chain Startup"));
//Broadcast the source center frequencysample rate so that each of the modules can self-configure
//Broadcast the source center frequency / sample rate so that each of the modules can self-configure
mSourceEventBroadcaster.broadcast(SourceEvent.frequencyChange(mSource.getFrequency(), "Processing Chain Startup"));
//Setup the channel state to monitor source overflow conditions
@@ -790,7 +815,7 @@ public class ProcessingChain implements IChannelEventListener
/**
* Adds listener to receive demodulated audio buffers from an modules that produce demodulated audio.
*/
public void addDemodulatedAudioListener(Listener<ReusableBuffer> listener)
public void addDemodulatedAudioListener(Listener<ReusableFloatBuffer> listener)
{
mDemodulatedAudioBufferBroadcaster.addListener(listener);
}
@@ -798,7 +823,7 @@ public class ProcessingChain implements IChannelEventListener
/**
* Removes the listener from receiving demodulated audio buffers.
*/
public void removeDemodulatedAudioListener(Listener<ReusableBuffer> listener)
public void removeDemodulatedAudioListener(Listener<ReusableFloatBuffer> listener)
{
mDemodulatedAudioBufferBroadcaster.removeListener(listener);
}

View File

@@ -93,6 +93,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
public class DecoderFactory
@@ -346,7 +347,6 @@ public class DecoderFactory
filters.add(new MPT1327MessageFilter());
break;
case P25_PHASE1:
case P25_PHASE2:
filters.add(new P25MessageFilterSet());
break;
case PASSPORT:
@@ -467,4 +467,14 @@ public class DecoderFactory
return null;
}
/**
* Decoder(s) that support bitstreams.
*
* @return set of decoders that support bitstreams.
*/
public static EnumSet<DecoderType> getBitstreamDecoders()
{
return EnumSet.of(DecoderType.P25_PHASE1);
}
}

View File

@@ -1,22 +1,22 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import java.util.ArrayList;
import java.util.EnumSet;
@@ -25,37 +25,37 @@ import java.util.EnumSet;
*/
public enum DecoderType
{
ACARS( "ACARS", "ACARS", "images/acars.png", 12500 ),
AIS( "AIS", "AIS", "images/ais.png", 12500 ),
AM( "AM", "AM", "images/am.png", 10000 ),
APRS( "APRS", "APRS", "images/aprs.png", 12500 ),
DMR( "DMR", "DMR", "images/dmr.png", 12500 ),
FLEETSYNC2( "Fleetsync II", "Fleetsync2", "images/fm.png", 12500 ),
LJ_1200( "LJ1200 173.075", "LJ1200", "images/lj1200.png", 12500 ),
LTR_STANDARD( "LTR-Standard", "LTR", "images/ltr.png", 10000 ),
LTR_NET( "LTR-Net", "LTR-Net", "images/ltr_net.png", 12500 ),
MDC1200( "MDC1200", "MDC1200", "images/fm.png", 12500 ),
MOTOROLA_TYPE_2( "MOTO T2", "MOTO T2", "images/motorola_type_2.png", 15000 ),
MPT1327( "MPT1327", "MPT1327", "images/mpt1327.png", 12500 ),
NXDN( "NXDN", "NXDN", "images/nxdn.png", 8000 ),
NBFM( "NBFM", "NBFM", "images/fm.png", 12500 ),
PASSPORT( "Passport", "Passport", "images/passport.png", 12500 ),
P25_PHASE1( "P25 Phase I", "P25-1", "images/p25_1.png", 12500 ),
P25_PHASE2( "P25 Phase II", "P25-2", "images/p25_2.png", 15000 ),
TAIT_1200( "Tait 1200", "Tait 1200", "images/tait.png", 12500 );
//Primary Decoders
AM("AM", "AM", 10000, new ChannelSpecification(25000.0, 3000.0, 5000.0)),
LTR_STANDARD("LTR-Standard", "LTR", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)),
LTR_NET("LTR-Net", "LTR-Net", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)),
MPT1327("MPT1327", "MPT1327", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)),
NBFM("NBFM", "NBFM", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)),
PASSPORT("Passport", "Passport", 12500, new ChannelSpecification(25000.0, 6000.0, 7000.0)),
P25_PHASE1("P25 Phase I", "P25-1", 12500, new ChannelSpecification(50000.0, 6000.0, 7000.0)),
//Auxiliary Decoders
FLEETSYNC2("Fleetsync II", "Fleetsync2", 12500),
LJ_1200("LJ1200 173.075", "LJ1200", 12500),
MDC1200("MDC1200", "MDC1200", 12500),
TAIT_1200("Tait 1200", "Tait 1200", 12500);
private String mDisplayString;
private String mShortDisplayString;
private String mIconFilename;
private int mChannelBandwidth;
DecoderType( String displayString, String shortDisplayString,
String iconFilename, int bandwidth )
private ChannelSpecification mChannelSpecification;
DecoderType(String displayString, String shortDisplayString, int bandwidth, ChannelSpecification channelSpecification)
{
mDisplayString = displayString;
mShortDisplayString = shortDisplayString;
mIconFilename = iconFilename;
mChannelBandwidth = bandwidth;
mChannelSpecification = channelSpecification;
}
DecoderType(String displayString, String shortDisplayString, int bandwidth)
{
this(displayString, shortDisplayString, bandwidth, null);
}
/**
@@ -63,68 +63,57 @@ public enum DecoderType
*/
public static EnumSet<DecoderType> getPrimaryDecoders()
{
return EnumSet.of( DecoderType.AM,
DecoderType.LTR_NET,
DecoderType.LTR_STANDARD,
DecoderType.MPT1327,
DecoderType.NBFM,
DecoderType.P25_PHASE1,
DecoderType.PASSPORT );
return EnumSet.of(DecoderType.AM,
DecoderType.LTR_NET,
DecoderType.LTR_STANDARD,
DecoderType.MPT1327,
DecoderType.NBFM,
DecoderType.P25_PHASE1,
DecoderType.PASSPORT);
}
/**
* Decoders that can be used in the viewer application
*/
public static EnumSet<DecoderType> getInstrumentableDecoders()
{
return EnumSet.of( DecoderType.FLEETSYNC2,
DecoderType.LJ_1200,
DecoderType.LTR_NET,
DecoderType.MDC1200,
DecoderType.MPT1327,
DecoderType.PASSPORT,
DecoderType.P25_PHASE1,
DecoderType.TAIT_1200 );
}
/**
* Available auxiliary decoders.
*/
public static ArrayList<DecoderType> getAuxDecoders()
{
ArrayList<DecoderType> decoders = new ArrayList<DecoderType>();
decoders.add( DecoderType.FLEETSYNC2 );
decoders.add( DecoderType.LJ_1200 );
decoders.add( DecoderType.MDC1200 );
decoders.add( DecoderType.TAIT_1200 );
return decoders;
ArrayList<DecoderType> decoders = new ArrayList<DecoderType>();
decoders.add(DecoderType.FLEETSYNC2);
decoders.add(DecoderType.LJ_1200);
decoders.add(DecoderType.MDC1200);
decoders.add(DecoderType.TAIT_1200);
return decoders;
}
public String getDisplayString()
{
return mDisplayString;
}
public String getShortDisplayString()
{
return mShortDisplayString;
return mShortDisplayString;
}
public String getIconFilename()
{
return mIconFilename;
}
public int getChannelBandwidth()
{
return mChannelBandwidth;
return mChannelBandwidth;
}
/**
* Channel specification for a channel for this decoder type.
* @return specification or null
*/
public ChannelSpecification getChannelSpecification()
{
return mChannelSpecification;
}
@Override
public String toString()
{
return mDisplayString;
return mDisplayString;
}
}

View File

@@ -19,7 +19,7 @@ import io.github.dsheirer.dsp.afsk.AFSK1200Decoder;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
/**
* Abstract class for Audio Frequency Shift Keying (AFSK) 1200-baud decoder based decoder modules.
@@ -28,7 +28,7 @@ import io.github.dsheirer.sample.buffer.ReusableBuffer;
*
* Sub-class implementations should invoke getDecoder().setListener() to receive decoded symbol stream.
*/
public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
{
private AFSK1200Decoder mAFSK1200Decoder;
@@ -48,13 +48,13 @@ public abstract class AbstractAFSKDecoder extends Decoder implements IReusableBu
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mAFSK1200Decoder.receive(reusableBuffer);
mAFSK1200Decoder.receive(reusableFloatBuffer);
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
return this;
}

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.fleetsync2;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class Fleetsync2DecoderInstrumented extends Fleetsync2Decoder implements
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.lj1200;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class LJ1200DecoderInstrumented extends LJ1200Decoder implements IInstrum
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -24,9 +24,9 @@ import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
public class LTRNetDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
public class LTRNetDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
{
public static final int LTR_NET_MESSAGE_LENGTH = 40;
protected LTRDecoder mLTRDecoder;
@@ -73,15 +73,15 @@ public class LTRNetDecoder extends Decoder implements IReusableBufferListener, L
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
return mLTRDecoder;
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mLTRDecoder.receive(reusableBuffer);
mLTRDecoder.receive(reusableFloatBuffer);
}
@Override

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.ltrnet;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.dsp.fsk.LTRDecoderInstrumented;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
public class LTRNetDecoderInstrumented extends LTRNetDecoder
@@ -35,9 +35,9 @@ public class LTRNetDecoderInstrumented extends LTRNetDecoder
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -24,9 +24,9 @@ import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
public class LTRStandardDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
public class LTRStandardDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
{
public static final int LTR_STANDARD_MESSAGE_LENGTH = 40;
@@ -65,15 +65,15 @@ public class LTRStandardDecoder extends Decoder implements IReusableBufferListen
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
return mLTRDecoder;
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mLTRDecoder.receive(reusableBuffer);
mLTRDecoder.receive(reusableFloatBuffer);
}
@Override

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.mdc1200;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class MDCDecoderInstrumented extends MDCDecoder implements IInstrumentedA
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.mpt1327;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class MPT1327DecoderInstrumented extends MPT1327Decoder implements IInstr
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -2,13 +2,13 @@ package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.dsp.gain.DirectGainControl;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.sample.real.RealSampleListener;
import io.github.dsheirer.sample.real.RealSampleProvider;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
public class C4FMSymbolFilter implements Listener<ReusableBuffer>, ISourceEventListener, RealSampleProvider
public class C4FMSymbolFilter implements Listener<ReusableFloatBuffer>, ISourceEventListener, RealSampleProvider
{
private static final float TAPS[][] =
{
@@ -149,7 +149,7 @@ public class C4FMSymbolFilter implements Listener<ReusableBuffer>, ISourceEventL
private static final int SAMPLE_RATE = 48000;
private static final int SYMBOL_RATE = 4800;
/* Tracking loop gain constant */
/* PLLGain loop gain constant */
private static final double K_SYMBOL_SPREAD = 0.0100;
/* Constraints on symbol spreading */
@@ -215,7 +215,7 @@ public class C4FMSymbolFilter implements Listener<ReusableBuffer>, ISourceEventL
}
@Override
public void receive(ReusableBuffer buffer)
public void receive(ReusableFloatBuffer buffer)
{
for(float sample : buffer.getSamples())
{

View File

@@ -16,19 +16,26 @@
package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
public abstract class P25Decoder extends Decoder implements ISourceEventListener, ISourceEventProvider,
IReusableComplexBufferListener, Listener<ReusableComplexBuffer>
IReusableComplexBufferListener, Listener<ReusableComplexBuffer>, IReusableByteBufferProvider
{
private double mSampleRate;
private Broadcaster<Dibit> mDibitBroadcaster = new Broadcaster<>();
private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300);
private P25MessageProcessor mMessageProcessor;
private AliasList mAliasList;
private Listener<SourceEvent> mSourceEventListener;
@@ -40,6 +47,42 @@ public abstract class P25Decoder extends Decoder implements ISourceEventListener
mAliasList = aliasList;
mMessageProcessor = new P25MessageProcessor(mAliasList);
mMessageProcessor.setMessageListener(getMessageListener());
mDibitBroadcaster.addListener(mByteBufferAssembler);
}
/**
* Assembler for packaging Dibit stream into reusable byte buffers.
*/
protected Broadcaster<Dibit> getDibitBroadcaster()
{
return mDibitBroadcaster;
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public void setBufferListener(Listener<ReusableByteBuffer> listener)
{
mByteBufferAssembler.setBufferListener(listener);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public void removeBufferListener(Listener<ReusableByteBuffer> listener)
{
mByteBufferAssembler.removeBufferListener(listener);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public boolean hasBufferListeners()
{
return mByteBufferAssembler.hasBufferListeners();
}
protected double getSymbolRate()
@@ -132,7 +175,7 @@ public abstract class P25Decoder extends Decoder implements ISourceEventListener
* issue frequency correction requests to the channel source.
* @param sourceEvent to broadcast
*/
protected void broadcast(SourceEvent sourceEvent)
public void broadcast(SourceEvent sourceEvent)
{
if(mSourceEventListener != null)
{

View File

@@ -23,8 +23,8 @@ import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator;
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
import io.github.dsheirer.dsp.psk.pll.AdaptivePLLGainMonitor;
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.dsp.psk.pll.Tracking;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
@@ -36,10 +36,12 @@ import java.util.Map;
public class P25DecoderC4FM extends P25Decoder
{
private final static Logger mLog = LoggerFactory.getLogger(P25DecoderC4FM.class);
protected static final float SAMPLE_COUNTER_GAIN = 0.5f;
protected static final float SAMPLE_COUNTER_GAIN = 0.3f;
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator;
protected CostasLoop mCostasLoop;
protected AdaptivePLLGainMonitor mPLLGainMonitor;
protected P25MessageFramer mMessageFramer;
private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32);
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
@@ -54,7 +56,7 @@ public class P25DecoderC4FM extends P25Decoder
public P25DecoderC4FM(AliasList aliasList)
{
super(4800.0, aliasList);
setSampleRate(48000.0);
setSampleRate(25000.0);
}
public void setSampleRate(double sampleRate)
@@ -64,15 +66,25 @@ public class P25DecoderC4FM extends P25Decoder
mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter());
mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate());
mCostasLoop.setTracking(Tracking.FASTEST);
mPLLGainMonitor = new AdaptivePLLGainMonitor(mCostasLoop, this);
mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
//Message framer can issue a symbol-inversion correction request to the PLL when detected
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
if(mMessageFramer != null)
{
getDibitBroadcaster().removeListener(mMessageFramer);
mMessageFramer.dispose();
}
//The Costas Loop receives symbol-inversion correction requests when detected.
//The PLL gain monitor receives sync detect/loss signals from the message framer
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor);
mMessageFramer.setListener(getMessageProcessor());
mQPSKDemodulator.setSymbolListener(mMessageFramer);
mMessageFramer.setSampleRate(sampleRate);
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
/**
@@ -165,6 +177,10 @@ public class P25DecoderC4FM extends P25Decoder
mCostasLoop.reset();
setSampleRate(sourceEvent.getValue().doubleValue());
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//Reset the PLL if/when the tuner PPM changes so that we can re-lock
mCostasLoop.reset();
break;
}
}
@@ -183,5 +199,6 @@ public class P25DecoderC4FM extends P25Decoder
public void reset()
{
mCostasLoop.reset();
mPLLGainMonitor.reset();
}
}

View File

@@ -78,7 +78,8 @@ public class P25DecoderC4FMInstrumented extends P25DecoderC4FM
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
instrumented.setSymbolListener(mMessageFramer);
instrumented.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
public void setComplexSymbolListener(Listener<Complex> listener)

View File

@@ -22,20 +22,17 @@ import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
import io.github.dsheirer.dsp.psk.DQPSKGardnerDemodulator;
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
import io.github.dsheirer.dsp.psk.pll.AdaptivePLLGainMonitor;
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.dsp.psk.pll.Tracking;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class P25DecoderLSM extends P25Decoder
{
private final static Logger mLog = LoggerFactory.getLogger(P25DecoderLSM.class);
protected static final float SAMPLE_COUNTER_GAIN = 0.5f;
protected static final float SAMPLE_COUNTER_GAIN = 0.3f;
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
private ComplexFIRFilter2 mBasebandFilter;
@@ -43,6 +40,7 @@ public class P25DecoderLSM extends P25Decoder
protected DQPSKGardnerDemodulator mQPSKDemodulator;
protected P25MessageFramer mMessageFramer;
protected CostasLoop mCostasLoop;
protected AdaptivePLLGainMonitor mPLLGainMonitor;
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
/**
@@ -54,11 +52,12 @@ public class P25DecoderLSM extends P25Decoder
public P25DecoderLSM(AliasList aliasList)
{
super(4800.0, aliasList);
setSampleRate(48000.0);
setSampleRate(25000.0);
}
/**
* Sets or changes the channel sample rate
*
* @param sampleRate in hertz
*/
public void setSampleRate(double sampleRate)
@@ -68,15 +67,25 @@ public class P25DecoderLSM extends P25Decoder
mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter());
mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate());
mCostasLoop.setTracking(Tracking.NORMAL);
mPLLGainMonitor = new AdaptivePLLGainMonitor(mCostasLoop, this);
mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
mQPSKDemodulator = new DQPSKGardnerDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
//Message framer can issue a symbol-inversion correction request to the PLL when detected
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
//The Costas Loop receives symbol-inversion correction requests when detected.
//The PLL gain monitor receives sync detect/loss signals from the message framer
if(mMessageFramer != null)
{
getDibitBroadcaster().removeListener(mMessageFramer);
mMessageFramer.dispose();
}
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop, mPLLGainMonitor);
mMessageFramer.setListener(getMessageProcessor());
mQPSKDemodulator.setSymbolListener(mMessageFramer);
mMessageFramer.setSampleRate(sampleRate);
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
/**
@@ -97,13 +106,18 @@ public class P25DecoderLSM extends P25Decoder
/**
* Filters the complex buffer and returns a new reusable complex buffer with the filtered contents.
*
* @param reusableComplexBuffer to filter
* @return filtered complex buffer
*/
protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer)
{
//No additional filtering of the channel is currently needed, since the polyphase channelizer
//provides the filtering.
return reusableComplexBuffer;
//User accounting of the incoming buffer is handled by the filter
return mBasebandFilter.filter(reusableComplexBuffer);
// return mBasebandFilter.filter(reusableComplexBuffer);
}
/**
@@ -119,6 +133,10 @@ public class P25DecoderLSM extends P25Decoder
setSampleRate(sourceEvent.getValue().doubleValue());
mCostasLoop.reset();
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//Reset the PLL if/when the tuner PPM changes so that we can re-lock
mCostasLoop.reset();
break;
}
}
@@ -129,6 +147,7 @@ public class P25DecoderLSM extends P25Decoder
public void reset()
{
mCostasLoop.reset();
mPLLGainMonitor.reset();
}
public void dispose()
@@ -163,7 +182,7 @@ public class P25DecoderLSM extends P25Decoder
if(filter == null)
{
filter = FilterFactory.getLowPass(48000, 7250, 8000, 60,
filter = FilterFactory.getLowPass(getSampleRate(), 7250, 8000, 60,
WindowType.HANN, true);
mBasebandFilters.put(getSampleRate(), filter);

View File

@@ -79,7 +79,8 @@ public class P25DecoderLSMInstrumented extends P25DecoderLSM
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
instrumented.setSymbolListener(mMessageFramer);
instrumented.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
public void setComplexSymbolListener(Listener<Complex> listener)

View File

@@ -254,6 +254,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent != null)
{
mCurrentCallEvent.end();
broadcast(mCurrentCallEvent);
}
mCurrentCallEvent = null;
@@ -395,6 +396,10 @@ public class P25DecoderState extends DecoderState
*/
private void processTDU(TDUMessage tdu)
{
if(mChannelType == ChannelType.STANDARD)
{
broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE));
}
}
/**
@@ -432,19 +437,21 @@ public class P25DecoderState extends DecoderState
*/
private void processTDULC(TDULinkControlMessage tdulc)
{
if(mCurrentCallEvent != null)
{
mCurrentCallEvent.end();
broadcast(mCurrentCallEvent);
mCurrentCallEvent = null;
}
if(tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION)
{
broadcast(new DecoderStateEvent(this, Event.END, State.FADE));
if(mCurrentCallEvent != null)
{
mCurrentCallEvent.end();
mCurrentCallEvent = null;
}
}
else
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL));
broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE));
}
switch(tdulc.getOpcode())
@@ -495,22 +502,17 @@ public class P25DecoderState extends DecoderState
/* This opcode as handled at the beginning of the method */
break;
case CHANNEL_IDENTIFIER_UPDATE:
//TODO: does the activity summary need this message?
/* This message is handled by the P25MessageProcessor and
* inserted into any channels needing frequency band info */
break;
case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT:
//TODO: does the activity summary need this message?
/* This message is handled by the P25MessageProcessor and
* inserted into any channels needing frequency band info */
break;
case EXTENDED_FUNCTION_COMMAND:
if(tdulc instanceof ExtendedFunctionCommand)
{
ExtendedFunctionCommand efc =
(ExtendedFunctionCommand)tdulc;
ExtendedFunctionCommand efc = (ExtendedFunctionCommand)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND)
.aliasList(getAliasList())
@@ -606,42 +608,25 @@ public class P25DecoderState extends DecoderState
}
break;
case GROUP_VOICE_CHANNEL_USER:
/* Used on a traffic channel to reflect current call entities */
//Used on a traffic channel to reflect that the channel is currently being used by the
//TO talkgroup, but that channel is currently quiet (ie no voice) */
if(tdulc instanceof GroupVoiceChannelUser)
{
GroupVoiceChannelUser gvcuser =
(GroupVoiceChannelUser)tdulc;
GroupVoiceChannelUser gvcuser = (GroupVoiceChannelUser)tdulc;
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL));
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE));
//The from value should always be "000000" indicating that nobody is currently talking
mFromTalkgroupMonitor.reset();
String from = gvcuser.getSourceAddress();
String to = gvcuser.getGroupAddress();
mFromTalkgroupMonitor.process(from);
mToTalkgroupMonitor.process(to);
if(mCurrentCallEvent == null)
{
mCurrentCallEvent = new P25CallEvent.Builder(CallEvent.CallEventType.GROUP_CALL)
.aliasList(getAliasList())
.channel(mCurrentChannel)
.details("TDULC GROUP VOICE CHANNEL USER" +
(gvcuser.isEncrypted() ? "ENCRYPTED " : "") +
(gvcuser.isEmergency() ? "EMERGENCY " : ""))
.frequency(mCurrentChannelFrequency)
.from(from)
.to(to)
.build();
broadcast(mCurrentCallEvent);
}
}
break;
case MESSAGE_UPDATE:
if(tdulc instanceof MessageUpdate)
{
MessageUpdate mu =
(MessageUpdate)tdulc;
MessageUpdate mu = (MessageUpdate)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.SDM)
.aliasList(getAliasList())
@@ -748,8 +733,7 @@ public class P25DecoderState extends DecoderState
io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcast sccb =
(io.github.dsheirer.module.decode.p25.message.tdu.lc.SecondaryControlChannelBroadcast)tdulc;
String site = sccb.getRFSubsystemID() + "-" +
sccb.getSiteID();
String site = sccb.getRFSubsystemID() + "-" + sccb.getSiteID();
mSiteAttributeMonitor.process(site);
}
@@ -761,11 +745,9 @@ public class P25DecoderState extends DecoderState
case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT:
if(tdulc instanceof SecondaryControlChannelBroadcastExplicit)
{
SecondaryControlChannelBroadcastExplicit sccb =
(SecondaryControlChannelBroadcastExplicit)tdulc;
SecondaryControlChannelBroadcastExplicit sccb = (SecondaryControlChannelBroadcastExplicit)tdulc;
String site = sccb.getRFSubsystemID() + "-" +
sccb.getSiteID();
String site = sccb.getRFSubsystemID() + "-" + sccb.getSiteID();
mSiteAttributeMonitor.process(site);
}
@@ -777,8 +759,7 @@ public class P25DecoderState extends DecoderState
case STATUS_QUERY:
if(tdulc instanceof StatusQuery)
{
StatusQuery sq =
(StatusQuery)tdulc;
StatusQuery sq = (StatusQuery)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.QUERY)
.aliasList(getAliasList())
@@ -816,8 +797,7 @@ public class P25DecoderState extends DecoderState
case TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
if(tdulc instanceof TelephoneInterconnectAnswerRequest)
{
TelephoneInterconnectAnswerRequest tiar =
(TelephoneInterconnectAnswerRequest)tdulc;
TelephoneInterconnectAnswerRequest tiar = (TelephoneInterconnectAnswerRequest)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE)
.aliasList(getAliasList())
@@ -835,8 +815,7 @@ public class P25DecoderState extends DecoderState
if(mChannelType == ChannelType.STANDARD &&
tdulc instanceof TelephoneInterconnectVoiceChannelUser)
{
TelephoneInterconnectVoiceChannelUser tivcu =
(TelephoneInterconnectVoiceChannelUser)tdulc;
TelephoneInterconnectVoiceChannelUser tivcu = (TelephoneInterconnectVoiceChannelUser)tdulc;
String to = tivcu.getAddress();
@@ -860,8 +839,7 @@ public class P25DecoderState extends DecoderState
case UNIT_AUTHENTICATION_COMMAND:
if(tdulc instanceof UnitAuthenticationCommand)
{
UnitAuthenticationCommand uac =
(UnitAuthenticationCommand)tdulc;
UnitAuthenticationCommand uac = (UnitAuthenticationCommand)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND)
.aliasList(getAliasList())
@@ -877,8 +855,7 @@ public class P25DecoderState extends DecoderState
case UNIT_REGISTRATION_COMMAND:
if(tdulc instanceof UnitRegistrationCommand)
{
UnitRegistrationCommand urc =
(UnitRegistrationCommand)tdulc;
UnitRegistrationCommand urc = (UnitRegistrationCommand)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.COMMAND)
.aliasList(getAliasList())
@@ -894,8 +871,7 @@ public class P25DecoderState extends DecoderState
case UNIT_TO_UNIT_ANSWER_REQUEST:
if(tdulc instanceof UnitToUnitAnswerRequest)
{
UnitToUnitAnswerRequest uuar =
(UnitToUnitAnswerRequest)tdulc;
UnitToUnitAnswerRequest uuar = (UnitToUnitAnswerRequest)tdulc;
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PAGE)
.aliasList(getAliasList())
@@ -913,27 +889,12 @@ public class P25DecoderState extends DecoderState
/* Used on traffic channels to indicate the current call entities */
if(tdulc instanceof UnitToUnitVoiceChannelUser)
{
UnitToUnitVoiceChannelUser uuvcu =
(UnitToUnitVoiceChannelUser)tdulc;
UnitToUnitVoiceChannelUser uuvcu = (UnitToUnitVoiceChannelUser)tdulc;
String from = uuvcu.getSourceAddress();
mFromTalkgroupMonitor.process(from);
mFromTalkgroupMonitor.reset();
String to = uuvcu.getTargetAddress();
mToTalkgroupMonitor.process(to);
if(mCurrentCallEvent != null)
{
mCurrentCallEvent = new P25CallEvent.Builder(CallEvent.CallEventType.UNIT_TO_UNIT_CALL)
.aliasList(getAliasList())
.channel(mCurrentChannel)
.details((uuvcu.isEncrypted() ? "ENCRYPTED " : "") +
(uuvcu.isEmergency() ? "EMERGENCY " : ""))
.frequency(mCurrentChannelFrequency)
.from(from)
.to(to)
.build();
}
}
break;
default:
@@ -1166,9 +1127,17 @@ public class P25DecoderState extends DecoderState
(io.github.dsheirer.module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu;
mFromTalkgroupMonitor.process(gvcuser.getSourceAddress());
mCurrentCallEvent.setFromID(mFromTalkgroupMonitor.getValue());
mToTalkgroupMonitor.process(gvcuser.getGroupAddress());
String to = mToTalkgroupMonitor.getValue();
if(mCurrentCallEvent.getToID() == null || !mCurrentCallEvent.getToID().contentEquals(to))
{
mCurrentCallEvent.setToID(to);
}
if(mChannelType == ChannelType.STANDARD)
{
if(mCurrentCallEvent.getCallEventType() != CallEvent.CallEventType.GROUP_CALL)
@@ -1631,14 +1600,13 @@ public class P25DecoderState extends DecoderState
*/
private void processPDU(PDUMessage pdu)
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
if(pdu instanceof PacketData || pdu instanceof PDUTypeUnknown)
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
}
else if(pdu instanceof PDUConfirmedMessage)
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
PDUConfirmedMessage pduc = (PDUConfirmedMessage)pdu;
switch(pduc.getPDUType())
@@ -1723,6 +1691,11 @@ public class P25DecoderState extends DecoderState
sbTo.append(sud.getUDPDestinationPort());
}
mFromTalkgroupMonitor.reset();
mFromTalkgroupMonitor.process(sbFrom.toString());
mToTalkgroupMonitor.reset();
mToTalkgroupMonitor.process(pduc.getLogicalLinkID());
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.DATA_CALL)
@@ -1750,9 +1723,6 @@ public class P25DecoderState extends DecoderState
}
else
{
/* These are alternate trunking control blocks in PDU format */
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL));
switch(pdu.getOpcode())
{
case GROUP_DATA_CHANNEL_GRANT:

View File

@@ -35,12 +35,9 @@ import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
public class P25MessageFramer implements Listener<Dibit>
{
private final static Logger mLog =
LoggerFactory.getLogger(P25MessageFramer.class);
private final static Logger mLog = LoggerFactory.getLogger(P25MessageFramer.class);
/* Determines the threshold for sync pattern soft matching */
private static final int SYNC_MATCH_THRESHOLD = 2;
@@ -49,9 +46,10 @@ public class P25MessageFramer implements Listener<Dibit>
/* Costas Loop phase lock error correction values. A phase lock error of
* 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An
* error of 180 degrees requires a correction of 1/2 of the symbol rate */
public static final double SYMBOL_FREQUENCY = 2.0d * Math.PI * 4800.0d / 48000.0d;
public static final double PHASE_CORRECTION_90_DEGREES = SYMBOL_FREQUENCY / 4.0d;
public static final double PHASE_CORRECTION_180_DEGREES = SYMBOL_FREQUENCY / 2.0d;
public static final double DEFAULT_SAMPLE_RATE = 25000.0;
public static final double FREQUENCY_PHASE_CORRECTION_90_DEGREES = 1200.0;
public static final double FREQUENCY_PHASE_CORRECTION_180_DEGREES = 2400.0;
public static final int TSBK_BEGIN = 64;
public static final int TSBK_CRC_START = 144;
public static final int TSBK_END = 260;
@@ -71,9 +69,12 @@ public class P25MessageFramer implements Listener<Dibit>
private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector(
FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD);
private MultiSyncPatternMatcher mMatcher = new MultiSyncPatternMatcher(48);
private ArrayList<P25MessageAssembler> mAssemblers =
new ArrayList<P25MessageAssembler>();
private PLLPhaseInversionDetector mInversionDetector90CW;
private PLLPhaseInversionDetector mInversionDetector90CCW;
private PLLPhaseInversionDetector mInversionDetector180;
private MultiSyncPatternMatcher mMatcher;
private P25MessageAssembler mMessageAssembler = new P25MessageAssembler();
private Listener<Message> mListener;
private AliasList mAliasList;
@@ -88,67 +89,72 @@ public class P25MessageFramer implements Listener<Dibit>
* to the message length, and then broadcast that bit buffer to the registered
* listener.
*/
public P25MessageFramer(AliasList aliasList)
public P25MessageFramer(AliasList aliasList, ISyncDetectListener syncDetectListener)
{
mAliasList = aliasList;
//Assign the sync detect listener to the matcher with a sync loss threshold equal to the longest message length
mMatcher = new MultiSyncPatternMatcher(syncDetectListener, DataUnitID.LDU1.getMessageLength(), 48);
mPrimarySyncDetector.setListener(new ISyncDetectListener()
{
@Override
public void syncDetected()
{
for(P25MessageAssembler assembler : mAssemblers)
if(!mMessageAssembler.isActive())
{
if(!assembler.isActive())
{
assembler.setActive(true);
break;
}
mMessageAssembler.setActive(true);
}
}
@Override
public void syncLost()
{
//no-op
}
});
mMatcher.add(mPrimarySyncDetector);
/**
* We use two message assemblers to catch any sync detections, so that
* if we have a false trigger then we still have a chance to catch the
* valid message. If we have two false triggers before the arrival of
* the actual message sync, then the third or subsequent real or false
* sync pattern will produce a debug message indicating no assemblers
* are available..
*/
mAssemblers.add(new P25MessageAssembler());
mAssemblers.add(new P25MessageAssembler());
}
public P25MessageFramer(AliasList aliasList, IPhaseLockedLoop phaseLockedLoop)
public P25MessageFramer(AliasList aliasList, IPhaseLockedLoop phaseLockedLoop, ISyncDetectListener syncDetectListener)
{
this(aliasList);
this(aliasList, syncDetectListener);
if(phaseLockedLoop != null)
{
//Add additional sync pattern detectors to detect when we get 90/180 degree out of phase sync pattern
//detections so that we can apply correction to the phase locked loop
mMatcher.add(new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CCW,
phaseLockedLoop, PHASE_CORRECTION_90_DEGREES));
mInversionDetector90CW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CW,
phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_90_DEGREES);
mMatcher.add(mInversionDetector90CW);
mMatcher.add(new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CW,
phaseLockedLoop, -PHASE_CORRECTION_90_DEGREES));
mInversionDetector90CCW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_90_CCW,
phaseLockedLoop, DEFAULT_SAMPLE_RATE, -FREQUENCY_PHASE_CORRECTION_90_DEGREES);
mMatcher.add(mInversionDetector90CCW);
mMatcher.add(new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_180,
phaseLockedLoop, PHASE_CORRECTION_180_DEGREES));
mInversionDetector180 = new PLLPhaseInversionDetector(FrameSync.P25_PHASE1_ERROR_180,
phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_180_DEGREES);
mMatcher.add(mInversionDetector180);
}
}
/**
* Updates the incoming sample stream sample rate to allow the PLL phase inversion detectors to
* recalculate their internal phase correction values.
*
* @param sampleRate of the incoming sample stream
*/
public void setSampleRate(double sampleRate)
{
mInversionDetector180.setSampleRate(sampleRate);
mInversionDetector90CW.setSampleRate(sampleRate);
mInversionDetector90CCW.setSampleRate(sampleRate);
}
public void dispose()
{
for(P25MessageAssembler a : mAssemblers)
{
a.dispose();
}
mAssemblers.clear();
mMessageAssembler.dispose();
mListener = null;
mAliasList = null;
@@ -172,16 +178,13 @@ public class P25MessageFramer implements Listener<Dibit>
@Override
public void receive(Dibit symbol)
{
for(P25MessageAssembler assembler : mAssemblers)
if(mMessageAssembler.isActive())
{
if(assembler.isActive())
{
assembler.receive(symbol);
mMessageAssembler.receive(symbol);
if(assembler.complete())
{
assembler.reset();
}
if(mMessageAssembler.complete())
{
mMessageAssembler.reset();
}
}
@@ -671,46 +674,71 @@ public class P25MessageFramer implements Listener<Dibit>
}
/**
* Sync pattern detector to listen for costas loop phase lock errors and
* apply a phase correction to the costas loop so that we don't miss any
* messages.
* Sync pattern detector to listen for costas loop phase lock errors and apply a phase correction to the costas
* loop so that we don't miss any messages.
*
* When the costas loop locks with a +/- 90 degree or 180 degree phase
* error, the slicer will incorrectly apply the symbol pattern rotated left
* or right by the phase error. However, we can detect these rotated sync
* patterns and apply immediate phase correction so that message processing
* can continue.
* When the costas loop locks with a +/- 90 degree or 180 degree phase error, the slicer will incorrectly apply
* the symbol pattern rotated left or right by the phase error. However, we can detect these rotated sync patterns
* and apply immediate phase correction so that message processing can continue.
*/
public class PLLPhaseInversionDetector extends SyncDetector
{
private FrameSync mFrameSync;
private IPhaseLockedLoop mPhaseLockedLoop;
private double mCorrection;
private double mSampleRate;
private double mFrequencyCorrection;
private double mPllCorrection;
public PLLPhaseInversionDetector(FrameSync frameSync, IPhaseLockedLoop phaseLockedLoop, double correction)
/**
* Constructs the PLL phase inversion detector.
*
* @param frameSync pattern to monitor for detecting phase inversion errors
* @param phaseLockedLoop to receive phase correction values
* @param sampleRate of the incoming sample stream
* @param frequencyCorrection to apply to the PLL. Examples:
* QPSK +/-90 degree correction: +/-SYMBOL RATE / 4.0
* QPSK 180 degree correction: SYMBOL RATE / 2.0
*/
public PLLPhaseInversionDetector(FrameSync frameSync, IPhaseLockedLoop phaseLockedLoop, double sampleRate,
double frequencyCorrection)
{
super(frameSync.getSync());
mFrameSync = frameSync;
mPhaseLockedLoop = phaseLockedLoop;
mCorrection = correction;
mFrequencyCorrection = frequencyCorrection;
setSampleRate(sampleRate);
setListener(new ISyncDetectListener()
{
@Override
public void syncDetected()
{
mPhaseLockedLoop.correctInversion(mCorrection);
mPhaseLockedLoop.correctInversion(mPllCorrection);
/* Since we detected a sync pattern, start a message assembler */
for(P25MessageAssembler assembler : mAssemblers)
if(!mMessageAssembler.isActive())
{
if(!assembler.isActive())
{
assembler.setActive(true);
break;
}
mMessageAssembler.setActive(true);
}
}
@Override
public void syncLost()
{
//no-op
}
});
}
/**
* Sets or adjusts the sample rate so that the phase inversion correction value can be recalculated.
* @param sampleRate
*/
public void setSampleRate(double sampleRate)
{
mSampleRate = sampleRate;
mPllCorrection = 2.0 * Math.PI * mFrequencyCorrection / mSampleRate;
}
}
}

View File

@@ -1,3 +1,18 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.alias.AliasList;
@@ -13,8 +28,7 @@ import java.util.HashMap;
public class P25MessageProcessor implements Listener<Message>
{
private final static Logger mLog =
LoggerFactory.getLogger( P25MessageProcessor.class );
private final static Logger mLog = LoggerFactory.getLogger( P25MessageProcessor.class );
private Listener<Message> mMessageListener;

View File

@@ -24,9 +24,9 @@ import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
public class PassportDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableBuffer>
public class PassportDecoder extends Decoder implements IReusableBufferListener, Listener<ReusableFloatBuffer>
{
public static final int PASSPORT_MESSAGE_LENGTH = 68;
@@ -52,15 +52,15 @@ public class PassportDecoder extends Decoder implements IReusableBufferListener,
}
@Override
public Listener<ReusableBuffer> getReusableBufferListener()
public Listener<ReusableFloatBuffer> getReusableBufferListener()
{
return mLTRDecoder;
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
mLTRDecoder.receive(reusableBuffer);
mLTRDecoder.receive(reusableFloatBuffer);
}
@Override

View File

@@ -17,7 +17,7 @@ package io.github.dsheirer.module.decode.tait;
import io.github.dsheirer.dsp.afsk.AFSK1200DecoderInstrumented;
import io.github.dsheirer.gui.instrument.chart.IInstrumentedAFSK1200Decoder;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import javafx.beans.property.SimpleIntegerProperty;
/**
@@ -44,9 +44,9 @@ public class Tait1200DecoderInstrumented extends Tait1200Decoder implements IIns
}
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
super.receive(reusableBuffer);
super.receive(reusableFloatBuffer);
bufferCount.setValue(bufferCount.intValue() + 1);
}

View File

@@ -29,8 +29,8 @@ import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
@@ -55,7 +55,7 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
private double mChannelBandwidth;
private double mOutputSampleRate;
private RealResampler mResampler;
private Listener<ReusableBuffer> mResampledReusableBufferListener;
private Listener<ReusableFloatBuffer> mResampledReusableBufferListener;
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
/**
@@ -109,7 +109,7 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
public void receive(ReusableComplexBuffer basebandBuffer)
{
ReusableComplexBuffer filteredBuffer = mIQFilter.filter(basebandBuffer);
ReusableBuffer demodulated = mDemodulator.demodulate(filteredBuffer);
ReusableFloatBuffer demodulated = mDemodulator.demodulate(filteredBuffer);
mResampler.resample(demodulated);
}
@@ -117,12 +117,12 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
* Processes resampled audio buffers produced by the resampler.
* @param resampledAudio buffer to process.
*/
private void processResampledAudio(ReusableBuffer resampledAudio)
private void processResampledAudio(ReusableFloatBuffer resampledAudio)
{
if(mResampledReusableBufferListener != null)
{
ReusableBuffer filteredAudio = mLowPassFilter.filter(resampledAudio);
ReusableBuffer gainApplied = mAGC.process(filteredAudio);
ReusableFloatBuffer filteredAudio = mLowPassFilter.filter(resampledAudio);
ReusableFloatBuffer gainApplied = mAGC.process(filteredAudio);
mResampledReusableBufferListener.receive(gainApplied);
}
else
@@ -172,7 +172,7 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
}
@Override
public void setBufferListener(Listener<ReusableBuffer> listener)
public void setBufferListener(Listener<ReusableFloatBuffer> listener)
{
mResampledReusableBufferListener = listener;
}
@@ -253,10 +253,10 @@ public class AMDemodulatorModule extends Module implements ISourceEventListener,
mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000);
mResampler.setListener(new Listener<ReusableBuffer>()
mResampler.setListener(new Listener<ReusableFloatBuffer>()
{
@Override
public void receive(ReusableBuffer resampledAudioBuffer)
public void receive(ReusableFloatBuffer resampledAudioBuffer)
{
processResampledAudio(resampledAudioBuffer);
}

View File

@@ -26,8 +26,8 @@ import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableFloatBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
@@ -47,7 +47,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
private FMDemodulator mDemodulator = new FMDemodulator();
private RealResampler mResampler;
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
private Listener<ReusableBuffer> mResampledReusableBufferListener;
private Listener<ReusableFloatBuffer> mResampledReusableBufferListener;
private double mChannelBandwidth;
private double mOutputSampleRate;
@@ -106,7 +106,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
}
@Override
public void setBufferListener(Listener<ReusableBuffer> listener)
public void setBufferListener(Listener<ReusableFloatBuffer> listener)
{
mResampledReusableBufferListener = listener;
}
@@ -128,7 +128,7 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
}
ReusableComplexBuffer basebandFilteredBuffer = mIQFilter.filter(reusableComplexBuffer);
ReusableBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer);
ReusableFloatBuffer demodulatedBuffer = mDemodulator.demodulate(basebandFilteredBuffer);
if(mResampler != null)
{
@@ -204,18 +204,18 @@ public class FMDemodulatorModule extends Module implements ISourceEventListener,
mResampler = new RealResampler(sampleRate, mOutputSampleRate, 2000, 1000);
mResampler.setListener(new Listener<ReusableBuffer>()
mResampler.setListener(new Listener<ReusableFloatBuffer>()
{
@Override
public void receive(ReusableBuffer reusableBuffer)
public void receive(ReusableFloatBuffer reusableFloatBuffer)
{
if(mResampledReusableBufferListener != null)
{
mResampledReusableBufferListener.receive(reusableBuffer);
mResampledReusableBufferListener.receive(reusableFloatBuffer);
}
else
{
reusableBuffer.decrementUserCount();
reusableFloatBuffer.decrementUserCount();
}
}
});

View File

@@ -1,19 +1,17 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* sdr-trunk
* Copyright (C) 2014-2018 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 io.github.dsheirer.record;
@@ -22,7 +20,7 @@ import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.record.config.RecordConfiguration;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.JCheckBox;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
@@ -30,121 +28,168 @@ import java.util.List;
public class RecordConfigurationEditor extends Editor<Channel>
{
private static final long serialVersionUID = 1L;
private JCheckBox mAudioRecorder;
private JCheckBox mBasebandRecorder;
private JCheckBox mTrafficBasebandRecorder;
private JCheckBox mBitstreamRecorder;
private JCheckBox mTrafficBitstreamRecorder;
public RecordConfigurationEditor()
{
init();
}
{
init();
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "", "[][grow]" ) );
mAudioRecorder = new JCheckBox( "Audio" );
mAudioRecorder.setEnabled( false );
mAudioRecorder.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( mAudioRecorder );
setLayout(new MigLayout("fill,wrap 2", "", "[][][][grow]"));
mBasebandRecorder = new JCheckBox( "Baseband I/Q" );
mBasebandRecorder.setEnabled( false );
mBasebandRecorder.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( mBasebandRecorder );
mAudioRecorder = new JCheckBox("Audio");
mAudioRecorder.setEnabled(false);
mAudioRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mAudioRecorder, "wrap");
mTrafficBasebandRecorder = new JCheckBox( "Traffic Channel Baseband I/Q" );
mTrafficBasebandRecorder.setEnabled( false );
mTrafficBasebandRecorder.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( mTrafficBasebandRecorder );
mBasebandRecorder = new JCheckBox("Baseband I/Q");
mBasebandRecorder.setEnabled(false);
mBasebandRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mBasebandRecorder);
mTrafficBasebandRecorder = new JCheckBox("Traffic Channel Baseband I/Q");
mTrafficBasebandRecorder.setEnabled(false);
mTrafficBasebandRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mTrafficBasebandRecorder);
mBitstreamRecorder = new JCheckBox("Demodulated Bitstream");
mBitstreamRecorder.setEnabled(false);
mBitstreamRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mBitstreamRecorder);
mTrafficBitstreamRecorder = new JCheckBox("Traffic Channel Bitstream");
mTrafficBitstreamRecorder.setEnabled(false);
mTrafficBitstreamRecorder.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(mTrafficBitstreamRecorder);
}
private void setControlsEnabled( boolean enabled )
private void setControlsEnabled(boolean enabled)
{
if( mAudioRecorder.isEnabled() != enabled )
{
mAudioRecorder.setEnabled( enabled );
}
if( mBasebandRecorder.isEnabled() != enabled )
{
mBasebandRecorder.setEnabled( enabled );
}
if( mTrafficBasebandRecorder.isEnabled() != enabled )
{
mTrafficBasebandRecorder.setEnabled( enabled );
}
if(mAudioRecorder.isEnabled() != enabled)
{
mAudioRecorder.setEnabled(enabled);
}
if(mBasebandRecorder.isEnabled() != enabled)
{
mBasebandRecorder.setEnabled(enabled);
}
if(mTrafficBasebandRecorder.isEnabled() != enabled)
{
mTrafficBasebandRecorder.setEnabled(enabled);
}
if(mBitstreamRecorder.isEnabled() != enabled)
{
mBitstreamRecorder.setEnabled(enabled);
}
if(mTrafficBitstreamRecorder.isEnabled() != enabled)
{
mTrafficBitstreamRecorder.setEnabled(enabled);
}
}
@Override
@Override
public void save()
{
if( hasItem() && isModified() )
{
RecordConfiguration config = getItem().getRecordConfiguration();
config.clearRecorders();
if( mAudioRecorder.isSelected() )
{
config.addRecorder( RecorderType.AUDIO );
}
if( mBasebandRecorder.isSelected() )
{
config.addRecorder( RecorderType.BASEBAND );
}
if( mTrafficBasebandRecorder.isSelected() )
{
config.addRecorder( RecorderType.TRAFFIC_BASEBAND );
}
}
setModified( false );
if(hasItem() && isModified())
{
RecordConfiguration config = getItem().getRecordConfiguration();
config.clearRecorders();
if(mAudioRecorder.isSelected())
{
config.addRecorder(RecorderType.AUDIO);
}
if(mBasebandRecorder.isSelected())
{
config.addRecorder(RecorderType.BASEBAND);
}
if(mTrafficBasebandRecorder.isSelected())
{
config.addRecorder(RecorderType.TRAFFIC_BASEBAND);
}
if(mBitstreamRecorder.isSelected())
{
config.addRecorder(RecorderType.DEMODULATED_BIT_STREAM);
}
if(mTrafficBitstreamRecorder.isSelected())
{
config.addRecorder(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM);
}
}
setModified(false);
}
@Override
public void setItem(Channel channel)
{
super.setItem(channel);
if(hasItem())
{
setControlsEnabled(true);
List<RecorderType> recorders = getItem().getRecordConfiguration().getRecorders();
mAudioRecorder.setSelected(recorders.contains(RecorderType.AUDIO));
mBasebandRecorder.setSelected(recorders.contains(RecorderType.BASEBAND));
mTrafficBasebandRecorder.setSelected(recorders.contains(RecorderType.TRAFFIC_BASEBAND));
mBitstreamRecorder.setSelected(recorders.contains(RecorderType.DEMODULATED_BIT_STREAM));
mTrafficBitstreamRecorder.setSelected(recorders.contains(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM));
}
else
{
setControlsEnabled(false);
}
}
@Override
public void setItem( Channel channel )
{
super.setItem( channel );
if( hasItem() )
{
setControlsEnabled( true );
List<RecorderType> recorders = getItem().getRecordConfiguration().getRecorders();
mAudioRecorder.setSelected( recorders.contains( RecorderType.AUDIO ) );
mBasebandRecorder.setSelected( recorders.contains( RecorderType.BASEBAND ) );
mTrafficBasebandRecorder.setSelected( recorders.contains( RecorderType.TRAFFIC_BASEBAND ) );
}
else
{
setControlsEnabled( false );
}
}
}

Some files were not shown because too many files have changed in this diff Show More