Compare commits

...

14 Commits

Author SHA1 Message Date
Denny Sheirer
e99aa3d3b4 #1411 Updates version to 0.5.0 beta1 (#1412)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-15 06:36:22 -05:00
Denny Sheirer
ec162ff84c #1408 Updates USB tuners to enhance transfer buffer error handling and logging. When an error occurs while (re)submitting a transfer buffer to LibUsb to fill with sample data, the buffer is temporarily placed in an error queue. Upon successfully submitting the next transfer buffer, we attempt to resubmit a buffer from the error queue, so that we wait until the device can accept buffers before trying to resubmit. If all transfer buffers are exhausted because they are in the error queue, then the tuner gets shutdown with an error state ... there's really no way to recover at this point. (#1409)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-15 06:16:37 -05:00
Denny Sheirer
f0db2865f2 #1397 Resolves issue where decoding channels go IDLE after period of time. Isolated the cause to a multi-threading issue where the polyphase channel manager receives notification that the tuner center frequency has changed and sets a flag to update each of the channels. This happens concurrent with a previous tuner center frequency change where the flag gets set and the second thread is updating the output processors with the previous frequency change and then unsets the frequency change flag, completely missing the second/latest frequency change update and causing the channels to all tune-out their desired signals as the tuner center frequency changes and the output channels continue mixing to the previous frequency. (#1407)
Identified and resolved issue where a 2-stream polyphase synthesizer was being updated with an incorrectly designed synthesis filter which was causing massive droop on the second half of the reconstructed channel.  Oddly enough, this didn't seem to impact the P25 decoders, but it may have impacted other decoding.

