Compare commits

..

74 Commits

Author SHA1 Message Date
Denny
d95c78c0f1 Updates version to 0.4.0 alpha 5 2018-09-23 05:51:28 -04:00
Denny Sheirer
aee5ece8d2 Merge pull request #430 from DSheirer/429-polyphase-channelizer-25khz-channel-rate
429 polyphase channelizer 25khz channel rate
2018-09-23 05:50:18 -04:00
Denny
ba358a7d48 Adjusted costas loop gain values. Updated channel filter values to mitigate adjacent channel interference. 2018-09-23 05:34:41 -04:00
Denny
09b209496b Resolves issue with NPE in 3/4 rate decoder. 2018-09-22 09:19:15 -04:00
Denny Sheirer
cc209683bd Merge pull request #428 from DSheirer/377-npe-in-trellis-decoder
377 NPE in P25 3/4 Rate Trellis Decoder
2018-09-22 02:53:16 -04:00
Denny
2771c28dba Resolves issue with NPE in 3/4 rate decoder. 2018-09-22 02:52:36 -04:00
Denny
d752917073 Resolves #427. Moves the module(s) reset operation to the stop() method
so that the modules are reset after they are shut down, in order to
prepare for processing chain reuse.

Previously, the modules were reset upon startup, but after any traffic
channel metadata had already been transferred to the channel state,
causing the metadata to be lost and allowing initial audio packets to
stream without any metadata and related streaming/monitoring rules.
2018-09-20 04:01:45 -04:00
Denny
28bc02e172 Updates channel state to ensure teardown processing is completed during
a call end and/or during a commanded stop.
2018-09-19 04:25:37 -04:00
Denny
0f6044d48b Updates mutable metadata to reset the buffer overflow flag between uses. 2018-09-18 19:15:06 -04:00
Denny Sheirer
58ecb83e2c Merge pull request #425 from DSheirer/417-channel-overflow-state-processing
417 Channel Overflow State Processing - Part 2
2018-09-16 10:23:15 -04:00
Denny
9e1de922e4 Resolves issue with overflow state processing. Displays the overflow
state in the Message field of the channel metadata table column instead
of displaying it in the channel state column.
2018-09-16 10:22:27 -04:00
Denny Sheirer
84d0b827d8 Merge pull request #422 from DSheirer/421-nbfm-25-khz-channel-bandwidth
421 Add 25 kHz Channel Bandwidth Support to NBFM Decoder
2018-09-16 07:22:38 -04:00
Denny
a17132be0d Updates the NBFM decoder to allow user to select between 12.5 and 25.0
kHz channel bandwidth so that 25 kHz channels can be demodulated correctly.
2018-09-16 07:21:14 -04:00
Denny Sheirer
dae68515a5 Merge pull request #420 from DSheirer/419-heterdyne-channelizer-center-frequency-tuning
419 Heterodyne Channelizer Center Frequency Calculations
2018-09-16 06:04:09 -04:00
Denny
0aaec8ff54 Updates the heterodyne channelizer's center frequency calculator to
only update the center frequency when needed and to place the set of
decoding channels adjacent to the right side of the tuner's center
frequency when the complete set of channels fit into the usable half
bandwidth of the tuner.
2018-09-16 06:02:46 -04:00
Denny Sheirer
001883edc2 Merge pull request #418 from DSheirer/417-channel-overflow-zombie-state
417 Traffic Channels in Buffer Overflow state do not tear down correctly
2018-09-16 05:23:05 -04:00
Denny
335fb51456 Updates buffer overflow handling to occur coincident to other channel
states rather than being a channel state itself.  This was preventing
traffic channels that overflow from being torn down correctly.
2018-09-16 05:21:46 -04:00
Denny Sheirer
e94b6ae172 Merge pull request #416 from DSheirer/415-overflow-state
415 Buffer Overflow State Listener Registration
2018-09-14 06:46:34 -04:00
Denny
785125da4f Correctly implements the buffer overflow listener registration so that
decoding channels can be notified when the internal buffer goes into
and out of overflow state.
2018-09-14 06:45:37 -04:00
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
181 changed files with 5390 additions and 2217 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.5'
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

