mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-11 08:07:24 -05:00
Compare commits
14 Commits
v0.5.0-bet
...
v0.5.1-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e99aa3d3b4 | ||
|
|
ec162ff84c | ||
|
|
f0db2865f2 | ||
|
|
f97b2150b9 | ||
|
|
8ce8aaddf9 | ||
|
|
e4a85a4ed7 | ||
|
|
f61a415b43 | ||
|
|
29ed3b4553 | ||
|
|
a87309e792 | ||
|
|
ddb5b5ef87 | ||
|
|
7a58bf85b6 | ||
|
|
8707eb2c88 | ||
|
|
08499ac73f | ||
|
|
70170ff70f |
@@ -28,7 +28,7 @@ repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.5.0-beta7'
|
||||
version = '0.5.1-beta1'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
sourceSets {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ->
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ) {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user