Identified and resolved issue with concurrent modification of the modules list in the processing chain associated with recent addition of the channel FFT view in the channel power tab.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-15 04:12:42 -05:00
Denny Sheirer
f97b2150b9 #1397 Adds channel fft frequency overlay, cursor readout, pll tracking indicator, and channel bandwidth lines. (#1406)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-13 04:28:00 -05:00
Denny Sheirer
8ce8aaddf9 #1399 Resolves device ID parsing issue for Motorola ARS Device Registration. (#1400)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-06 04:17:57 -05:00
Denny Sheirer
e4a85a4ed7 #1386 Updates version to 0.5.0 (final) (#1387)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-25 07:11:25 -05:00
Denny Sheirer
f61a415b43 #1383 Updates tuner editor to resolve sequencing issue where a recently enabled tuner was having its enabled state set to true and then enabling the tuner, which caused the tuner editor to mis-configure the tuner editor panel. (#1385)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-25 07:03:28 -05:00
Denny Sheirer
29ed3b4553 #1287 Disables event panel filtering until the feature is fully implemented with #1368. The current partial implementation is preventing valid events from displaying for protocols like DMR and likely others. (#1384)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-25 06:45:50 -05:00
Denny Sheirer
a87309e792 #1378 Resolves potential error where tuner shutdown (because of USB I/O error) hangs and causes application to run out of memory. This patch doesn't resolve the original issue with the USB I/O error, but it does ensure that the tuner shutdown which is happening on the USB processing thread, can complete in a timely fashion so that the application doesn't (potentially) crash from out of memory. (#1382)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-24 09:10:17 -05:00
Denny Sheirer
ddb5b5ef87 #1380 Improves Icecast streaming error handling and preserves streaming error messages for broadcast state to display in streaming status panel. Resolves issue with streaming status panel not showing icons and stream type. (#1381)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-24 07:48:04 -05:00
Denny Sheirer
7a58bf85b6 #1312 Resolves issue with radio reference editor attempting to access county info for a site that has county ID set to 0 (ie no county specified). (#1379)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-24 04:45:21 -05:00
Doug Hoffman
8707eb2c88 Correctly identify padded MP3 frames (#1375) 2022-12-23 06:12:44 -05:00
Denny Sheirer
08499ac73f #1237 Ensure MP3 input sample rate is supported by LAME encoder settings. (#1376)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-12-23 06:06:10 -05:00
Dennis Sheirer
70170ff70f Revert "Disable resampling if requested sample rate not supported"
This reverts commit 7de28519a1.
2022-12-18 08:29:35 -05:00
63 changed files with 2452 additions and 894 deletions

View File

@@ -28,7 +28,7 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.5.0-beta7'
version = '0.5.1-beta1'
sourceCompatibility = '17'
sourceSets {

View File

@@ -25,19 +25,15 @@ import io.github.dsheirer.audio.convert.MP3FrameTools;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.util.ThreadPool;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
{
@@ -255,7 +251,6 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
public boolean canConnect()
{
BroadcastState state = getBroadcastState();
return state != BroadcastState.CONNECTED && !state.isErrorState();
}

View File

@@ -26,13 +26,6 @@ import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.ThreadPool;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
@@ -48,6 +41,12 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
{
@@ -58,7 +57,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
public static final int COLUMN_SERVER_ICON = 0;
public static final int COLUMN_BROADCAST_SERVER_TYPE = 0;
public static final int COLUMN_STREAM_NAME = 1;
public static final int COLUMN_BROADCASTER_STATUS = 2;
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
@@ -67,7 +66,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
public static final String[] COLUMN_NAMES = new String[]
{"Streaming", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
@@ -575,15 +574,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
{
switch(columnIndex)
{
case COLUMN_SERVER_ICON:
String iconPath = configuredBroadcast.getBroadcastConfiguration()
.getBroadcastServerType().getIconPath();
if(iconPath != null && mIconModel != null)
{
return mIconModel.getScaledIcon(iconPath, 14);
}
break;
case COLUMN_BROADCAST_SERVER_TYPE:
return configuredBroadcast.getBroadcastServerType();
case COLUMN_STREAM_NAME:
return configuredBroadcast.getBroadcastConfiguration().getName();
case COLUMN_BROADCASTER_STATUS:
@@ -653,8 +645,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
case COLUMN_BROADCASTER_STREAMED_COUNT:
case COLUMN_BROADCASTER_ERROR_COUNT:
return Integer.class;
case COLUMN_SERVER_ICON:
return ImageIcon.class;
case COLUMN_BROADCAST_SERVER_TYPE:
return BroadcastServerType.class;
case COLUMN_STREAM_NAME:
default:
return String.class;

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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
@@ -14,8 +14,8 @@
*
* 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.audio.broadcast;
public enum BroadcastServerType
@@ -23,17 +23,17 @@ public enum BroadcastServerType
/**
* Broadcastify feeds (ie streaming) service
*/
BROADCASTIFY("Broadcastify Feed", "/images/broadcastify.png"), //Icecast Server 2.3.2
BROADCASTIFY("Broadcastify Feed", "images/broadcastify.png"), //Icecast Server 2.3.2
/**
* Broadcastify calls - completed audio recording push service
*/
BROADCASTIFY_CALL("Broadcastify Call", "/images/broadcastify.png"),
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
ICECAST_HTTP("Icecast 2 (v2.4+)", "/images/icecast.png"),
ICECAST_TCP("Icecast (v2.3)", "/images/icecast.png"),
SHOUTCAST_V1("Shoutcast v1.x", "/images/shoutcast.png"),
SHOUTCAST_V2("Shoutcast v2.x", "/images/shoutcast.png"),
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
UNKNOWN("Unknown", null);
private String mLabel;

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2022 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
@@ -14,8 +14,8 @@
*
* 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.audio.broadcast;
public enum BroadcastState
@@ -68,7 +68,7 @@ public enum BroadcastState
/**
* Specified mount point is already in use
*/
MOUNT_POINT_IN_USE("Mount Point In Use", false),
MOUNT_POINT_IN_USE("Mount Point In Use", true),
/**
* Network is unavailable or the server address cannot be resolved
@@ -103,10 +103,10 @@ public enum BroadcastState
private String mLabel;
private boolean mErrorState;
private BroadcastState(String label, boolean connected)
BroadcastState(String label, boolean errorState)
{
mLabel = label;
mErrorState = connected;
mErrorState = errorState;
}
public String toString()

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2022 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
@@ -15,23 +14,29 @@
*
* 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.audio.broadcast;
import io.github.dsheirer.icon.Icon;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import java.awt.Color;
import java.awt.Component;
import net.miginfocom.swing.MigLayout;
import javax.swing.ImageIcon;
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.Color;
import java.awt.Component;
/**
* Table of broadcast streams and statuses.
*/
public class BroadcastStatusPanel extends JPanel
{
private JTable mTable;
@@ -41,6 +46,12 @@ public class BroadcastStatusPanel extends JPanel
private UserPreferences mUserPreferences;
private String mPreferenceKey;
/**
* Constructs an instance
* @param broadcastModel to access the streams
* @param userPreferences for configuring the panel
* @param preferenceKey to store column preferences for this panel.
*/
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
{
mBroadcastModel = broadcastModel;
@@ -65,6 +76,7 @@ public class BroadcastStatusPanel extends JPanel
renderer.setHorizontalAlignment(SwingConstants.CENTER);
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCAST_SERVER_TYPE).setCellRenderer(new ServerTypeRenderer());
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
mScrollPane = new JScrollPane(mTable);
@@ -72,6 +84,37 @@ public class BroadcastStatusPanel extends JPanel
add(mScrollPane);
}
public class ServerTypeRenderer extends DefaultTableCellRenderer
{
public ServerTypeRenderer()
{
setOpaque(true);
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if(value instanceof BroadcastServerType broadcastServerType)
{
component.setText(broadcastServerType.toString());
Icon icon = new Icon("empty", broadcastServerType.getIconPath());
ImageIcon imageIcon = icon.getIcon();
ImageIcon scaledIcon = IconModel.getScaledIcon(imageIcon, 13);
component.setIcon(scaledIcon);
}
else
{
component.setText(null);
component.setIcon(null);
}
return component;
}
}
/**
* Custom cell renderer for the broadcast state column.
*/

View File

@@ -20,13 +20,20 @@ package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.future.ConnectFuture;
@@ -42,13 +49,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
@@ -143,8 +143,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
mSocketConnector = new NioSocketConnector();
mSocketConnector.setConnectTimeoutCheckInterval(10000);
// mSocketConnector.getFilterChain().addLast("logger",
// new LoggingFilter(IcecastHTTPAudioBroadcaster.class));
// LoggingFilter loggingFilter = new LoggingFilter(IcecastHTTPAudioBroadcaster.class);
// loggingFilter.setMessageReceivedLogLevel(LogLevel.DEBUG);
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
@@ -315,10 +316,49 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
* OK response and then play like everything is well and good.
*/
if(cause instanceof ArrayIndexOutOfBoundsException &&
((ProtocolDecoderException)throwable).getHexdump().startsWith(HTTP_1_0_OK_HEX_DUMP))
if(cause instanceof ArrayIndexOutOfBoundsException)
{
setBroadcastState(BroadcastState.CONNECTED);
String hexDump = ((ProtocolDecoderException)throwable).getHexdump();
if(hexDump.startsWith(HTTP_1_0_OK_HEX_DUMP))
{
setBroadcastState(BroadcastState.CONNECTED);
}
else
{
HttpDumpMessage message = new HttpDumpMessage(hexDump);
if(message.hasHttpResponseCode())
{
switch(message.getHttpResponseCode())
{
case 403: //Forbidden
if(message.toString().contains("Mountpoint in use"))
{
mLog.error("Stream [" + getStreamName() + "] - unable to connect - mountpoint in use");
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
disconnect();
}
else
{
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
break;
default:
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
}
else
{
mLog.error("HTTP protocol decoder error - message:\n\n" + message);
setBroadcastState(BroadcastState.DISCONNECTED);
disconnect();
}
}
}
else
{
@@ -374,4 +414,97 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
}
}
}
/**
* Class for parsing HTTP response messages from Icecast 2.4.2+
*/
public class HttpDumpMessage
{
private String mHexDump;
private String mMessage;
private int mHttpResponseCode = -1;
public HttpDumpMessage(String hexDump)
{
mHexDump = hexDump;
String[] split = mHexDump.split(" ");
byte[] bytes = new byte[split.length];
int pointer = 0;
for(String a : split)
{
try
{
int value = Integer.parseInt(a, 16);
bytes[pointer++] = (byte) (value & 0xFF);
}
catch(Exception e)
{
pointer++;
}
}
mMessage = new String(bytes);
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
Matcher m = pattern.matcher(mMessage);
if(m.find())
{
try
{
mHttpResponseCode = Integer.parseInt(m.group(1));
}
catch(Exception e)
{
mLog.error("Unable to parse HTTP response code that was matched from message: " + m.group(1));
}
}
}
/**
* Indicates if an HTTP response code was parsed from the message
* @return true if it was parsed.
*/
public boolean hasHttpResponseCode()
{
return mHttpResponseCode != -1;
}
/**
* Response code parsed from the hexdump message
* @return value.
*/
public int getHttpResponseCode()
{
return mHttpResponseCode;
}
@Override
public String toString()
{
return mMessage;
}
}
public static void main(String[] args)
{
String text = "HTTP/1.0 403 Forbidden";
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
Matcher m = pattern.matcher(text);
if(m.find())
{
mLog.info("Matching Group Count: " + m.groupCount());
mLog.info("Match 0: " + m.group(0));
mLog.info("Match 1: " + m.group(1));
}
else
{
mLog.info("No Match");
}
}
}

View File

@@ -21,13 +21,19 @@ package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.UnresolvedAddressException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
@@ -37,14 +43,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.UnresolvedAddressException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
@@ -215,7 +213,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
}
else
{
setBroadcastState(BroadcastState.DISCONNECTED);
//Only set broadcast state to disconnected if it's not already in an error state, to prevent a restart. We
//want to preserve the error state that got us here, so the user can see it.
if(!getBroadcastState().isErrorState())
{
setBroadcastState(BroadcastState.DISCONNECTED);
}
mLastConnectionAttempt = System.currentTimeMillis();
}
}

View File

@@ -20,6 +20,7 @@
package io.github.dsheirer.audio.convert;
import io.github.dsheirer.audio.AudioFormats;
import java.util.EnumSet;
import javax.sound.sampled.AudioFormat;
@@ -29,8 +30,8 @@ import javax.sound.sampled.AudioFormat;
public enum InputAudioFormat
{
SR_8000(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, "16-Bit 8000 Hz (no resample)"),
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz"),
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz (default)"),
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz (default)"),
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz"),
SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16_BIT_MONO, "16-Bit 44100 Hz"),
SR_32_8000(AudioFormats.PCM_SIGNED_8000_HZ_32_BIT_MONO, "32-Bit 8000 Hz (no resample)"),
@@ -41,6 +42,17 @@ public enum InputAudioFormat
private AudioFormat mAudioFormat;
private String mLabel;
/**
* Set that includes sample rates of 8 or 16 kHz
*/
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16 = EnumSet.of(SR_8000, SR_32_8000, SR_16000, SR_32_16000);
/**
* Set that includes sample rates of 8, 16, or 22 kHz
*/
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16_22 = EnumSet.of(SR_8000, SR_32_8000, SR_16000,
SR_32_16000, SR_22050, SR_32_22050);
/**
* Constructs an instance
* @param audioFormat for the specified sample rate
@@ -56,7 +68,7 @@ public enum InputAudioFormat
*/
public static InputAudioFormat getDefault()
{
return SR_22050;
return SR_16000;
}
/**

View File

@@ -57,19 +57,20 @@ public class MP3AudioConverter implements IAudioConverter
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting, boolean normalizeAudio)
{
mInputAudioFormat = inputAudioFormat;
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
if(inputAudioFormat.getSampleRate() != mEncoder.getEffectiveSampleRate())
{
mLog.warn("MP3 encoder setting '" + setting + "' doesn't support sample rate '" + inputAudioFormat + "', resampling disabled. Increase bitrate or decrease sample rate to fix.");
mInputAudioFormat = InputAudioFormat.SR_8000;
mEncoder = LameFactory.getLameEncoder(mInputAudioFormat, setting);
}
mNormalizeAudio = normalizeAudio;
//Ensure input audio sample rate is supported for the MP3 setting and update as necessary - should never happen.
if((mInputAudioFormat.getSampleRate() - mEncoder.getEffectiveSampleRate()) > 1.0)
{
mInputAudioFormat = InputAudioFormat.getDefault();
mLog.warn("MP3 setting [" + setting + "] does not support requested input audio sample rate [" +
inputAudioFormat + "] - using default sample rate [" + mInputAudioFormat + "]");
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
}
//Resampling is only required if desired input sample rate is not system default of 8kHz
if(inputAudioFormat != InputAudioFormat.SR_8000 && inputAudioFormat != InputAudioFormat.SR_32_8000)
if(mInputAudioFormat != InputAudioFormat.SR_8000 && mInputAudioFormat != InputAudioFormat.SR_32_8000)
{
mResampler = LameFactory.getResampler(inputAudioFormat);
}

View File

@@ -100,7 +100,7 @@ public class MP3Header
*/
public static boolean isPadded(byte[] frame, int offset)
{
return (frame[offset + 2] & 0x01) == 1;
return (frame[offset + 2] & 0x02) == 0x02;
}
/**

View File

@@ -19,6 +19,8 @@
package io.github.dsheirer.audio.convert;
import java.util.Arrays;
import java.util.List;
import net.sourceforge.lame.mp3.Lame;
/**
@@ -79,6 +81,30 @@ public enum MP3Setting
return mSetting;
}
/**
* Input audio formats that are supported by the MP3Setting value.
* @return list of supported input audio sample rates.
*/
public List<InputAudioFormat> getSupportedSampleRates()
{
switch(this)
{
case CBR_16:
{
return InputAudioFormat.SAMPLE_RATES_8_16.stream().toList();
}
case CBR_32:
case VBR_7:
{
return InputAudioFormat.SAMPLE_RATES_8_16_22.stream().toList();
}
default:
{
return Arrays.stream(InputAudioFormat.values()).toList();
}
}
}
@Override
public String toString()
{

View File

@@ -1,32 +1,28 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* 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.edac.CRC;
import java.util.BitSet;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import java.util.BitSet;
public class BinaryMessage extends BitSet
{
private static final long serialVersionUID = 1L;
@@ -1038,6 +1034,51 @@ public class BinaryMessage extends BitSet
return buffer;
}
/**
* Loads the hexadecimal text into a binary message
* @param hex text to load from
* @return loaded binary message
*/
public static BinaryMessage loadHex(String hex)
{
if(!hex.matches("[0-9A-F]*"))
{
throw new IllegalArgumentException("Message must contain only 0-9 and A-F hexadecimal characters");
}
int length = hex.length() / 2;
if(length * 2 < hex.length())
{
length++;
}
BinaryMessage buffer = new BinaryMessage(length * 8);
int bufferOffset = 0;
for(int x = 0; x < hex.length(); x += 2)
{
int endIndex = x + 2;
if(endIndex > hex.length())
{
endIndex--;
}
String rawHex = hex.substring(x, endIndex);
if(rawHex.length() == 1)
{
rawHex = rawHex + "0";
}
int value = Integer.parseInt(rawHex, 16);
buffer.setByte(bufferOffset, (byte)(value & 0xFF));
bufferOffset += 8;
}
return buffer;
}
/**
* Left rotates the bits between start and end indices, number of places.
*/

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -15,7 +14,7 @@
*
* 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;
@@ -28,10 +27,11 @@ import io.github.dsheirer.module.decode.event.DecodeEventPanel;
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import java.awt.Color;
import net.miginfocom.swing.MigLayout;
import javax.swing.JPanel;
import java.awt.Color;
public class NowPlayingPanel extends JPanel
{
@@ -45,13 +45,14 @@ public class NowPlayingPanel extends JPanel
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
* messages, events, and spectral view.
*/
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences)
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences,
SettingsManager settingsManager)
{
mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager());
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
mChannelPowerPanel = new ChannelPowerPanel(playlistManager);
mChannelPowerPanel = new ChannelPowerPanel(playlistManager, settingsManager);
init();
}
@@ -68,6 +69,10 @@ public class NowPlayingPanel extends JPanel
tabbedPane.setFont(this.getFont());
tabbedPane.setForeground(Color.BLACK);
//Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing
tabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(tabbedPane.getSelectedIndex() == tabbedPane
.indexOfComponent(mChannelPowerPanel)));
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
splitPane.setShowGripper(true);
splitPane.add(mChannelMetadataPanel);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -32,6 +32,8 @@ import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.ui.TunerViewPanel;
import java.awt.Color;
import java.awt.Dimension;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
@@ -41,8 +43,6 @@ import org.slf4j.LoggerFactory;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
public class ControllerPanel extends JPanel
{
@@ -63,7 +63,7 @@ public class ControllerPanel extends JPanel
{
mAudioPanel = new AudioPanel(iconModel, userPreferences, settingsManager, audioPlaybackManager,
playlistManager.getAliasModel());
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences);
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences, settingsManager);
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconModel, settingsManager);
mTunerManagerPanel = new TunerViewPanel(tunerManager, userPreferences);

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -15,7 +14,7 @@
*
* 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;
@@ -33,7 +32,7 @@ public class ChannelSelectionManager implements Listener<ChannelEvent>
public ChannelSelectionManager(ChannelModel channelModel)
{
mChannelEventListener = channelModel;
channelModel.addListener(this::receive);
channelModel.addListener(ChannelSelectionManager.this);
}
@Override

View File

@@ -1,31 +1,34 @@
/*******************************************************************************
* *********************************************************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.dsp.filter.channelizer;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class ChannelCalculator
{
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class);
/**
@@ -546,13 +549,13 @@ public class ChannelCalculator
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Channel Calculator Settings");
sb.append(" Channels:").append(getChannelCount());
sb.append(" Sample Rate:").append(getSampleRate());
sb.append(" Channel Bandwidth:").append(getChannelBandwidth());
sb.append(" Channel Rate:").append(getChannelSampleRate());
sb.append(" Min:").append(getMinimumFrequency());
sb.append(" Max:").append(getMaximumFrequency());
sb.append("Channel Calculator | Tuner SR:").append(FREQUENCY_FORMAT.format(getSampleRate() / 1E6D));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(getCenterFrequency() / 1E6D));
sb.append(" MIN:").append(FREQUENCY_FORMAT.format(getMinimumFrequency() / 1E6D));
sb.append(" MAX:").append(FREQUENCY_FORMAT.format(getMaximumFrequency() / 1E6D));
sb.append(" | Channel COUNT:").append(getChannelCount());
sb.append(" BW:").append(FREQUENCY_FORMAT.format(getChannelBandwidth() / 1E6D));
sb.append(" SR:").append(FREQUENCY_FORMAT.format(getChannelSampleRate() / 1E6D));
return sb.toString();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -35,6 +35,7 @@ import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.util.Dispatcher;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -43,6 +44,7 @@ import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,6 +67,7 @@ import org.slf4j.LoggerFactory;
*/
public class PolyphaseChannelManager implements ISourceEventProcessor
{
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
private static final double CHANNEL_OVERSAMPLING = 2.0;
@@ -123,6 +126,34 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
this(tunerController, tunerController.getFrequency(), tunerController.getSampleRate());
}
/**
* Provides a description of the state of this manager.
*/
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel Manager Providing [").append(mChannelSources.size()).append("] Channels");
sb.append("\n\t").append(mChannelCalculator);
for(PolyphaseChannelSource pcs: mChannelSources)
{
List<Integer> indexes = pcs.getOutputProcessorIndexes();
double sampleRate = pcs.getSampleRate();
long indexCenterFrequency = pcs.getIndexCenterFrequency();
long appliedFrequencyOffset = pcs.getFrequencyOffset();
long requestedCenterFrequency = pcs.getFrequency();
sb.append("\n\tPolyphase | Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
sb.append(" BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
sb.append(" | Channel CF: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
sb.append(" REQUESTED CF: ").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
sb.append(" MIXER:").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
sb.append(" | Polyphase Indices: ").append(indexes);
}
return sb.toString();
}
public void stopAllChannels()
{
mRunning = false;
@@ -278,7 +309,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
//no-op
break;
default:
mLog.info("Unrecognized source event: " + sourceEvent.toString());
mLog.info("Unrecognized source event: " + sourceEvent);
break;
}
}
@@ -427,9 +458,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
source.dispose();
}
break;
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);
@@ -445,16 +473,18 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
/**
* Processes the incoming buffer stream from the provider and transfers the buffers to the polyphase channelizer.
*
* This monitor incorporates a queue for receiving source events that affect configuration of the channelizer so
* that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer
* and/or the channel sources and output processors.
* This monitor incorporates a source event handler that queues a center frequency update so that it can be
* handled on the buffer processing thread, avoiding having to lock on the output processor thread. Since we
* anticipate that these two threads will contend for access to this update required flag, we use an update lock
* to protect access to the flag.
*/
public class NativeBufferReceiver implements Listener<INativeBuffer>
{
private boolean mOutputProcessorUpdateRequired = false;
private ReentrantLock mUpdateFlagLock = new ReentrantLock();
/**
* Queues the source event for deferred execution on the buffer processing thread.
* Processes tuner center frequency change source events to flag when output processors need updating.
* @param event that affects configuration of the channelizer (frequency or sample rate change events)
*/
public void receive(SourceEvent event)
@@ -463,14 +493,31 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(mChannelCalculator.getCenterFrequency() != frequency)
{
mChannelCalculator.setCenterFrequency(frequency);
mOutputProcessorUpdateRequired = true;
mUpdateFlagLock.lock();
try
{
//Update the channel calculator frequency so that it's ready when the output processor update occurs
mChannelCalculator.setCenterFrequency(frequency);
mOutputProcessorUpdateRequired = true;
}
finally
{
mUpdateFlagLock.unlock();
}
}
}
/**
* Process native buffer streams and update polyphase output channels when the parent tuner center
* frequency changes.
* @param nativeBuffer of sample to process.
*/
@Override
public void receive(INativeBuffer nativeBuffer)
{
mUpdateFlagLock.lock();
try
{
if(mOutputProcessorUpdateRequired)
@@ -478,21 +525,28 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
updateOutputProcessors();
mOutputProcessorUpdateRequired = false;
}
}
finally
{
mUpdateFlagLock.unlock();
}
if(mPolyphaseChannelizer != null)
if(mPolyphaseChannelizer != null)
{
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
while(iterator.hasNext())
{
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
while(iterator.hasNext())
try
{
mPolyphaseChannelizer.receive(iterator.next());
}
catch(Throwable throwable)
{
mLog.error("Error", throwable);
}
}
}
catch(Throwable throwable)
{
mLog.error("Error", throwable);
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -28,6 +28,7 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
@@ -45,9 +46,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
private double mChannelSampleRate;
private long mIndexCenterFrequency;
private long mChannelFrequencyCorrection;
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
private double mTunerSampleRate;
private double mTunerCenterFrequency;
/**
* Constructs an instance
*
@@ -66,6 +71,41 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
updateOutputProcessor(channelCalculator, filterManager);
}
/**
* Description of the state or configuration of the output processor.
*/
public String getStateDescription()
{
return mPolyphaseChannelOutputProcessor.getStateDescription();
}
/**
* Current output processor indexes.
* @return indexes
*/
public List<Integer> getOutputProcessorIndexes()
{
return mOutputProcessorIndexes;
}
/**
* Sample rate or bandwidth of the tuner providing input to the channelizer.
* @return sample rate
*/
public double getTunerSampleRate()
{
return mTunerSampleRate;
}
/**
* Center tuned frequency of the tuner providing input to the channelizer.
* @return center frequency
*/
public double getTunerCenterFrequency()
{
return mTunerCenterFrequency;
}
@Override
public void start()
{
@@ -125,6 +165,11 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
//calculator will throw an IllegalArgException ... handled below
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
mOutputProcessorIndexes.clear();
mOutputProcessorIndexes.addAll(indexes);
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
mTunerSampleRate = channelCalculator.getSampleRate();
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
@@ -137,7 +182,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
{
try
{
float[] filter = filterManager.getFilter(channelCalculator.getSampleRate(),
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
channelCalculator.getChannelBandwidth(), indexes.size());
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
}
@@ -263,59 +308,32 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
}
}
@Override
public long getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Adjusts the frequency correction value that is being applied to the channelized output stream by the
* polyphase channel output processor.
*
* @param value to apply for frequency correction in hertz
*/
protected void setChannelFrequencyCorrection(long value)
{
mChannelFrequencyCorrection = value;
updateFrequencyOffset();
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
}
/**
* Sets the center frequency for this channel.frequency for the incoming sample stream channel results and resets frequency correction to zero.
* Sets the center frequency for this channel.
* @param frequency in hertz
*/
@Override
public void setFrequency(long frequency)
{
mIndexCenterFrequency = frequency;
}
//Set frequency correction to zero to trigger an update to the mixer and allow downstream monitors to
//recalculate the frequency error correction again
setChannelFrequencyCorrection(0);
/**
* Center frequency from the incoming index channel(s).
* @return frequency in hertz
*/
public long getIndexCenterFrequency()
{
return mIndexCenterFrequency;
}
/**
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
* within the channel
*/
private long getFrequencyOffset()
public long getFrequencyOffset()
{
return mIndexCenterFrequency - getTunerChannel().getFrequency() + mChannelFrequencyCorrection;
}
/**
* Updates the frequency offset being applied by the output processor. Calculates the offset using the center
* frequency of the incoming polyphase channel results and the desired output channel frequency adjusted by any
* requested frequency correction value.
*/
private void updateFrequencyOffset()
{
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
return mIndexCenterFrequency - getTunerChannel().getFrequency();
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -19,6 +19,7 @@
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
@@ -65,6 +66,20 @@ public class TwoChannelSynthesizerM2
init(filter);
}
/**
* Description of the state/configuration of this synthesizer to support debugging.
* @return state description.
*/
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Two-Channel Synthesizer");
sb.append("\n\t\t Interleaved Filter: " + Arrays.toString(mIQInterleavedFilter));
sb.append("\n\t\t Serpentine Buffer Length: ").append(mSerpentineDataBuffer.length);
sb.append("\n\t\t Filter Vector Product Length: ").append(mFilterVectorProduct.length);
return sb.toString();
}
/**
* Initializes the synthesizer filter and data buffers for operation.
*
@@ -73,7 +88,6 @@ public class TwoChannelSynthesizerM2
private void init(float[] filter)
{
int tapsPerChannel = (int) FastMath.ceil(filter.length / 2);
mIQInterleavedFilter = getInterleavedFilter(filter, tapsPerChannel);
mSerpentineDataBuffer = new float[mIQInterleavedFilter.length];
mFilterVectorProduct = new float[mIQInterleavedFilter.length];

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -24,6 +24,11 @@ import java.util.List;
public interface IPolyphaseChannelOutputProcessor
{
/**
* Description of the state/configuration of this output processor
*/
String getStateDescription();
/**
* Start processing channel results
*/

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -45,6 +45,16 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
mMixerAssembler.getMixer().setSampleRate(sampleRate);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("One Channel Output Processor");
sb.append("\n\tIndex (doubled) [").append(mChannelOffset).append("]");
sb.append("\n\tMixer Assembler: none");
return sb.toString();
}
@Override
public void setSynthesisFilter(float[] filter)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -42,6 +42,19 @@ public class TwoChannelMixerAssembler extends MixerAssembler
super(gain);
}
/**
* Description of the state/configuration of this mixer assembler.
*/
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Two Channel Mixer Assembler - State Description:");
sb.append("\n\tTwo-Channel Synthesizer: ").append(mTwoChannelSynthesizer.getStateDescription());
sb.append("\n\tMixer Frequency: ").append(getMixer().getFrequency());
sb.append("\n\tGain: ").append(getGain().getGain());
return sb.toString();
}
/**
* Sets the filter coefficients to use for two-channel synthesis
* @param filter

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -49,6 +49,16 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
setSynthesisFilter(filter);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Two Channel Output Processor");
sb.append("\n\tIndices (doubled) 1 [").append(mChannelOffset1).append("] 2 [").append(mChannelOffset2).append("]");
sb.append("\n\tMixer Assembler: ").append(mMixerAssembler.getStateDescription());
return sb.toString();
}
/**
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
*

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -41,7 +41,7 @@ public abstract class ComplexGain
* Gain value that will be applied to complex samples
* @return gain value
*/
protected float getGain()
public float getGain()
{
return mGain;
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -72,6 +72,15 @@ public abstract class ComplexMixer
return mOscillator.hasFrequency();
}
/**
* Current frequency of this oscillator
* @return frequency in hertz
*/
public double getFrequency()
{
return mOscillator.getFrequency();
}
/**
* Sets the sample rate of the underlying oscillator
* @param sampleRate in Hertz

View File

@@ -0,0 +1,117 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.oscillator;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Random;
/**
* Additive white gaussian noise wrapper around a complex oscillator that mixes in gaussian noise to the complex
* samples generated by the underlying complex oscillator.
*/
public class AWGNOscillator implements IComplexOscillator
{
private final Random mRandom = new Random();
private IComplexOscillator mComplexOscillator;
private float mNoiseScale;
/**
* Constructs an instance
* @param complexOscillator to generate samples
* @param noiseScale to apply to generated samples to reduce the power of the noise and increase the SNR.
*/
public AWGNOscillator(IComplexOscillator complexOscillator, float noiseScale)
{
mComplexOscillator = complexOscillator;
mNoiseScale = noiseScale;
}
/**
* Produce the next scaled noise sample.
* @return next scaled noise sample.
*/
private float nextNoise()
{
return (float)mRandom.nextGaussian() * mNoiseScale;
}
@Override
public float[] generate(int sampleCount)
{
float[] generated = mComplexOscillator.generate(sampleCount);
for(int x = 0; x < generated.length; x++)
{
generated[x] += nextNoise();
}
return generated;
}
@Override
public ComplexSamples generateComplexSamples(int sampleCount, long timestamp)
{
ComplexSamples samples = mComplexOscillator.generateComplexSamples(sampleCount, timestamp);
for(int x = 0; x < samples.i().length; x++)
{
samples.i()[x] += nextNoise();
samples.q()[x] += nextNoise();
}
return samples;
}
@Override
public double getFrequency()
{
return mComplexOscillator.getFrequency();
}
@Override
public void setFrequency(double frequency)
{
mComplexOscillator.setFrequency(frequency);
}
@Override
public boolean hasFrequency()
{
return mComplexOscillator.hasFrequency();
}
@Override
public double getSampleRate()
{
return mComplexOscillator.getSampleRate();
}
@Override
public void setSampleRate(double sampleRate)
{
mComplexOscillator.setSampleRate(sampleRate);
}
@Override
public boolean isEnabled()
{
return mComplexOscillator.isEnabled();
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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/>
* * *****************************************************************************
* 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;
@@ -101,7 +98,9 @@ public class FrequencyCorrectionSyncMonitor implements ISyncDetectListener, IFre
@Override
public void processFrequencyError(long frequencyError)
{
//Only rebroadcast the frequency error if the sync count is more than 2
mFeedbackDecoder.broadcast(SourceEvent.pllFrequencyMeasurement(frequencyError));
//Only rebroadcast as a frequency error measurement if the sync count is more than 2
if(mSyncCount > 2)
{
mFeedbackDecoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -20,6 +20,7 @@ package io.github.dsheirer.gui.channelizer;
import io.github.dsheirer.buffer.FloatNativeBuffer;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.settings.SettingsManager;
@@ -36,6 +37,12 @@ import io.github.dsheirer.spectrum.ComplexDftProcessor;
import io.github.dsheirer.spectrum.DFTSize;
import io.github.dsheirer.spectrum.SpectrumPanel;
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,12 +57,6 @@ import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
public class ChannelizerViewer2 extends JFrame
{
@@ -194,9 +195,9 @@ public class ChannelizerViewer2 extends JFrame
getTopChannelArrayPanel().addChannel(tunerChannel);
}
catch(Exception exception)
catch(Exception e1)
{
mLog.error("Can't parse frequency from value: " + value);
mLog.error("Can't parse frequency from value: " + value, e1);
}
}
}
@@ -229,9 +230,9 @@ public class ChannelizerViewer2 extends JFrame
getBottomChannelArrayPanel().addChannel(tunerChannel);
}
catch(Exception exception)
catch(Exception e1)
{
mLog.error("Can't parse frequency from value: " + value);
mLog.error("Can't parse frequency from value: " + value, e1);
}
}
}
@@ -388,6 +389,26 @@ public class ChannelizerViewer2 extends JFrame
mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()));
});
if(mSource instanceof PolyphaseChannelSource pcs)
{ List<Integer> indexes = pcs.getOutputProcessorIndexes();
double sRate = pcs.getSampleRate();
long indexCenterFrequency = pcs.getIndexCenterFrequency();
long appliedFrequencyOffset = pcs.getFrequencyOffset();
long requestedCenterFrequency = pcs.getFrequency();
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel - Bandwidth: ").append(sRate);
sb.append(" Channel/Requested/Offset: ").append(indexCenterFrequency);
sb.append("/").append(requestedCenterFrequency);
sb.append("/").append(appliedFrequencyOffset);
sb.append(" Indexes: ").append(indexes);
sb.append(" Tuner SR:").append(pcs.getTunerSampleRate());
sb.append(" Tuner CF:").append(pcs.getTunerCenterFrequency());
mLog.info(sb.toString());
}
mSource.start();
}
else