@@ -20,6 +20,7 @@ package io.github.dsheirer.channel.metadata;
public enum Attribute
{
BUFFER_OVERFLOW,
CHANNEL_CONFIGURATION_NAME,
CHANNEL_CONFIGURATION_SITE,
CHANNEL_CONFIGURATION_SYSTEM,

View File

@@ -116,6 +116,16 @@ public class AttributeChangeRequest<T>
return null;
}
public Boolean getBooleanValue()
{
if(mValue instanceof Boolean)
{
return (Boolean)mValue;
}
return false;
}
/**
* Returns the value as a DecoderType or null if the value is not a DecoderType instance.
*/

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

@@ -1,19 +1,16 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
* 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 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.
* 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/>
* 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.channel.metadata;
@@ -21,9 +18,11 @@ package io.github.dsheirer.channel.metadata;
import io.github.dsheirer.channel.state.State;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.EventQueue;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
@@ -32,6 +31,8 @@ import java.util.Map;
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent>
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelMetadataModel.class);
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" );
public static final int COLUMN_STATE = 0;
@@ -45,7 +46,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener
public static final int COLUMN_CONFIGURATION_NAME = 8;
public static final int COLUMN_MESSAGE = 9;
private static final String[] COLUMNS = {"State", "Decoder", "Channel", "Frequency", "Primary From", "Primary To",
private static final String[] COLUMNS = {"Status", "Decoder", "Channel", "Frequency", "Primary From", "Primary To",
"Secondary From", "Secondary To", "Channel Name", "Message"};
private List<MutableMetadata> mChannelMetadata = new ArrayList();
@@ -184,7 +185,11 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener
return null;
case COLUMN_MESSAGE:
if(metadata.hasMessageType() | metadata.hasMessage())
if(metadata.isBufferOverflow())
{
return "**OVERFLOW**";
}
else if(metadata.hasMessageType() || metadata.hasMessage())
{
StringBuilder sb = new StringBuilder();
if(metadata.hasMessageType())
@@ -248,6 +253,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener
case CHANNEL_STATE:
fireTableCellUpdated(rowIndex, COLUMN_STATE);
break;
case BUFFER_OVERFLOW:
case MESSAGE:
case MESSAGE_TYPE:
fireTableCellUpdated(rowIndex, COLUMN_MESSAGE);

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,32 @@ 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.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 +214,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

@@ -41,6 +41,7 @@ public class Metadata
protected boolean mUpdated;
protected DecoderType mPrimaryDecoderType;
protected boolean mSelected;
protected boolean mBufferOverflow;
protected State mState = State.IDLE;
protected String mChannelConfigurationSystem;
protected String mChannelConfigurationSite;
@@ -173,6 +174,14 @@ public class Metadata
return mSelected;
}
/**
* Indicates if the channel for this metadata is in a buffer overflow state.
*/
public boolean isBufferOverflow()
{
return mBufferOverflow;
}
/**
* List of de-duplicated broadcast channels aggregated from the primary and secondary TO/FROM aliases.
*/

View File

@@ -80,6 +80,10 @@ public class MutableMetadata extends Metadata implements Listener<AttributeChang
{
switch(request.getAttribute())
{
case BUFFER_OVERFLOW:
mBufferOverflow = request.getBooleanValue();
broadcast(Attribute.BUFFER_OVERFLOW);
break;
case CHANNEL_CONFIGURATION_SYSTEM:
mChannelConfigurationSystem = request.getStringValue();
broadcast(Attribute.CHANNEL_CONFIGURATION_SYSTEM);
@@ -164,6 +168,7 @@ public class MutableMetadata extends Metadata implements Listener<AttributeChang
public void resetTemporalAttributes()
{
mDoNotRecord = null;
mBufferOverflow = false;
if(hasMessage())
{

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;
@@ -135,18 +135,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
@Override
public void stop()
{
mMutableMetadata.resetAllAttributes();
mTrafficChannelEndListener = null;
if(mTrafficChannelCallEvent != null)
{
mTrafficChannelCallEvent.end();
broadcast(mTrafficChannelCallEvent);
}
mTrafficChannelCallEvent = null;
processTeardownState();
mSquelchLocked = false;
}
@@ -160,12 +149,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 +244,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 +292,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())
@@ -328,8 +333,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
break;
}
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
}
}
@@ -352,8 +356,19 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{
mSourceOverflow = overflow;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
mMutableMetadata.receive(new AttributeChangeRequest<Boolean>(Attribute.BUFFER_OVERFLOW, mSourceOverflow));
}
/**
* Indicates if this channel's sample buffer is in overflow state, meaning that the inbound sample
* stream is not being processed fast enough and samples are being thrown away until the processing can
* catch up.
*
* @return true if the channel is in overflow state.
*/
public boolean isOverflow()
{
return mSourceOverflow;
}
/**
@@ -392,9 +407,13 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
if(mTrafficChannelEndListener != null)
if(mTrafficChannelEndListener != null && mTrafficChannelCallEvent != null)
{
mTrafficChannelCallEvent.end();
broadcast(mTrafficChannelCallEvent);
mTrafficChannelEndListener.callEnd(mTrafficChannelCallEvent.getChannel());
mTrafficChannelEndListener = null;
mTrafficChannelCallEvent = null;
}
}
@@ -516,18 +535,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,62 +24,109 @@ 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;
}
},
OVERFLOW("OVERFLOW")
/**
* Indicates the channel is idle and not actively decoding any messages
*/
IDLE("IDLE")
{
@Override
public boolean canChangeTo(State state)
{
return false;
return state != TEARDOWN &&
state != RESET;
}
},
/**
* Indicates that the channel has been reset and is ready to use for a new decoding session.
*/
RESET("RESET")
{
@Override
@@ -88,6 +135,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 +150,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,8 +160,25 @@ 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;
}
@Override
public String toString()
{
return mDisplayValue;
}
}