View File

@@ -34,6 +34,11 @@ import io.github.dsheirer.rrapi.type.Type;
import io.github.dsheirer.rrapi.type.Voice;
import io.github.dsheirer.service.radioreference.RadioReference;
import io.github.dsheirer.util.ThreadPool;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
@@ -55,12 +60,6 @@ import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Radio Reference editor for trunked radio systems
*/
@@ -293,7 +292,13 @@ public class SystemEditor extends VBox
List<EnrichedSite> enrichedSites = new ArrayList<>();
for(Site site: sites)
{
CountyInfo countyInfo = mRadioReference.getService().getCountyInfo(site.getCountyId());
CountyInfo countyInfo = null;
if(site.getCountyId() > 0)
{
countyInfo = mRadioReference.getService().getCountyInfo(site.getCountyId());
}
enrichedSites.add(new EnrichedSite(site, countyInfo));
}

View File

@@ -1,12 +1,52 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.power;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
import io.github.dsheirer.dsp.squelch.ISquelchConfiguration;
import io.github.dsheirer.gui.control.DbPowerMeter;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamplesToNativeBufferModule;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.HalfBandTunerChannelSource;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.spectrum.ComplexDftProcessor;
import io.github.dsheirer.spectrum.FrequencyOverlayPanel;
import io.github.dsheirer.spectrum.SpectrumPanel;
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
@@ -16,9 +56,12 @@ import org.slf4j.LoggerFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import java.awt.EventQueue;
import java.text.DecimalFormat;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.MouseInputAdapter;
/**
* Display for channel power and squelch details
@@ -27,28 +70,42 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
{
private static final Logger mLog = LoggerFactory.getLogger(ChannelPowerPanel.class);
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private static final String NOT_AVAILABLE = "Not Available";
private PlaylistManager mPlaylistManager;
private ProcessingChain mProcessingChain;
private ComplexSamplesToNativeBufferModule mSampleStreamTapModule = new ComplexSamplesToNativeBufferModule();
private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor();
private DFTResultsConverter mDFTResultsConverter = new ComplexDecibelConverter();
private JLayeredPane mLayeredPanel;
private SpectrumPanel mSpectrumPanel;
private FrequencyOverlayPanel mFrequencyOverlayPanel;
private DbPowerMeter mPowerMeter = new DbPowerMeter();
private PeakMonitor mPeakMonitor = new PeakMonitor(DbPowerMeter.DEFAULT_MINIMUM_POWER);
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
private SpinnerNumberModel mNoiseFloorSpinnerModel;
private JSpinner mNoiseFloorSpinner;
private JLabel mPowerLabel;
private JLabel mPeakLabel;
private JLabel mSquelchLabel;
private JLabel mSquelchValueLabel;
private JLabel mPllFrequencyLabel;
private JLabel mPllFrequencyValueLabel;
private JButton mSquelchUpButton;
private JButton mSquelchDownButton;
private JButton mLogIndexesButton;
private double mSquelchThreshold;
private boolean mPanelVisible = false;
private boolean mDftProcessing = false;
/**
* Constructs an instance.
*/
public ChannelPowerPanel(PlaylistManager playlistManager)
public ChannelPowerPanel(PlaylistManager playlistManager, SettingsManager settingsManager)
{
mPlaylistManager = playlistManager;
setLayout(new MigLayout("", "[][grow,fill]", "[grow,fill]"));
setLayout(new MigLayout("", "[][][][grow,fill]", "[grow,fill]"));
mPowerMeter.setPeakVisible(true);
mPowerMeter.setSquelchThresholdVisible(true);
add(mPowerMeter);
@@ -85,6 +142,184 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
valuePanel.add(mSquelchDownButton);
add(valuePanel);
add(new JSeparator(JSeparator.VERTICAL));
JPanel fftPanel = new JPanel();
fftPanel.setLayout(new MigLayout("insets 0", "[grow,fill]", "[][grow,fill]"));
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new MigLayout("insets 0", "[grow,fill][right][grow,fill][right][grow,fill][]", ""));
labelPanel.add(new JLabel("Channel Spectrum"));
mPllFrequencyLabel = new JLabel("PLL:");
mPllFrequencyLabel.setEnabled(false);
labelPanel.add(mPllFrequencyLabel);
mPllFrequencyValueLabel = new JLabel("0 Hz");
mPllFrequencyValueLabel.setEnabled(false);
labelPanel.add(mPllFrequencyValueLabel);
mNoiseFloorSpinnerModel = new SpinnerNumberModel(18, 8, 36, 1);
mNoiseFloorSpinnerModel.addChangeListener(e -> {
Number number = mNoiseFloorSpinnerModel.getNumber();
mSpectrumPanel.setSampleSize(number.doubleValue());
});
mNoiseFloorSpinner = new JSpinner(mNoiseFloorSpinnerModel);
labelPanel.add(mNoiseFloorSpinner);
labelPanel.add(new JLabel("Noise Floor"));
mLogIndexesButton = new JButton("Log Settings");
mLogIndexesButton.addActionListener(e -> {
if(mProcessingChain != null)
{
Source source = mProcessingChain.getSource();
if(source instanceof PolyphaseChannelSource pcs)
{
List<Integer> indexes = pcs.getOutputProcessorIndexes();
double sampleRate = pcs.getSampleRate();
long indexCenterFrequency = pcs.getIndexCenterFrequency();
long appliedFrequencyOffset = pcs.getFrequencyOffset();
long requestedCenterFrequency = pcs.getFrequency();
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel - BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
sb.append(" Center/Requested/Mixer: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
sb.append("/").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
sb.append("/").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
sb.append(" Polyphase Indexes: ").append(indexes);
sb.append(" Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
mLog.info(sb.toString());
mLog.info("Output Processor: " + pcs.getStateDescription());
}
else if(source instanceof HalfBandTunerChannelSource<?> hbtcs)
{
StringBuilder sb = new StringBuilder();
sb.append("Heterodyne Channel - CF:").append(FREQUENCY_FORMAT.format(hbtcs.getFrequency() / 1E6d));
sb.append(" SR:").append(FREQUENCY_FORMAT.format(hbtcs.getSampleRate() / 1E6d));
sb.append(" Mixer:").append(FREQUENCY_FORMAT.format(hbtcs.getMixerFrequency() / 1E6d));
mLog.info(sb.toString());
}
else
{
mLog.info("Unsupported channel type: " + (source != null ? source.getClass() : " null"));
}
}
});
labelPanel.add(mLogIndexesButton);
fftPanel.add(labelPanel, "wrap");
mFrequencyOverlayPanel = new FrequencyOverlayPanel(settingsManager);
mSpectrumPanel = new SpectrumPanel(settingsManager);
mSpectrumPanel.setSampleSize(18.0);
/**
* The layered pane holds the overlapping spectrum and channel panels
* and manages the sizing of each panel with the resize listener
*/
mLayeredPanel = new JLayeredPane();
mLayeredPanel.addComponentListener(new ResizeListener());
/**
* Create a mouse adapter to handle mouse events over the spectrum
* and waterfall panels
*/
MouseEventProcessor mouser = new MouseEventProcessor();
mFrequencyOverlayPanel.addMouseListener(mouser);
mFrequencyOverlayPanel.addMouseMotionListener(mouser);
mFrequencyOverlayPanel.addMouseWheelListener(mouser);
//Add the spectrum and channel panels to the layered panel
mLayeredPanel.add(mSpectrumPanel, 0, 0);
mLayeredPanel.add(mFrequencyOverlayPanel, 1, 0);
fftPanel.add(mLayeredPanel);
add(fftPanel);
mSampleStreamTapModule.setListener(mComplexDftProcessor);
mComplexDftProcessor.addConverter(mDFTResultsConverter);
mDFTResultsConverter.addListener(mSpectrumPanel);
mSpectrumPanel.clearSpectrum();
}
/**
* Signals this panel to indicate if this panel is visible to turn on the FFT processor when the panel is visible
* and turn off the FFT processor when it's not.
*
* Note: this method is intended to be called by the Swing event thread to ensure that only a single thread is
* invoking either this method, or the receive() method, since there is no thread synchronization between these
* two methods and they each depend on stable access to the mPanelVisible variable.
*
* @param visible true to indicate that this panel is showing/visible.
*/
public void setPanelVisible(boolean visible)
{
mPanelVisible = visible;
updateFFTProcessing();
}
/**
* Updates processing state for the DFT processor. Turns on DFT processing when we have a processing chain and
* when the user has this tab selected and visible. Otherwise, turns off DFT processing.
*/
private void updateFFTProcessing()
{
if(mPanelVisible && mProcessingChain != null)
{
startDftProcessing();
}
else
{
stopDftProcessing();
}
}
/**
* Starts DFT processing
*/
private void startDftProcessing()
{
if(!mDftProcessing)
{
mDftProcessing = true;
mSampleStreamTapModule.setListener(mComplexDftProcessor);
mComplexDftProcessor.start();
}
}
/**
* Stops DFT processing
*/
private void stopDftProcessing()
{
if(mDftProcessing)
{
mSampleStreamTapModule.removeListener();
mComplexDftProcessor.stop();
mSpectrumPanel.clearSpectrum();
mDftProcessing = false;
}
}
/**
* Updates the decoder's PLL frequency on the swing dispatch thread
* @param trackingFrequency that is currently measured.
*/
private void updatePllFrequency(long trackingFrequency)
{
//Note: we flip the sign on the error measurement because the value represents the amount of offset the PLL
//has to apply to move the signal to center/baseband
EventQueue.invokeLater(() -> {
String formattedValue = NumberFormat.getInstance().format(-trackingFrequency);
mPllFrequencyValueLabel.setText(formattedValue + " Hz");
mPllFrequencyValueLabel.setEnabled(true);
mPllFrequencyLabel.setEnabled(true);
});
mFrequencyOverlayPanel.setPllTrackingFrequency(trackingFrequency);
}
/**
@@ -124,6 +359,13 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
mPeakLabel.setText("0");
mPowerLabel.setText("0");
mPllFrequencyLabel.setEnabled(false);
mPllFrequencyValueLabel.setText("0 Hz");
mPllFrequencyValueLabel.setEnabled(false);
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, 0));
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(0));
mFrequencyOverlayPanel.setPllTrackingFrequency(0);
mFrequencyOverlayPanel.setChannelBandwidth(0);
mSquelchLabel.setEnabled(false);
mSquelchValueLabel.setText("Not Available");
@@ -138,9 +380,11 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
@Override
public void receive(ProcessingChain processingChain)
{
//Disconnect the FFT panel
if(mProcessingChain != null)
{
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
mProcessingChain.removeModule(mSampleStreamTapModule);
}
//Invoking reset - we're on the Swing dispatch thread here
@@ -151,11 +395,35 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
if(mProcessingChain != null)
{
mProcessingChain.addSourceEventListener(mSourceEventProcessor);
mProcessingChain.addModule(mSampleStreamTapModule);
Source source = mProcessingChain.getSource();
if(source instanceof TunerChannelSource tcs)
{
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, tcs.getFrequency()));
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(tcs.getSampleRate()));
}
Channel channel = mPlaylistManager.getChannelProcessingManager().getChannel(mProcessingChain);
if(channel != null)
{
TunerChannel tunerChannel = channel.getTunerChannel();
if(tunerChannel != null)
{
mFrequencyOverlayPanel.setChannelBandwidth(tunerChannel.getBandwidth());
}
}
}
updateFFTProcessing();
broadcast(SourceEvent.requestCurrentSquelchThreshold(null));
}
/**
* Processor for source event stream to capture power level and squelch related source events.
*/
@@ -171,17 +439,12 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
final double power = sourceEvent.getValue().doubleValue();
final double peak = mPeakMonitor.process(power);
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
mPowerMeter.setPower(power);
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
EventQueue.invokeLater(() -> {
mPowerMeter.setPower(power);
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
mPowerMeter.setPeak(peak);
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
}
mPowerMeter.setPeak(peak);
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
});
}
@@ -205,7 +468,70 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
}
});
}
case NOTIFICATION_PLL_FREQUENCY ->
{
if(sourceEvent.hasValue())
{
updatePllFrequency(sourceEvent.getValue().longValue());
}
}
}
}
}
/**
* Monitors the sizing of the layered pane and resizes the spectrum and
* channel panels whenever the layered pane is resized
*/
public class ResizeListener implements ComponentListener
{
@Override public void componentResized(ComponentEvent e)
{
Component c = e.getComponent();
mSpectrumPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
mFrequencyOverlayPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
}
@Override public void componentHidden(ComponentEvent arg0) {}
@Override public void componentMoved(ComponentEvent arg0) {}
@Override public void componentShown(ComponentEvent arg0) {}
}
/**
* Mouse event handler for the spectral display panel.
*/
public class MouseEventProcessor extends MouseInputAdapter
{
public MouseEventProcessor()
{
}
@Override public void mouseWheelMoved(MouseWheelEvent e) {}
@Override public void mouseMoved(MouseEvent event)
{
update(event);
}
@Override public void mouseDragged(MouseEvent event) {}
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseClicked(MouseEvent event) {}
/**
* Updates the cursor display while the mouse is performing actions
*/
private void update(MouseEvent event)
{
mFrequencyOverlayPanel.setCursorLocation(event.getPoint());
}
@Override public void mouseEntered(MouseEvent e)
{
mFrequencyOverlayPanel.setCursorVisible(true);
}
@Override public void mouseExited(MouseEvent e)
{
mFrequencyOverlayPanel.setCursorVisible(false);
}
}
}

View File

@@ -25,6 +25,7 @@ import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.mp3.MP3Preference;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
@@ -99,12 +100,47 @@ public class MP3PreferenceEditor extends HBox
mMP3SettingComboBox.getItems().addAll(MP3Setting.values());
mMP3SettingComboBox.getSelectionModel().select(mMP3Preference.getMP3Setting());
mMP3SettingComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> mMP3Preference.setMP3Setting(newValue));
.addListener((observable, oldValue, newValue) -> {
mMP3Preference.setMP3Setting(newValue);
updateAudioSampleRateComboBox();
});
}
return mMP3SettingComboBox;
}
/**
* Updates the input sample rate combo box values and alerts the user whenever we auto-change an input sample rate
* due to a change in the MP3 setting value.
*/
private void updateAudioSampleRateComboBox()
{
InputAudioFormat currentSelection = getAudioSampleRateComboBox().getSelectionModel().getSelectedItem();
MP3Setting setting = getMP3SettingComboBox().getSelectionModel().getSelectedItem();
getAudioSampleRateComboBox().getItems().clear();
getAudioSampleRateComboBox().getItems().addAll(setting.getSupportedSampleRates());
if(currentSelection != null && getAudioSampleRateComboBox().getItems().contains(currentSelection))
{
getAudioSampleRateComboBox().getSelectionModel().select(currentSelection);
}
else
{
getAudioSampleRateComboBox().getSelectionModel().select(InputAudioFormat.getDefault());
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Sample Rate Updated");
alert.setHeaderText("Input Sample Rate Updated");
Label wrappingLabel = new Label("Previous input sample rate [" + currentSelection +
"] is not supported with encoder setting [" + setting + "]. Sample rate updated to default.");
wrappingLabel.setWrapText(true);
alert.getDialogPane().setContent(wrappingLabel);
alert.show();
}
}
private ComboBox<InputAudioFormat> getAudioSampleRateComboBox()
{
if(mAudioSampleRateComboBox == null)
@@ -113,7 +149,12 @@ public class MP3PreferenceEditor extends HBox
mAudioSampleRateComboBox.getItems().addAll(InputAudioFormat.values());
mAudioSampleRateComboBox.getSelectionModel().select(mMP3Preference.getAudioSampleRate());
mAudioSampleRateComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> mMP3Preference.setAudioSampleRate(newValue));
.addListener((observable, oldValue, newValue) -> {
if(newValue != null)
{
mMP3Preference.setAudioSampleRate(newValue);
}
});
}
return mAudioSampleRateComboBox;

View File