View File

@@ -186,7 +186,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
if(frequency > 0)
{
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(frequency,
mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
mDecodeConfiguration.getChannelSpecification().getBandwidth()));
if(channel != null)
{

View File

@@ -489,14 +489,14 @@ public class Channel extends Configuration implements Listener<SourceEvent>
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
mDecodeConfiguration.getChannelSpecification().getBandwidth());
}
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
{
SourceConfigRecording config = (SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
mDecodeConfiguration.getChannelSpecification().getBandwidth());
}
}

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,7 @@ public class ChannelProcessingManager implements ChannelEventListener
try
{
source = mSourceManager.getSource(channel.getSourceConfiguration(),
channel.getDecodeConfiguration().getDecoderType().getChannelBandwidth());
channel.getDecodeConfiguration().getChannelSpecification());
}
catch(SourceException se)
{
@@ -308,6 +309,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

@@ -63,7 +63,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
{
private final static Logger mLog = LoggerFactory.getLogger(ComplexPolyphaseChannelizerM2.class);
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 12500;
private static final int DEFAULT_MINIMUM_CHANNEL_BANDWIDTH = 25000;
//Sized at 152 buffers a second where max = 5 seconds and reset = 2 seconds worth of buffers
private IFFTProcessor mIFFTProcessor = new IFFTProcessor((5 * 152), (2 * 152));

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

@@ -29,12 +29,12 @@ import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
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;
@@ -63,7 +63,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class PolyphaseChannelManager implements ISourceEventProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final double MINIMUM_CHANNEL_BANDWIDTH = 12500.0;
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
private static final double CHANNEL_OVERSAMPLING = 2.0;
private static final int POLYPHASE_CHANNELIZER_TAPS_PER_CHANNEL = 9;
private static final int POLYPHASE_SYNTHESIZER_TAPS_PER_CHANNEL = 9;
@@ -142,7 +142,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
* @param tunerChannel specifying center frequency and bandwidth.
* @return source or null.
*/
public TunerChannelSource getChannel(TunerChannel tunerChannel)
public TunerChannelSource getChannel(TunerChannel tunerChannel, ChannelSpecification channelSpecification)
{
PolyphaseChannelSource channelSource = null;
@@ -156,10 +156,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
{
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency);
try
{
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
mChannelSources.add(channelSource);
mChannelSources.add(channelSource);
}
catch(FilterDesignException fde)
{
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
}
}
}
catch(IllegalArgumentException iae)
@@ -172,36 +179,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 +190,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 +293,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 +483,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 +535,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

@@ -16,22 +16,35 @@
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.dsp.filter.channelizer.output.IPolyphaseChannelOutputProcessor;
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.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class PolyphaseChannelSource extends TunerChannelSource
{
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
private ReusableComplexBufferAssembler mReusableComplexBufferAssembler;
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
private IPolyphaseChannelOutputProcessor mReplacementPolyphaseChannelOutputProcessor;
private long mReplacementFrequency;
private ComplexFIRFilter2 mLowPassFilter;
private static Map<Integer,float[]> sLowPassFilters = new HashMap();
private double mChannelSampleRate;
private long mIndexCenterFrequency;
private long mChannelFrequencyCorrection;
@@ -46,15 +59,22 @@ public class PolyphaseChannelSource extends TunerChannelSource
* @param producerSourceEventListener to receive source event requests (e.g. start/stop sample stream)
* @param channelSampleRate for the downstream sample output
* @param centerFrequency of the incoming polyphase channel(s)
* @throws FilterDesignException if a channel low pass filter can't be designed to the channel specification
*/
public PolyphaseChannelSource(TunerChannel tunerChannel, IPolyphaseChannelOutputProcessor outputProcessor,
Listener<SourceEvent> producerSourceEventListener, double channelSampleRate,
long centerFrequency)
long centerFrequency, ChannelSpecification channelSpecification)
throws FilterDesignException
{
super(producerSourceEventListener, tunerChannel);
mPolyphaseChannelOutputProcessor = outputProcessor;
mChannelSampleRate = channelSampleRate;
mReusableComplexBufferAssembler = new ReusableComplexBufferAssembler(2500, mChannelSampleRate);
float[] filterCoefficients = getLowPassFilter(channelSampleRate, channelSpecification.getPassFrequency(),
channelSpecification.getStopFrequency());
mLowPassFilter = new ComplexFIRFilter2(filterCoefficients, 1.0f);
setFrequency(centerFrequency);
}
@@ -93,14 +113,33 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
public void setPolyphaseChannelOutputProcessor(IPolyphaseChannelOutputProcessor outputProcessor, long frequency)
{
//Lock on the channel output processor to block the channel results processor thread from servicing the
//channel output processor's buffer while we change out the processors.
synchronized(mPolyphaseChannelOutputProcessor)
//If this is the first time, simply assign the output processor
if(mPolyphaseChannelOutputProcessor == null)
{
mPolyphaseChannelOutputProcessor = outputProcessor;
}
//Otherwise, we have to swap out the processor on the sample processing thread
else
{
mReplacementPolyphaseChannelOutputProcessor = outputProcessor;
mReplacementFrequency = frequency;
}
}
/**
* Updates the output processor to use the new output processor provided by the external
* polyphase channel manager. This method should only be executed on the sample processing
* thread, within the processChannelResults() method.
*/
private void swapOutputProcessor()
{
if(mReplacementPolyphaseChannelOutputProcessor != null)
{
IPolyphaseChannelOutputProcessor existingProcessor = mPolyphaseChannelOutputProcessor;
//Swap out the processor so that incoming samples can accumulate in the new channel output processor
mPolyphaseChannelOutputProcessor = outputProcessor;
mPolyphaseChannelOutputProcessor = mReplacementPolyphaseChannelOutputProcessor;
mReplacementPolyphaseChannelOutputProcessor = null;
//Remove the buffer overflow listener and clear any previous buffer overflow state
existingProcessor.setSourceOverflowListener(null);
@@ -109,18 +148,9 @@ public class PolyphaseChannelSource extends TunerChannelSource
//Register to receive buffer overflow notifications so we can push them up through the source chain
mPolyphaseChannelOutputProcessor.setSourceOverflowListener(this);
//Fully process the residual channel results buffer of the previous channel output processor
if(existingProcessor != null)
{
existingProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
//Finally, setup the frequency offset for the output processor and reset the frequency correction value
//to allow consumers to adjust and calculate a new frequency correction value, if needed.
mIndexCenterFrequency = frequency;
mChannelFrequencyCorrection = 0;
//Finally, setup the frequency offset for the output processor.
mIndexCenterFrequency = mReplacementFrequency;
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
}
}
@@ -133,6 +163,11 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
public void receiveChannelResults(ReusableChannelResultsBuffer channelResultsBuffer)
{
if(mReplacementPolyphaseChannelOutputProcessor != null)
{
swapOutputProcessor();
}
mReusableComplexBufferAssembler.updateTimestamp(channelResultsBuffer.getTimestamp());
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsBuffer);
}
@@ -169,7 +204,6 @@ public class PolyphaseChannelSource extends TunerChannelSource
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.dispose();
mPolyphaseChannelOutputProcessor = null;
}
}
@@ -197,7 +231,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
* @param frequency in hertz
*/
@Override
protected void setFrequency(long frequency)
public void setFrequency(long frequency)
{
mIndexCenterFrequency = frequency;
@@ -209,11 +243,7 @@ public class PolyphaseChannelSource extends TunerChannelSource
@Override
protected void processSamples()
{
//Lock on the output processor so that it can't be changed in the middle of processing
synchronized(mPolyphaseChannelOutputProcessor)
{
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
mPolyphaseChannelOutputProcessor.processChannelResults(mReusableComplexBufferAssembler);
}
/**
@@ -232,9 +262,51 @@ public class PolyphaseChannelSource extends TunerChannelSource
*/
private void updateFrequencyOffset()
{
synchronized(mPolyphaseChannelOutputProcessor)
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
@Override
public String toString()
{
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
getTunerChannel().getFrequency();
}
/**
* 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))
{
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
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

@@ -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

@@ -16,29 +16,18 @@
package io.github.dsheirer.dsp.filter.channelizer.output;
import io.github.dsheirer.dsp.filter.channelizer.TwoChannelSynthesizerM2;
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.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.mixer.FS4DownConverter;
import io.github.dsheirer.sample.buffer.ReusableChannelResultsBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBufferAssembler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TwoChannelOutputProcessor extends ChannelOutputProcessor
{
private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private static Map<Integer,float[]> sLowPassFilterKernelMap = new HashMap<>();
// private final static Logger mLog = LoggerFactory.getLogger(TwoChannelOutputProcessor.class);
private TwoChannelSynthesizerM2 mSynthesizer;
private FS4DownConverter mFS4DownConverter = new FS4DownConverter();
private ComplexFIRFilter2 mLowPassFilter;
private int mChannelOffset1;
private int mChannelOffset2;
@@ -49,36 +38,20 @@ 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);
float[] lowPassFilter = sLowPassFilterKernelMap.get((int)sampleRate);
if(lowPassFilter == null)
{
try
{
lowPassFilter = getLowPassFilter(sampleRate);
sLowPassFilterKernelMap.put((int)sampleRate, lowPassFilter);
}
catch(FilterDesignException fde)
{
mLog.error("Couldn't design a low-pass filter for 2-channel synthesizer", fde);
throw new IllegalStateException("Cannot design low-pass filter for 2 channel synthesizer", fde);
}
}
mLowPassFilter = new ComplexFIRFilter2(lowPassFilter, 1.0f);
}
/**
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
*
* @param frequencyOffset in hertz
*/
@Override
@@ -125,7 +98,7 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
public void process(List<ReusableChannelResultsBuffer> channelResultsBuffers,
ReusableComplexBufferAssembler reusableComplexBufferAssembler)
{
for(ReusableChannelResultsBuffer buffer: channelResultsBuffers)
for(ReusableChannelResultsBuffer buffer : channelResultsBuffers)
{
ReusableComplexBuffer channel1 = buffer.getChannel(mChannelOffset1);
ReusableComplexBuffer channel2 = buffer.getChannel(mChannelOffset2);
@@ -139,46 +112,11 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
//Apply offset and frequency correction to center the signal of interest within the synthesized channel
getFrequencyCorrectionMixer().mixComplex(synthesized.getSamples());
ReusableComplexBuffer lowPassFiltered = mLowPassFilter.filter(synthesized);
synthesized.applyGain(getGain());
reusableComplexBufferAssembler.receive(lowPassFiltered);
reusableComplexBufferAssembler.receive(synthesized);
buffer.decrementUserCount();
}
}
/**
* Creates a low-pass filter kernel for the specified sample rate.
* @param sampleRate for the channel
* @return filter
* @throws FilterDesignException if the filter cannot be designed
*/
private static float[] getLowPassFilter(double sampleRate) throws FilterDesignException
{
double cutoff = sampleRate / 4.0;
int passBandCutoff = (int)(cutoff * 0.92);
int stopBandStart = (int)(cutoff * 1.08);
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(sampleRate)
.gridDensity(16)
.oddLength(true)
.passBandCutoff((int)passBandCutoff)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart((int)stopBandStart)
.stopBandAmplitude(0.0)
.stopBandRipple(0.005) //Approximately 94 dB attenuation
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
return designer.getImpulseResponse();
}
throw new FilterDesignException("Couldn't design low-pass filter for sample rate: " + sampleRate +
" Pass End:" + passBandCutoff + " Stop Begin:" + stopBandStart);
}
}

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(25000.0)
.gridDensity(16)
.passBandCutoff(11800)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(12400)
.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(170.0, 2, 4),
LEVEL_3(190.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

@@ -23,6 +23,7 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
@@ -376,10 +377,11 @@ public class ChannelizerViewer2 extends JFrame
mComplexDecibelConverter.addListener(mSpectrumPanel);
TunerChannel tunerChannel = new TunerChannel(frequency, bandwidth);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel);
mSource = mTestTuner.getChannelSourceManager().getSource(tunerChannel, new ChannelSpecification(50000, 12500, 6000, 7000));
if(mSource != null)
{
mLog.debug("Channel: " + mSource.getTunerChannel() + " Rate:" + mSource.getSampleRate());
mSource.setListener(new Listener<ReusableComplexBuffer>()
{
@Override

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);
@@ -540,16 +565,10 @@ public class ProcessingChain implements IChannelEventListener
{
if(mSource != null)
{
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
//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
@@ -627,6 +646,12 @@ public class ProcessingChain implements IChannelEventListener
{
module.stop();
}
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
}
}
@@ -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,19 +1,17 @@
/*******************************************************************************
* 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;
@@ -25,37 +23,28 @@ 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"),
LTR_STANDARD("LTR-Standard", "LTR"),
LTR_NET("LTR-Net", "LTR-Net"),
MPT1327("MPT1327", "MPT1327"),
NBFM("NBFM", "NBFM"),
PASSPORT("Passport", "Passport"),
P25_PHASE1("P25 Phase I", "P25-1"),
//Auxiliary Decoders
FLEETSYNC2("Fleetsync II", "Fleetsync2"),
LJ_1200("LJ1200 173.075", "LJ1200"),
MDC1200("MDC1200", "MDC1200"),
TAIT_1200("Tait 1200", "Tait 1200");
private String mDisplayString;
private String mShortDisplayString;
private String mIconFilename;
private int mChannelBandwidth;
DecoderType( String displayString, String shortDisplayString,
String iconFilename, int bandwidth )
DecoderType(String displayString, String shortDisplayString)
{
mDisplayString = displayString;
mShortDisplayString = shortDisplayString;
mIconFilename = iconFilename;
mChannelBandwidth = bandwidth;
}
/**
@@ -63,68 +52,43 @@ 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;
}
@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,9 +17,11 @@
******************************************************************************/
package io.github.dsheirer.module.decode.am;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigAM extends DecodeConfiguration
{
@@ -27,6 +29,17 @@ public class DecodeConfigAM extends DecodeConfiguration
{
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
3000, 3000.0, 5000.0);
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
public DecoderType getDecoderType()
{

View File

@@ -29,6 +29,7 @@ import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327;
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
import io.github.dsheirer.module.decode.p25.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@@ -58,4 +59,7 @@ public abstract class DecodeConfiguration extends Configuration
@JsonIgnore
public abstract DecoderType getDecoderType();
@JsonIgnore
public abstract ChannelSpecification getChannelSpecification();
}

View File

@@ -30,10 +30,17 @@ import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
@@ -86,64 +93,6 @@ public class MessageActivityPanel extends JPanel implements Listener<ProcessingC
});
}
// @Override
// public void channelChanged(ChannelEvent event)
// {
// boolean changed = false;
//
// if(event.getEvent() == Event.NOTIFICATION_SELECTION_CHANGE &&
// event.getChannel().isSelected())
// {
// if(mDisplayedChannel == null || (mDisplayedChannel != null && mDisplayedChannel != event.getChannel()))
// {
// mDisplayedChannel = event.getChannel();
//
// ProcessingChain chain = mChannelProcessingManager.getProcessingChain(mDisplayedChannel);
//
// if(chain != null)
// {
// mDisplayedModel = chain.getMessageActivityModel();
// }
// else
// {
// mDisplayedModel = EMPTY_MODEL;
// }
//
// mMessageTable.setModel(mDisplayedModel);
//
// if(mDisplayedChannel != null)
// {
// mManagementPanel.enableButtons();
// }
// else
// {
// mManagementPanel.disableButtons();
// }
//
// changed = true;
// }
// }
// else if(event.getEvent() == Event.NOTIFICATION_PROCESSING_STOP || event.getEvent() == Event.REQUEST_DISABLE)
// {
// if(mDisplayedChannel != null && mDisplayedChannel == event.getChannel())
// {
// mDisplayedChannel = null;
//
// mMessageTable.setModel(EMPTY_MODEL);
//
// mManagementPanel.disableButtons();
//
// changed = true;
// }
// }
//
// if(changed)
// {
// revalidate();
// repaint();
// }
// }
public class MessageManagementPanel extends JPanel
{
private static final long serialVersionUID = 1L;

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

@@ -17,10 +17,12 @@
******************************************************************************/
package io.github.dsheirer.module.decode.ltrnet;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigLTRNet extends DecodeConfiguration
{
@@ -46,4 +48,15 @@ public class DecodeConfigLTRNet extends DecodeConfiguration
{
mMessageDirection = direction;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
12500, 6000.0, 7000.0);
}
}

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

@@ -17,10 +17,12 @@
******************************************************************************/
package io.github.dsheirer.module.decode.ltrstandard;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigLTRStandard extends DecodeConfiguration
{
@@ -47,4 +49,15 @@ public class DecodeConfigLTRStandard extends DecodeConfiguration
{
mMessageDirection = direction;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
12500, 6000.0, 7000.0);
}
}

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,9 +17,11 @@
******************************************************************************/
package io.github.dsheirer.module.decode.mpt1327;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
public class DecodeConfigMPT1327 extends DecodeConfiguration
{
@@ -102,4 +104,15 @@ public class DecodeConfigMPT1327 extends DecodeConfiguration
{
mTrafficChannelPoolSize = size;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(25000.0,
12500, 6000.0, 7000.0);
}
}

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);
}

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