@@ -1,18 +1,20 @@
/*
* *********************************************************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2022 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.icon;
@@ -22,15 +24,6 @@ import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ImageIcon;
import java.awt.Image;
import java.io.IOException;
import java.io.InputStream;
@@ -43,6 +36,15 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ImageIcon;
public class IconModel
{
@@ -241,7 +243,6 @@ public class IconModel
if(path != null)
{
Icon icon = new Icon("", path);
return getScaledIcon(icon.getIcon(), height);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -72,14 +72,15 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processing chain provides a framework for connecting a complex or real sample
@@ -116,12 +117,15 @@ public class ProcessingChain implements Listener<ChannelEvent>
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
private AtomicBoolean mRunning = new AtomicBoolean();
private List<Module> mModules = new ArrayList<>();
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
private MessageHistory mMessageHistory = new MessageHistory(500);
private AbstractChannelState mChannelState;
private EventBus mEventBus;
protected Source mSource;
//Lock to protect access to the modules list.
private ReentrantLock mModuleLock = new ReentrantLock();
private List<Module> mModules = new ArrayList<>();
/**
* Creates a processing chain for managing a set of modules
@@ -196,30 +200,46 @@ public class ProcessingChain implements Listener<ChannelEvent>
*/
public void removeTrafficChannelManager()
{
Iterator<Module> it = mModules.iterator();
mModuleLock.lock();
while(it.hasNext())
try
{
if(it.next() instanceof TrafficChannelManager)
Iterator<Module> it = mModules.iterator();
while(it.hasNext())
{
it.remove();
if(it.next() instanceof TrafficChannelManager)
{
it.remove();
}
}
}
finally
{
mModuleLock.unlock();
}
}
public void dispose()
{
stop();
List<Module> modules = new ArrayList<>(mModules);
mModuleLock.lock();
for(Module module : modules)
try
{
removeModule(module);
module.dispose();
}
List<Module> modulesToRemove = new ArrayList<>(mModules);
mModules.clear();
for(Module module : modulesToRemove)
{
removeModule(module);
module.dispose();
}
}
finally
{
mModuleLock.unlock();
}
mAudioSegmentBroadcaster.dispose();
mDecodeEventBroadcaster.dispose();
@@ -269,12 +289,21 @@ public class ProcessingChain implements Listener<ChannelEvent>
addModule(mSource);
}
/**
* Source of sample data for this channel.
* @return source, may be null.
*/
public Source getSource()
{
return mSource;
}
/**
* List of current modules for this processing chain
*/
public List<Module> getModules()
{
return mModules;
return Collections.unmodifiableList(mModules);
}
/**
@@ -284,13 +313,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
{
List<DecoderState> decoderStates = new ArrayList<>();
for(Module module : mModules)
mModuleLock.lock();
try
{
if(module instanceof DecoderState)
for(Module module : mModules)
{
decoderStates.add((DecoderState)module);
if(module instanceof DecoderState)
{
decoderStates.add((DecoderState)module);
}
}
}
finally
{
mModuleLock.unlock();
}
return decoderStates;
}
@@ -322,7 +360,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
*/
public void addModule(Module module)
{
mModules.add(module);
mModuleLock.lock();
try
{
mModules.add(module);
}
finally
{
mModuleLock.unlock();
}
module.setInterModuleEventBus(getEventBus());
registerListeners(module);
registerProviders(module);
@@ -337,7 +385,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
unregisterListeners(module);
unregisterProviders(module);
module.setInterModuleEventBus(null);
mModules.remove(module);
mModuleLock.lock();
try
{
mModules.remove(module);
}
finally
{
mModuleLock.unlock();
}
}
/**
@@ -656,18 +714,27 @@ public class ProcessingChain implements Listener<ChannelEvent>
+ "sample type - cannot start processing chain");
}
/* Start each of the modules */
for(Module module : mModules)
mModuleLock.lock();
try
{
try
/* Start each of the modules */
for(Module module : mModules)
{
module.start();
}
catch(Exception e)
{
mLog.error("Error starting module", e);
try
{
module.start();
}
catch(Exception e)
{
mLog.error("Error starting module", e);
}
}
}
finally
{
mModuleLock.unlock();
}
}
else
{
@@ -708,15 +775,28 @@ public class ProcessingChain implements Listener<ChannelEvent>
}
/* Stop each of the remaining modules */
for(Module module : mModules)
{
module.stop();
}
mModuleLock.lock();
//Reset each of the modules
for(Module module : mModules)
try
{
module.reset();
for(Module module : mModules)
{
module.stop();
}
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
}
catch(Exception e)
{
mLog.error("Error stopping or resetting modules", e);
}
finally
{
mModuleLock.unlock();
}
}
}
@@ -728,13 +808,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
{
List<Module> eventLoggingModules = new ArrayList<>();
for(Module module : mModules)
mModuleLock.lock();
try
{
if(module instanceof EventLogger)
for(Module module : mModules)
{
eventLoggingModules.add(module);
if(module instanceof EventLogger)
{
eventLoggingModules.add(module);
}
}
}
finally
{
mModuleLock.unlock();
}
for(Module eventLoggingModule : eventLoggingModules)
{
@@ -749,21 +838,30 @@ public class ProcessingChain implements Listener<ChannelEvent>
{
List<Module> recordingModules = new ArrayList<>();
for(Module module : mModules)
mModuleLock.lock();
try
{
if(module instanceof ComplexSamplesWaveRecorder)
for(Module module : mModules)
{
recordingModules.add(module);
}
else if(module instanceof MBECallSequenceRecorder)
{
recordingModules.add(module);
}
else if(module instanceof BinaryRecorder)
{
recordingModules.add(module);
if(module instanceof ComplexSamplesWaveRecorder)
{
recordingModules.add(module);
}
else if(module instanceof MBECallSequenceRecorder)
{
recordingModules.add(module);
}
else if(module instanceof BinaryRecorder)
{
recordingModules.add(module);
}
}
}
finally
{
mModuleLock.unlock();
}
for(Module recordingModule : recordingModules)
{

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* 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.event;
@@ -30,13 +27,13 @@ import io.github.dsheirer.module.decode.event.filter.DecodeEventFilterSet;
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class DecodeEventModel extends AbstractTableModel implements Listener<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
{
@@ -149,10 +146,12 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
*/
public void receive(final IDecodeEvent event)
{
if (!mEventFilterSet.passes(event))
{
return;
}
//Disabled until #1368 decode event is fully implemented
// if (!mEventFilterSet.passes(event))
// {
// return;
// }
if(!mEvents.contains(event))
{
mEvents.add(0, event);

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* 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.event;
@@ -44,18 +41,24 @@ import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import io.github.dsheirer.sample.Listener;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain>
{
@@ -94,7 +97,8 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
updateCellRenderers();
add(mEventManagementPanel, "span,growx");
//Disabled until #1368 decode event is fully implemented
// add(mEventManagementPanel, "span,growx");
mEmptyScroller = new JScrollPane(mTable);
add(mEmptyScroller);

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -15,7 +14,7 @@
*
* 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.ip.ars;
@@ -25,11 +24,10 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSDevice;
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSPassword;
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Automatic Registration Service - Device Registration
@@ -176,7 +174,7 @@ public class DeviceRegistration extends ARSHeader
pointer += 8;
if(deviceIdentifierSize > 0)
if(deviceIdentifierSize > 0 && deviceIdentifierSize < 8)
{
StringBuilder sb = new StringBuilder();
@@ -186,21 +184,13 @@ public class DeviceRegistration extends ARSHeader
pointer += 8;
}
try
{
int device = Integer.parseInt(sb.toString());
mDevice = ARSDevice.createFrom(device);
}
catch(Exception e)
{
mLog.error("Error parsing ARS device identifier from value [" + sb.toString() + "]");
}
mDevice = ARSDevice.createFrom(sb.toString());
}
int userIdentifierSize = getMessage().getInt(BYTE_VALUE, pointer);
pointer += 8;
if(userIdentifierSize > 0)
if(userIdentifierSize > 0 && userIdentifierSize < 8)
{
StringBuilder sb = new StringBuilder();
@@ -216,7 +206,7 @@ public class DeviceRegistration extends ARSHeader
int passwordSize = getMessage().getInt(BYTE_VALUE, pointer);
pointer += 8;
if(passwordSize > 0)
if(passwordSize > 0 && passwordSize < 8)
{
StringBuilder sb = new StringBuilder();

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -15,7 +14,7 @@
*
* 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.ip.ars;
@@ -25,11 +24,10 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSDevice;
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSPassword;
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserRegistration extends ARSHeader
{
@@ -108,7 +106,7 @@ public class UserRegistration extends ARSHeader
pointer += 8;
if(deviceIdentifierSize > 0)
if(deviceIdentifierSize > 0 && deviceIdentifierSize < 8)
{
StringBuilder sb = new StringBuilder();
@@ -118,22 +116,13 @@ public class UserRegistration extends ARSHeader
pointer += 8;
}
try
{
int device = Integer.parseInt(sb.toString());
mDevice = ARSDevice.createFrom(device);
}
catch(Exception e)
{
//Abort processing
return;
}
mDevice = ARSDevice.createFrom(sb.toString());
}
int userIdentifierSize = getMessage().getInt(BYTE_VALUE, pointer);
pointer += 8;
if(userIdentifierSize > 0)
if(userIdentifierSize > 0 && deviceIdentifierSize < 8)
{
StringBuilder sb = new StringBuilder();
@@ -149,7 +138,7 @@ public class UserRegistration extends ARSHeader
int passwordSize = getMessage().getInt(BYTE_VALUE, pointer);
pointer += 8;
if(passwordSize > 0)
if(passwordSize > 0 && passwordSize < 8)
{
StringBuilder sb = new StringBuilder();

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -22,20 +22,20 @@ package io.github.dsheirer.module.decode.ip.ars.identifier;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.string.StringIdentifier;
import io.github.dsheirer.protocol.Protocol;
/**
* Automatic Registration Service - Device Identifier
*/
public class ARSDevice extends IntegerIdentifier
public class ARSDevice extends StringIdentifier
{
/**
* Constructs an instance
* @param value of the device identifier
* @param role of the device (TO/FROM)
*/
public ARSDevice(int value, Role role)
public ARSDevice(String value, Role role)
{
super(value, IdentifierClass.USER, Form.ARS_DEVICE, role);
}
@@ -49,7 +49,7 @@ public class ARSDevice extends IntegerIdentifier
/**
* Creates an ARS device with a FROM role
*/
public static ARSDevice createFrom(int value)
public static ARSDevice createFrom(String value)
{
return new ARSDevice(value, Role.FROM);
}
@@ -57,7 +57,7 @@ public class ARSDevice extends IntegerIdentifier
/**
* Creates an ARS device with a TO role
*/
public static ARSDevice createTo(int value)
public static ARSDevice createTo(String value)
{
return new ARSDevice(value, Role.TO);
}

View File

@@ -116,6 +116,24 @@ public class MP3Preference extends Preference
{
mInputAudioFormat = InputAudioFormat.getDefault();
}
//Ensure the input sample rate is supported by the MP3 setting, or change it to the first supported rate.
MP3Setting setting = getMP3Setting();
if(!setting.getSupportedSampleRates().contains(mInputAudioFormat))
{
InputAudioFormat supportedAudioFormat = InputAudioFormat.getDefault();
//Default should always be supported by all mp3 settings, but fall-back to a supported rate if not.
if(!setting.getSupportedSampleRates().contains(supportedAudioFormat))
{
supportedAudioFormat = setting.getSupportedSampleRates().get(0);
}
mLog.warn("MP3 Setting [" + setting + "] does not support input sample rate [" + getAudioSampleRate().name() +
"] - updating to supported sample rate [" + supportedAudioFormat.name() + "]");
setAudioSampleRate(supportedAudioFormat);
}
}
return mInputAudioFormat;

View File

@@ -0,0 +1,90 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.sample.complex;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple module that can be added live to a processing chain to tap into the stream of ComplexSamples buffers and
* convert them to INativeBuffers for relay to a registered native buffer listener.
*/
public class ComplexSamplesToNativeBufferModule extends Module implements IComplexSamplesListener, Listener<ComplexSamples>
{
private static final Logger mLog = LoggerFactory.getLogger(ComplexSamplesToNativeBufferModule.class);
private Listener<INativeBuffer> mNativeBufferListener;
public ComplexSamplesToNativeBufferModule()
{
}
@Override
public Listener<ComplexSamples> getComplexSamplesListener()
{
return this;
}
/**
* Primary receive method to process complex samples buffer by wrapping with a native buffer adapter and sending
* to the registered listener.
*
* Note: this method is synchronized to ensure that multiple calling threads don't step on each other.
*
* @param complexSamples to process and relay.
*/
@Override
public synchronized void receive(ComplexSamples complexSamples)
{
if(mNativeBufferListener != null)
{
mNativeBufferListener.receive(new ComplexSamplesNativeBufferAdapter(complexSamples));
}
}
/**
* Registers the listener to receive native buffers.
* @param listener to register
*/
public void setListener(Listener<INativeBuffer> listener)
{
mNativeBufferListener = listener;
}
/**
* Unregisters a listener from receiving native buffers.
*/
public void removeListener()
{
mNativeBufferListener = null;
}
@Override
public void reset() {}
@Override
public void start() {}
@Override
public void stop() {}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -29,6 +29,7 @@ public class SourceEvent
public enum Event
{
NOTIFICATION_CHANNEL_COUNT_CHANGE,
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION,
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
NOTIFICATION_CHANNEL_POWER,
NOTIFICATION_CHANNEL_SAMPLE_RATE_CHANGE,
@@ -40,6 +41,7 @@ public class SourceEvent
NOTIFICATION_FREQUENCY_ROTATION_FAILURE,
NOTIFICATION_MEASURED_FREQUENCY_ERROR,
NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED,
NOTIFICATION_PLL_FREQUENCY,
NOTIFICATION_RECORDING_FILE_LOADED,
NOTIFICATION_SAMPLE_RATE_CHANGE,
NOTIFICATION_SQUELCH_THRESHOLD,
@@ -47,7 +49,6 @@ public class SourceEvent
NOTIFICATION_TUNER_SHUTDOWN,
NOTIFICATION_ERROR_STATE,
REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
REQUEST_FREQUENCY_CHANGE,
REQUEST_FREQUENCY_ROTATION,
REQUEST_FREQUENCY_SELECTION,
@@ -242,13 +243,13 @@ public class SourceEvent
}
/**
* Creates a new channel frequency correction change event
* Creates a new PLL frequency error measurement notification event.
*
* @param frequencyCorrection in hertz
* @param frequencyError in hertz
*/
public static SourceEvent channelFrequencyCorrectionChange(long frequencyCorrection)
public static SourceEvent pllFrequencyMeasurement(long frequencyError)
{
return new SourceEvent(Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, frequencyCorrection);
return new SourceEvent(Event.NOTIFICATION_PLL_FREQUENCY, frequencyError);
}
/**
@@ -332,16 +333,6 @@ public class SourceEvent
return new SourceEvent(Event.REQUEST_FREQUENCY_CHANGE, frequency);
}
/**
* Creates a new channel frequency correction change request event
*
* @param frequency requested
*/
public static SourceEvent channelFrequencyCorrectionRequest(long frequency)
{
return new SourceEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, frequency);
}
/**
* Creates a new start sample stream request event. This method uses the current system time in
* milliseconds as the requested sample start time.

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -50,9 +50,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
private IRealDecimationFilter mIDecimationFilter;
private IRealDecimationFilter mQDecimationFilter;
private double mChannelSampleRate;
private long mChannelFrequencyCorrection = 0;
private long mTunerFrequency;
private Listener<ComplexSamples> mListener;
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
/**
@@ -131,38 +129,25 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
updateMixerFrequencyOffset();
}
/**
* Current frequency correction being applied to the channel sample stream
*
* @return correction in hertz
*/
public long getFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
*
* @param correction current frequency correction value.
*/
public void setFrequencyCorrection(long correction)
{
mChannelFrequencyCorrection = correction;
updateMixerFrequencyOffset();
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
}
/**
* Calculates the local mixer frequency offset from the tuned frequency,
* channel's requested frequency, and channel frequency correction.
*/
private void updateMixerFrequencyOffset()
{
long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection;
long offset = mTunerFrequency - getTunerChannel().getFrequency();
mFrequencyCorrectionMixer.setFrequency(offset);
}
/**
* Mixer frequency applying offset to the samples.
* @return mixer frequency
*/
public long getMixerFrequency()
{
return (long)mFrequencyCorrectionMixer.getFrequency();
}
/**
* Sets the sample rate of the incoming sample stream from the producer
*
@@ -174,28 +159,6 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
//Not implemented. Sample rate changes are not permitted once sample stream starts
}
/**
* Sets the frequency correction for the outbound sample stream to the consumer
*
* @param correction in hertz
*/
@Override
protected void setChannelFrequencyCorrection(long correction)
{
mChannelFrequencyCorrection = correction;
broadcastConsumerSourceEvent(SourceEvent.channelFrequencyCorrectionChange(correction));
updateMixerFrequencyOffset();
}
/**
* Frequency correction value currently being applied to the outbound sample stream
*/
@Override
public long getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Sets the listener to receive the complex buffer sample output from this channel
*

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -29,13 +29,12 @@ import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.util.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Multiple-frequency tuner channel source. Provides a wrapper around a tuner channel source and listens for external
@@ -273,26 +272,6 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource
}
}
@Override
protected void setChannelFrequencyCorrection(long correction)
{
if(mTunerChannelSource != null)
{
mTunerChannelSource.setChannelFrequencyCorrection(correction);
}
}
@Override
public long getChannelFrequencyCorrection()
{
if(mTunerChannelSource != null)
{
return mTunerChannelSource.getChannelFrequencyCorrection();
}
return 0;
}
@Override
public void setListener(Listener<ComplexSamples> complexSamplesListener)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -27,11 +27,10 @@ import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.util.Dispatcher;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
/**
* Pass-through channel source that simply passes complex sample buffers from the tuner controller
* directly to the registered listener
@@ -90,18 +89,6 @@ public class PassThroughChannelSource extends TunerChannelSource implements ISou
mLog.debug("Request to set sample rate: " + sampleRate);
}
@Override
protected void setChannelFrequencyCorrection(long correction)
{
mLog.debug("Request to set frequency correction: " + correction);
}
@Override
public long getChannelFrequencyCorrection()
{
return 0;
}
@Override
public void setListener(Listener<ComplexSamples> listener)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -78,17 +78,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
*/
protected abstract void setSampleRate(double sampleRate);
/**
* Sets the frequency correction for the outbound sample stream to the consumer
* @param correction in hertz
*/
protected abstract void setChannelFrequencyCorrection(long correction);
/**
* Frequency correction value currently being applied to the outbound sample stream
*/
public abstract long getChannelFrequencyCorrection();
/**
* Sets the listener to receive the complex buffer sample output from this channel
* @param complexSamplesListener to receive complex buffers
@@ -152,11 +141,11 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
{
switch(sourceEvent.getEvent())
{
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_FREQUENCY_CHANGE:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_LOCKED:
case NOTIFICATION_FREQUENCY_AND_SAMPLE_RATE_UNLOCKED:
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
case NOTIFICATION_PLL_FREQUENCY:
case NOTIFICATION_STOP_SAMPLE_STREAM:
//no-op
break;
@@ -172,10 +161,6 @@ public abstract class TunerChannelSource extends ComplexSource implements ISourc
sourceEvent.setSource(this);
broadcastProducerSourceEvent(sourceEvent);
break;
//Request events from the consumer
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
setChannelFrequencyCorrection(sourceEvent.getValue().longValue());
break;
case NOTIFICATION_FREQUENCY_ROTATION_FAILURE:
case NOTIFICATION_FREQUENCY_ROTATION_SUCCESS:
case NOTIFICATION_CHANNEL_POWER:

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -52,6 +52,12 @@ public abstract class ChannelSourceManager implements ISourceEventProcessor
*/
public abstract SortedSet<TunerChannel> getTunerChannels();
/**
* State of this channel source manager for logging and debug purposes
* @return description of the state or configuration of this manager
*/
public abstract String getStateDescription();
/**
* Count of tuner channels being sourced by this source manager.
* @return

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -24,11 +24,10 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A discovered tuner that may be accessible for use.
@@ -56,6 +55,27 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
return mTunerStatus;
}
/**
* Logs current state of the tuner
*/
public void logState()
{
StringBuilder sb = new StringBuilder();
sb.append("Discovered Tuner: ").append(getId());
if(hasTuner())
{
sb.append("\n\tTuner - Frequency:").append(getTuner().getTunerController().getFrequency());
sb.append("\n\tChannel Manager:").append(getTuner().getChannelSourceManager().getStateDescription());
}
else
{
sb.append("\n\tTuner - no tuner");
}
mLog.info(sb.toString());
}
/**
* Sets the status of the discovered tuner and notifies registered listeners of the status change.
* @param tunerStatus to set
@@ -106,8 +126,8 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
if(mEnabled)
{
setTunerStatus(TunerStatus.ENABLED);
start();
setTunerStatus(TunerStatus.ENABLED);
}
else
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -60,6 +60,23 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
mTunerController.addListener(this);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Heterodyne Channel Source Manager Providing [").append(mTunerChannels.size()).append("] Channels");
sb.append("\n\tTuner Controller Frequency: ").append(mTunerController.getFrequency());
for(HalfBandTunerChannelSource channelSource: mChannelSources)
{
sb.append("\n\tChannel [").append(channelSource.getTunerChannel())
.append("] Frequency [").append(channelSource.getFrequency())
.append("] Mixer [").append(channelSource.getMixerFrequency())
.append("]");
}
return sb.toString();
}
@Override
public void stopAllChannels()
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -54,6 +54,19 @@ public class PassThroughSourceManager extends ChannelSourceManager
mTunerController = tunerController;
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Pass-Through Channel Source Manager Providing [").append(mTunerChannels.size()).append("] Channels");
for(TunerChannel channel: mTunerChannels)
{
sb.append("\n\tChannel: ").append(channel);
}
return sb.toString();
}
@Override
public void stopAllChannels()
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -55,6 +55,17 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
mTunerController.addListener(mPolyphaseChannelManager);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel Source Manager");
sb.append("\n\tTuner Controller Frequency: ").append(mTunerController.getFrequency());
sb.append("\n\t").append(mPolyphaseChannelManager.getStateDescription());
return sb.toString();
}
@Override
public void stopAllChannels()
{
@@ -169,7 +180,7 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
* @return optimal center tuned frequency for the set of currently sourced tuner channels.
* @throws IllegalArgumentException if a center frequency cannot be determined for the set of tuner channels
*/
private long getCenterFrequency(SortedSet<TunerChannel> channels, long currentCenterFrequency)
protected long getCenterFrequency(SortedSet<TunerChannel> channels, long currentCenterFrequency)
throws IllegalArgumentException
{
if(channels.isEmpty())
@@ -454,8 +465,13 @@ public class PolyphaseChannelSourceManager extends ChannelSourceManager
}
catch(IllegalArgumentException iae)
{
mLog.error("Center frequency calculation failed", iae);
//Center frequency calculation failed
}
catch(Exception e)
{
mLog.error("Error getting source", e);
}
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.source.tuner.manager;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import java.util.SortedSet;
public class TestPolyphaseChannelSourceManager extends PolyphaseChannelSourceManager
{
private boolean mLockCenterFrequency = true;
/**
* Constructs an instance
*
* @param tunerController with a center tuned frequency that will be managed by this instance
*/
public TestPolyphaseChannelSourceManager(TunerController tunerController)
{
super(tunerController);
}
@Override
protected long getCenterFrequency(SortedSet<TunerChannel> channels, long currentCenterFrequency) throws IllegalArgumentException
{
if(mLockCenterFrequency)
{
return currentCenterFrequency;
}
return super.getCenterFrequency(channels, currentCenterFrequency);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -20,6 +20,7 @@ package io.github.dsheirer.source.tuner.test;
import io.github.dsheirer.buffer.FloatNativeBuffer;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.dsp.oscillator.AWGNOscillator;
import io.github.dsheirer.dsp.oscillator.IComplexOscillator;
import io.github.dsheirer.dsp.oscillator.OscillatorFactory;
import io.github.dsheirer.sample.Broadcaster;
@@ -59,7 +60,9 @@ public class SampleGenerator
throw new IllegalArgumentException("Sweep update rate cannot be greater than sample rate");
}
mComplexOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate);
// mComplexOscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate);
IComplexOscillator oscillator = OscillatorFactory.getComplexOscillator(frequency, sampleRate);
mComplexOscillator = new AWGNOscillator(oscillator, 0.003f);
mInterval = 1000 / (sampleRate / mSamplesPerInterval);
mSweepUpdateInterval = sweepUpdateRate;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -22,6 +22,7 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerClass;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.manager.TestPolyphaseChannelSourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,6 +39,7 @@ public class TestTuner extends Tuner
public TestTuner(ITunerErrorListener tunerErrorListener)
{
super(new TestTunerController(), tunerErrorListener);
setChannelSourceManager(new TestPolyphaseChannelSourceManager(getTunerController()));
}
/**

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -33,7 +33,7 @@ public class TestTunerController extends TunerController
private final static Logger mLog = LoggerFactory.getLogger(TestTunerController.class);
public static final long MINIMUM_FREQUENCY = 1l;
public static final long MAXIMUM_FREQUENCY = 1000000000l;
public static final long MAXIMUM_FREQUENCY = 1_000_000_000l;
public static final int SAMPLE_RATE = 2_400_000;
public static final int DC_NOISE_BANDWIDTH = 0;
public static final double USABLE_BANDWIDTH_PERCENTAGE = 1.00;
@@ -42,7 +42,7 @@ public class TestTunerController extends TunerController
public static final long SAMPLE_GENERATION_INTERVAL = 1000 / SPECTRAL_FRAME_RATE;
private SampleGenerator mSampleGenerator;
private long mFrequency = 100000000l;
private long mFrequency = 460662500l;
/**
* Tuner controller testing implementation.

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -189,6 +189,7 @@ public class DiscoveredTunerModel extends AbstractTableModel implements Listener
*/
public void removeDiscoveredTuner(DiscoveredTuner discoveredTuner)
{
mLog.info("Removing discovered tuner: " + discoveredTuner.getId());
mLock.lock();
try

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -39,7 +39,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
@@ -115,6 +117,32 @@ public class TunerViewPanel extends JPanel
}
});
//Add support for right-click context menu to the tuner table
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem logStateMenuItem = new JMenuItem("Log Tuner State");
logStateMenuItem.addActionListener(e -> {
int viewRow = mTunerTable.getSelectedRow();
DiscoveredTuner selected = null;
if(viewRow >= 0)
{
int modelRow = mTunerTable.convertRowIndexToModel(viewRow);
selected = mDiscoveredTunerModel.getDiscoveredTuner(modelRow);
}
if(selected != null)
{
selected.logState();
}
else
{
mLog.error("Can't log state - tuner not selected");
}
});
popupMenu.add(logStateMenuItem);
mTunerTable.setComponentPopupMenu(popupMenu);
//Monitor for tuner removal events so we can update the editor when our selected tuner is removed
mDiscoveredTunerModel.addTableModelListener(e ->
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -26,6 +26,7 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.TunerType;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.util.ThreadPool;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -267,9 +268,33 @@ public abstract class USBTunerController extends TunerController
public final void stop()
{
mRunning = false;
stopStreaming();
mNativeBufferBroadcaster.clear();
deviceStop();
//Spin the shutdown onto a new thread so that we can set a max wait threshold.
Thread t = new Thread(() -> {
stopStreaming();
mNativeBufferBroadcaster.clear();
deviceStop();
});
t.start();
try
{
//Wait up to 500 milliseconds for the shutdown to complete ... otherwise interrupt it and finish shutdown
t.join(500);
if(t.isAlive())
{
mLog.info("Tuner shutdown exceeded 500ms - forcing shutdown");
t.interrupt();
}
}
catch(InterruptedException ie)
{
}
//Release transfers
mTransferManager.freeTransfers();
if(mDeviceHandle != null)
{
@@ -495,6 +520,9 @@ public abstract class USBTunerController extends TunerController
private List<Transfer> mAvailableTransfers;
private LinkedTransferQueue<Transfer> mInProgressTransfers = new LinkedTransferQueue<>();
private boolean mAutoResubmitTransfers = false;
private int mTransferErrorCount = 0;
private List<Transfer> mErrorTransfers = new ArrayList<>();
private int mResubmitFailureLogCount = 0;
/**
* Creates USB Transfers to carry the streaming sample data. Transfer buffers are backed by native memory
@@ -559,41 +587,58 @@ public abstract class USBTunerController extends TunerController
int status = LibUsb.submitTransfer(transfer);
if(status == LibUsb.SUCCESS)
{
mInProgressTransfers.add(transfer);
}
else if(status == LibUsb.ERROR_BUSY)
{
mInProgressTransfers.add(transfer);
//Weird issue/bug with libusb on Windows where libusb tells us the transfer is complete and then tells
//us that the transfer is already submitted when we attempt to re-submit the transfer. We track the
//number of times this occurs and log when the anomaly count exceeds the number of transfer buffers
//in case the application gets to a state where all transfers are no longer usable ... so that we
//know post-mortem what happened to the application.
mAnomalousTransfersDetected++;
if(mAnomalousTransfersDetected < USB_BULK_TRANSFER_BUFFER_POOL_SIZE)
//Attempt to resubmit any previous transfers that failed on submit
if(!mErrorTransfers.isEmpty())
{
mLog.error("USB transfer anomaly detected - continuing - previous status [" + previousStatus +
" ] transferred [" + previousBytesTransferred + "] this has happened [" +
mAnomalousTransfersDetected + "] times");
}
Transfer toResubmit = mErrorTransfers.remove(0);
int resubmitStatus = LibUsb.submitTransfer(toResubmit);
//This should only log once, when the anomaly count matches the transfer count
if(mAnomalousTransfersDetected == USB_BULK_TRANSFER_BUFFER_POOL_SIZE)
{
mLog.warn("USB transfer anomaly detection count exceeded the total number [8] of available " +
"tranfers - sdrtrunk may no longer be streaming data from this tuner and may appear to " +
"be locked up. If so, restart the application to resolve the issue and please send the " +
"application log to the developer");
if(resubmitStatus == LibUsb.SUCCESS)
{
mInProgressTransfers.add(transfer);
mLog.error("Successfully resubmitted previous error USB transfer buffer. Current transfer buffer" +
" status (error queue/total available) [" + mErrorTransfers.size() + "/" +
mAvailableTransfers.size() + "]");
}
else
{
mErrorTransfers.add(transfer);
mTransferErrorCount++;
//Only log this a few times in case we get transfer buffer(s) that are perpetually stuck in error
if(mTransferErrorCount < mAvailableTransfers.size())
{
mLog.error("Attempt to resubmit previous error USB transfer buffer failed with status [" +
LibUsb.errorName(resubmitStatus) + "] - this may be temporary and it has happened [" +
mTransferErrorCount + "] times so far. Transfer buffer status (error queue/total available) [" +
mErrorTransfers.size() + "/" + mAvailableTransfers.size() + "]");
}
}
}
}
else
{
mLog.error("Error submitting USB transfer - status [" + status + "] " + LibUsb.errorName(status) +
" - previous status [" + previousStatus + "] and transferred [" + previousBytesTransferred + "]");
setErrorMessage("Usb I/O Error - Can't submit buffers to tuner - stopping tuner");
//For ERROR_BUSY error, there is a weird issue/bug with libusb on Windows where libusb tells us the
//transfer is complete and then tells us that the transfer is already submitted when we attempt to
//re-submit the transfer. We track the number of times this occurs and log when the anomaly count
//exceeds the number of transfer buffers in case the application gets to a state where all transfers
//are no longer usable ... so that we know post-mortem what happened to the application.
mErrorTransfers.add(transfer);
mTransferErrorCount++;
mLog.error("Attempt to submit USB transfer buffer failed with status [" +
LibUsb.errorName(status) + "] - this may be temporary and it has happened [" +
mTransferErrorCount + "] time(s) so far. Transfer buffer status (error queue/total available) [" +
mErrorTransfers.size() + "/" + mAvailableTransfers.size() + "]");
}
if(mErrorTransfers.size() >= mAvailableTransfers.size())
{
mLog.error("Maximum USB transfer buffer errors reached - transfer buffers exhausted - shutting down USB tuner");
ThreadPool.CACHED.submit(() -> setErrorMessage("USB Error - Transfer Buffer Errors"));
}
}
@@ -610,6 +655,35 @@ public abstract class USBTunerController extends TunerController
{
LibUsb.cancelTransfer(transfer);
}
for(Transfer transfer: mErrorTransfers)
{
LibUsb.cancelTransfer(transfer);
}
}
/**
* Frees/disposes allocated USB transfer buffers.
*/
private void freeTransfers()
{
if(mAvailableTransfers != null)
{
for(Transfer transfer: mAvailableTransfers)
{
try
{
LibUsb.freeTransfer(transfer);
}
catch(Exception e)
{
mLog.error("Error releasing allocated USB transfer buffer during tuner shutdown: " +
e.getLocalizedMessage());
}
}
mAvailableTransfers.clear();
mAvailableTransfers = null;
}
}
@Override
@@ -641,14 +715,15 @@ public abstract class USBTunerController extends TunerController
transfer.buffer().rewind();
break;
default:
//Unexpected transfer error - need to reset the bulk transfer interface
//Unexpected transfer error - shutdown the tuner
transfer.buffer().rewind();
//Only set an error if we're not shutting down
if(mAutoResubmitTransfers)
{
setErrorMessage("LibUsb Transfer Error - stopping device - status [" + transfer.status() + "] - " +
LibUsb.errorName(transfer.status()));
//spin this off onto the thread pool, so it doesn't impact the usb processor thread.
ThreadPool.CACHED.submit(() -> setErrorMessage("LibUsb Transfer Error - stopping device - " +
"status [" + transfer.status() + "] - " + LibUsb.errorName(transfer.status())));
}
break;
}

View File

@@ -1,268 +0,0 @@
/*******************************************************************************
* 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/>
******************************************************************************/
package io.github.dsheirer.spectrum;
import io.github.dsheirer.settings.ColorSetting;
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
import io.github.dsheirer.settings.Setting;
import io.github.dsheirer.settings.SettingChangeListener;
import io.github.dsheirer.settings.SettingsManager;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
public class ChannelOverlayPanel extends JPanel implements SettingChangeListener
{
private static final long serialVersionUID = 1L;
private static DecimalFormat sFORMAT = new DecimalFormat( "000.0" );
private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.0" );
private double mBandwidth = 6000.0d;
private Point mCursorLocation = new Point( 0, 0 );
private boolean mCursorVisible = false;
/**
* Colors used by this component
*/
private Color mColorSpectrumBackground;
private Color mColorSpectrumCursor;
private Color mColorSpectrumLine;
//Defines the offset at the bottom of the spectral display to account for
//the frequency labels
private float mSpectrumInset = 20.0f;
private SettingsManager mSettingsManager;
/**
* Translucent overlay panel for displaying channel configurations,
* processing channels, selected channels, frequency labels and lines, and
* a cursor with a frequency readout.
*/
public ChannelOverlayPanel( SettingsManager settingsManager )
{
mSettingsManager = settingsManager;
if( mSettingsManager != null )
{
mSettingsManager.addListener( this );
}
//Set the background transparent, so the spectrum display can be seen
setOpaque( false );
//Fetch color settings from settings manager
setColors();
}
public void dispose()
{
if( mSettingsManager != null )
{
mSettingsManager.removeListener( this );
}
mSettingsManager = null;
}
public void setCursorLocation( Point point )
{
mCursorLocation = point;
repaint();
}
public void setCursorVisible( boolean visible )
{
mCursorVisible = visible;
repaint();
}
/**
* Fetches the color settings from the settings manager
*/
private void setColors()
{
mColorSpectrumCursor = getColor( ColorSettingName.SPECTRUM_CURSOR );
mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE );
mColorSpectrumBackground =
getColor( ColorSettingName.SPECTRUM_BACKGROUND );
}
/**
* Fetches a named color setting from the settings manager. If the setting
* doesn't exist, creates the setting using the defaultColor
*/
private Color getColor( ColorSettingName name )
{
ColorSetting setting = mSettingsManager.getColorSetting( name );
return setting.getColor();
}
/**
* Monitors for setting changes. Colors can be changed by external actions
* and will automatically update in this class
*/
@Override
public void settingChanged( Setting setting )
{
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( colorSetting.getColorSettingName() )
{
case SPECTRUM_BACKGROUND:
mColorSpectrumBackground = colorSetting.getColor();
break;
case SPECTRUM_CURSOR:
mColorSpectrumCursor = colorSetting.getColor();
break;
case SPECTRUM_LINE:
mColorSpectrumLine = colorSetting.getColor();
break;
default:
break;
}
}
}
/**
* Renders the channel configs, lines, labels, and cursor
*/
@Override
public void paintComponent( Graphics g )
{
super.paintComponent( g );
Graphics2D graphics = (Graphics2D) g;
graphics.setBackground( mColorSpectrumBackground );
RenderingHints renderHints =
new RenderingHints( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
renderHints.put( RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY );
graphics.setRenderingHints( renderHints );
drawFrequencies( graphics );
drawCursor( graphics );
}
/**
* Draws a cursor on the panel, whenever the mouse is hovering over the
* panel
*/
private void drawCursor( Graphics2D graphics )
{
if( mCursorVisible )
{
drawFrequencyLine( graphics,
mCursorLocation.x,
mColorSpectrumCursor );
double value = mBandwidth *
( mCursorLocation.getX() / getSize().getWidth() );
String frequency = sCURSOR_FORMAT.format( value );
graphics.drawString( frequency ,
mCursorLocation.x + 5,
mCursorLocation.y );
}
}
/**
* Draws the frequency lines and labels every 10kHz
*/
private void drawFrequencies( Graphics2D graphics )
{
drawFrequencyLine( graphics, getAxisFromFrequency( 1000 ), Color.LIGHT_GRAY );
drawFrequencyLineAndLabel( graphics, 2000 );
drawFrequencyLine( graphics, getAxisFromFrequency( 3000 ), Color.LIGHT_GRAY );
drawFrequencyLineAndLabel( graphics, 4000 );
drawFrequencyLine( graphics, getAxisFromFrequency( 5000 ), Color.LIGHT_GRAY );
// drawFrequencyLineAndLabel( graphics, 6000 );
// drawFrequencyLine( graphics, getAxisFromFrequency( 7000 ), Color.LIGHT_GRAY );
// drawFrequencyLineAndLabel( graphics, 8000 );
// drawFrequencyLine( graphics, getAxisFromFrequency( 9000 ), Color.LIGHT_GRAY );
// drawFrequencyLineAndLabel( graphics, 10000 );
// drawFrequencyLine( graphics, getAxisFromFrequency( 11000 ), Color.LIGHT_GRAY );
}
private float getAxisFromFrequency( long frequency )
{
return (float)( getSize().getWidth() * ( (double)frequency / mBandwidth ) );
}
/**
* Draws a vertical line and a corresponding frequency label at the bottom
*/
private void drawFrequencyLineAndLabel( Graphics2D graphics, long frequency )
{
float xAxis = (float)( getSize().getWidth() *
( (double)frequency / mBandwidth ) );
drawFrequencyLine( graphics, xAxis, mColorSpectrumLine );
graphics.setColor( mColorSpectrumLine );
drawFrequencyLabel( graphics, xAxis, frequency );
}
/**
* Draws a vertical line at the xaxis
*/
private void drawFrequencyLine( Graphics2D graphics, float xaxis, Color color )
{
graphics.setColor( color );
graphics.draw( new Line2D.Float( xaxis, 0,
xaxis, (float)(getSize().getHeight()) - mSpectrumInset ) );
}
/**
* Draws a frequency label at the x-axis position, at the bottom of the panel
*/
private void drawFrequencyLabel( Graphics2D graphics,
float xaxis,
long frequency )
{
String label = sFORMAT.format( (float)frequency );
FontMetrics fontMetrics = graphics.getFontMetrics( this.getFont() );
Rectangle2D rect = fontMetrics.getStringBounds( label, graphics );
float offset = (float)rect.getWidth() / 2;
graphics.drawString( label, xaxis - offset,
(float)getSize().getHeight() - ( mSpectrumInset * 0.2f ) );
}
public void settingDeleted( Setting setting ) {}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -25,10 +25,6 @@ import io.github.dsheirer.dsp.window.WindowType;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
@@ -36,6 +32,9 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processes both complex samples or float samples and dispatches a float array of DFT results, using configurable fft
@@ -141,7 +140,7 @@ public class ComplexDftProcessor<T extends INativeBuffer> implements Listener<T>
//Cancel running DFT calculation task
if(mProcessorTaskHandle != null)
{
mProcessorTaskHandle.cancel(true);
mProcessorTaskHandle.cancel(false);
mProcessorTaskHandle = null;
}
}

View File

@@ -0,0 +1,613 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 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.spectrum;
import io.github.dsheirer.settings.ColorSetting;
import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
import io.github.dsheirer.settings.Setting;
import io.github.dsheirer.settings.SettingChangeListener;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
import org.apache.commons.math3.util.FastMath;
import javax.swing.JPanel;
/**
* Frequency overlay panel.
*/
public class FrequencyOverlayPanel extends JPanel implements ISourceEventProcessor, SettingChangeListener
{
private static final long serialVersionUID = 1L;
private final DecimalFormat PPM_FORMATTER = new DecimalFormat( "#.0" );
private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
static
{
RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
private static DecimalFormat CURSOR_FORMAT = new DecimalFormat("000.00000");
private long mFrequency = 0;
private int mBandwidth = 0;
private int mChannelBandwidth = 0;
private long mPllTrackingFrequency = 0;
private Point mCursorLocation = new Point(0, 0);
private boolean mCursorVisible = false;
private DFTSize mDFTSize = DFTSize.FFT04096;
/**
* Colors used by this component
*/
private Color mColorSpectrumBackground;
private Color mColorSpectrumCursor;
private Color mColorSpectrumLine;
//Defines the offset at the bottom of the spectral display to account for
//the frequency labels
private double mSpectrumInset = 20.0d;
private LabelSizeManager mLabelSizeMonitor = new LabelSizeManager();
private SettingsManager mSettingsManager;
/**
* Translucent overlay panel for displaying channel configurations,
* processing channels, selected channels, frequency labels and lines, and
* a cursor with a frequency readout.
*/
public FrequencyOverlayPanel(SettingsManager settingsManager)
{
mSettingsManager = settingsManager;
if(mSettingsManager != null)
{
mSettingsManager.addListener(this);
}
addComponentListener(mLabelSizeMonitor);
//Set the background transparent, so the spectrum display can be seen
setOpaque(false);
//Fetch color settings from settings manager
setColors();
}
/**
* Sets the tracking frequency of the Phase Locked Loop (PLL)
* @param pllTrackingFrequency current setting. Set to 0 to reset.
*/
public void setPllTrackingFrequency(long pllTrackingFrequency)
{
mPllTrackingFrequency = pllTrackingFrequency;
}
/**
* Sets the bandwidth of the channel.
* @param channelBandwidth in Hertz
*/
public void setChannelBandwidth(int channelBandwidth)
{
mChannelBandwidth = channelBandwidth;
}
public void dispose()
{
if(mSettingsManager != null)
{
mSettingsManager.removeListener(this);
}
mSettingsManager = null;
}
/**
* Sets/changes the DFT bin size
*/
public void setDFTSize(DFTSize size)
{
mDFTSize = size;
}
public void setCursorLocation(Point point)
{
mCursorLocation = point;
repaint();
}
public void setCursorVisible(boolean visible)
{
mCursorVisible = visible;
repaint();
}
/**
* Fetches the color settings from the settings manager
*/
private void setColors()
{
mColorSpectrumCursor = getColor(ColorSettingName.SPECTRUM_CURSOR);
mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE);
mColorSpectrumBackground = getColor(ColorSettingName.SPECTRUM_BACKGROUND);
}
/**
* Fetches a named color setting from the settings manager. If the setting
* doesn't exist, creates the setting using the defaultColor
*/
private Color getColor(ColorSettingName name)
{
ColorSetting setting = mSettingsManager.getColorSetting(name);
return setting.getColor();
}
/**
* Monitors for setting changes. Colors can be changed by external actions
* and will automatically update in this class
*/
@Override
public void settingChanged(Setting setting)
{
if(setting instanceof ColorSetting)
{
ColorSetting colorSetting = (ColorSetting)setting;
switch(colorSetting.getColorSettingName())
{
case SPECTRUM_BACKGROUND:
mColorSpectrumBackground = colorSetting.getColor();
break;
case SPECTRUM_CURSOR:
mColorSpectrumCursor = colorSetting.getColor();
break;
case SPECTRUM_LINE:
mColorSpectrumLine = colorSetting.getColor();
break;
default:
break;
}
}
}
/**
* Renders the channel configs, lines, labels, and cursor
*/
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D graphics = (Graphics2D)g;
graphics.setBackground(mColorSpectrumBackground);
graphics.setRenderingHints(RENDERING_HINTS);
drawFrequencies(graphics);
drawCursor(graphics);
drawPLL(graphics);
drawChannelBandwidth(graphics);
}
/**
* Draws a cursor on the panel, whenever the mouse is hovering over the
* panel
*/
private void drawCursor(Graphics2D graphics)
{
if(mCursorVisible)
{
drawFrequencyLine(graphics, mCursorLocation.x, mColorSpectrumCursor);
String frequency = CURSOR_FORMAT.format(getFrequencyFromAxis(mCursorLocation.getX()) / 1E6D);
FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont());
Rectangle2D rect = fontMetrics.getStringBounds(frequency, graphics);
if(mCursorLocation.y > rect.getHeight())
{
graphics.drawString(frequency, mCursorLocation.x + 5, mCursorLocation.y);
}
}
}
/**
* Draws the frequency lines and labels every 10kHz
*/
private void drawFrequencies(Graphics2D graphics)
{
long minFrequency = getMinDisplayFrequency();
long maxFrequency = getMaxDisplayFrequency();
//Frequency increments for label and tick spacing
int label = mLabelSizeMonitor.getLabelIncrement(graphics);
int major = mLabelSizeMonitor.getMajorTickIncrement(graphics);
int minor = mLabelSizeMonitor.getMinorTickIncrement(graphics);
//Avoid divide by zero error
if(minor == 0)
{
minor = 1;
}
if(label == 0)
{
label = 1;
}
//Adjust the start frequency to a multiple of the minor tick spacing
long frequency = minFrequency - (minFrequency % minor);
while(frequency < maxFrequency)
{
if(frequency % label == 0)
{
drawFrequencyLineAndLabel(graphics, frequency);
}
else if(frequency % major == 0)
{
drawTickLine(graphics, frequency, true);
}
else
{
drawTickLine(graphics, frequency, false);
}
frequency += minor;
}
}
/**
* Draws a vertical line and a corresponding frequency label at the bottom
*/
private void drawFrequencyLineAndLabel(Graphics2D graphics, long frequency)
{
double xAxis = getAxisFromFrequency(frequency);
drawFrequencyLine(graphics, xAxis, mColorSpectrumLine);
drawTickLine(graphics, frequency, false);
graphics.setColor(mColorSpectrumLine);
drawFrequencyLabel(graphics, xAxis, frequency);
}
/**
* Draws a vertical line at the xaxis
*/
private void drawTickLine(Graphics2D graphics, long frequency, boolean major)
{
graphics.setColor(mColorSpectrumLine);
double xAxis = getAxisFromFrequency(frequency);
double start = getSize().getHeight() - mSpectrumInset;
double end = start + (major ? 9.0d : 3.0d);
graphics.draw(new Line2D.Double(xAxis, start, xAxis, end));
}
/**
* Draws a vertical line at the xaxis
*/
private void drawFrequencyLine(Graphics2D graphics, double xaxis, Color color)
{
graphics.setColor(color);
graphics.draw(new Line2D.Double(xaxis, 0.0d, xaxis, getSize().getHeight() - mSpectrumInset));
}
/**
* Draws a vertical line at the xaxis
*/
private void drawChannelCenterLine(Graphics2D graphics, double xaxis)
{
double height = getSize().getHeight() - mSpectrumInset;
graphics.setColor(Color.LIGHT_GRAY);
graphics.draw(new Line2D.Double(xaxis, height * 0.65d, xaxis, height - 1.0d));
}
/**
* Draws the Phase Locked Loop tracking frequency, offset from channel center
*/
private void drawPLL(Graphics2D graphics)
{
if(mPllTrackingFrequency == 0)
{
return;
}
long frequency = mFrequency - mPllTrackingFrequency;
double xAxis = getAxisFromFrequency(frequency);
double height = getSize().getHeight() - mSpectrumInset;
double verticalAxisTop = height * 0.02d;
double verticalAxisBottom = height * 0.98d;
graphics.setColor(Color.YELLOW);
//Vertical band edge lines
graphics.draw(new Line2D.Double(xAxis, verticalAxisTop, xAxis, verticalAxisBottom));
}
/**
* Draws the Phase Locked Loop tracking frequency, offset from channel center
*/
private void drawChannelBandwidth(Graphics2D graphics)
{
if(mChannelBandwidth == 0)
{
return;
}
long minFrequency = mFrequency - (mChannelBandwidth / 2);
long maxFrequency = mFrequency + (mChannelBandwidth / 2);
double height = getSize().getHeight() - mSpectrumInset;
double verticalAxisTop = height * 0.8d;
double verticalAxisBottom = height * 0.98d;
double minXAxis = getAxisFromFrequency(minFrequency);
double maxXAxis = getAxisFromFrequency(maxFrequency);
graphics.setColor(Color.GREEN);
graphics.draw(new Line2D.Double(minXAxis, verticalAxisTop, minXAxis, verticalAxisBottom));
graphics.draw(new Line2D.Double(maxXAxis, verticalAxisTop, maxXAxis, verticalAxisBottom));
}
/**
* Returns the x-axis value corresponding to the frequency
*/
private double getAxisFromFrequency(long frequency)
{
double screenWidth = (double)getSize().getWidth();
double pixelsPerBin = screenWidth / (double)mDFTSize.getSize();
double pixelOffsetToMinDisplayFrequency = pixelsPerBin * 2.0d;
//Calculate frequency offset from the min frequency
double frequencyOffset = (double)(frequency - getMinDisplayFrequency());
//Determine ratio of frequency offset to overall bandwidth
double ratio = frequencyOffset / (double)getDisplayBandwidth();
//Apply the ratio to the screen width minus 1 bin width
double screenOffset = screenWidth * ratio;
return pixelOffsetToMinDisplayFrequency + screenOffset;
}
/**
* Returns the frequency corresponding to the x-axis value using the current
* zoom level.
*/
public long getFrequencyFromAxis(double xAxis)
{
double width = getSize().getWidth();
double offset = xAxis / width;
long frequency = getMinDisplayFrequency() + FastMath.round((double)getDisplayBandwidth() * offset);
if(frequency > (getMaxFrequency()))
{
frequency = getMaxFrequency();
}
return frequency;
}
/**
* Draws a frequency label at the x-axis position, at the bottom of the panel
*/
private void drawFrequencyLabel(Graphics2D graphics, double xaxis, long frequency)
{
String label = mLabelSizeMonitor.format(frequency);
FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont());
Rectangle2D rect = fontMetrics.getStringBounds(label, graphics);
float xOffset = (float)rect.getWidth() / 2;
graphics.drawString(label, (float)(xaxis - xOffset), (float)(getSize().getHeight() - 2.0f));
}
/**
* Frequency change event handler
*/
@Override
public void process(SourceEvent event)
{
switch(event.getEvent())
{
case NOTIFICATION_SAMPLE_RATE_CHANGE:
mBandwidth = event.getValue().intValue();
mLabelSizeMonitor.update();
break;
case NOTIFICATION_FREQUENCY_CHANGE:
mFrequency = event.getValue().longValue();
mLabelSizeMonitor.update();
break;
default:
break;
}
}
public int getBandwidth()
{
return mBandwidth;
}
/**
* Currently displayed minimum frequency
*/
public long getMinFrequency()
{
return mFrequency - (mBandwidth / 2);
}
/**
* Currently displayed maximum frequency
*/
public long getMaxFrequency()
{
return mFrequency + (mBandwidth / 2);
}
public boolean containsFrequency(long frequency)
{
return FastMath.abs(mFrequency - frequency) <= (mBandwidth / 2);
}
private long getMinDisplayFrequency()
{
double bandwidthPerBin = (double)mBandwidth / (double)(mDFTSize.getSize());
return getMinFrequency();
// return getMinFrequency() + (int)((mDFTZoomWindowOffset) * bandwidthPerBin);
}
private long getMaxDisplayFrequency()
{
return getMinDisplayFrequency() + getDisplayBandwidth();
}
private int getDisplayBandwidth()
{
return mBandwidth;
}
@Override
public void settingDeleted(Setting setting) { /* not implemented */ }
/**
* Calculates correct spacing and format for frequency labels and major/minor
* tick lines based on current frequency, bandwidth, zoom and screen size.
*/
public class LabelSizeManager implements ComponentListener
{
private static final double LABEL_FILL_THRESHOLD = 0.5d;
private DecimalFormat mFrequencyFormat = new DecimalFormat("0.0");
private boolean mUpdateRequired = true;
private int mLabelIncrement = 1;
private int mMajorTickIncrement = 1;
private int mMinorTickIncrement = 1;
public String format(long frequency)
{
return mFrequencyFormat.format((double)frequency / 1E6D);
}
private void setPrecision(int precision)
{
if(precision < 1)
{
precision = 1;
}
if(precision > 5)
{
precision = 5;
}
mFrequencyFormat.setMinimumFractionDigits(precision);
mFrequencyFormat.setMaximumFractionDigits(precision);
}
private void update(Graphics2D graphics)
{
if(mUpdateRequired)
{
//Set maximum precision as a starting point
setPrecision(5);
FontMetrics fontMetrics =
graphics.getFontMetrics(FrequencyOverlayPanel.this.getFont());
int maxLabelWidth = fontMetrics.stringWidth(format(getMaxDisplayFrequency()));
double maxLabels = ((double) FrequencyOverlayPanel.this.getWidth() * LABEL_FILL_THRESHOLD) / (double)maxLabelWidth;
//Calculate the next smallest base 10 value for the major increment
int power = (int)FastMath.log10((double)getDisplayBandwidth() / maxLabels);
//Set the number of decimal places to display in frequency labels
int precision = 5 - power;
int start = (int)FastMath.pow(10.0, power + 1);
int minimum = (int)FastMath.pow(10.0, power);
int labelIncrement = start;
while(((double)getDisplayBandwidth() / (double)labelIncrement) < maxLabels && labelIncrement >= minimum)
{
labelIncrement /= 2;
precision++;
}
if(labelIncrement == minimum)
{
precision = 5 - power;
}
setPrecision(precision);
mLabelIncrement = labelIncrement;
mMajorTickIncrement = labelIncrement / 2;
mMinorTickIncrement = labelIncrement / 10;
mUpdateRequired = false;
}
}
/**
* Forces the display to update the label and frequency display
* calculations
*/
public void update()
{
mUpdateRequired = true;
}
public int getMajorTickIncrement(Graphics2D graphics)
{
//Check to see if a calculation update is scheduled
update(graphics);
return mMajorTickIncrement;
}
public int getMinorTickIncrement(Graphics2D graphics)
{
return mMinorTickIncrement;
}
public int getLabelIncrement(Graphics2D graphics)
{
return mLabelIncrement;
}
@Override
public void componentResized(ComponentEvent arg0)
{
update();
}
public void componentHidden(ComponentEvent arg0) {}
public void componentMoved(ComponentEvent arg0) {}
public void componentShown(ComponentEvent arg0) {}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 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
@@ -15,7 +14,7 @@
*
* 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.spectrum;
@@ -33,9 +32,6 @@ import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import org.apache.commons.math3.util.FastMath;
import javax.swing.JPanel;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
@@ -53,6 +49,9 @@ import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.math3.util.FastMath;
import javax.swing.JPanel;
public class OverlayPanel extends JPanel implements Listener<ChannelEvent>, ISourceEventProcessor, SettingChangeListener
{
@@ -226,15 +225,10 @@ public class OverlayPanel extends JPanel implements Listener<ChannelEvent>, ISou
private void setColors()
{
mColorChannelConfig = getColor(ColorSettingName.CHANNEL_CONFIG);
mColorChannelConfigProcessing = getColor(ColorSettingName.CHANNEL_CONFIG_PROCESSING);
mColorChannelConfigSelected = getColor(ColorSettingName.CHANNEL_CONFIG_SELECTED);
mColorSpectrumCursor = getColor(ColorSettingName.SPECTRUM_CURSOR);
mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE);
mColorSpectrumBackground = getColor(ColorSettingName.SPECTRUM_BACKGROUND);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 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
@@ -29,12 +29,6 @@ import io.github.dsheirer.settings.ColorSetting.ColorSettingName;
import io.github.dsheirer.settings.Setting;
import io.github.dsheirer.settings.SettingChangeListener;
import io.github.dsheirer.settings.SettingsManager;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
@@ -45,6 +39,12 @@ import java.awt.RenderingHints;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.util.Arrays;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JPanel;
public class SpectrumPanel extends JPanel implements DFTResultsListener, SettingChangeListener, SpectralDisplayAdjuster
{
@@ -82,7 +82,6 @@ public class SpectrumPanel extends JPanel implements DFTResultsListener, Setting
private int mZoom = 0;
private int mZoomWindowOffset = 0;
;
private SettingsManager mSettingsManager;